web-dev-qa-db-fra.com

matplotlib: Alignement des étiquettes de l'axe des y dans les diagrammes de dispersion empilés

Dans le graphique ci-dessous, j'ai deux diagrammes de dispersion qui ont une échelle numérique différente, de sorte que leurs étiquettes d'axe des ordonnées ne sont pas alignées. Est-il possible de forcer l'alignement horizontal dans les étiquettes d'axe des 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()

stacked scatter plots

22
dimka

Vous pouvez utiliser la méthode 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)

enter image description here

25
Greg Whittier

Comme indiqué dans le commentaire, ce que vous recherchez est résolu en utilisant set_label_coords() comme décrit ici . Pour votre cas, ce sera quelque chose comme:

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

Depuis l'écriture de cette question, matplotlib a ajouté une fonction facile à utiliser qui aligne les étiquettes. La méthode correcte pour forcer l'alignement des étiquettes consiste à utiliser la fonction fig.align_labels() avant d'afficher la figure.

Si vous avez besoin d’un contrôle plus fin, vous pouvez également utiliser les fonctions Figure.align_xlabels() ou Figure.align_ylabels().

Voici une version de travail du code posté dans la question. Une seule ligne a été ajoutée (l'avant-dernière ligne) pour valider la solution.

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

Veuillez vous référer à la documentation de Matplotlib sur l’alignement des étiquettes pour plus d’informations.

3
Steven Noyce

Voici une fonction que j'ai écrite pour aligner automatiquement les étiquettes, mais cela ne semble pas fonctionner dans un script, mais de manière interactive.

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)

Et un exemple:

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

 example figure

1
Samuel Powell

Je propose une solution à la fin, mais d’abord, je dis de quelle manière ne mène pas au succès.

J’ai récemment réexaminé cette question et passé un certain temps à essayer diverses solutions, c’est-à-dire presque toutes les combinaisons possibles de transformations entre les différents systèmes de coordonnées et leur relation avec la tight_layout(). J'ai expérimenté uniquement avec backend_pdf, donc je ne peux pas parler de médias interactifs. Mais brièvement, ma conclusion est que peu importe la façon dont vous essayez de trouver les positions et tentez de les transformer, il n’est pas possible à ce niveau d’aligner les étiquettes des axes. Je suppose que cela devrait être possible. Par exemple, matplotlib est capable d’aligner lui-même les axes des sous-parcelles. mais toujours pas aligné:

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

Il est dommage qu'une telle fonctionnalité de base ne puisse être obtenue, et il est difficile de savoir comment les différents espaces de coordonnées sont transformés et redimensionnés aux différentes étapes du traçage. J'apprécierais beaucoup de voir une explication claire de cela, car la page Web matplotlib ne décrit que l'architecture, présente des cas simples, mais ne parvient pas à expliquer de telles situations. De plus, je suis surpris que les méthodes acceptant ou renvoyant des coordonnées n'indiquent pas dans leur docstring quels types de coordonnées sont ceux. Enfin, j'ai trouvé très utile ce tutoriel .

Solution

À la fin, au lieu de jouer avec les transformations, j'ai créé dans la GridSpec une rangée supplémentaire d'axes invisibles et de hauteur zéro (il en va de même avec une colonne de largeur zéro pour les étiquettes d'axe des y). Ensuite, j'ai ajouté des étiquettes pour ces sous-parcelles, en définissant verticalalignment à 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')
0
deeenes