Fitting a multiline suptitle (not enough vertical space) - matplotlib

How do I create enough vertical space to contain a figure's title when using subplots?
See the code (and output) below,
import matplotlib.pylab as plt
import numpy as np
fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(9, 3))
fig.suptitle("Figure 1: Some test title \n With a Second line \n And a Third line")
ax1 = fig.add_subplot(121, title="Model 1")
x = np.linspace(0, 2 * np.pi, 400)
y = np.sin(x ** 2)
ax1.plot(x,y)
ax2 = fig.add_subplot(122, title="Model 2")
x = np.linspace(0, 2 * np.pi, 400)
y = np.cos(x ** 2)
ax2.plot(x,y)
fig.tight_layout()
fig.show()

You can pass the y argument in fig.suptitle():
import matplotlib.pylab as plt
import numpy as np
fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(9, 3))
fig.suptitle("Figure 1: Some test title \n With a Second line \n And a Third line", y=1.2)
ax1 = fig.add_subplot(121, title="Model 1")
x = np.linspace(0, 2 * np.pi, 400)
y = np.sin(x ** 2)
ax1.plot(x,y)
ax2 = fig.add_subplot(122, title="Model 2")
x = np.linspace(0, 2 * np.pi, 400)
y = np.cos(x ** 2)
ax2.plot(x,y)
fig.tight_layout()
fig.show()

Related

Adding patch distorts alignment

I am working with the following image:
from matplotlib import cbook
import matplotlib.patches as mpatches
from matplotlib.axes._base import _TransformedBoundsLocator
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
import numpy as np
# a numpy array of 15x15
Z = cbook.get_sample_data("axes_grid/bivariate_normal.npy", np_load=True)
gs = GridSpec(2, 3)
fig = plt.figure(figsize=(3*3,2*3))
ax1 = fig.add_subplot(gs[:2, :2])
ax2 = fig.add_subplot(gs[1, 2])
Z2 = np.zeros((150, 150))
ny, nx = Z.shape
Z2[30:30+ny, 30:30+nx] = Z
ax1.imshow(Z2)
ax1.set_aspect("equal")
ax2.set_aspect("equal")
plt.tight_layout()
plt.show()
output:
As shown in the image, the x-axis of both plots are aligned. However, when I am adding a patch to the first plot the alignment becomes distorted:
Z = cbook.get_sample_data("axes_grid/bivariate_normal.npy", np_load=True)
gs = GridSpec(2, 3)
fig = plt.figure(figsize=(3*3,2*3))
ax1 = fig.add_subplot(gs[:2, :2])
ax2 = fig.add_subplot(gs[1, 2])
Z2 = np.zeros((150, 150))
ny, nx = Z.shape
Z2[30:30+ny, 30:30+nx] = Z
ax1.imshow(Z2)
x, y, width, height = 30, 30, 15, 15
ex, ey = (0,1)
xy_data = x + ex * width, y + ey * height
p = mpatches.ConnectionPatch(
xyA=(0,1), coordsA=ax2.transAxes,
xyB=xy_data, coordsB=ax1.transData)
ax1.add_patch(p)
ax1.set_aspect("equal")
ax2.set_aspect("equal")
plt.tight_layout()
plt.show()
output:
Why is this? How can I add a patch whilst retaining the original layout?

getting only the last plot

when plotting by below code I am getting c,d,e plots but I am getting only the last plot for plt.plot
def normalize(x):
return (x - x.min(0)) / x.ptp(0)
c=sns.distplot(mk[0]['mass'], hist=True, label='p', rug=True)
d=sns.distplot(mk[1]['mass'], hist=True, label='q', rug=True)
e=sns.distplot(mk[2]['mass'], hist=True, label='r', rug=True)
datadist=[c,d,e]
xd=dict()
yd2=dict()
for i in datadist:
line = i.get_lines()[0]
xd[i] = line.get_xdata()
yd = line.get_ydata()
yd2[i] = normalize(yd)
plt.plot(xd[c], yd2[c],color='black')
plt.plot(xd[d], yd2[d],color='yellow')
plt.plot(xd[e], yd2[e],color='green')
sns.distplot() returns the ax (the subplot) on which the histogram was drawn. All 3 are drawn on the same subplot, so the return value is the same three times.
The array lines = ax1.get_lines() contains exactly 3 elements: one for each of the kde curves, so you can extract them as follows:
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
def normalize(x):
return (x - x.min(0)) / x.ptp(0)
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(14, 4))
sns.distplot(np.random.randn(30) + 10, hist=True, label='p', rug=True, ax=ax1, color='black')
sns.distplot(np.random.randn(30) + 15, hist=True, label='q', rug=True, ax=ax1, color='gold')
sns.distplot(np.random.randn(30) + 20, hist=True, label='r', rug=True, ax=ax1, color='green')
for line in ax1.get_lines():
ax2.plot(line.get_xdata(), normalize(line.get_ydata()), color=line.get_color())
plt.show()
Now, if you just want the kde-curves and "normalize" them, you could use scipy.stats import gaussian_kde:
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import gaussian_kde
def normalize(x):
return (x - x.min(0)) / x.ptp(0)
fig, ax = plt.subplots(figsize=(12, 4))
mk0mass = np.random.randn(30) + 10
mk1mass = np.random.randn(30) + 15
mk2mass = np.random.randn(30) + 20
all_mkmass = [mk0mass, mk1mass, mk2mass]
x = np.linspace(min([mki.min() for mki in all_mkmass]) - 2,
max([mki.max() for mki in all_mkmass]) + 2, 1000)
for mki, color in zip(all_mkmass, ['black', 'gold', 'green']):
kde = gaussian_kde(mki)
yd = normalize(kde(x))
ax.plot(x, yd, color=color)
ax.fill_between(x, 0, yd, color=color, alpha=0.3)
plt.show()

Matplotlib animate subplots (2 hist and 2 scatter plots)

