Criando uma versão "janela-móvel" de qualquer função

O que são cálculos em janela móvel e por quê são importantes em análise de dados e de séries temporais? Neste texto exploramos a criação e aplicação de funções em janelas móveis usando o R.

Em análise de séries temporais, nada é estático. Uma correlação pode existir em um subconjunto temporal ou uma média pode variar de um dia para o outro. Os cálculos em janelas móveis simplesmente aplicam funções a um subconjunto de largura fixa (ou não) desses dados (também conhecido como uma janela móvel), indexando uma observação a cada cálculo.

Existem alguns motivos comuns pelos quais você pode querer usar um cálculo em janela móvel em análise de séries temporais:

  • Obter a tendência central ao longo do tempo (média, mediana)
  • Obter a volatilidade ao longo do tempo (desvio-padrão, variância)
  • Detectar mudanças de tendência (médias móveis rápidas vs. lentas)
  • Obter uma relação entre duas séries temporais ao longo do tempo (covariância, correlação)

O tipo de cálculo em janela móvel mais simples existente no R é o base::cumsum() que faz a soma acumulada de valores de um vetor, no qual cada janela móvel é definida como sendo todos os valores anteriores ao atual. Um exemplo visual de soma acumula de uma sequência de números de 1 a 10:

Entendida a importância e intuição do funcionamento dessas operações, passemos a alguns exemplos práticos. Demonstraremos como operacionalizar esses cálculos em janelas móveis com as principais opções de pacotes de R disponíveis atualmente para essa finalidade:

  • timetk
  • slider
  • runner

Essa lista com certeza não esgota todas as possibilidades, existem outras opções e fica ao critério do usuário escolher o que melhor se adequa ao seu contexto.

Pacotes

Para reproduzir os códigos aqui expostos é necessário alguns pacotes de R, o código a seguir verifica se os mesmos estão instalados, instala caso necessário e carrega os mesmos para a memória:

# Instalar/carregar pacotes
if(!require("pacman")) install.packages("pacman")
pacman::p_load(
"magrittr",
"dplyr",
"ggplot2",
"ggthemes",
"tidyquant",
"timetk",
"tidyr",
"slider",
"lubridate",
"runner"
)

O pacote {timetk}

O pacote timetk é um toolkit para visualização, tratamento e transformação de séries temporais e oferece as funções slidify() e slidify_vec() para operações em janela móvel.

Como exemplo inicial, utilizamos os dataset FANG que traz os preços diários de 4 ações de tecnologia listadas na NASDAQ:

# Dados de preços diários de ações (FANG)
df_fang <- tidyquant::FANG
df_fang
# # A tibble: 4,032 x 8
## symbol date open high low close volume adjusted
## <chr> <date> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 FB 2013-01-02 27.4 28.2 27.4 28 69846400 28
## 2 FB 2013-01-03 27.9 28.5 27.6 27.8 63140600 27.8
## 3 FB 2013-01-04 28.0 28.9 27.8 28.8 72715400 28.8
## 4 FB 2013-01-07 28.7 29.8 28.6 29.4 83781800 29.4
## 5 FB 2013-01-08 29.5 29.6 28.9 29.1 45871300 29.1
## 6 FB 2013-01-09 29.7 30.6 29.5 30.6 104787700 30.6
## 7 FB 2013-01-10 30.6 31.5 30.3 31.3 95316400 31.3
## 8 FB 2013-01-11 31.3 32.0 31.1 31.7 89598000 31.7
## 9 FB 2013-01-14 32.1 32.2 30.6 31.0 98892800 31.0
## 10 FB 2013-01-15 30.6 31.7 29.9 30.1 173242600 30.1
## # ... with 4,022 more rows

Calculando uma média móvel de 30 períodos para o preço ajustado (adjusted):

df_fang %>%
dplyr::select(symbol, date, adjusted) %>%
dplyr::group_by(symbol) %>% 
# Criar média móvel
dplyr::mutate(
roll_avg_30 = timetk::slidify_vec(
.x = adjusted,
.f = mean, 
.period = 30, 
.align = "right", 
.partial = TRUE
)
) %>%
tidyr::pivot_longer(cols = c(adjusted, roll_avg_30)) %>%
# Gerar gráfico
timetk::plot_time_series(
date, 
value, 
.title = "Média móvel de 30 períodos",
.color_var = name,
.facet_ncol = 2, 
.smooth = FALSE, 
.interactive = FALSE
)

