使用dplyr中的数字索引替换变量的部分.我是否需要创建索引列并使用ifelse?

Hen*_*rik 17 r dplyr

在较长的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:

edd*_*ddi 8

这是一种更快的就地修改方法:

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)

  • @Henrik我坚信`data.table`语法惯性是一个源于那些过于习惯于"plyr"语法的人的神话.如果你用一个干净的石板来接近它 - 它非常简单 - `d [i,j,by = b]`读作*"take`d`,apply`i`,计算`j`按'b`分组*.一旦你理解了这么多,你就会理解90%的`data.table`语法. (2认同)