How can I animate the following 4 subplots? So far I've managed to make the animation not stop at all or to make the frames appear but not the dots/bars of the plots. I tried to follow this https://matplotlib.org/gallery/animation/subplots.html example but "subclassing" is far ahead of my skills.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# generate 4 random variables
x = np.random.normal(-2.5, 1, 10000)
y = np.random.gamma(2, 1.5, 10000)
a = np.random.exponential(2, 10000)+7
b = np.random.uniform(14,20, 10000)
n_bins = 100
n = 100
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2,2, sharex='col')
# plot
ax1.hist(x, bins=n_bins, facecolor='c')
ax1.set_title('normal')
ax1.spines['top'].set_visible(False)
ax1.spines['right'].set_visible(False)
ax1.spines['bottom'].set_visible(False)
ax1.spines['left'].set_visible(False)
ax1.tick_params(bottom=False, left=False)
ax2.hist(y, bins=n_bins, facecolor='c')
ax2.set_title('exponential')
ax2.spines['top'].set_visible(False)
ax2.spines['right'].set_visible(False)
ax2.spines['bottom'].set_visible(False)
ax2.spines['left'].set_visible(False)
ax2.tick_params(bottom=False, left=False)
ax3.scatter(x, a, facecolor='c')
ax3.set_title('gamma')
ax3.spines['top'].set_visible(False)
ax3.spines['right'].set_visible(False)
ax3.spines['bottom'].set_visible(False)
ax3.spines['left'].set_visible(False)
ax3.tick_params(bottom=False, left=False)
ax4.scatter(y, b, facecolor='c')
ax4.set_title('uniform')
ax4.spines['top'].set_visible(False)
ax4.spines['right'].set_visible(False)
ax4.spines['bottom'].set_visible(False)
ax4.spines['left'].set_visible(False)
ax4.tick_params(bottom=False, left=False)
plt.show()
in this line
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2,2, sharex='col')
ax1, ax2, ax3, ax4, there are spaces for images
you need to create a image and put in ax1/2/3/4
i make a example that show how to work with a matplot
I understood after your comment about what you wanted to accomplish, to get a much more polished animation than what I am providing here would require planning out some math.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# some parameter to vary
vary = np.linspace(10, 120, 100, dtype=int)
n_bins = 100
n = 100
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, sharex='col')
def plot_prop():
# plot
ax1.set_ylim(0, 350)
ax1.set_title('normal')
ax1.spines['top'].set_visible(False)
ax1.spines['right'].set_visible(False)
ax1.spines['bottom'].set_visible(False)
ax1.spines['left'].set_visible(False)
ax1.tick_params(bottom=False, left=False)
ax2.set_ylim(0, 450)
ax2.set_title('exponential')
ax2.spines['top'].set_visible(False)
ax2.spines['right'].set_visible(False)
ax2.spines['bottom'].set_visible(False)
ax2.spines['left'].set_visible(False)
ax2.tick_params(bottom=False, left=False)
ax3.set_xlim(-7, 2)
ax3.set_ylim(5, 25)
ax3.set_title('gamma')
ax3.spines['top'].set_visible(False)
ax3.spines['right'].set_visible(False)
ax3.spines['bottom'].set_visible(False)
ax3.spines['left'].set_visible(False)
ax3.tick_params(bottom=False, left=False)
ax4.set_xlim(0, 20)
ax4.set_title('uniform')
ax4.spines['top'].set_visible(False)
ax4.spines['right'].set_visible(False)
ax4.spines['bottom'].set_visible(False)
ax4.spines['left'].set_visible(False)
ax4.tick_params(bottom=False, left=False)
for i, p in enumerate(vary):
# generate 4 random variables
x = np.random.normal(-2.5, 1, p * 100)
y = np.random.gamma(2, 1.5, p * 100)
a = np.random.exponential(2, p * 100) + 7
b = np.random.uniform(14, 20, p * 100)
plot_prop()
ax1.hist(x, bins=n_bins, facecolor='c')
ax2.hist(y, bins=n_bins, facecolor='c')
ax3.scatter(x, a, facecolor='c')
ax4.scatter(y, b, facecolor='c')
plt.savefig('../imgs/img' + str(i) + '.png')
ax1.cla()
ax2.cla()
ax3.cla()
ax4.cla()
fig = plt.figure()
ims = []
for i in range(100):
im = plt.imshow(plt.imread('../imgs/img' + str(i) + '.png'), animated=True)
ims.append([im])
ani = animation.ArtistAnimation(fig, ims, interval=200, blit=True)
ani.save('../imgs/animation.mp4', writer=animation.FFMpegFileWriter(), dpi=300)
Here is a link to the animation I've got

How to share xaxis in contour-map-subplots which share a colorbar

