使用 data.table 有条件地计算所需的聚合
Conditionally calculate the required aggregates using data.table
考虑以下代码段:
library("data.table")
dT <- data.table(keyCol=sample(x = c("A", "B", "C"), size = 20, replace = TRUE),
valCol=rpois(n = 20, lambda=10))
# head(dT)
keyCol valCol
<chr> <int>
A 11
C 14
C 9
B 9
C 11
C 10
我想计算一些聚合(valCol
的唯一计数,行数)分组 keyCol
列:
res <- dT[, .(unique__=length(unique(valCol)), count__=.N), by="keyCol"]
# res
keyCol unique__ count__
<chr> <int> <int>
A 4 4
C 5 8
B 6 8
但我想有条件地计算这些聚合,即有时我只想要唯一,有时我只想要计数,有时我想要两者。一种可能的解决方案是使用多个 if else
条件:
getCount <- TRUE
getUnique <- TRUE
aggColName <- "valCol"
if(getCount & getUnique){
res <- dT[, .(unique__=length(unique(valCol)), count__=.N), by="keyCol"]
} else if (getCount){
res <- dT[, .(count__=.N), by="keyCol"]
} else {
res <- dT[, .(unique__=length(unique(valCol))), by="keyCol"]
}
# res
keyCol unique__ count__
<chr> <int> <int>
A 4 4
C 5 8
B 6 8
来自Python背景,if else
阶梯上面好像写了一堆多余的代码。例如,在 pandas 上,我们可以传递包含列名和聚合函数的元组,或者我们可以解压包含列名和聚合函数的字典。
是否有使用 data.table
执行相同操作的简单方法,即使用某些变量传递列名和聚合函数,或有条件地传递它们?
这可能不是您要找的东西,但也许可以帮助您入门。
library("data.table")
dt <- data.table(keyCol=sample(x = c("A", "B", "C"), size = 20, replace = TRUE),
valCol1=rpois(n = 20, lambda=10),
valCol2=sample(20))
chrVals <- rep(c("valCol1", "valCol2"), each = 3)
lFuns <- rep(list(length, uniqueN, sum), 2)
nms <- c("length1", "unique1", "sum1", "length2", "unique2", "sum2")
dt[, setNames(lapply(seq_along(chrVals), function(i) lFuns[[i]](.SD[[i]])), nms), .SDcols = chrVals, by = "keyCol"]
#> keyCol length1 unique1 sum1 length2 unique2 sum2
#> 1: B 4 3 46 4 4 39
#> 2: C 11 9 104 11 11 129
#> 3: A 5 5 39 5 5 42
idx <- c(1,3:5)
dt[, setNames(lapply(idx, function(i) lFuns[[i]](.SD[[i]])), nms[idx]), .SDcols = chrVals, by = "keyCol"]
#> keyCol length1 sum1 length2 unique2
#> 1: B 4 46 4 4
#> 2: C 11 104 11 11
#> 3: A 5 39 5 5
你的具体例子有点棘手,因为 .N
不是一个函数,
这是一个象征。
稍后我会回过头来,
但我们首先假设您只有功能。
您可以编写自己的助手来帮助您实现您想要的目标:
helper <- function(dt, by, colNames, funs) {
dt[, by = by, .SDcols = colNames, Map(funs, .SD, f = function(fn, col) {
fn(col)
})]
}
helper(dT, "keyCol", "valCol", list(
unique__ = function(x) { length(unique(x)) }
))
# keyCol unique__
# 1: B 8
# 2: A 4
# 3: C 2
helper(dT, "keyCol", "valCol", list(
max__ = max
))
# keyCol max__
# 1: B 13
# 2: A 11
# 3: C 13
helper(dT, "keyCol", "valCol", list(
unique__ = function(x) { length(unique(x)) },
max__ = max
))
# keyCol unique__ max__
# 1: B 8 13
# 2: A 4 11
# 3: C 2 13
借助助手,您可以通过指定 .SDcols
来提供不同数量的列,
Map
应用您提供的功能。
但是,你必须知道 data.table
经常可以根据它“看到”的内容来优化调用,
在 helper
背后隐藏一些逻辑意味着你可能会失去它。
举个例子:
dT[, max(valCol), by = keyCol, verbose = TRUE]
Detected that j uses these columns: valCol
Finding groups using forderv ... forder.c received 20 rows and 1 columns
0.000s elapsed (0.000s cpu)
Finding group sizes from the positions (can be avoided to save RAM) ... 0.000s elapsed (0.000s cpu)
Getting back original order ... forder.c received a vector type 'integer' length 3
0.000s elapsed (0.000s cpu)
lapply optimization is on, j unchanged as 'max(valCol)'
GForce optimized j to 'gmax(valCol)'
Making each group and running j (GForce TRUE) ... gforce initial population of grp took 0.000
gforce assign high and low took 0.000
gforce eval took 0.000
0.000s elapsed (0.001s cpu)
keyCol V1
1: B 13
2: A 11
3: C 13
你看到那里GForce TRUE
。
如果您在 helper
内的调用中启用详细信息,
你会注意到 GForce
总是 FALSE
.
现在,关于 .N
,您还可以为此编写一个帮助程序并使用函数“包装”符号,
但老实说,这有点老套。
我的版本只是在堆栈的环境中搜索 .N
直到找到它
(所以它假设 data.table
会计算出在堆栈中的某个点,
在这种情况下是正确的,YMMV)。
count <- function(ignored) {
n <- 1L
e <- parent.frame(n)
ans <- get0(".N", e, inherits = FALSE)
while (is.null(ans) && !identical(e, .GlobalEnv)) {
n <- n + 1L
e <- parent.frame(n)
ans <- get0(".N", e, inherits = FALSE)
}
ans
}
helper(dT, "keyCol", "valCol", list(
unique__ = function(x) { length(unique(x)) },
count__ = count
))
# keyCol unique__ count__
# 1: B 8 10
# 2: A 4 6
# 3: C 2 4
考虑以下代码段:
library("data.table")
dT <- data.table(keyCol=sample(x = c("A", "B", "C"), size = 20, replace = TRUE),
valCol=rpois(n = 20, lambda=10))
# head(dT)
keyCol valCol
<chr> <int>
A 11
C 14
C 9
B 9
C 11
C 10
我想计算一些聚合(valCol
的唯一计数,行数)分组 keyCol
列:
res <- dT[, .(unique__=length(unique(valCol)), count__=.N), by="keyCol"]
# res
keyCol unique__ count__
<chr> <int> <int>
A 4 4
C 5 8
B 6 8
但我想有条件地计算这些聚合,即有时我只想要唯一,有时我只想要计数,有时我想要两者。一种可能的解决方案是使用多个 if else
条件:
getCount <- TRUE
getUnique <- TRUE
aggColName <- "valCol"
if(getCount & getUnique){
res <- dT[, .(unique__=length(unique(valCol)), count__=.N), by="keyCol"]
} else if (getCount){
res <- dT[, .(count__=.N), by="keyCol"]
} else {
res <- dT[, .(unique__=length(unique(valCol))), by="keyCol"]
}
# res
keyCol unique__ count__
<chr> <int> <int>
A 4 4
C 5 8
B 6 8
来自Python背景,if else
阶梯上面好像写了一堆多余的代码。例如,在 pandas 上,我们可以传递包含列名和聚合函数的元组,或者我们可以解压包含列名和聚合函数的字典。
是否有使用 data.table
执行相同操作的简单方法,即使用某些变量传递列名和聚合函数,或有条件地传递它们?
这可能不是您要找的东西,但也许可以帮助您入门。
library("data.table")
dt <- data.table(keyCol=sample(x = c("A", "B", "C"), size = 20, replace = TRUE),
valCol1=rpois(n = 20, lambda=10),
valCol2=sample(20))
chrVals <- rep(c("valCol1", "valCol2"), each = 3)
lFuns <- rep(list(length, uniqueN, sum), 2)
nms <- c("length1", "unique1", "sum1", "length2", "unique2", "sum2")
dt[, setNames(lapply(seq_along(chrVals), function(i) lFuns[[i]](.SD[[i]])), nms), .SDcols = chrVals, by = "keyCol"]
#> keyCol length1 unique1 sum1 length2 unique2 sum2
#> 1: B 4 3 46 4 4 39
#> 2: C 11 9 104 11 11 129
#> 3: A 5 5 39 5 5 42
idx <- c(1,3:5)
dt[, setNames(lapply(idx, function(i) lFuns[[i]](.SD[[i]])), nms[idx]), .SDcols = chrVals, by = "keyCol"]
#> keyCol length1 sum1 length2 unique2
#> 1: B 4 46 4 4
#> 2: C 11 104 11 11
#> 3: A 5 39 5 5
你的具体例子有点棘手,因为 .N
不是一个函数,
这是一个象征。
稍后我会回过头来,
但我们首先假设您只有功能。
您可以编写自己的助手来帮助您实现您想要的目标:
helper <- function(dt, by, colNames, funs) {
dt[, by = by, .SDcols = colNames, Map(funs, .SD, f = function(fn, col) {
fn(col)
})]
}
helper(dT, "keyCol", "valCol", list(
unique__ = function(x) { length(unique(x)) }
))
# keyCol unique__
# 1: B 8
# 2: A 4
# 3: C 2
helper(dT, "keyCol", "valCol", list(
max__ = max
))
# keyCol max__
# 1: B 13
# 2: A 11
# 3: C 13
helper(dT, "keyCol", "valCol", list(
unique__ = function(x) { length(unique(x)) },
max__ = max
))
# keyCol unique__ max__
# 1: B 8 13
# 2: A 4 11
# 3: C 2 13
借助助手,您可以通过指定 .SDcols
来提供不同数量的列,
Map
应用您提供的功能。
但是,你必须知道 data.table
经常可以根据它“看到”的内容来优化调用,
在 helper
背后隐藏一些逻辑意味着你可能会失去它。
举个例子:
dT[, max(valCol), by = keyCol, verbose = TRUE]
Detected that j uses these columns: valCol
Finding groups using forderv ... forder.c received 20 rows and 1 columns
0.000s elapsed (0.000s cpu)
Finding group sizes from the positions (can be avoided to save RAM) ... 0.000s elapsed (0.000s cpu)
Getting back original order ... forder.c received a vector type 'integer' length 3
0.000s elapsed (0.000s cpu)
lapply optimization is on, j unchanged as 'max(valCol)'
GForce optimized j to 'gmax(valCol)'
Making each group and running j (GForce TRUE) ... gforce initial population of grp took 0.000
gforce assign high and low took 0.000
gforce eval took 0.000
0.000s elapsed (0.001s cpu)
keyCol V1
1: B 13
2: A 11
3: C 13
你看到那里GForce TRUE
。
如果您在 helper
内的调用中启用详细信息,
你会注意到 GForce
总是 FALSE
.
现在,关于 .N
,您还可以为此编写一个帮助程序并使用函数“包装”符号,
但老实说,这有点老套。
我的版本只是在堆栈的环境中搜索 .N
直到找到它
(所以它假设 data.table
会计算出在堆栈中的某个点,
在这种情况下是正确的,YMMV)。
count <- function(ignored) {
n <- 1L
e <- parent.frame(n)
ans <- get0(".N", e, inherits = FALSE)
while (is.null(ans) && !identical(e, .GlobalEnv)) {
n <- n + 1L
e <- parent.frame(n)
ans <- get0(".N", e, inherits = FALSE)
}
ans
}
helper(dT, "keyCol", "valCol", list(
unique__ = function(x) { length(unique(x)) },
count__ = count
))
# keyCol unique__ count__
# 1: B 8 10
# 2: A 4 6
# 3: C 2 4