Somme sur plusieurs colonnes avec dplyr

98

Ma question consiste à additionner les valeurs sur plusieurs colonnes d'un bloc de données et à créer une nouvelle colonne correspondant à cette somme à l'aide de dplyr. Les entrées de données dans les colonnes sont binaires (0,1). Je pense à un analogue en ligne de la fonction summarise_eachou mutate_eachde dplyr. Voici un exemple minimal de la trame de données:

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

Je pourrais utiliser quelque chose comme:

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

mais cela impliquerait d'écrire les noms de chacune des colonnes. J'ai environ 50 colonnes. De plus, les noms de colonnes changent à différentes itérations de la boucle dans laquelle je veux implémenter cette opération, je voudrais donc essayer d'éviter d'avoir à donner des noms de colonne.

Comment puis-je le faire le plus efficacement possible? Toute assistance sera grandement appréciée.

amo
la source
11
Pourquoi dplyr? Pourquoi pas juste un simple à df$sumrow <- rowSums(df, na.rm = TRUE)partir de la base R? Ou df$sumrow <- Reduce(`+`, df)si vous souhaitez reproduire exactement ce que vous avez fait dplyr.
David Arenburg
7
Vous pouvez faire les deux avec dplyrtrop comme dans df %>% mutate(sumrow = Reduce(`+`, .))oudf %>% mutate(sumrow = rowSums(.))
David Arenburg
2
Mettez à jour vers la dernière dplyrversion et cela fonctionnera.
David Arenburg
1
Les suggestions de David Arenburg ont fonctionné après la mise à jour du package dplyr @DavidArenburg
amo
1
Le commentaire de @boern David Arenburgs était la meilleure réponse et la solution la plus directe. Votre réponse fonctionnerait, mais cela implique une étape supplémentaire de remplacement des valeurs NA par zéro, ce qui peut ne pas convenir dans certains cas.
amo le

Réponses:

112

Que diriez-vous

résumer chaque colonne

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

résumer chaque ligne

df %>%
   replace(is.na(.), 0) %>%
   mutate(sum = rowSums(.[1:5]))
Boern
la source
8
summarise_eachsommes vers le bas le long de chaque colonne tout ce qui est nécessaire est somme le long de chaque rangée
amo
1
J'essaie de réaliser la même chose, mais mon DF a une colonne qui est un caractère, donc je ne peux pas additionner toutes les colonnes. Je suppose que je devrais modifier la (.[1:5])partie, mais malheureusement, je ne suis pas familier avec la syntaxe et je ne sais pas comment chercher de l'aide. J'ai essayé mutate(sum = rowSums(is.numeric(.)))mais n'a pas fonctionné.
ccamara
5
Je vois. Vous voudrez peut-être df %>% replace(is.na(.), 0) %>% select_if(is.numeric) %>% summarise_each(funs(sum))essayer?
Boern
2
Utilisez summarise_allplutôt summarise_eachque car il est obsolète.
hmhensen
2
La syntaxe mutate(sum = rowSums(.[,-1]))peut être utile si vous ne savez pas combien de colonnes vous devez traiter.
Paulo S. Abreu
32

Si vous souhaitez additionner uniquement certaines colonnes, j'utiliserais quelque chose comme ceci:

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)

De cette façon, vous pouvez utiliser dplyr::selectla syntaxe de.

Richard DiSalvo
la source
J'aime cette approche au-dessus des autres car elle ne nécessite pas de forcer les AN à 0
Michael Bellhouse
Et mieux que grep car plus facile à gérer avec des choses comme x4: x11
Dov Rosenberg
32

J'utiliserais la correspondance d'expressions régulières pour additionner des variables avec certains noms de modèle. Par exemple:

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

De cette façon, vous pouvez créer plusieurs variables en tant que somme de certains groupes de variables de votre bloc de données.

Erick Chacon
la source
excellente solution! Je cherchais une fonction dplyr spécifique faisant cela dans les versions récentes, mais Couln't trouve
Agenis
Cette solution est excellente. S'il y a des colonnes que vous ne souhaitez pas inclure, vous devez simplement concevoir l'instruction grep () pour sélectionner les colonnes correspondant à un modèle spécifique.
Trenton Hoffman
1
@TrentonHoffman voici le bit de désélectionner les colonnes d'un modèle spécifique. juste besoin du -signe:rowSums(.[-grep("x[3-5]", names(.))], na.rm = TRUE)
alexb523
22

