如何有效地对 data.table 中的向量列进行操作
How to efficiently operate on column of vectors in data.table
我想对包含数字向量的列执行操作,我想知道执行此操作的最佳方法是什么。
到目前为止,我已经尝试了以下方法,设置的方法似乎是最好的,但也许我错过了一些更好的方法来解决这个问题?在 C++ 中执行此操作可以预期有多大的速度提升?
testVector <- data.table::data.table(A = lapply(1:10^5, function(x) runif(100)))
microbenchmark::microbenchmark(lapply = testVector[, B := lapply(A, diff)],
map = testVector[, C := Map(diff, A)],
set = set(testVector, NULL, "D", lapply(testVector[["A"]], diff)),
forset = {for(i in seq(nrow(testVector))) set(testVector, i, "E", list(list(diff(testVector[[i, "A"]]))))},
times = 10L)
结果如下:
Unit: milliseconds
expr min lq mean median uq max neval
set 789.7967 924.8178 1031.923 1082.325 1146.306 1174.671 10
lapply 1122.2454 1468.9556 1563.002 1619.668 1692.217 1919.405 10
map 1297.5236 1320.7022 1571.344 1592.176 1695.673 2012.051 10
forset 1887.0003 2023.7357 2139.202 2174.912 2245.943 2396.844 10
更新
我已经检查了 Rcpp 如何完成任务。虽然我的 C++ 技能很差,但速度提高了 >10 倍。
C++代码:
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
List cppDiff(List column){
int cSize = column.size();
List outputColumn(cSize, NumericVector());
for(int i = 0; i < cSize; ++i){
NumericVector vectorElement = column[i];
outputColumn[i] = Rcpp::diff(vectorElement);
}
return(outputColumn);
}
测试代码:
library(Rcpp);library(data.table);library(microbenchmark)
sourceCpp("diffColumn.cpp")
vLen <- 100L
cNum <- 1e4L
test <- data.table(A = lapply(1L:cNum, function(x) runif(vLen)))
throughMatrix <- function(column){
difmat <- diff(matrix(unlist(column), nrow = vLen, ncol = cNum))
lapply(seq(cNum), function(i) difmat[, i])
}
microbenchmark::microbenchmark(DT = set(test, NULL, "B", lapply(test[["A"]], diff)),
mat = set(test, NULL, "C", throughMatrix(test[["A"]])),
cpp = set(test, NULL, "D", cppDiff(test[["A"]])),
times = 5)
> all.equal(test$B, test$C)
[1] TRUE
> all.equal(test$B, test$D)
[1] TRUE
Unit: milliseconds
expr min lq mean median uq max neval
DT 845.04418 912.60961 1024.79183 1011.59417 1107.14306 1323.9963 10
mat 643.02187 663.92700 778.91145 816.95972 844.37206 864.1173 10
cpp 45.28504 49.35746 84.27799 78.32085 84.87942 226.1347 10
还有 10000 x 10000 列的另一个基准:
Unit: milliseconds
expr min lq mean median uq max neval
DT 7851.4352 8504.3501 21632.018 25246.7860 29133.358 37424.163 5
mat 8679.9386 8724.1497 22852.724 18235.7693 39199.966 39423.794 5
cpp 244.8572 247.7443 1439.011 303.2556 2715.643 3683.552 5
你考虑过使用矩阵吗?语法和数据结构差异很大,下面的代码不是直接替换,但根据此操作前后的分析管道,我怀疑矩阵 inputs/outputs 可能是处理数据的更合适方式无论如何都比列表列。
library(data.table)
VectorLength <- 1e5L
testVector <- data.table::data.table(A = lapply(1:VectorLength, function(x) runif(100)))
A <- matrix(data = runif(100L*VectorLength),nrow = 100L,ncol = VectorLength)
microbenchmark::microbenchmark(set = testVector[, B := lapply(A, diff)],
Matrix = B <- diff(A),
times = 10L)
在 Windows PC 上产生以下结果:
Unit: milliseconds
expr min lq mean median uq max neval
set 1143.933 1251.064 1316.944 1331.4672 1376.8016 1431.8988 10
Matrix 307.945 315.689 363.255 335.4382 390.1124 499.5492 10
以及 Linux 服务器上的以下内容 运行 Ubuntu 14.04
Unit: milliseconds
expr min lq mean median uq max neval
set 1342.6969 1410.3132 1519.6830 1551.2051 1594.3431 1699.7480 10
Matrix 285.0472 297.3283 375.0613 302.4198 488.3482 503.0959 10
仅供参考,当强制转换为 data.table:
时输出看起来像什么
str(as.data.table(t(B)))
returns
Classes ‘data.table’ and 'data.frame': 99 obs. of 100000 variables:
$ V1 : num 0.23 0.24 -0.731 0.724 0.074 ...
$ V2 : num -0.628 0.585 -0.164 0.269 -0.16 ...
$ V3 : num 0.1735 0.1128 -0.3069 0.0341 -0.2664 ...
$ V4 : num -0.392 0.593 -0.345 -0.327 0.747 ...
$ V5 : num 0.1084 0.2915 0.3858 -0.1574 -0.0929 ...
$ V6 : num -0.2053 -0.2669 -0.2 0.0214 0.1111 ...
$ V7 : num 0.0582 -0.2141 0.7282 -0.6877 0.4981 ...
$ V8 : num -0.439 -0.114 0.275 0.4 -0.184 ...
$ V9 : num 0.13673 0.55244 -0.43132 0.21692 -0.00308 ...
$ V10 : num 0.701 -0.0486 -0.1464 -0.5595 -0.046 ...
$ V11 : num 0.3583 -0.2588 -0.0742 -0.2113 0.9434 ...
$ V12 : num -0.1146 0.5346 -0.0594 -0.6534 0.6112 ...
$ V13 : num 0.473 0.307 -0.544 0.718 -0.315 ...
更新:视情况而定。
所以我很好奇性能改进在更大范围内会如何,结果证明这是一个有点有趣的问题,其中最有效的方法高度依赖于数据的 size/shape。
使用以下格式:
VectorLength <- 1e5L
ItemLength <- 1e2L
testVector <- data.table::data.table(A = lapply(1:VectorLength, function(x) runif(ItemLength)))
A <- matrix(data = runif(ItemLength*VectorLength),nrow = ItemLength,ncol = VectorLength)
microbenchmark::microbenchmark(set = set(testVector, NULL, "D", lapply(testVector[["A"]], diff)),
Matrix = B <- diff(A),
times = 5L)
我 运行 通过 运行ge 的 VectorLength
和 ItemLengths
值。从这里开始称为(矢量 x 项目),其中 (10,000 x 100) 表示具有 100 个元素的 10,000 个矢量(data.table 行)。由于矩阵形式 t运行 适合基本 R diff 函数,因此 t运行 将变成具有 100 行和 10,000 列的矩阵。
(10,000 x 10)
Unit: milliseconds
expr min lq mean median uq max neval
set 83.947769 88.420871 102.822626 90.91088 104.737002 146.096606 5
Matrix 2.368524 2.437371 2.661553 2.45122 2.476745 3.573904 5
(10,000 x 100)
Unit: milliseconds
expr min lq mean median uq max neval
set 119.33550 140.35294 174.17641 198.14286 199.56239 213.48837 5
Matrix 20.75578 23.00535 60.10874 79.47677 88.33331 88.97251 5
(10,000 x 1,000)
Unit: milliseconds
expr min lq mean median uq max neval
set 337.0859 382.6305 407.9396 429.0512 440.6331 450.2971 5
Matrix 300.3360 316.5533 411.4678 352.0477 534.4063 553.9957 5
(10,000 x 10,000)
Unit: milliseconds
expr min lq mean median uq max neval
set 1428.319 1483.324 1518.096 1508.114 1578.929 1591.792 5
Matrix 3059.825 3119.654 4366.107 3224.755 6164.489 6261.815 5
外卖
根据您实际使用的数据的维度,方法的相对性能将发生巨大变化。
如果您的实际数据与您最初提出的用于基准测试目的的数据相似,那么矩阵运算应该可以正常工作,但如果尺寸以某种方式变化,我会用代表性的方式重新进行基准测试 "shape"数据。
希望这对你有帮助,因为它对我来说很有趣。
我想对包含数字向量的列执行操作,我想知道执行此操作的最佳方法是什么。
到目前为止,我已经尝试了以下方法,设置的方法似乎是最好的,但也许我错过了一些更好的方法来解决这个问题?在 C++ 中执行此操作可以预期有多大的速度提升?
testVector <- data.table::data.table(A = lapply(1:10^5, function(x) runif(100)))
microbenchmark::microbenchmark(lapply = testVector[, B := lapply(A, diff)],
map = testVector[, C := Map(diff, A)],
set = set(testVector, NULL, "D", lapply(testVector[["A"]], diff)),
forset = {for(i in seq(nrow(testVector))) set(testVector, i, "E", list(list(diff(testVector[[i, "A"]]))))},
times = 10L)
结果如下:
Unit: milliseconds
expr min lq mean median uq max neval
set 789.7967 924.8178 1031.923 1082.325 1146.306 1174.671 10
lapply 1122.2454 1468.9556 1563.002 1619.668 1692.217 1919.405 10
map 1297.5236 1320.7022 1571.344 1592.176 1695.673 2012.051 10
forset 1887.0003 2023.7357 2139.202 2174.912 2245.943 2396.844 10
更新
我已经检查了 Rcpp 如何完成任务。虽然我的 C++ 技能很差,但速度提高了 >10 倍。
C++代码:
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
List cppDiff(List column){
int cSize = column.size();
List outputColumn(cSize, NumericVector());
for(int i = 0; i < cSize; ++i){
NumericVector vectorElement = column[i];
outputColumn[i] = Rcpp::diff(vectorElement);
}
return(outputColumn);
}
测试代码:
library(Rcpp);library(data.table);library(microbenchmark)
sourceCpp("diffColumn.cpp")
vLen <- 100L
cNum <- 1e4L
test <- data.table(A = lapply(1L:cNum, function(x) runif(vLen)))
throughMatrix <- function(column){
difmat <- diff(matrix(unlist(column), nrow = vLen, ncol = cNum))
lapply(seq(cNum), function(i) difmat[, i])
}
microbenchmark::microbenchmark(DT = set(test, NULL, "B", lapply(test[["A"]], diff)),
mat = set(test, NULL, "C", throughMatrix(test[["A"]])),
cpp = set(test, NULL, "D", cppDiff(test[["A"]])),
times = 5)
> all.equal(test$B, test$C)
[1] TRUE
> all.equal(test$B, test$D)
[1] TRUE
Unit: milliseconds
expr min lq mean median uq max neval
DT 845.04418 912.60961 1024.79183 1011.59417 1107.14306 1323.9963 10
mat 643.02187 663.92700 778.91145 816.95972 844.37206 864.1173 10
cpp 45.28504 49.35746 84.27799 78.32085 84.87942 226.1347 10
还有 10000 x 10000 列的另一个基准:
Unit: milliseconds
expr min lq mean median uq max neval
DT 7851.4352 8504.3501 21632.018 25246.7860 29133.358 37424.163 5
mat 8679.9386 8724.1497 22852.724 18235.7693 39199.966 39423.794 5
cpp 244.8572 247.7443 1439.011 303.2556 2715.643 3683.552 5
你考虑过使用矩阵吗?语法和数据结构差异很大,下面的代码不是直接替换,但根据此操作前后的分析管道,我怀疑矩阵 inputs/outputs 可能是处理数据的更合适方式无论如何都比列表列。
library(data.table)
VectorLength <- 1e5L
testVector <- data.table::data.table(A = lapply(1:VectorLength, function(x) runif(100)))
A <- matrix(data = runif(100L*VectorLength),nrow = 100L,ncol = VectorLength)
microbenchmark::microbenchmark(set = testVector[, B := lapply(A, diff)],
Matrix = B <- diff(A),
times = 10L)
在 Windows PC 上产生以下结果:
Unit: milliseconds
expr min lq mean median uq max neval
set 1143.933 1251.064 1316.944 1331.4672 1376.8016 1431.8988 10
Matrix 307.945 315.689 363.255 335.4382 390.1124 499.5492 10
以及 Linux 服务器上的以下内容 运行 Ubuntu 14.04
Unit: milliseconds
expr min lq mean median uq max neval
set 1342.6969 1410.3132 1519.6830 1551.2051 1594.3431 1699.7480 10
Matrix 285.0472 297.3283 375.0613 302.4198 488.3482 503.0959 10
仅供参考,当强制转换为 data.table:
时输出看起来像什么str(as.data.table(t(B)))
returns
Classes ‘data.table’ and 'data.frame': 99 obs. of 100000 variables:
$ V1 : num 0.23 0.24 -0.731 0.724 0.074 ...
$ V2 : num -0.628 0.585 -0.164 0.269 -0.16 ...
$ V3 : num 0.1735 0.1128 -0.3069 0.0341 -0.2664 ...
$ V4 : num -0.392 0.593 -0.345 -0.327 0.747 ...
$ V5 : num 0.1084 0.2915 0.3858 -0.1574 -0.0929 ...
$ V6 : num -0.2053 -0.2669 -0.2 0.0214 0.1111 ...
$ V7 : num 0.0582 -0.2141 0.7282 -0.6877 0.4981 ...
$ V8 : num -0.439 -0.114 0.275 0.4 -0.184 ...
$ V9 : num 0.13673 0.55244 -0.43132 0.21692 -0.00308 ...
$ V10 : num 0.701 -0.0486 -0.1464 -0.5595 -0.046 ...
$ V11 : num 0.3583 -0.2588 -0.0742 -0.2113 0.9434 ...
$ V12 : num -0.1146 0.5346 -0.0594 -0.6534 0.6112 ...
$ V13 : num 0.473 0.307 -0.544 0.718 -0.315 ...
更新:视情况而定。
所以我很好奇性能改进在更大范围内会如何,结果证明这是一个有点有趣的问题,其中最有效的方法高度依赖于数据的 size/shape。
使用以下格式:
VectorLength <- 1e5L
ItemLength <- 1e2L
testVector <- data.table::data.table(A = lapply(1:VectorLength, function(x) runif(ItemLength)))
A <- matrix(data = runif(ItemLength*VectorLength),nrow = ItemLength,ncol = VectorLength)
microbenchmark::microbenchmark(set = set(testVector, NULL, "D", lapply(testVector[["A"]], diff)),
Matrix = B <- diff(A),
times = 5L)
我 运行 通过 运行ge 的 VectorLength
和 ItemLengths
值。从这里开始称为(矢量 x 项目),其中 (10,000 x 100) 表示具有 100 个元素的 10,000 个矢量(data.table 行)。由于矩阵形式 t运行 适合基本 R diff 函数,因此 t运行 将变成具有 100 行和 10,000 列的矩阵。
(10,000 x 10)
Unit: milliseconds
expr min lq mean median uq max neval
set 83.947769 88.420871 102.822626 90.91088 104.737002 146.096606 5
Matrix 2.368524 2.437371 2.661553 2.45122 2.476745 3.573904 5
(10,000 x 100)
Unit: milliseconds
expr min lq mean median uq max neval
set 119.33550 140.35294 174.17641 198.14286 199.56239 213.48837 5
Matrix 20.75578 23.00535 60.10874 79.47677 88.33331 88.97251 5
(10,000 x 1,000)
Unit: milliseconds
expr min lq mean median uq max neval
set 337.0859 382.6305 407.9396 429.0512 440.6331 450.2971 5
Matrix 300.3360 316.5533 411.4678 352.0477 534.4063 553.9957 5
(10,000 x 10,000)
Unit: milliseconds
expr min lq mean median uq max neval
set 1428.319 1483.324 1518.096 1508.114 1578.929 1591.792 5
Matrix 3059.825 3119.654 4366.107 3224.755 6164.489 6261.815 5
外卖
根据您实际使用的数据的维度,方法的相对性能将发生巨大变化。
如果您的实际数据与您最初提出的用于基准测试目的的数据相似,那么矩阵运算应该可以正常工作,但如果尺寸以某种方式变化,我会用代表性的方式重新进行基准测试 "shape"数据。
希望这对你有帮助,因为它对我来说很有趣。