Xlabel / ylabel commun pour les sous-graphiques matplotlib

143

J'ai l'intrigue suivante:

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

et maintenant je voudrais donner à ce tracé des étiquettes communes sur l'axe des x et des étiquettes sur l'axe des y. Avec "commun", je veux dire qu'il devrait y avoir une grande étiquette de l'axe des x sous toute la grille des sous-graphiques, et une grande étiquette de l'axe des y vers la droite. Je ne trouve rien à ce sujet dans la documentation de plt.subplots, et mes googlings suggèrent que je dois faire un gros plt.subplot(111)pour commencer - mais comment puis-je ensuite mettre mes sous-parcelles 5 * 2 dans cela en utilisant plt.subplots?

jolindbe
la source
2
Avec la mise à jour de la question et les commentaires laissés dans les réponses ci-dessous, il s'agit d'un double de stackoverflow.com/questions/6963035/...
Hooked
Pas vraiment, puisque ma question est pour plt.subplots (), et la question à laquelle vous liez utilise add_subplot - je ne peux pas utiliser cette méthode à moins que je ne passe à add_subplot, ce que je voudrais éviter. Je pourrais utiliser la solution plt.text qui est donnée comme solution alternative dans votre lien, mais ce n'est pas la solution la plus élégante.
jolindbe
Pour élaborer, pour autant que je sache, plt.subplots ne peut pas générer un ensemble de sous-graphiques dans un environnement d'axe existant, mais crée toujours une nouvelle figure. Droite?
jolindbe
Une solution des plus élégantes peut être trouvée ici: stackoverflow.com/questions/6963035/…
Mr.H
Votre lien a été fourni par l'utilisateur Hooked il y a plus de 4 ans (juste quelques commentaires au-dessus du vôtre). Comme je l'ai dit précédemment, cette solution concerne add_subplot, et non plt.subplots ().
jolindbe

Réponses:

206

Cela ressemble à ce que vous voulez réellement. Il applique la même approche de cette réponse à votre cas spécifique:

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

Plots multiples avec étiquette d'axes communs

divenex
la source
4
notez que 0,5 pour la coordonnée x de l'étiquette x ne place pas l'étiquette au centre du sous-tracé central. vous devez aller légèrement plus grand que cela pour tenir compte des yticklabels.
dbliss
2
Jetez un œil à cette réponse pour une méthode qui n'utilise pas plt.text. Vous créez vos sous-tracés, puis ajoutez un tracé de bits, rendez-le invisible et étiquetez ses x et y.
James Owers
Merci, travaillé en général. Une solution à la casse lors de l'utilisation tight_layout?
serv-inc
3
@ serv-inc avec le tight_layoutremplacement 0.04par 0semble fonctionner.
divenex le
3
L'utilisation fig.textn'est pas une bonne idée. Cela gâche des choses commeplt.tight_layout()
Peaceful
55

Comme je le considère suffisamment pertinent et élégant (pas besoin de spécifier les coordonnées pour placer le texte), je copie (avec une légère adaptation) une réponse à une autre question connexe .

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

Il en résulte ce qui suit (avec matplotlib version 2.2.0):

Sous-graphiques à 5 lignes et 2 colonnes avec des étiquettes communes des axes x et y

bli
la source
4
Pour des raisons de simplicité, cela devrait être la réponse acceptée. Très simple. Toujours pertinent pour matplotlib v3.x.
Kyle Swanson
J'aimerais savoir comment peut-il être utilisé avec plusieurs objets figure? fig.xlabel ("foo") ne fonctionne pas.
Horror Vacui
FYI: Maintenant que les gens utilisent des thèmes sombres dans StackOverflow, les étiquettes peuvent à peine être lues, il est donc préférable d'exporter vos png avec un fond blanc
xyzzyqed
@xyzzyqed Je ne savais pas qu'il y avait des "thèmes" dans stackoverflow, et je ne me souviens même pas comment j'ai exporté la figure. Comment puis-je contrôler l'arrière-plan lors de l'exportation?
bli
2
Le seul problème de cette solution est qu'elle ne fonctionne pas lors de son utilisation constrained_layout=Truecar elle crée des étiquettes qui se chevauchent. Dans ce cas, vous devez ajuster manuellement les bordures des sous-graphiques.
baccandr le
35

Sans sharex=True, sharey=Truevous obtenez:

entrez la description de l'image ici

Avec lui, vous devriez le rendre plus agréable:

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

entrez la description de l'image ici

Mais si vous souhaitez ajouter des étiquettes supplémentaires, vous ne devez les ajouter qu'aux tracés d'arête:

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

entrez la description de l'image ici

Ajouter une étiquette pour chaque tracé le gâcherait (il existe peut-être un moyen de détecter automatiquement les étiquettes répétées, mais je n'en ai pas connaissance).

Piotr Migdal
la source
C'est beaucoup plus difficile si, par exemple, le nombre de tracés est inconnu (par exemple, vous avez une fonction de généralisation de tracé qui fonctionne pour n'importe quel nombre de sous-tracés).
naught101
15

Depuis la commande:

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

