Suma wielu kolumn za pomocą dplyr

104

Moje pytanie polega na zsumowaniu wartości w wielu kolumnach ramki danych i utworzeniu nowej kolumny odpowiadającej temu podsumowaniu za pomocą dplyr. Wpisy danych w kolumnach są binarne (0,1). Myślę o wierszowym odpowiedniku funkcji summarise_eachlub mutate_eachfunkcji dplyr. Poniżej znajduje się minimalny przykład ramki danych:

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

Przydałoby się coś takiego:

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

ale wymagałoby to wypisania nazw każdej z kolumn. Mam jakieś 50 kolumn. Ponadto nazwy kolumn zmieniają się przy różnych iteracjach pętli, w której chcę zaimplementować tę operację, dlatego chciałbym spróbować uniknąć konieczności nadawania jakichkolwiek nazw kolumn.

Jak mogę to zrobić najskuteczniej? Każda pomoc byłaby bardzo mile widziana.

amo
źródło
12
Dlaczego dplyr? Dlaczego nie po prostu prosty df$sumrow <- rowSums(df, na.rm = TRUE)z podstawy R? Lub df$sumrow <- Reduce(`+`, df)jeśli chcesz dokładnie odtworzyć to, co zrobiłeś dplyr.
David Arenburg,
7
Możesz zrobić jedno i drugie z dplyrtak jak w df %>% mutate(sumrow = Reduce(`+`, .))lubdf %>% mutate(sumrow = rowSums(.))
David Arenburg
2
Zaktualizuj do najnowszej dplyrwersji i będzie działać.
David Arenburg,
1
Sugestie Davida Arenburga działały po zaktualizowaniu pakietu dplyr @DavidArenburg
amo
1
Komentarz @boern David Arenburgs był najlepszą odpowiedzią i najbardziej bezpośrednim rozwiązaniem. Twoja odpowiedź byłaby skuteczna, ale obejmuje dodatkowy krok polegający na zamianie wartości NA na zero, co może nie być odpowiednie w niektórych przypadkach.
amo,

Odpowiedzi:

118

Co powiesz na

podsumuj każdą kolumnę

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

podsumuj każdy wiersz

df %>%
   replace(is.na(.), 0) %>%
   mutate(sum = rowSums(.[1:5]))
Boern
źródło
8
summarise_eachsumuje się wzdłuż każdej kolumny, podczas gdy wymagana jest suma w każdym wierszu
amo
1
Próbuję osiągnąć to samo, ale mój DF ma kolumnę, która jest znakiem, dlatego nie mogę zsumować wszystkich kolumn. Chyba powinienem zmodyfikować (.[1:5])część, ale niestety nie jestem zaznajomiony ze składnią ani nie wiem, jak szukać pomocy. Próbowałem, mutate(sum = rowSums(is.numeric(.)))ale nie działało.
ccamara
5
Widzę. Możesz spróbować df %>% replace(is.na(.), 0) %>% select_if(is.numeric) %>% summarise_each(funs(sum))?
Boern
2
Użyj summarise_allzamiast, summarise_eachponieważ zostało wycofane.
hmhensen
2
Składnia mutate(sum = rowSums(.[,-1]))może się przydać, jeśli nie wiesz, z iloma kolumnami masz do czynienia.
Paulo S. Abreu
35

Jeśli chcesz zsumować tylko niektóre kolumny, użyłbym czegoś takiego:

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)

W ten sposób możesz użyć dplyr::selectskładni.

Richard DiSalvo
źródło
Podoba mi się to podejście bardziej niż inne, ponieważ nie wymaga przymuszania NA do 0
Michael Bellhouse
I lepsze niż grep, ponieważ łatwiej radzić sobie z takimi rzeczami jak x4: x11
Dov Rosenberg
32

Użyłbym dopasowywania wyrażeń regularnych, aby zsumować zmienne o określonych nazwach wzorców. Na przykład:

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

W ten sposób można utworzyć więcej niż jedną zmienną jako sumę określonej grupy zmiennych w ramce danych.

Erick Chacon
źródło
świetne rozwiązanie! Szukałem konkretnej funkcji dplyr, która robiłaby to w ostatnich wydaniach, ale nie
mogę
To świetne rozwiązanie. Jeśli istnieją kolumny, których nie chcesz uwzględniać, wystarczy zaprojektować instrukcję grep (), aby wybrać kolumny pasujące do określonego wzorca.
Trenton Hoffman
1
@TrentonHoffman tutaj jest bitowym odznaczaniem kolumn według określonego wzorca. wystarczy -znak:rowSums(.[-grep("x[3-5]", names(.))], na.rm = TRUE)
alexb523
26

