108 votes

Plusieurs axes dans matplotlib avec des échelles différentes

Comment plusieurs échelles peuvent-elles être mises en œuvre dans Matplotlib? Je ne parle pas de l'axe primaire et secondaire tracé contre le même axe des x, mais de quelque chose comme de nombreuses tendances qui ont différentes échelles tracées sur le même axe des y et qui peuvent être identifiées par leurs couleurs.

Par exemple, si j'ai tendance1 ([0,1,2,3,4]) et tendance2 ([5000,6000,7000,8000,9000]) à tracer par rapport au temps et que je veux que les deux tendances soient de couleurs différentes et à des échelles différentes dans l'axe Y, comment puis-je accomplir cela avec Matplotlib?

Lorsque j'ai regardé dans Matplotlib, ils disent qu'ils n'ont pas cela pour le moment même si c'est certainement sur leur liste de souhaits, y a-t-il un moyen de contourner cela pour que cela se produise?

Y a-t-il d'autres outils de tracé pour python qui peuvent rendre cela possible?

130voto

Suuuehgi Points 926

Depuis la réponse de Steve Tjoa qui apparaît toujours en premier et souvent seule lorsque je cherche plusieurs axes y sur Google, j'ai décidé d'ajouter une version légèrement modifiée de sa réponse. Il s'agit de l'approche de cet exemple matplotlib.

