Menu

Nhóm và tóm tắt dữ liệu trong R (aggregate, tapply, dplyr)

Mọi cách tính thống kê theo nhóm trong R: đếm bằng table(), tapply() cho một thống kê mỗi nhóm, giao diện công thức của aggregate(), và group_by() với summarize() của dplyr.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Đếm số hàng theo nhóm: table()

Câu hỏi nhóm dữ liệu đơn giản nhất là "mỗi loại có bao nhiêu?", và base R trả lời bằng một lệnh gọi. table() đếm số lần mỗi giá trị xuất hiện:

Một cột cho số đếm theo từng giá trị; hai cột cho cả một bảng chéo (khu vực là hàng, gói là cột). Muốn biết "chiếm bao nhiêu phần?", hãy bọc nó trong prop.table(table(...)). table() là đường ngắn nhất đến câu trả lời về tần suất trong toàn bộ R: đừng dùng thứ gì nặng nề hơn khi bạn chỉ cần một con số đếm.

Một thống kê cho mỗi nhóm: tapply()

tapply(values, groups, function) chia vector thứ nhất theo vector thứ hai và áp dụng hàm lên từng phần:

Kết quả là một vector có tên: nhãn nhóm làm tên, thống kê làm giá trị, rất hợp để tra cứu nhanh (means["EU"]). Tên hàm được hiểu là "table apply": áp dụng một hàm theo một bảng phân nhóm. Nó thuộc cùng họ với sapply() và lapply(), được trình bày trong tài liệu về họ apply.

Giới hạn của tapply() nằm ở hình dạng: một vector có tên khó ghép bảng, khó vẽ biểu đồ và khó xử lý tiếp. Khi bản tóm tắt chỉ là bước đệm chứ chưa phải câu trả lời cuối cùng, bạn muốn nhận lại một data frame, và đó chính là thứ công cụ tiếp theo trả về.

Công cụ chủ lực với công thức: aggregate()

aggregate() là group-by đầy đủ của base R: nó trả về data frame, và giao diện công thức đọc giống hệt câu bạn sẽ nói. value ~ group nghĩa là "giá trị, chia theo nhóm":

Thêm cột nhóm chỉ là thêm + another_column vào công thức: lệnh gọi thứ hai tính tổng theo từng tổ hợp khu vực và quý. Bạn cũng có thể tổng hợp nhiều cột giá trị cùng lúc với cbind(a, b) ~ group. Vì đầu ra là một data frame bình thường, nó đi thẳng vào phép ghép bảng, phép sắp xếp hay biểu đồ: đây là công cụ base R nên dùng mặc định cho các bản tóm tắt theo nhóm.

Một hành vi âm thầm cần biết: giao diện công thức loại bỏ những hàng có NA ở bất kỳ cột nào được dùng trước khi tổng hợp. Thường đó là điều bạn muốn; đôi khi đó lại là lời giải thích cho một con số đếm trông thấp bất thường.

Cách của dplyr: group_by() + summarize()

Chuẩn hiện đại là mẫu hai động từ của dplyr: group_by() khai báo cách nhóm, summarize() gộp mỗi nhóm thành một hàng, tính bao nhiêu thống kê tùy bạn đặt tên (khối tĩnh; sandbox chỉ chạy base R):

library(dplyr)

sales |>
    group_by(region) |>
    summarize(
        n     = n(),
        total = sum(amount),
        avg   = mean(amount)
    )

Đây là chỗ dplyr thật sự vượt trội so với base R: nhiều thống kê cho mỗi nhóm trong một lệnh gọi dễ đọc (aggregate() phải xoay xở rất vất vả mới làm được), thêm n() miễn phí, và kết quả chảy thẳng vào bước pipe tiếp theo. (summarise() là cùng một hàm, viết theo chính tả Anh.)

