Tiques de date et rotation dans matplotlib

175

J'ai un problème en essayant de faire tourner mes tiques de date dans matplotlib. Un petit exemple de programme est ci-dessous. Si j'essaye de faire pivoter les graduations à la fin, les graduations ne sont pas tournées. Si j'essaie de faire pivoter les graduations comme indiqué sous le commentaire «plante», alors la bibliothèque matplot plante.

Cela ne se produit que si les valeurs x sont des dates. Si je remplace la variable datespar la variable tdans l'appel à avail_plot, l' xticks(rotation=70)appel fonctionne très bien à l'intérieur avail_plot.

Des idées?

import numpy as np
import matplotlib.pyplot as plt
import datetime as dt

def avail_plot(ax, x, y, label, lcolor):
    ax.plot(x,y,'b')
    ax.set_ylabel(label, rotation='horizontal', color=lcolor)
    ax.get_yaxis().set_ticks([])

    #crashes
    #plt.xticks(rotation=70)

    ax2 = ax.twinx()
    ax2.plot(x, [1 for a in y], 'b')
    ax2.get_yaxis().set_ticks([])
    ax2.set_ylabel('testing')

f, axs = plt.subplots(2, sharex=True, sharey=True)
t = np.arange(0.01, 5, 1)
s1 = np.exp(t)
start = dt.datetime.now()
dates=[]
for val in t:
    next_val = start + dt.timedelta(0,val)
    dates.append(next_val)
    start = next_val

avail_plot(axs[0], dates, s1, 'testing', 'green')
avail_plot(axs[1], dates, s1, 'testing2', 'red')
plt.subplots_adjust(hspace=0, bottom=0.3)
plt.yticks([0.5,],("",""))
#doesn't crash, but does not rotate the xticks
#plt.xticks(rotation=70)
plt.show()
Neal
la source
3
Créer un tracé avec des dates sur l'axe des x est une tâche tellement courante - dommage qu'il n'y ait pas d'exemples plus complets là-bas.
alexw
Je me demande si ce n'est pas un double de stackoverflow.com/questions/10998621/…
ImportanceOfBeingErnest

Réponses:

249

Si vous préférez une approche non orientée objet, passez plt.xticks(rotation=70)à droite avant les deux avail_plotappels, par exemple

plt.xticks(rotation=70)
avail_plot(axs[0], dates, s1, 'testing', 'green')
avail_plot(axs[1], dates, s1, 'testing2', 'red')

Cela définit la propriété de rotation avant de configurer les étiquettes. Puisque vous avez deux axes ici, plt.xticksdevient confus après avoir fait les deux graphiques. Au moment où plt.xticksne fait rien, plt.gca()ne pas vous donner les axes que vous souhaitez modifier, et donc plt.xticks, qui agit sur les axes actuels, ne va pas au travail.

Pour une approche orientée objet n'utilisant pas plt.xticks, vous pouvez utiliser

plt.setp( axs[1].xaxis.get_majorticklabels(), rotation=70 )

après les deux avail_plotappels. Cela définit la rotation sur les axes corrects spécifiquement.

cge
la source
11
Une autre chose pratique: lorsque vous appelez, plt.setpvous pouvez définir plusieurs paramètres en les spécifiant comme arguments de mot-clé supplémentaires. le horizontalalignmentkwarg est particulièrement utile lorsque vous faites pivoter les étiquettes de graduation:plt.setp( axs[1].xaxis.get_majorticklabels(), rotation=70, horizontalalignment='right' )
8one6
32
Je n'aime pas cette solution car elle mélange les approches pyplot et orientée objet. Vous pouvez simplement appeler ax.tick_params(axis='x', rotation=70)n'importe où.
Ted Petrou
3
@TedPetrou Qu'entendez-vous par «mélanger» ici? la plt.setpsolution est complètement orientée objet. Si vous n'aimez pas le fait qu'il y plten ait, utilisez from matplotlib.artist import setp; setp(ax.get_xticklabels(), rotation=90)plutôt.
ImportanceOfBeingErnest
@ImportanceOfBeingErnest La première ligne de code utilise plt.xticksqui est pyplot. La documentation elle-même dit de ne pas mélanger les styles. plt.setpest plus verbeux mais pas non plus le style orienté objet typique. Votre réponse est certainement la meilleure ici. Fondamentalement, tout ce qui a une fonction n'est pas orienté objet. Peu importe que vous importiez setpou non.
Ted Petrou
Une grande partie du code de la question utilise pyplot, comme yticks, et le code n'a pas du tout défini ax en dehors de avail_plot...
cge
115

La solution fonctionne pour matplotlib 2.1+

Il existe une méthode d'axes tick_paramsqui peut modifier les propriétés des graduations. Il existe également en tant que méthode d'axe commeset_tick_params

ax.tick_params(axis='x', rotation=45)

Ou

ax.xaxis.set_tick_params(rotation=45)

En remarque, la solution actuelle mélange l'interface avec état (à l'aide de pyplot) avec l'interface orientée objet à l'aide de la commande plt.xticks(rotation=70). Étant donné que le code de la question utilise l'approche orientée objet, il est préférable de s'en tenir à cette approche tout au long. La solution donne une bonne solution explicite avecplt.setp( axs[1].xaxis.get_majorticklabels(), rotation=70 )

