2015-06-25 12 views
7

Sto cercando di utilizzare un data.table all'interno di una funzione e sto cercando di capire perché il mio codice non funziona. Ho un data.table come segue:I nomi delle colonne data.table non funzionano all'interno di una funzione

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 

Sto cercando di creare tutte le coppie di "my_name" con diversi valori di "my_id", che per DT sarebbe:

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 

ho una funzione per restituire tutte le coppie di "my_name" per una data coppia di valori di "my_id" che funziona come previsto.

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 

Ora, voglio eseguire questa funzione per tutte le coppie di ID, che cerco di fare trovando tutte le coppie di ID e quindi utilizzando mapply con la funzione 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 

Ancora, se provo a fare la stessa cosa senza un mapply, funziona.

get_pairs3(tid1[1],tid2[1],DT) 
Var1 Var2 
1 A C 
2 B C 
3 A D 
4 B D 

Perché questa funzione ha esito negativo solo se utilizzata all'interno di un mapping? Penso che questo abbia qualcosa a che fare con lo scopo dei nomi data.table, ma non ne sono sicuro.

In alternativa, esiste un modo diverso/più efficiente per eseguire questa attività? Ho un grande data.table con un terzo "campione" id e ho bisogno di ottenere tutte queste coppie per ogni campione (ad esempio, operando su DT [sample == "sample_id",]). Sono nuovo nel pacchetto data.table e potrei non utilizzarlo nel modo più efficiente.

+0

Mi dispiace, non sono sicuro perché il mapply non funziona e quindi non ne ha parlato nella mia risposta. – Frank

+0

per 'mapply', funziona se si mette' DT' direttamente nella funzione e non come parametro (sebbene non risolva la parte "perché non funziona" ...) – Cath

+0

Ogni ID ha sempre esattamente due 'nomi'? – Frank

risposta

3

Perché questa funzione ha esito negativo solo se utilizzata all'interno di un mapping? Penso che abbia qualcosa a che fare con l'ambito dei nomi data.table, ma io sono non sicuro.

Il motivo per cui la funzione ha esito negativo non ha nulla a che fare con l'ambito in questo caso. mapply vettorizza la funzione, prende ogni elemento di ogni parametro e passa alla funzione.Quindi, nel tuo caso, gli elementi data.table sono le sue colonne, quindi sostituisce la colonnaanziché il numero completo data.table.

Se si desidera passare lo data.table completo a mapply, è necessario utilizzare il parametro MoreArgs. Poi la funzione funziona:

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 
+0

Ah ok, questo ha senso. È perché un data.table è anche un elenco()? – Sam

+0

@Sam Yeap, 'data.table's,' data.frame's, 'tbl_df's sono elenchi con alcune proprietà aggiuntive. –

4

enumerare tutte le possibili coppie

u_name <- unique(DT$my_name) 
all_pairs <- CJ(u_name,u_name)[V1 < V2] 

Enumera osservate coppie

obs_pairs <- unique(
    DT[,{un <- unique(my_name); CJ(un,un)[V1 < V2]}, by=my_id][, !"my_id", with=FALSE] 
) 

Prendere la differenza

all_pairs[!J(obs_pairs)] 

CJ è come expand.grid eccezione del fatto che crea un data.table con tutti le sue colonne come chiave. Un data.table X deve essere digitato per un join X[J(Y)] o un non-join X[!J(Y)] (come l'ultima riga) per funzionare. Il J è facoltativo, ma rende più ovvio che stiamo facendo un join.


Semplificazioni. @CathG ha sottolineato che esiste un modo più semplice di costruire obs_pairs se si dispone sempre di due "nomi" ordinati per ciascun "id" (come nei dati di esempio): utilizzare as.list(un) al posto di CJ(un,un)[V1 < V2].

+0

Spiacente, non ho detto che potrebbero esserci duplicati in "my_name" ma la soluzione funziona se non ci sono duplicati. Questo è molto più elegante del mio approccio però. Chiaramente ho bisogno di imparare a usare i join di più. – Sam

+0

@Sam Ho modificato per quel caso ora (se ho capito bene). – Frank

3

La funzione debugonce() è estremamente utile in questi scenari.

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) 

che è errato. Fondamentalmente, mapply() prende il primo elemento di ogni argomento di input e lo passa alla tua funzione. In questo caso hai fornito un data.table, che è anche lista. Quindi, invece di passare l'intero data.table, passa ogni elemento della lista (colonne).

Quindi, è possibile aggirare il problema facendo:

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

Ma mapply() semplifica il risultato di default, e quindi si otterrebbe un back matrix. Dovrai usare SIMPLIFY = FALSE.

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

o semplicemente utilizzare Map:

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

Usa rbindlist() di impegnare la risultati.

HTH