จัดกลุ่มตามหลายคอลัมน์ใน dplyr โดยใช้อินพุตเวกเตอร์สตริง


157

ฉันพยายามโอนความเข้าใจของ plyr ไปเป็น dplyr แต่ฉันไม่สามารถหาวิธีจัดกลุ่มตามคอลัมน์หลายคอลัมน์ได้

# 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

ฉันขาดอะไรในการแปลตัวอย่าง plyr ให้เป็นไวยากรณ์ dplyr-esque

แก้ไข 2017 : อัปเดต Dplyr แล้วดังนั้นจึงมีวิธีแก้ปัญหาที่ง่ายกว่า ดูคำตอบที่เลือกในปัจจุบัน


3
เพิ่งมาถึงที่นี่เพราะมันติดอันดับ google คุณสามารถใช้group_by_อธิบายได้ในตอนนี้vignette("nse")
James Owers

3
@kungfujam: ดูเหมือนจะจัดกลุ่มตามคอลัมน์แรกเท่านั้นไม่ใช่คู่ของคอลัมน์
sharoz

1
.dotsคุณจำเป็นต้องใช้ นี่คือคำตอบที่ดัดแปลงมาจากคำตอบของ @hadley ด้านล่าง:df %>% group_by_(.dots=list(quote(asihckhdoydk), quote(a30mvxigxkgh))) %>% summarise(n = n())
James Owers

1
ได้ใส่รหัสเต็มในคำตอบด้านล่าง
James Owers

1
ในขณะที่บางคนชี้ให้เห็นในคำตอบของความคิดเห็นจุดมุ่งหมายคือไม่ต้องใช้ชื่อคอลัมน์แบบฮาร์ดโค้ด
sharoz

คำตอบ:


52

เนื่องจากคำถามนี้ถูกโพสต์, dplyr เพิ่มขอบเขตรุ่นgroup_by( เอกสารที่นี่ ) สิ่งนี้ช่วยให้คุณใช้ฟังก์ชั่นเดียวกับที่คุณใช้ด้วยselectเช่น

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 

ผลลัพธ์จากคำถามตัวอย่างของคุณเป็นไปตามคาด (ดูการเปรียบเทียบกับ plyr ด้านบนและผลลัพธ์ด้านล่าง):

# 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

โปรดทราบว่าเนื่องจากdplyr::summarizeมีการจัดกลุ่มเพียงครั้งละหนึ่งชั้นคุณจึงยังคงมีการจัดกลุ่มบางอย่างเกิดขึ้นในผลลัพธ์ที่ได้ (ซึ่งบางครั้งสามารถจับผู้คนได้โดยการทำให้ตกใจในภายหลัง) หากคุณต้องการความปลอดภัยอย่างยิ่งจากพฤติกรรมการจัดกลุ่มที่ไม่คาดคิดคุณสามารถเพิ่ม%>% ungroupไปยังขั้นตอนการทำงานของคุณได้หลังจากที่คุณสรุป


มีการอัปเดตเพื่อ0.7.0ให้ระบบอ้างคำพูดพร้อมใช้งานกับหลายคอลัมน์ด้วยหรือไม่
JelenaČuklina

4
นอกจากนี้คุณยังสามารถใช้.dotsข้อโต้แย้งที่จะเป็นเช่น:group_by() data %>% group_by(.dots = columns) %>% summarize(value = mean(value))
Paul Rougieux

การเรียกร้องให้one_of()ทำอะไรที่นี่หรือไม่? vars()ฉันคิดว่ามันจะซ้ำซ้อนในบริบทนี้เป็นสำนวนที่ถูกห่อในการเรียกไปยัง
knowah

@Kashashir ใช่คำตอบนี้ยังคงทำงาน @ Knowah คุณพูดถูกการเรียกone_of()ซ้ำซ้อนในบริบทนี้
Empiromancer

