Tiquetaques de data e rotação no matplotlib

175

Estou com um problema ao tentar alternar meus marcadores de data no matplotlib. Um pequeno programa de amostra está abaixo. Se eu tentar girar os carrapatos no final, eles não serão rotacionados. Se eu tentar girar os ticks, conforme mostrado no comentário 'travamentos', a matplot lib travará.

Isso só acontece se os valores x forem datas. Se eu substituir a variável datespela variável tna chamada para avail_plot, a xticks(rotation=70)chamada funcionará bem por dentro avail_plot.

Alguma ideia?

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
fonte
3
Criar um gráfico com datas no eixo x é uma tarefa tão comum - uma pena que não haja exemplos mais completos por aí.
alexw
Estou querendo saber se isso não é uma duplicata do stackoverflow.com/questions/10998621/…
ImportanceOfBeingErnest

Respostas:

249

Se você preferir uma abordagem não orientada a objetos, vá plt.xticks(rotation=70)para a direita antes das duas avail_plotchamadas, por exemplo

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

Isso define a propriedade de rotação antes de configurar os rótulos. Como você tem dois eixos aqui, plt.xticksfica confuso depois de fazer os dois gráficos. No momento em que plt.xticksnão faz nada, plt.gca()não não dar-lhe os eixos que pretende modificar, e assim plt.xticks, que atua sobre os eixos atuais, não está indo para o trabalho.

Para uma abordagem orientada a objeto que não está sendo usada plt.xticks, você pode usar

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

após as duas avail_plotchamadas. Isso define a rotação nos eixos corretos especificamente.

cge
fonte
11
Outra coisa útil: quando você liga, plt.setppode definir vários parâmetros, especificando-os como argumentos adicionais de palavras-chave. o horizontalalignmentkwarg é particularmente útil quando você girar os rótulos de escala:plt.setp( axs[1].xaxis.get_majorticklabels(), rotation=70, horizontalalignment='right' )
8one6
32
Não gosto dessa solução, pois combina o pyplot e as abordagens orientadas a objetos. Você pode simplesmente ligar ax.tick_params(axis='x', rotation=70)em qualquer lugar.
Ted Petrou #
3
@TedPetrou O que você quer dizer com "mixar" aqui? a plt.setpsolução é completamente orientada a objetos. Se você não gosta do fato de que existe algum plt, use-o from matplotlib.artist import setp; setp(ax.get_xticklabels(), rotation=90).
ImportanceOfBeingErnest
@ImportanceOfBeingErnest A primeira linha de código usa o plt.xticksque é pyplot. A documentação em si diz para não misturar estilos. plt.setpé mais detalhado, mas também não é o estilo típico de orientação a objetos. Sua resposta é definitivamente a melhor aqui. Basicamente, qualquer coisa com uma função não é orientada a objetos. Não importa se você importa setpou não.
Ted Petrou #
Grande parte usa o código da pergunta pyplot, como yticks, eo código não tem machado definido em tudo fora de avail_plot...
cge
113

A solução funciona para o matplotlib 2.1+

Existe um método de eixos tick_paramsque pode alterar as propriedades do tick. Também existe como método de eixo comoset_tick_params

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

Ou

ax.xaxis.set_tick_params(rotation=45)

Como uma nota lado, a solução actual mistura a interface com informações de estado (utilizando pyplot) com a interface orientada para o objecto, utilizando o comando plt.xticks(rotation=70). Como o código da pergunta usa a abordagem orientada a objetos, é melhor manter essa abordagem o tempo todo. A solução fornece uma boa solução explícita complt.setp( axs[1].xaxis.get_majorticklabels(), rotation=70 )

Ted Petrou
fonte
É possível alterar a rotação padrão para todas as plotagens? Eu acho que preciso mudar isso em cada enredo que faço.
Chogg 17/06
42

Uma solução fácil que evita repetir os ticklabes é usar apenas

fig.autofmt_xdate()

Este comando gira automaticamente os rótulos de xaxis e ajusta sua posição. Os valores padrão são um ângulo de rotação de 30 ° e alinhamento horizontal "à direita". Mas eles podem ser alterados na chamada de função

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

O bottomargumento adicional é equivalente à configuração plt.subplots_adjust(bottom=bottom), que permite definir o preenchimento dos eixos inferiores para um valor maior para hospedar as etiquetas rotacionadas.

Então, basicamente, aqui você tem todas as configurações necessárias para ter um bom eixo de data em um único comando.

Um bom exemplo pode ser encontrado na página matplotlib.

ImportanceOfBeingErnest
fonte
Ótima resposta! Obrigado!
Abramodj 7/07
você pode controlar o parâmetro 'rotation_mode' através desta função?
TheoryX
1
@TheoryX Não. Se você precisar de rotation_mode, use a plt.setpabordagem ou faça um loop sobre os ticks (que são essencialmente os mesmos).
ImportanceOfBeingErnest
Sim, é uma solução fácil apenas quando o eixo x está presente na parte inferior do gráfico. No entanto, quando o eixo x está na parte superior do gráfico ou quando dois eixos x ( twinx) estão presentes, ele estraga o alinhamento entre os ticks e os rótulos dos ticks. Nesse caso, a solução de Ted Petrou é melhor.
Śubham
@ Hamubham Correto. Isso é principalmente um atalho para todas as outras soluções para o caso mais comum.
ImportanceOfBeingErnest
18

Outra maneira de aplicar horizontalalignmente rotationa cada rótulo de escala é fazer um forloop sobre os rótulos de escala que você deseja alterar:

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

insira a descrição da imagem aqui

E aqui está um exemplo, se você deseja controlar a localização de ticks maiores e menores:

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

insira a descrição da imagem aqui

Pablo Reyes
fonte
1
Isso funcionou para mim em v1.5.1 matplotlib (eu estou preso em uma versão legado de matplotlib no trabalho, não pergunte por quê)
Eddy
Acabei de testar com python3.6 e matplotlib v2.2.2 e funciona também.
Pablo Reyes
2

Basta usar

ax.set_xticklabels(label_list, rotation=45)
gizzmole
fonte