Một điều khiến người mới bối rối: khi có nhiều cột nhóm, summarize() chỉ gỡ bỏ cột nhóm cuối cùng, kết quả vẫn còn được nhóm, và nó in ra một thông báo về điều này. Hãy nói rõ điều bạn muốn bằng đối số .groups: summarize(avg = mean(amount), .groups = "drop") trả về một bảng thường không còn nhóm, và đó là thói quen mặc định đúng đắn. Một bảng còn nhóm lọt vào code phía sau sẽ khiến mutate() và các hàm tương tự âm thầm chạy theo từng nhóm, một nguồn kinh điển của những kết quả khó hiểu.

Chọn công cụ nào

  • Một con số đếm: table(), không gì hơn một lệnh gọi.
  • Một thống kê, xem nhanh: tapply(), rồi đọc kết quả từ vector có tên.
  • Cần data frame, môi trường chỉ có base R: aggregate() với công thức.
  • Nhiều thống kê, một phần của chuỗi pipe, hoặc bất cứ việc gì phức tạp: group_by() |> summarize().

Không có lựa chọn nào sai trong số này, vì chúng đều tính ra cùng những con số, nhưng chọn công cụ khớp với hình dạng đầu ra bạn cần sẽ tiết kiệm bước chuyển đổi về sau. Bản thân trung bình, trung vị và độ phân tán được trình bày trong thống kê mô tả.

Ví dụ hoàn chỉnh: mtcars theo số xi lanh

Tất cả kết hợp lại trên một bộ dữ liệu có sẵn: với mỗi số xi lanh, có bao nhiêu xe, mức tiêu hao nhiên liệu và mã lực trung bình là bao nhiêu:

Mười một xe 4 xi lanh trung bình khoảng 26,7 mpg, bảy xe 6 xi lanh khoảng 19,7, mười bốn xe 8 xi lanh khoảng 15,1, còn mã lực thì đi theo chiều ngược lại. Hai lệnh gọi, cả một bảng tóm tắt: đây chính là loại câu hỏi mà tổng hợp theo nhóm sinh ra để trả lời.

Những điều cần nhớ

  • table() để đếm, prop.table() để tính tỷ lệ.
  • tapply(values, groups, fn) trả về một vector có tên: xem nhanh, tra cứu dễ.
  • aggregate(value ~ group, data, FUN) trả về data frame; + thêm cột nhóm, cbind() thêm cột giá trị.
  • group_by() |> summarize(n = n(), avg = mean(x), .groups = "drop") của dplyr là chuẩn hiện đại cho các bản tóm tắt nhiều thống kê.
  • Chọn theo hình dạng đầu ra; con số thì công cụ nào cũng cho như nhau.

Tiếp theo: kết hợp các bảng có chung một khóa, với merge() và họ hàm join của dplyr.

Câu hỏi thường gặp

Tính trung bình theo nhóm trong R như thế nào?

Base R có hai công cụ: tapply(df$value, df$group, mean) trả về một vector có tên chứa trung bình của từng nhóm, còn aggregate(value ~ group, data = df, FUN = mean) trả về cùng kết quả dưới dạng data frame. Với dplyr: df |> group_by(group) |> summarize(avg = mean(value)).

Đếm số lần xuất hiện theo nhóm trong R như thế nào?

table(df$group) đếm số hàng theo từng giá trị nhóm chỉ bằng một lệnh gọi; table(df$a, df$b) lập bảng chéo hai cột. Trong dplyr, count(df, group) làm điều tương tự và trả về data frame, dễ xử lý tiếp hơn.

Hàm aggregate() trong R làm gì?

aggregate() chia data frame theo một hoặc nhiều cột nhóm, áp dụng một hàm lên từng phần, rồi trả về một data frame kết quả. Giao diện công thức đọc rất tự nhiên: aggregate(sales ~ region + quarter, data = df, FUN = mean) nghĩa là doanh số, nhóm theo khu vực và quý, lấy trung bình.

tapply và aggregate khác nhau thế nào?

Cùng một phép tính, khác hình dạng đầu ra. tapply() trả về một vector có tên (hoặc mảng khi có hai cột nhóm), tiện để tra cứu nhanh. aggregate() trả về data frame, tốt hơn khi kết quả còn dùng cho phân tích tiếp, cho phép ghép bảng hoặc để vẽ biểu đồ. Khi phân vân, hãy chọn aggregate().

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu