Tính tổng trên nhiều cột với dplyr


98

Câu hỏi của tôi liên quan đến việc tổng hợp các giá trị trên nhiều cột của khung dữ liệu và tạo một cột mới tương ứng với phép tổng kết này bằng cách sử dụng dplyr. Các mục dữ liệu trong các cột là nhị phân (0,1). Tôi đang nghĩ về một tương tự khôn ngoan của hàng của summarise_eachhoặc mutate_eachchức năng của dplyr. Dưới đây là một ví dụ tối thiểu về khung dữ liệu:

library(dplyr)
df=data.frame(
  x1=c(1,0,0,NA,0,1,1,NA,0,1),
  x2=c(1,1,NA,1,1,0,NA,NA,0,1),
  x3=c(0,1,0,1,1,0,NA,NA,0,1),
  x4=c(1,0,NA,1,0,0,NA,0,0,1),
  x5=c(1,1,NA,1,1,1,NA,1,0,1))

> df
   x1 x2 x3 x4 x5
1   1  1  0  1  1
2   0  1  1  0  1
3   0 NA  0 NA NA
4  NA  1  1  1  1
5   0  1  1  0  1
6   1  0  0  0  1
7   1 NA NA NA NA
8  NA NA NA  0  1
9   0  0  0  0  0
10  1  1  1  1  1

Tôi có thể sử dụng một cái gì đó như:

df <- df %>% mutate(sumrow= x1 + x2 + x3 + x4 + x5)

nhưng điều này sẽ liên quan đến việc viết ra tên của từng cột. Tôi có 50 cột. Ngoài ra, tên cột thay đổi ở các lần lặp khác nhau của vòng lặp mà tôi muốn thực hiện thao tác này, vì vậy tôi muốn tránh phải đặt bất kỳ tên cột nào.

Làm thế nào tôi có thể làm điều đó hiệu quả nhất? Bất kỳ sự trợ giúp nào cũng sẽ được đánh giá cao.


11
Tại sao dplyr? Tại sao không chỉ là một đơn giản df$sumrow <- rowSums(df, na.rm = TRUE)từ cơ sở R? Hoặc df$sumrow <- Reduce(`+`, df)nếu bạn muốn sao chép chính xác những gì bạn đã làm với dplyr.
David Arenburg

7
Bạn có thể thực hiện cả hai với dplyrquá như trong df %>% mutate(sumrow = Reduce(`+`, .))haydf %>% mutate(sumrow = rowSums(.))
David Arenburg

2
Cập nhật lên dplyrphiên bản mới nhất và nó sẽ hoạt động.
David Arenburg

1
Đề xuất của David Arenburg đã hoạt động sau khi cập nhật gói dplyr @DavidArenburg
amo

1
@boern David Arenburgs nhận xét là câu trả lời tốt nhất và giải pháp trực tiếp nhất. Câu trả lời của bạn sẽ hoạt động nhưng nó liên quan đến một bước bổ sung là thay thế các giá trị NA bằng 0 có thể không phù hợp trong một số trường hợp.
amo

Câu trả lời:


112

Làm thế nào về

tổng hợp từng cột

df %>%
   replace(is.na(.), 0) %>%
   summarise_all(funs(sum))

tổng hợp từng hàng

df %>%
   replace(is.na(.), 0) %>%
   mutate(sum = rowSums(.[1:5]))

8
summarise_eachtính tổng dọc theo mỗi cột trong khi những gì được yêu cầu được tính tổng dọc theo mỗi hàng
amo

1
Tôi đang cố gắng đạt được điều tương tự, nhưng DF của tôi có một cột là một ký tự, do đó tôi không thể tính tổng tất cả các cột. Tôi đoán tôi nên sửa đổi (.[1:5])một phần, nhưng tiếc là tôi không quen với cú pháp và tôi không biết cách tìm kiếm sự trợ giúp về nó. Đã thử mutate(sum = rowSums(is.numeric(.)))nhưng không hiệu quả.
ccamara

5
Tôi hiểu rồi. Bạn có thể muốn thử df %>% replace(is.na(.), 0) %>% select_if(is.numeric) %>% summarise_each(funs(sum))?
Boern

2
Sử dụng summarise_allthay vì summarise_eachnó đã không được dùng nữa.
hmhensen

