Suma en varias columnas con dplyr

98

Mi pregunta implica sumar valores en múltiples columnas de un marco de datos y crear una nueva columna correspondiente a esta suma usando dplyr. Las entradas de datos en las columnas son binarias (0,1). Estoy pensando en un análogo de fila de la función summarise_eacho mutate_eachde dplyr. A continuación se muestra un ejemplo mínimo del marco de datos:

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

Podría usar algo como:

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

pero esto implicaría escribir los nombres de cada una de las columnas. Tengo como 50 columnas. Además, los nombres de las columnas cambian en diferentes iteraciones del ciclo en el que quiero implementar esta operación, así que me gustaría intentar evitar tener que dar nombres de columna.

¿Cómo puedo hacer eso de la manera más eficiente? Cualquier ayuda será muy apreciada.

amo
fuente
11
¿Por qué dplyr? ¿Por qué no simplemente un simple df$sumrow <- rowSums(df, na.rm = TRUE)de base R? O df$sumrow <- Reduce(`+`, df)si quieres replicar exactamente lo que hiciste dplyr.
David Arenburg
7
Puede hacer ambas cosas con dplyrtambién como en df %>% mutate(sumrow = Reduce(`+`, .))odf %>% mutate(sumrow = rowSums(.))
David Arenburg
2
Actualice a la última dplyrversión y funcionará.
David Arenburg
1
Las sugerencias de David Arenburg funcionaron después de actualizar el paquete dplyr @DavidArenburg
amo
1
El comentario de @boern David Arenburgs fue la mejor respuesta y la solución más directa. Su respuesta funcionaría, pero implica un paso adicional para reemplazar los valores NA con cero, lo que podría no ser adecuado en algunos casos.
amo

Respuestas:

112

Qué tal si

suma cada columna

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

resumir cada fila

df %>%
   replace(is.na(.), 0) %>%
   mutate(sum = rowSums(.[1:5]))
Boern
fuente
8
summarise_eachsuma a lo largo de cada columna, mientras que lo que se requiere es la suma a lo largo de cada fila
amo
1
Estoy tratando de lograr lo mismo, pero mi DF tiene una columna que es un carácter, por lo tanto, no puedo sumar todas las columnas. Supongo que debería modificar la (.[1:5])parte, pero desafortunadamente no estoy familiarizado con la sintaxis ni sé cómo buscar ayuda al respecto. Lo intenté mutate(sum = rowSums(is.numeric(.)))pero no funcionó.
ccamara
5
Veo. ¿Quizás quieras df %>% replace(is.na(.), 0) %>% select_if(is.numeric) %>% summarise_each(funs(sum))intentarlo?
Boern
2
Úselo en summarise_alllugar de summarise_eachya que ha quedado obsoleto.
hmhensen
2
La sintaxis mutate(sum = rowSums(.[,-1]))puede resultar útil si no sabe con cuántas columnas debe tratar.
Paulo S. Abreu
32

Si desea sumar solo ciertas columnas, usaría algo como esto:

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 esta forma puede utilizar dplyr::selectla sintaxis de.

Richard DiSalvo
fuente
Me gusta este enfoque por encima de otros, ya que no requiere de coaccionar a AN 0
Michael Bellhouse
Y mejor que grep porque es más fácil lidiar con cosas como x4: x11
Dov Rosenberg
32

Usaría la coincidencia de expresiones regulares para sumar variables con ciertos nombres de patrones. Por ejemplo:

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

De esta manera, puede crear más de una variable como una suma de cierto grupo de variables de su marco de datos.

Erick Chacón
fuente
gran solucion! Estaba buscando una función dplyr específica que hiciera esto en lanzamientos recientes, pero no pude encontrar
agenis
Esta solución es genial. Si hay columnas que no desea incluir, simplemente debe diseñar la declaración grep () para seleccionar columnas que coincidan con un patrón específico.
Trenton Hoffman
1
@TrentonHoffman aquí está el bit deseleccionar columnas de un patrón específico. solo necesito el -cartel:rowSums(.[-grep("x[3-5]", names(.))], na.rm = TRUE)
alexb523
22

