R 在多个条件下从 data.table 获取价值的最快方法
R Fastest way to get value from a data.table under multiple criteria
我正在尝试获得最快(并且在某种程度上优雅)的方法来根据一些条件(支持 table)从 data.table 中提取单个元素。
为简单起见,一个显着缩短的示例:
library(data.table)
dt <- data.table(
person = c("Rick", "Michelle", "Richard", "Ryan", "Larry"),
criteria = c("A", "B", "C", "A", "C"),
number = c(5, 62, 25, 77, 91),
gender = c("M", "F", "M", "M", "M")
)
supp.dt <- data.table(
crits = c("A", "B", "C"),
ID = c("ID.1", "ID.2", "ID.3")
)
value.dt <- data.table(
ID.1M = runif(100, 0, 1),
ID.1F = runif(100, 0, 1),
ID.2M = runif(100, 1, 2),
ID.2F = runif(100, 1, 2),
ID.3M = runif(100, 2, 3),
ID.3F = runif(100, 2, 3)
)
getValue <- function(crit=NULL, numb=NULL, gend=NULL) {
return(value.dt[numb, paste0(supp.dt[crits == crit]$ID, gend), with = F])
}
dt$value <- mapply(getValue, dt$criteria, dt$number, dt$gender)
基准和结果:
library(microbenchmark)
runmapply <- function() {
dt$value <- mapply(getValue, dt$criteria, dt$number, dt$gender)
}
microbenchmark(
runmapply()
)
# Unit: milliseconds
# expr min lq mean median uq max neval
# runmapply() 5.5528 5.979 7.912311 6.4392 8.4442 24.1152 100
这里的方法好像还可以,但是
- 我的数据有上百万行
- 我需要提取不止一个值,实际上我使用了 13 个
不同的价值观;我首先将它们存储为 data.table 的列表,然后
综上所述,数据量大,取值不同,耗时过长
提前感谢您提供任何优化想法。
更新:
我考虑塑造和加入 tables。但不知道如何接近。我想我不知道 data.table 有什么能力。感谢@langtang
更具体地说,我提到的那 13 tables:
他们的所有 data.table 都有 100 行,但列数不同。
我还应该提到列的名称各不相同,因此示例中的列名称 ID.1 到 ID.3 设置错误。但这可以很容易地解决 ID=str_sub(ID, 1, length(ID)).
有了更多的价值 tables,我们可以清楚地看到,标准为我们提供了与 supp.dt 的联系,另一方面,它包含了与 13 [=47] 的所有信息=]s.
因此我们可以查看包含两个 value.dt 的示例,我还稍微更改了这些列名称:
library(data.table)
dt <- data.table(
person = c("Rick", "Michelle", "Richard", "Ryan", "Larry"),
criteria = c("A", "B", "C", "A", "C"),
number = c(5, 62, 25, 77, 91),
gender = c("M", "F", "M", "M", "M")
)
supp.dt <- data.table(
crits = c("A", "B", "C"),
valueoneID = c("uno", "dos", "tres"),
valuetwoID = c("eins", "zwei", "drei")
) # for every valuetable there is a respective id column
valueone.dt <- data.table(
unoM = runif(100, 0, 1),
unoF = runif(100, 0, 1),
dosM = runif(100, 1, 2),
dosF = runif(100, 1, 2),
tresM = runif(100, 2, 3),
tresF = runif(100, 2, 3)
)
valuetwo.dt <- data.table(
einsM = runif(100, 0, 1),
einsF = runif(100, 0, 1),
zweiM = runif(100, 1, 2),
zweiF = runif(100, 1, 2),
dreiM = runif(100, 2, 3),
dreiF = runif(100, 2, 3)
)
我的预期输出应该是这样的:(不需要 ID 列)
gender person number valueone valuetwo
<char> <char> <int> <num> <num>
1: M Rick 5 0.8572478 0.2312414
2: M Ryan 77 0.6211473 0.8585884
3: F Michelle 62 1.8570321 1.2232323
4: M Richard 25 2.5732931 2.2323179
5: M Larry 91 2.0300149 2.0919987
我认为该方法将取决于您的多个值的确切结构(13 个,以及这些值的存储位置),但您可以考虑使用联接等。
melt(value.dt[, number:=.I],id.vars = "number", variable.name = "ID")[
,`:=`(ID=str_sub(ID, 1,4), gender=str_sub(ID,-1,-1))][
dt[supp.dt,on=c("criteria"="crits")],
on=.(ID,gender,number)]
输出:
number ID value gender person criteria
<int> <char> <num> <char> <char> <char>
1: 5 ID.1 0.8572478 M Rick A
2: 77 ID.1 0.6211473 M Ryan A
3: 62 ID.2 1.8570321 F Michelle B
4: 25 ID.3 2.5732931 M Richard C
5: 91 ID.3 2.0300149 M Larry C
更新
如果您有多个“值”帧,可以采用以下方法:
首先,将它们放在一个命名列表中
value_frames = list("valueoneID" = valueone.dt,"valuetwoID" = valuetwo.dt)
其次,创建一个 value.dt
data.table row-binds 融合来自 value_frames
的这些不同框架的版本
value.dt = rbindlist(
lapply(value_frames, \(f) melt(f[,number:=.I], id.vars="number", variable.name="ID")),
idcol = "vsrc"
)
然后,将 value.dt
连接到 dt
和 supp.dt
之间连接的熔化版本,并将结果 dcast 回宽。
dcast(
value.dt[,`:=`(ID = str_sub(ID,1,-2), gender=str_sub(ID,-1,-1))][
melt(dt[supp.dt,on=c("criteria"="crits")],
measure.vars = patterns("value"),
variable.name = "vsrc",
value.name = "ID"),
on=.(ID,gender,number,vsrc)],
gender+person+number~vsrc, value.var="value"
)
我正在尝试获得最快(并且在某种程度上优雅)的方法来根据一些条件(支持 table)从 data.table 中提取单个元素。
为简单起见,一个显着缩短的示例:
library(data.table)
dt <- data.table(
person = c("Rick", "Michelle", "Richard", "Ryan", "Larry"),
criteria = c("A", "B", "C", "A", "C"),
number = c(5, 62, 25, 77, 91),
gender = c("M", "F", "M", "M", "M")
)
supp.dt <- data.table(
crits = c("A", "B", "C"),
ID = c("ID.1", "ID.2", "ID.3")
)
value.dt <- data.table(
ID.1M = runif(100, 0, 1),
ID.1F = runif(100, 0, 1),
ID.2M = runif(100, 1, 2),
ID.2F = runif(100, 1, 2),
ID.3M = runif(100, 2, 3),
ID.3F = runif(100, 2, 3)
)
getValue <- function(crit=NULL, numb=NULL, gend=NULL) {
return(value.dt[numb, paste0(supp.dt[crits == crit]$ID, gend), with = F])
}
dt$value <- mapply(getValue, dt$criteria, dt$number, dt$gender)
基准和结果:
library(microbenchmark)
runmapply <- function() {
dt$value <- mapply(getValue, dt$criteria, dt$number, dt$gender)
}
microbenchmark(
runmapply()
)
# Unit: milliseconds
# expr min lq mean median uq max neval
# runmapply() 5.5528 5.979 7.912311 6.4392 8.4442 24.1152 100
这里的方法好像还可以,但是
- 我的数据有上百万行
- 我需要提取不止一个值,实际上我使用了 13 个 不同的价值观;我首先将它们存储为 data.table 的列表,然后
综上所述,数据量大,取值不同,耗时过长
提前感谢您提供任何优化想法。
更新:
我考虑塑造和加入 tables。但不知道如何接近。我想我不知道 data.table 有什么能力。感谢@langtang
更具体地说,我提到的那 13 tables: 他们的所有 data.table 都有 100 行,但列数不同。 我还应该提到列的名称各不相同,因此示例中的列名称 ID.1 到 ID.3 设置错误。但这可以很容易地解决 ID=str_sub(ID, 1, length(ID)).
有了更多的价值 tables,我们可以清楚地看到,标准为我们提供了与 supp.dt 的联系,另一方面,它包含了与 13 [=47] 的所有信息=]s.
因此我们可以查看包含两个 value.dt 的示例,我还稍微更改了这些列名称:
library(data.table)
dt <- data.table(
person = c("Rick", "Michelle", "Richard", "Ryan", "Larry"),
criteria = c("A", "B", "C", "A", "C"),
number = c(5, 62, 25, 77, 91),
gender = c("M", "F", "M", "M", "M")
)
supp.dt <- data.table(
crits = c("A", "B", "C"),
valueoneID = c("uno", "dos", "tres"),
valuetwoID = c("eins", "zwei", "drei")
) # for every valuetable there is a respective id column
valueone.dt <- data.table(
unoM = runif(100, 0, 1),
unoF = runif(100, 0, 1),
dosM = runif(100, 1, 2),
dosF = runif(100, 1, 2),
tresM = runif(100, 2, 3),
tresF = runif(100, 2, 3)
)
valuetwo.dt <- data.table(
einsM = runif(100, 0, 1),
einsF = runif(100, 0, 1),
zweiM = runif(100, 1, 2),
zweiF = runif(100, 1, 2),
dreiM = runif(100, 2, 3),
dreiF = runif(100, 2, 3)
)
我的预期输出应该是这样的:(不需要 ID 列)
gender person number valueone valuetwo
<char> <char> <int> <num> <num>
1: M Rick 5 0.8572478 0.2312414
2: M Ryan 77 0.6211473 0.8585884
3: F Michelle 62 1.8570321 1.2232323
4: M Richard 25 2.5732931 2.2323179
5: M Larry 91 2.0300149 2.0919987
我认为该方法将取决于您的多个值的确切结构(13 个,以及这些值的存储位置),但您可以考虑使用联接等。
melt(value.dt[, number:=.I],id.vars = "number", variable.name = "ID")[
,`:=`(ID=str_sub(ID, 1,4), gender=str_sub(ID,-1,-1))][
dt[supp.dt,on=c("criteria"="crits")],
on=.(ID,gender,number)]
输出:
number ID value gender person criteria
<int> <char> <num> <char> <char> <char>
1: 5 ID.1 0.8572478 M Rick A
2: 77 ID.1 0.6211473 M Ryan A
3: 62 ID.2 1.8570321 F Michelle B
4: 25 ID.3 2.5732931 M Richard C
5: 91 ID.3 2.0300149 M Larry C
更新
如果您有多个“值”帧,可以采用以下方法:
首先,将它们放在一个命名列表中
value_frames = list("valueoneID" = valueone.dt,"valuetwoID" = valuetwo.dt)
其次,创建一个 value.dt
data.table row-binds 融合来自 value_frames
value.dt = rbindlist(
lapply(value_frames, \(f) melt(f[,number:=.I], id.vars="number", variable.name="ID")),
idcol = "vsrc"
)
然后,将 value.dt
连接到 dt
和 supp.dt
之间连接的熔化版本,并将结果 dcast 回宽。
dcast(
value.dt[,`:=`(ID = str_sub(ID,1,-2), gender=str_sub(ID,-1,-1))][
melt(dt[supp.dt,on=c("criteria"="crits")],
measure.vars = patterns("value"),
variable.name = "vsrc",
value.name = "ID"),
on=.(ID,gender,number,vsrc)],
gender+person+number~vsrc, value.var="value"
)