2
Cú pháp mutate(sum = rowSums(.[,-1]))có thể hữu ích nếu bạn không biết mình cần xử lý bao nhiêu cột.
Paulo S. Abreu

32

Nếu bạn chỉ muốn tổng hợp các cột nhất định, tôi sẽ sử dụng một cái gì đó như sau:

library(dplyr)
df=data.frame(
  x1=c(1,0,0,NA,0,1,1,NA,0,1),
  x2=c(1,1,NA,1,1,0,NA,NA,0,1),
  x3=c(0,1,0,1,1,0,NA,NA,0,1),
  x4=c(1,0,NA,1,0,0,NA,0,0,1),
  x5=c(1,1,NA,1,1,1,NA,1,0,1))
df %>% select(x3:x5) %>% rowSums(na.rm=TRUE) -> df$x3x5.total
head(df)

Bằng cách này, bạn có thể sử dụng dplyr::selectcú pháp của.


Tôi giống như phương pháp này trên những người khác vì nó không đòi hỏi ép buộc NA 0
Michael Bellhouse

Và tốt hơn so với grep vì dễ dàng hơn để đối phó với những thứ như x4: x11
Dov Rosenberg

32

Tôi sẽ sử dụng so khớp biểu thức chính quy để tính tổng các biến có tên mẫu nhất định. Ví dụ:

df <- df %>% mutate(sum1 = rowSums(.[grep("x[3-5]", names(.))], na.rm = TRUE),
                    sum_all = rowSums(.[grep("x", names(.))], na.rm = TRUE))

Bằng cách này, bạn có thể tạo nhiều hơn một biến dưới dạng tổng hợp của một số nhóm biến nhất định trong khung dữ liệu của bạn.


giải pháp tuyệt vời! Tôi đang tìm kiếm một chức năng cụ thể dplyr làm điều này trong các phiên gần đây, nhưng couln't tìm
agenis

Giải pháp này là tuyệt vời. Nếu có các cột bạn không muốn đưa vào, bạn chỉ cần thiết kế câu lệnh grep () để chọn các cột phù hợp với một mẫu cụ thể.
Trenton Hoffman

1
@TrentonHoffman ở đây là bit bỏ chọn cột một mẫu cụ thể. chỉ cần -dấu hiệu:rowSums(.[-grep("x[3-5]", names(.))], na.rm = TRUE)
alexb523

22

Tôi gặp sự cố này thường xuyên và cách dễ nhất để làm điều này là sử dụng apply()hàm trong một mutatelệnh.

library(tidyverse)
df=data.frame(
  x1=c(1,0,0,NA,0,1,1,NA,0,1),
  x2=c(1,1,NA,1,1,0,NA,NA,0,1),
  x3=c(0,1,0,1,1,0,NA,NA,0,1),
  x4=c(1,0,NA,1,0,0,NA,0,0,1),
  x5=c(1,1,NA,1,1,1,NA,1,0,1))

df %>%
  mutate(sum = select(., x1:x5) %>% apply(1, sum, na.rm=TRUE))

Ở đây bạn có thể sử dụng bất cứ thứ gì bạn muốn để chọn các cột bằng các dplyrthủ thuật tiêu chuẩn (ví dụ: starts_with()hoặc contains()). Bằng cách thực hiện tất cả công việc trong một mutatelệnh duy nhất , hành động này có thể xảy ra ở bất kỳ đâu trong dplyrchuỗi các bước xử lý. Cuối cùng, bằng cách sử dụng apply()hàm, bạn có thể linh hoạt sử dụng bất kỳ bản tóm tắt nào bạn cần, bao gồm cả chức năng tóm tắt được xây dựng theo mục đích của riêng bạn.

Ngoài ra, nếu ý tưởng sử dụng một hàm không ngăn nắp không hấp dẫn, thì bạn có thể thu thập các cột, tóm tắt chúng và cuối cùng nối kết quả trở lại khung dữ liệu ban đầu.

df <- df %>% mutate( id = 1:n() )   # Need some ID column for this to work

df <- df %>%
  group_by(id) %>%
  gather('Key', 'value', starts_with('x')) %>%
  summarise( Key.Sum = sum(value) ) %>%
  left_join( df, . )

Ở đây tôi đã sử dụng starts_with()hàm để chọn các cột và tính tổng và bạn có thể làm bất cứ điều gì bạn muốn với NAcác giá trị. Nhược điểm của phương pháp này là mặc dù nó khá linh hoạt, nhưng nó không thực sự phù hợp với một dplyrchuỗi các bước làm sạch dữ liệu.


