Xlabel / ylabel común para subplots matplotlib

143

Tengo la siguiente trama:

fig,ax = plt.subplots(5,2,sharex=True,sharey=True,figsize=fig_size)

y ahora me gustaría dar a este gráfico etiquetas comunes del eje xy etiquetas del eje y. Con "común", quiero decir que debería haber una etiqueta grande del eje x debajo de la cuadrícula completa de subtramas, y una etiqueta grande del eje y a la derecha. No puedo encontrar nada sobre esto en la documentación plt.subplots, y mis google sugieren que necesito hacer un gran plt.subplot(111)comienzo, pero ¿cómo pongo mis subtramas 5 * 2 en ese uso plt.subplots?

jolindbe
fuente
2
Con la actualización de la pregunta y los comentarios que quedan en las respuestas a continuación, este es un duplicado de stackoverflow.com/questions/6963035/…
Enganchado el
En realidad no, ya que mi pregunta es para plt.subplots (), y la pregunta a la que enlaza usa add_subplot: no puedo usar ese método a menos que cambie a add_subplot, que me gustaría evitar. Yo podría utilizar la solución plt.text que se da como una solución alternativa en su enlace, pero no es la solución más elegante.
jolindbe
Para elaborar, por lo que entiendo, plt.subplots no puede generar un conjunto de subtramas dentro de un entorno de eje existente, pero siempre crea una nueva figura. ¿Correcto?
jolindbe
La solución más elegante se puede encontrar aquí: stackoverflow.com/questions/6963035/…
Mr.H
Su enlace fue proporcionado por el usuario Enganchado hace más de 4 años (solo algunos comentarios sobre el suyo). Como dije anteriormente, esa solución pertenece a add_subplot, y no a plt.subplots ().
jolindbe

Respuestas:

206

Esto se parece a lo que realmente quieres. Aplica el mismo enfoque de esta respuesta a su caso específico:

import matplotlib.pyplot as plt

fig, ax = plt.subplots(nrows=3, ncols=3, sharex=True, sharey=True, figsize=(6, 6))

fig.text(0.5, 0.04, 'common X', ha='center')
fig.text(0.04, 0.5, 'common Y', va='center', rotation='vertical')

Múltiples parcelas con etiqueta de ejes comunes

Divenex
fuente
44
tenga en cuenta que 0.5 para la coordenada x de la etiqueta x no coloca la etiqueta en el centro de la subtrama central. tendrías que ir un poco más grande que eso para tener en cuenta las etiquetas ytick.
dbliss
2
Eche un vistazo a esta respuesta para un método que no utiliza plt.text. Usted crea sus subtramas, pero luego agrega una gráfica de bits, la hace invisible y etiqueta sus x e y.
James Owers
Gracias, trabajado en general. ¿Alguna solución a la rotura cuando se usa tight_layout?
serv-inc
3
@ serv-inc con tight_layoutla sustitución 0.04con 0parece funcionar.
divenex
3
Usar fig.textno es una buena idea. Esto arruina cosas comoplt.tight_layout()
Pacífica
55

Como lo considero relevante y lo suficientemente elegante (no es necesario especificar coordenadas para colocar texto), copio (con una ligera adaptación) una respuesta a otra pregunta relacionada .

import matplotlib.pyplot as plt
fig, axes = plt.subplots(5, 2, sharex=True, sharey=True, figsize=(6,15))
# add a big axis, hide frame
fig.add_subplot(111, frameon=False)
# hide tick and tick label of the big axis
plt.tick_params(labelcolor='none', top=False, bottom=False, left=False, right=False)
plt.xlabel("common X")
plt.ylabel("common Y")

Esto da como resultado lo siguiente (con matplotlib versión 2.2.0):

Subparcelas de 5 filas y 2 columnas con etiquetas comunes de eje x e y

bli
fuente
44
Debido a la simplicidad, esta debería ser la respuesta aceptada. Muy sencillo Sigue siendo relevante para matplotlib v3.x.
Kyle Swanson el
Me gustaría saber cómo podría usarse con múltiples objetos de figura. fig.xlabel ("foo") no funciona.
Horror Vacui
FYI: Ahora que la gente usa temas oscuros en StackOverflow, las etiquetas apenas se pueden leer, así que es mejor exportar tus png con fondo blanco
xyzzyqed
@xyzzyqed No sabía que existían los "temas" en stackoverflow, y ni siquiera recuerdo cómo exporté la figura. ¿Cómo puedo controlar el fondo al exportar?
bli
2
El único problema de esta solución es que no funciona cuando se usa constrained_layout=Trueporque crea etiquetas superpuestas. En este caso, debe ajustar manualmente los bordes de las subtramas.
baccandr
35