2
@Sos หากต้องการใช้ฟังก์ชั่นในหลายคอลัมน์โดยใช้selectไวยากรณ์ดูacrossฟังก์ชั่นใหม่: dplyr.tidyverse.org/reference/across.htmlในกรณีของคุณมันจะมีลักษณะเหมือนsummarize(across(all_of(c(''value_A", "value_B")), mean))
Empiromancer

102

เพื่อเขียนโค้ดแบบเต็มนี่คือการอัปเดตคำตอบของ Hadley ด้วยไวยากรณ์ใหม่:

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

เอาท์พุท:

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
สิ่งนี้ดูเหมือนว่าจะยังคง hardcoding ชื่อคอลัมน์เพียงในสูตรแทน จุดของคำถามคือวิธีการใช้สายเพื่อให้เป็นไปไม่ได้ที่จะพิมพ์asihckhdoydk...
เกรโทมัส

1
อัปเดตโซลูชันที่ใช้dots <- lapply(names(df)[-3], function(x) as.symbol(x))เพื่อสร้าง.dotsอาร์กิวเมนต์
James Owers

4
พยายามจัดเรียงคำตอบเหล่านี้.dots=เป็นขั้นตอนสำคัญ หากมีคนจัดการที่ดีเกี่ยวกับสาเหตุที่จำเป็นต้องใช้ในการgroup_byโทรคุณสามารถแก้ไขคำตอบนี้ได้หรือไม่? ตอนนี้มันไม่น่าเชื่อถือเลยสักนิด
Andrew

12
vignette("nse")ระบุว่ามีสามวิธีในการอ้างอิงที่ยอมรับได้: สูตรคำพูดและตัวละคร หากคุณไม่กังวลเกี่ยวกับสภาพแวดล้อมที่จะดึงออกมาคุณอาจหนีไปได้group_by_(.dots=grp_cols)
Ari B. Friedman

58

การสนับสนุนสิ่งนี้ใน dplyr ปัจจุบันค่อนข้างอ่อนแอในที่สุดฉันคิดว่าไวยากรณ์จะเป็นดังนี้:

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

แต่นั่นอาจจะไม่อยู่ที่นั่นซักพัก (เพราะฉันต้องคิดให้ได้ผลทั้งหมด)

ในระหว่างนี้คุณสามารถใช้regroup()ซึ่งใช้รายการสัญลักษณ์:

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

หากคุณมีเวกเตอร์อักขระของชื่อคอลัมน์คุณสามารถแปลงเป็นโครงสร้างที่เหมาะสมด้วยlapply()และas.symbol():

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

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

6
as.symbolแก้มัน ขอบคุณ! ในกรณีที่ช่วยในการพัฒนา: สถานการณ์นี้เป็นเรื่องธรรมดาสำหรับฉัน รวมผลลัพธ์ที่เป็นตัวเลขในการรวมกันของตัวแปรอื่น ๆ
sharoz

เห็นได้ชัดว่านี่ใช้ได้เฉพาะกับตัวอย่างนี้เท่านั้นและไม่มีอื่น ๆ
เปาโลอีคาโดโซ่

3
ฉันทำเครื่องหมายว่านี่เป็นคำตอบ แต่การอัปเดตเป็น dplyr อนุญาตให้คำตอบของ kungfujam ทำงานได้
sharoz

regroupเลิกใช้แล้ว (อย่างน้อยเป็นรุ่น 0.4.3)
Berk U.

27

สตริงสเปคของคอลัมน์ในdplyrขณะนี้ได้รับการสนับสนุนผ่านรูปแบบของdplyrฟังก์ชั่นที่มีชื่อจบในขีด ตัวอย่างเช่นสอดคล้องกับgroup_byฟังก์ชั่นที่มีgroup_by_ฟังก์ชั่นที่อาจใช้เวลาเป็นอาร์กิวเมนต์สตริง บทความสั้น ๆ นี้จะอธิบายรายละเอียดเกี่ยวกับไวยากรณ์ของฟังก์ชันเหล่านี้

ตัวอย่างต่อไปนี้แก้ไขปัญหาที่ @sharoz โพสต์ไว้อย่างหมดจด (โปรดสังเกตว่าจำเป็นต้องเขียน.dotsอาร์กิวเมนต์):

# Given data and columns from the OP

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

(โปรดทราบว่า dplyr ตอนนี้ใช้%>%โอเปอเรเตอร์และ%.%เลิกใช้แล้ว)


17

จนกว่า dplyr จะได้รับการสนับสนุนอย่างเต็มที่สำหรับการโต้แย้งสตริงบางทีส่วนสำคัญนี้มีประโยชน์:

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

มันมีฟังก์ชั่นห่อหุ้มเช่น s_group_by, s_mutate, s_filter ฯลฯ ที่ใช้อาร์กิวเมนต์สตริง คุณสามารถผสมกับฟังก์ชั่น dplyr ปกติ ตัวอย่างเช่น

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

11

มันใช้งานได้ถ้าคุณส่งผ่านวัตถุ (ดีคุณไม่ใช่ แต่ ... ) แทนที่จะเป็นเวกเตอร์อักขระ:

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

ที่เป็นของคุณdfdata

?group_by พูดว่า:

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

ซึ่งฉันตีความหมายถึงไม่ใช่ชื่อเวอร์ชั่นของตัวละคร แต่คุณจะอ้างถึงพวกเขาได้foo$barอย่างไร barไม่ได้ยกมาที่นี่ หรือวิธีที่คุณอ้างถึงตัวแปรในสูตร:foo ~ barหรือว่าคุณต้องการอ้างถึงตัวแปรในสูตร:

@Arun กล่าวถึงว่าคุณสามารถทำได้:

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

แต่คุณไม่สามารถผ่านสิ่งที่ไม่ได้ประเมินค่าได้ไม่ได้เป็นชื่อของตัวแปรในวัตถุข้อมูลที่

ฉันคิดว่านี่เป็นเพราะวิธีการภายในที่ Hadley ใช้เพื่อค้นหาสิ่งที่คุณส่งผ่านทาง...อาร์กิวเมนต์


1
@Arun ขอบคุณสำหรับสิ่งนั้น ฉันไม่ได้สังเกตสิ่งนั้น แต่มันก็สมเหตุสมผลดี ฉันได้เพิ่มหมายเหตุเกี่ยวกับเรื่องนี้โดยอ้างถึงคุณและความคิดเห็นของคุณ
Gavin Simpson

4
น่าเสียดายที่ฉันไม่สามารถเชื่อถือการเขียนชื่อคอลัมน์อย่างหนักได้ ฉันพยายามทำสิ่งนี้โดยไม่ต้องระบุ
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

กรณีเล็ก (เล็ก) ที่หายไปจากคำตอบที่นี่ที่ฉันต้องการให้ชัดเจนคือเมื่อตัวแปรที่จะจัดกลุ่มตามจะถูกสร้างขึ้นกลางคันแบบไดนามิกในไปป์ไลน์:

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

สิ่งนี้แสดงให้เห็นว่าจะใช้งานgrepร่วมกับgroup_by_(.dots = ...)การทำสิ่งนี้ได้อย่างไร


3

ตัวอย่างทั่วไปเกี่ยวกับการใช้.dotsอาร์กิวเมนต์เป็นอินพุตเวกเตอร์อักขระของdplyr::group_byฟังก์ชัน:

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

หรือไม่มีชื่อฮาร์ดโค้ดสำหรับตัวแปรการจัดกลุ่ม (ตามที่ถามโดย OP):

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

ด้วยตัวอย่างของ OP:

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

ดูบทความสั้น ๆ ของ dplyr เกี่ยวกับการเขียนโปรแกรมซึ่งจะอธิบายสรรพนามสรรพนามการ quasiquotation quashes และ tidyeval

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.