Subplots margins to fit title and labels in matplotlib - python

I have read tons of answers on similar problems and none has helped.
I just want to increase the margins between the subplots and top, left and bottom edges in order to fit a fig.suptitle and common xlabel and ylabel.
import matplotlib.pyplot as plt
import random
#from matplotlib import rcParams
#rcParams['axes.titlepad'] = 20 # kind of works but shifts all titles, not only suptitle
def dummy(n):
return random.sample(range(1, 100), n)
data = dict()
for i in range(4):
data["area{}".format(i)] = [dummy(10), dummy(10)]
fig, ax = plt.subplots(2, 2, sharex='all', sharey='all', figsize=(10, 10))
fig.suptitle("Common title", fontsize=22)
ax = ax.reshape(-1)
for i, (area, data) in enumerate(data.items()):
ax[i].set_title(area)
ax[i].scatter(data[0], data[1])
fig.text(0.5, 0.01, 'xlabel', ha='center', fontsize=15)
fig.text(0.01, 0.5, 'ylabel', va='center', fontsize=15, rotation=90)
fig.tight_layout() # Does nothing
fig.subplots_adjust(top=0.85) # Does nothing
fig.show()
Matplotlib version 3.0.0
Python 3.6.6
Example plot from shared code

There are many ways to handle this, but I suggest using the gridspec_kw input to plt.subplots for this. The documentation for gridspec_kw is basically just the gridspec.GridSpec documentation. For example:
fig, ax = plt.subplots(2, 2, sharex='all', sharey='all',
gridspec_kw=dict(left=0.1, right=0.9,
bottom=0.1, top=0.9),
figsize=(10, 10))
The left, right, ... inputs to the gridspec_kw specify the extents of the entire group of subplots.
Does that give you what you're looking for?

Related

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()

Left aligning markers in legend with legend title using matplotlib

I'm trying to make a plot with Matplotlib, and I would look to have the legend on the top left. Without a legend title this is working fine with the code below, but when I add a legend title which is longer than the legend labels, the markers shift.
Is there any way I can prevent this and have them all align to the left?
import numpy as np
import matplotlib.pyplot as plt
# Data
x = np.arange(0, 4 * np.pi ,0.1)
y = np.sin(x)
fig, axs = plt.subplots(1, 2, figsize=(8, 4))
# Working fine without legend title
axs[0].plot(x, y, label='sin')
axs[0].legend(title='', loc='lower left', bbox_to_anchor=(0,1), facecolor='white', edgecolor='white', framealpha=1)
# Position shifts when using long legend title
axs[1].plot(x, y, label='sin')
axs[1].legend(title='Some longer title', loc='lower left', bbox_to_anchor=(0,1), facecolor='white', edgecolor='white', framealpha=1)
plt.show()
For me, your code does something similar to what you want to have by me simply changing the bbox_to_anchor argument from the axs[1].legend() command.
Now it reads: bbox_to_anchor=(-0.1,1) and it produces the below. Of course, you can change the first value to -0.15 or so if you want it to be more to the left.
Full code:
import numpy as np
import matplotlib.pyplot as plt
# Data
x = np.arange(0, 4 * np.pi ,0.1)
y = np.sin(x)
fig, axs = plt.subplots(1, 2, figsize=(8, 4))
# Working fine without legend title
axs[0].plot(x, y, label='sin')
axs[0].legend(title='', loc='lower left', bbox_to_anchor=(0,1), facecolor='white', edgecolor='white', framealpha=1)
# Position shifts when using long legend title
axs[1].plot(x, y, label='sin')
axs[1].legend(title='Some longer title', loc='lower left', bbox_to_anchor=(-0.1,1), facecolor='white', edgecolor='white', framealpha=1)
plt.show()
I found a workaround in a still unresolved issue on GitHub. Apparently the default alignment is "center", which can be changed like this.
axs[1].get_legend()._legend_box.align = "left"

using sharex with odd number of subplots in matplotlib