Sin sharex=True, sharey=Trueque consigas:

ingrese la descripción de la imagen aquí

Con él deberías hacerlo más agradable:

fig, axes2d = plt.subplots(nrows=3, ncols=3,
                           sharex=True, sharey=True,
                           figsize=(6,6))

for i, row in enumerate(axes2d):
    for j, cell in enumerate(row):
        cell.imshow(np.random.rand(32,32))

plt.tight_layout()

ingrese la descripción de la imagen aquí

Pero si desea agregar etiquetas adicionales, debe agregarlas solo a los gráficos de borde:

fig, axes2d = plt.subplots(nrows=3, ncols=3,
                           sharex=True, sharey=True,
                           figsize=(6,6))

for i, row in enumerate(axes2d):
    for j, cell in enumerate(row):
        cell.imshow(np.random.rand(32,32))
        if i == len(axes2d) - 1:
            cell.set_xlabel("noise column: {0:d}".format(j + 1))
        if j == 0:
            cell.set_ylabel("noise row: {0:d}".format(i + 1))

plt.tight_layout()

ingrese la descripción de la imagen aquí

Agregar etiqueta para cada gráfico lo estropearía (tal vez haya una manera de detectar automáticamente etiquetas repetidas, pero no estoy al tanto de una).

Piotr Migdal
fuente
Esto es mucho más difícil si, por ejemplo, se desconoce la cantidad de parcelas (por ejemplo, tiene una función de trama generalizada que funciona para cualquier cantidad de subtramas).
naught101
15

Desde el comando:

fig,ax = plt.subplots(5,2,sharex=True,sharey=True,figsize=fig_size)

usó devuelve una tupla que consiste en la figura y una lista de las instancias de los ejes, ya es suficiente hacer algo como (tenga en cuenta que he cambiado fig,axa fig,axes):

fig,axes = plt.subplots(5,2,sharex=True,sharey=True,figsize=fig_size)

for ax in axes:
    ax.set_xlabel('Common x-label')
    ax.set_ylabel('Common y-label')

Si desea cambiar algunos detalles en una subtrama específica, puede acceder a través de axes[i]donde iitera sobre sus subtramas.

También podría ser muy útil incluir un

fig.tight_layout()

al final del archivo, antes del plt.show(), para evitar la superposición de etiquetas.

Mario
fuente
55
Lo siento, estaba un poco confuso arriba. Con "común" me refería a una sola etiqueta x debajo de todas las parcelas, y una sola etiqueta y a la izquierda de las parcelas, he actualizado la pregunta para reflejar esto.
jolindbe
2
@JohanLindberg: Con respecto a sus comentarios aquí y arriba: Indeed plt.subplots()creará una nueva instancia de figura. Si desea seguir con este comando, puede agregar fácilmente un big_ax = fig.add_subplot(111), ya que ya tiene una figura y puede agregar otro eje. Después de eso, puede manipular big_axla forma en que se muestra en el enlace de Hooked.
Marius
Gracias por sus sugerencias, pero si hago eso, tengo que agregar big_ax después de plt.subplots (), y obtengo esa subtrama por encima de todo lo demás, ¿puedo hacerla transparente o enviarla al fondo de alguna manera? Incluso si configuro todos los colores en ninguno como en el enlace de Hooked, sigue siendo un cuadro blanco que cubre todas mis subtramas.
jolindbe
2
@JohanLindberg, tienes razón, no lo había comprobado. Pero puede establecer fácilmente el color de fondo del eje grande nonehaciendo: big_ax.set_axis_bgcolor('none')También debe hacer el color de la etiqueta none(a diferencia del ejemplo vinculado por Hooked):big_ax.tick_params(labelcolor='none', top='off', bottom='off', left='off', right='off')
Marius
2
Me sale un error: AttributeError: 'numpy.ndarray' object has no attribute 'set_xlabel'en la declaración ax.set_xlabel('Common x-label'). ¿Puedes resolverlo?
hengxin
5

Se verá mejor si reserva espacio para las etiquetas comunes haciendo etiquetas invisibles para la subtrama en la esquina inferior izquierda. También es bueno pasar el tamaño de fuente de rcParams. De esta manera, las etiquetas comunes cambiarán de tamaño con la configuración de su rc, y los ejes también se ajustarán para dejar espacio para las etiquetas comunes.

