如何在 data.table 中添加一列并返回多列而不修改基础数据?

Posted

技术标签:

【中文标题】如何在 data.table 中添加一列并返回多列而不修改基础数据?【英文标题】:How do I add a column to a data.table and return multiple columns without modifying underlying data? 【发布时间】:2022-01-22 09:14:33 【问题描述】:

我在R 中有以下data.table

dt <- data.table(gender = c("Male", "Female"), Prop = c(0.49, 0.51))
#   gender Prop
# 1:   Male  0.49
# 2: Female  0.51

我想计算一个Freq = Prop * 1000 列,然后只返回genderFreq 列。如何在一行代码中明确引用gender 列并且修改dt

我能做到的最好的是:

onsdist$gender[, c(.SD, Freq = Prop * 1000)][, .SD, .SDcols = - "Prop"]
#    gender Freq1 Freq2
# 1:   Male   490   490
# 2: Female   510   510

但我最终得到了重复的 Freq 列。

(我不想引用gender的原因是因为它在data.tables之间发生了变化。我不想修改dt的原因是因为我需要重新使用原始版本稍后)。

【问题讨论】:

【参考方案1】:

我们可以使用data.table语法来获取输出格式

dt[, c(.SD, .(Freq = Prop * 1000)), .SDcols = -"Prop"]

-输出

   gender Freq
1:   Male  490
2: Female  510

【讨论】:

【参考方案2】:

1) 使用带有 Prop = NULL 的变换

dt[, transform(.SD, Freq = Prop * 1000, Prop = NULL)]
##    gender Freq
## 1:   Male  490
## 2: Female  510

2) 或此变体

transform(dt, Freq = Prop * 1000, Prop = NULL)
##    gender Freq
## 1:   Male  490
## 2: Female  510

3) 我们可以通过将 transform 替换为 collapse 包中的 ftransform 来显着加快速度。

library(collapse)
dt[, ftransform(.SD, Freq = Prop * 1000, Prop = NULL)]

4)类似

library(collapse)
ftransform(dt, Freq = Prop * 1000, Prop = NULL)

基准测试

使用问题中的数据,我们看到上面的 (4),下面标记为 ex4,它使用 ftransform from collapse 而没有 [.data.table 比上面的其他方法快得多。

library(collapse)
library(data.table)
library(microbenchmark)

microbenchmark(
    ex1 = dt[, transform(.SD, Freq = Prop * 1000, Prop = NULL)],
    ex2 = transform(dt, Freq = Prop * 1000, Prop = NULL),
    ex3 = dt[, ftransform(.SD, Freq = Prop * 1000, Prop = NULL)],
    ex4 = ftransform(dt, Freq = Prop * 1000, Prop = NULL)
)

Unit: microseconds
 expr      min       lq       mean    median       uq      max neval  cld
  ex1 1847.601 1927.402 2046.04098 2015.4015 2093.251 2706.200   100    d
  ex2  959.700 1000.701 1074.93098 1046.1510 1122.601 1606.201   100  b  
  ex3 1048.201 1090.351 1139.57598 1121.6005 1174.201 1381.602   100   c 
  ex4   68.401   85.551   93.08802   89.2515  100.551  168.400   100 a   

【讨论】:

添加了从折叠中用 ftransform 替换 transform 的代码。【参考方案3】:

另一种解决方案

dt[, .(dt[, 1], Freq = Prop * 1000)]

   gender Freq
1:   Male  490
2: Female  510

所有答案中给出的选项的一些基准

请注意,我增加了相当多的样本数据,但我也只是好奇其他数据集的方法之间的差异。

这里的转换非常慢,不推荐使用,其他方法非常相似,.SD 和 .SDcols 的功能是最快的,尽管在这种情况下,保留所有行并且不使用第一个引用更新任何内容方法几乎不会慢。

set.seed(42)

dt <- data.table(
  gender = rep(LETTERS[1:25], 40000),
  Prop = runif(n = 1000000))

library(rbenchmark)

benchmark(
  "dt[, .(dt[, 1], Freq = Prop * 1000)]" = 
    dt[, .(dt[, 1], Freq = Prop * 1000)]
  ,
  "dt[, c(.SD, .(Freq = Prop * 1000)), .SDcols = 1]" = 
    dt[, c(.SD, .(Freq = Prop * 1000)), .SDcols = 1]
  ,
  "dt[, c(.SD, .(Freq = Prop * 1000)), .SDcols = -\"Prop\"]" = 
    dt[, c(.SD, .(Freq = Prop * 1000)), .SDcols = -"Prop"]
  ,
  "dt[, transform(.SD, Freq = Prop * 1000, Prop = NULL)]" = 
    dt[, transform(.SD, Freq = Prop * 1000, Prop = NULL)]
  ,
  "transform(dt, Freq = Prop * 1000, Prop = NULL)" = 
    transform(dt, Freq = Prop * 1000, Prop = NULL)
  ,
  replications = 1000,
  columns = c("test", "replications", "elapsed", "relative")
)

#                                                     test replications elapsed relative
# 1                   dt[, .(dt[, 1], Freq = Prop * 1000)]         1000   18.66    1.112
# 3 dt[, c(.SD, .(Freq = Prop * 1000)), .SDcols = -"Prop"]         1000   17.02    1.014
# 2       dt[, c(.SD, .(Freq = Prop * 1000)), .SDcols = 1]         1000   16.78    1.000
# 4  dt[, transform(.SD, Freq = Prop * 1000, Prop = NULL)]         1000  333.51   19.875
# 5         transform(dt, Freq = Prop * 1000, Prop = NULL)         1000  329.41   19.631

旁注

请记住,通过引用创建列的速度快了 5 倍 dt[, Freq := Prop * 1000] 和 OP 使用该表稍后重新使用的参数。我建议始终在速度加快时通过参考表格进行所有计算和准备工作。您始终可以从那里对输出进行子集化。

#                                               test replications elapsed relative
# 1             dt[, .(dt[, 1], Freq = Prop * 1000)]         1000   16.25    5.783
# 2 dt[, c(.SD, .(Freq = Prop * 1000)), .SDcols = 1]         1000   13.33    4.744
# 3                         t[, Freq := Prop * 1000]         1000    2.81    1.000

【讨论】:

以上是关于如何在 data.table 中添加一列并返回多列而不修改基础数据?的主要内容,如果未能解决你的问题,请参考以下文章

Prisma:如何在查询返回中添加一列?

包含 data.table 名称的变量已就地更改? [复制]

如何使用更改表查询在 AWS Redshift 中添加多列

在函数中通过引用向 data.table 添加新列并不总是有效

如何使用 javascript/jquery 在时间字符串中添加一秒

如何在点击触发的backgroundWorker中添加一段代码?