Una guía para subfiguras de Matplotlib para crear complejas figuras de varios paneles

Una guía completa de subfiguras de Matplotlib para crear impresionantes figuras de varios paneles

Subfiguras – una herramienta poderosa para hermosas figuras de varios paneles

Motivación

Las figuras complejas (científicas) a menudo consisten en múltiples gráficos con diferentes tamaños o anotaciones. Si trabajas con el ecosistema de matplotlib/seaborn, hay muchas formas de crear figuras complejas, por ejemplo, usando gridspec. Sin embargo, esto puede volverse desafiante muy rápidamente, especialmente si quieres integrar gráficos de ejes múltiples de seaborn, como jointplot o pairgrid, en tu figura porque no tienen la opción de proporcionar ejes como parámetros de entrada. Pero hay otra forma de ensamblar figuras en matplotlib en lugar de solo trabajar con subplots: Subfiguras. Un marco poderoso para crear figuras de varios paneles como esta:

El objetivo del artículo es mostrarte cómo hacer esta figura.

En este artículo, daré una introducción a las subfiguras y sus capacidades. Combinaremos subfiguras con subplots y gridspecs para recrear esta figura. Para seguir este artículo, debes tener una comprensión básica de los subplots de matplotlib subplots y gridspec (si no, puedes consultar los tutoriales enlazados).

Subfiguras de Matplotlib

Primero, importamos matplotlib, seaborn y cargamos algunos datos de ejemplo, que utilizaremos para llenar los gráficos con contenido:

import matplotlib.pyplot as pltimport seaborn as snsdata = sns.load_dataset('mpg')

Comencemos con el concepto de subfiguras en matplotlib. Para crear subfiguras, primero necesitamos crear una figura:

fig = plt.figure(figsize=(10, 7))

A partir de este punto, podemos definir subfiguras de manera similar a los subplots. Es posible crear una cuadrícula de subfiguras proporcionando el número de filas (2) y columnas (1). Además, coloreamos los fondos de las figuras para resaltarlas:

(topfig, bottomfig) = fig.subfigures(2, 1)topfig.set_facecolor('#cbe4c6ff')topfig.suptitle('Superior')bottomfig.set_facecolor('#c6c8e4ff')bottomfig.suptitle('Inferior')

Solo las figuras sin gráficos (ejes) no se mostrarán, por lo tanto, necesitamos definir subplots para cada subfigura. Aquí ya podemos ver una gran característica de las subfiguras, para cada subfigura podemos definir diferentes diseños de subplots:

top_axs = topfig.subplots(2, 4)bottom_axs = bottomfig.subplots(3, 7)plt.show()

Ahora tenemos dos figuras separadas que podemos configurar de manera diferente pero colocar juntas en una figura final. Por supuesto, también podemos jugar con las proporciones de tamaño de las subfiguras:

figure = plt.figure(figsize=(10, 7))figs = figure.subfigures(2, 2, height_ratios=(2,1), width_ratios=(2,1))figs = figs.flatten()for i, fig in enumerate(figs): fig.suptitle(f'Subfigura {i}') axs = fig.subplots(2, 2)plt.show()

Sin embargo, hay una desventaja de las subfiguras. Para eliminar etiquetas superpuestas o elementos fuera de la figura, `plt.tight_layout()` es una buena manera de ajustarlo todo correctamente en la figura. Sin embargo, esto no se admite para subfiguras. Aquí puedes ver qué sucede si intentas usarlo:

figura = plt.figure(figsize=(10, 7))
figs = figura.subfigures(2, 2, height_ratios=(2,1), width_ratios=(2,1))
figs = figs.flatten()
for i, fig in enumerate(figs):
    fig.suptitle(f'Subfigura {i}')
    axs = fig.subplots(2, 2)
plt.tight_layout()
plt.show()

No es exactamente lo que pretendíamos… Para insertar espaciado entre los gráficos y eliminar cualquier superposición, necesitamos usar la función ‘subplots_adjust’, que nos permite insertar (o eliminar) más espacio entre los gráficos y los bordes:

figura = plt.figure(figsize=(10, 7))
(figura_superior, figura_inferior) = figura.subfigures(2, 1)
figura_superior.set_facecolor('#cbe4c6ff')
figura_superior.suptitle('Superior')
figura_inferior.set_facecolor('#c6c8e4ff')
figura_inferior.suptitle('Inferior')
ejes_superiores = figura_superior.subplots(2, 4)
ejes_inferiores = figura_inferior.subplots(3, 7)
# Agregar más espacio entre los gráficos y reducir el espacio a los lados
figura_superior.subplots_adjust(left=.1, right=.9, wspace=.5, hspace=.5)
# También podemos ajustar los gráficos inferiores hacia abajo
figura_inferior.subplots_adjust(wspace=.5, hspace=.8, top=.7, bottom=.3)
plt.show()