Raisons :

  • Ses modules échouent parfois pour moi dans des circonstances inconnues et des erreurs internes cryptiques.
  • Je n'aime pas charger des modules exotiques que je ne connais pas (mpl_toolkits.axisartist, mpl_toolkits.axes_grid1).
  • Le code ci-dessous contient des commandes plus explicites des problèmes sur lesquels les gens butent souvent (comme une seule légende pour plusieurs axes, l'utilisation de viridis, ...) plutôt que des comportements implicites.

Graphique

import matplotlib.pyplot as plt 

# Créer figure et subplot manuellement
# fig = plt.figure()
# host = fig.add_subplot(111)

# Enveloppe plus polyvalente
fig, host = plt.subplots(figsize=(8,5), layout='constrained') # (largeur, hauteur) en pouces
# (voir https://matplotlib.org/stable/api/_as-gen/matplotlib.pyplot.subplots.html et
# .. https://matplotlib.org/stable/tutorials/intermediate/constrainedlayout_guide.html)

ax2 = host.twinx()
ax3 = host.twinx()

host.set_xlim(0, 2)
host.set_ylim(0, 2)
ax2.set_ylim(0, 4)
ax3.set_ylim(1, 65)

host.set_xlabel("Distance")
host.set_ylabel("Densité")
ax2.set_ylabel("Température")
ax3.set_ylabel("Vitesse")

couleur1, couleur2, couleur3 = plt.cm.viridis([0, .5, .9])

p1 = host.plot([0, 1, 2], [0, 1, 2],    color=couleur1, label="Densité")
p2 = ax2.plot( [0, 1, 2], [0, 3, 2],    color=couleur2, label="Température")
p3 = ax3.plot( [0, 1, 2], [50, 30, 15], color=couleur3, label="Vitesse")

host.legend(handles=p1+p2+p3, loc='meilleur')

# droite, gauche, haut, bas
ax3.spines['right'].set_position(('vers l'extérieur', 60))

# pas de repères x                 
host.xaxis.set_ticks([])

# Alternativement (plus verbeux) :
# host.tick_params(
#     axis='x',          # modifications appliquées à l'axe x
#     which='both',      # à la fois les repères principaux et secondaires sont affectés
#     bottom=False,      # les repères le long du bord inférieur sont désactivés
#     labelbottom=False) # les étiquettes le long du bord inférieur sont désactivées
# parfois utile : direction='intérieur'    

# Déplacer l'axe "Vitesse" sur la gauche
# ax3.spines['left'].set_position(('vers l'extérieur', 60))
# ax3.spines['left'].set_visible(True)
# ax3.spines['right'].set_visible(False)
# ax3.yaxis.set_label_position('left')
# ax3.yaxis.set_ticks_position('left')

host.yaxis.label.set_color(p1[0].get_color())
ax2.yaxis.label.set_color(p2[0].get_color())
ax3.yaxis.label.set_color(p3[0].get_color())

# Pour une composition professionnelle, par exemple LaTeX, utiliser .pgf ou .pdf
# Pour des graphiques raster, utiliser l'argument dpi. Par exemple, '[...].png", dpi=300)'
plt.savefig("pyplot_multiple_y-axis.pdf", bbox_inches='tight')
# bbox_inches='tight' : essaie de supprimer l'excès d'espace vide
# https://matplotlib.org/stable/api/_as-gen/matplotlib.pyplot.savefig.html

125voto

Steve Tjoa Points 15116

Si je comprends bien la question, vous pourriez être intéressé par cet exemple dans la galerie Matplotlib.

entre description de l'image ici

Le commentaire de Yann ci-dessus fournit un exemple similaire.


Édition - Lien corrigé ci-dessus. Code correspondant copié de la galerie Matplotlib :

from mpl_toolkits.axes_grid1 import host_subplot
import mpl_toolkits.axisartist as AA
import matplotlib.pyplot as plt

host = host_subplot(111, axes_class=AA.Axes)
plt.subplots_adjust(right=0.75)

par1 = host.twinx()
par2 = host.twinx()

offset = 60
new_fixed_axis = par2.get_grid_helper().new_fixed_axis
par2.axis["right"] = new_fixed_axis(loc="right", axes=par2,
                                        offset=(offset, 0))

par2.axis["right"].toggle(all=True)

host.set_xlim(0, 2)
host.set_ylim(0, 2)

host.set_xlabel("Distance")
host.set_ylabel("Densité")
par1.set_ylabel("Température")
par2.set_ylabel("Vitesse")

p1, = host.plot([0, 1, 2], [0, 1, 2], label="Densité")
p2, = par1.plot([0, 1, 2], [0, 3, 2], label="Température")
p3, = par2.plot([0, 1, 2], [50, 30, 15], label="Vitesse")

par1.set_ylim(0, 4)
par2.set_ylim(1, 65)

host.legend()

host.axis["left"].label.set_color(p1.get_color())
par1.axis["right"].label.set_color(p2.get_color())
par2.axis["right"].label.set_color(p3.get_color())

plt.draw()
plt.show()

#plt.savefig("Test")

81voto

ShitalShah Points 2213

Si vous voulez faire des tracés très rapides avec un axe Y secondaire, il y a un moyen beaucoup plus facile d'utiliser la fonction wrapper de Pandas et juste 2 lignes de code. Il suffit de tracer votre première colonne, puis de tracer la seconde mais avec le paramètre secondary_y=True, comme ceci :

df.A.plot(label="Points", legend=True)
df.B.plot(secondary_y=True, label="Comments", legend=True)

Cela ressemblerait à quelque chose comme ci-dessous :

entrez ici la description de l'image

Vous pouvez également faire quelques autres choses. Jetez un œil à la documentation sur les tracés de Pandas.

18voto

Nasser Al-Wohaibi Points 578

Bootstrapping quelque chose de rapide pour tracer plusieurs y-axes partageant un axe des x en utilisant @joe-kington's réponse : entrer la description de l'image ici

# d = Pandas Dataframe, 
# ys = [ [colonnes dans le même y], [colonnes dans le même y], [colonnes dans le même y], .. ] 
def graphique(d,ys):

    from itertools import cycle
    fig, ax = plt.subplots()

    axes = [ax]
    pour y in ys[1:]:
        # Jumeler l'axe des x deux fois pour créer des y-axes indépendants.
        axes.append(ax.twinx())

    extra_ys =  len(axes[2:])

    # Faire de la place sur le côté droit pour les y-axes supplémentaires.
    si extra_ys > 0:
        temp = 0.85
        si extra_ys <= 2:
            temp = 0.75
        elif extra_ys <= 4:
            temp = 0.6
        si extra_ys > 5:
            print 'tu es ridicule'
        fig.subplots_adjust(right=temp)
        right_additive = (0.98-temp)/float(extra_ys)
    # Déplacer l'épine du dernier y-axis vers la droite par x% de la largeur des axes
    i = 1.
    for ax in axes[2:]:
        ax.spines['right'].set_position(('axes', 1.+right_additive*i))
        ax.set_frame_on(True)
        ax.patch.set_visible(False)
        ax.yaxis.set_major_formatter(matplotlib.ticker.OldScalarFormatter())
        i +=1.
    # Pour rendre la bordure de l'axe le plus à droite visible, nous devons activer le cadre
    # sur. Cela masque les autres tracés, cependant, nous devons désactiver son remplissage.

    cols = []
    lignes = []
    styles_de_ligne = cycle(['-','-','-', '--', '-.', ':', '.', ',', 'o', 'v', '^', '<', '>',
               '1', '2', '3', '4', 's', 'p', '*', 'h', 'H', '+', 'x', 'D', 'd', '|', '_'])
    couleurs = cycle(matplotlib.rcParams['axes.color_cycle'])
    pour ax,y in zip(axes,ys):
        ls=styles_de_ligne.next()
        si len(y)==1:
            col = y[0]
            cols.append(col)
            couleur = couleurs.next()
            lignes.append(ax.plot(d[col],linestyle=ls,label=col,color=couleur))
            ax.set_ylabel(col,color=couleur)
            #ax.tick_params(axis='y', colors=color)
            ax.spines['right'].set_color(couleur)
        sinon:
            pour col in y:
                couleur = couleurs.next()
                lignes.append(ax.plot(d[col],linestyle=ls,label=col,color=couleur))
                cols.append(col)
            ax.set_ylabel(', '.join(y))
            #ax.tick_params(axis='y')
    axes[0].set_xlabel(d.index.name)
    lns = lignes[0]
    pour l in lignes[1:]:
        lns +=l
    labs = [l.get_label() pour l in lns]
    axes[0].legend(lns, labs, loc=0)

    plt.show()

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X