Filled contour using class labels - python

I'm having a 2D grid of points where each of the points have a corresponding label, which is in the range [0.0, 5.0]. Now I want to do the following:
Plot all points in the grid and color them according to their label.
However, I don't want to do this using a scatter plot. I've tried to do it with a contourf and pcolormesh plot:
import matplotlib.pyplot as plt
np.random.seed(1234)
x = np.linspace(-1.0, 1.0, num=5)
xx, yy = np.meshgrid(x, x)
z = np.random.randint(low=0, high=6, size=xx.shape)
levels = np.arange(0, 6)
fig, axes = plt.subplots(nrows=2, ncols=2)
axes[0, 0].contourf(xx, yy, z)
axes[0, 1].contour(xx, yy, z, colors='k')
axes[1, 0].scatter(xx, yy, marker='.', c=z)
axes[1, 1].pcolormesh(xx, yy, z)
plt.show()
How should I specify the levels of the contourf plot such that I get contour lines separating the labels. (Similar to the pcolormesh plot)
Additionally, how can I fix the color for every label, i.e label 4 should always have color red?
EDIT: This is an example of a contourf plot which produces too many coloured areas:
Actually, there are only two labels in the grid. However, at the border between the two areas, several additional contour lines are drawn.
For the example above, there should be a single contour line separating the two areas (cyan and blue)
I appreciate any help.

Possibly you simply forgot to provide the levels you want to show. For N labels, one would need 7 levels, e.g. for labels [0 1 2 3 4 5] one would choose levels such that the labels are in the middle of the level interval, [-0.5 0.5 1.5 2.5 3.5 4.5 5.5].
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors
np.random.seed(1234)
x = np.linspace(-1.0, 1.0, num=5)
xx, yy = np.meshgrid(x, x)
z = np.random.randint(low=0, high=6, size=xx.shape)
levels = np.arange(0, z.max()+2)-0.5
fig, ax = plt.subplots()
im = ax.contourf(xx, yy, z, levels=levels)
fig.colorbar(im, ax=ax, ticks=np.unique(z))
ax.contour(xx, yy, z, colors='k',levels=levels)
ax.scatter(xx, yy, marker='.', c=z)
plt.show()
Note that the colors of the contourf plot are slightly different than those of the scatter. The reason is explained in the answer to the question: How does pyplot.contourf choose colors from a colormap?

If I understand you correctly, you want something like the pcolormesh plot, but with only the outlines. One way to achieve this is to extend (or widen) your array such that it contains the same value many times in x and y direction. This basically means that your z consists of many plateaus with very steep gradients in between. You can do this easily with np.repeat. Below I show an example where each point in the original data is expanded to a 20x20 plateau.
The colours of the plots can be fixed by creating a custom colormap. In your case, using a ListedColormap should be enough. When using contour, you also have to specify the levels at which the contours should be drawn in order to make it work correctly.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import colors
cmap = colors.ListedColormap([
'royalblue', 'cyan','yellow', 'orange', 'red', 'green'
])
np.random.seed(1234)
num = 5
x = np.linspace(-1.0, 1.0, num=num)
xx, yy = np.meshgrid(x, x)
z = np.random.randint(low=0, high=6, size=xx.shape)
levels = np.arange(0, 6)
fig, axes = plt.subplots(nrows=2, ncols=2)
axes[0, 0].contourf(xx, yy, z)
axes[0, 1].contour(xx, yy, z, colors='k')
axes[1, 0].scatter(xx, yy, marker='.', c=z)
axes[1, 1].pcolormesh(xx, yy, z, cmap=cmap) ##I added here the custom colormap for reference
##expanding the arrays
N = 20
x1 = np.linspace(-1.0, 1.0, num=N*num)
xx1, yy1 = np.meshgrid(x1,x1)
z1 = np.repeat(np.repeat(z, N,axis=0),N).reshape(xx1.shape)
fig2, ax2 = plt.subplots()
ax2.contour(xx1, yy1, z1, cmap=cmap, levels = levels)
plt.show()
produces this kind of plot:
As you can see, the lines are still not quite straight and sometimes two lines next to each other can be seen. This is because the gradients between different plateaus are not equal. I ran another example using N=200, in which case the lines are much straighter:
Hope this helps.

Related

Matplotlib: Scatter plot in a loop over set of arrays with consistent scatter point size and color bar

I am trying to generate a scatter plot using dataframe series x & y and the size of the scatter data point using dataframe series z.
I should mention that I iterate through a set of each x,y, and z arrays and add the color plot outside the loop.
I see that the scatter sizes and color bar are generated at each iteration therefore scatter sizes are not consistent with all data points in the plot and also with the colorbar at the end. How do I solve this?
fig, ax = plt.subplots()
for x, y, z in arrays_of_xyz:
splot = ax.scatter(x.to_numpy(), y.to_numpy(), marker= 'o', s = z.to_numpy(), cmap ='viridis_r', c = z.to_numpy())
fig.tight_layout()
plt.colorbar(splot)
plt.show()
Gautham
Can't see in which way the sizes in the plot are inconsistent.
The colorbar can be inconsistent if you do not enforce consistent vmin and vmax when calling scatter.
Can you please try with the following code and tell more about inconsistencies you got:
import numpy as np
import matplotlib.pyplot as plt
num_sets = 3
colors = ("red", "green", "blue")
num_pts_per_set = 20
xs = np.random.randn(num_sets, num_pts_per_set)
ys = np.random.randn(num_sets, num_pts_per_set)
zs = (
np.random.rand(num_sets, num_pts_per_set)
* np.arange(1, num_sets + 1).reshape(-1, 1)
* 30
)
zmin = zs.min()
zmax = zs.max()
fig, (ax1, ax2) = plt.subplots(ncols=2)
ax1.set_title("Sizes according to z\nColors according to set #")
for i, (x, y, z, clr) in enumerate(zip(xs, ys, zs, colors)):
ax1.scatter(x, y, marker="o", s=z, c=clr, label=f"Set #{i}")
ax1.legend()
ax2.set_title("Facecolors according to z\nSizes according to set #")
for i, (x, y, z, clr) in enumerate(zip(xs, ys, zs, colors)):
splot = ax2.scatter(x, y, marker="o", c=z, edgecolors=clr, s=(i+1)*30, vmin=zmin, vmax=zmax, label=f"Set #{i}")
ax2.legend()
fig.colorbar(splot)
plt.show()

how to set the grid when using pcolormesh

I am using pcolormesh to create a grid that overlaps a 2dhistogram.
import matplotlib.pyplot as plt
import numpy as np
import random
x = [random.randrange(1,161,1) for _ in range (10)]
y = [random.randrange(1,121,1) for _ in range (10)]
fig, ax = plt.subplots()
ax.set_xlim(0,160)
ax.set_ylim(0,120)
zi, yi, xi = np.histogram2d(y, x, bins=(50,120))
zi = np.ma.masked_equal(zi, 0)
ax.pcolormesh(xi, yi, zi, edgecolors='black')
scat = ax.scatter(x, y, s=2)
Although, this code only produces a grid that covers the outermost xy data points.
I'd like the grid to be constant with the set axes limits (x = 0,160), (y = 0,120). So The grid is constantly covering the plotted area. From 0,0 to 160,120.
I have tried to use the vmin, vmax function in pcolormesh. But this just produces a blank figure. I don't get an error code though?
ax.pcolormesh(xi, yi, zi, edgecolors='black', vmin = (0,0), vmax = (120,160))
Is there another way to extend the grid to the desired axes limits?
One problem is that the histogram2d function determines the bins itself if you use it like you do.
This means that both the offset and the width of your bins is unclear until runtime because they depend on your random points rather than on your axis limits. Now once the bins are found you could read back their shape and set an axis grid accordingly. But it's easier to create your own bins so you get a grid that spans the whole axis ranges.
Then you can set the edges of your bins as minor ticks and enable a grid on them.
Using the lines created by pcolormesh would work too but when using it you will get some lines that are thicker than others (this has to do with line positions falling between pixels). With axis grid this doesn't happen but some lines appear to cut through your bins. In the end it's a matter of taste which one you prefer. You can always play around with edgecolor and linewidth until pcolormesh shows a decent result.
import matplotlib.pyplot as plt
import numpy as np
import random
x = [random.randrange(1,161,1) for _ in range (10)]
y = [random.randrange(1,121,1) for _ in range (10)]
fig, ax = plt.subplots()
ax.set_xlim(0,160)
ax.set_ylim(0,120)
bins = [
np.linspace(*ax.get_xlim(), 120),
np.linspace(*ax.get_ylim(), 50)
]
# Note that I switched back to x, y and used zi.T later which I find
# more readable
zi, xi, yi = np.histogram2d(x, y, bins=bins)
zi = np.ma.masked_equal(zi, 0)
# Either use the next four lines for axis grid
ax.pcolormesh(xi, yi, zi.T)
ax.set_xticks(bins[0], minor=True)
ax.set_yticks(bins[1], minor=True)
ax.grid(True, which='minor')
# or use the next line to stick with edges drawn by pcolormesh
# ax.pcolormesh(xi, yi, zi.T, edgecolor='black')
scat = ax.scatter(x, y, s=2)

