Adjusting space in-between subplots - python

I am plotting 4 subplots in one figure, and I want to adjust the space in-between evenly.
I tried grid.GridSpec.update.
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
plt.figure(figsize=(8,8))
gs2 = gridspec.GridSpec(2, 2)
gs2.update(wspace=0.01, hspace=0.01)
ax1 = plt.subplot(gs2[0,0],aspect='equal')
ax1.imshow(img)
ax1.axis('off')
ax2 = plt.subplot(gs2[0,1],aspect='equal')
ax2.imshow(img)
ax2.axis('off')
ax3 = plt.subplot(gs2[1,0],aspect='equal')
ax3.imshow(img)
ax3.axis('off')
ax4 = plt.subplot(gs2[1,1],aspect='equal')
ax4.imshow(img)
ax4.axis('off')
The vertical space in-between 2 plots is too big, and it does not change no matter how I adjust gs2.update(hspace= ), as shown below:

It's likely your aspect='equal' that's causing the problem.
Try this
import numpy as np
%matplotlib inline # if in a jupyter notebook like environment
img = np.ones((30, 30))
fig, axes = plt.subplots(2, 2, sharex=True, sharey=True, figsize=(8,8),
gridspec_kw={'wspace': 0.01, 'hspace': 0.01})
axes = axes.ravel()
for ax in axes:
# aspect : ['auto' | 'equal' | scalar], optional, default: None
ax.imshow(img, aspect='auto')
ax.axis('off')

Related

Matplotlib - Tight layout of multiple subplots with colorbar

I have a series of subplots in a single row, all sharing the same colorbar and I would like to use plt.tight_layout().
However when used naively, the colorbar messes everything up. Luckily, I found this in the matplotlib documentation, but it works only for one subplot.
Minimal Working Example
I tried to adapt it to multiple subplots but the subplot to which the colorbar is assigned to ends up being smaller.
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
import numpy as np
plt.close('all')
arr = np.arange(100).reshape((10, 10))
fig, ax = plt.subplots(ncols=2, figsize=(8, 4))
im0 = ax[0].imshow(arr, interpolation="none")
im1 = ax[1].imshow(arr, interpolation='none')
divider = make_axes_locatable(plt.gca())
cax = divider.append_axes("right", "5%", pad="3%")
plt.colorbar(im0, cax=cax)
plt.tight_layout()
This is what the result looks like.
With the newest matplotlib (3.6), there is a new option layout='compressed' for this situation:
import matplotlib.pyplot as plt
import numpy as np
arr = np.arange(100).reshape((10, 10))
fig, ax = plt.subplots(ncols=2, figsize=(4, 2), layout='compressed')
im0 = ax[0].imshow(arr)
im1 = ax[1].imshow(arr)
plt.colorbar(im0, ax=ax)
plt.show()

tight_layout() reserves ylabel and yticklabels space even when disabled