Je rencontre souvent ce problème et le moyen le plus simple de le faire est d'utiliser la apply()fonction dans une mutatecommande.

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

Ici, vous pouvez utiliser ce que vous voulez pour sélectionner les colonnes en utilisant les dplyrastuces standard (par exemple starts_with()ou contains()). En effectuant tout le travail dans une seule mutatecommande, cette action peut se produire n'importe où dans un dplyrflux d'étapes de traitement. Enfin, en utilisant la apply()fonction, vous avez la possibilité d'utiliser le résumé dont vous avez besoin, y compris votre propre fonction de résumé spécialement conçue.

Sinon, si l'idée d'utiliser une fonction non-tidyverse n'est pas attrayante, vous pouvez rassembler les colonnes, les résumer et enfin joindre le résultat à la trame de données d'origine.

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

Ici, j'ai utilisé la starts_with()fonction pour sélectionner les colonnes et calculé la somme et vous pouvez faire ce que vous voulez avec des NAvaleurs. L'inconvénient de cette approche est que, bien qu'elle soit assez flexible, elle ne s'intègre pas vraiment dans un dplyrflux d'étapes de nettoyage des données.

Derek Sonderegger
la source
3
applyCela semble idiot à utiliser quand c'est ce rowSumspour quoi il a été conçu.
zacdav le
6
Dans ce cas, cela rowSumsfonctionne très bien rowMeans, mais je me suis toujours senti un peu étrange en me demandant "Et si ce que je dois calculer n'est pas une somme ou une moyenne?" Cependant, 99% du temps que je dois faire quelque chose comme ça, c'est soit une somme, soit une moyenne, alors peut-être que le peu de flexibilité supplémentaire dans l'utilisation de la applyfonction générale n'est pas garanti.
Derek Sonderegger
22

L'utilisation de reduce()from purrrest légèrement plus rapide rowSumset certainement plus rapide que apply, puisque vous évitez d'itérer sur toutes les lignes et profitez simplement des opérations vectorisées:

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

Voir ceci pour les horaires

skd
la source
J'aime ça, mais comment le feriez-vous quand vous en avez besoinna.rm = TRUE
24
@ see24 Je ne suis pas sûr de savoir ce que vous voulez dire. Cela fait la somme des vecteurs a + b + c, tous de même longueur. Étant donné que chaque vecteur peut ou non avoir NA à différents endroits, vous ne pouvez pas les ignorer. Cela rendrait les vecteurs non alignés. Si vous voulez supprimer les valeurs NA, vous devez le faire ensuite avec, par exemple, drop_na
skd
J'ai fini par le faire rowSums(select(., matches("myregex")) , na.rm = TRUE))parce que c'est ce dont j'avais besoin pour ignorer les NA. Donc, si les chiffres sont sum(NA, 5)les résultats est 5. Mais vous avez dit que réduire est mieux que rowSumsdonc je me demandais s'il y avait un moyen de l'utiliser dans cette situation?
24
Je vois. Si vous voulez la somme et ignorez définitivement les valeurs NA, la rowSumsversion est probablement la meilleure. Le principal inconvénient est que seuls rowSumset rowMeanssont disponibles (il est légèrement plus lent que de réduire, mais pas de beaucoup). Si vous devez effectuer une autre opération (pas la somme), la reduceversion est probablement la seule option. Évitez simplement d'utiliser applydans ce cas.
skd
1

Dans les versions plus récentes de, dplyrvous pouvez utiliser rowwise()avec c_acrosspour effectuer une agrégation par ligne pour les fonctions qui n'ont pas de variantes spécifiques par ligne, mais si la variante par ligne existe, elle devrait être plus rapide.

Puisqu'il rowwise()s'agit simplement d'une forme spéciale de regroupement et qu'il change la façon dont les verbes fonctionnent, vous voudrez probablement le diriger ungroup()après avoir effectué votre opération par ligne.

Pour sélectionner une plage de lignes:

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

Pour sélectionner des lignes par type:

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

Dans votre cas spécifique, une variante par ligne existe, vous pouvez donc faire ce qui suit (notez l'utilisation de à la acrossplace):

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

Pour plus d'informations, consultez la page sur rowwise .

LMc
la source