vous avez utilisé renvoie un tuple composé de la figure et d'une liste des instances d'axes, il suffit déjà de faire quelque chose comme (attention, j'ai changé fig,axen 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 vous souhaitez modifier certains détails sur une sous-parcelle spécifique, vous pouvez y accéder via axes[i]iitère sur vos sous-parcelles.

Il peut également être très utile d'inclure un

fig.tight_layout()

à la fin du fichier, avant le plt.show(), afin d'éviter le chevauchement des étiquettes.

Marius
la source
5
Je suis désolé de ne pas être un peu clair ci-dessus. Avec "commun", je voulais dire une seule étiquette x en dessous de toutes les parcelles, et une seule étiquette y à gauche des parcelles, j'ai mis à jour la question pour refléter cela.
jolindbe
2
@JohanLindberg: Concernant vos commentaires ici et ci-dessus: Indeed plt.subplots()créera une nouvelle instance de figure. Si vous souhaitez vous en tenir à cette commande, vous pouvez facilement ajouter un big_ax = fig.add_subplot(111), car vous avez déjà une figure et pouvez ajouter un autre axe. Après cela, vous pouvez manipuler big_axla façon dont il est affiché dans le lien de Hooked.
Marius
Merci pour vos suggestions, mais si je fais cela, je dois ajouter le big_ax après plt.subplots (), et j'obtiens ce sous-graphique en plus de tout le reste - puis-je le rendre transparent ou l'envoyer à l'arrière d'une manière ou d'une autre? Même si je mets toutes les couleurs sur aucune comme dans le lien Hooked, il s'agit toujours d'une boîte blanche couvrant toutes mes sous-parcelles.
jolindbe
2
@JohanLindberg, tu as raison, je n'avais pas vérifié ça. Mais vous pouvez facilement définir la couleur d'arrière-plan du grand axe noneen faisant: big_ax.set_axis_bgcolor('none')Vous devez également créer la couleur de l'étiquette none(par opposition à l'exemple lié par Hooked):big_ax.tick_params(labelcolor='none', top='off', bottom='off', left='off', right='off')
Marius
2
J'obtiens une erreur: AttributeError: 'numpy.ndarray' object has no attribute 'set_xlabel'dans la déclaration ax.set_xlabel('Common x-label'). Pouvez-vous le comprendre?
hengxin
5

Cela sera mieux si vous réservez de l'espace pour les étiquettes communes en créant des étiquettes invisibles pour le sous-tracé dans le coin inférieur gauche. Il est également bon de passer la taille de la police depuis rcParams. De cette façon, les étiquettes communes changeront de taille avec votre configuration rc, et les axes seront également ajustés pour laisser de l'espace pour les étiquettes communes.

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

entrez la description de l'image ici entrez la description de l'image ici

EL_DON
la source
1
Belle utilisation de l'étiquette invisible! Merci
colelemonz
3

J'ai rencontré un problème similaire en traçant une grille de graphiques. Les graphiques se composaient de deux parties (haut et bas). L'étiquette y était censée être centrée sur les deux parties.

Je ne voulais pas utiliser une solution qui dépend de la connaissance de la position dans la figure extérieure (comme fig.text ()), j'ai donc manipulé la position y de la fonction set_ylabel (). Il est généralement égal à 0,5, au milieu du graphique auquel il est ajouté. Comme le remplissage entre les parties (hspace) dans mon code était nul, j'ai pu calculer le milieu des deux parties par rapport à la partie supérieure.

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

image

Inconvénients:

  • La distance horizontale par rapport au tracé est basée sur la partie supérieure, les graduations inférieures peuvent s'étendre dans l'étiquette.

  • La formule ne tient pas compte de l'espace entre les pièces.

  • Lève une exception lorsque la hauteur de la partie supérieure est de 0.

Il existe probablement une solution générale qui prend en compte le remplissage entre les chiffres.

CPe
la source
Hé, j'ai trouvé un moyen de faire cela dans la veine de votre réponse, mais je pourrais résoudre certains de ces problèmes; voir stackoverflow.com/a/44020303/4970632 (ci-dessous)
Luke Davis
2

Mettre à jour:

Cette fonctionnalité fait maintenant partie du paquet proplot matplotlib que j'ai récemment publié sur pypi. Par défaut, lorsque vous créez des figures, les étiquettes sont "partagées" entre les axes.


Réponse originale:

J'ai découvert une méthode plus robuste:

Si vous connaissez les kwargs bottomet topqui sont entrés dans une GridSpecinitialisation, ou si vous connaissez les positions des bords de vos axes en Figurecoordonnées , vous pouvez également spécifier la position de l'étiquette y en Figurecoordonnées avec une magie de «transformation» sophistiquée. Par exemple:

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

... et vous devriez voir que l'étiquette s'ajuste toujours de manière appropriée de gauche à droite pour éviter de se chevaucher avec les étiquettes, comme d'habitude - mais maintenant elle s'ajustera pour être toujours exactement entre les sous-graphiques souhaités.

De plus, si vous ne l'utilisez même pas set_position, le ylabel apparaîtra par défaut exactement à mi-hauteur de la figure . Je suppose que c'est parce que lorsque l'étiquette est finalement dessinée, matplotlibutilise 0,5 pour la y-coordonnée sans vérifier si la transformation de coordonnées sous-jacente a changé.

Luke Davis
la source