Combining properties of pcolormesh and imshow - python

An advantage of plt.pcolormesh over plt.imshow is the possibility to have unequal axis spacing.
On the other hand, plt.imshow's advantage over plt.pcolormesh is that it can display RGB-triplets.
Now, the predicament I am in is that I need to plot RGB-triplets with uneven axis spacing....
Below is a MWE:
import numpy as np
import matplotlib.pyplot as plt
from colorsys import hsv_to_rgb
square_x_axis = np.linspace(0,1,100)**2
cube_y_axis = np.linspace(0,1,200)**3
X,Y = np.meshgrid(cube_y_axis,square_x_axis); print(f'meshgrid has shape: {X.shape}')
rgb_array = np.zeros((square_x_axis.size, cube_y_axis.size,3)); print(f'rgb_array has shape: {rgb_array.shape}')
""" Now we populate the rgb array (initially in hsv color space for clarity)"""
for i,row in enumerate(rgb_array):
for j,col in enumerate(row):
rgb_array[i,j,:] = np.array(hsv_to_rgb(0,square_x_axis[i],cube_y_axis[j]))
fig = plt.figure(figsize=(15,10))
imshow_ax = plt.subplot(1,2,1)
imshow_ax.imshow(rgb_array, aspect='auto', extent=[0,1,0,1])
pcolor_R_ax = plt.subplot(3,2,2)
pcolor_R_ax.pcolormesh(X,Y,rgb_array[:,:,0], cmap='Reds')
pcolor_G_ax = plt.subplot(3,2,4)
pcolor_G_ax.pcolormesh(X,Y,rgb_array[:,:,1], cmap='Greens')
pcolor_B_ax = plt.subplot(3,2,6)
pcolor_B_ax.pcolormesh(X,Y,rgb_array[:,:,2], cmap='Blues')
Which produces the following figure:
The problem becomes immediately obvious: imshow (on the left) is capable of representing the 3D array, but its axis are scaled wrong, leading to a distorted representation. pcolormesh (on the right), on the other hand, can not represent the 3D array (hence why I plot all three channels separately), but is capable of applying the axis correctly, leading to no distortion.
How can I combine these properties?

I found another answer here that seems to work on your example, with a small tweak for some new pcolorbesh behaviour (the shading='auto' bit). Try this plot on your data:
fig = plt.figure(figsize=(15,10))
placeholder = rgb_array[..., 0]
colors = rgb_array.reshape(-1, 3)
mesh = plt.pcolormesh(X, Y, placeholder, facecolors=colors, shading='auto')
mesh.set_array(None)
It produces:

#kwinkunks answer is the method that solved my problem:
The original data, using imshow, looked like this, where both the x- and y-axis of the data plot and the colorbar are wrong. Of all 4 axes, only the data y-axis is linear, the 3 other axes are non-linear, and so using imshows's extent option is no good:
Now... taking #kwinkunks answer directly produced the following plot:
...where the axes tickmarks are now as they should be! Amazing!

Related

How do I re-write the axes in matplotlib?

I have a 2D array and it's contents will display correctly as an image when I simply use
img = plt.imshow(full2DArray)
but my problem is that the axes just naively show the number of rows and columns. For example if my 2D array is 53x53 then the axes will count 0-53 on the y-axis and 0-53 on the x-axis.
I need to show the exact same image but have the axes display a linear scale from -130 to +130 instead.
I have a similar answer to this question here but to explain for your case, we can take an array data = np.random.rand(53,53) filled with random values, and plot it with imshow. You simply need to adjust the extent=[<xmin>,<xmax>,<ymin>,<ymax>] parameter, so in the example code:
import numpy as np
import matplotlib.pyplot as plt
data = np.random.rand(53,53)
print(data.shape) # Displays (53,53)
plt.figure()
plt.xlabel("x")
plt.ylabel("y")
plt.imshow(data, origin='lower', aspect='auto',
extent = [-130,130,-130,130], cmap=plt.cm.jet)
plt.colorbar()
plt.show()
We get the following plot with your desired bounds:
If I understand it correctly, you need predifined axis, instead of pyplot infering these from the image.
Setting xlim before calling imshow will do the job.
plt.xlim([-130, 130])
Similarly, you can call ylim for the y axis.

