DEV Community

Gisely Alves Brandão
Gisely Alves Brandão

Posted on • Edited on

Séries Temporais: Parte 1

Séries temporais são conjuntos de dados onde o tempo guia todas as outras variáveis. Pode conter qualquer forma de tempo, anos, dias, minutos, etc. Essa ideia de ordem temporal no conjunto de dados faz com que as análises das séries sejam um pouco diferentes.

Existe todo um campo de estudo em volta do assunto e, mesmo escrevendo sobre conceitos básicos, terei mais do que um artigo para abordar o tema (que também estou estudando). Nesse artigo vamos ver uma introdução a séries temporais, suas componentes e estacionariedade.

Antes de tudo precisamos preparar os nosso dados para a análise. Muitos datasets apresentam o tempo como uma string então a primeira coisa a se fazer é a conversão para o tipo Date. Também vamos passar essa coluna para o index do dataframe e plotar os dados logo depois. Esse dataset possui informações sobre o clima da cidade Delhi — Temperatura média, humidade, velocidade do vento e pressão.

import pandas as pd  
import numpy as np  
from matplotlib import pyplot as pltdf = pd.read_csv("DailyDelhiClimateTest.csv")  
df.date = pd.to_datetime(df.date)  
df = df.set_index('date')  
df.plot(subplots=True, figsize=(10,8))
Enter fullscreen mode Exit fullscreen mode

Esses dados são diários e eu vou trabalhar com eles assim, entretanto, podemos trabalhar numa frequência de tempo diferente usando a função resample . Como exemplo vou redimensionar os dados para mostrar por meses, agrupando os valores pela média e também vou redimensionar por hora e preencher os campos nulos que aparecem por não termos informações tão granuladas.

#Por mês:  
df_exemple.resample('M').mean()
Enter fullscreen mode Exit fullscreen mode


#Por hora:  
#Ffil preenche com o último valor anterior aos campos  
df_exemple.resample('H').ffill()
#Bfil preenche com os campos com o próximo valor válido na caluna
df_exemple.resample('H').bfill()
Enter fullscreen mode Exit fullscreen mode

método ffill versus método bfill
método ffill versus método bfill

Agora, para fazer as análises é preciso entender as séries temporais. Do modo como esse dataset está montado podemos tratar cada coluna como uma série. E as séries temporais são compostas por 4 componentes:

Tendência / Trend

Aqui é a tendência dos dados de crescer ou diminuir em longo período de tempo. Essa análise pode ser feita observando um gráfico de linha, onde conseguimos ver um crescimento ou uma diminuição suave com o tempo.

Sazonalidade

Essa é uma variação dos dados que se repete, de acordo com algum padrão, dentro de um curto intervalo de tempo bem definido (um ano no máximo). Podemos observar a sazonalidade no trânsito, que aumenta sempre nos horários de começo e fim de expediente, ou nas vendas de sorvete que aumentam sempre no verão e caem sempre no inverno.

Ciclicidade

Também é uma variação nos dados observada com algum padrão só que em longos períodos de tempo (mais de um ano) que nem sempre são iguais. Como a venda de casas, que por mais que possa ter alguma sazonalidade durante o ano, tem um ciclo de altas e baixas vendas quando observadas ao longo de décadas.

Resíduos / Ruído

São variações que não são explicadas pelas variáveis do dataset. Em geral são aleatórias.

A biblioteca “Statsmodels” é uma grande amiga quando o assunto é séries temporais. Vou usá-la para mostrar a decomposição da série da coluna meantemp (temperatura média). O resultado da função seasonal_decompose é um conjunto de séries dessa decomposição que você pode plotar como quiser.

from statsmodels.tsa.seasonal import seasonal_decompose  
result = seasonal_decompose(df.meantemp)  
result.plot()  
plt.show()
Enter fullscreen mode Exit fullscreen mode

Existem dois modos nos quais as componentes podem se relacionar com os seus dados, o modelo aditivo onde suas observações são fruto dessas componentes somadas e o modelo multiplicativo onde as componentes são multiplicadas. A componente cíclica é mostrada junto da componente de resíduos pois as duas não possuem um tempo certo de acontecimento.

Estacionariedade

Para trabalhar com séries temporais precisamos que ela seja estacionária. Essa característica além de ser necessária para algum modelos, facilita a projeção dos dados.

Simplificando bastante podemos entender a estacionariedade como uma série temporal com sua média, variância e co-variância permanecendo iguais quando comparados os períodos de tempo da série, ou seja, as estatísticas da série não sofrem variações com o tempo, não são dependentes dele.

A “forma” dos dados nos gráficos podem nos dar uma noção se a série é ou não estacionária. Uma maneira legal de enxergar isso é plotando as estatísticas de rolagem, isso é, ao invés de mostrar todas as observações ou calcular as estatísticas do conjunto inteiro, cada ponto é a estatística (média, desvio, variância, etc) dos pontos da “janela” anterior.

janela é um subconjunto de observações seguidas

mediamovel = df['meantemp'].rolling(window=7).mean()   
#window = 7 diz que a janela usada para média móvel é de 7 observações (no caso 7 dias)  
desviomovel = df['meantemp'].rolling(window=7).std()
orig = plt.plot(df['meantemp'], color='blue', label='Observado')  
media = plt.plot(mediamovel, color='red', label='Média Móvel')  
desvio = plt.plot(desviomovel, color='black', label='Desvio Padrão Móvel')  
plt.legend(loc='best')  
plt.title('Estatísticas de rolagem')  
plt.show(block=False)
Enter fullscreen mode Exit fullscreen mode

