Come riassumere i dati per gruppo in R? [chiuso]


181

Ho un frame di dati R in questo modo:

        age group
1   23.0883     1
2   25.8344     1
3   29.4648     1
4   32.7858     2
5   33.6372     1
6   34.9350     1
7   35.2115     2
8   35.2115     2
9   35.2115     2
10  36.7803     1
...

Devo ottenere il frame di dati nel seguente formato:

group mean     sd
1     34.5     5.6
2     32.3     4.2
...

Il numero di gruppo può variare, ma i loro nomi e quantità possono essere ottenuti chiamando levels(factor(data$group))

Quali manipolazioni dovrebbero essere fatte con i dati per ottenere il risultato?


le virgole nel riquadro dei dati dei risultati significano qualcosa di speciale o è solo il punto decimale?
mpiktas,

@mpiktas Grazie per aver notato. Corretto. Questi erano problemi di localizzazione (io sono russo) - usiamo la virgola per la separazione decimale.
Yuriy Petrovskiy,

3
Lo sospettavo. Tutta l' Europa usa la virgola tranne gli inglesi.
mpiktas,

4
Pur non essendo britannico, preferisco il punto per il separatore decimale.
Roman Luštrik,

1
Vedere aggregate, tapplye quindi stackoverflow.com per eventuali domande di codifica successive di questo tipo.
conjugateprior,

Risposte:


140

Ecco la variante plyr a una riga usando ddply :

dt <- data.frame(age=rchisq(20,10),group=sample(1:2,20,rep=T))
ddply(dt,~group,summarise,mean=mean(age),sd=sd(age))

Ecco un'altra variante di una riga usando il nuovo pacchetto data.table .

dtf <- data.frame(age=rchisq(100000,10),group=factor(sample(1:10,100000,rep=T)))
dt <- data.table(dtf)
dt[,list(mean=mean(age),sd=sd(age)),by=group]

Questo è più veloce, anche se questo è evidente solo sulla tabella con 100k righe. Tempi sul mio Macbook Pro con processore Core 2 Duo da 2,53 Ghz e R 2.11.1:

> system.time(aa <- ddply(dtf,~group,summarise,mean=mean(age),sd=sd(age)))
utilisateur     système      écoulé 
      0.513       0.180       0.692 
> system.time(aa <- dt[,list(mean=mean(age),sd=sd(age)),by=group])
utilisateur     système      écoulé 
      0.087       0.018       0.103 

Ulteriori risparmi sono possibili se utilizziamo setkey:

> setkey(dt,group)
> system.time(dt[,list(mean=mean(age),sd=sd(age)),by=group])
utilisateur     système      écoulé 
      0.040       0.007       0.048 

2
@chl, mi ha dato la possibilità di provare questo nuovo pacchetto data.table . Sembra davvero promettente.
mpiktas,

7
+6000 per data.table. È davvero molto più veloce di ddply, anche per me su set di dati inferiori a 100k (ne ho uno con solo 20k righe). Deve essere qualcosa a che fare con le funzioni che sto applicando, ma ddply richiederà pochi minuti e data.table alcuni secondi.
atomici

Errore di battitura semplice: penso che intendevi dt <- data.table(dtf)invece che dt <- data.table(dt)nel secondo blocco di codice. In questo modo, si sta creando la tabella di dati da un frame di dati anziché dalla dtfunzione dal statspacchetto. Ho provato a modificarlo, ma non posso effettuare modifiche con meno di sei caratteri.
Christopher Bottoms,

Secondo me (non umile in questo caso) data.tableè il modo migliore per aggregare i dati e questa risposta è ottima, ma graffia ancora solo la superficie. Oltre ad essere sintatticamente superiore, è anche estremamente flessibile e ha molte funzionalità avanzate che coinvolgono join e meccanica interna. Consulta le FAQ, la pagina di github o il corso per ulteriori informazioni.
geneorama,

98

Una possibilità è utilizzare la funzione aggregata . Per esempio,

aggregate(data$age, by=list(data$group), FUN=mean)[2]

ti dà la seconda colonna del risultato desiderato.


1
Non collegare al tuo server di assistenza locale :-) +1 ma vedi i miei commenti alla risposta di @ steffen.
chl

Fatto la cosa chiamando data.frame(group=levels(factor(data$group)),mean=(aggregate(data$age, by=list(data$group), FUN=mean)$x),sd=(aggregate(data$age, by=list(data$group), FUN=sd)$x))ma non sono sicuro che sia il modo corretto. Non sono sicuro di cosa accadrà, quindi i risultati delle colonne associate saranno in un ordine diverso (penso che sia possibile). Qual è la tua opinione?
Yuriy Petrovskiy,

