Raggruppa per più colonne in dplyr, utilizzando l'input vettoriale stringa


157

Sto cercando di trasferire la mia comprensione di plyr in dplyr, ma non riesco a capire come raggruppare per più colonne.

# 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

Cosa mi manca per tradurre l'esempio plyr in una sintassi dplyr-esque?

Modifica 2017 : Dplyr è stato aggiornato, quindi è disponibile una soluzione più semplice. Vedi la risposta attualmente selezionata.


3
Sono appena arrivato perché era il top di Google. È possibile utilizzare group_by_ora spiegato invignette("nse")
James Owers il

3
@kungfujam: sembra raggrupparsi solo per la prima colonna, non per la coppia di colonne
sharoz,

1
È necessario utilizzare .dots. Ecco la soluzione adattata dalla risposta di @hadley di seguito:df %>% group_by_(.dots=list(quote(asihckhdoydk), quote(a30mvxigxkgh))) %>% summarise(n = n())
James Owers,

1
Ho inserito il codice completo in una risposta di seguito
James Owers,

1
Come qualcuno ha sottolineato in una risposta al commento, l'obiettivo è quello di non richiedere nomi di colonne codificate.
Sharoz,

Risposte:


52

Da quando questa domanda è stata pubblicata, dplyr ha aggiunto versioni con ambito di group_by( documentazione qui ). Ciò ti consente di utilizzare le stesse funzioni che utilizzeresti select, in questo modo:

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 

L'output della tua domanda di esempio è come previsto (vedi confronto con plyr sopra e output sotto):

# 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

Si noti che poiché dplyr::summarizesi toglie solo uno strato di raggruppamento alla volta, è ancora in corso un raggruppamento nella tabella risultante (che a volte può catturare le persone sorpreso più avanti lungo la linea). Se si desidera essere assolutamente al sicuro da comportamenti di raggruppamento imprevisti, è sempre possibile aggiungere %>% ungroupalla pipeline dopo il riepilogo.


si aggiorna per 0.7.0rendere disponibile anche il sistema di quotazioni non quotate con più colonne?
JelenaČuklina,

4
È inoltre possibile utilizzare gli .dotsargomenti per group_by()quanto tale: data %>% group_by(.dots = columns) %>% summarize(value = mean(value)).
Paul Rougieux,

La chiamata one_of()fa qualcosa qui? Penso che sia ridondante in questo contesto, poiché l'espressione è racchiusa in una chiamata a vars().
knowah

@Khashir sì, questa risposta funziona ancora @knowah Hai ragione, la chiamata a one_of()è ridondante in questo contesto
Empiromancer

1
@Sos Per applicare una funzione su più colonne usando la selectsintassi, vedere la nuova acrossfunzione: dplyr.tidyverse.org/reference/across.html Nel tuo caso, sarebbe simile asummarize(across(all_of(c(''value_A", "value_B")), mean))
Empiromancer

102

Proprio per scrivere il codice per intero, ecco un aggiornamento sulla risposta di Hadley con la nuova sintassi:

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

produzione:

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

1
Questo sembra essere ancora hardcoding dei nomi delle colonne, solo in una formula. Il punto della domanda è come usare le stringhe per non dover digitare asihckhdoydk...
Gregor Thomas

1
Hai aggiornato la soluzione usando dots <- lapply(names(df)[-3], function(x) as.symbol(x))per creare l' .dotsargomento
James Owers,

4
cercare di ordinare queste risposte è .dots=stato il passo cruciale. se qualcuno ha una buona conoscenza del perché richiesto nella group_bychiamata, puoi modificare questa risposta? in questo momento è un po 'imperscrutabile.
Andrew,

12
vignette("nse")indica che ci sono tre modi per citare che sono accettabili: formula, citazione e carattere. A meno che tu non sia preoccupato per quale ambiente trarrà, probabilmente puoi group_by_(.dots=grp_cols)
cavartela

58

Il supporto per questo in dplyr è attualmente piuttosto debole, alla fine penso che la sintassi sarà qualcosa del tipo:

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

Ma probabilmente non ci sarà per un po '(perché ho bisogno di riflettere su tutte le conseguenze).

Nel frattempo, puoi usare regroup(), che accetta un elenco di simboli:

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

Se hai un vettore di caratteri con nomi di colonne, puoi convertirli nella struttura corretta con lapply()e as.symbol():

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

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

6
as.symbollo risolve. Grazie! Nel caso in cui aiuti con lo sviluppo: questo scenario è molto comune per me. Aggrega un risultato numerico su ogni combinazione delle altre variabili.
Sharoz,

apparentemente questo funziona solo per questo esempio particolare e nessun altro.
Paulo E. Cardoso,

3
Inizialmente l'ho contrassegnata come risposta, ma gli aggiornamenti a dplyr consentono al lavoro della risposta di kungfujam.
Sharoz,

regroupè anche deprecato (almeno dalla versione 0.4.3).
Berk U.

27

Le specifiche delle stringhe delle colonne dplyrsono ora supportate attraverso varianti delle dplyrfunzioni con nomi che finiscono in un trattino basso. Ad esempio, in corrispondenza della group_byfunzione esiste una group_by_funzione che può accettare argomenti stringa. Questa vignetta descrive in dettaglio la sintassi di queste funzioni.

Il frammento seguente risolve in modo chiaro il problema originariamente posto da @sharoz (nota la necessità di scrivere l' .dotsargomento):

# Given data and columns from the OP

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

(Si noti che dplyr ora utilizza l' %>%operatore ed %.%è obsoleto).


17

Fino a quando dplyr ha il pieno supporto per gli argomenti di stringa, forse questa sintesi è utile:

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

Contiene un sacco di funzioni wrapper come s_group_by, s_mutate, s_filter, ecc. Che usano argomenti stringa. Puoi mescolarli con le normali funzioni dplyr. Per esempio

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

11

Funziona se gli passi gli oggetti (beh, non lo sei, ma ...) piuttosto che come un personaggio vettoriale:

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

dov'era il dftuo data.

?group_by dice:

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

che interpreto per indicare non le versioni dei personaggi dei nomi, ma il modo in cui ti riferiresti a loro foo$bar; barnon è citato qui. O come ci si fa riferimento a variabili in una formula: foo ~ bar.

@Arun menziona anche che puoi fare:

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

Ma non puoi passare qualcosa che non valutato non è il nome di una variabile nell'oggetto dati.

Presumo che ciò sia dovuto ai metodi interni che Hadley sta usando per cercare le cose che passi attraverso l' ...argomento.


1
@Arun Grazie per quello. Non me ne ero accorto, ma ha anche senso. Ho aggiunto una nota a questo proposito, citando te e il tuo commento.
Gavin Simpson,

4
Sfortunatamente, non posso fare affidamento sulla codifica dei nomi delle colonne. Sto provando a farlo senza doverli specificare.
Sharoz,

4
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

Un (piccolo) caso che manca nelle risposte qui, che volevo esplicitare, è quando le variabili da raggruppare vengono generate dinamicamente a metà flusso in una 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_")
  )

Questo in sostanza mostra come utilizzare grepinsieme group_by_(.dots = ...)a raggiungere questo obiettivo.


3

Esempio generale sull'uso .dotsdell'argomento come input vettoriale di caratteri per la dplyr::group_byfunzione:

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

O senza un nome in codice per la variabile di raggruppamento (come richiesto dall'OP):

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

Con l'esempio dell'OP:

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

Vedi anche la vignetta dplyr sulla programmazione che spiega pronomi, quasiquotazione, quosure e ordinamento.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.