在 dplyr
较长链的一个阶段函数中,我需要使用数字索引替换变量的一部分来指定要替换的元素。
我的数据如下所示:
df1 <- data.frame(grp = rep(1:2, each = 3),
a = 1:6,
b = rep(c(10, 20), each = 3))
df1
# grp a b
# 1 1 1 10
# 2 1 2 10
# 3 1 3 10
# 4 2 4 20
# 5 2 5 20
# 6 2 6 20
假设我们在每个组中希望替换变量 a
中的元素与 b
中的相应元素,在一个或多个位置。在这个简单的示例中,我使用单个索引( id
),但这可能是索引向量。首先,我将如何使用 ddply
来做到这一点:
library(plyr)
id <- 2
ddply(.data = df1, .variables = .(grp), function(x){
x$a[id] <- x$b[id]
x
})
# grp a b
# 1 1 1 10
# 2 1 10 10
# 3 1 3 10
# 4 2 4 20
# 5 2 20 20
# 6 2 6 20
在 dplyr
我可以想出一些不同的方法来执行替换。 (1)使用do
使用匿名函数,类似于 ddply
中使用的函数。 (2)使用mutate
:使用数字索引连接一个向量,其中替换是“插入”的。这可能只对单个索引有效。 (3)使用mutate
:创建索引向量并使用 ifelse
进行条件替换(例如,参见 here 、 here 、 here 和 here )。
detach("package:plyr", unload = TRUE)
library(dplyr)
# (1)
fun_do <- function(df){
l <- df %.%
group_by(grp) %.%
do(function(dat){
dat$a[id] <- dat$b[id]
dat
})
do.call(rbind, l)
}
# (2)
fun_mut <- function(df){
df %.%
group_by(grp) %.%
mutate(
a = c(a[1:(id - 1)], b[id], a[(id + 1):length(a)])
)
}
# (3)
fun_mut_ifelse <- function(df){
df %.%
group_by(grp) %.%
mutate(
idx = 1:n(),
a = ifelse(idx %in% id, b, a)) %.%
select(-idx)
}
fun_do(df1)
fun_mut(df1)
fun_mut_ifelse(df1)
在数据集稍大的基准测试中,“拼图插入”速度最快,但同样,此方法可能只适合单个替换。而且看起来不太干净......
set.seed(123)
df2 <- data.frame(grp = rep(1:200, each = 3),
a = rnorm(600),
b = rnorm(600))
library(microbenchmark)
microbenchmark(fun_do(df2),
fun_mut(df2),
fun_mut_ifelse(df2),
times = 10)
# Unit: microseconds
# expr min lq median uq max neval
# fun_do(df2) 48443.075 49912.682 51356.631 53369.644 55108.769 10
# fun_mut(df2) 891.420 933.996 1019.906 1066.663 1155.235 10
# fun_mut_ifelse(df2) 2503.579 2667.798 2869.270 3027.407 3138.787 10
只是为了检查 do.call(rbind
的影响力参与do
函数,尝试不使用它:
fun_do2 <- function(df){
df %.%
group_by(grp) %.%
do(function(dat){
dat$a[2] <- dat$b[2]
dat
})
}
fun_do2(df1)
然后是更大数据集上的新基准:
df3 <- data.frame(grp = rep(1:2000, each = 3),
a = rnorm(6000),
b = rnorm(6000))
microbenchmark(fun_do(df3),
fun_do2(df3),
fun_mut(df3),
fun_mut_ifelse(df3),
times = 10)
同样,简单的“插入”速度最快,而 do
功能正在失势。在帮助文本中do
被描述为另一个 dplyr
的“通用补充”功能。对我来说,这似乎是匿名函数的自然选择。然而,令我惊讶的是do
速度慢得多,当非 dplyr
时也是如此。 rbind
ing 部分被跳过。目前,do
文档相当稀缺,所以我想知道我是否滥用了该功能,并且可能有更合适的(未记录的?)方法 do
它?
当我搜索 dplyr
help text 时,我没有找到任何索引。或vignette 。所以现在我想知道:
还有其他的dplyr
使用我忽略的数字索引替换变量部分的方法?具体来说就是结合ifelse
创建索引列该怎么走,或者有没有更直接的a[i] <- b[i]
-类似的替代品?
编辑以下来自 @G.Grothendieck 的评论(谢谢!)。已添加replace
替代方案(?[
中“另请参阅”的候选者)。
fun_replace <- function(df){
df %.%
group_by(grp) %.%
mutate(
a = replace(a, id, b[id]))
}
fun_replace(df1)
microbenchmark(fun_do(df3),
fun_do2(df3),
fun_mut(df3),
fun_mut_ifelse(df3),
fun_replace(df3),
times = 10)
# Unit: milliseconds
# expr min lq median uq max neval
# fun_do(df3) 685.154605 693.327160 706.055271 712.180410 851.757790 10
# fun_do2(df3) 291.787455 294.047747 297.753888 299.624730 302.368554 10
# fun_mut(df3) 5.736640 5.883753 6.206679 6.353222 7.381871 10
# fun_mut_ifelse(df3) 24.321894 26.091049 29.361553 32.649924 52.981525 10
# fun_replace(df3) 4.616757 4.748665 4.981689 5.279716 5.911503 10
replace
函数速度最快,而且肯定比 fun_mut
更容易使用当有多个索引时。
编辑 2 fun_do
和fun_do2
不再适用于 dplyr 0.2
; Error: Results are not data frames at positions:
最佳答案
这是一种更快的就地修改方法:
library(data.table)
# select rows we want, then assign b to a for those rows, in place
fun_dt = function(dt) dt[dt[, .I[id], by = grp]$V1, a := b]
# benchmark
df4 = data.frame(grp = rep(1:20000, each = 3),
a = rnorm(60000),
b = rnorm(60000))
dt4 = as.data.table(df4)
library(microbenchmark)
# using fastest function from OP
microbenchmark(fun_dt(dt4), fun_replace(df4), times = 10)
#Unit: milliseconds
# expr min lq median uq max neval
# fun_dt(dt4) 15.62325 17.22828 18.42445 20.83768 21.25371 10
# fun_replace(df4) 99.03505 107.31529 116.74830 188.89134 286.50199 10
关于使用 dplyr 中的数字索引替换变量的部分内容。我需要创建索引列并使用 ifelse 吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23642811/