RGB polar plot in Python

I am trying to produce RGB polar plots in Python and I was expecting matplotlib.pyplot.imshow to be able to do it. However, whenever I try plotting data using this method I obtain a blank output.
import matplotlib.pyplot as plt
import numpy as np
data = np.array([[[0,0,1],[0,1,0],[1,0,0]],[[0,0,0.5],[0,0.5,0],[0.5,0,0]]])
# Sample, any N,M,3 data should work
ax = plt.subplot(111,polar=True)
ax.imshow(data,extent=[0,2*np.pi,0,1]) # Produces a white circle
Is there a good way to accomplish this using the aforementioned method or another ?
Thanks.
EDIT: I managed to make a single quadrant by using extent=[0,np.pi/2,0,1] but its use is clearly bugged for polar plots. since anything but a full quadrant doesn't produce the expected outcome.
Using imshow on a polar plot is unfortunately not possible, because the imshow grid is necessarily quadratic in its pixels. You may however use pcolormesh and apply a trick (similar to this one), namely to provide the colors as color argument to pcolormesh, as it would usually just take 2D input.
import matplotlib.pyplot as plt
import numpy as np
data = np.array([[[0,0,1],[0,1,0],[1,0,0]],
[[0,0,0.5],[0,0.5,0],[0.5,0,0]]])
ax = plt.subplot(111, polar=True)
#get coordinates:
phi = np.linspace(0,2*np.pi,data.shape[1]+1)
r = np.linspace(0,1,data.shape[0]+1)
Phi,R = np.meshgrid(phi, r)
# get color
color = data.reshape((data.shape[0]*data.shape[1],data.shape[2]))
# plot colormesh with Phi, R as coordinates,
# and some 2D array of the same shape as the image, except the last dimension
# provide colors as `color` argument
m = plt.pcolormesh(Phi,R,data[:,:,0], color=color, linewidth=0)
# This is necessary to let the `color` argument determine the color
m.set_array(None)
plt.show()
The result is not a circle because you do not have enough points. Repeating the data, data = np.repeat(data, 25, axis=1) would then allow to get a circle.

Wireframe joins the wrong way in numpy matplotlib mplot3d

I'm trying to create a 3D wireframe in Python using matplotlib.
When I get to the actual graph plotting, however, the wireframe joins the wrong way, as shown in the images below.
How can I force matplotlib to join the wireframe along a certain axis?
My code is below:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
def rossler(x_n, y_n, z_n, h, a, b, c):
#defining the rossler function
x_n1=x_n+h*(-y_n-z_n)
y_n1=y_n+h*(x_n+a*y_n)
z_n1=z_n+h*(b+z_n*(x_n-c))
return x_n1,y_n1,z_n1
#defining a, b, and c
a = 1.0/5.0
b = 1.0/5.0
c = 5
#defining time limits and steps
t_0 = 0
t_f = 32*np.pi
h = 0.01
steps = int((t_f-t_0)/h)
#3dify
c_list = np.linspace(5,10,6)
c_size = len(c_list)
c_array = np.zeros((c_size,steps))
for i in range (0, c_size):
for j in range (0, steps):
c_array[i][j] = c_list[i]
#create plotting values
t = np.zeros((c_size,steps))
for i in range (0, c_size):
t[i] = np.linspace(t_0,t_f,steps)
x = np.zeros((c_size,steps))
y = np.zeros((c_size,steps))
z = np.zeros((c_size,steps))
binvar, array_size = x.shape
#initial conditions
x[0] = 0
y[0] = 0
z[0] = 0
for j in range(0, c_size-1):
for i in range(array_size-1):
c = c_list[j]
#re-evaluate the values of the x-arrays depending on the initial conditions
[x[j][i+1],y[j][i+1],z[j][i+1]]=rossler(x[j][i],y[j][i],z[j][i],t[j][i+1]-t[j][i],a,b,c)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_wireframe(t,x,c_array, rstride=10, cstride=10)
plt.show()
I am getting this as an output:
The same output from another angle:
Whereas I'd like the wireframe to join along the wave-peaks. Sorry, I can't give you an image I'd like to see, that's my problem, but I guess it'd be more like the tutorial image.
If I understood, you want to link the 6 traces with polygons. You can do that by triangulating the traces 2 by 2, then plotting the surface with no edges or antialising. Maybe choosing a good colormap will also help.
Just keep in mind that this will be a very heavy plot. The exported SVG weight 10mb :)
import matplotlib.tri as mtri
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for LineIndex in range(c_size-1):
# If plotting all at once, you get a MemoryError. I'll plot each 6 points
for Sample in range(0, array_size-1, 3):
# I switched x and c_array, because the surface and the triangles
# will look better by default
X = np.concatenate([t[LineIndex,Sample:Sample+3], t[LineIndex+1,Sample:Sample+3]])
Y = np.concatenate([c_array[LineIndex,Sample:Sample+3], c_array[LineIndex+1,Sample:Sample+3]])
Z = np.concatenate([x[LineIndex,Sample:Sample+3], x[LineIndex+1,Sample:Sample+3]])
T = mtri.Triangulation(X, Y)
ax.plot_trisurf(X, Y, Z, triangles=T.triangles, edgecolor='none', antialiased=False)
ax.set_xlabel('t')
ax.set_zlabel('x')
plt.savefig('Test.png', format='png', dpi=600)
plt.show()
Here is the resulting image:
I'm quite unsure about what you're exactly trying to achieve, but I don't think it will work.
Here's what your data looks like when plotted layer by layer (without and with filling):
You're trying to plot this as a wireframe plot. Here's how a wireframe plot looks like as per the manual:
Note the huge differene: a wireframe plot is essentially a proper surface plot, the only difference is that the faces of the surface are fully transparent. This also implies that you can only plot
single-valued functions of the form z(x,y), which are furthermore
specified on a rectangular mesh (at least topologically)
Your data is neither: your points are given along lines, and they are stacked on top of each other, so there's no chance that this is a single surface that can be plotted.
If you just want to visualize your functions above each other, here's how I plotted the above figures:
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for zind in range(t.shape[0]):
tnow,xnow,cnow = t[zind,:],x[zind,:],c_array[zind,:]
hplot = ax.plot(tnow,xnow,cnow)
# alternatively fill:
stride = 10
tnow,xnow,cnow = tnow[::stride],xnow[::stride],cnow[::stride]
slice_from = slice(None,-1)
slice_to = slice(1,None)
xpoly = np.array([tnow[slice_from],
tnow[slice_to],
tnow[slice_to],
tnow[slice_from]]
).T
ypoly = np.array([xnow[slice_from],
xnow[slice_to],
np.zeros_like(xnow[slice_to]),
np.zeros_like(xnow[slice_from])]
).T
zpoly = np.array([cnow[slice_from],
cnow[slice_to],
cnow[slice_to],
cnow[slice_from]]
).T
tmppoly = [tuple(zip(xrow,yrow,zrow)) for xrow,yrow,zrow in zip(xpoly,ypoly,zpoly)]
poly3dcoll = Poly3DCollection(tmppoly,linewidth=0.0)
poly3dcoll.set_edgecolor(hplot[0].get_color())
poly3dcoll.set_facecolor(hplot[0].get_color())
ax.add_collection3d(poly3dcoll)
plt.xlabel('t')
plt.ylabel('x')
plt.show()
There is one other option: switching your coordinate axes, such that the (x,t) pair corresponds to a vertical plane rather than a horizontal one. In this case your functions for various c values are drawn on parallel planes. This allows a wireframe plot to be used properly, but since your functions have extrema in different time steps, the result is as confusing as your original plot. You can try using very few plots along the t axis, and hoping that the extrema are close. This approach needs so much guesswork that I didn't try to do this myself. You can plot each function as a filled surface instead, though:
from matplotlib.collections import PolyCollection
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for zind in range(t.shape[0]):
tnow,xnow,cnow = t[zind,:],x[zind,:],c_array[zind,:]
hplot = ax.plot(tnow,cnow,xnow)
# alternative to fill:
stride = 10
tnow,xnow,cnow = tnow[::stride],xnow[::stride],cnow[::stride]
slice_from = slice(None,-1)
slice_to = slice(1,None)
xpoly = np.array([tnow[slice_from],
tnow[slice_to],
tnow[slice_to],
tnow[slice_from]]
).T
ypoly = np.array([xnow[slice_from],
xnow[slice_to],
np.zeros_like(xnow[slice_to]),
np.zeros_like(xnow[slice_from])]
).T
tmppoly = [tuple(zip(xrow,yrow)) for xrow,yrow in zip(xpoly,ypoly)]
polycoll = PolyCollection(tmppoly,linewidth=0.5)
polycoll.set_edgecolor(hplot[0].get_color())
polycoll.set_facecolor(hplot[0].get_color())
ax.add_collection3d(polycoll,zdir='y',zs=cnow[0])
hplot[0].set_color('none')
ax.set_xlabel('t')
ax.set_zlabel('x')
plt.show()
This results in something like this:
There are a few things to note, however.
3d scatter and wire plots are very hard to comprehend, due to the lacking depth information. You might be approaching your visualization problem in a fundamentally wrong way: maybe there are other options with which you can visualize your data.
Even if you do something like the plots I showed, you should be aware that matplotlib has historically been failing to plot complicated 3d objects properly. Now by "properly" I mean "with physically reasonable apparent depth", see also the mplot3d FAQ note describing exactly this. The core of the problem is that matplotlib projects every 3d object to 2d, and draws these pancakes on the sreen one after the other. Sometimes the asserted drawing order of the pancakes doesn't correspond to their actual relative depth, which leads to artifacts that are both very obvious to humans and uncanny to look at. If you take a closer look at the first filled plot in this post, you'll see that the gold flat plot is behind the magenta one, even though it should be on top of it. Similar things often happen with 3d bar plots and convoluted surfaces.
When you're saying "Sorry, I can't give you an image I'd like to see, that's my problem", you're very wrong. It's not just your problem. It might be crystal clear in your head what you're trying to achieve, but unless you very clearly describe what you see in your head, the outside world will have to resort to guesswork. You can make the work of others and yourself alike easier by trying to be as informative as possible.

