Data tyknięcia i rotacja w matplotlib

175

Mam problem z obracaniem moich znaczników daty w matplotlib. Mały przykładowy program znajduje się poniżej. Jeśli spróbuję obrócić kleszcze na końcu, kleszcze nie obracają się. Jeśli spróbuję obrócić znaczniki, jak pokazano pod komentarzem „awarie”, wtedy matplot lib ulega awarii.

Dzieje się tak tylko wtedy, gdy wartości x są datami. Jeśli zastąpię zmienną dateszmienną tw wywołaniu avail_plot, xticks(rotation=70)połączenie działa dobrze w środku avail_plot.

Jakieś pomysły?

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
źródło
3
Tworzenie wykresu z datami na osi X jest tak częstym zadaniem - szkoda, że ​​nie ma tam pełniejszych przykładów.
alexw
Zastanawiam się, czy to nie jest duplikat stackoverflow.com/questions/10998621/ ...
ImportanceOfBeingErnest

Odpowiedzi:

249

Jeśli wolisz podejście nie zorientowane obiektowo, przejdź plt.xticks(rotation=70)na prawo przed dwoma avail_plotwywołaniami, np

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

Powoduje to ustawienie właściwości obrotu przed skonfigurowaniem etykiet. Ponieważ masz tutaj dwie osie, plt.xtickspo wykonaniu dwóch wykresów jest zdezorientowany. W momencie, gdy plt.xticksnic nie robi, plt.gca()nie nie daje osie, które chcesz zmodyfikować, i tak plt.xticks, która działa na obecnych osi, nie ma pracy.

Aby nie używać podejścia zorientowanego obiektowo plt.xticks, możesz użyć

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

po dwóch avail_plottelefonach. W szczególności ustawia to obrót na prawidłowych osiach.

cge
źródło
11
Jeszcze jedna poręczna rzecz: kiedy dzwonisz plt.setp, możesz ustawić wiele parametrów, określając je jako dodatkowe argumenty słów kluczowych. horizontalalignmentkwarg jest szczególnie przydatna podczas obracania etykiet podziałki:plt.setp( axs[1].xaxis.get_majorticklabels(), rotation=70, horizontalalignment='right' )
8one6
32
Nie podoba mi się to rozwiązanie, ponieważ łączy w sobie podejście pyplot i obiektowe. Możesz po prostu zadzwonić ax.tick_params(axis='x', rotation=70)w dowolnym miejscu.
Ted Petrou
3
@TedPetrou Co masz na myśli, mówiąc tutaj o „miksowaniu”? plt.setprozwiązanie jest całkowicie zorientowany obiektowo. Jeśli nie podoba ci się fakt, że coś pltw nim jest, użyj from matplotlib.artist import setp; setp(ax.get_xticklabels(), rotation=90)zamiast tego.
ImportanceOfBeingErnest
@ImportanceOfBeingErnest Pierwsza linia kodu używa plt.xtickspyplot. Sama dokumentacja mówi, aby nie mieszać stylów. plt.setpjest bardziej szczegółowy, ale nie jest też typowym stylem zorientowanym obiektowo. Twoja odpowiedź jest tutaj zdecydowanie najlepsza. Zasadniczo wszystko, co ma funkcję, nie jest zorientowane obiektowo. Nie ma znaczenia, czy importujesz, setpczy nie.
Ted Petrou
Znaczna część kodu pytania używa pyplot, na przykład yticks, a kod nie ma zdefiniowanego ax w ogóle poza avail_plot...
cge
113

Rozwiązanie działa dla matplotlib 2.1+

Istnieje metoda osi, tick_paramsktóra może zmieniać właściwości znaczników. Istnieje również jako metoda osi, jakset_tick_params

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

Lub

ax.xaxis.set_tick_params(rotation=45)

Na marginesie, obecne rozwiązanie łączy stanowy interfejs (przy użyciu pyplot) z interfejsem zorientowanym obiektowo za pomocą polecenia plt.xticks(rotation=70). Ponieważ kod w pytaniu wykorzystuje podejście obiektowe, najlepiej jest trzymać się tego podejścia przez cały czas. Rozwiązanie daje dobre jawne rozwiązanie zplt.setp( axs[1].xaxis.get_majorticklabels(), rotation=70 )

Ted Petrou
źródło
Czy można zmienić domyślny obrót dla wszystkich działek? Myślę, że muszę to zmienić na każdej działce, którą tworzę.
Chogg
42

Prostym rozwiązaniem, które pozwala uniknąć zapętlenia się znaczników, jest po prostu użycie

fig.autofmt_xdate()

To polecenie automatycznie obraca etykiety osi x i dostosowuje ich położenie. Wartości domyślne to kąt obrotu 30 ° i wyrównanie poziome „w prawo”. Ale można je zmienić w wywołaniu funkcji

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

Dodatkowy bottomargument jest równoważny ustawieniu plt.subplots_adjust(bottom=bottom), które pozwala ustawić dopełnienie dolnych osi na większą wartość, aby hostować obrócone etykiety znaczników.

Więc w zasadzie tutaj masz wszystkie ustawienia, których potrzebujesz, aby mieć ładną oś daty w jednym poleceniu.

Dobry przykład można znaleźć na stronie matplotlib.

ImportanceOfBeingErnest
źródło
Świetna odpowiedź! Dzięki!
Abramodj,
czy możesz kontrolować parametr „tryb_obrotu” za pomocą tej funkcji?
TheoryX
1
@TheoryX Nie. Jeśli potrzebujesz trybu rotacji, musisz albo użyć plt.setppodejścia, albo zapętlić tiki (co jest zasadniczo takie samo).
ImportanceOfBeingErnest
Tak, jest to łatwe rozwiązanie tylko wtedy, gdy oś x znajduje się na dole wykresu. Jednak gdy oś x znajduje się na górze wykresu lub gdy dwie osie x ( twinx) są obecne, zaburza to wyrównanie między znacznikami a etykietami znaczników. W takim przypadku rozwiązanie Teda Petrou jest lepsze.
Śubham
@ Śubham. Poprawnie. Jest to głównie skrót do wszystkich innych rozwiązań dla najczęściej spotykanego przypadku.
ImportanceOfBeingErnest
18

Innym sposobem zastosowania horizontalalignmenti rotationdo każdej etykiety ticka jest wykonanie forpętli nad etykietami tików, które chcesz zmienić:

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

wprowadź opis obrazu tutaj

A oto przykład, jeśli chcesz kontrolować lokalizację głównych i mniejszych tików:

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

wprowadź opis obrazu tutaj

Pablo Reyes
źródło
1
To zadziałało dla mnie na matplotlib v1.5.1 (utknąłem na starszej wersji matplotlib w pracy, nie pytaj dlaczego)
Eddy
Właśnie przetestowałem python3.6 i matplotlib v2.2.2 i też działa.
Pablo Reyes
2

Po prostu użyj

ax.set_xticklabels(label_list, rotation=45)
gizzmole
źródło