real - matplotlib python
matplotlib: alinear etiquetas de eje y en diagramas de dispersiĆ³n apilados (5)
En la gráfica de abajo tengo dos gráficos de dispersión que tienen una escala numérica diferente, por lo que sus etiquetas del eje Y no están alineadas. ¿Hay alguna manera de forzar la alineación horizontal en las etiquetas del eje y?
import matplotlib.pylab as plt
import random
import matplotlib.gridspec as gridspec
random.seed(20)
data1 = [random.random() for i in range(10)]
data2 = [random.random()*1000 for i in range(10)]
gs = gridspec.GridSpec(2,1)
fig = plt.figure()
ax = fig.add_subplot(gs[0])
ax.plot(data1)
ax.set_ylabel(r''Label One'', size =16)
ax = fig.add_subplot(gs[1])
ax.plot(data2)
ax.set_ylabel(r''Label Two'', size =16)
plt.show()
Aquí hay una función que escribí para alinear automáticamente las etiquetas, pero no parece funcionar en un script, solo de forma interactiva.
def align_labels(axes_list,axis=''y'',align=None):
if align is None:
align = ''l'' if axis == ''y'' else ''b''
yx,xy = [],[]
for ax in axes_list:
yx.append(ax.yaxis.label.get_position()[0])
xy.append(ax.xaxis.label.get_position()[1])
if axis == ''x'':
if align in (''t'',''top''):
lim = max(xy)
elif align in (''b'',''bottom''):
lim = min(xy)
else:
if align in (''l'',''left''):
lim = min(yx)
elif align in (''r'',''right''):
lim = max(yx)
if align in (''t'',''b'',''top'',''bottom''):
for ax in axes_list:
t = ax.xaxis.label.get_transform()
x,y = ax.xaxis.label.get_position()
ax.xaxis.set_label_coords(x,lim,t)
else:
for ax in axes_list:
t = ax.yaxis.label.get_transform()
x,y = ax.yaxis.label.get_position()
ax.yaxis.set_label_coords(lim,y,t)
Y un ejemplo:
fig,ax = subplots(2,2)
ax00,ax01 = ax[0]
ax10,ax11 = ax[1]
ax00.set_ylim(1000,5000)
ax00.set_ylabel(''top'')
ax10.set_ylabel(''bottom'')
ax10.set_xlabel(''left'')
ax11.set_xlabel(''right'')
ax11.xaxis.axis_date()
fig.autofmt_xdate()
#we have to call draw() so that matplotlib will figure out the automatic positions
fig.canvas.draw()
align_labels(ax[:,0],''y'')
align_labels(ax[1],''x'')
Como se publicó en el comentario, lo que buscas se resuelve utilizando set_label_coords()
como se matplotlib.org/faq/… . Para tu caso será algo como:
labelx = -0.5
ax = fig.add_subplot(gs[0])
ax.plot(data1)
ax.set_ylabel(r''Label One'', size=16)
ax.yaxis.set_label_coords(labelx, 0.5)
ax = fig.add_subplot(gs[1])
ax.plot(data2)
ax.set_ylabel(r''Label Two'', size=16)
ax.yaxis.set_label_coords(labelx, 0.5)
Desde la escritura de esta pregunta, matplotlib ha agregado una función fácil de usar que alinea las etiquetas. La forma correcta de forzar la alineación de las etiquetas es usar la función fig.align_labels()
antes de mostrar la figura.
Si necesita un control más preciso, también puede usar las funciones Figure.align_xlabels()
o Figure.align_ylabels()
.
Aquí hay una versión de trabajo del código publicado en la pregunta. Solo se ha agregado una línea (la segunda a la última línea) para promulgar la solución.
import matplotlib.pylab as plt
import random
import matplotlib.gridspec as gridspec
random.seed(20)
data1 = [random.random() for i in range(10)]
data2 = [random.random()*1000 for i in range(10)]
gs = gridspec.GridSpec(2,1)
fig = plt.figure()
ax = fig.add_subplot(gs[0])
ax.plot(data1)
ax.set_ylabel(r''Label One'', size =16)
ax = fig.add_subplot(gs[1])
ax.plot(data2)
ax.set_ylabel(r''Label Two'', size =16)
fig.align_labels()
plt.show()
Consulte la documentación de Matplotlib sobre etiquetas de alineación para obtener más información.
Puedes usar el método set_label_coords.
import matplotlib.pylab as plt
import random
import matplotlib.gridspec as gridspec
random.seed(20)
data1 = [random.random() for i in range(10)]
data2 = [random.random()*1000 for i in range(10)]
gs = gridspec.GridSpec(2,1)
fig = plt.figure()
ax = fig.add_subplot(gs[0])
ax.plot(data1)
ax.set_ylabel(r''Label One'', size =16)
ax.get_yaxis().set_label_coords(-0.1,0.5)
ax = fig.add_subplot(gs[1])
ax.plot(data2)
ax.set_ylabel(r''Label Two'', size =16)
ax.get_yaxis().set_label_coords(-0.1,0.5)
Ofrezco una solución al final, pero primero digo qué camino no conduce al éxito.
Revisé este problema recientemente, y pasé bastante tiempo probando varias soluciones, es decir, probando casi todas las combinaciones posibles de transformaciones entre los diferentes sistemas de coordenadas y su relación con el tight_layout()
. Experimenté solo con backend_pdf
, por lo que no puedo hablar sobre medios interactivos. Pero brevemente, mi conclusión es que no importa cómo intente averiguar las posiciones e intentar transformarlas, en este nivel no es posible alinear las etiquetas de los ejes. Supongo que de alguna manera debería ser posible, por ejemplo, de alguna manera internamente matplotlib puede alinear los ejes de las subparcelas en sí. Solo con dibujar en el archivo pdf dos veces y hacer algo como abajo entre medio, podría lograr mejores posiciones, pero aún no alineado:
# sorry for the `self`, this is from a class
def align_x_labels(self):
self.lowest_ax = min(self.axes.values(),
key = lambda ax: ax.xaxis.label.get_position()[1])
self.lowest_xlab_dcoo = self.lowest_ax.transData.transform(
self.lowest_ax.xaxis.label.get_position())
list(
map(
lambda ax: /
ax.xaxis.set_label_coords(
self.fig.transFigure.inverted().transform(
ax.transAxes.transform((0.5, 0.5)))[0],
self.fig.transFigure.inverted().transform(
self.lowest_xlab_dcoo)[1],
transform = self.fig.transFigure
),
self.axes.values()
)
)
Es una pena que una funcionalidad tan básica no se pueda lograr, y no está claro cómo los diferentes espacios de coordenadas se transforman y se ajustan a los diferentes pasos de la gráfica. Apreciaría mucho ver una explicación clara de esto, porque la página web matplotlib solo describe la arquitectura, presenta casos simples, pero no explica situaciones como esta. También me sorprende que los métodos que aceptan o devuelvan coordenadas no digan en su documentación qué tipos de coordenadas son esos. Finalmente encontré muy útil este tutorial .
Solución
Al final, en lugar de meterme con las transformaciones, creé en GridSpec
una fila adicional de altura cero y ejes invisibles (lo mismo se puede hacer con una columna de ancho cero para las etiquetas del eje y). Luego agregué etiquetas para estas subparcelas, estableciendo la alineación verticalalignment
en la top
.
# get one of the zero height phantom subplots to `self.ax`:
self.get_subplot(i, 1)
# set empty ticklabels:
self.ax.xaxis.set_ticklabels([])
self.ax.yaxis.set_ticklabels([])
# set the axis label:
self.ax.set_xlabel(labtext, fontproperties = self.fp_axis_lab)
# and this is matter of aesthetics
# sometimes bottom or center might look better:
self.ax.xaxis.label.set_verticalalignment(''top'')