9
@Yuriy Le righe non devono essere fuori servizio, ma ecco un modo per farlo una chiamata a aggregate():aggregate(age ~ group, data=dat, FUN = function(x) c(M=mean(x), SD=sd(x)))
bloccato

@lockedoff: grazie per aver completato la mia risposta!
Ocram,

27

Poiché stai manipolando un frame di dati, il dplyrpacchetto è probabilmente il modo più veloce per farlo.

library(dplyr)
dt <- data.frame(age=rchisq(20,10), group=sample(1:2,20, rep=T))
grp <- group_by(dt, group)
summarise(grp, mean=mean(age), sd=sd(age))

o equivalentemente, usando l' operatore dplyr/ magrittrpipe:

library(dplyr)
dt <- data.frame(age=rchisq(20,10), group=sample(1:2,20, rep=T))
group_by(dt, group) %>%
 summarise(mean=mean(age), sd=sd(age))

MODIFICA pieno utilizzo dell'operatore di tubi:

library(dplyr)
data.frame(age=rchisq(20,10), group=sample(1:2,20, rep=T)) %>%
  group_by(group) %>%
  summarise(mean=mean(age), sd=sd(age))

3
+1 per dplyr. Ha semplificato tante attività R e molti di questi metodi sono obsoleti.
gregmacfarlane,

Sfortunatamente l'uso completo della versione dell'operatore di pipa non funziona per me
dagcilibili,

hai caricato dplyr o magrittr?
Bastiaan Quast,

grazie mille @bquast per aver sottolineato la soluzione, da cui è stata chiamata la funzione di riepilogo plyranziché la dplyrquale stava causando il problema.
dagcilibili,

12

Fantastico, grazie bquast per l'aggiunta della soluzione dplyr!

Si scopre che quindi, dplyr e data.table sono molto vicini:

library(plyr)
library(dplyr)
library(data.table)
library(rbenchmark)

dtf <- data.frame(age=rchisq(100000,10),group=factor(sample(1:10,100000,rep=T)))
dt <- data.table(dtf)

setkey(dt,group)

a<-benchmark(ddply(dtf,~group,plyr:::summarise,mean=mean(age),sd=sd(age)),
         dt[,list(mean=mean(age),sd=sd(age)),by=group],
         group_by(dt, group) %>% summarise(mean=mean(age),sd=sd(age) ),
         group_by(dtf, group) %>% summarise(mean=mean(age),sd=sd(age) )
)

a[, c(1,3,4)]

data.table è ancora il più veloce, seguito da vicino da dplyr (), che in modo interessante sembra più veloce su data.frame rispetto a data.table:

                                                              test elapsed relative
1 ddply(dtf, ~group, plyr:::summarise, mean = mean(age), sd = sd(age))   1.689    4.867
2               dt[, list(mean = mean(age), sd = sd(age)), by = group]   0.347    1.000
4   group_by(dtf, group) %>% summarise(mean = mean(age), sd = sd(age))   0.369    1.063
3    group_by(dt, group) %>% summarise(mean = mean(age), sd = sd(age))   0.580    1.671

All'inizio pensavo che dovessi spostare setkey nel benchmark, ma risulta che non ci vuole quasi nulla.
Kasterma,

10

Oltre ai suggerimenti esistenti, potresti voler controllare la describe.byfunzione nel psychpacchetto.

Fornisce una serie di statistiche descrittive tra cui la media e la deviazione standard basata su una variabile di raggruppamento.


è bello, ma un po 'complicato esportare in LaTeX IME.
richiemorrisroe,

10

Ho trovato la funzione summaryBynel pacchetto doBy la più conveniente per questo:

library(doBy)

age    = c(23.0883, 25.8344, 29.4648, 32.7858, 33.6372,
           34.935,  35.2115, 35.2115,  5.2115, 36.7803)
group  = c(1, 1, 1, 2, 1, 1, 2, 2, 2, 1)
dframe = data.frame(age=age, group=group)

summaryBy(age~group, data=dframe, FUN=c(mean, sd))
# 
#   group age.mean    age.sd
# 1     1 30.62333  5.415439
# 2     2 27.10507 14.640441

9

Usa il sqldfpacchetto. Ciò consente ora di utilizzare SQL per riepilogare i dati. Una volta caricato, puoi scrivere qualcosa del tipo -

sqldf('  select group,avg(age) from data group by group  ')

8

A cura: secondo i suggerimenti di chl

La funzione che stai cercando si chiama "tapply" che applica una funzione per gruppo specificata da un fattore.

# create some artificial data
set.seed(42)
groups <- 5

agedat <- c()
groupdat <- c()

for(group in 1:groups){
    agedat <- c(agedat,rnorm(100,mean=0 + group,1/group))
    groupdat <- c(groupdat,rep(group,100))
}
dat <- data.frame("age"=agedat,"group"=factor(groupdat))

