Data Science

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

By 20 de agosto de 2021 No Comments

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.

 

Receba diretamente em seu e-mail gratuitamente nossas promoções especiais
e conteúdos exclusivos sobre Análise de Dados!

Assinar Gratuitamente
{"cart_token":"","hash":"","cart_data":""}