Matplotlib tight_layout () non tiene conto della cifra del sottotitolo


208

Se aggiungo un sottotitolo alla mia figura matplotlib, questo viene sovrapposto dai titoli della sottotrama. Qualcuno sa come occuparsene facilmente? Ho provato la tight_layout()funzione, ma non fa che peggiorare le cose.

Esempio:

import numpy as np
import matplotlib.pyplot as plt

f = np.random.random(100)
g = np.random.random(100)
fig = plt.figure()
fig.suptitle('Long Suptitle', fontsize=24)
plt.subplot(121)
plt.plot(f)
plt.title('Very Long Title 1', fontsize=20)
plt.subplot(122)
plt.plot(g)
plt.title('Very Long Title 2', fontsize=20)
plt.tight_layout()
plt.show()

Risposte:


183

È possibile regolare la geometria della sottotrama nella tight_layoutchiamata stessa come segue:

fig.tight_layout(rect=[0, 0.03, 1, 0.95])

Come indicato nella documentazione ( https://matplotlib.org/users/tight_layout_guide.html ):

tight_layout()considera solo ticklabel, etichette degli assi e titoli. Pertanto, altri artisti possono essere ritagliati e possono anche sovrapporsi.


1
Ha funzionato come per incanto, ma come avviene il cambiamento quando si specifica il riquadro di selezione? Ho letto il documento, ma non mi era molto chiaro. Sarebbe bello se potessi spiegarmi per favore! Grazie.
thepunitsingh

2
potresti spiegare il significato di ciascun numero in rettangolo? Non li ho visti nel documento.
Steven

6
@steven see tight_layoutdocs:[left, bottom, right, top] in normalized (0, 1) figure coordinates
soupault

1
Puoi aumentare ulteriormente lo spazio tra sottotitoli e assi abbassando il valore più giusto: tight_layout(rect=[0, 0.03, 1, 0.9])anziché 0.95come nella risposta.
ComFreek,

1
Non ha funzionato all'interno di Giove. Ho provato valori diversi per i numeri, senza alcun effetto
Aleksejs Fomins il

114

Puoi regolare manualmente la spaziatura usando plt.subplots_adjust(top=0.85):

import numpy as np
import matplotlib.pyplot as plt

f = np.random.random(100)
g = np.random.random(100)
fig = plt.figure()
fig.suptitle('Long Suptitle', fontsize=24)
plt.subplot(121)
plt.plot(f)
plt.title('Very Long Title 1', fontsize=20)
plt.subplot(122)
plt.plot(g)
plt.title('Very Long Title 2', fontsize=20)
plt.subplots_adjust(top=0.85)
plt.show()

5
Nota che il valore predefinito per top è 0.9 quando chiamisubplots_adjust
Brian Burns,

72
Non posso credere nell'anno 2017 del nostro signore, questo è ancora un bug di matplotlib.
parole per il

29
Si noti che subplots_adjustdeve essere chiamato dopo qualsiasi chiamata a tight_layout, o non ci sarà alcun effetto di chiamata subplots_adjust.
Achal Dave,

7
@wordsforthewise lo rendono ora 2018
vlsd

6
@vlsd Hello dal 2019
Xiaoyu Lu

55

Una cosa che potresti cambiare nel tuo codice molto facilmente è quella fontsizeche stai usando per i titoli. Tuttavia, suppongo che tu non voglia semplicemente farlo!

Alcune alternative all'utilizzo fig.subplots_adjust(top=0.85):

Di solito tight_layout()fa un ottimo lavoro nel posizionare tutto in buone posizioni in modo che non si sovrappongano. Il motivo tight_layout()non aiuta in questo caso perché tight_layout()non tiene conto di fig.suptitle (). C'è un problema aperto al riguardo su GitHub: https://github.com/matplotlib/matplotlib/issues/829 [chiuso nel 2014 a causa della necessità di un gestore della geometria completo - spostato su https://github.com/matplotlib/matplotlib/matplotlib/matplotlib / issues / 1109 ].

Se leggi la discussione, c'è una soluzione al tuo problema GridSpec. La chiave è lasciare un po 'di spazio nella parte superiore della figura quando si chiama tight_layout, usando il rectkwarg. Per il tuo problema, il codice diventa:

Utilizzando GridSpec

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec

f = np.random.random(100)
g = np.random.random(100)

fig = plt.figure(1)
gs1 = gridspec.GridSpec(1, 2)
ax_list = [fig.add_subplot(ss) for ss in gs1]

ax_list[0].plot(f)
ax_list[0].set_title('Very Long Title 1', fontsize=20)

ax_list[1].plot(g)
ax_list[1].set_title('Very Long Title 2', fontsize=20)

fig.suptitle('Long Suptitle', fontsize=24)    

gs1.tight_layout(fig, rect=[0, 0.03, 1, 0.95])  

plt.show()

Il risultato:

usando gridspec

Forse GridSpecè un po 'eccessivo per te, o il tuo vero problema coinvolgerà molte più trame secondarie su una tela molto più grande, o altre complicazioni. Un semplice trucco è semplicemente usare annotate()e bloccare le coordinate 'figure fraction'per imitare un suptitle. Tuttavia, potrebbe essere necessario apportare alcune modifiche più precise dopo aver dato un'occhiata all'output. Si noti che questa seconda soluzione non utilizza tight_layout().

Soluzione più semplice (anche se potrebbe essere necessario perfezionare)

fig = plt.figure(2)

ax1 = plt.subplot(121)
ax1.plot(f)
ax1.set_title('Very Long Title 1', fontsize=20)

ax2 = plt.subplot(122)
ax2.plot(g)
ax2.set_title('Very Long Title 2', fontsize=20)

# fig.suptitle('Long Suptitle', fontsize=24)
# Instead, do a hack by annotating the first axes with the desired 
# string and set the positioning to 'figure fraction'.
fig.get_axes()[0].annotate('Long Suptitle', (0.5, 0.95), 
                            xycoords='figure fraction', ha='center', 
                            fontsize=24
                            )
plt.show()

Il risultato:

semplice

[Utilizzo di Python2.7.3 (64 bit) e matplotlib1.2.0]


1
Grazie, se utilizzo la prima soluzione che mi hai suggerito (utilizzando GridSpec) come posso creare sottotrame che condividono gli assi? Di solito lo uso plt.subplots(N,1, sharex=<>, sharey=<>)durante la creazione di sottotrame, ma add_subplotinvece usa il codice che hai pubblicato
Amelio Vazquez-Reina

10
Anche il casting fig.tight_layout(rect=[0, 0.03, 1, 0.95])funziona.
soupault

1
La seconda soluzione è l'unica che ha funzionato per me. Grazie!
Hagbard

19

Una soluzione alternativa e semplice da usare è quella di regolare le coordinate del testo dei sottotitoli nella figura usando l'argomento y nella chiamata del sottotitolo (vedi i documenti ):

import numpy as np
import matplotlib.pyplot as plt

f = np.random.random(100)
g = np.random.random(100)
fig = plt.figure()
fig.suptitle('Long Suptitle', y=1.05, fontsize=24)
plt.subplot(121)
plt.plot(f)
plt.title('Very Long Title 1', fontsize=20)
plt.subplot(122)
plt.plot(g)
plt.title('Very Long Title 2', fontsize=20)
plt.show()

8
Questo è un ottimo modo in un notebook ma il comando plt.savefig () non obbedisce. Questa soluzione interrompe ancora il titolo in un comando savefig.
supereroe,

11

Il layout stretto non funziona con i sottotitoli, ma constrained_layoutfunziona. Visualizza questa domanda Migliora le dimensioni / la spaziatura della sottotrama con molte sottotrame in matplotlib

Ho scoperto che l'aggiunta delle sottotrame contemporaneamente sembrava migliore, ad es

fig, axs = plt.subplots(rows, cols, constrained_layout=True)

# then iterating over the axes to fill in the plots

Ma può anche essere aggiunto nel punto in cui viene creata la figura:

fig = plt.figure(constrained_layout=True)

ax1 = fig.add_subplot(cols, rows, 1)
# etc

Nota: per rendere più vicini i miei subplot, stavo anche usando

fig.subplots_adjust(wspace=0.05)

e obligined_layout non funziona con questo :(


4

Ho lottato con la matplotlib rifilatura metodi, così ora ho appena fatto una funzione per fare questo tramite una bashchiamata a ImageMagick's comando mogrify , che funziona bene e ottiene tutti gli spazi bianchi in più fuori bordo della figura. Ciò richiede l'utilizzo di UNIX / Linux, l'utilizzo della bashshell e l' ImageMagickinstallazione.

Basta inviare una chiamata a questo dopo la savefig()chiamata.

def autocrop_img(filename):
    '''Call ImageMagick mogrify from bash to autocrop image'''
    import subprocess
    import os

    cwd, img_name = os.path.split(filename)

    bashcmd = 'mogrify -trim %s' % img_name
    process = subprocess.Popen(bashcmd.split(), stdout=subprocess.PIPE, cwd=cwd)

3

Come menzionato da altri, per impostazione predefinita il layout stretto non tiene conto dei sottotitoli. Tuttavia, ho scoperto che è possibile utilizzare l' bbox_extra_artistsargomento per passare il sottotitolo come riquadro di selezione che dovrebbe essere preso in considerazione:

st = fig.suptitle("My Super Title")
plt.savefig("figure.png", bbox_extra_artists=[st], bbox_inches='tight')

Ciò impone che il calcolo rigoroso del layout suptitletenga conto di ciò che sembra e che ti aspetteresti.


Questa è l'unica opzione che ha funzionato per me. Uso i quaderni Jupyter per creare figure e salvarle. Lavoro in Python 3.6 su una macchina Linux.
GRquanti

2

L'unica cosa che ha funzionato per me è stata la modifica della chiamata al sottotitolo:

fig.suptitle("title", y=.995)

1

Ho avuto un problema simile che è emerso quando si utilizzava tight_layoutper una griglia molto grande di grafici (più di 200 sottotrame) e il rendering in un notebook jupyter. Ho creato una soluzione rapida che posiziona sempre il tuo suptitlea una certa distanza sopra la sottotrama superiore:

import matplotlib.pyplot as plt

n_rows = 50
n_col = 4
fig, axs = plt.subplots(n_rows, n_cols)

#make plots ...

# define y position of suptitle to be ~20% of a row above the top row
y_title_pos = axs[0][0].get_position().get_points()[1][1]+(1/n_rows)*0.2
fig.suptitle('My Sup Title', y=y_title_pos)

Per sottotrame di dimensioni variabili, è ancora possibile utilizzare questo metodo per ottenere la parte superiore del sottotrama più in alto, quindi definire manualmente un importo aggiuntivo da aggiungere al sottotitolo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.