How do I plot a contour from a table of values? - matplotlib

I have a table that has 2 features (x,y) - and a vector with the same length that contains their corresponding values (z).
I'm trying to use matplotlib to print this as a 2D plot but I am get an error:
TypeError: Input z must be at least a (2, 2) shaped array, but has shape (5797, 1)
Is there any way to solve this? (since I am trying to use 1d arrays instead of 2d arrays)
The relevant code:
x, y = train_features[:,0], train_features[:,1]
z = train_predictions.detach()
print(x.size())
print(y.size())
print(z.size())
plt.figure()
CS = plt.contour(x, y, z)
CS = plt.contourf(x, y, z)
plt.clabel(CS, fontsize=8, colors='black')
cbar = plt.colorbar(CS)
The prints that result from the prints commands:
torch.Size([5797])
torch.Size([5797])
torch.Size([5797, 1])
EDIT:
I tried to implement this with a second method:
import matplotlib.pyplot as plt
import matplotlib.tri as tri
import numpy as np
npts = 200
ngridx = 100
ngridy = 200
x = train_features[:,0]
y = train_features[:,1]
z = train_predictions.detach().squeeze()
fig, ax1 = plt.subplots()
# -----------------------
# Interpolation on a grid
# -----------------------
# A contour plot of irregularly spaced data coordinates
# via interpolation on a grid.
# Create grid values first.
xi = np.linspace(1, 10, ngridx)
yi = np.linspace(1, 10, ngridy)
# Perform linear interpolation of the data (x,y)
# on a grid defined by (xi,yi)
triang = tri.Triangulation(x, y)
interpolator = tri.LinearTriInterpolator(triang, z)
Xi, Yi = np.meshgrid(xi, yi)
zi = interpolator(Xi, Yi)
ax1.contour(xi, yi, zi, levels=100, linewidths=0.5, colors='k')
cntr1 = ax1.contourf(xi, yi, zi, levels=14, cmap="RdBu_r")
fig.colorbar(cntr1, ax=ax1)
ax1.plot(x, y, 'ko', ms=3)
ax1.set_title('grid and contour (%d points, %d grid points)' %
(npts, ngridx * ngridy))
But the resulting image was the following:
even though z's values are:
tensor([-0.2434, -0.2155, -0.1900, ..., 64.7516, 65.2064, 65.6612])

Related

Surface Plot of a function B(x,y,z)

I have to plot a surface plot which has axes x,y,z and a colormap set by a function of x,y,z [B(x,y,z)].
I have the coordinate arrays:
x=np.arange(-100,100,1)
y=np.arange(-100,100,1)
z=np.arange(-100,100,1)
Moreover, my to-be-colormap function B(x,y,z) is a 3D array, whose B(x,y,z)[i] elements are the (x,y) coordinates at z.
I have tried something like:
Z,X,Y=np.meshgrid(z,x,y) # Z is the first one since B(x,y,z)[i] are the (x,y) coordinates at z.
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
img = ax.scatter(Z, X, Y, c=B(x,y,z), cmap=plt.hot())
fig.colorbar(img)
plt.show()
However, it unsurprisingly plots dots, which is not what I want. Rather, I need a surface plot.
The figure I have obtained:
The kind of figure I want:
where the colors are determined by B(x,y,z) for my case.
You have to:
use plot_surface to create a surface plot.
your function B(x, y, z) will be used to compute the color parameter, a number assigned to each face of the surface.
the color parameter must be normalized between 0, 1. We use matplotlib's Normalize to achieve that.
then, you create the colors by applying the colormap to the normalized color parameter.
finally, you create the plot.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from matplotlib.colors import Normalize
t = np.linspace(0, 2*np.pi)
p = np.linspace(0, 2*np.pi)
t, p = np.meshgrid(t, p)
r1, r2 = 1, 3
x = (r2 + r1 * np.cos(t)) * np.cos(p)
y = (r2 + r1 * np.cos(t)) * np.sin(p)
z = r1 * np.sin(t)
color_param = np.sin(x / 2) * np.cos(y) + z
cmap = cm.jet
norm = Normalize(vmin=color_param.min(), vmax=color_param.max())
norm_color_param = norm(color_param)
colors = cmap(norm_color_param)
fig = plt.figure()
ax = fig.add_subplot(projection="3d")
ax.plot_surface(x, y, z, facecolors=colors)
ax.set_zlim(-4, 4)
plt.show()

Is the pyplot contour plot using bin centers or bin edges?

