Kelompokkan beberapa kolom dalam dplyr, menggunakan input vektor string

157

Saya mencoba untuk mentransfer pemahaman saya tentang plyr ke dplyr, tetapi saya tidak tahu bagaimana cara mengelompokkan berdasarkan beberapa kolom.

# 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

Apa yang saya lewatkan untuk menerjemahkan contoh plyr ke dalam sintaks dplyr-esque?

Sunting 2017 : Dplyr telah diperbarui, sehingga solusi yang lebih sederhana tersedia. Lihat jawaban yang dipilih saat ini.

sharoz
sumber
3
Baru sampai di sini karena itu adalah top google. Anda dapat menggunakan group_by_sekarang dijelaskan divignette("nse")
James Owers
3
@kungfujam: Yang tampaknya hanya dikelompokkan berdasarkan kolom pertama, bukan pasangan kolom
sharoz
1
Anda harus menggunakan .dots. Inilah solusi yang diadaptasi dari jawaban @hadley di bawah ini:df %>% group_by_(.dots=list(quote(asihckhdoydk), quote(a30mvxigxkgh))) %>% summarise(n = n())
James Owers
1
Letakkan kode lengkap dalam jawaban di bawah ini
James Owers
1
Seperti yang ditunjukkan seseorang dalam jawaban pada komentar, tujuannya adalah untuk tidak memerlukan nama kolom yang dikodekan dengan hardcod.
sharoz

Jawaban:

52

Karena pertanyaan ini diposting, dplyr menambahkan versi cakupan group_by( dokumentasi di sini ). Ini memungkinkan Anda menggunakan fungsi yang sama dengan yang akan Anda gunakan select, seperti:

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 

Output dari contoh pertanyaan Anda seperti yang diharapkan (lihat perbandingan plyr di atas dan output di bawah):

# 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

Perhatikan bahwa karena dplyr::summarizehanya menghapus satu lapisan pengelompokan pada satu waktu, Anda masih memiliki beberapa pengelompokan yang terjadi di tibble yang dihasilkan (yang kadang-kadang dapat menangkap orang dengan mengejutkan kemudian di telepon). Jika Anda ingin benar-benar aman dari perilaku pengelompokan yang tidak terduga, Anda selalu dapat menambahkan %>% ungroupke saluran Anda setelah Anda merangkum.

