web-dev-qa-db-fra.com

En-têtes de ligne et de colonne dans les sous-tracés de matplotlib

Quelle est la meilleure pratique pour ajouter une ligne et un en-tête de colonne à une grille de sous-tracés générés dans une boucle dans matplotlib? Je peux penser à un couple, mais pas particulièrement soigné:

  1. Pour les colonnes, avec un compteur à votre boucle, vous pouvez utiliser set_title() pour la première ligne uniquement. Pour les lignes, cela ne fonctionne pas. Vous devriez dessiner text en dehors des tracés.
  2. Vous ajoutez une ligne supplémentaire de sous-parcelles en haut et une colonne supplémentaire de sous-parcelles à gauche, et dessinez du texte au milieu de cette sous-parcelle.

Pouvez-vous suggérer une meilleure alternative?

enter image description here

61
gozzilli

Il y a plusieurs moyens de le faire. Le moyen le plus simple est d'exploiter les étiquettes y et les titres de l'intrigue, puis d'utiliser fig.tight_layout() pour faire de la place aux étiquettes. Alternativement, vous pouvez placer du texte supplémentaire au bon endroit avec annotate, puis faire de la place semi-manuellement.


Si vous n'avez pas d'étiquettes y sur vos axes, il est facile d'exploiter le titre et l'étiquette y de la première ligne et colonne des axes.

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

enter image description here


Si vous avez des étiquettes y ou si vous préférez un peu plus de flexibilité, vous pouvez utiliser annotate pour placer les étiquettes. Ceci est plus compliqué, mais vous permet d'avoir des titres de tracé, des étiquettes, etc. en plus des étiquettes de ligne et de colonne.

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

enter image description here

71
Joe Kington

La réponse ci-dessus fonctionne. Mais pas que dans la deuxième version de la réponse, vous avez:

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

au lieu 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')
1
Alan Shteyman