Dynamic marker colour in matplotlib

I have two lists containing the x and y coordinates of some points. There is also a list with some values assigned to each of those points. Now my question is, I can always plot the points (x,y) using markers in python. Also I can select colour of the marker manually (as in this code).
import matplotlib.pyplot as plt
x=[0,0,1,1,2,2,3,3]
y=[-1,3,2,-2,0,2,3,1]
colour=['blue','green','red','orange','cyan','black','pink','magenta']
values=[2,6,10,8,0,9,3,6]
for i in range(len(x)):
plt.plot(x[i], y[i], linestyle='none', color=colour[i], marker='o')
plt.axis([-1,4,-3,4])
plt.show()
But is it possible to choose a colour for the marker marking a particular point according to the value assigned to that point (using cm.jet, cm.gray or similar other color schemes) and provide a colorbar with the plot ?
For example, this is the kind of plot I am looking for
where the red dots denote high temperature points and the blue dots denote low temperature ones and others are for temperatures in between.
You are most likely looking for matplotlib.pyplot.scatter. Example:
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
# Generate data:
N = 10
x = np.linspace(0, 1, N)
y = np.linspace(0, 1, N)
x, y = np.meshgrid(x, y)
colors = np.random.rand(N, N) # colors for each x,y
# Plot
circle_size = 200
cmap = matplotlib.cm.viridis # replace with your favourite colormap
fig, ax = plt.subplots(figsize=(4, 4))
s = ax.scatter(x, y, s=circle_size, c=colors, cmap=cmap)
# Prettify
ax.axis("tight")
fig.colorbar(s)
plt.show()
Note: viridis may fail on older version of matplotlib.
Resulting image:
Edit
scatter does not require your input data to be 2-D, here are 4 alternatives that generate the same image:
import matplotlib
import matplotlib.pyplot as plt
x = [0,0,1,1,2,2,3,3]
y = [-1,3,2,-2,0,2,3,1]
values = [2,6,10,8,0,9,3,6]
# Let the colormap extend between:
vmin = min(values)
vmax = max(values)
cmap = matplotlib.cm.viridis
norm = matplotlib.colors.Normalize(vmin=vmin, vmax=vmax)
fig, ax = plt.subplots(4, sharex=True, sharey=True)
# Alternative 1: using plot:
for i in range(len(x)):
color = cmap(norm(values[i]))
ax[0].plot(x[i], y[i], linestyle='none', color=color, marker='o')
# Alternative 2: using scatter without specifying norm
ax[1].scatter(x, y, c=values, cmap=cmap)
# Alternative 3: using scatter with normalized values:
ax[2].scatter(x, y, c=cmap(norm(values)))
# Alternative 4: using scatter with vmin, vmax and cmap keyword-arguments
ax[3].scatter(x, y, c=values, vmin=vmin, vmax=vmax, cmap=cmap)
plt.show()

Applying colormaps to custom axis in Matplotlib 3D surface

I have timeseries data which I've segmented into hundreds of chunks. I solved the autocorrelation for each segment and plotted them:
# plot superimposed
fig = plt.figure()
color = iter(plt.cm.Set2(np.linspace(0,1,num_segs)))
seg_iterator = df.iterrows()
for index, seg in seg_iterator: # iterate over dataframe
c=next(color)
sns.plt.plot(seg, color=c)
Next, I plotted them as a 3D surface:
# plot as a surface
surfacefig = plt.figure()
surfaceax = surfacefig.gca(projection='3d')
X = np.arange(LAGS+1)
Y = np.arange(num_segs)
X, Y = np.meshgrid(X, Y)
surfaceax.plot_surface(X, Y, df, cmap=plt.cm.Set2)
plt.show()
How can I map colors to row index (rather than z-values)? I'd like to preserve the colors of the lines.
Update with result:
# updated lines. Make sure XX and YY are floats
surf = surfaceax.plot_surface(XX, YY, df, shade=False,
facecolors=plt.cm.Set2((YY-YY.min()) / (YY.max()-YY.min())),
cstride=1, rstride=5, alpha=0.7)
plt.draw() # you need this to get the edge color
line = np.array(surf.get_edgecolor())
surf.set_edgecolor(line*np.array([0,0,0,0])+1)
You can try this:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
X = np.linspace(-np.pi, np.pi, 200, endpoint=True)
Y = np.linspace(-np.pi, np.pi, 200, endpoint=True)
XX, YY = np.meshgrid(X,Y)
Z = np.cos(XX)*np.cos(YY)
fig = plt.figure()
ax1 = plt.subplot2grid((1,2), (0,0), projection='3d')
ax2 = plt.subplot2grid((1,2), (0,1), projection='3d')
surf = ax1.plot_surface(XX, YY, Z,
cmap=plt.cm.Set2)
surf2 = ax2.plot_surface(XX, YY, Z, shade=False,
facecolors=plt.cm.Set2((XX-XX.min())/(XX.max()-XX.min()))
)
Where on the second plot, you set the facecolors as being function of XX, instead of Z by default. You need to rescale your XX values between 0 and 1 or the colormap will be saturated outside 0 and 1. You also need to remove the shade which is removed when yous use cmap (in the first plot).
However, for some unknown reasons, the lines disappear.
You can add them back with:
plt.draw() # you need this to get the edge color
lines = np.array(surf2.get_edgecolor())
surf2.set_edgecolor(lines*np.array([0,0,0,0])+1) # make lines white, and keep alpha==1. It's an array of colors like this: [r,g,b,alpha]
It gives:
HTH

