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.