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)