Скользящее окно среднего значения по кадру данных, основанное на альтернативном столбце в R

#r #dataframe #mean #sliding-window

Вопрос:

Я надеюсь вычислить среднее значение переменной, используя скользящее окно, но на основе значений другого столбца в кадре данных. Это трудно объяснить так…

Возьмем этот пример фрейма данных:

 dist <- c(seq(1,100,by=1),seq(101,200,by=2))
value<- runif(150, min=0, max=10)

df <- as.data.frame(cbind(dist,value))
head(df)
 

Я понимаю, что могу рассчитать среднее значение скользящего окна с помощью приведенного ниже кода:

 zoo::rollapply(df$value, width=50, by=25, FUN=mean, na.rm=TRUE,align="left")
 

Однако это не совсем то, чего я хочу. Я хотел бы рассчитать mean(df$value) df$dist диапазоны «когда 1-50 25-75 «, «тогда», «тогда 50-100 » и так далее.

Вышеизложенное не делает этого, так как в моем наборе данных я не могу предположить, что df$dist подсчитывает систематически (т. Е. не пропускает случайные числа). Следовательно, простое применение окна, перемещающегося вниз на определенное количество строк, приведет к неправильному результату.

Любой совет о том, как бы я подошел к этому, был бы фантастическим.

Заранее спасибо.

Ответ №1:

Вот несколько альтернатив. (1) не имеет зависимостей от пакетов, (2) использует rollapply и поэтому наиболее похож на код в вопросе и (3) использует SQL и является самым коротким с точки зрения кода.

1) База R Если проблема в том, что dist не содержит каждое число от 1 до его максимального значения, то мы можем перебирать интервалы следующим образом:

 Fun <- function(st, width, df, fun) {
  fun(subset(df, dist >= st amp; dist <= st   width - 1)$value)
}

width <- 50
step <- 25

starts <- seq(1, max(df$dist), step)

data.frame(starts, 
   ends = starts   width - 1,
   mean = sapply(starts, Fun, width, df, mean),
   N = sapply(starts, Fun, width, df, length))
 

дающий:

   starts ends     mean  N
1      1   50 5.200910 50
2     26   75 4.710030 50
3     51  100 4.770270 50
4     76  125 4.880030 38
5    101  150 5.318415 25
6    126  175 5.575938 25
7    151  200 4.989383 25
8    176  225 3.918574 12
 

2) rollapply Другой подход заключается в расширении фрейма входных данных, и в этом случае мы можем использовать rollapply.

 library(zoo)

roll <- function(x, width, fun, step) {
  fun2 <- function(x) fun(na.omit(x))
  rollapply(x, width, by = step, fun2, partial = TRUE, align = "left")
}

width <- 50
step <- 25

m <- merge(df, data.frame(dist = 1:max(df$dist)), all = TRUE)
data.frame(starts, 
  ends = starts   width - 1,
  mean = roll(m$value, width, mean, step),
  N = roll(m$value, width, length, step)
)
 

дающий:

   starts ends     mean  N
1      1   50 5.200910 50
2     26   75 4.710030 50
3     51  100 4.770270 50
4     76  125 4.880030 38
5    101  150 5.318415 25
6    126  175 5.575938 25
7    151  200 4.989383 25
8    176  225 3.918574 12
 

3) sqldf Это можно компактно сформулировать с помощью SQL с указанным левым соединением.

 library(sqldf)

width <- 50
step <- 25

starts <- data.frame(starts = seq(1, max(df$dist), step))
fn$sqldf("select starts, starts $width-1 ends, avg(value) mean, count(value) N
  from starts
  left join df on dist between starts and ends
  group by starts.rowid")
 

дающий:

   starts ends     mean  N
1      1   50 5.200910 50
2     26   75 4.710030 50
3     51  100 4.770270 50
4     76  125 4.880030 38
5    101  150 5.318415 25
6    126  175 5.575938 25
7    151  200 4.989383 25
8    176  225 3.918574 12
 

Примечание

Чтобы входные данные были воспроизводимыми, мы должны установить начальное значение перед использованием любых случайных чисел, поэтому в приведенном выше примере мы использовали это:

 set.seed(123)
dist <- c(seq(1, 100, by = 1), seq(101, 200, by = 2))
value <- runif(150, min = 0, max = 10)
df <- data.frame(dist, value)
 

Комментарии:

1. Большое вам спасибо @G. Гротендик, это похоже именно на то, что мне было нужно. Есть ли способ также включить na.rm=T в mean функцию? Глядя на этот сценарий, это также будет работать для нескольких наблюдений на каждом расстоянии? Еще раз спасибо

2. Также извините, что забыл set.seed()

3. Первоначально я использовал na.rm = TRUE, но потом мне показалось, что, поскольку мы делали это как для среднего, так и для длины, имело больше смысла учитывать это. Вы могли бы указать среднее значение функции(x) (x, na.rm = TRUE) и длину функции(x) (na.опустить(x)) для отката, а затем использовать ее напрямую, а не определять fun2, но это расширило бы код.

4. Хорошо, большое вам спасибо за ваш очень исчерпывающий и полезный ответ. Поддержано и принято — хорошего дня!