Como resumir dados por grupo em R? [fechadas]

181

Eu tenho R quadro de dados como este:

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

Preciso obter o quadro de dados no seguinte formato:

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

O número do grupo pode variar, mas seus nomes e quantidades podem ser obtidos ligando para levels(factor(data$group))

Quais manipulações devem ser feitas com os dados para obter o resultado?

Yuriy Petrovskiy
fonte
as vírgulas no quadro de dados do resultado significam algo especial, ou é apenas o ponto decimal?
Mvctas 13/03/11
@mpiktas Obrigado por observar. Corrigido. Esses eram problemas de localidade (eu sou russo) - usamos vírgula para separação decimal.
Yuriy Petrovskiy
3
Eu suspeitava disso. Toda a Europa usa vírgula, exceto os britânicos.
Mvctas 13/03/11
4
Apesar de não ser britânico, prefiro ponto para separador decimal.
Roman Luštrik 14/03/11
1
Consulte aggregate, tapplye stackoverflow.com para quaisquer questões de codificação subsequentes deste tipo.
conjugateprior

Respostas:

140

Aqui está a variante plyr de uma linha usando o 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))

Aqui está outra variante de uma linha usando o novo pacote 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]

Este é mais rápido, embora seja perceptível apenas na tabela com 100 mil linhas. Tempos no meu Macbook Pro com processador Core 2 Duo de 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 

Economias adicionais são possíveis se usarmos 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
fonte
2
@chl, me deu a chance de experimentar este novo pacote data.table . Parece realmente promissor.
Mvctas
7
+6000 para tabela de dados. É realmente muito mais rápido que o ddply, mesmo para mim em conjuntos de dados menores que 100k (eu tenho um com apenas 20k linhas). Deve haver algo a ver com as funções que estou aplicando, mas o ddply levará minutos e a data.table por alguns segundos.
Atomicules
Erro de digitação simples: acho que você quis dizer em dt <- data.table(dtf)vez do dt <- data.table(dt)segundo bloco de código. Dessa forma, você está criando a tabela de dados a partir de um quadro de dados em vez da dtfunção do statspacote. Tentei editá-lo, mas não consigo fazer edições com menos de seis caracteres.
Christopher Bottoms
Na minha opinião (não humilde neste caso), data.tableé a melhor maneira de agregar dados e essa resposta é ótima, mas ainda assim arranha a superfície. Além de sintaticamente superior, também é extremamente flexível e possui muitos recursos avançados que envolvem junções e mecânica interna. Confira as perguntas frequentes, a página do github ou o curso para obter mais informações.
geneorama
97

Uma possibilidade é usar a função agregada . Por exemplo,

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

fornece a segunda coluna do resultado desejado.

ocram
fonte
1
Não conecte ao seu servidor de ajuda local :-) +1, mas veja meus comentários à resposta de @ steffen.
chl
Feito a coisa chamando, 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))mas eu não tenho certeza que é o caminho correto. Não tenho certeza do que acontecerá; os resultados das colunas vinculadas estarão em ordem diferente (acho que é possível). Qual é a sua opinião?
Yuriy Petrovskiy
9
@Yuriy As linhas não devem estar fora de ordem, mas aqui é uma maneira de fazê-lo uma chamada para aggregate():aggregate(age ~ group, data=dat, FUN = function(x) c(M=mean(x), SD=sd(x)))
lockedoff
@lockedoff: Obrigado por ter completado minha resposta!
Ocram
27

Como você está manipulando um quadro de dados, o dplyrpacote é provavelmente a maneira mais rápida de fazê-lo.

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 equivalente, usando o operador 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))

EDITAR pleno uso do operador de tubo:

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
fonte
3
+1 para dplyr. Isso tornou muitas tarefas R simples e muitos desses métodos obsoletos.
Gregmacfarlane
A plena utilização da versão operador pipe não funciona para mim, infelizmente
dagcilibili
você carregou dplyr ou magrittr?
Bastiaan Quast
muito obrigado @bquast por apontar para a solução, a função resume foi chamada de em plyrvez de dplyrqual estava causando o problema.
dagcilibili
12

Ótimo, obrigado bquast por adicionar a solução dplyr!

Acontece que então, dplyr e data.table estão muito próximos:

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 ainda é o mais rápido, seguido de perto por dplyr (), que curiosamente parece mais rápido no data.frame do que no 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
fonte
No começo, pensei que você precisava mover o setkey para o benchmark, mas acontece que isso quase não leva tempo.
Kasterma # 16/14
10

Além das sugestões existentes, você pode conferir a describe.byfunção no psychpacote.

Ele fornece várias estatísticas descritivas, incluindo a média e o desvio padrão com base em uma variável de agrupamento.

Jeromy Anglim
fonte
É bom, mas um pouco complicado de exportar para o LaTeX IME.
richiemorrisroe
10

Eu achei a função summaryByno pacote doBy a mais conveniente para isso:

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
fonte
9

Use o sqldfpacote. Isso permite que você agora use o SQL para resumir os dados. Depois de carregá-lo, você pode escrever algo como -

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

Editado: de acordo com as sugestões de chl

A função que você está procurando é chamada "tapply", que aplica uma função por grupo especificado por um fator.

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

Eu realmente sugiro trabalhar com um tutorial básico de R, explicando todas as estruturas e métodos de dados mais usados. Caso contrário, você ficará preso a cada centímetro durante a programação. Consulte esta pergunta para obter uma coleção de recursos disponíveis gratuitos.

Steffen
fonte
2
@ steffen +1, mas não há necessidade de um forloop aqui, você pode construir o seu quadro de dados em linha, IMO. Para a tapplychamada, use function(x) c(mean(x),sd(x)))e cbindo resultado, pois o OP solicitou as duas estatísticas. Além disso, a ddplypartir do pacote plyr poderia fazer isso sem problemas.
chl
@ steffen O problema é que eu preciso exatamente da estrutura da tabela que descrevi. Não há problema em obter meios e sd. O problema está na estrutura.
Yuriy Petrovskiy
@chl: Obrigado pelo seu comentário, não sabia sobre plyr :). Adicionei cbind, mas deixei o resto intocado. Que outro possa receber o crédito, essa resposta permanecerá como um exemplo menos ideal.
Steffen
@Yuriy: Adicionado cbind. Se você já sabia como aplicar funções por grupo, pode reformular sua pergunta (apenas para maior clareza;)).
22611 steffen
@steffen cbind("mean"=mperage,"stdev"=stperage) gives no 'group' column. Will be joining by cbind (grupo = níveis (fator (dados $ grupo)), "mean" = mperage, "stdev" = stperage) `correto?
Yuriy Petrovskiy
7

Aqui está um exemplo com a função que aggregates()eu mesmo fiz há algum tempo:

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

Fornece o seguinte resultado:

> 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

Talvez você possa obter o mesmo resultado a partir da função 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

Deixe-me voltar à saída da aggregatesfunção. Você pode transformá-la em uma bela tabela usando 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")) 

Isto dá:

> 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

Bonito, não é? Você pode exportar esta tabela para um pdf com a textplot()função do gplotspacote.

Veja aqui as soluções de outras pessoas.

Stéphane Laurent
fonte