Para mais operações simultaneamente pode ser interessante primeiro definir as funções utilizando o slidify() para depois calcular a operação em janelas móveis:

# Média móvel
roll_avg_30 <- timetk::slidify(.f = mean, .period = 30, .align = "right", .partial = TRUE)
# Soma acumulada
roll_sum_30 <- timetk::slidify(.f = sum, .period = 30, .align = "right", .partial = TRUE)
# Desvio padrão móvel
roll_sd_30 <- timetk::slidify(.f = sd, .period = 30, .align = "right", .partial = TRUE)
# Regressão móvel (rolling regression)
roll_lm_90 <- timetk::slidify(~ lm(..1 ~ ..2 + ..3), .period = 90, .unlist = FALSE, .align = "right")

E então aplicar as funções criadas:

df_fang %>%
dplyr::select(symbol, date, adjusted, volume) %>%
dplyr::group_by(symbol) %>% 
# Operações em janelas móveis
dplyr::mutate(
numeric_date = as.numeric(date),
m_avg_30 = roll_avg_30(adjusted),
m_sum_30 = roll_sum_30(adjusted),
m_sd_30 = roll_sd_30(adjusted),
m_lm_90 = roll_lm_90(adjusted, volume, numeric_date)
) %>% 
dplyr::filter(!is.na(m_lm_90))

## # A tibble: 3,676 x 9
## # Groups: symbol [4]
## symbol date adjusted volume numeric_date m_avg_30 m_sum_30 m_sd_30
## <chr> <date> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 FB 2013-05-10 26.7 30847100 15835 26.8 805. 0.842
## 2 FB 2013-05-13 26.8 29068800 15838 26.9 807. 0.805
## 3 FB 2013-05-14 27.1 24930300 15839 26.9 808. 0.756
## 4 FB 2013-05-15 26.6 30299800 15840 27.0 809. 0.747
## 5 FB 2013-05-16 26.1 35499100 15841 26.9 808. 0.762
## 6 FB 2013-05-17 26.2 29462700 15842 26.9 807. 0.766
## 7 FB 2013-05-20 25.8 42402900 15845 26.9 806. 0.794
## 8 FB 2013-05-21 25.7 26261300 15846 26.8 805. 0.822
## 9 FB 2013-05-22 25.2 45314500 15847 26.7 802. 0.863
## 10 FB 2013-05-23 25.1 37663100 15848 26.6 799. 0.880
## # ... with 3,666 more rows, and 1 more variable: m_lm_90 <list>

O pacote {slider}

O pacote slider oferece uma sintaxe familiar ao purrr para calcular operações em janelas móveis.

O exemplo mais simples de sua utilização é o cálculo de uma média móvel dado um vetor numérico. A janela móvel é indicada pelo argumento .before:

# Média móvel (2 "períodos") com alinhamento a direita
slider::slide_dbl(
.x = 1:5, 
.f = ~mean(.x), 
.before = 1, # valor atual + 1 valor anterior
.complete = TRUE 
)

O pacote oferece algumas funções derivadas de slide_dbl() para os cálculos mais comuns: slide_mean(), slide_sum(), slide_prod(), slide_min() e slide_max().

Para séries temporais o pacote oferece ainda um recurso interessante de realizar a operação com janela móvel relativa a um índice com as funções slide_index(). Se você já quis calcular algo como uma “média móvel de 3 meses”, em que o número de dias em cada mês é irregular, você pode gostar desta função.

No exemplo abaixo, primeiro é calculado uma média móvel de 90 períodos sem considerar o índice e depois é calculado outra média móvel, mas considerando um índice de 3 meses. A função slide_index_mean() realiza o cálculo da média móvel de 3 meses considerando o valor atual em relação a mesma data aproximada de 3 meses anteriores (não necessariamente 90 observações anteriores). O resultado é uma média móvel diária relativa a uma janela móvel de 3 meses:


# Calcular médias móveis
df_amzn_roll <- df_fang %>%
dplyr::filter(symbol == "AMZN") %>%
dplyr::select(date, symbol, adjusted) %>%
dplyr::mutate(
roll_avg_90 = slider::slide_mean(adjusted, before = 89, complete = TRUE),
roll_idx_3m = slider::slide_index_mean(
x = adjusted, # vetor numérico
i = date, # vetor índice (datas)
before = ~ .x %m-% base::months(3), # para evitar erro gerado por NAs
complete = TRUE
)
)

