Cálculo da média móvel

185

Estou tentando usar R para calcular a média móvel sobre uma série de valores em uma matriz. A pesquisa normal da lista de discussão R não tem sido muito útil. Não parece haver uma função interna em R que me permita calcular médias móveis. Algum pacote fornece um? Ou preciso escrever o meu?

Jared
fonte

Respostas:

140
  • Rolling Meios / Máximos / Medianas no pacote zoo (rollmean)
  • MovingAverages em TTR
  • ma na previsão
f3lix
fonte
1
Qual é a média móvel em R que não contém valores futuros de um carimbo de data / hora fornecido? Eu verifiquei forecast::mae ele contém toda a vizinhança, não está certo.
hhh
214

Ou você pode simplesmente calcular usando o filtro, aqui está a função que eu uso:

ma <- function(x, n = 5){filter(x, rep(1 / n, n), sides = 2)}

Se você usar dplyr, tenha cuidado para especificar stats::filterna função acima.

Matti Pastell
fonte
49
Devo salientar que "lados = 2" pode ser uma opção importante nos casos de uso de muitas pessoas que elas não desejam ignorar. Se você deseja apenas informações finais na sua média móvel, use lados = 1.
Evanrsparks
36
Alguns anos mais tarde, mas dplyr agora tem uma função de filtro, se você tem esse pacote utilização carregadostats::filter
blmoore
sides = 2é equivalente a align = "center" para o zoo :: rollmean ou RcppRoll :: roll_mean. sides = 1é equivalente ao alinhamento "correto". Não vejo uma maneira de fazer o alinhamento "esquerdo" ou calcular com dados "parciais" (2 ou mais valores)?
Matt L.
29

O uso cumsumdeve ser suficiente e eficiente. Supondo que você tenha um vetor x e que deseja uma soma contínua de n números

cx <- c(0,cumsum(x))
rsum <- (cx[(n+1):length(cx)] - cx[1:(length(cx) - n)]) / n

Conforme apontado nos comentários de @mzuther, isso pressupõe que não haja NAs nos dados. lidar com isso exigiria a divisão de cada janela pelo número de valores não-NA. Aqui está uma maneira de fazer isso, incorporando o comentário de Ricardo Cruz:

cx <- c(0, cumsum(ifelse(is.na(x), 0, x)))
cn <- c(0, cumsum(ifelse(is.na(x), 0, 1)))
rx <- cx[(n+1):length(cx)] - cx[1:(length(cx) - n)]
rn <- cn[(n+1):length(cx)] - cn[1:(length(cx) - n)]
rsum <- rx / rn

Isso ainda tem o problema de que, se todos os valores na janela forem NAs, haverá uma divisão por erro zero.

peixe-pipa
fonte
8
Uma desvantagem desta solução é que ela não pode lidar com perdas:cumsum(c(1:3,NA,1:3))
Jthorpe
Você pode facilmente lidar com NAs fazendo isso cx <- c(0, cumsum(ifelse(is.na(x), 0, x))).
Ricardo Cruz
@ Ricardo Cruz: talvez seja melhor remover as NAs e ajustar o comprimento do vetor de acordo. Pense em um vetor com muitos NAs - zeros puxarão a média para zero, enquanto a remoção dos NAs deixará a média como está. Tudo depende dos seus dados e da pergunta que você deseja responder, é claro. :)
mzuther
@mzuther, atualizei a resposta após seus comentários. Obrigado pela contribuição. Penso que a maneira correta de lidar com dados ausentes não é estender a janela (removendo os valores de NA), mas calculando a média de cada janela pelo denominador correto.
pipefish
1
rn <- cn [(n + 1): comprimento (cx)] - cx [1: (comprimento (cx) - n)] deve realmente ser rn <- cn [(n + 1): comprimento (cx)] - cn [1: (length (cx) - n)]
adrianmcmenamin
22

Em data.table 1.12.0 nova frollmeanfunção foi adicionada para calcular rápida e exata rolando média cuidadosamente manipulação NA, NaNe +Inf, -Infvalores.

Como não há exemplo reproduzível na questão, não há muito mais a ser abordado aqui.

Você pode encontrar mais informações ?frollmeanno manual, também disponível on-line em ?frollmean.

Exemplos do manual abaixo:

library(data.table)
d = as.data.table(list(1:6/2, 3:8/4))

# rollmean of single vector and single window
frollmean(d[, V1], 3)

# multiple columns at once
frollmean(d, 3)

# multiple windows at once
frollmean(d[, .(V1)], c(3, 4))

# multiple columns and multiple windows at once
frollmean(d, c(3, 4))

## three above are embarrassingly parallel using openmp
jangorecki
fonte
10

O caToolspacote possui média de rolagem muito rápida / min / max / sd e poucas outras funções. Eu só trabalhei com runmeane runsde eles são os mais rápidos de qualquer um dos outros pacotes mencionados até o momento.

eddi
fonte
1
Isso é incrível! É a única função que faz isso de uma maneira agradável e simples. E é 2018 agora ...
Felipe Gerard
9

Você pode usar RcppRollpara médias móveis muito rápidas escritas em C ++. Basta chamar a roll_meanfunção. Os documentos podem ser encontrados aqui .

Caso contrário, esse loop for (mais lento) deve fazer o truque:

ma <- function(arr, n=15){
  res = arr
  for(i in n:length(arr)){
    res[i] = mean(arr[(i-n):i])
  }
  res
}
cantdutchthis
fonte
3
Você pode me explicar em detalhes, como esse algoritmo funciona? Porque eu não consigo entender a idéia
Daniel Yefimov
Primeiro ele inicializa um vetor do mesmo comprimento com res = arr. Depois, há um loop que itera iniciando no n, ou no 15 ° elemento, até o final da matriz. isso significa que o primeiro subconjunto do qual ele leva a média é o arr[1:15]que preenche o ponto res[15]. Agora, eu prefiro definir em res = rep(NA, length(arr))vez de res = arrcada elemento res[1:14]igual a NA, em vez de um número, onde não podemos obter uma média completa de 15 elementos.
Evan Friedland
7

De fato RcppRoll é muito bom.

O código postado por cantdutchthis deve ser corrigido na quarta linha da janela a ser corrigida:

ma <- function(arr, n=15){
  res = arr
  for(i in n:length(arr)){
    res[i] = mean(arr[(i-n+1):i])
  }
  res
}

Outra maneira, que lida com as perdas, é dada aqui .

Uma terceira maneira, melhorando código deste código para calcular médias parciais ou não, segue:

  ma <- function(x, n=2,parcial=TRUE){
  res = x #set the first values

  if (parcial==TRUE){
    for(i in 1:length(x)){
      t<-max(i-n+1,1)
      res[i] = mean(x[t:i])
    }
    res

  }else{
    for(i in 1:length(x)){
      t<-max(i-n+1,1)
      res[i] = mean(x[t:i])
    }
    res[-c(seq(1,n-1,1))] #remove the n-1 first,i.e., res[c(-3,-4,...)]
  }
}
Rodrigo Remedio
fonte
5

Para complementar a resposta de cantdutchthis e Rodrigo Remedio ;

moving_fun <- function(x, w, FUN, ...) {
  # x: a double vector
  # w: the length of the window, i.e., the section of the vector selected to apply FUN
  # FUN: a function that takes a vector and return a summarize value, e.g., mean, sum, etc.
  # Given a double type vector apply a FUN over a moving window from left to the right, 
  #    when a window boundary is not a legal section, i.e. lower_bound and i (upper bound) 
  #    are not contained in the length of the vector, return a NA_real_
  if (w < 1) {
    stop("The length of the window 'w' must be greater than 0")
  }
  output <- x
  for (i in 1:length(x)) {
     # plus 1 because the index is inclusive with the upper_bound 'i'
    lower_bound <- i - w + 1
    if (lower_bound < 1) {
      output[i] <- NA_real_
    } else {
      output[i] <- FUN(x[lower_bound:i, ...])
    }
  }
  output
}

# example
v <- seq(1:10)

# compute a MA(2)
moving_fun(v, 2, mean)

# compute moving sum of two periods
moving_fun(v, 2, sum)
Cristóbal Alcázar
fonte
2

Aqui está um código de exemplo que mostra como calcular uma média móvel centralizada e uma média móvel final usando a rollmeanfunção do pacote zoo .

library(tidyverse)
library(zoo)

some_data = tibble(day = 1:10)
# cma = centered moving average
# tma = trailing moving average
some_data = some_data %>%
    mutate(cma = rollmean(day, k = 3, fill = NA)) %>%
    mutate(tma = rollmean(day, k = 3, fill = NA, align = "right"))
some_data
#> # A tibble: 10 x 3
#>      day   cma   tma
#>    <int> <dbl> <dbl>
#>  1     1    NA    NA
#>  2     2     2    NA
#>  3     3     3     2
#>  4     4     4     3
#>  5     5     5     4
#>  6     6     6     5
#>  7     7     7     6
#>  8     8     8     7
#>  9     9     9     8
#> 10    10    NA     9
Eu gosto de codificar
fonte
1

Embora um pouco lento, mas você também pode usar o zoo :: rollapply para executar cálculos em matrizes.

reqd_ma <- rollapply(x, FUN = mean, width = n)

onde x é o conjunto de dados, FUN = mean é a função; você também pode alterá-lo para min, max, sd etc e width é a janela de rolamento.

Garima gulati
fonte
2
Não é lento; Comparando-o com a base R, é muito mais rápido. set.seed(123); x <- rnorm(1000); system.time(apply(embed(x, 5), 1, mean)); library(zoo); system.time(rollapply(x, 5, mean)) Na minha máquina, é tão rápido que retorna um tempo de 0 segundos.
G. Grothendieck
1

Pode-se usar o runnerpacote para mover funções. Neste caso, mean_runfunção. O problema cummeané que ele não lida com NAvalores, mas mean_runsim. runnerO pacote também suporta séries temporais irregulares e o Windows pode depender da data:

library(runner)
set.seed(11)
x1 <- rnorm(15)
x2 <- sample(c(rep(NA,5), rnorm(15)), 15, replace = TRUE)
date <- Sys.Date() + cumsum(sample(1:3, 15, replace = TRUE))

mean_run(x1)
#>  [1] -0.5910311 -0.2822184 -0.6936633 -0.8609108 -0.4530308 -0.5332176
#>  [7] -0.2679571 -0.1563477 -0.1440561 -0.2300625 -0.2844599 -0.2897842
#> [13] -0.3858234 -0.3765192 -0.4280809

mean_run(x2, na_rm = TRUE)
#>  [1] -0.18760011 -0.09022066 -0.06543317  0.03906450 -0.12188853 -0.13873536
#>  [7] -0.13873536 -0.14571604 -0.12596067 -0.11116961 -0.09881996 -0.08871569
#> [13] -0.05194292 -0.04699909 -0.05704202

mean_run(x2, na_rm = FALSE )
#>  [1] -0.18760011 -0.09022066 -0.06543317  0.03906450 -0.12188853 -0.13873536
#>  [7]          NA          NA          NA          NA          NA          NA
#> [13]          NA          NA          NA

mean_run(x2, na_rm = TRUE, k = 4)
#>  [1] -0.18760011 -0.09022066 -0.06543317  0.03906450 -0.10546063 -0.16299272
#>  [7] -0.21203756 -0.39209010 -0.13274756 -0.05603811 -0.03894684  0.01103493
#> [13]  0.09609256  0.09738460  0.04740283

mean_run(x2, na_rm = TRUE, k = 4, idx = date)
#> [1] -0.187600111 -0.090220655 -0.004349696  0.168349653 -0.206571573 -0.494335093
#> [7] -0.222969541 -0.187600111 -0.087636571  0.009742884  0.009742884  0.012326968
#> [13]  0.182442234  0.125737145  0.059094786

Pode-se também especificar outras opções como lag, e rolar apenas atíndices específicos. Mais na documentação de pacotes e funções .

GoGonzo
fonte
1

O pacote deslizante pode ser usado para isso. Ele tem uma interface que foi projetada especificamente para parecer semelhante ao ronronar. Ele aceita qualquer função arbitrária e pode retornar qualquer tipo de saída. Os quadros de dados são ainda iterados em linhas. O site pkgdown está aqui .

library(slider)

x <- 1:3

# Mean of the current value + 1 value before it
# returned as a double vector
slide_dbl(x, ~mean(.x, na.rm = TRUE), .before = 1)
#> [1] 1.0 1.5 2.5


df <- data.frame(x = x, y = x)

# Slide row wise over data frames
slide(df, ~.x, .before = 1)
#> [[1]]
#>   x y
#> 1 1 1
#> 
#> [[2]]
#>   x y
#> 1 1 1
#> 2 2 2
#> 
#> [[3]]
#>   x y
#> 1 2 2
#> 2 3 3

A sobrecarga do controle deslizante e da tabela data.t frollapply()deve ser bem baixa (muito mais rápida que o zoo). frollapply()parece ser um pouco mais rápido para este exemplo simples aqui, mas observe que são necessárias apenas entradas numéricas e a saída deve ser um valor numérico escalar. As funções do controle deslizante são completamente genéricas e você pode retornar qualquer tipo de dados.

library(slider)
library(zoo)
library(data.table)

x <- 1:50000 + 0L

bench::mark(
  slider = slide_int(x, function(x) 1L, .before = 5, .complete = TRUE),
  zoo = rollapplyr(x, FUN = function(x) 1L, width = 6, fill = NA),
  datatable = frollapply(x, n = 6, FUN = function(x) 1L),
  iterations = 200
)
#> # A tibble: 3 x 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 slider      19.82ms   26.4ms     38.4    829.8KB     19.0
#> 2 zoo        177.92ms  211.1ms      4.71    17.9MB     24.8
#> 3 datatable    7.78ms   10.9ms     87.9    807.1KB     38.7
Davis Vaughan
fonte
0
vector_avg <- function(x){
  sum_x = 0
  for(i in 1:length(x)){
    if(!is.na(x[i]))
      sum_x = sum_x + x[i]
  }
  return(sum_x/length(x))
}
Mohamed Galia
fonte
2
Por favor, adicione uma descrição para mais detalhes.
Farbod Ahmadian
Relacione sua resposta com a pergunta e inclua alguns resultados que mostrem que a pergunta foi respondida. Consulte Como responder para obter orientação sobre como fazer uma boa resposta.
Peter