Me encuentro con este problema a menudo y la forma más sencilla de hacerlo es utilizar la apply()función dentro de un mutatecomando.

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

Aquí puede usar lo que quiera para seleccionar las columnas usando los dplyrtrucos estándar (por ejemplo, starts_with()o contains()). Al hacer todo el trabajo con un solo mutatecomando, esta acción puede ocurrir en cualquier lugar dentro de un dplyrflujo de pasos de procesamiento. Finalmente, al usar la apply()función, tiene la flexibilidad de usar cualquier resumen que necesite, incluida su propia función de resumen diseñada específicamente.

Alternativamente, si la idea de usar una función no tidyverse no es atractiva, entonces puede reunir las columnas, resumirlas y finalmente unir el resultado al marco de datos original.

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

Aquí usé la starts_with()función para seleccionar las columnas y calculé la suma y puedes hacer lo que quieras con los NAvalores. La desventaja de este enfoque es que, si bien es bastante flexible, realmente no encaja en un dplyrflujo de pasos de limpieza de datos.

Derek Sonderegger
fuente
3
Parece una tontería usarlo applycuando esto es para lo que rowSumsfue diseñado.
zacdav
6
En este caso rowSumsfunciona muy bien como lo hace rowMeans, pero siempre me sentí un poco extraño al preguntarme "¿Qué pasa si lo que necesito calcular no es una suma o una media?" Sin embargo, el 99% de las veces tengo que hacer algo como esto, es una suma o una media, por lo que tal vez applyno se justifique un poco de flexibilidad adicional para usar la función general .
Derek Sonderegger
22

Usar reduce()from purrres un poco más rápido rowSumsy definitivamente más rápido que apply, ya que evita iterar sobre todas las filas y simplemente aprovecha las operaciones vectorizadas:

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

Vea esto para los tiempos

skd
fuente
Me gusta esto, pero ¿cómo lo harías cuando lo necesitesna.rm = TRUE
24
@ see24 No estoy seguro de saber a qué te refieres. Esto suma los vectores a + b + c, todos de la misma longitud. Dado que cada vector puede tener o no NA en diferentes ubicaciones, no puede ignorarlos. Esto haría que los vectores no estén alineados. Si desea eliminar los valores de NA, debe hacerlo después con, por ejemplo, drop_na
skd
Terminé haciéndolo rowSums(select(., matches("myregex")) , na.rm = TRUE))porque eso es lo que necesitaba en términos de ignorar las NA. Entonces, si los números son, sum(NA, 5)el resultado es 5. Pero usted dijo que reducir es mejor que rowSumseso. Me preguntaba si hay una manera de usarlo en esta situación.
24
Veo. Si desea la suma e ignorar los valores NA definitivamente, la rowSumsversión es probablemente la mejor. La principal desventaja es que solo rowSumsy rowMeansestán disponibles (es un poco más lento que reducir, pero no mucho). Si necesita realizar otra operación (no la suma), reduceprobablemente la versión sea la única opción. Simplemente evite usar applyen este caso.
skd
2

En las versiones más recientes dplyr, puede usar rowwise()junto con c_acrosspara realizar agregaciones por filas para funciones que no tienen variantes específicas por filas, pero si existe la variante por filas, debería ser más rápido.

Dado que rowwise()es solo una forma especial de agrupación y cambia la forma en que funcionan los verbos, es probable que desee canalizarlos ungroup()después de realizar la operación de filas.

Para seleccionar un rango de filas:

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

Para seleccionar filas por tipo:

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

En su caso específico, existe una variante por filas para que pueda hacer lo siguiente (tenga en cuenta el uso de en su acrosslugar):

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

Para obtener más información, consulte la página sobre filas .

LMc
fuente