web-dev-qa-db-fra.com

Grouper par plusieurs colonnes dans dplyr, en utilisant l'entrée de vecteur de chaîne

J'essaie de transférer ma compréhension de plyr dans dplyr, mais je ne vois pas comment grouper par plusieurs colonnes.

# make data with weird column names that can't be hard coded
data = data.frame(
  asihckhdoydkhxiydfgfTgdsx = sample(LETTERS[1:3], 100, replace=TRUE),
  a30mvxigxkghc5cdsvxvyv0ja = sample(LETTERS[1:3], 100, replace=TRUE),
  value = rnorm(100)
)

# get the columns we want to average within
columns = names(data)[-3]

# plyr - works
ddply(data, columns, summarize, value=mean(value))

# dplyr - raises error
data %.%
  group_by(columns) %.%
  summarise(Value = mean(value))
#> Error in eval(expr, envir, enclos) : index out of bounds

Que manque-t-il pour traduire l'exemple plyr en une syntaxe dplyr-esque?

Edit 2017: Dplyr a été mis à jour, une solution plus simple est donc disponible. Voir la réponse actuellement sélectionnée.

143
sharoz

Depuis que cette question a été postée, dplyr a ajouté des versions étendues de group_by ( documentation ici ). Cela vous permet d'utiliser les mêmes fonctions que vous utiliseriez avec select, comme ceci:

data = data.frame(
    asihckhdoydkhxiydfgfTgdsx = sample(LETTERS[1:3], 100, replace=TRUE),
    a30mvxigxkghc5cdsvxvyv0ja = sample(LETTERS[1:3], 100, replace=TRUE),
    value = rnorm(100)
)

# get the columns we want to average within
columns = names(data)[-3]

library(dplyr)
df1 <- data %>%
  group_by_at(vars(one_of(columns))) %>%
  summarize(Value = mean(value))

#compare plyr for reference
df2 <- plyr::ddply(data, columns, plyr::summarize, value=mean(value))
table(df1 == df2, useNA = 'ifany')
## TRUE 
##  27 

Le résultat de votre exemple de question est celui attendu (voir la comparaison avec plyr ci-dessus et le résultat ci-dessous):

# A tibble: 9 x 3
# Groups:   asihckhdoydkhxiydfgfTgdsx [?]
  asihckhdoydkhxiydfgfTgdsx a30mvxigxkghc5cdsvxvyv0ja       Value
                     <fctr>                    <fctr>       <dbl>
1                         A                         A  0.04095002
2                         A                         B  0.24943935
3                         A                         C -0.25783892
4                         B                         A  0.15161805
5                         B                         B  0.27189974
6                         B                         C  0.20858897
7                         C                         A  0.19502221
8                         C                         B  0.56837548
9                         C                         C -0.22682998

Notez que, puisque dplyr::summarize ne supprime qu'une couche de groupe à la fois, il reste encore un groupe dans le tibble résultant (qui peut parfois surprendre des personnes par la suite). Si vous voulez être absolument à l'abri d'un comportement de regroupement inattendu, vous pouvez toujours ajouter %>% ungroup à votre pipeline après la synthèse.

40
Empiromancer

Juste pour écrire le code en entier, voici une mise à jour de la réponse de Hadley avec la nouvelle syntaxe:

library(dplyr)

df <-  data.frame(
    asihckhdoydk = sample(LETTERS[1:3], 100, replace=TRUE),
    a30mvxigxkgh = sample(LETTERS[1:3], 100, replace=TRUE),
    value = rnorm(100)
)

# Columns you want to group by
grp_cols <- names(df)[-3]

# Convert character vector to list of symbols
dots <- lapply(grp_cols, as.symbol)

# Perform frequency counts
df %>%
    group_by_(.dots=dots) %>%
    summarise(n = n())

sortie:

Source: local data frame [9 x 3]
Groups: asihckhdoydk

  asihckhdoydk a30mvxigxkgh  n
1            A            A 10
2            A            B 10
3            A            C 13
4            B            A 14
5            B            B 10
6            B            C 12
7            C            A  9
8            C            B 12
9            C            C 10
100
James Owers

Le support pour cela dans dplyr est actuellement assez faible, je pense que la syntaxe ressemblera à quelque chose comme:

df %.% group_by(.groups = c("asdfgfTgdsx", "asdfk30v0ja"))

Mais ce ne sera probablement pas là pendant un moment (parce que je dois réfléchir à toutes les conséquences).

En attendant, vous pouvez utiliser regroup(), qui prend une liste de symboles:

library(dplyr)

df <-  data.frame(
  asihckhdoydk = sample(LETTERS[1:3], 100, replace=TRUE),
  a30mvxigxkgh = sample(LETTERS[1:3], 100, replace=TRUE),
  value = rnorm(100)
)

df %.%
  regroup(list(quote(asihckhdoydk), quote(a30mvxigxkgh))) %.%
  summarise(n = n())

Si vous avez un vecteur de caractère composé de noms de colonnes, vous pouvez les convertir en structure correcte avec lapply() et as.symbol():