How do I get independent scaling for two surface plots on the same graph

I am trying to have two data values drawn on the same set of 3d axis. The value of the two data sets can differ by 1 or 2 orders of magnitude. As a result I want two Z axis similar to the twinx or twiny commands for 2d plots. A rough example is shown in the code below
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
result=[['1', '2', '3'],
['4', '5', '6'],
['7', '8', '9']]
result = np.array(result, dtype=np.int)
fig=plt.figure(figsize=(5, 5), dpi=150)
ax1=fig.add_subplot(111, projection='3d')
xlabels = np.array(['data1x', 'data2x', 'data3x'])
xpos = np.arange(xlabels.shape[0])
ylabels = np.array(['data1y','data2y','data3y'])
ypos = np.arange(ylabels.shape[0])
xposM, yposM = np.meshgrid(xpos, ypos, copy=False)
zpos=result
zpos = zpos.ravel()
dx=0.5
dy=0.5
dz=zpos
ax1.w_xaxis.set_ticks(xpos + dx/2.)
#ax1.w_xaxis.set_ticklabels(xlabels)
ax1.w_yaxis.set_ticks(ypos + dy/2.)
#ax1.w_yaxis.set_ticklabels(ylabels)
values = np.linspace(0.2, 1., xposM.ravel().shape[0])
colors = cm.rainbow(values)
ax1.bar3d(xposM.ravel(), yposM.ravel(), dz*0, dx, dy, dz, color=colors)
X = np.arange(-1, 1, 0.25)
Y = np.arange(-1, 1, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = 100*np.sin(R)
surf = ax1.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
plt.show()
How can i change the z scale for one of the plots so I can better see the features of both?
You can scale the data from one of the arrays to correspond to the magnitude of the other. Then, when plotted, the z-extend will be comparable.
In order to inlcude a colorbar for the scaled data that shows the original data range, I used a second set of axis.
Import the following additional libraries:
import matplotlib as mpl # for general access to the colorbar class
import matplotlib.gridspec as gridspec # to set up an axis-grid
Set up your axis:
gs = gridspec.GridSpec(1, 2,
width_ratios=[20,1],
)
ax1 = fig.add_subplot(gs[0], projection='3d')
ax2 = fig.add_subplot(gs[1])
You can adjust the width-ratios to change the width of the colorbar (given by ax2) relative to the data plot (given by ax1).
use numpy's amax to determine the maxima of your two data sets for the scaling (which can be taken care of when calling the surface plot:
surf = ax1.plot_surface(X, Y, Z/np.amax(Z)*np.amax(zpos),
rstride=1, cstride=1, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
using Z/np.amax(Z)*np.amax(zpos) will scale your Z-data to the magnitude of zpos.
Now, plot a colorbar on the second axis:
cbar = mpl.colorbar.ColorbarBase(ax2, cmap = cm.coolwarm,
norm=mpl.colors.Normalize(vmin=np.amin(Z), vmax=np.amax(Z)))
To ensure that the colorbar covers the range of the Z-date, use the norm functionality.
This is your plot:
You can place second - transparent - axes over the first ones and use shared x and y axes.
Set up a second set of axes, including sharex and sharey:
ax2 = fig.add_axes(ax1.get_position(), projection='3d',
sharex=ax1, sharey=ax1)
Make the background transparent and remove the actual axes lines (and ticks, etc.):
ax2.set_axis_off()
ax2.patch.set_facecolor('none')
Plot as before, but specify the second axes (ax2), you can also plot a colorbar without any additional effort:
surf = ax2.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
cb = fig.colorbar(surf,ax=ax1)
Set xlim and ylim to avoid any misalignment (this can probably be linked to the data):
ax1.set_xlim([-1.0, 3.0])
ax1.set_ylim([-1.0, 3.0])
The resulting plot will be:

Categories

Resources