fig_size = [8, 6]
fig, ax = plt.subplots(5, 2, sharex=True, sharey=True, figsize=fig_size)
# Reserve space for axis labels
ax[-1, 0].set_xlabel('.', color=(0, 0, 0, 0))
ax[-1, 0].set_ylabel('.', color=(0, 0, 0, 0))
# Make common axis labels
fig.text(0.5, 0.04, 'common X', va='center', ha='center', fontsize=rcParams['axes.labelsize'])
fig.text(0.04, 0.5, 'common Y', va='center', ha='center', rotation='vertical', fontsize=rcParams['axes.labelsize'])

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

EL_DON
fuente
1
Buen uso de la etiqueta invisible! Gracias
colelemonz
3

Me encontré con un problema similar al trazar una cuadrícula de gráficos. Los gráficos constaban de dos partes (superior e inferior). Se suponía que la etiqueta y estaba centrada sobre ambas partes.

No quería usar una solución que dependiera de conocer la posición en la figura externa (como fig.text ()), por lo que manipulé la posición y de la función set_ylabel (). Por lo general, es 0.5, el medio del gráfico al que se agrega. Como el relleno entre las partes (hspace) en mi código era cero, pude calcular el medio de las dos partes en relación con la parte superior.

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec

# Create outer and inner grid
outerGrid = gridspec.GridSpec(2, 3, width_ratios=[1,1,1], height_ratios=[1,1])
somePlot = gridspec.GridSpecFromSubplotSpec(2, 1,
               subplot_spec=outerGrid[3], height_ratios=[1,3], hspace = 0)

# Add two partial plots
partA = plt.subplot(somePlot[0])
partB = plt.subplot(somePlot[1])

# No x-ticks for the upper plot
plt.setp(partA.get_xticklabels(), visible=False)

# The center is (height(top)-height(bottom))/(2*height(top))
# Simplified to 0.5 - height(bottom)/(2*height(top))
mid = 0.5-somePlot.get_height_ratios()[1]/(2.*somePlot.get_height_ratios()[0])
# Place the y-label
partA.set_ylabel('shared label', y = mid)

plt.show()

imagen

Desventajas:

  • La distancia horizontal al gráfico se basa en la parte superior, las marcas inferiores pueden extenderse a la etiqueta.

  • La fórmula no tiene en cuenta el espacio entre las partes.

  • Lanza una excepción cuando la altura de la parte superior es 0.

Probablemente haya una solución general que tenga en cuenta el relleno entre las figuras.

CPe
fuente
Oye, descubrí una manera de hacer esto en la línea de tu respuesta, pero podría resolver algunos de estos problemas; ver stackoverflow.com/a/44020303/4970632 (abajo)
Luke Davis
2

Actualizar:

Esta característica ahora es parte del paquete proplot matplotlib que lancé recientemente en pypi. Por defecto, cuando crea figuras, las etiquetas se "comparten" entre los ejes.


Respuesta original:

Descubrí un método más robusto:

Si conoce los bottomy topkwargs que entraron en una GridSpecinicialización, o conoce las posiciones de los bordes de sus ejes en Figurecoordenadas , también puede especificar la posición de la etiqueta en Figurecoordenadas con un poco de magia de "transformación". Por ejemplo:

import matplotlib.transforms as mtransforms
bottom, top = .1, .9
f, a = plt.subplots(nrows=2, ncols=1, bottom=bottom, top=top)
avepos = (bottom+top)/2
a[0].yaxis.label.set_transform(mtransforms.blended_transform_factory(
       mtransforms.IdentityTransform(), f.transFigure # specify x, y transform
       )) # changed from default blend (IdentityTransform(), a[0].transAxes)
a[0].yaxis.label.set_position((0, avepos))
a[0].set_ylabel('Hello, world!')

... y debería ver que la etiqueta todavía se ajusta adecuadamente de izquierda a derecha para evitar que se superpongan con las etiquetas, como de costumbre, pero ahora se ajustará para estar siempre exactamente entre las subtramas deseadas.

Además, si ni siquiera lo usa set_position, la etiqueta aparecerá por defecto exactamente a la mitad de la figura . Supongo que esto se debe a que cuando la etiqueta finalmente se dibuja, matplotlibusa 0.5 para la ycoordenada sin verificar si la transformación de coordenadas subyacente ha cambiado.

Luke Davis
fuente