Otro gran aspecto de las subfiguras es que se pueden anidar, lo que significa que podemos dividir cada subfigura en más subfiguras:

figura = plt.figure(figsize=(10, 7))
(figura_superior, figura_inferior) = figura.subfigures(2, 1)
figura_superior.set_facecolor('#cbe4c6ff')
figura_superior.suptitle('Superior')
ejes_superiores = figura_superior.subplots(2, 4)
(figura_inferior_izquierda, figura_inferior_derecha) = figura_inferior.subfigures(1, 2, width_ratios=(1,2))
figura_inferior_izquierda.set_facecolor('#c6c8e4ff')
figura_inferior_izquierda.suptitle('Inferior izquierda')
ejes_inferiores = figura_inferior_izquierda.subplots(2, 2)
figura_inferior_derecha.set_facecolor('#aac8e4ff')
figura_inferior_derecha.suptitle('Inferior derecha')
ejes_inferiores = figura_inferior_derecha.subplots(3, 3)
# Espaciado entre las subfiguras
figura_superior.subplots_adjust(left=.1, right=.9, wspace=.4, hspace=.4)
figura_inferior_izquierda.subplots_adjust(left=.2, right=.9, wspace=.5, hspace=.4)
figura_inferior_derecha.subplots_adjust(left=.1, right=.9, wspace=.4, hspace=.4)
plt.show()

Vamos a insertar un jointplot en esta figura. Desafortunadamente, esto no es directo, ya que la función de seaborn no tiene la posibilidad de proporcionar un objeto de figura como entrada. Pero si revisamos el código fuente de la función, podemos ver que este gráfico consiste en tres subgráficos con ejes x e y compartidos que se definen a través de una grid específica.

Esto significa que podemos trazarlo fácilmente dentro de una subfigura:

figura = plt.figure(figsize=(10, 7))
(figura_superior, figura_inferior) = figura.subfigures(2, 1)
figura_superior.set_facecolor('#cbe4c6ff')
figura_superior.suptitle('Superior')
ejes_superiores = figura_superior.subplots(2, 4)
# Usaremos la subfigura inferior izquierda para el jointplot
(figura_inferior_izquierda, figura_inferior_derecha) = figura_inferior.subfigures(1, 2, width_ratios=(1,2))
# Este parámetro define la proporción de tamaño entre el gráfico principal y los gráficos marginales
ratio=2
# Definimos una gridspec donde se colocan los subgráficos
gs = plt.GridSpec(ratio + 1, ratio + 1)
# El gráfico de dispersión principal
ax_joint  = figura_inferior_izquierda.add_subplot(gs[1:, :-1])
# Los gráficos marginales comparten un eje con el gráfico principal
ax_marg_x = figura_inferior_izquierda.add_subplot(gs[0, :-1], sharex=ax_joint)
ax_marg_y = figura_inferior_izquierda.add_subplot(gs[1:, -1], sharey=ax_joint)
# Los rótulos y las marcas de los ejes para los gráficos marginales están establecidos en no visibles
# Ya que se comparten con el gráfico principal,
# al eliminarlos de los márgenes también se eliminarán del gráfico principal
plt.setp(ax_marg_x.get_xticklabels(), visible=False)
plt.setp(ax_marg_y.get_yticklabels(), visible=False)
plt.setp(ax_marg_x.get_xticklabels(minor=True), visible=False)
plt.setp(ax_marg_y.get_yticklabels(minor=True), visible=False)
# Rellenando los gráficos con datos
sns.scatterplot(data=data, y='horsepower', x='mpg', ax=ax_joint)
sns.histplot(data=data, y='horsepower', ax=ax_marg_y)
sns.histplot(data=data, x='mpg', ax=ax_marg_x)
figura_inferior_derecha.set_facecolor('#aac8e4ff')
figura_inferior_derecha.suptitle('Inferior derecha')
ejes_inferiores = figura_inferior_derecha.subplots(3, 3)
# Espaciado entre las subfiguras
figura_superior.subplots_adjust(left=.1, right=.9, wspace=.4, hspace=.4)
figura_inferior_derecha.subplots_adjust(left=.1, right=.9, wspace=.4, hspace=.4)
plt.show()

Puedes jugar con el parámetro ratio y ver cómo cambia el gráfico.

Ahora, tenemos todas las herramientas que necesitamos para crear figuras complejas, utilizando subfiguras, subplots y cuadrículas. Para estas figuras, a menudo es crucial anotar cada gráfico con letras para explicarlos en la leyenda o hacer referencia a ellos en un texto. Esto se hace a menudo con otro software como Adobe Illustrator o Inkscape después de crear la figura. Pero también podemos hacerlo directamente en Python, lo que nos ahorrará esfuerzo adicional más tarde.

Para esto, definiremos una función para hacer estas anotaciones:

def letter_annotation(ax, xoffset, yoffset, letter): ax.text(xoffset, yoffset, letter, transform=ax.transAxes,         size=12, weight='bold')

La función toma como entrada un eje (axes), junto con las coordenadas x e y, que serán transformadas en coordenadas de ejes relativas. Podemos usar esto para anotar algunos gráficos en nuestra figura previamente creada:

fig = plt.figure(figsize=(10, 7))(topfig, bottomfig) = fig.subfigures(2, 1)topfig.set_facecolor('#cbe4c6ff')topfig.suptitle('Superior')top_axs = topfig.subplots(2, 4)letter_annotation(top_axs[0][0], -.2, 1.1, 'A')(bottomleft, bottomright) = bottomfig.subfigures(1, 2, width_ratios=(1,2))bottomleft.set_facecolor('#c6c8e4ff')bottomleft.suptitle('Inferior izquierda')bottoml_axs = bottomleft.subplots(2, 2)letter_annotation(bottoml_axs[0][0], -.2, 1.1, 'B')bottomright.set_facecolor('#aac8e4ff')bottomright.suptitle('Inferior derecha')bottomr_axs = bottomright.subplots(3, 3)letter_annotation(bottomr_axs[0][0], -.2, 1.1, 'C')# Espaciado entre subplots topfig.subplots_adjust(left=.1, right=.9, wspace=.4, hspace=.4)bottomleft.subplots_adjust(left=.2, right=.9, wspace=.5, hspace=.4)bottomright.subplots_adjust(left=.1, right=.9, wspace=.4, hspace=.4)plt.show()

Ahora podemos crear el gráfico mostrado al comienzo del artículo. Consiste en tres subfiguras. Una subfigura superior, que abarca la primera fila, y dos subfiguras inferiores. La subfigura inferior izquierda se utilizará para el jointplot (como se mostró antes) y para la subfigura inferior derecha definiremos una cuadrícula para ubicar 4 subplots de diferentes tamaños.

