web-dev-qa-db-fra.com

Résumer plusieurs colonnes avec dplyr?

Je me bats un peu avec la syntaxe dplyr. J'ai un bloc de données avec différentes variables et une variable de regroupement. Maintenant, je veux calculer la moyenne pour chaque colonne dans chaque groupe, en utilisant dplyr dans R.

df <- data.frame(
    a = sample(1:5, n, replace = TRUE), 
    b = sample(1:5, n, replace = TRUE), 
    c = sample(1:5, n, replace = TRUE), 
    d = sample(1:5, n, replace = TRUE), 
    grp = sample(1:3, n, replace = TRUE)
)
df %>% group_by(grp) %>% summarise(mean(a))

Cela me donne la moyenne pour la colonne "a" pour chaque groupe indiqué par "grp".

Ma question est la suivante: est-il possible d’obtenir les moyens pour chaque colonne à la fois dans chaque groupe? Ou dois-je répéter df %>% group_by(grp) %>% summarise(mean(a)) pour chaque colonne?

Ce que j'aimerais avoir, c'est quelque chose comme

df %>% group_by(grp) %>% summarise(mean(a:d)) # "mean(a:d)" does not work
138
Daniel

Le package dplyr contient summarise_all pour cet objectif:

df %>% group_by(grp) %>% summarise_all(funs(mean))
#> Source: local data frame [3 x 5]
#> 
#>     grp        a        b        c        d
#>   (int)    (dbl)    (dbl)    (dbl)    (dbl)
#> 1     1 3.000000 2.666667 2.666667 3.333333
#> 2     2 2.666667 2.666667 2.500000 2.833333
#> 3     3 4.000000 1.000000 4.000000 3.000000

Si vous souhaitez résumer uniquement certaines colonnes, utilisez les fonctions summarise_at ou summarise_if.

Sinon, le package purrrlyr fournit les mêmes fonctionnalités:

df %>% slice_rows("grp") %>% dmap(mean)
#> Source: local data frame [3 x 5]
#> 
#>     grp        a        b        c        d
#>   (int)    (dbl)    (dbl)    (dbl)    (dbl)
#> 1     1 3.000000 2.666667 2.666667 3.333333
#> 2     2 2.666667 2.666667 2.500000 2.833333
#> 3     3 4.000000 1.000000 4.000000 3.000000

N'oubliez pas non plus de data.table:

setDT(df)[, lapply(.SD, mean), by = grp]
#>    grp        a        b        c        d
#> 1:   3 3.714286 3.714286 2.428571 2.428571
#> 2:   1 1.000000 4.000000 5.000000 2.000000
#> 3:   2 4.000000 4.500000 3.000000 3.000000

Essayons de comparer les performances.

library(dplyr)
library(purrrlyr)
library(data.table)
library(benchr)
n <- 10000
df <- data.frame(
    a = sample(1:5, n, replace = TRUE), 
    b = sample(1:5, n, replace = TRUE), 
    c = sample(1:5, n, replace = TRUE), 
    d = sample(1:5, n, replace = TRUE), 
    grp = sample(1:3, n, replace = TRUE)
)
dt <- setDT(df)
benchmark(
    dplyr = df %>% group_by(grp) %>% summarise_all(funs(mean)),
    purrrlyr = df %>% slice_rows("grp") %>% dmap(mean),
    data.table = dt[, lapply(.SD, mean), by = grp]
)
#> Benchmark summary:
#> Time units : microseconds 
#>        expr n.eval  min lw.qu median mean up.qu   max  total relative
#>       dplyr    100 3490  3550   3710 3890  3780 15100 389000     6.98
#>    purrrlyr    100 2540  2590   2680 2920  2860 12000 292000     5.04
#>  data.table    100  459   500    531  563   571  1380  56300     1.00
241
Artem Klevtsov

Nous pouvons résumer en utilisant summarize_at, summarize_all et summarize_if sur dplyr 0.7.4. Nous pouvons définir les multiples colonnes et fonctions en utilisant les arguments vars et funs comme ci-dessous. La partie gauche de la formule de funs est affectée au suffixe des vars résumés. Dans le dplyr 0.7.4, summarise_each (et mutate_each) est déjà obsolète, nous ne pouvons donc pas utiliser ces fonctions.

options(scipen = 100, dplyr.width = Inf, dplyr.print_max = Inf)

library(dplyr)
packageVersion("dplyr")
# [1] ‘0.7.4’

set.seed(123)
df <- data_frame(
  a = sample(1:5, 10, replace=T), 
  b = sample(1:5, 10, replace=T), 
  c = sample(1:5, 10, replace=T), 
  d = sample(1:5, 10, replace=T), 
  grp = as.character(sample(1:3, 10, replace=T)) # For convenience, specify character type
)

df %>% group_by(grp) %>% 
  summarise_each(.vars = letters[1:4],
                 .funs = c(mean="mean"))
# `summarise_each()` is deprecated.
# Use `summarise_all()`, `summarise_at()` or `summarise_if()` instead.
# To map `funs` over a selection of variables, use `summarise_at()`
# Error: Strings must match column names. Unknown columns: mean

Vous devriez changer le code suivant. Les codes suivants ont tous le même résultat.

# summarise_at
df %>% group_by(grp) %>% 
  summarise_at(.vars = letters[1:4],
               .funs = c(mean="mean"))

df %>% group_by(grp) %>% 
  summarise_at(.vars = names(.)[1:4],
               .funs = c(mean="mean"))

df %>% group_by(grp) %>% 
  summarise_at(.vars = vars(a,b,c,d),
               .funs = c(mean="mean"))

# summarise_all
df %>% group_by(grp) %>% 
  summarise_all(.funs = c(mean="mean"))

# summarise_if
df %>% group_by(grp) %>% 
  summarise_if(.predicate = function(x) is.numeric(x),
               .funs = funs(mean="mean"))
# A tibble: 3 x 5
# grp a_mean b_mean c_mean d_mean
# <chr>  <dbl>  <dbl>  <dbl>  <dbl>
# 1     1   2.80   3.00    3.6   3.00
# 2     2   4.25   2.75    4.0   3.75
# 3     3   3.00   5.00    1.0   2.00

Vous pouvez également avoir plusieurs fonctions.

df %>% group_by(grp) %>% 
  summarise_at(.vars = letters[1:2],
               .funs = c(Mean="mean", Sd="sd"))
# A tibble: 3 x 5
# grp a_Mean b_Mean      a_Sd     b_Sd
# <chr>  <dbl>  <dbl>     <dbl>    <dbl>
# 1     1   2.80   3.00 1.4832397 1.870829
# 2     2   4.25   2.75 0.9574271 1.258306
# 3     3   3.00   5.00        NA       NA
49
Keiku

Vous pouvez simplement passer plus d'arguments à summarise:

df %>% group_by(grp) %>% summarise(mean(a), mean(b), mean(c), mean(d))

Source: trame de données locale [3 x 5]

  grp  mean(a)  mean(b)  mean(c) mean(d)
1   1 2.500000 3.500000 2.000000     3.0
2   2 3.800000 3.200000 3.200000     2.8
3   3 3.666667 3.333333 2.333333     3.0
34
Paul Hiemstra

Pour être complet: avec dplyr v0.2 ddply avec colwise fera également ceci:

> ddply(df, .(grp), colwise(mean))
  grp        a    b        c        d
1   1 4.333333 4.00 1.000000 2.000000
2   2 2.000000 2.75 2.750000 2.750000
3   3 3.000000 4.00 4.333333 3.666667

mais c'est plus lent, du moins dans ce cas:

> microbenchmark(ddply(df, .(grp), colwise(mean)), 
                  df %>% group_by(grp) %>% summarise_each(funs(mean)))
Unit: milliseconds
                                            expr      min       lq     mean
                ddply(df, .(grp), colwise(mean))     3.278002 3.331744 3.533835
 df %>% group_by(grp) %>% summarise_each(funs(mean)) 1.001789 1.031528 1.109337

   median       uq      max neval
 3.353633 3.378089 7.592209   100
 1.121954 1.133428 2.292216   100
6
Steven Matz

Tous les exemples sont excellents, mais je pense en ajouter un de plus pour montrer à quel point le travail dans un format "ordonné" simplifie les choses. Actuellement, le bloc de données est au format "large", ce qui signifie que les variables "a" à "d" sont représentées en colonnes. Pour obtenir un format "ordonné" (ou long), vous pouvez utiliser gather() du package tidyr qui décale les variables des colonnes "a" à "d" en lignes. Ensuite, vous utilisez les fonctions group_by() et summarize() pour obtenir la moyenne de chaque groupe. Si vous souhaitez présenter les données dans un format large, ajoutez simplement un appel supplémentaire à la fonction spread().


library(tidyverse)

# Create reproducible df
set.seed(101)
df <- tibble(a   = sample(1:5, 10, replace=T), 
             b   = sample(1:5, 10, replace=T), 
             c   = sample(1:5, 10, replace=T), 
             d   = sample(1:5, 10, replace=T), 
             grp = sample(1:3, 10, replace=T))

# Convert to tidy format using gather
df %>%
    gather(key = variable, value = value, a:d) %>%
    group_by(grp, variable) %>%
    summarize(mean = mean(value)) %>%
    spread(variable, mean)
#> Source: local data frame [3 x 5]
#> Groups: grp [3]
#> 
#>     grp        a     b        c        d
#> * <int>    <dbl> <dbl>    <dbl>    <dbl>
#> 1     1 3.000000   3.5 3.250000 3.250000
#> 2     2 1.666667   4.0 4.666667 2.666667
#> 3     3 3.333333   3.0 2.333333 2.333333
4
Matt Dancho