Jumlahkan di beberapa kolom dengan dplyr

98

Pertanyaan saya melibatkan penjumlahan nilai di beberapa kolom dari bingkai data dan membuat kolom baru yang sesuai dengan penjumlahan ini menggunakan dplyr. Entri data di kolom adalah biner (0,1). Saya sedang memikirkan analog bijak darisummarise_each atau mutate_eachfungsi dplyr. Di bawah ini adalah contoh minimal dari bingkai data:

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

Saya bisa menggunakan sesuatu seperti:

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

tetapi ini akan melibatkan penulisan nama masing-masing kolom. Saya memiliki 50 kolom. Selain itu, nama kolom berubah pada iterasi yang berbeda dari loop di mana saya ingin menerapkan operasi ini, jadi saya ingin mencoba menghindari keharusan memberikan nama kolom apa pun.

Bagaimana saya bisa melakukannya dengan paling efisien? Bantuan apa pun akan sangat dihargai.

amo
sumber
11
Kenapa dplyr? Mengapa tidak hanya yang sederhana df$sumrow <- rowSums(df, na.rm = TRUE)dari basis R? Atau df$sumrow <- Reduce(`+`, df)jika Anda ingin meniru persis hal yang Anda lakukan dengan dplyr.
David Arenburg
7
Anda dapat melakukan keduanya dengan dplyrtoo as in df %>% mutate(sumrow = Reduce(`+`, .))ataudf %>% mutate(sumrow = rowSums(.))
David Arenburg
2
Perbarui ke dplyrversi terbaru dan itu akan berfungsi.
David Arenburg
1
Saran oleh David Arenburg bekerja setelah memperbarui paket dplyr @DavidArenburg
amo
1
Komentar @boern David Arenburgs adalah jawaban terbaik dan solusi paling langsung. Jawaban Anda akan berhasil tetapi ini melibatkan langkah ekstra untuk mengganti nilai NA dengan nol yang mungkin tidak cocok dalam beberapa kasus.
amo

Jawaban:

112

Bagaimana tentang

jumlahkan setiap kolom

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

jumlahkan setiap baris

df %>%
   replace(is.na(.), 0) %>%
   mutate(sum = rowSums(.[1:5]))
Boern
sumber
8
summarise_eachmenjumlahkan di sepanjang setiap kolom sementara yang diperlukan adalah jumlah di setiap baris
amo
1
Saya mencoba untuk mencapai hal yang sama, tetapi DF saya memiliki kolom yang merupakan karakter, oleh karena itu saya tidak dapat menjumlahkan semua kolom. Saya kira saya harus memodifikasi (.[1:5])bagian tersebut, tetapi sayangnya saya tidak terbiasa dengan sintaksnya atau saya tidak tahu cara mencari bantuan untuk itu. Sudah mencoba mutate(sum = rowSums(is.numeric(.)))tapi tidak berhasil.
ccamara
5
Saya melihat. Anda mungkin ingin mencobanya df %>% replace(is.na(.), 0) %>% select_if(is.numeric) %>% summarise_each(funs(sum))?
Boern
2
Gunakan summarise_allalih-alih summarise_eachseperti yang sudah tidak digunakan lagi.
hmhensen
2
Sintaks mutate(sum = rowSums(.[,-1]))mungkin berguna jika Anda tidak tahu berapa banyak kolom yang perlu Anda tangani.
Paulo S. Abreu
33

Jika Anda ingin menjumlahkan kolom tertentu saja, saya akan menggunakan sesuatu seperti ini:

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)

Dengan cara ini Anda dapat menggunakan dplyr::selectsintaks.

Richard DiSalvo
sumber
Saya suka pendekatan ini di atas yang lain karena tidak perlu memaksa NAs ke 0
Michael Bellhouse
Dan lebih baik daripada grep karena lebih mudah menangani hal-hal seperti x4: x11
Dov Rosenberg
32

Saya akan menggunakan pencocokan ekspresi reguler untuk menjumlahkan variabel dengan nama pola tertentu. Sebagai contoh:

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

Dengan cara ini Anda dapat membuat lebih dari satu variabel sebagai jumlah grup variabel tertentu dari bingkai data Anda.

Erick Chacon
sumber
solusi yang bagus! Saya mencari fungsi dplyr tertentu yang melakukan ini dalam rilis terbaru, tetapi tidak dapat menemukan
agenis
Solusi ini bagus. Jika ada kolom yang tidak ingin Anda sertakan, Anda hanya perlu mendesain pernyataan grep () untuk memilih kolom yang cocok dengan pola tertentu.
Trenton Hoffman
1
@TrentonHoffman di sini adalah sedikit hapus kolom pola tertentu. hanya perlu -tanda:rowSums(.[-grep("x[3-5]", names(.))], na.rm = TRUE)
alexb523
22