Empiromancer
sumber
apakah pembaruan 0.7.0membuat sistem kutipan-tanda kutip tersedia dengan beberapa kolom juga?
JelenaČuklina
4
Anda juga dapat menggunakan .dotsargumen untuk group_by()seperti: data %>% group_by(.dots = columns) %>% summarize(value = mean(value)).
Paul Rougieux
Apakah panggilan untuk one_of()melakukan sesuatu di sini? Saya pikir itu berlebihan dalam konteks ini, karena ungkapan dibungkus dengan panggilan untuk vars().
knowah
@ Khashir ya, jawaban ini masih berfungsi @tahu Anda benar, panggilan untuk one_of()menjadi berlebihan dalam konteks ini
Empiromancer
1
@Sos Untuk menerapkan fungsi di beberapa kolom menggunakan selectsintaks, lihat acrossfungsi baru : dplyr.tidyverse.org/reference/across.html Dalam kasus Anda, itu akan terlihat sepertisummarize(across(all_of(c(''value_A", "value_B")), mean))
Empiromancer
102

Untuk menulis kode secara lengkap, berikut ini adalah pembaruan pada jawaban Hadley dengan sintaks baru:

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

keluaran:

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
James Owers
sumber
1
Ini sepertinya masih berupa pengodean nama kolom, hanya dalam formula saja. Inti pertanyaannya adalah bagaimana menggunakan string agar tidak harus mengetik asihckhdoydk...
Gregor Thomas
1
Telah memperbarui solusi yang digunakan dots <- lapply(names(df)[-3], function(x) as.symbol(x))untuk membuat .dotsargumen
James Owers
4
mencoba memilah-milah jawaban ini, .dots=adalah langkah penting. jika seseorang memiliki pegangan yang baik tentang mengapa hal itu diperlukan dalam group_bypanggilan, dapatkah Anda mengedit jawaban ini? sekarang ini agak sulit dipahami.
Andrew
12
vignette("nse")menunjukkan ada tiga cara untuk mengutip yang dapat diterima: formula, kutipan, dan karakter. Kecuali jika Anda khawatir tentang dari lingkungan mana itu akan menarik, Anda mungkin dapat pergi dengangroup_by_(.dots=grp_cols)
Ari B. Friedman
58

Dukungan untuk ini di dplyr saat ini cukup lemah, akhirnya saya pikir sintaksnya akan menjadi seperti:

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

Tapi itu mungkin tidak akan ada untuk sementara waktu (karena saya perlu memikirkan semua konsekuensinya).

Sementara itu, Anda dapat menggunakan regroup(), yang mengambil daftar simbol:

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

Jika Anda memiliki vektor karakter nama kolom, Anda dapat mengonversinya ke struktur yang tepat dengan lapply()dan as.symbol():

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

df %.% regroup(vars2) %.% summarise(n = n())
Hadley
sumber
6
as.symbolmenyelesaikannya. Terima kasih! Dalam hal ini membantu dengan pengembangan: skenario ini sangat umum bagi saya. Gabungkan hasil numerik dari setiap kombinasi variabel lainnya.
sharoz
ternyata ini hanya berfungsi untuk contoh khusus ini dan tidak ada yang lain.
Paulo E. Cardoso
3
Saya awalnya menandai ini sebagai jawabannya, tetapi pembaruan dplyr memungkinkan jawaban kungfujam bekerja.
sharoz
regroupjuga tidak digunakan lagi (setidaknya pada versi 0.4.3).
Berk U.
27

Spesifikasi string kolom dalam dplyrsekarang didukung melalui varian dplyrfungsi dengan nama yang diakhiri dengan garis bawah. Misalnya, terkait dengan group_byfungsi ada group_by_fungsi yang dapat mengambil argumen string. Sketsa ini menjelaskan sintaks dari fungsi-fungsi ini secara rinci.

Cuplikan berikut ini dengan bersih menyelesaikan masalah yang semula diajukan oleh @sharoz (perhatikan kebutuhan untuk menuliskan .dotsargumennya):

# Given data and columns from the OP

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

(Perhatikan bahwa dplyr sekarang menggunakan %>%operator, dan %.%sudah usang).

edward
sumber
17

Sampai dplyr memiliki dukungan penuh untuk argumen string, mungkin inti ini berguna:

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

Ini berisi banyak fungsi wrapper seperti s_group_by, s_mutate, s_filter, dll yang menggunakan argumen string. Anda dapat mencampurnya dengan fungsi dplyr normal. Sebagai contoh

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

Ini berfungsi jika Anda memberikan objek (well, Anda tidak, tapi ...) alih-alih sebagai vektor karakter:

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

di mana dfadalah Anda data.

?group_by mengatakan:

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

yang saya artikan bukan versi karakter dari nama-nama itu, tetapi bagaimana Anda akan merujuknya pada foo$bar; bartidak dikutip di sini. Atau bagaimana Anda akan merujuk ke variabel dalam rumus: foo ~ bar.

@Arun juga menyebutkan bahwa Anda dapat melakukan:

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

Tapi Anda tidak bisa meneruskan sesuatu yang tidak dievaluasi bukan nama variabel dalam objek data.

Saya kira ini karena metode internal yang digunakan Hadley untuk mencari hal-hal yang Anda sampaikan melalui ...argumen.

Gavin Simpson
sumber
1
@Arun Terima kasih untuk itu. Saya tidak memperhatikan itu, tetapi juga masuk akal. Saya menambahkan catatan untuk hal ini, mengutip Anda dan komentar Anda.
Gavin Simpson
4
Sayangnya, saya tidak bisa mengandalkan hard coding nama kolom. Saya mencoba melakukan ini tanpa harus menentukannya.
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))
Yordania
sumber
4

Satu (kecil) kasus yang hilang dari jawaban di sini, yang ingin saya perjelas, adalah ketika variabel yang dikelompokkan oleh dihasilkan secara dinamis midstream dalam pipa:

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_")
  )

Ini pada dasarnya menunjukkan bagaimana menggunakan grepbersama group_by_(.dots = ...)untuk mencapai ini.

tchakravarty
sumber
3

Contoh umum tentang penggunaan .dotsargumen sebagai input vektor karakter ke dplyr::group_byfungsi:

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

Atau tanpa nama kode keras untuk variabel pengelompokan (seperti yang diminta oleh OP):

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

Dengan contoh OP:

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

Lihat juga sketsa dplyr pada pemrograman yang menjelaskan kata ganti, kuasiquotation, quosures, dan rapi.

Paul Rougieux
sumber