2015-06-04 16 views
9

Desidero contare il numero di volte in cui ciascuna combinazione di due elementi appare nello stesso gruppo.r contare le combinazioni di elementi nei gruppi

Ad esempio, con:

> dat = data.table(group = c(1,1,1,2,2,2,3,3), id=c(10,11,12,10,11,13,11,13)) 
> dat 
    group id 
1:  1 10 
2:  1 11 
3:  1 12 
4:  2 10 
5:  2 11 
6:  2 13 
7:  3 11 
8:  3 13 

Il risultato atteso sarebbe:

id.1 id.2 nb_common_appearances 
10 11 2      (in group 1 and 2) 
10 12 1      (in group 1) 
11 12 1      (in group 1) 
10 13 1      (in group 2) 
11 13 2      (in group 2 and 3) 

risposta

9

Qui è un approccio data.table (più o meno lo stesso di @ josilber c'è da plyr):

pairs <- dat[, c(id=split(combn(id,2),1:2)), by=group ] 
pairs[, .N, by=.(id.1,id.2) ] 
# id.1 id.2 N 
# 1: 10 11 2 
# 2: 10 12 1 
# 3: 11 12 1 
# 4: 10 13 1 
# 5: 11 13 2 

Si potrebbe anche prendere in considerazione la visualizzazione dei risultati in un table:

pairs[, table(id.1,id.2) ] 
#  id.2 
# id.1 11 12 13 
# 10 2 1 1 
# 11 0 1 2 

È possibile utilizzare fonde invece di combn :

setkey(dat, group) 
dat[ dat, allow.cartesian=TRUE ][ id<i.id, .N, by=.(id,i.id) ] 

Benchmark. Per i dati di grandi dimensioni, le unioni possono essere un po 'più veloci (come ipotizzato da @DavidArenburg). @ Risposta di Arun è ancora più veloce:

DT <- data.table(g=1,id=1:(1.5e3),key="id") 
system.time({a <- combn(DT$id,2)}) 
# user system elapsed 
# 0.81 0.00 0.81 
system.time({b <- DT[DT,allow.cartesian=TRUE][id<i.id]}) 
# user system elapsed 
# 0.13 0.00 0.12 
system.time({d <- DT[,.(rep(id,(.N-1L):0L),id[indices(.N-1L)])]}) 
# user system elapsed 
# 0.01 0.00 0.02 

(ho lasciato fuori il gruppo-by come io non credo che sarà importante per i tempi.)


In difesa della combn . L'approccio combn si estende ben al combo più grandi, mentre si fonde e @ risposta di Arun, mentre molto più veloce per le coppie, non (per quanto posso vedere):

DT2  <- data.table(g=rep(1:2,each=5),id=1:5) 
tuple_size <- 4 

tuples <- DT2[, c(id=split(combn(id,tuple_size),1:tuple_size)), by=g ] 
tuples[, .N, by=setdiff(names(tuples),"g")]  
# id.1 id.2 id.3 id.4 N 
# 1: 1 2 3 4 2 
# 2: 1 2 3 5 2 
# 3: 1 2 4 5 2 
# 4: 1 3 4 5 2 
# 5: 2 3 4 5 2 
+1

Io in realtà come l'ultimo approccio migliore. Perché non è raccomandato? –

+0

@DavidArenburg Immagino che sia piuttosto lento fare simili join cartesiani. Inoltre, funziona solo per il caso speciale di coppie, mentre 'c (id = split (combn (id, 3), 1: 3))' è un'estensione diretta del primo approccio. Infine, navigare nella notazione 'i. *' Qui è un po 'macchinoso e porta ai nomi "sbagliati" sul risultato. – Frank

+1

Non penso che un join binario sarà lento, anche se potrebbe costare un po 'di memoria aggiuntiva a causa dell'adesione cartesiana. –

6

Si potrebbe rimodellare i dati per avere ogni coppia in ogni gruppo in una riga separata (ho usato split-apply-combine per quel passaggio) e quindi utilizzare count dal pacchetto plyr per contare la frequenza di righe univoche:

library(plyr) 
count(do.call(rbind, lapply(split(dat, dat$group), function(x) t(combn(x$id, 2))))) 
# x.1 x.2 freq 
# 1 10 11 2 
# 2 10 12 1 
# 3 10 13 1 
# 4 11 12 1 
# 5 11 13 2 
2

Ecco un approccio dplyr, utilizzando combn a fare le combinazioni.

dat %>% 
    group_by(group) %>% 
    do(as.data.frame(t(combn(.[["id"]], 2)))) %>% 
    group_by(V1, V2) %>% 
    summarise(n()) 

Source: local data frame [5 x 3] 
Groups: V1 

    V1 V2 n() 
1 10 11 2 
2 10 12 1 
3 10 13 1 
4 11 12 1 
5 11 13 2 
6

Un altro modo con data.table:

require(data.table) 
indices <- function(n) sequence(n:1L) + rep(1:n, n:1) 
dat[, .(id1 = rep(id, (.N-1L):0L), 
     id2 = id[indices(.N-1L)]), 
     by=group 
    ][, .N, by=.(id1, id2)] 
# id1 id2 N 
# 1: 10 11 2 
# 2: 10 12 1 
# 3: 11 12 1 
# 4: 10 13 1 
# 5: 11 13 2 
+0

In generale il set di dati deve essere ordinato 'setorder (dat, group, id)' –

Problemi correlati