Matplotlib tight_layout () no tiene en cuenta la cifra de los supuestos

208

Si agrego un subtítulo a mi figura matplotlib, los títulos de la subtrama se superponen. ¿Alguien sabe cómo solucionarlo fácilmente? Probé la tight_layout()función, pero solo empeora las cosas.

Ejemplo:

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
fuente

Respuestas:

183

Puede ajustar la geometría de la subtrama en la misma tight_layoutllamada de la siguiente manera:

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

Como se indica en la documentación ( https://matplotlib.org/users/tight_layout_guide.html ):

tight_layout()solo considera las marcas de graduación, las etiquetas de eje y los títulos. Por lo tanto, otros artistas pueden ser recortados y también pueden superponerse.

soupault
fuente
1
Funcionó de maravilla, pero ¿cómo ocurre el cambio al especificar el cuadro delimitador? Leí el documento, pero no me quedó claro. ¡Sería genial si pudieras explicarme! Gracias.
thepunitsingh
2
¿podría explicar el significado de cada número en rect? No los vi en el documento.
Steven
66
@steven ver tight_layoutdocumentos:[left, bottom, right, top] in normalized (0, 1) figure coordinates
soupault
1
Puede aumentar el espacio entre el título y los ejes aún más bajando el valor más correcto: en tight_layout(rect=[0, 0.03, 1, 0.9])lugar de 0.95como en la respuesta.
ComFreek
1
No funcionó dentro de Jupyter. Intenté diferentes valores para los números, no tuvo efecto
Aleksejs Fomins
114

Puede ajustar manualmente el espaciado 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
fuente
55
Tenga en cuenta que el valor predeterminado para top es 0.9 cuando llamasubplots_adjust
Brian Burns el
72
No puedo creer en el año 2017 de nuestro señor, esto sigue siendo un error de matplotlib.
wordsforthewise
29
Tenga en cuenta que subplots_adjustdebe llamarse después de cualquier llamada a tight_layout, o no habrá ningún efecto de la llamada subplots_adjust.
Achal Dave
77
@wordsforthewise hace ese 2018 ahora
vlsd
66
@vlsd Hola desde 2019
Xiaoyu Lu
55

Una cosa que podría cambiar en su código muy fácilmente es la fontsizeque está utilizando para los títulos. Sin embargo, ¡voy a suponer que no solo quieres hacer eso!

Algunas alternativas al uso fig.subplots_adjust(top=0.85):

Por tight_layout()lo general, hace un trabajo bastante bueno al colocar todo en buenas ubicaciones para que no se superpongan. La razón por la tight_layout()que no ayuda en este caso es porque tight_layout()no tiene en cuenta fig.suptitle (). Hay un problema abierto sobre esto en GitHub: https://github.com/matplotlib/matplotlib/issues/829 [cerrado en 2014 debido a que requiere un administrador de geometría completo - cambiado a https://github.com/matplotlib/matplotlib / cuestiones / 1109 ].

Si lees el hilo, hay una solución a tu problema GridSpec. La clave es dejar algo de espacio en la parte superior de la figura al llamar tight_layout, usando el rectkwarg. Para su problema, el código se convierte en:

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

El resultado:

usando gridspec

Tal vez GridSpecsea ​​un poco excesivo para usted, o su problema real implicará muchas más subtramas en un lienzo mucho más grande u otras complicaciones. Un truco simple es usar annotate()y bloquear las coordenadas 'figure fraction'para imitar a suptitle. Sin embargo, es posible que deba hacer algunos ajustes más finos una vez que eche un vistazo a la salida. Tenga en cuenta que esta segunda solución no utilizatight_layout() .

Solución más simple (aunque es posible que deba ajustarse)

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

El resultado:

sencillo

[Utilizando Python2.7.3 (64 bits) y matplotlib1.2.0]

aseagrama
fuente
1
Gracias, si uso la primera solución que sugirió (usando GridSpec), ¿cómo puedo crear subtramas que compartan ejes? Usualmente uso plt.subplots(N,1, sharex=<>, sharey=<>)cuando creo subtramas, pero el código que publicaste usa en su add_subplotlugar
Amelio Vazquez-Reina
10
El casting fig.tight_layout(rect=[0, 0.03, 1, 0.95])también funciona.
soupault
1
La segunda solución es la única que funcionó para mí. ¡Gracias!
Hagbard
19

Una solución alternativa y fácil de usar es ajustar las coordenadas del texto del título en la figura usando el argumento y en la llamada del título (ver los documentos ):

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
fuente
8
Esta es una excelente manera en un cuaderno, pero el comando plt.savefig () no obedece. Esta solución aún corta el título en un comando savefig.
superhéroe
11

El diseño ajustado no funciona con el título, pero constrained_layoutsí. Ver esta pregunta Mejore el tamaño / espaciado de la subtrama con muchas subtramas en matplotlib

Encontré que agregar las subtramas a la vez se veía mejor, es decir

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

# then iterating over the axes to fill in the plots

Pero también se puede agregar en el momento en que se crea la figura:

fig = plt.figure(constrained_layout=True)

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

Nota: para acercar mis subtramas, también estaba usando

fig.subplots_adjust(wspace=0.05)

y restricined_layout no funciona con esto :(

bhayley
fuente
4

He luchado con el recorte matplotlib métodos, por lo que he ahora acabo de hacer una función para hacer esto a través de una bashllamada a ImageMagick's comando mogrify , que funciona bien y se pone todo el espacio en blanco adicionales fuera del borde de la figura. Esto requiere que esté utilizando UNIX / Linux, esté utilizando el bashshell y lo haya ImageMagickinstalado.

Simplemente envíe una llamada a esto después de su savefig()llamada.

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

Como lo mencionaron otros, por defecto, el diseño ajustado no tiene en cuenta el título. Sin embargo, he descubierto que es posible usar el bbox_extra_artistsargumento para pasar el supítulo como un cuadro delimitador que debe tenerse en cuenta:

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

Esto obliga al cálculo de diseño ajustado a tener suptitleen cuenta, y se ve como es de esperar.

Herman Schaaf
fuente
Esta es la única opción que funcionó para mí. Utilizo los cuadernos Jupyter para hacer figuras y guardarlas. Trabajo en Python 3.6 en una máquina Linux.
GRquanti
2

Lo único que funcionó para mí fue modificar la llamada al suptitle:

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

Tuve un problema similar que surgió cuando se usaba tight_layoutpara una cuadrícula de tramas muy grande (más de 200 subtramas) y se renderizaba en un cuaderno jupyter. Hice una solución rápida que siempre te coloca suptitlea cierta distancia por encima de tu subtrama 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 las subtramas de tamaño variable, aún puede utilizar este método para obtener la parte superior de la subtrama superior, luego definir manualmente una cantidad adicional para agregar al título.

Brian Pollack
fuente