2012-08-08 12 views
8

Ho difficoltà ad aggregare un frame di dati mantenendo i gruppi nel loro ordine originale (ordine basato sulla prima apparizione nel frame di dati). Sono riuscito a farlo bene, ma speravo che ci fosse un modo più semplice per farlo.Insieme di dati aggregati mantenendo l'ordine originale, in modo semplice

ecco un insieme di dati campione da elaborare:

set.seed(7) 
sel.1 <- sample(1:5, 20, replace = TRUE)  # selection vector 1 
sel.2 <- sample(1:5, 20, replace = TRUE) 
add.1 <- sample(81:100)      # additional vector 1 
add.2 <- sample(81:100) 
orig.df <- data.frame(sel.1, sel.2, add.1, add.2) 

Alcuni punti da notare: ci sono due colonne di selezione per determinare come i dati vengono raggruppati insieme. Saranno lo stesso, e i loro nomi sono noti. Ho inserito solo due colonne aggiuntive in questi dati, ma potrebbero essercene di più. Ho dato i nomi delle colonne cominciando con 'sel' e 'add' per renderlo più facile da seguire, ma i dati effettivi hanno nomi diversi (quindi mentre i trucchi grep sono fantastici, non saranno utili qui).

Quello che sto cercando di fare è aggregare il frame di dati in gruppi basati sulle colonne "sel" e sommare tutte le colonne "aggiungi". Questo è abbastanza semplice utilizzando aggregate come segue:

# Get the names of all the additional columns 
all.add <- names(orig.df)[!(names(orig.df)) %in% c("sel.1", "sel.2")] 
aggr.df <- aggregate(orig.df[,all.add], 
        by=list(sel.1 = orig.df$sel.1, sel.2 = orig.df$sel.2), sum) 

Il problema è che il risultato è ordinato dalle colonne 'SEL'; Lo voglio ordinato in base alla prima apparizione di ciascun gruppo nei dati originali.

Qui sono i miei migliori tentativi di fare questo lavoro:

## Attempt 1 
# create indices for each row (x) and find the minimum index for each range 
index.df <- aggregate(x = 1:nrow(orig.df), 
         by=list(sel.1 = orig.df$sel.1, sel.2 = orig.df$sel.2), min) 
# Make sure the x vector (indices) are in the right range for aggr.df 
index.order <- (1:nrow(index.df))[order(index.df$x)] 
aggr.df[index.order,] 

## Attempt 2 
# get the unique groups. These are in the right order. 
unique.sel <- unique(orig.df[,c("sel.1", "sel.2")]) 
# use sapply to effectively loop over data and sum additional columns. 
sums <- t(sapply(1:nrow(unique.sel), function (x) { 
    sapply(all.add, function (y) { 
     sum(aggr.df[which(aggr.df$sel.1 == unique.sel$sel.1[x] & 
          aggr.df$sel.2 == unique.sel$sel.2[x]), y]) 
     }) 
})) 
data.frame(unique.sel, sums) 

Mentre questi mi danno il risultato giusto, speravo che qualcuno potrebbe indicare una soluzione più semplice. Sarebbe preferibile se la soluzione funzionasse con i pacchetti forniti con l'installazione R standard.

Ho guardato la documentazione per aggregate e match, ma non riuscivo a trovare una risposta (immagino speravo in qualcosa di simile a un parametro "keep.original.order" per aggregate).

Qualsiasi aiuto sarebbe molto apprezzato!


aggiornamento: (in caso qualcuno si imbatte in questo)

Ecco il modo più pulito che ho potuto trovare dopo aver provato per qualche giorno in più:

unique(data.frame(sapply(names(orig.df), function(x){ 
    if(x %in% c("sel.1", "sel.2")) orig.df[,x] else 
    ave(orig.df[,x], orig.df$sel.1, orig.df$sel.2, FUN=sum)}, 
simplify=FALSE))) 
+1

Grazie per l'aggiornamento, questo è forse il più bello breve soluzione di utilizzare data.table. Come si fa a peggiorare il team di sviluppo R per implementare un parametro 'keep.original.order' per l'aggregato? Sembra una chiara svista –

risposta

1

un po 'difficile da leggere , ma ti dà quello che vuoi e ho aggiunto alcuni commenti per chiarire.

# Define the columns you want to combine into the grouping variable 
sel.col <- grepl("^sel", names(orig.df)) 
# Create the grouping variable 
lev <- apply(orig.df[sel.col], 1, paste, collapse=" ") 
# Split and sum up 
data.frame(unique(orig.df[sel.col]), 
      t(sapply(split(orig.df[!sel.col], factor(lev, levels=unique(lev))), 
        apply, 2, sum))) 

L'output è simile al seguente

sel.1 sel.2 add.1 add.2 
1  5  4 96 84 
2  2  2 175 176 
3  1  5 384 366 
5  2  5 95 89 
6  4  1 174 192 
7  2  4 82 87 
8  5  3 91 98 
10  3  2 189 178 
11  1  4 170 183 
14  1  1 100 91 
17  3  3 81 82 
19  5  5 83 88 
20  2  3 90 96 
5

E 'breve e semplice in data.table. Per impostazione predefinita restituisce i gruppi in ordine di prima apparizione.

require(data.table) 
DT = as.data.table(orig.df) 
DT[, list(sum(add.1),sum(add.2)), by=list(sel.1,sel.2)] 

    sel.1 sel.2 V1 V2 
1:  5  4 96 84 
2:  2  2 175 176 
3:  1  5 384 366 
4:  2  5 95 89 
5:  4  1 174 192 
6:  2  4 82 87 
7:  5  3 91 98 
8:  3  2 189 178 
9:  1  4 170 183 
10:  1  1 100 91 
11:  3  3 81 82 
12:  5  5 83 88 
13:  2  3 90 96 

E questo sarà veloce per i dati di grandi dimensioni, quindi non c'è bisogno di cambiare il codice in seguito se si riscontrano problemi di velocità. La seguente sintassi alternativa è il modo più semplice per passare in quale colonna raggruppare.

DT[, lapply(.SD,sum), by=c("sel.1","sel.2")] 

    sel.1 sel.2 add.1 add.2 
1:  5  4 96 84 
2:  2  2 175 176 
3:  1  5 384 366 
4:  2  5 95 89 
5:  4  1 174 192 
6:  2  4 82 87 
7:  5  3 91 98 
8:  3  2 189 178 
9:  1  4 170 183 
10:  1  1 100 91 
11:  3  3 81 82 
12:  5  5 83 88 
13:  2  3 90 96 

o, by può anche essere una sola virgola stringa separata di nomi di colonna, anche:

DT[, lapply(.SD,sum), by="sel.1,sel.2"] 
Problemi correlati