fig = plt.figure(figsize=(10, 7))# Creando una subfigura para la primera y segunda fila(fila1fig, fila2fig) = fig.subfigures(2, 1, height_ratios=[1, 1])# Dividiendo la subfigura de la fila inferior en dos subfiguras(fig_fila2izq, fig_fila2der) = fila2fig.subfigures(1, 2, wspace=.08, width_ratios = (1, 2))# ###### Gráficos de la fila 1# ###### Crear 4 subplots para la subfigura de la fila 1plot_fila1 = fila1fig.subplots(1, 4)fila1fig.subplots_adjust(wspace=0.5, left=0, right=1, bottom=.16)ax = plot_fila1[0]sns.histplot(data=data, x='mpg', ax=ax)ax.set_title('MPG')# Anotar los gráficos con letrasletter_annotation(ax, -.25, 1, 'A')# Algunos ajustes de estilo para que los gráficos luzcan mejor # y tengan un aspecto estandarizadosns.despine(offset=5, trim=False, ax=ax)ax = plot_fila1[1]sns.histplot(data=data, x='displacement', ax=ax)ax.set_title('Desplazamiento')letter_annotation(ax, -.25, 1, 'B')sns.despine(offset=5, trim=False, ax=ax)ax = plot_fila1[2]sns.histplot(data=data, x='weight', ax=ax)ax.set_title('Peso')letter_annotation(ax, -.25, 1, 'C')sns.despine(offset=5, trim=False, ax=ax)ax = plot_fila1[3]sns.histplot(data=data, x='horsepower', ax=ax)ax.set_title('Potencia')letter_annotation(ax, -.25, 1, 'D')sns.despine(offset=5, trim=False, ax=ax)# ###### Gráficos de la fila 2# ###### ### Seaborn jointplot# ### Usando código de la clase Seaborn JointGrid# Relación de tamaño entre los gráficos principales y los márgenesratio=2# Definiendo una cuadrícula (gridspec) para la subfiguradentro de la subfigurags = plt.GridSpec(ratio + 1, ratio + 1)ax_joint  = fig_fila2izq.add_subplot(gs[1:, :-1])# Compartir el eje entre los gráficos de los márgenes y los principalesax_marg_x = fig_fila2izq.add_subplot(gs[0, :-1], sharex=ax_joint)ax_marg_y = fig_fila2izq.add_subplot(gs[1:, -1], sharey=ax_joint)# Remover etiquetas de ejes y marcadores de los gráficos de los márgenesplt.setp(ax_marg_x.get_xticklabels(), visible=False)plt.setp(ax_marg_y.get_yticklabels(), visible=False)plt.setp(ax_marg_x.get_xticklabels(minor=True), visible=False)plt.setp(ax_marg_y.get_yticklabels(minor=True), visible=False)sns.scatterplot(data=data, y='horsepower', x='mpg', ax=ax_joint)sns.histplot(data=data, y='horsepower', ax=ax_marg_y)sns.histplot(data=data, x='mpg', ax=ax_marg_x)sns.despine(offset=5, trim=False, ax=ax_joint)sns.despine(offset=5, trim=False, ax=ax_marg_y)sns.despine(offset=5, trim=False, ax=ax_marg_x)# Dejar algo de espacio a la derecha para evitar superposicionesfig_fila2izq.subplots_adjust(left=0, right=.8)letter_annotation(ax_marg_x, -.25, 1, 'E')# ### Gráficos de la derecha de la fila 2# ##gs = plt.GridSpec(2, 3)ax_izq   = fig_fila2der.add_subplot(gs[:, 0])ax_medio = fig_fila2der.add_subplot(gs[:, 1])ax_arriba     = fig_fila2der.add_subplot(gs[0, 2])ax_abajo   = fig_fila2der.add_subplot(gs[1, 2])fig_fila2der.subplots_adjust(left=0, right=1, hspace=.5)ax = ax_izqsns.scatterplot(data=data, x='model_year', y='weight', hue='origin', ax=ax)sns.despine(offset=5, trim=False, ax=ax)letter_annotation(ax, -.3, 1, 'F')ax = ax_mediosns.boxplot(data=data, x='origin', y='horsepower', ax=ax)sns.despine(offset=5, trim=False, ax=ax)letter_annotation(ax, -.3, 1, 'G')ax = ax_arribasns.kdeplot(data=data, x='mpg', y='aceleración', ax=ax)sns.despine(offset=5, trim=False, ax=ax)letter_annotation(ax, -.3, 1, 'H')ax = ax_abajosns.histplot(data=data, x='weight', y='horsepower', ax=ax)sns.despine(offset=5, trim=False, ax=ax)letter_annotation(ax, -.3, 1, 'I')plt.show()

Conclusión

Las subfiguras son un concepto relativamente nuevo en matplotlib. Facilitan el ensamblaje de figuras grandes con muchos gráficos. Todo lo mostrado en este artículo también se puede lograr completamente utilizando gridspec. Sin embargo, esto requiere una cuadrícula grande con muchas consideraciones para los tamaños de cada subgráfico. Las subfiguras son más plug-and-play y se puede lograr el mismo resultado con menos trabajo.

Para mí, las subfiguras son una herramienta muy conveniente para crear figuras científicas y espero que también te puedan ser útiles.

También puedes encontrar todo el código de este artículo en GitHub: https://github.com/tdrose/blogpost-subfigures-code

A menos que se indique lo contrario, todas las imágenes fueron creadas por el autor.

We will continue to update Zepes; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

Inteligencia Artificial

¿Qué tan seguro es el aire de tu oficina? Hay una forma de averiguarlo

Los sensores de calidad del aire interior instalados en edificios comerciales durante la pandemia ahora están resulta...

Inteligencia Artificial

Los programas piloto de IA buscan reducir el consumo de energía y las emisiones en el campus del MIT

Un equipo interdepartamental está liderando los esfuerzos para utilizar el aprendizaje automático con el fin de aumen...

Inteligencia Artificial

Asistentes de correo electrónico AI más valorados (noviembre de 2023)

Translate this html (keep the html code in the result) to Spanish: Los asistentes de correo electrónico de inteligenc...

Inteligencia Artificial

Conoce al Creador Estudiante de Robótica presenta la Silla de Ruedas Autónoma con NVIDIA Jetson

Con la ayuda de la IA, los robots, los tractores y los cochecitos de bebé – incluso los parques de patinaje ...