Nhóm theo nhiều cột trong dplyr, sử dụng đầu vào vector chuỗi


157

Tôi đang cố gắng chuyển sự hiểu biết của tôi về plyr vào dplyr, nhưng tôi không thể tìm ra cách nhóm theo nhiều cột.

# 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

Tôi còn thiếu gì để dịch ví dụ plyr thành cú pháp dplyr-esque?

Chỉnh sửa 2017 : Dplyr đã được cập nhật, do đó, một giải pháp đơn giản hơn đã có sẵn. Xem câu trả lời hiện đang được chọn.


3
Chỉ cần đến đây vì nó là top google. group_by_Bây giờ bạn có thể sử dụng giải thích trongvignette("nse")
James Owers

3
@kungfujam: Điều đó dường như chỉ nhóm theo cột đầu tiên, không phải cặp cột
sharoz

1
Bạn cần sử dụng .dots. Đây là giải pháp được điều chỉnh từ câu trả lời của @hadley bên dưới:df %>% group_by_(.dots=list(quote(asihckhdoydk), quote(a30mvxigxkgh))) %>% summarise(n = n())
James Owers 27/1/2015

1
Đã đặt mã đầy đủ trong câu trả lời bên dưới
James Owers

1
Như ai đó đã chỉ ra trong một câu trả lời trên bình luận, mục đích là không yêu cầu tên cột được mã hóa cứng.
sharoz

Câu trả lời:


52

Vì câu hỏi này đã được đăng, dplyr đã thêm các phiên bản phạm vi của group_by( tài liệu ở đây ). Điều này cho phép bạn sử dụng các chức năng tương tự bạn sẽ sử dụng với select, như vậy:

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 

Đầu ra từ câu hỏi ví dụ của bạn là như mong đợi (xem so sánh với plyr ở trên và đầu ra bên dưới):

# 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

Lưu ý rằng vì dplyr::summarizechỉ loại bỏ một lớp nhóm tại một thời điểm, bạn vẫn có một số nhóm đang diễn ra trong phần kết quả (đôi khi có thể bắt mọi người bằng cách xuất hiện sau khi xuống dòng). Nếu bạn muốn an toàn tuyệt đối khỏi hành vi nhóm bất ngờ, bạn luôn có thể thêm %>% ungroupvào đường ống của mình sau khi tóm tắt.


có cập nhật để 0.7.0làm cho hệ thống quote-unquote có sẵn với một số cột không?
JelenaČuklina

4
Bạn cũng có thể sử dụng các .dotsđối số group_by()như vậy : data %>% group_by(.dots = columns) %>% summarize(value = mean(value)).
Paul Rougieux

Có cuộc gọi để one_of()làm bất cứ điều gì ở đây? Tôi nghĩ rằng nó là dư thừa trong bối cảnh này, vì biểu thức được gói trong một cuộc gọi đến vars().
biết

@Khashir có, câu trả lời này vẫn hoạt động @ Recognah Bạn nói đúng, cuộc gọi đến one_of()là dư thừa trong bối cảnh này
Empiromancer

Làm thế nào bạn sẽ áp dụng giải pháp này nếu bạn muốn trung bình của nhiều cột? Giả sử bạn có value_Avalue_Btrong đầu vào của bạn, và bạn muốn có một ý nghĩa cho mỗi cột dựa trên group_by? bạn có thể làm ...summarize(Mean_A = value_A, Mean_B = value_B)
Sos

102

Để viết mã đầy đủ, đây là bản cập nhật về câu trả lời của Hadley với cú pháp mới:

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

đầu ra:

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
Điều này dường như vẫn còn mã hóa tên cột, chỉ trong một công thức thay thế. Điểm chính của câu hỏi là làm thế nào để sử dụng các chuỗi để không phải gõ asihckhdoydk...
Gregor Thomas

1
Đã cập nhật giải pháp bằng cách sử dụng dots <- lapply(names(df)[-3], function(x) as.symbol(x))để tạo .dotsđối số
James Owers

4
cố gắng sắp xếp thông qua những câu trả lời này, .dots=là bước quan trọng. nếu ai đó xử lý tốt lý do tại sao điều đó được yêu cầu trong group_bycuộc gọi, bạn có thể chỉnh sửa câu trả lời này không? ngay bây giờ nó là một chút khó hiểu.
Andrew

12
vignette("nse")chỉ ra có ba cách để trích dẫn được chấp nhận: công thức, trích dẫn và ký tự. Trừ khi bạn lo lắng về việc nó sẽ kéo theo môi trường nào, bạn có thể thoát khỏigroup_by_(.dots=grp_cols)
Ari B. Friedman

