R的数据表格列名在函数内部无法正常使用

10

我正在尝试在一个函数中使用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_name”的配对,使用不同的“my_id”值,对于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_id”值对的所有“my_name”对,其功能正常。

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对,并使用mapply函数与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名称的作用域有关,但我不确定。

另外,有没有不同/更有效的方法来完成此任务?我有一个带有第三个id“sample”的大型data.table,并且我需要获取每个样本的所有这些对(例如操作DT [样本==“sample_id”,])。我对data.table包还不熟悉,可能没有以最有效的方式使用它。


抱歉,我不确定为什么mapply无法工作,所以在我的答案中没有提到它。 - Frank
对于mapply,如果您直接将DT放入函数中而不是作为参数,则它可以工作(尽管这并不能解决“为什么它不起作用”的问题...) - Cath
每个 id 是否总是有两个 names - Frank
1
每个ID可能有一个或多个名称,而ID或名称可能重复。此外,名称、ID对不保证唯一。 - Sam
3个回答

4

列举所有可能的对

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。要使连接X[J(Y)]或非连接X[!J(Y)](如最后一行)有效,data.table X必须被键入。 J是可选的,但使用它会更明显地表明我们正在进行连接。


简化。如果每个"id"都具有两个按排序后的"name"(例如示例数据),那么@CathG指出构建obs_pairs的更清晰方法是:使用as.list(un)代替CJ(un,un)[V1 < V2]


抱歉,我没有提到“my_name”中可能会有重复项,但是如果没有重复项,您的解决方案是有效的。虽然这比我的方法更优雅,但显然我需要学习更多关于使用连接的知识。 - Sam
@Sam 我现在已经为那种情况进行了编辑(如果我理解正确的话)。 - Frank

4
函数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,它也是一个 list。因此,它不是传递整个数据表,而是传递列表的每个元素(列)。

所以,您可以通过以下方式解决:

mapply(get_pairs, tid1, tid2, list(DT))

但是mapply()默认情况下会简化结果,因此您将得到一个矩阵返回。您需要使用SIMPLIFY = FALSE

mapply(get_pairs, tid1, tid2, list(DT), SIMPLIFY = FALSE)

或者简单地使用 Map

Map(get_pairs, tid1, tid2, list(DT))

使用rbindlist()来绑定结果。
希望这有帮助。

3
为什么这个函数只在mapply中使用时失败?我认为这与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

啊,好的,那很有道理。这是因为data.table也是一个list()吗? - Sam
@Sam Yeap,data.tabledata.frametbl_df都是带有一些额外属性的列表。 - Carlos Cinelli

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接