Comment résumer les données par groupe dans R? [fermé]

181

J'ai un cadre de données R comme ceci:

        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
...

Je dois obtenir un bloc de données sous la forme suivante:

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

Le numéro de groupe peut varier, mais vous pouvez obtenir leur nom et leur quantité en appelant le levels(factor(data$group))

Quelles manipulations faut-il faire avec les données pour obtenir le résultat?

Yuriy Petrovskiy
la source
les virgules dans le cadre de données de résultats signifient quelque chose de spécial, ou est-ce juste le point décimal?
Mpiktas
@mpiktas Merci d'avoir noté. Corrigée. Ce sont des problèmes de localisation (je suis russe) - nous utilisons des virgules pour séparer les décimales.
Yuriy Petrovskiy
3
Je m'en doutais. Toute l' Europe utilise des virgules, à l'exception des Britanniques.
Mpiktas
4
Bien que je ne sois pas britannique, je préfère les points pour séparateur décimal.
Roman Luštrik
1
Voir aggregate, tapplypuis stackoverflow.com pour toute question de codage ultérieure de ce type.
conjugateprior

Réponses:

140

Voici la variante plyr one line utilisant 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))

Voici une autre variante d'une ligne utilisant le nouveau package 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]

Celui-ci est plus rapide, bien que cela ne se remarque que sur les tables de 100 000 lignes. Timings sur mon Macbook Pro avec processeur Core 2 Duo de 2,53 Ghz et 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 

Des économies supplémentaires sont possibles si nous utilisons 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 
mpiktas
la source
2
@chl, cela m'a donné l'occasion d'essayer ce nouveau package data.table . Cela semble vraiment prometteur.
Mpiktas
7
+6000 pour data.table. C'est vraiment beaucoup plus rapide que ddply, même pour moi sur des jeux de données inférieurs à 100k (j'en ai un avec seulement 20k lignes). Ce doit être quelque chose à voir avec les fonctions que j'applique, mais cela prendra quelques minutes et data.table quelques secondes.
atomicules
Typo simple: je pense que vous vouliez dire dt <- data.table(dtf)au lieu de dt <- data.table(dt)dans le deuxième bloc de code. Ainsi, vous créez la table de données à partir d'un bloc de données et non à partir de la dtfonction du statspackage. J'ai essayé de le modifier, mais je ne peux pas modifier moins de six caractères.
Christopher Bottoms
À mon avis (pas humble dans ce cas), data.tablec’est le meilleur moyen d’agréger les données et cette réponse est excellente, mais ne fait qu’effleurer la surface. En plus d'être syntaxiquement supérieur, il est également extrêmement flexible et possède de nombreuses fonctionnalités avancées qui impliquent des jointures et des mécanismes internes. Consultez la FAQ, la page github ou le cours pour plus d'informations.
geneorama
98

Une possibilité consiste à utiliser la fonction d'agrégat . Par exemple,

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

vous donne la deuxième colonne du résultat souhaité.

ocram
la source
1
Ne créez pas de lien vers votre serveur d'aide local :-) +1, mais consultez mes commentaires à la réponse de @ steffen.
chl
Fait la chose en appelant 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))mais je ne suis pas sûr que c'est la bonne façon. Je ne suis pas sûr de ce qui va arriver alors les résultats des colonnes liées seront dans un ordre différent (je pense que c'est possible). Quel est ton avis?
Youri Petrovskiy
9
@Yuriy Les lignes ne doivent pas être en panne, mais voici une façon de le faire, un appel à aggregate():aggregate(age ~ group, data=dat, FUN = function(x) c(M=mean(x), SD=sd(x)))
lock
@lockedoff: Merci d'avoir complété ma réponse!
ocram
27

Comme vous manipulez un cadre de données, le dplyrpackage est probablement le moyen le plus rapide de le faire.

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))

ou de manière équivalente, en utilisant l' opérateur 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))

