ใช้ฟังก์ชันสรุปหลายตัวแปรตามกลุ่มในการโทรครั้งเดียว


93

ฉันมีกรอบข้อมูลต่อไปนี้

x <- read.table(text = "  id1 id2 val1 val2
1   a   x    1    9
2   a   x    2    4
3   a   y    3    5
4   a   y    4    9
5   b   x    1    7
6   b   y    4    4
7   b   x    3    9
8   b   y    2    8", header = TRUE)

ฉันต้องการคำนวณค่าเฉลี่ยของ val1 และ val2 ที่จัดกลุ่มตาม id1 และ id2 และนับจำนวนแถวสำหรับชุดค่าผสม id1-id2 แต่ละชุดพร้อมกัน ฉันสามารถทำการคำนวณแต่ละรายการแยกกัน:

# calculate mean
aggregate(. ~ id1 + id2, data = x, FUN = mean)

# count rows
aggregate(. ~ id1 + id2, data = x, FUN = length)

เพื่อที่จะทำการคำนวณทั้งสองในการโทรครั้งเดียวฉันพยายาม

do.call("rbind", aggregate(. ~ id1 + id2, data = x, FUN = function(x) data.frame(m = mean(x), n = length(x))))

อย่างไรก็ตามฉันได้รับผลลัพธ์ที่อ่านไม่ออกพร้อมกับคำเตือน:

#     m   n
# id1 1   2
# id2 1   1
#     1.5 2
#     2   2
#     3.5 2
#     3   2
#     6.5 2
#     8   2
#     7   2
#     6   2
# Warning message:
#   In rbind(id1 = c(1L, 2L, 1L, 2L), id2 = c(1L, 1L, 2L, 2L), val1 = list( :
#   number of columns of result is not a multiple of vector length (arg 1)

ฉันสามารถใช้แพ็คเกจ plyr ได้ แต่ชุดข้อมูลของฉันค่อนข้างใหญ่และ plyr ช้ามาก (แทบจะใช้ไม่ได้) เมื่อขนาดของชุดข้อมูลโตขึ้น

ฉันจะใช้aggregateหรือฟังก์ชันอื่น ๆ เพื่อทำการคำนวณหลายอย่างในการโทรครั้งเดียวได้อย่างไร


ข้างaggregateที่กล่าวถึงในคำตอบก็ยังมีและby tapply
Roman Luštrik

คำตอบ:


156

คุณสามารถทำได้ทั้งหมดในขั้นตอนเดียวและรับการติดฉลากที่เหมาะสม:

> aggregate(. ~ id1+id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) )
#   id1 id2 val1.mn val1.n val2.mn val2.n
# 1   a   x     1.5    2.0     6.5    2.0
# 2   b   x     2.0    2.0     8.0    2.0
# 3   a   y     3.5    2.0     7.0    2.0
# 4   b   y     3.0    2.0     6.0    2.0

สิ่งนี้สร้างดาต้าเฟรมที่มีคอลัมน์รหัสสองคอลัมน์และคอลัมน์เมทริกซ์สองคอลัมน์:

str( aggregate(. ~ id1+id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) ) )
'data.frame':   4 obs. of  4 variables:
 $ id1 : Factor w/ 2 levels "a","b": 1 2 1 2
 $ id2 : Factor w/ 2 levels "x","y": 1 1 2 2
 $ val1: num [1:4, 1:2] 1.5 2 3.5 3 2 2 2 2
  ..- attr(*, "dimnames")=List of 2
  .. ..$ : NULL
  .. ..$ : chr  "mn" "n"
 $ val2: num [1:4, 1:2] 6.5 8 7 6 2 2 2 2
  ..- attr(*, "dimnames")=List of 2
  .. ..$ : NULL
  .. ..$ : chr  "mn" "n"

ตามที่ระบุไว้ใน @ lord.garbage ด้านล่างนี้สามารถแปลงเป็น dataframe ที่มีคอลัมน์ "ง่าย" ได้โดยใช้ do.call(data.frame, ...)

str( do.call(data.frame, aggregate(. ~ id1+id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) ) ) 
    )
