O Matplotlib tight_layout () não leva em consideração o subtítulo da figura

208

Se eu adicionar uma legenda à minha figura do matplotlib, ela será sobreposta pelos títulos da subtrama. Alguém sabe como cuidar disso facilmente? Eu tentei a tight_layout()função, mas isso só piora as coisas.

Exemplo:

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
fonte

Respostas:

179

Você pode ajustar a geometria da subparcela na própria tight_layoutchamada da seguinte maneira:

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

Conforme declarado na documentação ( https://matplotlib.org/users/tight_layout_guide.html ):

tight_layout()considera apenas etiquetas, rótulos de eixos e títulos. Assim, outros artistas podem ser cortados e também podem se sobrepor.

soupault
fonte
1
Funcionou como charme, mas como a mudança acontece ao especificar a caixa delimitadora? Eu li o documento, mas não estava muito claro para mim. Seria ótimo se você pudesse me explicar por favor! Obrigado.
thepunitsingh
2
você poderia explicar o significado de cada número em ret? Eu não os vi no documento.
Steven
6
@steven see tight_layoutdocs:[left, bottom, right, top] in normalized (0, 1) figure coordinates
soupault
1
Você pode aumentar ainda mais o espaço entre o subtítulo e os eixos diminuindo o valor mais correto: em tight_layout(rect=[0, 0.03, 1, 0.9])vez de 0.95como na resposta.
ComFreek 24/09/19
1
Não funcionou dentro do jupyter. Tentei valores diferentes para números, não teve efeito
Aleksejs Fomins 23/01
114

Você pode ajustar manualmente o espaçamento 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()
unutbu
fonte
5
Observe o valor padrão para cima é 0,9 quando você chamarsubplots_adjust
Brian Burns
72
Não acredito que no ano de 2017 de nosso senhor isso ainda é um bug do matplotlib.
wordsforthewise
29
Observe que subplots_adjustdeve ser chamado após todas as chamadas para tight_layout, ou não haverá efeito de chamada subplots_adjust.
Achal Dave
7
@wordsforthewise make that 2018 now
vlsd 16/03/2019
6
@vlsd Olá a partir de 2019
Xiaoyu Lu
55

Uma coisa que você pode alterar no seu código com muita facilidade é a que fontsizevocê está usando para os títulos. No entanto, vou assumir que você não quer apenas fazer isso!

Algumas alternativas ao uso fig.subplots_adjust(top=0.85):

Geralmente, tight_layout()faz um bom trabalho no posicionamento de tudo em bons locais para que não se sobreponham. O motivo tight_layout()não ajuda nesse caso é porque tight_layout()não leva em consideração fig.suptitle (). Há um problema em aberto sobre isso no GitHub: https://github.com/matplotlib/matplotlib/issues/829 [encerrado em 2014 devido à necessidade de um gerente de geometria completo - alterado para https://github.com/matplotlib/matplotlib / edições / 1109 ].

Se você ler o tópico, há uma solução para o seu problema GridSpec. A chave é deixar algum espaço na parte superior da figura ao chamar tight_layout, usando o rectkwarg. Para o seu problema, o código se torna:

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

O resultado:

usando gridspec

Talvez GridSpecseja um pouco exagerado para você, ou o seu problema real envolverá muito mais subtramas em uma tela muito maior ou outras complicações. Um hack simples é usar annotate()e bloquear as coordenadas 'figure fraction'para imitar a suptitle. Você pode precisar fazer alguns ajustes mais precisos depois de analisar a saída. Observe que esta segunda solução não usatight_layout() .

Solução mais simples (embora possa precisar ser ajustada)

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

O resultado:

simples

[Usando Python2.7.3 (64 bits) e matplotlib1.2.0]

aseagram
fonte
1
Obrigado, se eu usar a primeira solução que você sugeriu (usando GridSpec) como posso criar subparcelas que compartilham eixos? Eu costumo usar plt.subplots(N,1, sharex=<>, sharey=<>)ao criar subtramas, mas o código que você postou usos add_subplotvez
Amelio Vazquez-Reina
10
Fundição fig.tight_layout(rect=[0, 0.03, 1, 0.95])também funciona.
soupault 16/09
1
A segunda solução é a única que funcionou para mim. Obrigado!
Hagbard 9/03
19

Uma solução alternativa e simples de usar é ajustar as coordenadas do texto do subtítulo na figura usando o argumento y na chamada do subtítulo (consulte a documentação ):

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
fonte
8
Essa é uma ótima maneira em um notebook, mas o comando plt.savefig () não obedece. Essa solução ainda corta o título em um comando savefig.
Super
11

Layout apertado não funciona com legendas, mas constrained_layoutfunciona. Veja esta pergunta Melhorar o tamanho / espaçamento das subparcelas com muitas subtramas no matplotlib

Eu achei que adicionar as subparcelas de uma só vez parecia melhor, ou seja,

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

# then iterating over the axes to fill in the plots

Mas também pode ser adicionado no ponto em que a figura é criada:

fig = plt.figure(constrained_layout=True)

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

Nota: Para aproximar minhas subparcelas, eu também estava usando

fig.subplots_adjust(wspace=0.05)

e constrained_layout não funciona com isso :(

bhayley
fonte
4

Eu lutei com os métodos de corte do matplotlib, então agora criei uma função para fazer isso através de uma bashchamada ao comando mogrify deImageMagick s , que funciona bem e tira todo o espaço em branco da borda da figura. Isso requer que você esteja usando o UNIX / Linux, o shell e a instalação.bashImageMagick

Basta ligar para isso após a savefig()ligação.

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

Conforme mencionado por outros, por padrão, o layout restrito não leva em consideração o subtítulo. No entanto, descobri que é possível usar o bbox_extra_artistsargumento para passar no subtítulo como uma caixa delimitadora que deve ser levada em consideração:

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

Isso força o cálculo apertado do layout a levar suptitleem consideração e parece com o que você esperaria.

Herman Schaaf
fonte
Esta é a única opção que funcionou para mim. Uso os cadernos Jupyter para fazer figuras e salvá-las. Eu trabalho em python 3.6 em uma máquina Linux.
GRquanti 07/04
2

A única coisa que funcionou para mim foi modificar a chamada para legenda:

fig.suptitle("title", y=.995)
markemus
fonte
1

Eu tive um problema semelhante que surgiu ao usar tight_layoutpara uma grade muito grande de plotagens (mais de 200 sub-plotagens) e renderizar em um notebook jupiter. Fiz uma solução rápida que sempre coloca você suptitlea uma certa distância acima da subparcela superior:

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)

Para subparcelas de tamanho variável, você ainda pode usar esse método para obter o topo da subparcela superior e, em seguida, definir manualmente uma quantia adicional a ser adicionada ao subtítulo.

Brian Pollack
fonte