Matplotlib tight_layout () nie uwzględnia napisów do figur

208

Jeśli dodam podtytuł do mojej figury matplotlib, zostanie on nałożony na tytuły podplotu. Czy ktoś wie, jak łatwo się tym zająć? Próbowałem tej tight_layout()funkcji, ale to tylko pogarsza sytuację.

Przykład:

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
źródło

Odpowiedzi:

179

Możesz dostosować geometrię podplota w samym tight_layoutwywołaniu w następujący sposób:

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

Jak stwierdzono w dokumentacji ( https://matplotlib.org/users/tight_layout_guide.html ):

tight_layout()uwzględnia tylko etykiety, etykiety osi i tytuły. W ten sposób inni artyści mogą zostać przycięci i mogą się nakładać.

zupa
źródło
1
Działa jak urok, ale jak zmienia się przy określaniu obwiedni? Przeczytałem dokument, ale niewiele było dla mnie jasne. Byłoby wspaniale, gdybyś mógł mi wyjaśnić! Dzięki.
thepunitsingh
2
czy mógłbyś wyjaśnić znaczenie każdej liczby bezpośrednio? Nie widziałem ich w dokumencie.
Steven
6
@steven patrz tight_layout: docs:[left, bottom, right, top] in normalized (0, 1) figure coordinates
soupault
1
Możesz jeszcze bardziej zwiększyć odstęp między napisami a osiami, obniżając prawą wartość: tight_layout(rect=[0, 0.03, 1, 0.9])zamiast 0.95jak w odpowiedzi.
ComFreek
1
Nie działał w jupyter. Próbowałem różnych wartości liczb, nie przyniosło żadnego efektu
Aleksejs Fomins
114

Możesz ręcznie dostosować odstępy za pomocą 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
źródło
5
Zauważ, że domyślną wartością dla góry jest 0,9, gdy dzwoniszsubplots_adjust
Brian Burns
72
Nie mogę uwierzyć, że w 2017 roku naszego pana wciąż jest to błąd Matplotliba.
wordsforthewise
29
Pamiętaj, że subplots_adjustnależy go wywoływać po każdym połączeniu tight_layout, inaczej nie będzie efektu wywołania subplots_adjust.
Achal Dave
7
@wordsforthewise sprawiają, że teraz 2018
vlsd
6
@vlsd Witaj od 2019 r.
Xiaoyu Lu
55

Jedną rzeczą, którą możesz bardzo łatwo zmienić w kodzie, jest to, fontsizektórego używasz do tytułów. Zakładam jednak, że nie tylko chcesz to zrobić!

Niektóre alternatywy dla używania fig.subplots_adjust(top=0.85):

Zwykle tight_layout()wykonuje dobrą robotę, umieszczając wszystko w dobrych lokalizacjach, aby się nie nakładały. Przyczyna tight_layout()nie pomaga w tym przypadku, ponieważ tight_layout()nie uwzględnia fig.suptitle (). W GitHub istnieje otwarty problem: https://github.com/matplotlib/matplotlib/issues/829 [zamknięty w 2014 r. Ze względu na wymaganie pełnego menedżera geometrii - przeniesiony do https://github.com/matplotlib/matplotlib / Issues / 1109 ].

Jeśli czytasz wątek, możesz rozwiązać problem GridSpec. Kluczem jest pozostawienie miejsca na górze figury podczas dzwonienia tight_layoutza pomocą rectkwarg. W przypadku Twojego problemu kod staje się:

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

Wynik:

using gridspec

Może GridSpecto dla ciebie trochę przesada, albo twój prawdziwy problem będzie wiązał się z wieloma dodatkowymi wątkami na znacznie większym obszarze roboczym lub innymi komplikacjami. Prostym hakiem jest po prostu użycie annotate()i zablokowanie współrzędnych 'figure fraction'do naśladowania suptitle. Być może jednak będziesz musiał dokonać drobniejszych korekt, gdy spojrzysz na wynik. Zauważ, że to drugie rozwiązanie ma nie wykorzystaniatight_layout() .

Prostsze rozwiązanie (choć może wymagać dopracowania)

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

Wynik:

prosty

[Korzystanie z wersji Python2.7.3 (64-bitowej) i matplotlib1.2.0]

aseagram
źródło
1
Dzięki, jeśli użyję pierwszego rozwiązania, które zasugerowałeś (używasz GridSpec), jak mogę utworzyć podploty, które współużytkują osie? Zwykle używam do plt.subplots(N,1, sharex=<>, sharey=<>)tworzenia podplotów, ale add_subplotzamiast tego używa kodu, który opublikowałeś
Amelio Vazquez-Reina
10
Przesyłanie fig.tight_layout(rect=[0, 0.03, 1, 0.95])również działa.
soupault
1
Drugie rozwiązanie jest dla mnie jedyne. Dziękuję Ci!
Hagbard
19

Alternatywnym i prostym w użyciu rozwiązaniem jest dostosowanie współrzędnych tekstu napisów na rysunku za pomocą argumentu y w wywołaniu napisów (patrz dokumentacja ):

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
źródło
8
Jest to świetny sposób w notatniku, ale komenda plt.savefig () nie jest posłuszna. To rozwiązanie nadal odcina tytuł w poleceniu savefig.
superbohater
11

Ciasny układ nie działa z napisami, ale constrained_layoutdziała. Zobacz to pytanie Popraw rozmiar / odstępy podplota dzięki wielu podplotom w matplotlib

Odkryłem, że dodanie podplotów od razu wyglądało lepiej, tj

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

# then iterating over the axes to fill in the plots

Ale można go również dodać w momencie tworzenia rysunku:

fig = plt.figure(constrained_layout=True)

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

Uwaga: aby zbliżyć do siebie moje wątki, również używałem

fig.subplots_adjust(wspace=0.05)

i constrained_layout nie działa z tym :(

Bhayley
źródło
4

Mam kłopoty z matplotlib przycinania metod, więc już teraz po prostu się funkcję, aby to zrobić poprzez bashwywołanie ImageMagick„s mogrify polecenia , który działa dobrze i pobiera wszystkie dodatkowe spacje poza krawędź figurki. Wymaga to używania systemu UNIX / Linux, bashpowłoki i ImageMagickzainstalowania.

Po prostu zadzwoń po to savefig().

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
źródło
3

Jak wspomnieli inni, domyślnie ciasny układ nie bierze pod uwagę napisów. Odkryłem jednak, że można użyć bbox_extra_artistsargumentu do przekazania napisu jako ramki granicznej, którą należy wziąć pod uwagę:

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

Wymusza to uwzględnienie obliczeń ścisłego układu suptitlei wygląda tak, jak można się spodziewać.

Herman Schaaf
źródło
To jedyna opcja, która działała dla mnie. Używam notesów Jupyter do robienia figurek i zapisywania ich. Pracuję w Pythonie 3.6 na komputerze z systemem Linux.
GRquanti
2

Jedyną rzeczą, która działała dla mnie, była modyfikacja wezwania do napisów:

fig.suptitle("title", y=.995)
markemus
źródło
1

Miałem podobny problem, który pojawił się podczas używania tight_layoutbardzo dużej siatki wykresów (ponad 200 podplotów) i renderowania w notatniku jupyter. Zrobiłem szybkie rozwiązanie, które zawsze umieszcza cię suptitlew pewnej odległości nad twoim górnym wątkiem:

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)

W przypadku podplotów o zmiennej wielkości nadal można użyć tej metody, aby uzyskać najwyższą pozycję podplotu, a następnie ręcznie zdefiniować dodatkową ilość do dodania do napisów.

Brian Pollack
źródło