Matplotlib tight_layout () berücksichtigt keinen Abbildungstitel

207

Wenn ich meiner Matplotlib-Figur einen Untertitel hinzufüge, wird dieser von den Titeln des Unterplots überlagert. Weiß jemand, wie man sich leicht darum kümmert? Ich habe die tight_layout()Funktion ausprobiert , aber sie macht die Sache nur noch schlimmer.

Beispiel:

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
quelle

Antworten:

178

Sie können die Unterplotgeometrie im tight_layoutAufruf wie folgt anpassen :

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

Wie in der Dokumentation angegeben ( https://matplotlib.org/users/tight_layout_guide.html ):

tight_layout()Berücksichtigt nur Ticklabels, Achsenbeschriftungen und Titel. Somit können andere Künstler abgeschnitten werden und sich auch überlappen.

Suppeault
quelle
1
Es hat wie ein Zauber funktioniert, aber wie geschieht die Änderung bei der Angabe des Begrenzungsrahmens? Ich habe das Dokument gelesen, aber mir war nicht viel klar. Es wäre toll, wenn Sie es mir bitte erklären könnten! Vielen Dank.
Thepunitsingh
2
Könnten Sie bitte die Bedeutung jeder Zahl in rect erklären? Ich habe sie im Dokument nicht gesehen.
Steven
6
@steven siehe tight_layoutDokumente:[left, bottom, right, top] in normalized (0, 1) figure coordinates
Suppeault
1
Sie können den Abstand zwischen Suptitle und Achsen noch weiter vergrößern, indem Sie den am weitesten rechts liegenden Wert verringern: tight_layout(rect=[0, 0.03, 1, 0.9])anstatt 0.95wie in der Antwort.
ComFreek
1
Hat im Jupiter nicht funktioniert. Versuchte verschiedene Werte für Zahlen, hatte keine Auswirkung
Aleksejs Fomins
114

Sie können den Abstand manuell anpassen, indem Sie 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
quelle
5
Beachten Sie, dass der Standardwert für top 0,9 ist, wenn Sie anrufensubplots_adjust
Brian Burns
72
Ich kann nicht glauben, dass das Jahr 2017 unseres Herrn immer noch ein Fehler von Matplotlib ist.
Worte für den
29
Beachten Sie, dass subplots_adjustdies nach jedem tight_layoutAnruf bei aufgerufen werden muss , da sonst keine Auswirkungen auf den Anruf auftreten subplots_adjust.
Achal Dave
7
@wordsforthewise machen, dass 2018 jetzt
vlsd
6
@vlsd Hallo von 2019
Xiaoyu Lu
55

Eine Sache, die Sie in Ihrem Code sehr leicht ändern können, ist die, die fontsizeSie für die Titel verwenden. Ich gehe jedoch davon aus, dass Sie das nicht nur tun wollen!

Einige Alternativen zur Verwendung fig.subplots_adjust(top=0.85):

Normalerweise tight_layout()macht es einen ziemlich guten Job, alles an guten Orten zu positionieren, damit sie sich nicht überlappen. Der Grund, tight_layout()der in diesem Fall nicht hilft, ist, dass tight_layout()fig.suptitle () nicht berücksichtigt wird. Auf GitHub gibt es dazu ein offenes Problem: https://github.com/matplotlib/matplotlib/issues/829 [2014 geschlossen, da ein vollständiger Geometriemanager erforderlich ist - verschoben auf https://github.com/matplotlib/matplotlib / Issues / 1109 ].

Wenn Sie den Thread lesen, gibt es eine Lösung für Ihr Problem GridSpec. Der Schlüssel ist, beim Aufrufen tight_layoutmit dem rectkwarg etwas Platz oben in der Figur zu lassen. Für Ihr Problem lautet der Code:

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

Das Ergebnis:

mit gridspec

Vielleicht GridSpecist es ein bisschen übertrieben für Sie, oder Ihr eigentliches Problem wird viel mehr Nebenhandlungen auf einer viel größeren Leinwand oder andere Komplikationen beinhalten. Ein einfacher Hack besteht darin, einfach annotate()die Koordinaten zu verwenden und zu sperren 'figure fraction', um a zu imitieren suptitle. Möglicherweise müssen Sie jedoch einige feinere Anpassungen vornehmen, wenn Sie sich die Ausgabe ansehen. Beachten Sie, dass diese zweite Lösung nicht verwendet wird tight_layout().

Einfachere Lösung (muss jedoch möglicherweise verfeinert werden)

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

Das Ergebnis:

einfach

[Verwenden von Python2.7.3 (64-Bit) und matplotlib1.2.0]

Aseagramm
quelle
1
Vielen Dank, wenn ich die erste von Ihnen vorgeschlagene Lösung verwende, GridSpecwie kann ich Unterzeichnungen erstellen, die Achsen gemeinsam nutzen? Ich benutze normalerweise, plt.subplots(N,1, sharex=<>, sharey=<>)wenn ich Nebenhandlungen erstelle, aber der Code, den Sie gepostet haben, verwendet add_subplotstattdessen
Amelio Vazquez-Reina
10
Casting fig.tight_layout(rect=[0, 0.03, 1, 0.95])funktioniert auch.
Suppeault
1
Die zweite Lösung ist die einzige, die für mich funktioniert hat. Danke dir!
Hagbard
19

Eine alternative und einfach zu verwendende Lösung besteht darin, die Koordinaten des Untertiteltextes in der Abbildung mithilfe des y-Arguments im Aufruf von suptitle anzupassen (siehe Dokumentation ):

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
quelle
8
Dies ist eine großartige Möglichkeit in einem Notizbuch, aber der Befehl plt.savefig () gehorcht nicht. Diese Lösung schneidet den Titel in einem Befehl savefig weiterhin ab.
Superheld
11

Enges Layout funktioniert nicht mit Suptitle, aber es constrained_layoutfunktioniert. Siehe diese Frage Verbessern Sie die Größe / den Abstand von Unterplots mit vielen Unterplots in matplotlib

Ich fand, dass das Hinzufügen der Nebenhandlungen auf einmal besser aussah, dh

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

# then iterating over the axes to fill in the plots

Es kann aber auch an dem Punkt hinzugefügt werden, an dem die Figur erstellt wird:

fig = plt.figure(constrained_layout=True)

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

Hinweis: Um meine Nebenhandlungen näher zusammenzubringen, habe ich auch verwendet

fig.subplots_adjust(wspace=0.05)

und Constrained_layout funktioniert damit nicht :(

Bhayley
quelle
4

Ich habe mit dem matplotlib Trimmen Methoden zu kämpfen, so dass ich jetzt habe gerade eine Funktion machte dies über eine tun bashAufruf ImageMagick‚s mogrify Befehl , die gut funktioniert und bekommt alle zusätzlichen Leerraum aus dem Wert des Rand. Dies setzt voraus, dass Sie UNIX / Linux verwenden, die bashShell verwenden und ImageMagickinstalliert haben.

Rufen Sie einfach nach Ihrem an savefig() Anruf an.

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
quelle
3

Wie von anderen erwähnt, berücksichtigt das enge Layout standardmäßig keine Untertitel. Ich habe jedoch festgestellt, dass es möglich ist, das bbox_extra_artistsArgument zu verwenden, um den Untertitel als Begrenzungsrahmen zu übergeben, der berücksichtigt werden sollte:

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

Dies zwingt die strenge Layoutberechnung dazu, dies suptitlezu berücksichtigen, und es sieht so aus, wie Sie es erwarten würden.

Herman Schaaf
quelle
Dies ist die einzige Option, die für mich funktioniert hat. Ich benutze Jupyter-Notizbücher, um Figuren zu machen und sie zu speichern. Ich arbeite in Python 3.6 auf einem Linux-Computer.
GRquanti
1

Ich hatte ein ähnliches Problem, das bei der Verwendung tight_layoutfür ein sehr großes Raster von Plots (mehr als 200 Unterplots) und beim Rendern in einem Jupyter-Notizbuch auftrat. Ich habe eine schnelle Lösung gefunden, bei der Sie immer suptitlein einem bestimmten Abstand über Ihrer obersten Nebenhandlung platziert werden:

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)

Bei Unterplots mit variabler Größe können Sie diese Methode weiterhin verwenden, um den oberen Rand des obersten Unterplots abzurufen und dann manuell einen zusätzlichen Betrag zu definieren, der dem Suptitle hinzugefügt werden soll.

Brian Pollack
quelle
1

Das einzige, was für mich funktioniert hat, war, den Aufruf zu suptitle zu ändern:

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