58

Sự hỗ trợ cho điều này trong dplyr hiện tại khá yếu, cuối cùng tôi nghĩ cú pháp sẽ giống như:

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

Nhưng điều đó có lẽ sẽ không ở đó một thời gian (vì tôi cần suy nghĩ về tất cả các hậu quả).

Trong khi đó, bạn có thể sử dụng regroup(), trong đó có một danh sách các ký hiệu:

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

Nếu bạn có một vectơ ký tự của tên cột, bạn có thể chuyển đổi chúng sang cấu trúc bên phải với lapply()as.symbol():

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

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

6
as.symbolgiải quyết nó Cảm ơn! Trong trường hợp nó giúp phát triển: kịch bản này thực sự phổ biến đối với tôi. Tổng hợp một kết quả số trên mỗi kết hợp của các biến khác.
sharoz

rõ ràng điều này chỉ hoạt động cho ví dụ cụ thể này và không có khác.
Paulo E. Cardoso

3
Ban đầu tôi đánh dấu đây là câu trả lời, nhưng cập nhật cho dplyr cho phép câu trả lời của kungfujam hoạt động.
sharoz

regroupcũng không được dùng nữa (ít nhất là từ phiên bản 0.4.3).
Berk U.

27

Đặc tả chuỗi của các cột trong dplyrhiện được hỗ trợ thông qua các biến thể của dplyrhàm với tên hoàn thành trong một dấu gạch dưới. Ví dụ, tương ứng với group_byhàm có một group_by_hàm có thể lấy các đối số chuỗi. Họa tiết này mô tả cú pháp của các chức năng này một cách chi tiết.

Đoạn mã sau đây giải quyết dứt điểm vấn đề mà @sharoz ban đầu đặt ra (lưu ý sự cần thiết phải viết ra .dotsđối số):

# Given data and columns from the OP

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

(Lưu ý rằng dplyr hiện sử dụng %>%toán tử và %.%không được dùng nữa).


17

Cho đến khi dplyr có hỗ trợ đầy đủ cho các đối số chuỗi, có lẽ ý chính này là hữu ích:

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

Nó chứa một loạt các hàm bao bọc như s_group_by, s_mutate, s_filter, v.v ... sử dụng các đối số chuỗi. Bạn có thể trộn chúng với các hàm dplyr bình thường. Ví dụ

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

11

Nó hoạt động nếu bạn truyền cho nó các đối tượng (tốt, bạn không, nhưng ...) chứ không phải là một vectơ ký tự:

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

nơi dflà của bạn data.

?group_by nói:

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

mà tôi diễn giải không có nghĩa là các phiên bản ký tự của tên, mà là cách bạn muốn đề cập đến chúng trong foo$bar; barkhông được trích dẫn ở đây. Hoặc cách bạn tham khảo các biến trong công thức : foo ~ bar.

@Arun cũng đề cập rằng bạn có thể làm:

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

Nhưng bạn không thể vượt qua trong một cái gì đó unevaluated không phải là tên của một biến trong đối tượng dữ liệu.

Tôi cho rằng điều này là do các phương thức nội bộ mà Hadley đang sử dụng để tra cứu những thứ bạn truyền vào thông qua ...đối số.


1
@Asl Cảm ơn vì điều đó. Tôi đã không nhận thấy điều đó, nhưng nó cũng có ý nghĩa. Tôi đã thêm một lưu ý về vấn đề này, trích dẫn bạn và nhận xét của bạn.
Gavin Simpson

4
Thật không may, tôi không thể dựa vào mã hóa cứng tên cột. Tôi đang cố gắng làm điều này mà không cần phải chỉ định chúng.
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

Một trường hợp (nhỏ) bị thiếu trong các câu trả lời ở đây, mà tôi muốn làm rõ ràng, là khi các biến để nhóm theo được tạo động giữa dòng trong một đường ống:

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

Điều này về cơ bản cho thấy làm thế nào để sử dụng grepkết hợp với group_by_(.dots = ...)để đạt được điều này.


3

Ví dụ chung về việc sử dụng .dotsđối số làm đầu vào vectơ ký tự cho dplyr::group_byhàm:

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

Hoặc không có tên được mã hóa cứng cho biến nhóm (theo yêu cầu của OP):

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

Với ví dụ về OP:

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

Xem thêm các họa tiết dplyr về lập trình giải thích các đại từ, quasiquotation, quosures, và tidyeval.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.