Calcular a média por grupo

Eu tenho um grande quadro de dados semelhante a este:

df  df dive speed 1 dive1 0.80668490 2 dive1 0.53349584 3 dive2 0.07571784 4 dive2 0.39518628 5 dive1 0.84557955 6 dive1 0.69121443 7 dive1 0.38124950 8 dive2 0.22536126 9 dive1 0.04704750 10 dive2 0.93561651 

Meu objective é calcular a média dos valores de uma coluna quando outra coluna for igual a um determinado valor e repetir isso para todos os valores. Ou seja, no exemplo acima, eu gostaria de retornar uma média para a speed da coluna para cada valor único do dive coluna. Então, quando dive==dive1 , a média da speed é essa e assim por diante para cada valor de dive .

Há muitas maneiras de fazer isso em R. Especificamente, by , aggregate , split e plyr , cast , tapply , data.table , dplyr e assim por diante.

De um modo geral, esses problemas são da forma split-apply-combine. Hadley Wickham escreveu um belo artigo que lhe dará uma visão mais profunda de toda a categoria de problemas, e vale a pena lê-lo. Seu pacote plyr implementa a estratégia para estruturas de dados gerais e dplyr é um novo desempenho de implementação ajustado para frameworks de dados. Eles permitem resolver problemas da mesma forma, mas de complexidade ainda maior que esta. Eles valem a pena aprender como uma ferramenta geral para resolver problemas de manipulação de dados.

O desempenho é um problema em conjuntos de dados muito grandes e, para isso, é difícil superar as soluções com base em data.table . Se você lida apenas com conjuntos de dados de tamanho médio ou menores, no entanto, data.table tempo para aprender data.table é provável que não valha a pena. dplyr também pode ser rápido, então é uma boa escolha se você quer acelerar as coisas, mas não precisa da escalabilidade dos data.table .

Muitas das outras soluções abaixo não exigem pacotes adicionais. Alguns deles são até razoavelmente rápidos em conjuntos de dados médios e grandes. Sua principal desvantagem é a metáfora ou a flexibilidade. Por metáfora, quero dizer que é uma ferramenta projetada para que outra coisa seja coagida a resolver esse tipo específico de problema de uma maneira “inteligente”. Por flexibilidade, quero dizer, falta-lhes a capacidade de resolver uma gama tão ampla de problemas semelhantes ou de produzir com facilidade uma saída ordenada.


Exemplos

funções base

tapply :

 tapply(df$speed, df$dive, mean) # dive1 dive2 # 0.5419921 0.5103974 

aggregate :

aggregate recebe em data.frames, gera data.frames e usa uma interface de fórmula.

 aggregate( speed ~ dive, df, mean ) # dive speed # 1 dive1 0.5790946 # 2 dive2 0.4864489 

by :

Em sua forma mais amigável, ele usa vetores e aplica uma function a eles. No entanto, sua saída não está em uma forma muito manipulável:

 res.by < - by(df$speed, df$dive, mean) res.by # df$dive: dive1 # [1] 0.5790946 # --------------------------------------- # df$dive: dive2 # [1] 0.4864489 

Para contornar isso, para usos simples do método as.data.frame na biblioteca taRifx funciona:

 library(taRifx) as.data.frame(res.by) # IDX1 value # 1 dive1 0.6736807 # 2 dive2 0.4051447 

split :

Como o nome sugere, ele executa apenas a parte "dividida" da estratégia de divisão de combinação de aplicação. Para fazer o resto funcionar, vou escrever uma pequena function que usa sapply para aplicar-combinar. sapply simplifica automaticamente o resultado, tanto quanto possível. No nosso caso, isso significa um vetor em vez de um data.frame, pois temos apenas uma dimensão de resultados.

 splitmean < - function(df) { s <- split( df, df$dive) sapply( s, function(x) mean(x$speed) ) } splitmean(df) # dive1 dive2 # 0.5790946 0.4864489 

Pacotes Externos

data.table :

 library(data.table) setDT(df)[ , .(mean_speed = mean(speed)), by = dive] # dive mean_speed # 1: dive1 0.5419921 # 2: dive2 0.5103974 

dplyr :

 library(dplyr) group_by(df, dive) %>% summarize(m = mean(speed)) 

plyr (o pré-cursor do dplyr )

Aqui está o que a página oficial tem a dizer sobre plyr :

Já é possível fazer isso com funções base R (como split e a família de funções plyr ), mas o plyr torna tudo um pouco mais fácil com:

  • nomes, argumentos e resultados totalmente consistentes
  • paralelização conveniente através do pacote foreach
  • input e saída para data.frames, matrizes e listas
  • barras de progresso para acompanhar as operações de longa duração
  • recuperação de erros incorporada e mensagens de erro informativas
  • labels que são mantidos em todas as transformações

Em outras palavras, se você aprender uma ferramenta para a manipulação split-apply-combine, ela deve ser plyr .

 library(plyr) res.plyr < - ddply( df, .(dive), function(x) mean(x$speed) ) res.plyr # dive V1 # 1 dive1 0.5790946 # 2 dive2 0.4864489 

reshape2 :

A biblioteca reshape2 não foi projetada com split-apply-combine como foco principal. Em vez disso, ele usa uma estratégia de fundição / fundição de duas partes para executar uma ampla variedade de tarefas de redefinição de dados . No entanto, uma vez que permite uma function de agregação, ela pode ser usada para esse problema. Não seria minha primeira escolha para operações de divisão de combinação de aplicação, mas seus resources de remodelação são poderosos e, portanto, você deve aprender esse pacote também.

 library(reshape2) dcast( melt(df), variable ~ dive, mean) # Using dive as id variables # variable dive1 dive2 # 1 speed 0.5790946 0.4864489 

Referências

10 linhas, 2 grupos

 library(microbenchmark) m1 < - microbenchmark( by( df$speed, df$dive, mean), aggregate( speed ~ dive, df, mean ), splitmean(df), ddply( df, .(dive), function(x) mean(x$speed) ), dcast( melt(df), variable ~ dive, mean), dt[, mean(speed), by = dive], summarize( group_by(df, dive), m = mean(speed) ), summarize( group_by(dt, dive), m = mean(speed) ) ) > print(m1, signif = 3) Unit: microseconds expr min lq mean median uq max neval cld by(df$speed, df$dive, mean) 302 325 343.9 342 362 396 100 b aggregate(speed ~ dive, df, mean) 904 966 1012.1 1020 1060 1130 100 e splitmean(df) 191 206 249.9 220 232 1670 100 a ddply(df, .(dive), function(x) mean(x$speed)) 1220 1310 1358.1 1340 1380 2740 100 f dcast(melt(df), variable ~ dive, mean) 2150 2330 2440.7 2430 2490 4010 100 h dt[, mean(speed), by = dive] 599 629 667.1 659 704 771 100 c summarize(group_by(df, dive), m = mean(speed)) 663 710 774.6 744 782 2140 100 d summarize(group_by(dt, dive), m = mean(speed)) 1860 1960 2051.0 2020 2090 3430 100 g autoplot(m1) 

benchmark 10 linhas

Como de costume, o data.table tem um pouco mais de sobrecarga, portanto, vem em média para pequenos conjuntos de dados. Estes são microssegundos, portanto, as diferenças são triviais. Qualquer uma das abordagens funciona bem aqui e você deve escolher com base em:

  • O que você já está familiarizado ou quer estar familiarizado ( plyr sempre vale a pena aprender por sua flexibilidade; data.table vale a pena aprender se você planeja analisar grandes conjuntos de dados; e aggregate e split são todas as funções R básicas e, portanto, universalmente acessível)
  • Qual saída ele retorna (numérico, data.frame ou data.table - o último dos quais herda de data.frame)

10 milhões de linhas, 10 grupos

Mas e se tivermos um grande dataset? Vamos tentar 10 ^ 7 linhas divididas em dez grupos.

 df < - data.frame(dive=factor(sample(letters[1:10],10^7,replace=TRUE)),speed=runif(10^7)) dt <- data.table(df) setkey(dt,dive) m2 <- microbenchmark( by( df$speed, df$dive, mean), aggregate( speed ~ dive, df, mean ), splitmean(df), ddply( df, .(dive), function(x) mean(x$speed) ), dcast( melt(df), variable ~ dive, mean), dt[,mean(speed),by=dive], times=2 ) > print(m2, signif = 3) Unit: milliseconds expr min lq mean median uq max neval cld by(df$speed, df$dive, mean) 720 770 799.1 791 816 958 100 d aggregate(speed ~ dive, df, mean) 10900 11000 11027.0 11000 11100 11300 100 h splitmean(df) 974 1040 1074.1 1060 1100 1280 100 e ddply(df, .(dive), function(x) mean(x$speed)) 1050 1080 1110.4 1100 1130 1260 100 f dcast(melt(df), variable ~ dive, mean) 2360 2450 2492.8 2490 2520 2620 100 g dt[, mean(speed), by = dive] 119 120 126.2 120 122 212 100 a summarize(group_by(df, dive), m = mean(speed)) 517 521 531.0 522 532 620 100 c summarize(group_by(dt, dive), m = mean(speed)) 154 155 174.0 156 189 321 100 b autoplot(m2) 

linhas 1e7 de referência, 10 grupos

Em seguida, o data.table ou o dplyr usando a operação em data.table é claramente o caminho a percorrer. Certas abordagens ( aggregate e dcast ) estão começando a parecer muito lentas.

10 milhões de linhas, 1.000 grupos

Se você tem mais grupos, a diferença se torna mais pronunciada. Com 1.000 grupos e as mesmas 10 ^ 7 linhas:

 df < - data.frame(dive=factor(sample(seq(1000),10^7,replace=TRUE)),speed=runif(10^7)) dt <- data.table(df) setkey(dt,dive) # then run the same microbenchmark as above print(m3, signif = 3) Unit: milliseconds expr min lq mean median uq max neval cld by(df$speed, df$dive, mean) 776 791 816.2 810 828 925 100 b aggregate(speed ~ dive, df, mean) 11200 11400 11460.2 11400 11500 12000 100 f splitmean(df) 5940 6450 7562.4 7470 8370 11200 100 e ddply(df, .(dive), function(x) mean(x$speed)) 1220 1250 1279.1 1280 1300 1440 100 c dcast(melt(df), variable ~ dive, mean) 2110 2190 2267.8 2250 2290 2750 100 d dt[, mean(speed), by = dive] 110 111 113.5 111 113 143 100 a summarize(group_by(df, dive), m = mean(speed)) 625 630 637.1 633 644 701 100 b summarize(group_by(dt, dive), m = mean(speed)) 129 130 137.3 131 142 213 100 a autoplot(m3) 

insira a descrição da imagem aqui

Portanto, o data.table continua sendo bem dimensionado e o dplyr opera em um data.table também funciona bem, com dplyr em data.frame perto de uma ordem de magnitude mais lenta. A estratégia de split / sapply parece ter uma escala fraca no número de grupos (o que significa que a split() provavelmente é lenta e a sapply é rápida). by continua a ser relativamente eficiente - em 5 segundos, é definitivamente perceptível para o usuário, mas para um dataset tão grande ainda não razoável. Ainda assim, se você está trabalhando rotineiramente com conjuntos de dados deste porte, o data.table é claramente o caminho a percorrer - 100% de dados para o melhor desempenho ou dplyr com dplyr usando data.table como alternativa viável.

 aggregate(speed~dive,data=df,FUN=mean) dive speed 1 dive1 0.7059729 2 dive2 0.5473777 

Atualização de 2015 com dplyr:

 df %>% group_by(dive) %>% summarise(percentage = mean(speed)) Source: local data frame [2 x 2] dive percentage 1 dive1 0.4777462 2 dive2 0.6726483