Matplotlib tight_layout () ne prend pas en compte le sous-titre de la figure

208

Si j'ajoute un sous-titre à ma figure matplotlib, il est superposé par les titres du sous-intrigue. Quelqu'un sait-il comment s'en occuper facilement? J'ai essayé la tight_layout()fonction, mais cela ne fait qu'empirer les choses.

Exemple:

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()
katrasnikj
la source

Réponses:

183

Vous pouvez ajuster la géométrie du sous-tracé dans l' tight_layoutappel même comme suit:

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

Comme indiqué dans la documentation ( https://matplotlib.org/users/tight_layout_guide.html ):

tight_layout()ne prend en compte que les ticklabels, les étiquettes d'axe et les titres. Ainsi, d'autres artistes peuvent être coupés et peuvent également se chevaucher.

soupault
la source
1
Cela a fonctionné comme un charme, mais comment le changement se produit-il lors de la spécification de la boîte englobante? J'ai lu le doc mais pas grand-chose était clair pour moi. Ce serait génial si vous pouviez m'expliquer! Merci.
thepunitsingh
2
pourriez-vous s'il vous plaît expliquer la signification de chaque nombre en rect? Je ne les ai pas vus dans le doc.
Steven
6
@steven voir les tight_layoutdocuments:[left, bottom, right, top] in normalized (0, 1) figure coordinates
soupault
1
Vous pouvez augmenter encore plus l'espace entre les sous-titres et les axes en diminuant la valeur la plus à droite: tight_layout(rect=[0, 0.03, 1, 0.9])au lieu de 0.95celle indiquée dans la réponse.
ComFreek
1
N'a pas fonctionné à l'intérieur de jupyter. A essayé différentes valeurs pour les nombres, n'a eu aucun effet
Aleksejs Fomins
114

Vous pouvez ajuster manuellement l'espacement en utilisant 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()
unutbu
la source
5
Notez que la valeur par défaut pour top est 0,9 lorsque vous appelezsubplots_adjust
Brian Burns
72
Je ne peux pas croire en l'année 2017 de notre seigneur c'est toujours un bug de matplotlib.
wordsforhewise
29
Notez que cela subplots_adjustdoit être appelé après tout appel à tight_layout, sinon l'appel n'aura aucun effet subplots_adjust.
Achal Dave
7
@wordsforthewise fait que 2018 maintenant
vlsd
6
@vlsd Bonjour de 2019
Xiaoyu Lu
55

Une chose que vous pourriez changer très facilement dans votre code est celle que fontsizevous utilisez pour les titres. Cependant, je vais supposer que vous ne voulez pas simplement faire ça!

Quelques alternatives à l'utilisation fig.subplots_adjust(top=0.85):

Fait généralement tight_layout()un assez bon travail pour tout placer dans de bons endroits afin qu'ils ne se chevauchent pas. La raison tight_layout()n'aide pas dans ce cas parce tight_layout()que ne prend pas en compte fig.suptitle (). Il existe un problème ouvert à ce sujet sur GitHub: https://github.com/matplotlib/matplotlib/issues/829 [fermé en 2014 en raison de la nécessité d'un gestionnaire de géométrie complet - déplacé vers https://github.com/matplotlib/matplotlib / issues / 1109 ].

Si vous lisez le fil, il existe une solution à votre problème impliquant GridSpec. La clé est de laisser un espace en haut de la figure lors de l'appel tight_layout, en utilisant le rectkwarg. Pour votre problème, le code devient:

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

Le résultat:

en utilisant gridspec

C'est peut GridSpec- être un peu exagéré pour vous, ou votre vrai problème impliquera beaucoup plus de sous-intrigues sur une toile beaucoup plus grande, ou d'autres complications. Un hack simple consiste à simplement utiliser annotate()et verrouiller les coordonnées 'figure fraction'pour imiter a suptitle. Cependant, vous devrez peut-être effectuer des ajustements plus fins une fois que vous aurez examiné la sortie. Notez que cette deuxième solution ne pas utiliser tight_layout().

Solution plus simple (mais peut-être besoin d'être affinée)

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

Le résultat:

Facile

[Utilisation de Python2.7.3 (64 bits) et matplotlib1.2.0]

aseagram
la source
1
Merci, si j'utilise la première solution que vous avez suggérée (en utilisant GridSpec), comment puis-je créer des sous-parcelles qui partagent des axes? J'utilise habituellement plt.subplots(N,1, sharex=<>, sharey=<>)lors de la création de sous-intrigues, mais le code que vous avez publié utilise à la add_subplotplace
Amelio Vazquez-Reina
10
Le casting fig.tight_layout(rect=[0, 0.03, 1, 0.95])fonctionne également.
soupault
1
La deuxième solution est la seule qui a fonctionné pour moi. Je vous remercie!
Hagbard
19

Une solution alternative et simple à utiliser consiste à ajuster les coordonnées du texte de sous-titre dans la figure en utilisant l'argument y dans l'appel de sous-titre (voir la documentation ):

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()
Puggie
la source
8
C'est un excellent moyen dans un cahier mais la commande plt.savefig () n'obéit pas. Cette solution coupe toujours le titre dans une commande savefig.
super
11

Une disposition serrée ne fonctionne pas avec les sous-titres, mais le constrained_layoutfait. Voir cette question Améliorer la taille / l'espacement des sous-parcelles avec de nombreuses sous-parcelles dans matplotlib

J'ai trouvé que l'ajout des sous-parcelles était à la fois mieux,

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

# then iterating over the axes to fill in the plots

Mais il peut également être ajouté au moment où la figure est créée:

fig = plt.figure(constrained_layout=True)

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

Remarque: Pour rapprocher mes sous-parcelles, j'utilisais également

fig.subplots_adjust(wspace=0.05)

et constrained_layout ne fonctionne pas avec ceci :(

bhayley
la source
4

J'ai eu du mal avec les méthodes de découpage matplotlib, donc je viens de créer une fonction pour le faire via un bashappel à ImageMagickla commande mogrify , qui fonctionne bien et obtient tout l'espace blanc supplémentaire du bord de la figure. Cela nécessite que vous utilisiez UNIX / Linux, que vous utilisiez le bashshell et que vous l'ayez ImageMagickinstallé.

Appelez simplement cela après votre savefig()appel.

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)
ryanjdillon
la source
3

Comme mentionné par d'autres, par défaut, la mise en page serrée ne prend pas en compte les sous-titres. Cependant, j'ai trouvé qu'il est possible d'utiliser l' bbox_extra_artistsargument pour passer le sous-titre comme une boîte englobante qui devrait être prise en compte:

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

Cela force le calcul de mise en page serré à prendre suptitleen compte, et il ressemble à ce que vous attendez.

Herman Schaaf
la source
C'est la seule option qui a fonctionné pour moi. J'utilise des cahiers Jupyter pour faire des figures et les enregistrer. Je travaille en python 3.6 sur une machine Linux.
GRquanti
2

La seule chose qui a fonctionné pour moi a été de modifier l'appel au sous-titre:

fig.suptitle("title", y=.995)
Markemus
la source
1

J'ai eu un problème similaire qui s'est produit lors de l'utilisation tight_layoutpour une très grande grille de tracés (plus de 200 sous-tracés) et le rendu dans un cahier jupyter. J'ai fait une solution rapide qui place toujours votre suptitleà une certaine distance au-dessus de votre sous-intrigue supérieure:

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)

Pour les sous-tracés de taille variable, vous pouvez toujours utiliser cette méthode pour obtenir le haut du sous-tracé le plus haut, puis définir manuellement un montant supplémentaire à ajouter au sous-titre.

Brian Pollack
la source