I have a 2*4 subplots figure, with half of the ylabel and yticklabels disabled. Unfortunately, tight_layout() does not remove the extra white space which corresponds to the area where the ylabel and yticklabels would appear if they were not disabled. The ylabel and yticklabels are removed because I would like to have 4 pairs of comparison subplots. The plot looks something like this.
I am looking for an efficient way to remove the extra white space. In fact, I would like each pair of plots to be next to each other with no space at all. Here is an working example.
import matplotlib.pyplot as plt
fig, ((ax0, ax1, ax2, ax3), (ax4, ax5, ax6, ax7)) = plt.subplots(2, 4, figsize=(8, 4))
axs = [ax0, ax1, ax2, ax3, ax4, ax5, ax6, ax7]
for i in range(4):
axs[2*i].set_ylabel('parameter '+str(i))
axs[2*i+1].set_yticklabels([])
plt.tight_layout()
plt.show()
The code should yield the above plot. Any tips would be appreciated. Many thanks!
You can do it by having two subgrids and forcing a null distance between the axis (I followed this tutorial). The wspace parameter is described here.
import matplotlib.pyplot as plt
fig = plt.figure(constrained_layout=False) # you must disable automatic spacing
gs = fig.add_gridspec(1, 2, figure=fig)
gs0 = gs[0].subgridspec(2, 2, wspace=0.0)
gs1 = gs[1].subgridspec(2, 2, wspace=0.0)
axs = [[],[]]
for i in range(2):
for j in range(2):
axs[0].append(fig.add_subplot(gs0[i, j]))
axs[1].append(fig.add_subplot(gs1[i, j]))
axs = [*axs[0],*axs[1]]
for i in range(4):
axs[2*i].set_ylabel('parameter ' + str(i))
axs[2*i+1].set_yticklabels([])
Since the automatic spacing is disabled, you may have to play with axis and figure properties to adapt the position of the axis, labels, etc.
A partial (distance won't be null, but may be interesting for other users), more elegant solution is to use constrained_layout when creating the figure. More info in matplotlib's documentation.
import matplotlib.pyplot as plt
fig, axs = plt.subplots(
2, 4,
figsize=(8, 4),
constrained_layout=True,
)
axs = axs.reshape([8])
for i in range(4):
axs[2*i].set_ylabel('parameter '+str(i))
axs[2*i+1].set_yticklabels([])
# plt.tight_layout() # not necessary
plt.show()

How to customize the location of color bar in Seaborn heatmap?

I have the following code to create a heatmap. However, it creates an overlap of the color bar and the right axis text. The text has no problems, I want it to be in that length.
How can I locate the colorbar on the right/left side of the heatmap with no overlap?
I tried with "pad" parameter in cbar_kws but it didn't help.enter image description here
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
PT=pd.DataFrame(np.random.randn(300,3), columns=list('ABC'))
miniPT=PT.iloc[:,:-1]
SMALL_SIZE = 8
MEDIUM_SIZE = 80
BIGGER_SIZE = 120
plt.rc('font', size=MEDIUM_SIZE) # controls default text sizes
plt.rc('axes', titlesize=MEDIUM_SIZE) # fontsize of the axes title
plt.rc('axes', labelsize=MEDIUM_SIZE) # fontsize of the x and y labels
plt.rc('xtick', labelsize=MEDIUM_SIZE) # fontsize of the tick labels
plt.rc('ytick', labelsize=SMALL_SIZE) # fontsize of the tick labels
plt.rc('legend', fontsize=MEDIUM_SIZE) # legend fontsize
plt.rc('figure', titlesize=BIGGER_SIZE) # fontsize of the figure title
plt.figure(figsize=(10, miniPT.shape[0]/5.2))
ax =sns.heatmap(miniPT, annot=False, cmap='RdYlGn')
for _, spine in ax.spines.items():
spine.set_visible(True)
# second axis
asset_list=np.asarray(PT['C'])
asset_list=asset_list[::-1]
ax3 = ax.twinx()
ax3.set_ylim([0,ax.get_ylim()[1]])
ax3.set_yticks(ax.get_yticks())
ax3.set_yticklabels(asset_list, fontsize=MEDIUM_SIZE*0.6)
# colorbar
cbar = ax.collections[0].colorbar
cbar.ax.tick_params(labelsize=MEDIUM_SIZE)
One way to get the overlap automatically adjusted by matplotlib, is to explicitly create subplots: one for the heatmap and another for the colorbar. sns.heatmap's cbar_ax= parameter can be set to point to this subplot. gridspec_kws= is needed to set the relative sizes. At the end, plt.tight_layout() will adjust all the paddings to make everything fit nicely.
The question's code contains some strange settings (e.g. a fontsize of 80 is immense). Also, 300 rows will inevitably lead to overlapping text (the fontsize needs to be so small that non-overlapping text wouldn't be readable). Here is some more simplified example code:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
PT = pd.DataFrame(np.random.randn(100, 3), columns=list('ABC'))
fig, (ax, cbar_ax) = plt.subplots(ncols=2, figsize=(10, len(PT) / 5.2), gridspec_kw={'width_ratios': [10, 1]})
sns.heatmap(PT.iloc[:, :-1], annot=False, cmap='RdYlGn', cbar_ax=cbar_ax, ax=ax)
for _, spine in ax.spines.items():
spine.set_visible(True)
# second axis
asset_list = np.asarray(PT['C'])
ax3 = ax.twinx()
ax3.set_ylim(ax.get_ylim())
ax3.set_yticks(np.arange(len(PT)))
ax3.set_yticklabels(asset_list, fontsize=80)
# colorbar
cbar_ax.tick_params(labelsize=80)
plt.tight_layout()
plt.show()
As the plot is quite large, here only the bottom part is pasted, with a link to the full plot.
This is how it would look like with:
fontsize 80 (Note that font sizes are measured in "points per inch", standard 72 points per inch);
figure width of 20 inches (instead of 10);
300 rows
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
PT = pd.DataFrame(np.random.randn(300, 3), columns=list('ABC'))
fig, (ax, cbar_ax) = plt.subplots(ncols=2, figsize=(20, len(PT) / 5.2), gridspec_kw={'width_ratios': [15, 1]})
sns.heatmap(PT.iloc[:, :-1], annot=False, cmap='RdYlGn', cbar_ax=cbar_ax, ax=ax)
for _, spine in ax.spines.items():
spine.set_visible(True)
# second axis
asset_list = np.asarray(PT['C'])
ax3 = ax.twinx()
ax3.set_ylim(ax.get_ylim())
ax3.set_yticks(np.arange(len(PT)))
ax3.set_yticklabels(asset_list, fontsize=80)
# colorbar
cbar_ax.tick_params(labelsize=80)
plt.tight_layout()
plt.show()
My solution was eventually move the colorbar to left side. This is the code and the output:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
PT = pd.DataFrame(np.random.randn(300, 3), columns=list('ABC'))
fig, (ax0, ax1) = plt.subplots(ncols=2, figsize=(10, len(PT) / 5.2), gridspec_kw={'width_ratios': [15, 15]})
sns.heatmap(PT.iloc[:, :-1], annot=False, cmap='RdYlGn', cbar_ax=ax0, ax=ax1)
for _, spine in ax1.spines.items():
spine.set_visible(True)
# second axis
asset_list = np.asarray(PT['C'])
ax3 = ax1.twinx()
ax3.set_ylim(ax1.get_ylim())
ax3.set_yticks(np.arange(len(PT)))
ax3.set_yticklabels(asset_list, fontsize=80)
# colorbar
ax0.tick_params(labelsize=80)
plt.tight_layout()
plt.show()