Saya sering mengalami masalah ini, dan cara termudah untuk melakukannya adalah dengan menggunakan apply()fungsi di dalam mutateperintah.

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

Di sini Anda dapat menggunakan apa pun yang Anda inginkan untuk memilih kolom menggunakan dplyrtrik standar (mis. starts_with()Atau contains()). Dengan melakukan semua pekerjaan dalam satu mutateperintah, tindakan ini dapat terjadi di mana saja dalam dplyraliran langkah pemrosesan. Terakhir, dengan menggunakan fileapply() fungsi tersebut, Anda memiliki fleksibilitas untuk menggunakan ringkasan apa pun yang Anda butuhkan, termasuk fungsi peringkasan yang dibuat untuk tujuan Anda sendiri.

Alternatifnya, jika gagasan menggunakan fungsi non-tidyverse tidak menarik, maka Anda bisa mengumpulkan kolom, meringkasnya dan akhirnya menggabungkan hasilnya kembali ke bingkai data asli.

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, . )

Di sini saya menggunakan starts_with()fungsi untuk memilih kolom dan menghitung jumlahnya dan Anda dapat melakukan apa pun yang Anda inginkan dengan NAnilai. Kelemahan dari pendekatan ini adalah meskipun cukup fleksibel, ia tidak benar-benar cocok dengan dplyraliran langkah pembersihan data.

Derek Sonderegger
sumber
3
Tampaknya konyol untuk digunakan applysaat ini adalah apa rowSumsyang dirancang untuk itu.
zacdav
6
Dalam hal ini rowSumsbekerja sangat baik juga rowMeans, tetapi saya selalu merasa agak aneh bertanya-tanya tentang "Bagaimana jika hal yang perlu saya hitung bukanlah penjumlahan atau mean?" Namun, 99% dari waktu saya harus melakukan sesuatu seperti ini, entah itu penjumlahan atau mean, jadi mungkin sedikit ekstra fleksibilitas dalam menggunakan applyfungsi umum tidak diberikan.
Derek Sonderegger
22

Menggunakan reduce()from purrrsedikit lebih cepat daripada rowSumsdan pasti lebih cepat daripada apply, karena Anda menghindari iterasi pada semua baris dan hanya memanfaatkan operasi vektorisasi:

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

Lihat ini untuk pengaturan waktu

skd
sumber
Saya suka ini tapi bagaimana Anda melakukannya saat Anda membutuhkannyana.rm = TRUE
lihat 24
@ see24 Saya tidak yakin saya tahu apa yang Anda maksud. Ini menjumlahkan vektor a + b + c, semuanya memiliki panjang yang sama. Karena setiap vektor mungkin atau mungkin tidak memiliki NA di lokasi yang berbeda, Anda tidak dapat mengabaikannya. Ini akan membuat vektor tidak sejajar. Jika Anda ingin menghapus nilai NA, Anda harus melakukannya setelahnya dengan, misalnya, drop_na
skd
Saya akhirnya melakukannya rowSums(select(., matches("myregex")) , na.rm = TRUE))karena itulah yang saya butuhkan dalam hal mengabaikan NAs. Jadi jika angkanya adalah sum(NA, 5)hasilnya 5. Tetapi Anda mengatakan mengurangi lebih baik daripada rowSumsjadi saya bertanya-tanya apakah ada cara untuk menggunakannya dalam situasi ini?
lihat
Saya melihat. Jika Anda ingin menjumlahkan dan mengabaikan nilai NA pasti rowSumsversinya mungkin yang terbaik. Kerugian utama adalah bahwa hanya rowSumsdan rowMeanstersedia (itu sedikit lebih lambat daripada mengurangi, tetapi tidak banyak). Jika Anda perlu melakukan operasi lain (bukan penjumlahan) maka reduceversi mungkin satu-satunya pilihan. Hindari penggunaan applydalam kasus ini.
skd
2

Dalam versi yang lebih baru dplyrAnda dapat menggunakan rowwise()bersama denganc_across untuk melakukan agregasi berdasarkan baris untuk fungsi yang tidak memiliki varian berdasarkan baris tertentu, tetapi jika varian bijak baris ada, seharusnya lebih cepat.

Karena rowwise()ini hanya bentuk pengelompokan khusus dan mengubah cara kerja kata kerja, Anda mungkin ingin menyalurkannya ungroup()setelah melakukan operasi baris-bijaksana Anda.

Untuk memilih berbagai baris:

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

Untuk memilih baris berdasarkan jenis:

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

Dalam kasus khusus Anda, ada varian berdasarkan baris sehingga Anda dapat melakukan hal berikut (perhatikan penggunaan acrosssebagai gantinya):

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

Untuk informasi lebih lanjut, lihat halaman di rowwise .

LMc
sumber