Com essa imagem conseguimos ter uma noção que a série não é estacionária, mas para termos certeza existe um teste estatístico chamado Augmented Dickey–Fuller (ADCF). O teste considera como hipótese nula a não estacionariedade e como hipótese alternativa a estacionariedade. Para interpretar o teste podemos olhar para o p-valor que tem ser menor do que o nível de significância escolhido e o valor do teste tem que ser menor que o valor crítico para esse mesmo nível de significância.

from statsmodels.tsa.stattools import adfuller  
adftest = adfuller(df['meantemp'])#Para printar os resultados de maneira elegante:  
out = pd.Series(adftest[0:4], index=['Teste','p-valor','Lags','Número de observações usadas'])  
for key,value in adftest[4].items():  
    out['Valor crítico (%s)'%key] = value  
print(out)

Teste                            -1.096474  
p-valor                           0.716476  
Lags                              0.000000  
Número de observações usadas    113.000000  
Valor crítico (10%)              -2.580604  
Valor crítico (5%)               -2.887477  
Valor crítico (1%)               -3.489590
Enter fullscreen mode Exit fullscreen mode

Como podemos ver essa série não é estacionária ainda. Pensando num nível de significância de 5%, o p-valor e o valor do teste estão muito altos.

Como deixar uma série estacionária?

Podemos aplicar algumas técnicas nos dados para conseguir uma série estacionária, como exemplo, transformações (logarítmica, a raiz quadrada, boxcox), diferenciação, etc.

O primeiro método que vou tentar é a diferenciação. Esse processo cria uma nova série a partir da diferença de uma observação com a observação anterior.

diff(t) = obs(t) - obs(t-1)

Através da diferenciação também é possível remover a dependência temporal de uma série, reduzindo a tendência e a sazonalidade. Por esse motivo esse método pode ser usado para atingir a estacionariedade. Pode-se diferenciar uma série mais de uma vez, caso seja necessário para remover a dependência.

dfdiff = df.meantemp.diff()  
dfdiff = dfdiff.dropna()  
dfdiff.plot()
Enter fullscreen mode Exit fullscreen mode

E aplicando o ADCF teste temos:

Teste                         -1.203580e+01  
p-valor                        2.805321e-22  
Lags                           0.000000e+00  
Número de observações usadas   1.120000e+02  
Valor Crítico (10%)           -2.580730e+00  
Valor Crítico (5%)            -2.887712e+00  
Valor Crítico (1%)            -3.490131e+00
Enter fullscreen mode Exit fullscreen mode

Com esses valores já teríamos uma série estacionária, considerando um nível de significância de 5%, e já daria para prosseguir com outras análises.

O segundo método que vou usar é a transformação de Box-cox. Essa transformação tem como objetivo deixar os dados mais normalizados e consequentemente estabiliza a variância durante os dados. A sua função tem apenas um parâmetro, lambda, que denota como será feita essa transformação, podemos definir esse valor ou deixar a função achar “o melhor valor”.

from scipy.stats import boxcox  
meantemp_bcx, lam = boxcox(df['meantemp'])  
print('Lambda: %f' % lam)  
fig, ax = plt.subplots()  
fig.suptitle("BoxCox resultados")  
# line plot  
plt.subplot(211)  
plt.plot(meantemp_bcx)  
# histogram  
plt.subplot(212)  
plt.hist(meantemp_bcx)  
plt.show()

#resultado: 
Lambda: -0.118270
Enter fullscreen mode Exit fullscreen mode

Olhando para o gráfico e para o resultado do teste abaixo vemos que esse método não foi muito eficaz para conseguir a estacionariedade.

Teste                           -1.444017  
p-valor                          0.561001  
Lags                             0.000000  
Número de observações usadas   113.000000  
Valor Crítico (1%)              -3.489590  
Valor Crítico (5%)              -2.887477  
Valor Crítico (10%)             -2.580604
Enter fullscreen mode Exit fullscreen mode

Seguindo para o último método, vou tentar também uma abordagem bem legal que vi em uma das referências que usei para estudar. Vou fazer uma transformação logarítmica e subtrair a média móvel dos dados transformados. O gráfico abaixo mostra as duas séries comparadas.

temp_log = np.log(df['meantemp']) #Transformação logarítmica 
Enter fullscreen mode Exit fullscreen mode


log_menos_media = temp_log - mediamovel_log  
log_menos_media.dropna(inplace=True)
Enter fullscreen mode Exit fullscreen mode


Estatísticas de rolagem referentes a série “log_menos_media”

Teste                         -5.988135e+00  
p-valor                        1.771911e-07  
Lags                           3.000000e+00  
Número de observações usadas   1.040000e+02  
Valor Crítico (1%)            -3.494850e+00  
Valor Crítico (5%)            -2.889758e+00  
Valor Crítico (10%)           -2.581822e+00
Enter fullscreen mode Exit fullscreen mode

Conseguimos uma série estacionária \o/, mas por que deu certo?

Quando plotamos a média móvel dessa série e a série transformada é possível ver nos dois casos que existe uma tendência nos dados. A teoria era que subtraindo as duas essa tendência fosse perdida e sobraria apenas as partes estacionárias da série final.

MM = tendênciaM + estacionariedadeM  
TL = tendênciaL + estacionariedadeL  
TL-MM = (tendênciaL + estacionariedadeL)-(tendênciaM + estacionariedadeM) = tendênciaL - tendênciaM + estacionariedadeL - estacionariedadeM
Enter fullscreen mode Exit fullscreen mode

Referências e sugestões:

Dataset usado

Link para o código completo do artigo

Everything you can do with a time series

Time Series for beginners with ARIMA

Análise de séries temporais — UFSC : Explica decomposição em detalhes e em português!

Top comments (0)