ChatGPT解决这个技术问题 Extra ChatGPT

Improve subplot size/spacing with many subplots in matplotlib

Very similar to this question but with the difference that my figure can be as large as it needs to be.

I need to generate a whole bunch of vertically-stacked plots in matplotlib. The result will be saved using figsave and viewed on a webpage, so I don't care how tall the final image is as long as the subplots are spaced so they don't overlap.

No matter how big I allow the figure to be, the subplots always seem to overlap.

My code currently looks like

import matplotlib.pyplot as plt
import my_other_module

titles, x_lists, y_lists = my_other_module.get_data()

fig = plt.figure(figsize=(10,60))
for i, y_list in enumerate(y_lists):
    plt.subplot(len(titles), 1, i)
    plt.xlabel("Some X label")
    plt.ylabel("Some Y label")
    plt.title(titles[i])
    plt.plot(x_lists[i],y_list)
fig.savefig('out.png', dpi=100)
This question also applies to pandas.DataFrame.plot with subplots, and to seaborn axes-level plots (those with the ax parameter): sns.lineplot(..., ax=ax)

T
Trenton McKinney

Try using plt.tight_layout

As a quick example:

import matplotlib.pyplot as plt

fig, axes = plt.subplots(nrows=4, ncols=4)
fig.tight_layout() # Or equivalently,  "plt.tight_layout()"

plt.show()

Without Tight Layout

https://i.stack.imgur.com/roV9q.png

https://i.stack.imgur.com/ouSJi.png


It's worth mentioning that this must be applied after adding the overlapping aspects. My x and y labels were overlapping neighboring graphs until I moved the fig.tight_layout() after. One can think of this function as saying "my figure layout is too tight now, please readjust"
This seems like a bad default to me. One wonder if there is a reasonable reason for why this has to be called and is not just done automagicly?
B
Brian Burns

You can use plt.subplots_adjust to change the spacing between the subplots (source)

call signature:

subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=None, hspace=None)

The parameter meanings (and suggested defaults) are:

left  = 0.125  # the left side of the subplots of the figure
right = 0.9    # the right side of the subplots of the figure
bottom = 0.1   # the bottom of the subplots of the figure
top = 0.9      # the top of the subplots of the figure
wspace = 0.2   # the amount of width reserved for blank space between subplots
hspace = 0.2   # the amount of height reserved for white space between subplots

The actual defaults are controlled by the rc file


I've tried messing with hspace, but increasing it only seems to make all of the graphs smaller without resolving the overlap problem. I've tried playing with the other parameters as well, but I don't know what left, right, bottom, and top are actually specifying there.
@mcstrother you can interactively change all 6 of those parameters if you click the 'adjustment' button after showing a plot, then copy them down into the code once you find what works.
I don't see an adjustment button. Although I'm in a Jupyter notebook. I tried %matplotlib inline and %matplotlib notebook.
@MattKleinsmith: The adjustment button has the hover text "Configure subplots" and appears in regular non-notebook uses of Matplotlib. It is the button to the left of the "floppy disk" save button here: pythonspot-9329.kxcdn.com/wp-content/uploads/2016/07/… - note the button looks different depending on what window system you're using, but it's always to the left of the save button.
@JohnZwinck, the link in your comment is dead now.
A
Alexa Halford

I found that subplots_adjust(hspace = 0.001) is what ended up working for me. When I use space = None, there is still white space between each plot. Setting it to something very close to zero however seems to force them to line up. What I've uploaded here isn't the most elegant piece of code, but you can see how the hspace works.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as tic

fig = plt.figure()

x = np.arange(100)
y = 3.*np.sin(x*2.*np.pi/100.)

for i in range(5):
    temp = 510 + i
    ax = plt.subplot(temp)
    plt.plot(x,y)
    plt.subplots_adjust(hspace = .001)
    temp = tic.MaxNLocator(3)
    ax.yaxis.set_major_locator(temp)
    ax.set_xticklabels(())
    ax.title.set_visible(False)

plt.show()

https://i.stack.imgur.com/52ZH1.png


This code produces an error : ValueError Traceback (most recent call last) in 10 for i in range(5): 11 temp = 510 + i ---> 12 ax = plt.subplot(temp) ValueError: num must be 1 <= num <= 5, not 0
S
Steven C. Howell

Similar to tight_layout matplotlib now (as of version 2.2) provides constrained_layout. In contrast to tight_layout, which may be called any time in the code for a single optimized layout, constrained_layout is a property, which may be active and will optimze the layout before every drawing step.

Hence it needs to be activated before or during subplot creation, such as figure(constrained_layout=True) or subplots(constrained_layout=True).

Example:

import matplotlib.pyplot as plt

fig, axes = plt.subplots(4,4, constrained_layout=True)

plt.show()

https://i.stack.imgur.com/HcF1b.png

constrained_layout may as well be set via rcParams

plt.rcParams['figure.constrained_layout.use'] = True

See the what's new entry and the Constrained Layout Guide


