如何在 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
列,然后只返回gender
和Freq
列。如何在一行代码中不明确引用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.table
s之间发生了变化。我不想修改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 中添加一列并返回多列而不修改基础数据?的主要内容,如果未能解决你的问题,请参考以下文章
包含 data.table 名称的变量已就地更改? [复制]
在函数中通过引用向 data.table 添加新列并不总是有效