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

这里的方法好像还可以,但是

综上所述,数据量大,取值不同,耗时过长

提前感谢您提供任何优化想法。

更新:

我考虑塑造和加入 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 连接到 dtsupp.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"
)