going to try this out: had not seen this option - and tight_layout is unreliable
this sounded promising but didn't give me enough spacing (axes labels and titles still overlapped) and rendering took much longer. tight_layout() worked better
@craq Correct, in general contrained_layout is slower, because as seen in this answer, it optimzes the layout before every drawing step.
for me this was the most useful answer - tight_layout for me always improves the vertical spacing to leave room for the panel title, but at the cost of cutting off the y-axis label each time. This, instead, works perfectly, thanks.
@craq, if you have a reproducible example that fails to properly space the axes, it would be very helpful if you opened an issue at github.com/matplotlib/matplotlib The newest Matplotlib (3.4.x) is much faster with constrained_layout.
B
Brian Burns
import matplotlib.pyplot as plt

fig = plt.figure(figsize=(10,60))
plt.subplots_adjust( ... )

The plt.subplots_adjust method:

def subplots_adjust(*args, **kwargs):
    """
    call signature::

      subplots_adjust(left=None, bottom=None, right=None, top=None,
                      wspace=None, hspace=None)

    Tune the subplot layout via the
    :class:`matplotlib.figure.SubplotParams` mechanism.  The parameter
    meanings (and suggested defaults) are::

      left  = 0.125  # the left side of the subplots of the figure
      right = 0.9    # the right side of the subplots of the figure
      bottom = 0.1   # the bottom of the subplots of the figure
      top = 0.9      # the top of the subplots of the figure
      wspace = 0.2   # the amount of width reserved for blank space between subplots
      hspace = 0.2   # the amount of height reserved for white space between subplots

    The actual defaults are controlled by the rc file
    """
    fig = gcf()
    fig.subplots_adjust(*args, **kwargs)
    draw_if_interactive()

or

fig = plt.figure(figsize=(10,60))
fig.subplots_adjust( ... )

The size of the picture matters.

"I've tried messing with hspace, but increasing it only seems to make all of the graphs smaller without resolving the overlap problem."

Thus to make more white space and keep the sub plot size the total image needs to be bigger.


The size of the picture matters, bigger picture size can solve this problem! set plt.figure(figsize=(10, 7)), the picture's size would be 2000 x 1400 pix
C
CiaranWelsh

You could try the subplot_tool()

plt.subplot_tool()

T
Trenton McKinney

Resolving this issue when plotting a dataframe with pandas.DataFrame.plot, which uses matplotlib as the default backend. The following works for whichever kind= is specified (e.g. 'bar', 'scatter', 'hist', etc.)

The following works for whichever kind= is specified (e.g. 'bar', 'scatter', 'hist', etc.)

Tested in python 3.8.12, pandas 1.3.4, matplotlib 3.4.3

Imports and sample data

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# sinusoidal sample data
sample_length = range(1, 15+1)
rads = np.arange(0, 2*np.pi, 0.01)
data = np.array([np.sin(t*rads) for t in sample_length])
df = pd.DataFrame(data.T, index=pd.Series(rads.tolist(), name='radians'), columns=[f'freq: {i}x' for i in sample_length])

# display(df.head(3))
         freq: 1x  freq: 2x  freq: 3x  freq: 4x  freq: 5x  freq: 6x  freq: 7x  freq: 8x  freq: 9x  freq: 10x  freq: 11x  freq: 12x  freq: 13x  freq: 14x  freq: 15x
radians                                                                                                                                                            
0.00     0.000000  0.000000  0.000000  0.000000  0.000000  0.000000  0.000000  0.000000  0.000000   0.000000   0.000000   0.000000   0.000000   0.000000   0.000000
0.01     0.010000  0.019999  0.029996  0.039989  0.049979  0.059964  0.069943  0.079915  0.089879   0.099833   0.109778   0.119712   0.129634   0.139543   0.149438
0.02     0.019999  0.039989  0.059964  0.079915  0.099833  0.119712  0.139543  0.159318  0.179030   0.198669   0.218230   0.237703   0.257081   0.276356   0.295520

# default plot with subplots; each column is a subplot
axes = df.plot(subplots=True)

https://i.stack.imgur.com/9K9iM.png

Adjust the Spacing

Adjust the default parameters in pandas.DataFrame.plot Change figsize: a width of 5 and a height of 4 for each subplot is a good place to start Change layout: (rows, columns) for the layout of subplots. sharey=True and sharex=True so space isn't taken for redundant labels on each subplot.

Change figsize: a width of 5 and a height of 4 for each subplot is a good place to start

Change layout: (rows, columns) for the layout of subplots.

sharey=True and sharex=True so space isn't taken for redundant labels on each subplot.

The .plot method returns a numpy array of matplotlib.axes.Axes, which should be flattened to easily work with.

Use .get_figure() to extract the DataFrame.plot figure object from one of the Axes.

Use fig.tight_layout() if desired.

axes = df.plot(subplots=True, layout=(3, 5), figsize=(25, 16), sharex=True, sharey=True)

# flatten the axes array to easily access any subplot
axes = axes.flat

# extract the figure object
fig = axes[0].get_figure()

# use tight_layout
fig.tight_layout()

https://i.stack.imgur.com/ncA2i.png