Ted Petrou
la source
Est-il possible de modifier la rotation par défaut pour tous les tracés? Je pense que je dois changer cela sur chaque intrigue que je fais.
Chogg le
42

Une solution simple qui évite de boucler sur les ticklabes consiste simplement à utiliser

fig.autofmt_xdate()

Cette commande fait automatiquement pivoter les étiquettes de l'axe x et ajuste leur position. Les valeurs par défaut sont un angle de rotation de 30 ° et un alignement horizontal "à droite". Mais ils peuvent être modifiés dans l'appel de fonction

fig.autofmt_xdate(bottom=0.2, rotation=30, ha='right')

L' bottomargument supplémentaire est équivalent au paramètre plt.subplots_adjust(bottom=bottom), qui permet de définir le remplissage des axes inférieurs sur une valeur plus grande pour héberger les étiquettes pivotées.

Donc, fondamentalement, ici, vous avez tous les paramètres dont vous avez besoin pour avoir un bel axe de date en une seule commande.

Un bon exemple peut être trouvé sur la page matplotlib.

ImportanceOfErnest
la source
Très bonne réponse! Merci!
Abramodj
pouvez-vous contrôler le paramètre 'rotation_mode' via cette fonction?
TheoryX
1
@TheoryX Non. Si vous avez besoin de rotation_mode, vous devez soit utiliser l' plt.setpapproche, soit faire une boucle sur les graduations (ce qui est essentiellement le même).
ImportanceOfBeingErnest
Oui, c'est une solution facile uniquement lorsque l'axe des x est présent en bas du graphique. Cependant, lorsque l'axe x est en haut du graphique ou lorsque deux axes x ( twinx) sont présents, cela déforme l'alignement entre les graduations et les étiquettes de graduation. Dans ce cas, la solution de Ted Petrou est meilleure.
Śubham
@ Śubham Correct. Il s'agit principalement d'un raccourci vers toutes les autres solutions pour le cas le plus courant.
ImportanceOfBeingErnest
18

Une autre façon d'appliquer horizontalalignmentet rotationà chaque étiquette de graduation consiste à faire une forboucle sur les étiquettes de graduation que vous souhaitez modifier:

import numpy as np
import matplotlib.pyplot as plt
import datetime as dt

now = dt.datetime.now()
hours = [now + dt.timedelta(minutes=x) for x in range(0,24*60,10)]
days = [now + dt.timedelta(days=x) for x in np.arange(0,30,1/4.)]
hours_value = np.random.random(len(hours))
days_value = np.random.random(len(days))

fig, axs = plt.subplots(2)
fig.subplots_adjust(hspace=0.75)
axs[0].plot(hours,hours_value)
axs[1].plot(days,days_value)

for label in axs[0].get_xmajorticklabels() + axs[1].get_xmajorticklabels():
    label.set_rotation(30)
    label.set_horizontalalignment("right")

entrez la description de l'image ici

Et voici un exemple si vous souhaitez contrôler l'emplacement des graduations majeures et mineures:

import numpy as np
import matplotlib.pyplot as plt
import datetime as dt

fig, axs = plt.subplots(2)
fig.subplots_adjust(hspace=0.75)
now = dt.datetime.now()
hours = [now + dt.timedelta(minutes=x) for x in range(0,24*60,10)]
days = [now + dt.timedelta(days=x) for x in np.arange(0,30,1/4.)]

axs[0].plot(hours,np.random.random(len(hours)))
x_major_lct = mpl.dates.AutoDateLocator(minticks=2,maxticks=10, interval_multiples=True)
x_minor_lct = matplotlib.dates.HourLocator(byhour = range(0,25,1))
x_fmt = matplotlib.dates.AutoDateFormatter(x_major_lct)
axs[0].xaxis.set_major_locator(x_major_lct)
axs[0].xaxis.set_minor_locator(x_minor_lct)
axs[0].xaxis.set_major_formatter(x_fmt)
axs[0].set_xlabel("minor ticks set to every hour, major ticks start with 00:00")

axs[1].plot(days,np.random.random(len(days)))
x_major_lct = mpl.dates.AutoDateLocator(minticks=2,maxticks=10, interval_multiples=True)
x_minor_lct = matplotlib.dates.DayLocator(bymonthday = range(0,32,1))
x_fmt = matplotlib.dates.AutoDateFormatter(x_major_lct)
axs[1].xaxis.set_major_locator(x_major_lct)
axs[1].xaxis.set_minor_locator(x_minor_lct)
axs[1].xaxis.set_major_formatter(x_fmt)
axs[1].set_xlabel("minor ticks set to every day, major ticks show first day of month")
for label in axs[0].get_xmajorticklabels() + axs[1].get_xmajorticklabels():
    label.set_rotation(30)
    label.set_horizontalalignment("right")

entrez la description de l'image ici

Pablo Reyes
la source
1
Cela a fonctionné pour moi sur matplotlib v1.5.1 (je suis coincé sur une version héritée de matplotlib au travail, ne demandez pas pourquoi)
Eddy
Je viens de tester avec python3.6 et matplotlib v2.2.2 et cela fonctionne aussi.
Pablo Reyes
3

Utilisez simplement

ax.set_xticklabels(label_list, rotation=45)
gizzmole
la source