Summe über mehrere Spalten mit dplyr

94

Meine Frage besteht darin, Werte über mehrere Spalten eines Datenrahmens zu summieren und eine neue Spalte zu erstellen, die dieser Summierung entspricht dplyr. Die Dateneinträge in den Spalten sind binär (0,1). Ich denke an ein zeilenweises Analogon der summarise_eachoder mutate_eachFunktion von dplyr. Unten finden Sie ein minimales Beispiel für den Datenrahmen:

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

Ich könnte so etwas gebrauchen wie:

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

Dies würde jedoch das Aufschreiben der Namen jeder der Spalten beinhalten. Ich habe gerne 50 Spalten. Darüber hinaus ändern sich die Spaltennamen bei verschiedenen Iterationen der Schleife, in der ich diese Operation implementieren möchte, sodass ich versuchen möchte, keine Spaltennamen anzugeben.

Wie kann ich das am effizientesten machen? Jede Unterstützung wäre sehr dankbar.

amo
quelle
11
Warum dplyr? Warum nicht einfach eine einfache df$sumrow <- rowSums(df, na.rm = TRUE)von Basis R? Oder df$sumrow <- Reduce(`+`, df)wenn Sie genau das replizieren möchten, was Sie getan haben dplyr.
David Arenburg
7
Sie können beides auch mit dplyrwie in df %>% mutate(sumrow = Reduce(`+`, .))oderdf %>% mutate(sumrow = rowSums(.))
David Arenburg
2
Aktualisieren Sie auf die neueste dplyrVersion und es wird funktionieren.
David Arenburg
1
Vorschläge von David Arenburg arbeiteten nach der Aktualisierung des Pakets dplyr @DavidArenburg
amo
1
@boern David Arenburgs Kommentar war die beste Antwort und direkteste Lösung. Ihre Antwort würde funktionieren, beinhaltet jedoch einen zusätzlichen Schritt zum Ersetzen der NA-Werte durch Null, der in einigen Fällen möglicherweise nicht geeignet ist.
Amo

Antworten:

108

Wie wäre es mit

Fassen Sie jede Spalte zusammen

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

Fassen Sie jede Zeile zusammen

df %>%
   replace(is.na(.), 0) %>%
   mutate(sum = rowSums(.[1:5]))
Boern
quelle
8
summarise_eachsummiert sich entlang jeder Spalte, während die Summe entlang jeder Zeile erforderlich ist
amo
1
Ich versuche das Gleiche zu erreichen, aber mein DF hat eine Spalte, die ein Zeichen ist, daher kann ich nicht alle Spalten summieren. Ich denke, ich sollte das (.[1:5])Teil ändern , aber leider bin ich weder mit der Syntax vertraut noch weiß ich nicht, wie ich Hilfe dazu suchen soll. Versucht mit mutate(sum = rowSums(is.numeric(.))), aber nicht funktioniert.
Ccamara
5
Aha. Vielleicht möchten Sie eine Chance geben df %>% replace(is.na(.), 0) %>% select_if(is.numeric) %>% summarise_each(funs(sum))?
Boern
2
Verwenden Sie summarise_allstatt summarise_eachwie veraltet.
Hmhensen
2
Die Syntax mutate(sum = rowSums(.[,-1]))kann nützlich sein, wenn Sie nicht wissen, mit wie vielen Spalten Sie arbeiten müssen.
Paulo S. Abreu
31

Wenn Sie nur bestimmte Spalten summieren möchten, würde ich Folgendes verwenden:

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)

Auf diese Weise können Sie die dplyr::selectSyntax verwenden.

Richard DiSalvo
quelle
Ich mag diesen Ansatz über andere, da es nicht erforderlich ist, NAs auf 0 zu zwingen
Michael Bellhouse
Und besser als grep, weil es einfacher ist, mit Dingen wie x4: x11 umzugehen
Dov Rosenberg
30

Ich würde Matching mit regulären Ausdrücken verwenden, um Variablen mit bestimmten Musternamen zu summieren. Beispielsweise:

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

Auf diese Weise können Sie mehr als eine Variable als Summe bestimmter Variablengruppen Ihres Datenrahmens erstellen.

Erick Chacon
quelle
tolle Lösung! Ich habe nach einer bestimmten dplyr-Funktion gesucht, die dies in den letzten Versionen tut, konnte sie aber nicht finden
agenis
Diese Lösung ist großartig. Wenn Sie Spalten nicht einschließen möchten, müssen Sie lediglich die Anweisung grep () entwerfen, um Spalten auszuwählen, die einem bestimmten Muster entsprechen.
Trenton Hoffman
1
@TrentonHoffman hier ist die Bit-Abwahlspalte ein bestimmtes Muster. brauche nur das -Zeichen:rowSums(.[-grep("x[3-5]", names(.))], na.rm = TRUE)
alexb523
21

Die Verwendung von reduce()from purrrist etwas schneller als rowSumsund definitiv schneller als apply, da Sie das Durchlaufen aller Zeilen vermeiden und nur die vektorisierten Operationen nutzen:

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

Siehe dies für Timings

skd
quelle
Ich mag das, aber wie würden Sie es tun, wenn Sie es brauchenna.rm = TRUE
siehe 24.
@ see24 Ich bin nicht sicher, ob ich weiß, was du meinst. Dies summiert die Vektoren a + b + c, alle gleich lang. Da jeder Vektor NA an verschiedenen Orten haben kann oder nicht, können Sie sie nicht ignorieren. Dies würde die Vektoren unausgerichtet machen. Wenn Sie NA-Werte entfernen möchten, müssen Sie dies anschließend beispielsweise mit drop_na
skd
rowSums(select(., matches("myregex")) , na.rm = TRUE))Am Ende tat ich das, weil ich genau das brauchte, um NAs zu ignorieren. Wenn die Zahlen sum(NA, 5)also 5 sind, haben Sie gesagt, dass Reduzieren besser ist als rowSumsdas. Ich habe mich gefragt, ob es in dieser Situation eine Möglichkeit gibt, es zu verwenden.
siehe 24.
Aha. Wenn Sie die Summe wollen und NA-Werte definitiv ignorieren möchten, ist die rowSumsVersion wahrscheinlich die beste. Der Hauptnachteil ist, dass nur rowSumsund rowMeansverfügbar sind (es ist etwas langsamer als zu reduzieren, aber nicht viel). Wenn Sie eine andere Operation ausführen müssen (nicht die Summe), reduceist wahrscheinlich die Version die einzige Option. Vermeiden Sie applyin diesem Fall nur die Verwendung .
Skd
20

Ich stoße häufig auf dieses Problem, und der einfachste Weg, dies zu tun, besteht darin, die apply()Funktion innerhalb eines mutateBefehls zu verwenden.

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

Hier können Sie alles verwenden, was Sie möchten, um die Spalten mit den Standardtricks dplyr(z . B. starts_with()oder contains()) auszuwählen . Wenn Sie die gesamte Arbeit in einem einzigen mutateBefehl ausführen, kann diese Aktion an einer beliebigen Stelle innerhalb eines dplyrStroms von Verarbeitungsschritten ausgeführt werden. Schließlich haben Sie durch die Verwendung der apply()Funktion die Flexibilität, jede benötigte Zusammenfassung zu verwenden, einschließlich Ihrer eigenen speziell entwickelten Zusammenfassungsfunktion.

Wenn die Idee, eine nicht-tidyverse-Funktion zu verwenden, nicht ansprechend ist, können Sie alternativ die Spalten zusammenfassen, zusammenfassen und das Ergebnis schließlich wieder mit dem ursprünglichen Datenrahmen verbinden.

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

Hier habe ich die starts_with()Funktion verwendet, um die Spalten auszuwählen und die Summe zu berechnen, und Sie können mit NAWerten tun, was Sie wollen . Der Nachteil dieses Ansatzes ist, dass er zwar ziemlich flexibel ist, aber nicht wirklich in einen dplyrStrom von Datenbereinigungsschritten passt .

Derek Sonderegger
quelle
3
Scheint albern zu verwenden, applywenn dies das ist, wofür rowSumses entwickelt wurde.
Zacdav
5
In diesem Fall rowSumsfunktioniert das wirklich gut rowMeans, aber ich habe mich immer ein bisschen komisch gefragt: "Was ist, wenn das, was ich berechnen muss, keine Summe oder kein Mittelwert ist?" In 99% der applyFälle, in denen ich so etwas tun muss, handelt es sich entweder um eine Summe oder um einen Mittelwert . Daher ist die zusätzliche Flexibilität bei der Verwendung der allgemeinen Funktion möglicherweise nicht gewährleistet.
Derek Sonderegger