同时对数据框的特定列进行子集化和操作

Simultaneously subsetting and operating on a specific column of a data frame

假设我有 data.frame df

df<-data.frame(a=1:5,b=101:105,c=201:205)

我可以在调用此数据的子集的同时对其中一列(或行)执行某种修改(例如算术)吗?

例如,如果我想 return df 的第一列和第二列,但 return 第 1 列值的日志。是否有一些符号可以修改 df[,1:2] 以即时生成以下所有内容?:

            a   b
>1  0.0000000 101
>2  0.6931472 102
>3  1.0986123 103
>4  1.3862944 104
>5  1.6094379 105
`[`(transform(df, a = log(a)),1:2)      
#          a   b
#1 0.0000000 101
#2 0.6931472 102
#3 1.0986123 103
#4 1.3862944 104
#5 1.6094379 105

您可以在执行功能时调用子集。但它比同时操作更巧妙。但是 dplyr 和其他方法基本上会掩盖相同的行为。如果您要完成的是 space 和代码高尔夫,这应该会有所帮助。我喜欢 Mr.Flick 的建议,但这有点快(位)。

dplyr版本:

library(dplyr)
transmute(df, a = log(a), b = b)
          a   b
1 0.0000000 101
2 0.6931472 102
3 1.0986123 103
4 1.3862944 104
5 1.6094379 105

dplyr 中,transmute() 将 return 只有在调用它时命名的变量。在这里,我们实际上只转换了两个变量之一,但我们通过创建它的副本将第二个变量包含在结果中。与 transmute() 相比,mutate() 将 return 整个原始数据框以及创建的变量。如果您为新变量赋予与现有变量相同的名称,mutate() 将覆盖这些变量。

dplyr 版本的一个好处是,它很容易混合转换并给结果起好听的名字,像这样:

> transmute(df, a.log = log(a), b.sqrt = sqrt(b))
      a.log   b.sqrt
1 0.0000000 10.04988
2 0.6931472 10.09950
3 1.0986123 10.14889
4 1.3862944 10.19804
5 1.6094379 10.24695

这是within()

的一个很好的例子
within(df[1:2], a <- log(a))
#           a   b
# 1 0.0000000 101
# 2 0.6931472 102
# 3 1.0986123 103
# 4 1.3862944 104
# 5 1.6094379 105

或者如果您不想在通话中使用 <-,您可以使用括号

within(df[1:2], { a = log(a) })

我不相信这些比两步法更快,只是用更少的击键来完成。以下是一些基准:

library(microbenchmark)
microbenchmark(dplyr = {df<-data.frame(a=1:5,b=101:105,c=201:205);df<-transmute(df, a = log(a), b = b)},
               transform = {df<-data.frame(a=1:5,b=101:105,c=201:205);df<-transform(df, a = log(a))},
               within = {df<-data.frame(a=1:5,b=101:105,c=201:205);df<-within(df[1:2], a <- log(a))},
               twosteps = {df<-data.frame(a=1:5,b=101:105,c=201:205);df<-df[,1:2];df[,1]<-log(df[,1])})

Unit: microseconds
      expr      min       lq      mean    median        uq       max neval
     dplyr 1374.710 1438.453 1657.3807 1534.0680 1658.2910  5231.572   100
 transform  489.597  508.413  764.6921  524.9240  569.4680 18127.718   100
    within  493.436  518.396  593.6254  534.9085  585.7880  1554.420   100
  twosteps  421.245  438.909  501.6850  450.6210  491.5165  2101.231   100

为了演示下面 Gregor 的评论,首先是 5 行,但将对象创建置于基准测试之外:

n = 5
df = data.frame(a = runif(n), b = rnorm(n), c = 1:n)

microbenchmark(dplyr = {df2 <- transmute(df, a = log(a), b = b)},
               subset = {df2 <- `[`(transform(df, a = log(a)),1:2)},
               within = {df2 <- within(df[1:2], a <- log(a))},
               twosteps = {df2 <- df[,1:2]; df2[,1]<-log(df2[,1])})
# twosteps looks much better!

但是如果您将行数增加到足够大,您可能会关心速度差异:

n = 1e6
df = data.frame(a = runif(n), b = rnorm(n), c = 1:n)

microbenchmark(dplyr = {df2 <- transmute(df, a = log(a), b = b)},
               subset = {df2 <- `[`(transform(df, a = log(a)),1:2)},
               within = {df2 <- within(df[1:2], a <- log(a))},
               twosteps = {df2 <- df[,1:2]; df2[,1]<-log(df2[,1])})

差异消失了。

data.table 的方法如下:

library(data.table)
setDT(df)[, .(a=log(a),b)]

大型数据集测试:

library(data.table)
dt1 <- CJ(a = seq(1, 1e3, by=1), b = sample(1e2L), c = sample(1e2L))
df1 <- copy(dt1)
setDF(df1)

基准:

library(rbenchmark)
benchmark(replications = 10, order = "elapsed", columns = c("test", "elapsed", "relative"),
          dt = dt1[, .(a=log(a),b)],
          dplyr = transmute(df1, a = log(a), b = b),
          transform = transform(df1, a = log(a), b = b),
          within = within(df1, a <- log(a))[,1:2],
          twosteps = {df1<-df1[,1:2];df1[,1]<-log(df1[,1])})

       test elapsed relative
5  twosteps   0.249    1.000
4    within   0.251    1.008
3 transform   0.251    1.008
2     dplyr   0.300    1.205
1        dt   0.462    1.855

令我惊讶的是,data.table 方法是最慢的方法。而在大多数其他情况下(例如:, two),这是更快的方法。