'data.frame':   4 obs. of  6 variables:
 $ id1    : Factor w/ 2 levels "a","b": 1 2 1 2
 $ id2    : Factor w/ 2 levels "x","y": 1 1 2 2
 $ val1.mn: num  1.5 2 3.5 3
 $ val1.n : num  2 2 2 2
 $ val2.mn: num  6.5 8 7 6
 $ val2.n : num  2 2 2 2

นี่คือไวยากรณ์สำหรับตัวแปรหลายตัวใน LHS:

aggregate(cbind(val1, val2) ~ id1 + id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) )

1
ขอบคุณมาก. ในฐานะที่เป็นหมายเหตุด้านข้างฉันจะรวมเพื่อสรุปเพียงคอลัมน์เดียวได้อย่างไร ถ้าฉันมีคอลัมน์ตัวเลขหลายคอลัมน์ฉันไม่ต้องการให้มันรวมคอลัมน์ที่ฉันไม่ต้องการ แน่นอนฉันสามารถทิ้งคอลัมน์หลังจากการรวมเสร็จสิ้น แต่รอบของ CPU จะถูกใช้ไปแล้ว
บรอกโคลี

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

2
ฉันพบข้อบกพร่องที่ user2659402 กล่าวถึงในการอัปเดตของเขาขณะใช้ RStudio 0.98.1014 บนเครื่อง windows 7 หากคุณส่งออก data frame ไปยังคอนโซลตามที่แสดงจะดูเหมือนเป็นปกติอย่างไรก็ตามหากคุณบันทึกลงใน d แล้วพยายามเข้าถึง d $ val1.mn จะส่งคืนค่า NULL d ยังมีรูปแบบไม่ถูกต้องหากคุณเรียกใช้ view (d) การใช้รหัสในการอัปเดตได้รับการแก้ไข
JHowIX

4
สาเหตุที่คุณประสบปัญหาคือ "vals" จะถูกส่งกลับเป็นเมทริกซ์โดยมีคอลัมน์สองคอลัมน์แทนที่จะเป็นคอลัมน์ธรรมดา ลองd$val1[ , ""mn"]ดูโครงสร้างด้วยstr.
IRTFM

5
คุณสามารถผูกคอลัมน์ที่มีการฝึกอบรมกลับเข้ามาในกรอบข้อมูล: โดยใช้agg <- aggregate(cbind(val1, val2) ~ id1 + id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x))) agg_df <- do.call(data.frame, agg)ดูเพิ่มเติมที่นี่
lord.garbage

30

ระบุสิ่งนี้ในคำถาม:

ฉันสามารถใช้แพ็คเกจ plyr ได้ แต่ชุดข้อมูลของฉันค่อนข้างใหญ่และ plyr ช้ามาก (แทบจะใช้ไม่ได้) เมื่อขนาดของชุดข้อมูลโตขึ้น

จากนั้นในdata.table( 1.9.4+) คุณสามารถลอง:

> DT
   id1 id2 val1 val2
1:   a   x    1    9
2:   a   x    2    4
3:   a   y    3    5
4:   a   y    4    9
5:   b   x    1    7
6:   b   y    4    4
7:   b   x    3    9
8:   b   y    2    8

> DT[ , .(mean(val1), mean(val2), .N), by = .(id1, id2)]   # simplest
   id1 id2  V1  V2 N
1:   a   x 1.5 6.5 2
2:   a   y 3.5 7.0 2
3:   b   x 2.0 8.0 2
4:   b   y 3.0 6.0 2

> DT[ , .(val1.m = mean(val1), val2.m = mean(val2), count = .N), by = .(id1, id2)]  # named
   id1 id2 val1.m val2.m count
1:   a   x    1.5    6.5     2
2:   a   y    3.5    7.0     2
3:   b   x    2.0    8.0     2
4:   b   y    3.0    6.0     2

> DT[ , c(lapply(.SD, mean), count = .N), by = .(id1, id2)]   # mean over all columns
   id1 id2 val1 val2 count
1:   a   x  1.5  6.5     2
2:   a   y  3.5  7.0     2
3:   b   x  2.0  8.0     2
4:   b   y  3.0  6.0     2

