【发布时间】:2021-07-03 15:58:27
【问题描述】:
我正在尝试计算 dplyr/tidyverse 框架内之前 k 个非 NA 值的滚动平均值。我编写了一个似乎可以工作的函数,但想知道是否已经有某个包中的函数(这可能比我的尝试更有效率)正在执行此操作。示例数据集:
tmp.df <- data.frame(
x = c(NA, 1, 2, NA, 3, 4, 5, NA, NA, NA, 6, 7, NA)
)
假设我想要前 3 个非 NA 值的滚动平均值。那么输出y应该是:
x y
1 NA NA
2 1 NA
3 2 NA
4 NA NA
5 3 NA
6 4 2
7 5 3
8 NA 4
9 NA 4
10 NA 4
11 6 4
12 7 5
13 NA 6
y 的前 5 个元素是 NAs,因为 x 第一次有 3 个之前的非 NA 值位于第 6 行,这 3 个元素的平均值为 2。下一个 y 元素是不言自明的。第 9 行得到 4,因为 x 的前 3 个非 NA 值位于第 5、6 和 7 行,依此类推。
我的尝试是这样的:
roll_mean_previous_k <- function(x, k){
require(dplyr)
res <- NA
lagged_vector <- dplyr::lag(x)
lagged_vector_without_na <- lagged_vector[!is.na(lagged_vector)]
previous_k_values <- tail(lagged_vector_without_na, k)
if (length(previous_k_values) >= k) res <- mean(previous_k_values)
res
}
按如下方式使用(使用slider 包中的slide_dbl 函数):
library(dplyr)
tmp.df %>%
mutate(
y = slider::slide_dbl(x, roll_mean_previous_k, k = 3, .before = Inf)
)
提供所需的输出。但是,我想知道是否有现成的(如前所述)更有效的方法来做到这一点。我应该提到,我分别从zoo 和RcppRoll 包中知道rollmean 和roll_mean,但除非我弄错了,否则它们似乎在固定滚动窗口上工作,可以选择处理@987654338 @ 值(例如忽略它们)。就我而言,我想“扩展”我的窗口以包含 k 非 NA 值。
欢迎提出任何想法/建议。
编辑 - 模拟结果
感谢所有贡献者。首先,我没有提到我的数据集确实更大并且经常运行,因此任何性能改进都是最受欢迎的。因此,在决定接受哪个答案之前,我运行了以下模拟来检查执行时间。请注意,某些答案需要稍作调整才能返回所需的输出,但如果您认为您的解决方案被歪曲(因此效率低于预期),请随时告诉我,我会相应地进行编辑。我在下面的回答中使用了G. Grothendieck 的技巧,以消除对if-else 检查滞后、非NA 向量长度的需要。
所以这是模拟代码:
library(tidyverse)
library(runner)
library(zoo)
library(slider)
library(purrr)
library(microbenchmark)
set.seed(20211004)
test_vector <- sample(x = 100, size = 1000, replace = TRUE)
test_vector[sample(1000, size = 250)] <- NA
# Based on GoGonzo's answer and the runner package
f_runner <- function(z, k){
runner(
x = z,
f = function(x) {
mean(`length<-`(tail(na.omit(head(x, -1)), k), k))
}
)
}
# Based on my inital answer (but simplified), also mentioned by GoGonzo
f_slider <- function(z, k){
slide_dbl(
z,
function(x) {
mean(`length<-`(tail(na.omit(head(x, -1)), k), k))
},
.before = Inf
)
}
# Based on helios' answer. Return the correct results but with a warning.
f_helios <- function(z, k){
reduced_vec <- na.omit(z)
unique_means <- rollapply(reduced_vec, width = k, mean)
start <- which(!is.na(z))[k] + 1
repeater <- which(is.na(z)) + 1
repeater_cut <- repeater[(repeater > start-1) & (repeater <= length(z))]
final <- as.numeric(rep(NA, length(z)))
index <- start:length(z)
final[setdiff(index, repeater_cut)] <- unique_means
final[(start):length(final)] <- na.locf(final)
final
}
# Based on G. Grothendieck's answer (but I couldn't get it to run with the performance improvements)
f_zoo <- function(z, k){
rollapplyr(
z,
seq_along(z),
function(x, k){
mean(`length<-`(tail(na.omit(head(x, -1)), k), k))
},
k)
}
# Based on AnilGoyal's answer
f_purrr <- function(z, k){
map_dbl(
seq_along(z),
~ ifelse(
length(tail(na.omit(z[1:(.x -1)]), k)) == k,
mean(tail(na.omit(z[1:(.x -1)]), k)),
NA
)
)
}
# Check if all are identical #
all(
sapply(
list(
# f_helios(test_vector, 10),
f_purrr(test_vector, 10),
f_runner(test_vector, 10),
f_zoo(test_vector, 10)
),
FUN = identical,
f_slider(test_vector, 10),
)
)
# Run benchmarking #
microbenchmark(
# f_helios(test_vector, 10),
f_purrr(test_vector, 10),
f_runner(test_vector, 10),
f_slider(test_vector, 10),
f_zoo(test_vector, 10)
)
结果:
Unit: milliseconds
expr min lq mean median uq max neval cld
f_purrr(test_vector, 10) 31.9377 37.79045 39.64343 38.53030 39.65085 104.9613 100 c
f_runner(test_vector, 10) 23.7419 24.25170 29.12785 29.23515 30.32485 98.7239 100 b
f_slider(test_vector, 10) 20.6797 21.71945 24.93189 26.52460 27.67250 32.1847 100 a
f_zoo(test_vector, 10) 43.4041 48.95725 52.64707 49.59475 50.75450 122.0793 100 d
基于上述,除非代码可以进一步改进,否则slider 和runner 解决方案似乎更快。任何最终建议都非常受欢迎。
非常感谢您的宝贵时间!!
【问题讨论】:
标签: r dplyr na rolling-computation