通过从表数组中选择每一行来构建 R data.table

Constructing an R data.table by selecting each row from an array of tables

假设我有一个长度为 D 的列表,其中包含 data.table 个对象。每个 data.table 具有相同的列 (X, Y) 和相同的行数 N。我想构造另一个 table 具有 N 行,其中各个行取自 table s 由同样长度为 N 的索引向量指定。重申一下,最终 table 中的每一行取自数组中的一个且仅一个 tables,其索引为源 table 由现有向量指定。

N = 100  # rows in each table (actual ~1000000 rows)
D = 4    # number of tables in array (actual ~100 tables)
tableArray = vector("list", D)
for (d in 1:D) {
  tableArray[[d]] = data.table(X=rnorm(N), Y=d)  # actual ~100 columns
}
tableIndexVector = sample.int(D, N, replace=TRUE) # length N of random 1:D
finalTable = copy(tableArray[[1]]) # just for length and column names
for (n in 1:N) {
  finalTable[n] = tableArray[[tableIndexVector[n]]][n]
} 

这似乎按我想要的方式工作,但数组符号中的数组很难理解,而且我认为 for 循环的性能不会很好。似乎应该有一些优雅的方式来做到这一点,但我还没有偶然发现它。有没有另一种有效且不那么神秘的方法?

(如果你想知道,数组中的每个 table 代表在特定治疗方案下对受试者的模拟反事实观察,我想从这些样本中以不同的概率来测试行为观察到的不同制度比率的不同回归方法。)

for 循环与 data.table 一起工作得很好,但我们可以使用以下方法显着提高特定循环的性能(我相信)。

方法 # 1

  1. 改用set,因为它避免了[.data.table开销
  2. 不要循环遍历 1:N,因为您可以将循环简化为 运行,仅针对 tableIndexVector 的唯一值并一次分配所有相应的值。这应该将 运行 时间减少至少 x10K(因为 N 的大小为 1MM,而 D 的大小仅为 100,而 unique(tableIndexVector) <= D)

所以您基本上可以将循环转换为以下内容

for (i in unique(tableIndexVector)) {
  indx <- which(tableIndexVector == i)
  set(finalTable, i = indx, j = 1:2, value = tableArray[[i]][indx])
}

方法 # 2

另一种方法是使用rbindlist并将所有table组合成一个大data.table,同时添加新的idcol参数以识别不同的tables 在大 table 内。为此,您将需要 devel version。这将按要求避免循环,但结果将按 table 出现

排序
temp <- rbindlist(tableArray, idcol = "indx")
indx <- temp[, .I[which(tableIndexVector == indx)], by = indx]$V1
finalTable <- temp[indx]

这是更大数据集的基准测试

N = 100000  
D = 10    
tableArray = vector("list", D)
set.seed(123)
for (d in 1:D) {
  tableArray[[d]] = data.table(X=rnorm(N), Y=d)  
}

set.seed(123)
tableIndexVector = sample.int(D, N, replace=TRUE) 
finalTable = copy(tableArray[[1]]) 
finalTable2 = copy(tableArray[[1]])

## Your approach
system.time(for (n in 1:N) {
  finalTable[n] = tableArray[[tableIndexVector[n]]][n]
})
#   user  system elapsed 
# 154.79   33.14  191.57     

## My approach # 1
system.time(for (i in unique(tableIndexVector)) {
  indx <- which(tableIndexVector == i)
  set(finalTable2, i = indx, j = 1:2, value = tableArray[[i]][indx])
})    
# user  system elapsed 
# 0.01    0.00    0.02

## My approach # 2
system.time({
  temp <- rbindlist(tableArray, idcol = "indx")
  indx <- temp[, .I[which(tableIndexVector == indx)], by = indx]$V1
  finalTable3 <- temp[indx]
})    
# user  system elapsed 
# 0.11    0.00    0.11 

identical(finalTable, finalTable2)
## [1] TRUE
identical(setorder(finalTable, X), setorder(finalTable3[, indx := NULL], X))
## [1] TRUE

所以总结

  • 我的第一种方法是迄今为止最快的方法,并且运行速度快 x15K 倍 比你原来的。这也是 returns 相同的结果
  • 我的第二种方法仍然比您原来的方法快 x1.5K 倍,但避免了循环(您出于某种原因不喜欢循环)。虽然结果是按 table 的出现顺序排列的,但顺序与您的结果不相同。