Encabezados de fila y columna en las subtramas de matplotlib

82

¿Cuál es la mejor práctica para agregar una fila y un encabezado de columna a una cuadrícula de subtramas generadas en un bucle matplotlib? Puedo pensar en un par, pero no particularmente bueno:

  1. Para las columnas, con un contador para su bucle, puede usar solo set_title()para la primera fila. Para las filas, esto no funciona. Tendría que dibujar textfuera de las parcelas.
  2. Agrega una fila adicional de subtramas en la parte superior y una columna adicional de subtramas a la izquierda, y dibuja texto en el medio de esa subtrama.

¿Puede sugerir una alternativa mejor?

ingrese la descripción de la imagen aquí

gozzilli
fuente

Respuestas:

109

Hay varias formas de hacerlo. La forma más fácil es explotar las etiquetas y y los títulos de la trama y luego usar fig.tight_layout()para hacer espacio para las etiquetas. Alternativamente, puede colocar texto adicional en la ubicación correcta con annotatey luego dejar espacio para él de forma semi-manual.


Si no tiene etiquetas y en sus ejes, es fácil aprovechar el título y la etiqueta y de la primera fila y columna de ejes.

import matplotlib.pyplot as plt

cols = ['Column {}'.format(col) for col in range(1, 4)]
rows = ['Row {}'.format(row) for row in ['A', 'B', 'C', 'D']]

fig, axes = plt.subplots(nrows=4, ncols=3, figsize=(12, 8))

for ax, col in zip(axes[0], cols):
    ax.set_title(col)

for ax, row in zip(axes[:,0], rows):
    ax.set_ylabel(row, rotation=0, size='large')

fig.tight_layout()
plt.show()

ingrese la descripción de la imagen aquí


Si tiene etiquetas en Y, o si prefiere un poco más de flexibilidad, puede usarlas annotatepara colocar las etiquetas. Esto es más complicado, pero le permite tener títulos de gráficos individuales, ylabels, etc. además de las etiquetas de fila y columna.

import matplotlib.pyplot as plt
from matplotlib.transforms import offset_copy


cols = ['Column {}'.format(col) for col in range(1, 4)]
rows = ['Row {}'.format(row) for row in ['A', 'B', 'C', 'D']]

fig, axes = plt.subplots(nrows=4, ncols=3, figsize=(12, 8))
plt.setp(axes.flat, xlabel='X-label', ylabel='Y-label')

pad = 5 # in points

for ax, col in zip(axes[0], cols):
    ax.annotate(col, xy=(0.5, 1), xytext=(0, pad),
                xycoords='axes fraction', textcoords='offset points',
                size='large', ha='center', va='baseline')

for ax, row in zip(axes[:,0], rows):
    ax.annotate(row, xy=(0, 0.5), xytext=(-ax.yaxis.labelpad - pad, 0),
                xycoords=ax.yaxis.label, textcoords='offset points',
                size='large', ha='right', va='center')

fig.tight_layout()
# tight_layout doesn't take these labels into account. We'll need 
# to make some room. These numbers are are manually tweaked. 
# You could automatically calculate them, but it's a pain.
fig.subplots_adjust(left=0.15, top=0.95)

plt.show()

ingrese la descripción de la imagen aquí

Joe Kington
fuente
8
Los métodos is_first_col(), is_last_col(), is_first_row()y is_last_row()pueden ser también conveniente en este contexto.
gerrit
1
También como nota, anotar matplotlib tiene la opción de rotar, así que si desea rotar su etiqueta 90 grados, simplemente agregue el argumentorotation = 90
mathishard.butweloveit
2

La respuesta anterior funciona. Solo que no en la segunda versión de la respuesta, tienes:

for ax, row in zip(axes[:,0], rows):
    ax.annotate(col, xy=(0, 0.5), xytext=(-ax.yaxis.labelpad-pad,0),
                xycoords=ax.yaxis.label, textcoords='offset points',
                size='large', ha='right', va='center')

en vez de:

for ax, row in zip(axes[:,0], rows):
    ax.annotate(row,xy=(0, 0.5), xytext=(-ax.yaxis.labelpad-pad,0),                    
                xycoords=ax.yaxis.label, textcoords='offset points',
                size='large', ha='right', va='center')
Alan Shteyman
fuente