109 votes

Appliquer plusieurs fonctions de synthèse sur plusieurs variables par groupe en un seul appel

J'ai le cadre de données suivant

x <- read.table(text = "  id1 id2 val1 val2
1   a   x    1    9
2   a   x    2    4
3   a   y    3    5
4   a   y    4    9
5   b   x    1    7
6   b   y    4    4
7   b   x    3    9
8   b   y    2    8", header = TRUE)

Je veux calculer la moyenne de val1 et val2 groupés par id1 et id2, et simultanément compter le nombre de lignes pour chaque combinaison id1-id2. Je peux effectuer chaque calcul séparément :

# calculate mean
aggregate(. ~ id1 + id2, data = x, FUN = mean)

# count rows
aggregate(. ~ id1 + id2, data = x, FUN = length)

Afin de faire les deux calculs en un seul appel, j'ai essayé

do.call("rbind", aggregate(. ~ id1 + id2, data = x, FUN = function(x) data.frame(m = mean(x), n = length(x))))

Cependant, j'obtiens une sortie brouillée ainsi qu'un avertissement :

#     m   n
# id1 1   2
# id2 1   1
#     1.5 2
#     2   2
#     3.5 2
#     3   2
#     6.5 2
#     8   2
#     7   2
#     6   2
# Warning message:
#   In rbind(id1 = c(1L, 2L, 1L, 2L), id2 = c(1L, 1L, 2L, 2L), val1 = list( :
#   number of columns of result is not a multiple of vector length (arg 1)

Je pourrais utiliser le paquet plyr, mais mon ensemble de données est assez grand et plyr est très lent (presque inutilisable) lorsque la taille de l'ensemble de données augmente.

Comment puis-je utiliser aggregate ou d'autres fonctions pour effectuer plusieurs calculs en un seul appel ?

182voto

BondedDust Points 105234

Vous pouvez faire tout cela en une seule étape et obtenir un étiquetage approprié :

> aggregate(. ~ id1+id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) )
#   id1 id2 val1.mn val1.n val2.mn val2.n
# 1   a   x     1.5    2.0     6.5    2.0
# 2   b   x     2.0    2.0     8.0    2.0
# 3   a   y     3.5    2.0     7.0    2.0
# 4   b   y     3.0    2.0     6.0    2.0

Cela crée un cadre de données avec deux colonnes d'identification et deux colonnes de matrice :

str( aggregate(. ~ id1+id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) ) )
'data.frame':   4 obs. of  4 variables:
 $ id1 : Factor w/ 2 levels "a","b": 1 2 1 2
 $ id2 : Factor w/ 2 levels "x","y": 1 1 2 2
 $ val1: num [1:4, 1:2] 1.5 2 3.5 3 2 2 2 2
  ..- attr(*, "dimnames")=List of 2
  .. ..$ : NULL
  .. ..$ : chr  "mn" "n"
 $ val2: num [1:4, 1:2] 6.5 8 7 6 2 2 2 2
  ..- attr(*, "dimnames")=List of 2
  .. ..$ : NULL
  .. ..$ : chr  "mn" "n"

Comme le souligne @lord.garbage ci-dessous, il est possible de convertir ces données en un cadre de données avec des colonnes "simples" en utilisant la méthode suivante do.call(data.frame, ...)

str( do.call(data.frame, aggregate(. ~ id1+id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) ) ) 
    )
'data.frame':   4 obs. of  6 variables:
 $ id1    : Factor w/ 2 levels "a","b": 1 2 1 2
 $ id2    : Factor w/ 2 levels "x","y": 1 1 2 2
 $ val1.mn: num  1.5 2 3.5 3
 $ val1.n : num  2 2 2 2
 $ val2.mn: num  6.5 8 7 6
 $ val2.n : num  2 2 2 2

C'est la syntaxe pour les variables multiples sur la LHS :

aggregate(cbind(val1, val2) ~ id1 + id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) )

31voto

Matt Dowle Points 20936

Étant donné que dans la question :

Je pourrais utiliser le paquet plyr, mais mon ensemble de données est assez grand et plyr est très lent (presque inutilisable) lorsque la taille de l'ensemble de données augmente.

Ensuite, dans data.table ( 1.9.4+ ) vous pouvez essayer :

> DT
   id1 id2 val1 val2
1:   a   x    1    9
2:   a   x    2    4
3:   a   y    3    5
4:   a   y    4    9
5:   b   x    1    7
6:   b   y    4    4
7:   b   x    3    9
8:   b   y    2    8

> DT[ , .(mean(val1), mean(val2), .N), by = .(id1, id2)]   # simplest
   id1 id2  V1  V2 N
1:   a   x 1.5 6.5 2
2:   a   y 3.5 7.0 2
3:   b   x 2.0 8.0 2
4:   b   y 3.0 6.0 2

> DT[ , .(val1.m = mean(val1), val2.m = mean(val2), count = .N), by = .(id1, id2)]  # named
   id1 id2 val1.m val2.m count
1:   a   x    1.5    6.5     2
2:   a   y    3.5    7.0     2
3:   b   x    2.0    8.0     2
4:   b   y    3.0    6.0     2

> DT[ , c(lapply(.SD, mean), count = .N), by = .(id1, id2)]   # mean over all columns
   id1 id2 val1 val2 count
1:   a   x  1.5  6.5     2
2:   a   y  3.5  7.0     2
3:   b   x  2.0  8.0     2
4:   b   y  3.0  6.0     2

Pour la comparaison des horaires aggregate (utilisé dans la question et dans les 3 autres réponses) à data.table voir cette référence (le agg y agg.x cas).

16voto

Jaap Points 3814

Utilisation de la dplyr vous pouvez y parvenir en utilisant summarise_all . Avec cette fonction de synthèse, vous pouvez appliquer d'autres fonctions (dans le cas présent mean y n() ) à chacune des colonnes non groupées :

x %>%
  group_by(id1, id2) %>%
  summarise_all(funs(mean, n()))

ce qui donne :

     id1    id2 val1_mean val2_mean val1_n val2_n
1      a      x       1.5       6.5      2      2
2      a      y       3.5       7.0      2      2
3      b      x       2.0       8.0      2      2
4      b      y       3.0       6.0      2      2

Si vous ne souhaitez pas appliquer la (les) fonction(s) à toutes les colonnes non groupées, vous spécifiez les colonnes auxquelles elles doivent être appliquées ou en excluant les non désirées avec un moins en utilisant la fonction summarise_at() fonction :

# inclusion
x %>%
  group_by(id1, id2) %>%
  summarise_at(vars(val1, val2), funs(mean, n()))

# exclusion
x %>%
  group_by(id1, id2) %>%
  summarise_at(vars(-val2), funs(mean, n()))

12voto

flodel Points 41487

Vous pourriez ajouter un count l'agrégat avec sum puis réduire pour obtenir le mean :

x$count <- 1
agg <- aggregate(. ~ id1 + id2, data = x,FUN = sum)
agg
#   id1 id2 val1 val2 count
# 1   a   x    3   13     2
# 2   b   x    4   16     2
# 3   a   y    7   14     2
# 4   b   y    6   12     2

agg[c("val1", "val2")] <- agg[c("val1", "val2")] / agg$count
agg
#   id1 id2 val1 val2 count
# 1   a   x  1.5  6.5     2
# 2   b   x  2.0  8.0     2
# 3   a   y  3.5  7.0     2
# 4   b   y  3.0  6.0     2

Elle a l'avantage de préserver les noms de vos colonnes et de créer une seule count colonne.

10voto

neilfws Points 3881

Vous voulez peut-être fusionner ?

x.mean <- aggregate(. ~ id1+id2, p, mean)
x.len  <- aggregate(. ~ id1+id2, p, length)

merge(x.mean, x.len, by = c("id1", "id2"))

  id1 id2 val1.x val2.x val1.y val2.y
1   a   x    1.5    6.5      2      2
2   a   y    3.5    7.0      2      2
3   b   x    2.0    8.0      2      2
4   b   y    3.0    6.0      2      2

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X