data.table:以列作为输入对大型 data.table 执行高效的行式操作
data.table: Perform efficient row-wise operation on large data.table with columns as input
我有一个非常大的 data.table,有 1.6x10^8 行,我想在列 exposure
和 exposure.before.index
之间执行逐行操作,如我的下面的例子。
我创建了 TI 列(即治疗强化),它是一个指标,表明一个非 ID 目前是否正在使用 drug/drugs、exposure
,这与他们使用的任何药物都不同在每个 ID 各自的第一行,exposure.before.index
。您可以查看我的代码并观察最终输出如所解释的那样。
library(data.table)
DT <- data.table::data.table(ID=c("a","a","a","b","b","c","c"),
drugA=c(1,1,1,0,0,0,0),
drugB=c(0,1,1,1,0,0,0),
drugC=c(0,0,1,0,1,0,0))
DT[, exposure := gsub("NA\+|\+NA", "", do.call(paste,
c(Map(function(x, y) names(.SD)[(NA^!x) * y], .SD,
seq_along(.SD)), sep="+"))), .SDcols = drugA:drugC]
DT[exposure=="NA",exposure:="NONE"]
DT[,exposure.before.index:=c("drugA","drugA","drugA","drugB","drugB","NONE","NONE")]
DT[,CNT:=1:.N]
DT[!(exposure.before.index!="NONE" & exposure=="NONE"),TI:=(any(!unlist(strsplit(exposure, "[+]"))%in%unlist(strsplit(exposure.before.index, "[+]")))),by="CNT"]
DT[is.na(TI),TI:=FALSE]
DT
ID drugA drugB drugC exposure exposure.before.index CNT TI
1: a 1 0 0 drugA drugA 1 FALSE
2: a 1 1 0 drugA+drugB drugA 2 TRUE
3: a 1 1 1 drugA+drugB+drugC drugA 3 TRUE
4: b 0 1 0 drugB drugB 4 FALSE
5: b 0 0 1 drugC drugB 5 TRUE
6: c 0 0 0 NONE NONE 6 FALSE
7: c 0 0 0 NONE NONE 7 FALSE
我创建 CNT 是为了在 exposure
和 exposure.before.index
之间应用我的函数 any(!unlist(strsplit(exposure, "[+]"))%in%unlist(strsplit(exposure.before.index, "[+]")))
。由于 1.6x10^8 行,我有这个方法需要相当长的时间。当我想按行应用某个 operation/function 时,我通常会使用这种 data.table[...,by="CNT"] 技术,但我发现这对于非常大 data.table。你们有没有比我的方法更强大的其他方法?
我发现了与我的主题类似的其他问题,但答案并未概括为以稳健的方式对用户定义的函数应用按行操作。
如有任何帮助and/or,我们将不胜感激。
这很难。 strsplit
对于这个 1 亿的数据集来说,内存效率不是很高——每一行都需要从 strsplit
中创建两个列表。我的建议是使用函数并跳过 by = 1:.N
步骤。
exposed = function(before, after) {
out = vector(length = length(before))
for (i in seq_along(before)) {
bef = before[i]
aft = after[i]
if (bef == "NONE" || aft == "NONE")
out[i] = FALSE
else
out[i] = any(!unlist(strsplit(aft, "[+]", fixed = TRUE), use.names = FALSE)%chin%unlist(strsplit(bef, "[+]", fixed = TRUE), use.names = FALSE))
}
return(out)
}
DT[, TI3 := exposed(exposure.before.index, exposure)]
> DT[, .(exposure.before.index, exposure, TI, TI3)]
exposure.before.index exposure TI TI3
1: drugA drugA FALSE FALSE
2: drugA drugA+drugB TRUE TRUE
3: drugA drugA+drugB+drugC TRUE TRUE
4: drugB drugB FALSE FALSE
5: drugB drugC TRUE TRUE
6: NONE NONE FALSE FALSE
7: NONE NONE FALSE FALSE
注意这里有一些优化:
- 使用
%chin%
而不是 %in%
这是一个 data.table 效用函数,在字符向量上比 %in%
更快
- 使用
strsplit(..., fixed = TRUE)
进行优化 - 这不是我们使用的正则表达式。可能是最大的性能提升。
unlist(..., use.names = FALSE)
下一步是将函数转换为 Rcpp
,此处未完成。字符串比 Rcpp
中的数字更复杂(至少对我而言)。
下面是这个函数的表现。对于 7 行示例,这快了 4 倍。但是随着行数的增加,速度差异变得不那么明显了:
## 7 rows
Unit: microseconds
expr min lq mean median uq max
use_fx 375.801 395.251 662.582 409.751 431.351 21345.701
OP 1889.901 2021.601 2211.858 2096.101 2285.201 4042.801
## 700,000 rows
Unit: seconds
expr min lq mean median uq max
use_fx 4.409595 4.409595 4.409595 4.409595 4.409595 4.409595
OP 12.592520 12.592520 12.592520 12.592520 12.592520 12.592520
## 7,000,000 rows
Unit: seconds
expr min lq mean median uq max
use_fx 43.90979 43.90979 43.90979 43.90979 43.90979 43.90979
OP 130.16418 130.16418 130.16418 130.16418 130.16418 130.16418
## code used:
DT_big = DT[rep(seq_len(.N), 1e5)]
microbenchmark(
use_fx = DT_big[, TI3 := exposed(exposure.before.index, exposure)],
OP = {
DT_big[,CNT:=1:.N]
DT_big[!(exposure.before.index!="NONE" & exposure=="NONE"),TI:=(any(!unlist(strsplit(exposure, "[+]")) %in% unlist(strsplit(exposure.before.index, "[+]")))),by="CNT"]
DT_big[is.na(TI),TI:=FALSE]
}
, times = 1L
)
如果您对 Rcpp
感兴趣,这可能会有所帮助:
我有一个非常大的 data.table,有 1.6x10^8 行,我想在列 exposure
和 exposure.before.index
之间执行逐行操作,如我的下面的例子。
我创建了 TI 列(即治疗强化),它是一个指标,表明一个非 ID 目前是否正在使用 drug/drugs、exposure
,这与他们使用的任何药物都不同在每个 ID 各自的第一行,exposure.before.index
。您可以查看我的代码并观察最终输出如所解释的那样。
library(data.table)
DT <- data.table::data.table(ID=c("a","a","a","b","b","c","c"),
drugA=c(1,1,1,0,0,0,0),
drugB=c(0,1,1,1,0,0,0),
drugC=c(0,0,1,0,1,0,0))
DT[, exposure := gsub("NA\+|\+NA", "", do.call(paste,
c(Map(function(x, y) names(.SD)[(NA^!x) * y], .SD,
seq_along(.SD)), sep="+"))), .SDcols = drugA:drugC]
DT[exposure=="NA",exposure:="NONE"]
DT[,exposure.before.index:=c("drugA","drugA","drugA","drugB","drugB","NONE","NONE")]
DT[,CNT:=1:.N]
DT[!(exposure.before.index!="NONE" & exposure=="NONE"),TI:=(any(!unlist(strsplit(exposure, "[+]"))%in%unlist(strsplit(exposure.before.index, "[+]")))),by="CNT"]
DT[is.na(TI),TI:=FALSE]
DT
ID drugA drugB drugC exposure exposure.before.index CNT TI
1: a 1 0 0 drugA drugA 1 FALSE
2: a 1 1 0 drugA+drugB drugA 2 TRUE
3: a 1 1 1 drugA+drugB+drugC drugA 3 TRUE
4: b 0 1 0 drugB drugB 4 FALSE
5: b 0 0 1 drugC drugB 5 TRUE
6: c 0 0 0 NONE NONE 6 FALSE
7: c 0 0 0 NONE NONE 7 FALSE
我创建 CNT 是为了在 exposure
和 exposure.before.index
之间应用我的函数 any(!unlist(strsplit(exposure, "[+]"))%in%unlist(strsplit(exposure.before.index, "[+]")))
。由于 1.6x10^8 行,我有这个方法需要相当长的时间。当我想按行应用某个 operation/function 时,我通常会使用这种 data.table[...,by="CNT"] 技术,但我发现这对于非常大 data.table。你们有没有比我的方法更强大的其他方法?
我发现了与我的主题类似的其他问题,但答案并未概括为以稳健的方式对用户定义的函数应用按行操作。
如有任何帮助and/or,我们将不胜感激。
这很难。 strsplit
对于这个 1 亿的数据集来说,内存效率不是很高——每一行都需要从 strsplit
中创建两个列表。我的建议是使用函数并跳过 by = 1:.N
步骤。
exposed = function(before, after) {
out = vector(length = length(before))
for (i in seq_along(before)) {
bef = before[i]
aft = after[i]
if (bef == "NONE" || aft == "NONE")
out[i] = FALSE
else
out[i] = any(!unlist(strsplit(aft, "[+]", fixed = TRUE), use.names = FALSE)%chin%unlist(strsplit(bef, "[+]", fixed = TRUE), use.names = FALSE))
}
return(out)
}
DT[, TI3 := exposed(exposure.before.index, exposure)]
> DT[, .(exposure.before.index, exposure, TI, TI3)]
exposure.before.index exposure TI TI3
1: drugA drugA FALSE FALSE
2: drugA drugA+drugB TRUE TRUE
3: drugA drugA+drugB+drugC TRUE TRUE
4: drugB drugB FALSE FALSE
5: drugB drugC TRUE TRUE
6: NONE NONE FALSE FALSE
7: NONE NONE FALSE FALSE
注意这里有一些优化:
- 使用
%chin%
而不是%in%
这是一个 data.table 效用函数,在字符向量上比%in%
更快
- 使用
strsplit(..., fixed = TRUE)
进行优化 - 这不是我们使用的正则表达式。可能是最大的性能提升。 unlist(..., use.names = FALSE)
下一步是将函数转换为 Rcpp
,此处未完成。字符串比 Rcpp
中的数字更复杂(至少对我而言)。
下面是这个函数的表现。对于 7 行示例,这快了 4 倍。但是随着行数的增加,速度差异变得不那么明显了:
## 7 rows
Unit: microseconds
expr min lq mean median uq max
use_fx 375.801 395.251 662.582 409.751 431.351 21345.701
OP 1889.901 2021.601 2211.858 2096.101 2285.201 4042.801
## 700,000 rows
Unit: seconds
expr min lq mean median uq max
use_fx 4.409595 4.409595 4.409595 4.409595 4.409595 4.409595
OP 12.592520 12.592520 12.592520 12.592520 12.592520 12.592520
## 7,000,000 rows
Unit: seconds
expr min lq mean median uq max
use_fx 43.90979 43.90979 43.90979 43.90979 43.90979 43.90979
OP 130.16418 130.16418 130.16418 130.16418 130.16418 130.16418
## code used:
DT_big = DT[rep(seq_len(.N), 1e5)]
microbenchmark(
use_fx = DT_big[, TI3 := exposed(exposure.before.index, exposure)],
OP = {
DT_big[,CNT:=1:.N]
DT_big[!(exposure.before.index!="NONE" & exposure=="NONE"),TI:=(any(!unlist(strsplit(exposure, "[+]")) %in% unlist(strsplit(exposure.before.index, "[+]")))),by="CNT"]
DT_big[is.na(TI),TI:=FALSE]
}
, times = 1L
)
如果您对 Rcpp
感兴趣,这可能会有所帮助: