我试图在一个函数中使用data.table,我想知道为什么我的代码会失败。我有一个data.table,内容如下。
DT <- data.table(my_name=c("A","B","C","D","E","F"),my_id=c(2,2,3,3,4,4))
> DT
my_name my_id
1: A 2
2: B 2
3: C 3
4: D 3
5: E 4
6: F 4
我试图用不同的 "my_id "值来创建所有的 "my_name "对,对DT来说是:
Var1 Var2
A C
A D
A E
A F
B C
B D
B E
B F
C E
C F
D E
D F
我有一个函数来返回所有的 "my_name "对给定的 "my_id "值的对子,它的工作原理和预期一样。
get_pairs <- function(id1,id2,tdt) {
return(expand.grid(tdt[my_id==id1,my_name],tdt[my_id==id2,my_name]))
}
> get_pairs(2,3,DT)
Var1 Var2
1 A C
2 B C
3 A D
4 B D
现在,我想对所有的id对执行这个函数,我尝试通过找到所有的id对,然后使用maply与get_pairs函数来实现。
> combn(unique(DT$my_id),2)
[,1] [,2] [,3]
[1,] 2 2 3
[2,] 3 4 4
tid1 <- combn(unique(DT$my_id),2)[1,]
tid2 <- combn(unique(DT$my_id),2)[2,]
mapply(get_pairs, tid1, tid2, DT)
Error in expand.grid(tdt[my_id == id1, my_name], tdt[my_id == id2, my_name]) :
object 'my_id' not found
同样,如果我试着做同样的事情而不使用 mapply,它也能工作。
get_pairs3(tid1[1],tid2[1],DT)
Var1 Var2
1 A C
2 B C
3 A D
4 B D
为什么这个函数只有在mapply中使用时才会失败?我认为这与data.table名称的范围有关,但我不确定。
另外,有没有其他更有效的方法来完成这个任务?我有一个大的data.table,其中有第三个id "sample",我需要得到每个样本的所有这些对(例如,在DT[sample=="sample_id",]上操作)。我对 data.table 包很陌生,可能没有以最有效的方式使用它。
为什么这个函数只有在apply中使用时才会失败?我认为这与data.table名称的范围有关,但我不确定。
在这种情况下,函数失败的原因与范围无关。mapply
矢量化函数,它把每个参数的每个元素都传递给函数。所以,在你的例子中, data.table
元素是它的列,所以 mapply
正在通过列 my_name
而不是完整的 data.table
.
如果你想通过完整的 data.table
到 mapply
,你应该使用 MoreArgs
参数。那么你的函数就可以工作了。
res <- mapply(get_pairs, tid1, tid2, MoreArgs = list(tdt=DT), SIMPLIFY = FALSE)
do.call("rbind", res)
Var1 Var2
1 A C
2 B C
3 A D
4 B D
5 A E
6 B E
7 A F
8 B F
9 C E
10 D E
11 C F
12 D F
这个函数 debugonce()
在这些情况下是非常有用的。
debugonce(mapply)
mapply(get_pairs, tid1, tid2, DT)
# Hit enter twice
# from within BROWSER
debugonce(FUN)
# Hit enter twice
# you'll be inside your function, and then type DT
DT
# [1] "A" "B" "C" "D" "E" "F"
Q # (to quit debugging mode)
这是错的。基本上。mapply()
取每个输入参数的第一个元素并将其传递给你的函数。在本例中,你提供了一个 数据表,这也是 列表. 所以,它不是传递整个data.table,而是传递列表(列)的每个元素。
所以,你可以通过这样做来解决这个问题。
mapply(get_pairs, tid1, tid2, list(DT))
但是... mapply()
默认情况下简化了结果,因此你会得到一个 matrix
回。你得用 SIMPLIFY = FALSE
.
mapply(get_pairs, tid1, tid2, list(DT), SIMPLIFY = FALSE)
或者干脆使用 Map
:
Map(get_pairs, tid1, tid2, list(DT))
使用 rbindlist()
来绑定结果。
HTH
枚举所有可能的配对
u_name <- unique(DT$my_name)
all_pairs <- CJ(u_name,u_name)[V1 < V2]
枚举观察到的数据对
obs_pairs <- unique(
DT[,{un <- unique(my_name); CJ(un,un)[V1 < V2]}, by=my_id][, !"my_id"]
)
取其差额
all_pairs[!J(obs_pairs)]
CJ
就像 expand.grid
除了它创建了一个以所有列作为键的data.table。一个data.table X
必须通过键控才能加入 X[J(Y)]
或不加入 X[!J(Y)]
(比如最后一行)来工作。的 J
是可选的,但可以使我们在做加入时更加明显。
简化。 @CathG 指出,有一种更简单的方式来构造 obs_pairs
如果你总是对每个 "id "有两个排序的 "名字"(如在示例数据中):使用 as.list(un)
代替 CJ(un,un)[V1 < V2]
.