在较长的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
Run Code Online (Sandbox Code Playgroud)
假设我们在每个组内希望在一个或多个位置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
Run Code Online (Sandbox Code Playgroud)
在dplyr我可以想到一些不同的方式来执行替换.(1)使用do匿名函数,类似于使用的函数ddply.(2)使用mutate:使用数字索引连接替换"插入"的向量.这可能只对单一指数有益.(3)使用mutate:创建索引向量并使用条件替换ifelse(参见此处,此处,此处和此处).
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)
Run Code Online (Sandbox Code Playgroud)
在具有稍大数据集的基准测试中,"拼图拼图插入"是最快的,但同样,这种方法可能仅适用于单个替换.它看起来不太干净......
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
Run Code Online (Sandbox Code Playgroud)
只是为了检查的影响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)
Run Code Online (Sandbox Code Playgroud)
然后是更大数据集的新基准:
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)
Run Code Online (Sandbox Code Playgroud)
同样,简单的"插入"是最快的,而do功能正在失去基础.在帮助文本中do被描述为其他dplyr功能的"通用补充" .对我而言,它似乎是匿名函数的自然选择.然而,我感到惊讶的do是,当非dplyr rbind跳跃部分被忽略时,速度要慢得多.目前,do文档相当稀缺,所以我想知道我是否滥用这个功能,并且可能有更合适的(未记录的?)方法do呢?
当我搜索dplyr帮助文本或插图时,我没有对索引/索引进行点击.所以现在我想知道:
还有其他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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2625 次 |
| 最近记录: |