I have trouble understanding the documentation for matplotlib.pyplot.contour (https://matplotlib.org/3.5.0/api/_as_gen/matplotlib.pyplot.contour.html). The documentation says that the arguments for contour are X,Y-coordinates and Z heights.
However, my plot is offset by a half bin width. So it seems to me as if the contour plot needs x_edges and y_edges instead. Could someone help me understand how this works and what is the proper way to use this function?
Documentation says this:
X, Y: array-like, optional
The coordinates of the values in Z.
X and Y must both be 2D with the same shape as Z (e.g. created via numpy.meshgrid), or they must both be 1-D such that len(X) == N is the number of columns in Z and len(Y) == M is the number of rows in Z. [...]
Z(M, N): array-like
The height values over which the contour is drawn.
This is my code:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
values = np.tile(3,(10,2)) # array of [[3,3] ... [3,3]]
X = Y = np.arange(0.5, 6.5, 1) # 0.5 ... 5.5
edges = np.arange(0, 7, 1) # 0, 1, 2, 3, 4, 5, 6
Z, xedges, yedges = np.histogram2d(values[0],
values[1],
bins=(edges, edges))
# len(edges) == 7; len(X) == len(Y) == 6
# Z.shape == (6, 6)
# contour reference says use "The coordinates of the values in Z."
c = ax.contour(X, Y, Z)
plt.savefig('peak_at_3p5.png')
plt.cla()
# however I only get the intended result when using edges
c = ax.contour(xedges[:-1], yedges[:-1], Z)
plt.savefig('peak_at_3p0.png')
Here's the result:
The two figures that the code produces
I also tried using numpy.meshgrid to construct X and Y as 2D arrays but the peak in the figure is still offset by a half bin width.

To make value interpolation on cylindrical surface

I have a issue to interpolate my values "c" on cylindrical surface.
The problem is that possibly I dont understand how to indicate surface for gridding with gridddata function..
>import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import griddata as gd
#Creating data in polar coordinates
phi,d = np.linspace(0, 2* np.pi, 20), np.linspace(0,20,20)
PHI,D = np.meshgrid(phi,d)
R = 2
#Transforming in X Y Z coordinates
X = R * np.cos(PHI)
Y = R * np.sin(PHI)
Z = R * D
T=np.linspace(0,10,400)
c=np.sin(T)*np.cos(T/2) #Value c I would like to interpolate
fig1 = plt.figure()
ax = fig1.add_subplot(1,1,1, projection='3d')
xi=np.array(np.meshgrid(X,Y,Z))
img = ax.scatter(X, Y, Z,c=c, cmap=plt.hot()) #To plot data scatter before interpolation
fig1.colorbar(img)
plt.show()
X1,Y1,Z1 =np.meshgrid(X ,Y ,Z) #To define sufrace for interpolation
int = gd((X,Y,Z), c, (X1,Y1,Z1), method='linear')
fig2 = plt.figure() #trying to plot the answer
ax1 = fig2.add_subplot(1,1,1, projection='3d')
ax1.scatter(int)
img = ax1.scatter(X, Y, Z, c=c, cmap=plt.hot())
`
Its gives error: different number of values and points
I dont know how to indicate (X1,Y1,Z1) surface in griddata function
Thanks a lot for any tips ...

matplotlib heatmap, customize y axis

Right now my code looks like this:
#generate 262*20 elements
values = np.random.random(262*20).tolist()
# convert the list to a 2D NumPy array
values = np.array(values).reshape((262, 20))
h, w = values.shape
#h=262, w=20
fig = plt.figure(num=None, dpi=80,figsize=(9, 7), facecolor='w', edgecolor='k')
ax = fig.add_subplot(111)
#fig, ax = plt.subplots()
plt.imshow(values)
plt.colorbar()
plt.xticks(np.arange(w), list('PNIYLKCVFWABCDEFGHIJ'))
ax.set_aspect(w/h)
plt.show()
The plot looks like this:
As you can see, the range of y axis is 0-261.
But I want my y axis to go from 26 to 290, missing 57, 239, and 253. So still 262 in total. I tried to generate a list like this:
mylist =[26, 27, ......missing 57, 239, 253, ....290]
plt.yticks(np.arange(h), mylist)
The Y axis just looks like everything squished together.
So I tried:
pylab.ylim([26, 290])
And It looks like this:
So it just feels like the data in first row always corresponds to [0], not to [26]
Suggest you use pcolormesh. If you want gaps, then use an numpy.ma.masked array for the area with gaps.
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import numpy as np
values = np.random.rand(290,20)
values[:26, :] = np.NaN
values[ [57, 239, 253], :] = np.NaN
values = np.ma.masked_invalid(values)
h, w = values.shape
fig, ax = plt.subplots(figsize=(9,7))
# Make one larger so these values represent the edge of the data pixels.
y = np.arange(0, 290.5)
x = np.arange(0, 20.5)
pcm = ax.pcolormesh(x, y, values, rasterized=True) # you don't need rasterized=True
fig.colorbar(pcm)
plt.xticks(np.arange(w), list('PNIYLKCVFWABCDEFGHIJ'))
plt.show()
Result
EDIT: If you want to just work w/ a 262x20 array:
values = np.random.rand(262,20)
h, w = values.shape
fig, ax = plt.subplots(figsize=(9,7))
# Make one larger so these values represent the edge of the data pixels.
y = np.arange(0, 290.5)
y = np.delete(y, [57, 239, 253])
y = np.delete(y, range(26))
x = np.arange(0, 20.5)
pcm = ax.pcolormesh(x, y, values, rasterized=True) # you don't need rasterized=True
fig.colorbar(pcm)
plt.xticks(np.arange(w), list('PNIYLKCVFWABCDEFGHIJ'))
plt.show()
Note that this doesn't put a blank line at 57, 239 and 253. If you want that, you need to do:
values = np.random.rand(262,20)
Z = np.ones((290, 20)) * np.NaN
inds = set(range(290)) - set(list(range(26)) + [57, 239, 253])
for nn, ind in enumerate(inds):
Z[ind, :] = values[nn,:]
h, w = values.shape
fig, ax = plt.subplots(figsize=(9,7))
# Make one larger so these values represent the edge of the data pixels.
y = np.arange(0, 290.5)
x = np.arange(0, 20.5)
pcm = ax.pcolormesh(x, y, Z, rasterized=True) # you don't need rasterized=True
fig.colorbar(pcm)
plt.xticks(np.arange(w), list('PNIYLKCVFWABCDEFGHIJ'))
plt.show()

Matplotlib darker hsv colormap

I'm using the HSV colormap from matplotlib to plot some vector fields. Is there a way to darken or make smoother the HSV colours so they look more like this
than my original plot colours, which are too bright:
Introduction
Assuming you're trying to plot a pcolor image like this:
import numpy as np
import matplotlib.pyplot as plt
y, x = np.mgrid[slice(-3, 3 + 0.05, 0.05),
slice(-3, 3 + 0.15, 0.15)]
z = (1 - x / 2. + x ** 5 + y ** 3) * np.exp(-x ** 2 - y ** 2)
# x and y are bounds, so z should be the value *inside* those bounds.
# Therefore, remove the last value from the z array.
z = z[:-1, :-1]
fig = plt.figure(1)
fig.clf()
ax = plt.gca()
pcol = ax.pcolormesh(x, y, z, cmap=plt.get_cmap('hsv'), )
plt.colorbar(pcol)
ax.set_xlim([-3, 3])
ax.set_ylim([-3, 3])
Your image will be:
Methods
I've written an alternate implementation of the MPL cookbook cmap_map function that modifies colormaps. In addition to support for kwargs and pep8 compliance, this version handles discontinuities in a colormap:
import numpy as np
from matplotlib.colors import LinearSegmentedColormap as lsc
def cmap_map(function, cmap, name='colormap_mod', N=None, gamma=None):
"""
Modify a colormap using `function` which must operate on 3-element
arrays of [r, g, b] values.
You may specify the number of colors, `N`, and the opacity, `gamma`,
value of the returned colormap. These values default to the ones in
the input `cmap`.
You may also specify a `name` for the colormap, so that it can be
loaded using plt.get_cmap(name).
"""
if N is None:
N = cmap.N
if gamma is None:
gamma = cmap._gamma
cdict = cmap._segmentdata
# Cast the steps into lists:
step_dict = {key: map(lambda x: x[0], cdict[key]) for key in cdict}
# Now get the unique steps (first column of the arrays):
step_list = np.unique(sum(step_dict.values(), []))
# 'y0', 'y1' are as defined in LinearSegmentedColormap docstring:
y0 = cmap(step_list)[:, :3]
y1 = y0.copy()[:, :3]
# Go back to catch the discontinuities, and place them into y0, y1
for iclr, key in enumerate(['red', 'green', 'blue']):
for istp, step in enumerate(step_list):
try:
ind = step_dict[key].index(step)
except ValueError:
# This step is not in this color
continue
y0[istp, iclr] = cdict[key][ind][1]
y1[istp, iclr] = cdict[key][ind][2]
# Map the colors to their new values:
y0 = np.array(map(function, y0))
y1 = np.array(map(function, y1))
# Build the new colormap (overwriting step_dict):
for iclr, clr in enumerate(['red', 'green', 'blue']):
step_dict[clr] = np.vstack((step_list, y0[:, iclr], y1[:, iclr])).T
return lsc(name, step_dict, N=N, gamma=gamma)
Implementation
To use it, simply define a function that will modify your RGB colors as you like (values from 0 to 1) and supply it as input to cmap_map. To get colors close to the ones in the images you provided, for example, you could define:
def darken(x, ):
return x * 0.8
dark_hsv = cmap_map(darken, plt.get_cmap('hsv'))
And then modify the call to pcolormesh:
pcol = ax.pcolormesh(x, y, z, cmap=dark_hsv)
If you only wanted to darken the greens in the image, you could do (now all in one line):
pcol = ax.pcolormesh(x, y, z,
cmap=cmap_map(lambda x: x * [1, 0.7, 1],
plt.get_cmap('hsv'))
)