# calculate mean and stdev age per group
res <- rbind.data.frame(group=1:5, with(dat, tapply(age, group, function(x) c(mean(x), sd(x)))))
names(res) <- paste("group",1:5)
row.names(res)[2:3] <- c("mean","sd")

Consiglio davvero di lavorare attraverso un tutorial R di base che spieghi tutte le strutture e i metodi di dati comunemente usati. Altrimenti rimarrai bloccato ogni centimetro durante la programmazione. Vedi questa domanda per una raccolta di risorse disponibili gratuite.


2
@steffen +1 ma qui non è necessario un forloop, puoi modificare il tuo frame di dati in linea, IMO. Per la tapplychiamata, utilizzare function(x) c(mean(x),sd(x)))e cbindil risultato come richiesto dall'OP per entrambe le statistiche. Inoltre, ddplydal pacchetto plyr potrebbe farlo senza problemi.
chl

@steffen Il problema è che ho bisogno esattamente della struttura della tabella che ho descritto. Non ci sono problemi con ottenere mezzi e sd. Il problema è con la struttura.
Yuriy Petrovskiy,

@chl: Grazie per il tuo commento, non sapevo di plyr :). Ho aggiunto cbind, ma il resto è rimasto intatto. Che un altro ne prenda il merito, questa risposta deve rimanere un esempio meno ottimale.
Steffen,

@Yuriy: aggiunto cbind. Se sapevi già come applicare le funzioni per gruppo, puoi riformulare la tua domanda (solo per chiarezza;)).
Steffen,

@steffen cbind("mean"=mperage,"stdev"=stperage) gives no 'group' column. Will be joining by cbind (gruppo = livelli (fattore (gruppo $ di dati)), "mean" = mperage, "stdev" = stperage) `corretto?
Yuriy Petrovskiy,

7

Ecco un esempio con la funzione che aggregates()ho fatto io stesso qualche tempo fa:

# simulates data
set.seed(666)
( dat <- data.frame(group=gl(3,6), level=factor(rep(c("A","B","C"), 6)), 
                    y=round(rnorm(18,10),1)) )

> dat
   group level    y
1      1     A 10.8
2      1     B 12.0
3      1     C  9.6
4      1     A 12.0
5      1     B  7.8
6      1     C 10.8
7      2     A  8.7
8      2     B  9.2
9      2     C  8.2
10     2     A 10.0
11     2     B 12.2
12     2     C  8.2
13     3     A 10.9
14     3     B  8.3
15     3     C 10.1
16     3     A  9.9
17     3     B 10.9
18     3     C 10.3

# aggregates() function
aggregates <- function(formula, data=NULL, FUNS){ 
    if(class(FUNS)=="list"){ 
        f <- function(x) sapply(FUNS, function(fun) fun(x)) 
    }else{f <- FUNS} 
    temp <- aggregate(formula, data, f) 
    out <- data.frame(temp[,-ncol(temp)], temp[,ncol(temp)]) 
    colnames(out)[1] <- colnames(temp)[1] 
return(out) 
} 

# example 
FUNS <- function(x) c(mean=round(mean(x),0), sd=round(sd(x), 0)) 
( ag <- aggregates(y~group:level, data=dat, FUNS=FUNS) ) 

Dà il seguente risultato:

> ag
  group level mean sd
1     1     A   11  1
2     2     A    9  1
3     3     A   10  1
4     1     B   10  3
5     2     B   11  2
6     3     B   10  2
7     1     C   10  1
8     2     C    8  0
9     3     C   10  0

Forse puoi ottenere lo stesso risultato a partire dalla funzione R split ():

> with(dat, sapply( split(y, group:level), FUNS ) )
     1:A 1:B 1:C 2:A 2:B 2:C 3:A 3:B 3:C
mean  11  10  10   9  11   8  10  10  10
sd     1   3   1   1   2   0   1   2   0

Vorrei tornare all'output della aggregatesfunzione. È possibile trasformare in un bel tavolo con reshape(), xtabs()e ftable():

rag <- reshape(ag, varying=list(3:4), direction="long", v.names="y") 
rag$time <- factor(rag$time) 
ft <- ftable(xtabs(y~group+level+time, data=rag)) 
attributes(ft)$col.vars <- list(c("mean","sd")) 

Questo da:

> ft 
             mean sd
group level         
1     A        11  1
      B        10  3
      C        10  1
2     A         9  1
      B        11  2
      C         8  0
3     A        10  1
      B        10  2
      C        10  0

Bello no? È possibile esportare questa tabella in un pdf con la textplot()funzione del gplotspacchetto.

Vedi qui per le soluzioni degli altri.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.