Używanie reduce()from purrrjest nieco szybsze rowSumsi zdecydowanie szybsze niż apply, ponieważ unikasz iteracji po wszystkich wierszach i po prostu korzystasz z operacji wektoryzowanych:

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

Zobacz to dla czasów

skd
źródło
Podoba mi się to, ale jak byś to zrobił, kiedy potrzebujeszna.rm = TRUE
zobacz
@ see24 Nie wiem, co masz na myśli. To sumuje wektory a + b + c, wszystkie o tej samej długości. Ponieważ każdy wektor może mieć NA w różnych lokalizacjach lub nie, nie możesz ich zignorować. Spowodowałoby to niewyrównanie wektorów. Jeśli chcesz usunąć wartości NA, musisz to zrobić później , na przykład drop_na
skd
Skończyło się na tym, rowSums(select(., matches("myregex")) , na.rm = TRUE))że zrobiłem, ponieważ tego potrzebowałem, jeśli chodzi o ignorowanie NA. Więc jeśli liczby to sum(NA, 5)wynik to 5. Ale powiedziałeś, że redukcja jest lepsza niż rowSumstak, zastanawiałem się, czy jest sposób, aby to wykorzystać w tej sytuacji?
patrz 24
Widzę. Jeśli chcesz otrzymać sumę i zdecydowanie zignorować wartości NA, rowSumswersja jest prawdopodobnie najlepsza. Główną wadą jest to, że tylko rowSumsi rowMeanssą dostępne (jest nieco wolniejsza niż redukcja, ale nie za dużo). Jeśli musisz wykonać inną operację (nie sumę), reducewersja jest prawdopodobnie jedyną opcją. Po prostu unikaj używania applyw tym przypadku.
skd
23

Często spotykam się z tym problemem, a najłatwiejszym sposobem jest użycie apply()funkcji w mutatepoleceniu.

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

Tutaj możesz użyć wszystkiego, co chcesz, aby wybrać kolumny za pomocą standardowych dplyrsztuczek (np. starts_with()Lub contains()). Wykonując całą pracę w ramach jednego mutatepolecenia, ta akcja może wystąpić w dowolnym miejscu dplyrstrumienia kroków przetwarzania. Wreszcie, korzystając z tej apply()funkcji, masz elastyczność w używaniu dowolnego podsumowania, którego potrzebujesz, w tym własnej funkcji podsumowania zbudowanej specjalnie.

Alternatywnie, jeśli pomysł użycia funkcji non-tidyverse jest nieatrakcyjny, możesz zebrać kolumny, podsumować je i ostatecznie połączyć wynik z powrotem z oryginalną ramką danych.

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

Tutaj użyłem starts_with()funkcji, aby wybrać kolumny i obliczyć sumę, i możesz zrobić, co chcesz z NAwartościami. Wadą tego podejścia jest to, że chociaż jest dość elastyczne, tak naprawdę nie pasuje do dplyrstrumienia kroków czyszczenia danych.

Derek Sonderegger
źródło
3
Wydaje się głupie w użyciu, applykiedy to jest to, do czego rowSumszostało zaprojektowane.
zacdav
6
W tym przypadku rowSumsdziała naprawdę dobrze rowMeans, ale zawsze czułem się trochę dziwnie, zastanawiając się: „A jeśli to, co muszę obliczyć, nie jest sumą ani średnią?” Jednak w 99% przypadków muszę zrobić coś takiego, albo jest to suma, albo średnia, więc może dodatkowa elastyczność w korzystaniu z applyfunkcji ogólnej nie jest uzasadniona.
Derek Sonderegger
9

W nowszych wersjach programu dplyrmożna używać rowwise()wraz z c_acrossdo wykonywania agregacji wierszowej dla funkcji, które nie mają określonych wariantów wierszowych, ale jeśli istnieje wariant wierszowy, powinno być szybsze.

Ponieważ rowwise()jest to tylko specjalna forma grupowania i zmienia sposób działania czasowników, prawdopodobnie będziesz chciał to potokować ungroup()po wykonaniu operacji na wierszach.

Aby wybrać zakres wierszy:

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

Aby wybrać wiersze według typu:

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

W twoim konkretnym przypadku istnieje wariant wierszowy, więc możesz wykonać następujące czynności (zwróć uwagę na użycie acrosszamiast):

df %>%
  dplyr::mutate(sumrow = rowSums(dplyr::across(x1:x5), na.rm = T))

Więcej informacji można znaleźć na stronie w wierszach .

LMc
źródło