# Gerar gráfico
df_amzn_roll %>%
dplyr::filter(date <= lubridate::as_date("2014-06-30")) %>%
tidyr::pivot_longer(cols = c(adjusted, roll_avg_90, roll_idx_3m)) %>%
timetk::plot_time_series(
date,
value,
.title = "AMZN",
.color_var = name,
.smooth = FALSE,
.interactive = FALSE
)

Perceba que a média móvel que considera o índice de 3 meses tem início anterior, pois considera que os meses possuem número de dias irregulares e se ajusta a tal.

O pacote {runner}

O pacote runner oferece um framework arrojado para calcular operações com janelas móveis de tamanho fixo ou variante, opções de inclusão de lags, tratamento de NAs, computação paralela, e é desenhado para dados em formato de séries temporais ou longitudinais.

Exemplos simples de médias móveis com e sem lag:


# Média móvel de 2 "períodos"
runner::runner(1:5, f = mean, k = 2, na_pad = TRUE)

## [1] NaN 1.5 2.5 3.5 4.5

# Média móvel de 2 "períodos" com 1 lag
runner::runner(1:5, f = mean, k = 2, lag = 1, na_pad = TRUE)

## [1] NaN NaN 1.5 2.5 3.5

Às vezes, as observações de um conjunto de dados não são espaçados igualmente (ausência de fins de semana, feriados, etc.) e, portanto, o tamanho da janela móvel deve variar para manter o período de tempo esperado. Para essa situação, podemos especificar o argumento idx para que a função em execução seja aplicada nas janelas móveis dependendo da data. Neste exemplo abaixo a janela móvel possui o índice semanal variante conforme a data:


# Criar vetores de dados
dates <- Sys.Date() + cumsum(sample(1:5, 20, replace = TRUE)) # série temporal irregular
values <- 1:20

dates

## [1] "2021-08-23" "2021-08-24" "2021-08-26" "2021-08-30" "2021-09-04"
## [6] "2021-09-08" "2021-09-12" "2021-09-13" "2021-09-16" "2021-09-18"
## [11] "2021-09-23" "2021-09-26" "2021-09-27" "2021-09-30" "2021-10-03"
## [16] "2021-10-07" "2021-10-11" "2021-10-15" "2021-10-17" "2021-10-20"

values

## [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

# Calcular média móvel com janela variante
runner::runner(
x = values,
k = "7 days",
idx = dates,
f = mean
)

## [1] 1.0 1.5 2.0 3.0 4.5 5.5 6.5 7.0 8.0 8.5 10.5 11.5 12.0 13.0 14.0
## [16] 15.5 16.5 17.5 18.0 19.0

Espero que esta exploração com exemplos de cálculos com funções em janelas móveis tenha sido proveitosa, apesar de breve e resumida. Recomendo fortemente consultar as documentações dos pacotes, pois os mesmos fornecem todas as informações, detalhes, motivação e mais exemplos.

 

Compartilhe esse artigo

Facebook
Twitter
LinkedIn
WhatsApp
Telegram
Email
Print

Comente o que achou desse artigo

Outros artigos relacionados

Deploy de modelos com Python + Shinylive + GitHub gastando ZERO reais

Colocar modelos em produção pode ser um grande desafio. Lidar com custos monetários, infraestrutura operacional e complexidades de códigos e ferramentas pode acabar matando potenciais projetos. Uma solução que elimina todos estes obstáculos é a recém lançada Shinylive. Neste artigo mostramos um exemplo com um modelo de previsão para o preço do petróleo Brent.

Como automatizar tarefas repetitivas usando Python? Um exemplo para largar o Excel

Manter relatórios diários com dados e análises atualizados é um desafio, pois envolve várias etapas: coleta de dados, tratamento de informações, produção de análises e atualização de relatório. Para superar este desafio algumas ferramentas como Python + Quarto + GitHub podem ser usadas para automatizar tudo que for automatizável. Neste artigo mostramos um exemplo com dados do mercado financeiro.

Criando um Dashboard de análise de Ações no Python

Um Dashboard é um painel de controle que consolida uma variedade de informações sobre um determinado objeto de estudo em um ou mais painéis. Ele simplifica significativamente o processo de análise de dados, oferecendo uma visão global e fácil de entender. Uma maneira simples de construir um Dashboard para acompanhar uma ação específica é utilizando duas ferramentas: Quarto e Python. Neste post, mostramos o resultado da criação de um Dashboard de Ação.

como podemos ajudar?

Preencha os seus dados abaixo e fale conosco no WhatsApp

Boletim AM

Preencha o formulário abaixo para receber nossos boletins semanais diretamente em seu e-mail.