I try to make 3 subplot which share one colorbar and the xaxis, as already explained by spinup in
Matplotlib 2 Subplots, 1 Colorbar
Using maps (with coastlines) in the subplots, it seems that a sharex is not supported.
However, is there a way, to apply a shared axis?
import cartopy.crs as ccrs
from cartopy.mpl.geoaxes import GeoAxes
from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import AxesGrid
import numpy as np
def sample_data_3d(shape):
"""Returns `lons`, `lats`, `times` and fake `data`"""
ntimes, nlats, nlons = shape
lats = np.linspace(-np.pi / 2, np.pi / 2, nlats)
lons = np.linspace(0, 2 * np.pi, nlons)
lons, lats = np.meshgrid(lons, lats)
wave = 0.75 * (np.sin(2 * lats) ** 8) * np.cos(4 * lons)
mean = 0.5 * np.cos(2 * lats) * ((np.sin(2 * lats)) ** 2 + 2)
lats = np.rad2deg(lats)
lons = np.rad2deg(lons)
data = wave + mean
times = np.linspace(-1, 1, ntimes)
new_shape = data.shape + (ntimes, )
data = np.rollaxis(data.repeat(ntimes).reshape(new_shape), -1)
data *= times[:, np.newaxis, np.newaxis]
return lons, lats, times, data
def main():
projection = ccrs.PlateCarree()
axes_class = (GeoAxes,
dict(map_projection=projection))
lons, lats, times, data = sample_data_3d((6, 73, 145))
fig = plt.figure()
axgr = AxesGrid(fig, 111, axes_class=axes_class,
nrows_ncols=(3, 1),
axes_pad=0.6,
share_all=True, #doesn't change anything
cbar_location='bottom',
cbar_mode='single',
cbar_pad=0.2,
cbar_size='3%',
label_mode='') # note the empty label_mode
for i, ax in enumerate(axgr):
ax.coastlines()
ax.add_feature(cartopy.feature.LAND, zorder=100,
edgecolor='k',facecolor='w')
ax.set_xticks(np.linspace(-180, 180, 5), crs=projection)
ax.set_yticks(np.linspace(-90, 90, 5), crs=projection)
p = ax.contourf(lons, lats, data[i, ...],
transform=projection,
cmap='RdBu')
axgr.cbar_axes[0].colorbar(p)
plt.show()

matlibplot: How to add space between some subplots

How can I adjust the whitespace between some subplots? In the example below, let's say I want to eliminate all whitespace between the 1st and 2nd subplots as well as between the 3rd and 4th and increase the space between the 2nd and 3rd?
import matplotlib.pyplot as plt
import numpy as np
# Simple data to display in various forms
x = np.linspace(0, 2 * np.pi, 400)
y = np.sin(x ** 2)
f, ax = plt.subplots(4,figsize=(10,10),sharex=True)
ax[0].plot(x, y)
ax[0].set_title('Panel: A')
ax[1].plot(x, y**2)
ax[2].plot(x, y**3)
ax[2].set_title('Panel: B')
ax[3].plot(x, y**4)
plt.tight_layout()
To keep the solution close to your code you may use create 5 subplots with the middle one being one forth in height of the others and remove that middle plot.
import matplotlib.pyplot as plt
import numpy as np
# Simple data to display in various forms
x = np.linspace(0, 2 * np.pi, 400)
y = np.sin(x ** 2)
f, ax = plt.subplots(5,figsize=(7,7),sharex=True,
gridspec_kw=dict(height_ratios=[4,4,1,4,4], hspace=0))
ax[0].plot(x, y)
ax[0].set_title('Panel: A')
ax[1].plot(x, y**2)
ax[2].remove()
ax[3].plot(x, y**3)
ax[3].set_title('Panel: B')
ax[4].plot(x, y**4)
plt.tight_layout()
plt.show()
You would need to use GridSpec to have different spaces between plots:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
# Simple data to display in various forms
x = np.linspace(0, 2 * np.pi, 400)
y = np.sin(x ** 2)
f = plt.figure(figsize=(10,10))
gs0 = gridspec.GridSpec(2, 1)
gs00 = gridspec.GridSpecFromSubplotSpec(2, 1, subplot_spec=gs0[0], hspace=0)
ax0 = f.add_subplot(gs00[0])
ax0.plot(x, y)
ax0.set_title('Panel: A')
ax1 = f.add_subplot(gs00[1], sharex=ax0)
ax1.plot(x, y**2)
gs01 = gridspec.GridSpecFromSubplotSpec(2, 1, subplot_spec=gs0[1], hspace=0)
ax2 = f.add_subplot(gs01[0])
ax2.plot(x, y**3)
ax2.set_title('Panel: B')
ax3 = f.add_subplot(gs01[1], sharex=ax0)
ax3.plot(x, y**4)
plt.show()