EDIT toute utilisation de l’opérateur de pipe:

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))
Bastiaan Quast
la source
3
+1 pour dplyr. Il a simplifié de nombreuses tâches R et rendu obsolètes nombre de ces méthodes.
gregmacfarlane
L'utilisation complète de la version d'opérateur de tuyau ne fonctionne pas pour moi malheureusement
dagcilibili
Avez-vous chargé Dplyr ou Magrittr?
Bastiaan Quast
merci beaucoup @bquast d'avoir indiqué la solution, résumez la fonction appelée à la plyrplace du dplyrproblème.
dagcilibili
12

Génial, merci bquast d'avoir ajouté la solution Dplyr!

Il s'avère que dplyr et data.table sont très proches:

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 est toujours le plus rapide, suivi de très près par dplyr (), qui semble intéressant plus rapidement sur le data.frame que le 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
Matifou
la source
Au début, je pensais que vous deviez placer la clé dans la référence, mais il s’avère que cela ne prend presque pas de temps.
Kasterma
10

En plus des suggestions existantes, vous souhaiterez peut-être vérifier la describe.byfonction dans le psychpackage.

Il fournit un certain nombre de statistiques descriptives, notamment la moyenne et l’écart-type, en fonction d’une variable de regroupement.

Jeromy Anglim
la source
son bon, mais un peu difficile à exporter à LaTeX IME.
richiemorrisroe
10

J'ai trouvé que la fonction summaryBydu paquet doBy était la plus pratique pour cela:

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
gung
la source
9

Utilisez le sqldfpaquet. Cela vous permet maintenant d'utiliser SQL pour résumer les données. Une fois que vous l'avez chargé, vous pouvez écrire quelque chose comme:

sqldf('  select group,avg(age) from data group by group  ')
Kalel
la source
8

Edité: selon les suggestions de chl

La fonction que vous recherchez s'appelle "tapply" et applique une fonction par groupe spécifiée par un facteur.

# 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")

Je suggère vraiment de travailler à travers un tutoriel de base R expliquant toutes les infrastructures de données et méthodes couramment utilisées. Sinon, vous resterez coincé à chaque instant pendant la programmation. Voir cette question pour une collection de ressources gratuites disponibles.

steffen
la source
2
@steffen +1 mais aucune forboucle n'est nécessaire ici, vous pouvez contrôler votre dataframe en ligne, IMO. Pour l' tapplyappel, utilisez function(x) c(mean(x),sd(x)))et cbindle résultat car le PO a demandé les deux statistiques. En outre, ddplydu paquet plyr pourrait le faire en douceur.
chl
@steffen Le problème est que j'ai besoin de la structure de table que j'ai décrite. Il n'y a pas de problème à obtenir des moyens et du DD. Le problème est avec la structure.
Yuriy Petrovskiy
@chl: Merci pour votre commentaire, je ne connaissais pas plyr :). J'ai ajouté cbind, mais le reste n'a pas été touché. Qu'un autre prenne le crédit, cette réponse restera un exemple moins optimal.
steffen
@Yuriy: cbind ajouté. Si vous saviez déjà comment appliquer des fonctions par groupe, vous pouvez reformuler votre question (pour plus de clarté;)).
steffen
@steffen cbind("mean"=mperage,"stdev"=stperage) gives no 'group' column. Will be joining by cbind (groupe = niveaux (facteur (groupe $ données)), "moyenne" = mperage, "stdev" = stperage) `correct?
Yuriy Petrovskiy
7

Voici un exemple avec la fonction que aggregates()j'ai moi-même créée il y a quelque temps:

# 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) ) 

Cela donne le résultat suivant:

> 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

Vous pouvez peut-être obtenir le même résultat à partir de la fonction 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

Permettez-moi de revenir à la sortie de la aggregatesfonction. Vous pouvez le transformer en une belle table en utilisant reshape(), xtabs()et 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")) 

Cela donne:

> 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

Beau, n'est ce pas? Vous pouvez exporter cette table vers un pdf avec la textplot()fonction du gplotspackage.

Voir ici pour les solutions des autres.

Stéphane Laurent
la source