在按组切片data.table
时,用于切片数据的变量在函数执行期间不在子集中。我使用debugonce
来证明这一点。
library(data.table)
x <- data.table(a = rep(letters[1:4], each = 3), b = rep(c("a", "b"), each = 6), c = rnorm(12))
myfun <- function(y) paste(y$a, y$b, y$c, collapse = "")
> debugonce(myfun)
> x[, myfun(.SD), by = .(b, a)]
debugging in: myfun(.SD)
debug: paste(y$a, y$b, y$c, collapse = "")
Browse[2]> y
c
1: -1.2662416
2: 0.9818497
3: -0.5395385
我所追求的是split-sapply范例的功能,我将根据因子切片data.frame并将该函数应用于所有列,也就是说,包括已经变量的变量用来切片(如下所示)。
> debugonce(myfun)
> sapply(split(x, f = list(x$b, x$a)), FUN = myfun)
debugging in: FUN(X[[i]], ...)
debug: paste(y$a, y$b, y$c, collapse = "")
Browse[2]> y
a b c
1: a a -1.2662416
2: a a 0.9818497
3: a a -0.5395385
答案 0 :(得分:14)
OP有一个函数,它将一个列表作为参数,该列应该包含data.table的所有列,包括用于by
中分组的列。
根据help(".SD")
:
.SD
是一个data.table,其中包含每个组的x
数据的子集,排除by
中使用的任何列(或keyby
)。
(强调我的)
.BY
是一个列表,其中包含by
中每个项目的长度为1的向量。如果事先不知道by
,这可能很有用。
因此,.BY
和.SD
相互补充,以访问data.table的所有列。
而不是在函数调用中明确重复by
列
x[, myfun(c(list(b, a), .SD)), by = .(b, a)]
我们可以使用
x[, myfun(c(.BY, .SD)), by = .(b, a)]
b a V1 1: a a a a -1.02091215130492a a -0.295107569536843a a 0.77776326093429 2: a b b a -0.369037832486311b a -0.716211663822323b a -0.264799143319049 3: b c c b -1.39603530693486c b 1.4707902839894c b 0.721925347069227 4: b d d b -1.15220308230505d b -0.736782242593426d b 0.420986999145651
OP使用debugonce()
来显示传递给myfun()
的参数:
> debugonce(myfun)
> x[, myfun(c(.BY, .SD)), by = .(b, a)]
debugging in: myfun(c(.BY, .SD))
debug at #1: paste(y$a, y$b, y$c, collapse = "")
Browse[2]> y
$b
[1] "a"
$a
[1] "a"
$c
[1] -1.0209122 -0.2951076 0.7777633
使用另一个样本数据集和函数,可能更容易举例说明问题的核心:
x <- data.table(a = rep(letters[3:6], each = 3), b = rep(c("x", "y"), each = 6), c = 1:12)
myfun <- function(y) paste(y$a, y$b, y$c, sep = "/", collapse = "-")
x[, myfun(.SD), by = .(b, a)]
b a V1 1: x c //1-//2-//3 2: x d //4-//5-//6 3: y e //7-//8-//9 4: y f //10-//11-//12
因此,列b
和a
确实在输出中显示为分组变量,但它们不会通过.SD
传递给函数。
现在,.BY
补充.SD
x[, myfun(c(.BY, .SD)), by = .(b, a)]
b a V1 1: x c c/x/1-c/x/2-c/x/3 2: x d d/x/4-d/x/5-d/x/6 3: y e e/y/7-e/y/8-e/y/9 4: y f f/y/10-f/y/11-f/y/12
data.table的所有列都传递给函数。
Roland has suggested将.BY
和.SD
作为单独的参数传递给函数。实际上,.BY
是一个列表对象,.SD
是一个data.table对象(它本质上也是一个允许我们使用c(.BY, .SD)
的列表)。可能存在差异可能很重要的情况。
为了验证,我们可以定义一个打印str()
作为副作用的函数。该函数仅针对第一组(.GRP == 1L
)调用。
myfun1 <- function(y) str(y)
x[, if (.GRP == 1L) myfun1(.SD), by = .(b, a)]
Classes ‘data.table’ and 'data.frame': 3 obs. of 1 variable: $ c: int 1 2 3 - attr(*, ".internal.selfref")=<externalptr> - attr(*, ".data.table.locked")= logi TRUE Empty data.table (0 rows) of 2 cols: b,a
x[, if (.GRP == 1L) myfun1(.BY), by = .(b, a)]
List of 2 $ b: chr "x" $ a: chr "c" Empty data.table (0 rows) of 2 cols: b,a
x[, if (.GRP == 1L) myfun1(c(.BY, .SD)), by = .(b, a)]
List of 3 $ b: chr "x" $ a: chr "c" $ c: int [1:3] 1 2 3 Empty data.table (0 rows) of 2 cols: b,a
旁边help(".SD")
评论&amp;对以下SO问题的回答可能很有用: