Related
I have to do three plots (contour, 3d surface, and heatmap) in matplotlib. The corresponding grid dimension for the three plots are ([0, 0], [0, 1], and [1, 0:1])
I have a few problems
The text annotation for heatmap (ax3), seem to fly out of ax3, into
ax1 and ax2. How can I constrain them to be within the ax3 only ?
Is this the fastest way to annotate text assuming that I do not want
to use seaborn ?
Can I get some tips on how to resolve my problems ?
Below is the code snippet to perform the plot operation
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gspec
from scipy.interpolate import griddata
import pyautogui
from scipy import stats
x = pyautogui.size()
width = x.width
height = x.height
x = np.arange(0, 10, 0.5)
y = np.arange(0, 10, 0.5)
X, Y = np.meshgrid(x, y)
data = 2 * (np.sin(X) + np.sin(3 * Y))
fig = plt.figure()
fig.set_figheight(height / 100)
fig.set_figwidth(width / 100)
fig.set_dpi(100)
gs = gspec.GridSpec(nrows=2, ncols=2)
ax1 = plt.subplot(gs[0, 0])
ax2 = plt.subplot(gs[0, 1], projection='3d')
ax3 = plt.subplot(gs[1, 0:1])
ctr = ax1.contourf(X, Y, data, 10, cmap='viridis')
ax1.clabel(ctr, inline=True, fontsize=8)
cbar = plt.colorbar(ctr, ax=ax1)
cbar.set_label('ColorbarLabel', size=15)
surf = ax2.plot_surface(X, Y, data, cmap='jet')
cbar1 = plt.colorbar(surf, ax=ax2)
cbar1.set_label('Colorbar2', size=15)
hmap = ax3.pcolormesh(X, Y, data, cmap='viridis')
cbar2 = plt.colorbar(hmap, ax=ax3)
for y in range(data.shape[0]):
for x in range(data.shape[1]):
ax3.text(x, y, '%.1f' % data[y, x], size=3)
I assume you want your heatmap to cover both columns. To achieve that you have to use ax3 = plt.subplot(gs[1, 0:2]): this tells matplotlib to use columns 0 and 1 (2 is excluded).
The text annotation for heatmap (ax3), seem to fly out of ax3, into ax1 and ax2. How can I constrain them to be within the ax3 only ?
That's because you are using the wrong coordinates in ax3.text.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gspec
from scipy.interpolate import griddata
import pyautogui
from scipy import stats
x = pyautogui.size()
width = x.width
height = x.height
x = np.arange(0, 10, 0.5)
y = np.arange(0, 10, 0.5)
X, Y = np.meshgrid(x, y)
data = 2 * (np.sin(X) + np.sin(3 * Y))
fig = plt.figure()
fig.set_figheight(height / 100)
fig.set_figwidth(width / 100)
fig.set_dpi(100)
gs = gspec.GridSpec(nrows=2, ncols=2)
ax1 = plt.subplot(gs[0, 0])
ax2 = plt.subplot(gs[0, 1], projection='3d')
ax3 = plt.subplot(gs[1, 0:2])
ctr = ax1.contourf(X, Y, data, 10, cmap='viridis')
ax1.clabel(ctr, inline=True, fontsize=8)
cbar = plt.colorbar(ctr, ax=ax1)
cbar.set_label('ColorbarLabel', size=15)
surf = ax2.plot_surface(X, Y, data, cmap='jet')
cbar1 = plt.colorbar(surf, ax=ax2)
cbar1.set_label('Colorbar2', size=15)
hmap = ax3.pcolormesh(X, Y, data, cmap='viridis')
cbar2 = plt.colorbar(hmap, ax=ax3)
for i in range(data.shape[0]):
for j in range(data.shape[1]):
ax3.text(x[j], y[i], '%.1f' % data[i, j], size=5)
I have a scatter plot on which I have colormapped the points using matplotlib.colors.LogNorm. This gives me the colour map scale which I desire, but I am not able to make this a discrete colormap.
Here's what I have:
I am aiming for something like this (ignoring the inset plot) :
I am able to use matplotlib.colors.BoundaryNorm with some level of success, but seem to lose the helpful formatting of the colorbar from matplotlib.colors.LogNorm:
Thanks
You can explicitly set the text for the colorbar ticks. Here is an example:
import matplotlib.pyplot as plt
from matplotlib.colors import BoundaryNorm, LogNorm
import numpy as np
x = np.linspace(0, 1, 60)
y = np.linspace(0, 1, 60)
c = np.logspace(-4, 1, 60)
fig, ax = plt.subplots()
sc1 = ax.scatter(x, y, c=c, cmap='viridis', norm=LogNorm())
cbar1 = plt.colorbar(sc1, ax=ax)
bounds = np.power(10.0, np.arange(-4, 2))
ncolors = len(bounds) - 1
cmap = plt.cm.get_cmap('turbo', ncolors)
norm = BoundaryNorm(boundaries=bounds, ncolors=ncolors)
sc2 = ax.scatter(x, y + 0 + 0.1, c=c, cmap=cmap, norm=norm)
cbar = plt.colorbar(sc2, ax=ax)
cbar.ax.set_yticklabels([f'$10^{{{np.log10(b):.0f}}}$' for b in bounds])
plt.show()
I am trying to add an additional small colorbar for the inset axis. The current code, without that, is
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib.colors as colors
from mpl_toolkits.axes_grid1.inset_locator import mark_inset
A = np.linspace(1,20,20)
B = A
X,Y = np.meshgrid(A,B)
Z = X**2 + Y**2
fig, ax = plt.subplots()
im = ax.pcolor(X, Y, Z, cmap='hot_r')
ax.set_xlabel('x',fontsize=labelsize)
ax.set_ylabel('y',fontsize=labelsize)
ca = fig.colorbar(im)#, shrink=0.5, aspect=5)
axins = ax.inset_axes([0.1, 0.5, 0.25, 0.25])
axins.pcolor(A[0:4], B[0:4], Z[0:4,0:4], cmap='hot_r')
axins.tick_params(axis='both', which='major', labelsize=11)
for axis in ['top','bottom','left','right']:
axins.spines[axis].set_linewidth(1)
axins.spines[axis].set_color('gray')
mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec='gray', lw=1)
plt.tight_layout()
You could create an additional inset axis for the colorbar. E.g. located just right of the inset. Then create a colorbar proving this axis (cax=...).
Please note that pcolor creates faces (large pixels) between the given x and y positions. So, you need one row and one column more of position then the number of colors. The current version of matplotlib gives a warning in case too many colors (or not enough x and y positions) are given.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import mark_inset
A = np.linspace(1, 20, 20)
B = A
X, Y = np.meshgrid(A, B)
Z = X ** 2 + Y ** 2
fig, ax = plt.subplots()
im = ax.pcolor(X, Y, Z[:-1, :-1], cmap='hot_r')
ax.set_xlabel('x', fontsize=12)
ax.set_ylabel('y', fontsize=12)
ca = fig.colorbar(im) # , shrink=0.5, aspect=5)
axins = ax.inset_axes([0.1, 0.5, 0.25, 0.25])
axins_cbar = ax.inset_axes([0.37, 0.5, 0.02, 0.25])
img_in = axins.pcolor(A[0:5], B[0:5], Z[0:4, 0:4], cmap='hot_r')
axins.tick_params(axis='both', which='major', labelsize=11)
for axis in ['top', 'bottom', 'left', 'right']:
axins.spines[axis].set_linewidth(1)
axins.spines[axis].set_color('gray')
mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec='gray', lw=1)
fig.colorbar(img_in, cax=axins_cbar)
plt.tight_layout()
plt.show()
I am programmatically creating a scatterplot like this:
(Ipython sample code)
%matplotlib inline
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, axisbg="1.0")
d1 = [range(1,11)]
d2 = [range(1,11)]
dcolor = ['red','red','red','green','green','green','blue','blue','blue', 'blue']
colordict{'red': 'monkey', 'green':'whale', 'blue':'cat'}
ax.scatter(d1,d2,alpha=0.8, c=dcolor,edgecolors='none',s=30)
I would like to add a legend for each different point, so that the legend contains a point in the given color and the name from colordict. Is that possible without splitting the creation of the scatterplot into multiple calls to scatter? Since this happens in a automated library, I would rather avoid to have different calls to scatter().
I would probably do the following.
%matplotlib inline
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, axisbg="1.0")
g1 = ([1,2,3], [1,2,3])
g2 = ([4,5,6], [4,5,6])
g3 = ([7,8,9,10], [7,8,9,10])
data = (g1, g2, g3)
colors = ("red", "green", "blue")
groups = ("monkey", "whale", "cat")
for data, color, group in zip(data, colors, groups):
x, y = data
ax.scatter(x, y, alpha=0.8, c=color, edgecolors='none', s=30, label=group)
plt.legend(loc=2)
I like keeping the data and its symbols (color, label) even tighter than cel does. I find the code more readable and more checkable, and often I'm getting them together out of some datasource anyway:
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, axisbg="1.0")
zoo=[]
zoo.append(([4,5,6], [4,5,6], "blue","ape"))
zoo.append(([1,2,3], [1,2,3], "red","monkey"))
for x,y,c,l in zoo:
plt.scatter(x,y,c=c,label=l)
plt.legend(loc="upper left")
Finally, I have used the following code:
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, axisbg="1.0")
d1 = [range(1,11)]
d2 = [range(1,11)]
dcolor = ['red','red','red','green','green','green','blue','blue','blue', 'blue']
ax.scatter(d1,d2,alpha=0.8, c=dcolor,edgecolors='none',s=30)
import matplotlib.patches as mpatches
patch = mpatches.Patch(color='red', label='a')
patch2 = mpatches.Patch(color='red', label='a')
fig.legend( [patch, patch2],['abc', 'xyz'], loc = 'lower center', ncol=5, labelspacing=0. )
Here it is not yet in a loop, but that is easily doable.
I'm trying to generate two subplots side by side, sharing the y axis, with a single colorbar for both.
This is a MWE of my code:
import matplotlib.pyplot as plt
import numpy as np
def rand_data(l, h):
return np.random.uniform(low=l, high=h, size=(100,))
# Generate data.
x1, x2, y, z = rand_data(0., 1.), rand_data(100., 175.), \
rand_data(150., 200.), rand_data(15., 33.)
fig = plt.figure()
cm = plt.cm.get_cmap('RdYlBu')
ax0 = plt.subplot(121)
plt.scatter(x1, y, c=z, cmap=cm)
ax1 = plt.subplot(122)
# make these y tick labels invisible
plt.setp(ax1.get_yticklabels(), visible=False)
plt.scatter(x2, y, c=z, cmap=cm)
cbar = plt.colorbar()
plt.show()
what this returns is a left subplot slightly larger horizontally than the right one since this last includes the colorbar, see below:
I've tried using ax.set_aspect('equal') but since the x axis are not in the same range the result looks awful.
I need both these plots to be displayed squared. How can I do this?
To expend my comment that one can make 3 plots, plot the colorbar() in the 3rd one, the data plots in the 1st and 2nd. This way, if necessary, we are free to do anything we want to the 1st and 2nd plots:
def rand_data(l, h):
return np.random.uniform(low=l, high=h, size=(100,))
# Generate data.
x1, x2, y, z = rand_data(0., 1.), rand_data(100., 175.), \
rand_data(150., 200.), rand_data(15., 33.)
fig = plt.figure(figsize=(12,6))
gs=gridspec.GridSpec(1,3, width_ratios=[4,4,0.2])
ax1 = plt.subplot(gs[0])
ax2 = plt.subplot(gs[1])
ax3 = plt.subplot(gs[2])
cm = plt.cm.get_cmap('RdYlBu')
ax1.scatter(x1, y, c=z, cmap=cm)
SC=ax2.scatter(x2, y, c=z, cmap=cm)
plt.setp(ax2.get_yticklabels(), visible=False)
plt.colorbar(SC, cax=ax3)
plt.tight_layout()
plt.savefig('temp.png')
Updated - here is another option without using GridSpec.
import numpy as np
import matplotlib.pyplot as plt
N = 50
x_vals = np.random.rand(N)
y_vals = np.random.rand(N)
z1_vals = np.random.rand(N)
z2_vals = np.random.rand(N)
minimum_z = min(np.min(z1_vals), np.min(z2_vals))
maximum_z = max(np.max(z1_vals), np.max(z2_vals))
fig, axis_array = plt.subplots(1,2, figsize = (20, 10), subplot_kw = {'aspect':1})
ax0 = axis_array[0].scatter(x_vals, y_vals, c = z1_vals, s = 100, cmap = 'rainbow', vmin = minimum_z, vmax = maximum_z)
ax1 = axis_array[1].scatter(x_vals, y_vals, c = z2_vals, s = 100, cmap = 'rainbow', vmin = minimum_z, vmax = maximum_z)
cax = fig.add_axes([0.95, 0.05, 0.02, 0.95]) #this locates the axis that is used for your colorbar. It is scaled 0 - 1.
fig.colorbar(ax0, cax, orientation = 'vertical') #'ax0' tells it which plot to base the colors on
plt.show()