I have an odd number of subplots like so:
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 2, sharex=True)
for i, ax in enumerate(axes.flat):
ax.plot(range(10))
fig.delaxes(axes.flat[-1])
I want them all to have the same x-axis, but easily add the x-ticks back to the plot on the right, since there is no longer a 4th plot.
It seems like there should be an easier/cleaner solution than adding each subplot manually (similar to this answer), but I can't seem to find anything. Thanks.
you can use setp to make the xtick labels visible for ax[0][1] like this
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 2, sharex=True)
for i, ax in enumerate(axes.flat):
ax.plot(range(10))
# for matploltib version 2.0.1
plt.setp(axes[0][1].get_xticklabels(), visible=True)
# for matplotlib version 2.1.1
axes[0][1].xaxis.set_tick_params(which='both', labelbottom=True, labeltop=False)
fig.delaxes(axes.flat[-1])
plt.show()
which will result in

Set height and width of figure created with plt.subplots in matplotlib?

In matplotlib, I know how to set the height and width and DPI of a figure:
fig = plt.figure(figsize=(4, 5), dpi=100)
However, it seems that if I want to create small multiple plots, I can't create a figure like this, I have to use this:
fig, subplots = plt.subplots(nrows=4, ncols=4)
How can I set the height and width and DPI of a figure created with subplots like this?
You can actually specify height and widthplt.savefig('Desktop/test.png',dpi=500)
, even though it's not listed as keyword in the help (I think it is passed on to the figure call(?)):
fig,axs=plt.subplots(nrows,ncols,figsize=(width,height))
For some reason, dpi is ignored though. However, you can use it when saving the figure, when it is important:
plt.savefig('test.png',dpi=1000)
A working example of the gridspec module:
import matplotlib.pyplot as plt
from matplotlib import gridspec
fig = plt.figure(figsize=(18,18))
gs = gridspec.GridSpec(3, 3)
ax1 = fig.add_subplot(gs[0,:])
ax1.plot([1,2,3,4,5], [10,5,10,5,10], 'r-')
ax2 = fig.add_subplot(gs[1,:-1])
ax2.plot([1,2,3,4], [1,4,9,16], 'k-')
ax3 = fig.add_subplot(gs[1:, 2])
ax3.plot([1,2,3,4], [1,10,100,1000], 'b-')
ax4 = fig.add_subplot(gs[2,0])
ax4.plot([1,2,3,4], [0,0,1,1], 'g-')
ax5 = fig.add_subplot(gs[2,1])
ax5.plot([1,2,3,4], [1,0,0,1], 'c-')
gs.update(wspace=0.5, hspace=0.5)
plt.show()
But I prefer wrapping it in a function and using it like this:
def mySubplotFunction(fig,gs,x,y,c,ax=None):
if not ax:
ax = fig.add_subplot(gs)
ax.plot(x, y, c)
return fig, ax
Usage:
fig2 = plt.figure(figsize=(9,9))
fig2, ax1 = mySubplotFunction(fig2,gs[0,:],[1,2,3,4,5],[10,5,10,5,10],'r-');
fig2, ax2 = mySubplotFunction(fig2,gs[1,:-1],[1,2,3,4],[1,4,9,16],'k-');

Python Numpy add hspace between 3D plot and 2D plot with shared axes

Attached is an image showing the current plot. I am setting fig.subplots_adjust(hspace=0) for the 2D plots to share a common x-axis. I would like to add space between the 3D and 2d plots but am not quite sure how to accomplish this as hspace is set to 0.
fig.subplots_adjust(hspace=0)
for ax in [px_t, py_t, pz_t]:
plt.setp(ax.get_xticklabels(), visible=False)
In this case, it's best to use two separate GridSpec instances. That way you can have two separate hspace parameters. Alternatively, you can manually place the top axes.
As an example of the first option:
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
fig = plt.figure(figsize=(8, 10))
gs1 = plt.GridSpec(2, 1, hspace=0.2)
gs2 = plt.GridSpec(8, 1, hspace=0)
ax1 = fig.add_subplot(gs1[0], projection='3d')
ax1.plot(range(10), range(10), range(10))
ax = fig.add_subplot(gs2[4])
lower_axes = [ax]
for i in range(4, 8):
if i > 4:
ax = fig.add_subplot(gs2[i], sharex=lower_axes[0])
ax.plot(range(10))
ax.locator_params(axis='y', nbins=5, prune='both')
lower_axes.append(ax)
for ax in lower_axes:
ax.label_outer()
plt.show()

Categories

Resources