3
Có vẻ ngớ ngẩn applykhi sử dụng đây là những gì rowSumsđược thiết kế cho.
zacdav

6
Trong trường hợp này rowSumshoạt động thực sự tốt rowMeans, nhưng tôi luôn cảm thấy hơi kỳ lạ khi tự hỏi "Điều gì sẽ xảy ra nếu thứ tôi cần tính không phải là tổng hay trung bình?" Tuy nhiên, 99% thời gian tôi phải làm điều gì đó như thế này, nó là tổng hoặc trung bình, vì vậy có thể chút linh hoạt hơn trong việc sử dụng applyhàm chung không bị ảnh hưởng.
Derek Sonderegger

22

Việc sử dụng reduce()từ purrrnhanh hơn một chút rowSumsvà nhanh hơn rõ ràng apply, vì bạn tránh lặp lại tất cả các hàng và chỉ tận dụng các thao tác được vector hóa:

library(purrr)
library(dplyr)
iris %>% mutate(Petal = reduce(select(., starts_with("Petal")), `+`))

Xem cái này để biết thời gian


Tôi thích điều này nhưng bạn sẽ làm như thế nào khi cầnna.rm = TRUE
xem tại 24

@ see24 Tôi không chắc mình biết ý bạn. Điều này tính tổng các vectơ a + b + c, có cùng độ dài. Vì mỗi vector có thể có hoặc không có NA ở các vị trí khác nhau, bạn không thể bỏ qua chúng. Điều này sẽ làm cho các vectơ không bị lệch. Ví dụ: nếu bạn muốn xóa các giá trị NA, bạn phải làm điều đó sau đó với drop_na
skd

Tôi đã kết thúc làm rowSums(select(., matches("myregex")) , na.rm = TRUE))bởi vì đó là những gì tôi cần về bỏ qua NAS. Vì vậy, nếu các con số là sum(NA, 5)kết quả là 5. Nhưng bạn nói giảm là tốt hơn rowSumsnên tôi đã tự hỏi liệu có cách nào để sử dụng nó trong tình huống này không?
xem 24

Tôi hiểu rồi. Nếu bạn muốn tổng và bỏ qua các giá trị NA chắc chắn thì rowSumsphiên bản có lẽ là tốt nhất. Nhược điểm chính là chỉ rowSumsrowMeanscó sẵn (nó chậm hơn rất nhiều so với giảm, nhưng không nhiều). Nếu bạn cần thực hiện một thao tác khác (không phải tính tổng) thì reducephiên bản có lẽ là lựa chọn duy nhất. Chỉ cần tránh sử dụng applytrong trường hợp này.
skd

1

Trong các phiên bản mới hơn, dplyrbạn có thể sử dụng rowwise()cùng với c_acrossđể thực hiện tổng hợp theo hàng cho các hàm không có các biến thể theo hàng cụ thể, nhưng nếu biến thể theo hàng tồn tại thì sẽ nhanh hơn.

rowwise()chỉ là một dạng nhóm đặc biệt và thay đổi cách hoạt động của các động từ mà bạn có thể muốn chuyển nó thành ungroup()sau khi thực hiện thao tác theo hàng của mình.

Để chọn một loạt các hàng:

df %>%
  dplyr::rowwise() %>% 
  dplyr::mutate(sumrange = sum(dplyr::c_across(x1:x5), na.rm = T))
# %>% dplyr::ungroup() # you'll likely want to ungroup after using rowwise()

Để chọn hàng theo loại:

df %>%
  dplyr::rowwise() %>% 
  dplyr::mutate(sumnumeric = sum(c_across(where(is.numeric)), na.rm = T))
# %>% dplyr::ungroup() # you'll likely want to ungroup after using rowwise()

Trong trường hợp cụ thể của bạn, một biến thể theo hàng tồn tại để bạn có thể làm như sau (lưu ý việc sử dụng acrossthay thế):

df %>%
  dplyr::mutate(sumrow = rowSums(dplyr::across(x1:x5), na.rm = T))
# %>% dplyr::ungroup() # you'll likely want to ungroup after using rowwise()

Để biết thêm thông tin, hãy xem trang trên rowwise .

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.