vars <- setdiff(names(df), "value")
vars2 <- lapply(vars, as.symbol)

df %.% regroup(vars2) %.% summarise(n = n())
57
hadley

La spécification de chaîne des colonnes dans dplyr est maintenant prise en charge via des variantes des fonctions dplyr avec des noms se terminant par un trait de soulignement. Par exemple, correspondant à la fonction group_by, il existe une fonction group_by_ pouvant accepter des arguments de chaîne. Cette vignette décrit la syntaxe de ces fonctions en détail.

Le fragment de code suivant résout proprement le problème que @sharoz a posé à l'origine (notez la nécessité d'écrire l'argument .dots):

# Given data and columns from the OP

data %>%
    group_by_(.dots = columns) %>%
    summarise(Value = mean(value))

(Notez que dplyr utilise maintenant l'opérateur %>% et que %.% est obsolète).

25
edward

Jusqu'à ce que dplyr prenne en charge les arguments de chaîne, peut-être que ce Gist est utile:

https://Gist.github.com/skranz/9681509

Il contient un tas de fonctions d'encapsulage telles que s_group_by, s_mutate, s_filter, etc. qui utilisent des arguments de chaîne. Vous pouvez les mélanger avec les fonctions normales de Dplyr. Par exemple

cols = c("cyl","gear")
mtcars %.%
  s_group_by(cols) %.%  
  s_summarise("avdisp=mean(disp), max(disp)") %.%
  arrange(avdisp)
17
Sebastian Kranz

Cela fonctionne si vous transmettez les objets (enfin, vous ne l'êtes pas, mais ...) plutôt que sous forme de vecteur de caractère:

df %.%
    group_by(asdfgfTgdsx, asdfk30v0ja) %.%
    summarise(Value = mean(value))

> df %.%
+   group_by(asdfgfTgdsx, asdfk30v0ja) %.%
+   summarise(Value = mean(value))
Source: local data frame [9 x 3]
Groups: asdfgfTgdsx

  asdfgfTgdsx asdfk30v0ja        Value
1           A           C  0.046538002
2           C           B -0.286359899
3           B           A -0.305159419
4           C           A -0.004741504
5           B           B  0.520126476
6           C           C  0.086805492
7           B           C -0.052613078
8           A           A  0.368410146
9           A           B  0.088462212

df était votre data.

?group_by dit:

 ...: variables to group by. All tbls accept variable names, some
      will also accept functons of variables. Duplicated groups
      will be silently dropped.

ce que j’interprète comme signifiant non pas les versions de caractères des noms, mais la façon dont vous les désigneriez dans foo$bar; bar n'est pas cité ici. Ou comment vous feriez référence à des variables dans une formule: foo ~ bar.

@Arun mentionne également que vous pouvez faire:

df %.%
    group_by("asdfgfTgdsx", "asdfk30v0ja") %.%
    summarise(Value = mean(value))

Mais vous ne pouvez pas transmettre quelque chose qui non évalué n'est pas le nom d'une variable dans l'objet de données.

Je suppose que cela est dû aux méthodes internes que Hadley utilise pour rechercher les éléments que vous transmettez via l'argument ....

11
Gavin Simpson
data = data.frame(
  my.a = sample(LETTERS[1:3], 100, replace=TRUE),
  my.b = sample(LETTERS[1:3], 100, replace=TRUE),
  value = rnorm(100)
)

group_by(data,newcol=paste(my.a,my.b,sep="_")) %>% summarise(Value=mean(value))
4
Jordan

Un cas (minuscule) qui manque dans les réponses ici, que je voulais expliciter, est lorsque les variables à grouper sont générées de manière dynamique à mi-chemin dans un pipeline:

library(wakefield)
df_foo = r_series(rnorm, 10, 1000)
df_foo %>% 
  # 1. create quantized versions of base variables
  mutate_each(
    funs(Quantized = . > 0)
  ) %>% 
  # 2. group_by the indicator variables
  group_by_(
    .dots = grep("Quantized", names(.), value = TRUE)
    ) %>% 
  # 3. summarize the base variables
  summarize_each(
    funs(sum(., na.rm = TRUE)), contains("X_")
  )

Cela montre essentiellement comment utiliser grep conjointement avec group_by_(.dots = ...) pour y parvenir.

3
tchakravarty

Exemple général d'utilisation de l'argument .dots comme entrée de vecteur de caractères dans la fonction dplyr::group_by:

iris %>% 
    group_by(.dots ="Species") %>% 
    summarise(meanpetallength = mean(Petal.Length))

Ou sans nom codé en dur pour la variable de regroupement (comme demandé par l'OP):

iris %>% 
    group_by(.dots = names(iris)[5]) %>% 
    summarise_at("Petal.Length", mean)

Avec l'exemple du PO:

data %>% 
    group_by(.dots =names(data)[-3]) %>% 
    summarise_at("value", mean)

Voir aussi la vignette de dplyr sur la programmation qui explique les pronoms, la quasiquotation, les quosures et le bon ordre.

1
Paul Rougieux