How to change the orientation of axis to a direction given by tuple in matplotlib

For example the orientation of histogram in the picture below is (2,-2)
Use transformations. Since you did not provide any code that would plot the non-rotated picture, I'm using a simple example:
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy
n = numpy.random.normal(size=10000)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.set_aspect(1)
ax.set_xlim(-4, 4)
ax.set_ylim(-4, 4)
base_trans = ax.transData
tr = matplotlib.transforms.Affine2D().rotate_deg(-30) + base_trans
ax.hist(n, normed=True, transform=tr, bins=20)
fig.savefig('t.png')
Notes:
I do not know what you mean by a "direction given by a tuple". In your picture the axes are clearly not just rotated, but moved as well (the (0,0) point is not on the x-axis). I only used rotation in this example; see docs for Affine2D for more transformation properties.
In order for your graph to not look skewed, you must match the plot's aspect ratio, x/y limits, and the transformation's scaling coefficients. In the example I used the aspect 1 and the same scale for x and y axes, so I could just use the rotate_deg() method without any additional corrections.

plotting coordinate as a matrix matplotlib python

I have a set of coordinates, say [(2,3),(45,4),(3,65)]
I need to plot them as a matrix is there anyway I can do this in matplotlib so I want it to have this sort of look http://imgur.com/Q6LLhmk
Edit: My original answer used ax.scatter. There is a problem with this: If two points are side-by-side, ax.scatter may draw them with a bit of space in between, depending on the scale:
For example, with
data = np.array([(2,3),(3,3)])
Here is a zoomed-in detail:
So here is a alternative solution that fixes this problem:
import matplotlib.pyplot as plt
import numpy as np
data = np.array([(2,3),(3,3),(45,4),(3,65)])
N = data.max() + 5
# color the background white (1 is white)
arr = np.ones((N,N), dtype = 'bool')
# color the dots black (0)
arr[data[:,1], data[:,0]] = 0
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.imshow(arr, interpolation='nearest', cmap = 'gray')
ax.invert_yaxis()
# ax.axis('off')
plt.show()
No matter how much you zoom in, the adjacent squares at (2,3) and (3,3) will remain side-by-side.
Unfortunately, unlike ax.scatter, using ax.imshow requires building an N x N array, so it could be more memory-intensive than using ax.scatter. That should not be a problem unless data contains very large numbers, however.

Categories