สำหรับการเปรียบเทียบเวลาaggregate(ใช้ในคำถามและคำตอบอื่น ๆ ทั้ง 3 ข้อ) เพื่อdata.tableดู เกณฑ์มาตรฐานนี้ ( กรณีaggและปัญหาagg.x)


12

คุณสามารถเพิ่มcountคอลัมน์รวมกับsumจากนั้นปรับขนาดกลับเพื่อรับmean:

x$count <- 1
agg <- aggregate(. ~ id1 + id2, data = x,FUN = sum)
agg
#   id1 id2 val1 val2 count
# 1   a   x    3   13     2
# 2   b   x    4   16     2
# 3   a   y    7   14     2
# 4   b   y    6   12     2

agg[c("val1", "val2")] <- agg[c("val1", "val2")] / agg$count
agg
#   id1 id2 val1 val2 count
# 1   a   x  1.5  6.5     2
# 2   b   x  2.0  8.0     2
# 3   a   y  3.5  7.0     2
# 4   b   y  3.0  6.0     2

มีข้อดีในการรักษาชื่อคอลัมน์ของคุณและสร้างcountคอลัมน์เดียว


12

การใช้dplyrแพ็คเกจคุณสามารถทำได้โดยใช้summarise_allไฟล์. ด้วยฟังก์ชันสรุปนี้คุณสามารถใช้ฟังก์ชันอื่น ๆ (ในกรณีนี้meanและn()) กับแต่ละคอลัมน์ที่ไม่ได้จัดกลุ่ม:

x %>%
  group_by(id1, id2) %>%
  summarise_all(funs(mean, n()))

ซึ่งจะช่วยให้:

     id1    id2 val1_mean val2_mean val1_n val2_n
1      a      x       1.5       6.5      2      2
2      a      y       3.5       7.0      2      2
3      b      x       2.0       8.0      2      2
4      b      y       3.0       6.0      2      2

หากคุณไม่ต้องการใช้ฟังก์ชันกับคอลัมน์ที่ไม่ได้จัดกลุ่มทั้งหมดให้ระบุคอลัมน์ที่ควรใช้หรือโดยการยกเว้นที่ไม่ต้องการด้วยเครื่องหมายลบโดยใช้summarise_at()ฟังก์ชัน:

# inclusion
x %>%
  group_by(id1, id2) %>%
  summarise_at(vars(val1, val2), funs(mean, n()))

# exclusion
x %>%
  group_by(id1, id2) %>%
  summarise_at(vars(-val2), funs(mean, n()))

10

บางทีคุณอาจต้องการรวม ?

x.mean <- aggregate(. ~ id1+id2, p, mean)
x.len  <- aggregate(. ~ id1+id2, p, length)

merge(x.mean, x.len, by = c("id1", "id2"))

  id1 id2 val1.x val2.x val1.y val2.y
1   a   x    1.5    6.5      2      2
2   a   y    3.5    7.0      2      2
3   b   x    2.0    8.0      2      2
4   b   y    3.0    6.0      2      2

4

คุณยังสามารถใช้plyr::each()เพื่อแนะนำฟังก์ชันต่างๆ:

aggregate(cbind(val1, val2) ~ id1 + id2, data = x, FUN = plyr::each(avg = mean, n = length))

1

อีกdplyrทางเลือกหนึ่งคือacrossซึ่งเป็นส่วนหนึ่งของเวอร์ชัน dev ปัจจุบัน

#devtools::install_github("tidyverse/dplyr")
library(dplyr)

x %>% 
  group_by(id1, id2) %>% 
  summarise(across(starts_with("val"), list(mean = mean, n = length)))

ผลลัพธ์

# A tibble: 4 x 4
# Groups:   id1 [2]
  id1   id2   mean$val1 $val2 n$val1 $val2
  <fct> <fct>     <dbl> <dbl>  <int> <int>
1 a     x           1.5   6.5      2     2
2 a     y           3.5   7        2     2
3 b     x           2     8        2     2
4 b     y           3     6        2     2

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