How to draw a dashed line across two subplots with Python?

import matplotlib.pyplot as plt
import numpy as np
time = np.linspace(-1,5, 100)
fig, (ax1, ax2, ax3) = plt.subplots(3,1, sharex=True, figsize=(5,5))
ax1.plot([-1,0,0,5], [1,1,0,0], color='navy')
ax1.set_ylim(-0.1,1.2)
ax2.plot(time, np.clip(np.exp(-time),0,1), color='darkolivegreen')
ax2.set_ylim(-0.1,1.2)
ax3.plot(time, np.clip(np.exp(-time),0,0.4), color='darksalmon')
ax3.set_ylim(-0.1,0.8)
I have made three subplots and arranged them vertically (x-axis shared). Is there a convenient way to add two vertical lines x=0 and x=-log(0.4) that go through all three subplots?
You can use axvline():
import matplotlib.pyplot as plt
import numpy as np
time = np.linspace(-1.5, 100)
fig, (ax1, ax2, ax3) = plt.subplots(3,1, sharex=True, figsize=(5,5))
ax1.plot([-1,0,0,5], [1,1,0,0], color='navy')
ax1.set_ylim(-0.1,1.2)
ax2.plot(time, np.clip(np.exp(-time),0,1), color='darkolivegreen')
ax2.set_ylim(-0.1,1.2)
ax3.plot(time, np.clip(np.exp(-time),0,0.4), color='darksalmon')
ax3.set_ylim(-0.1,0.8)
# add vertical lines to all subplots
for ax in [ax1, ax2, ax3]:
ax.axvline(20, c='r')
This gives:
If you want the vertical line to go through the whole figure, you need to set up an additional subplot as big as the three subplots taken together and make its background transparent. Then you can draw the line in the background subplot:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.gridspec import GridSpec
time = np.linspace(-1.5, 100)
fig = plt.figure()
gs = fig.add_gridspec(3, 2)
ax1 = fig.add_subplot(gs[0, :])
ax1.plot([-1,0,0,5], [1,1,0,0], color='navy')
ax1.set_ylim(-0.1,1.2)
ax2 = fig.add_subplot(gs[1, :], sharex = ax1)
ax2.plot(time, np.clip(np.exp(-time),0,1), color='darkolivegreen')
ax2.set_ylim(-0.1,1.2)
ax3 = fig.add_subplot(gs[2, :], sharex = ax1)
ax3.plot(time, np.clip(np.exp(-time),0,0.4), color='darksalmon')
ax3.set_ylim(-0.1,0.8)
# background axes object for plotting the vertical line
ax = fig.add_subplot(gs[:, :], sharex = ax1)
# set background color to transparent and turn off the frame
ax.patch.set_alpha(0)
ax.axis("off")
# plot the vertical line
ax.axvline(20, c='r')
plt.show()
This gives:

Matplotlib: how to remove spacing between a group of subplots

I have a series of pyplot subplots that I've created using a gridspec. They all have an hspace between them, which is fine, except that I would like to keep three of them without any space. Is there a way to do this? Currently, they look like this:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure()
grid_spec = gridspec.GridSpec(nrows=10, ncols=10)
grid_spec.update(hspace=1.5)
ax1 = plt.subplot(grid_spec[0:4, :])
ax2 = plt.subplot(grid_spec[4:7, :], sharex=ax1)
# I would like to group the next 3 together
# so that they are stacked top to bottom and side by side
ax3 = plt.subplot(grid_spec[7:8, :5])
ax4 = plt.subplot(grid_spec[8:, :5], sharex=ax3)
ax5 = plt.subplot(grid_spec[8:, 5:6], sharey=ax4)
plt.show()
I would like them to be arranged like this so I can plot the following 2-D KDE diagram and have the relevant 1-D diagrams above and to the right (roughly displaying this sort of data crudely drawn in paint):
I appreciate any help with this one. Can't seem to find documentation on this sort of thing. Thanks!
You can use mpl_toolkits.axes_grid1.make_axes_locatable to subdivide the area of a subplot of a 3 x 2 grid.
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
fig = plt.figure()
gs = fig.add_gridspec(nrows=3, ncols=2, hspace=.5,
height_ratios=[4, 3, 3], width_ratios=[7, 4])
ax1 = fig.add_subplot(gs[0, :])
ax2 = fig.add_subplot(gs[1, :], sharex=ax1)
ax3 = fig.add_subplot(gs[2, 0])
div = make_axes_locatable(ax3)
ax4 = div.append_axes("top", "40%", pad=0.2, sharex=ax3)
ax5 = div.append_axes("right", "25%", pad=0.2, sharey=ax3)
ax4.tick_params(labelbottom=False)
ax5.tick_params(labelleft=False)
plt.show()
Also, you can create a subgridspec, like
import matplotlib.pyplot as plt
from matplotlib import gridspec
fig = plt.figure()
gs = gridspec.GridSpec(nrows=3, ncols=2, hspace=.5,
height_ratios=[4, 3, 3], width_ratios=[7, 4])
ax1 = fig.add_subplot(gs[0, :])
ax2 = fig.add_subplot(gs[1, :], sharex=ax1)
sub_gs = gridspec.GridSpecFromSubplotSpec(2,2, subplot_spec=gs[2,0], hspace=0.3, wspace=0.1,
height_ratios=[1,3], width_ratios=[3,1])
ax3 = fig.add_subplot(sub_gs[1,0])
ax4 = fig.add_subplot(sub_gs[0,0], sharex=ax3)
ax5 = fig.add_subplot(sub_gs[1,1], sharey=ax3)
ax4.tick_params(labelbottom=False)
ax5.tick_params(labelleft=False)
plt.show()
In both cases you will probably want to fine tune the parameters a bit. In general, the matplotlib gridspec tutorial gives a nice overview with many examples on this matter.

Categories

Resources