Soma em várias colunas com dplyr

103

Minha pergunta envolve somar valores em várias colunas de um quadro de dados e criar uma nova coluna correspondente a esse somatório usando dplyr. As entradas de dados nas colunas são binárias (0,1). Estou pensando em um análogo de linha da função summarise_eachou mutate_eachde dplyr. Abaixo está um exemplo mínimo do quadro de dados:

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

Eu poderia usar algo como:

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

mas isso envolveria escrever os nomes de cada uma das colunas. Eu tenho cerca de 50 colunas. Além disso, os nomes das colunas mudam em diferentes iterações do loop em que desejo implementar esta operação, portanto, gostaria de evitar ter de fornecer nomes de coluna.

Como posso fazer isso com mais eficiência? Qualquer ajuda seria muito apreciada.

amo
fonte
12
Por quê dplyr? Por que não apenas um simples df$sumrow <- rowSums(df, na.rm = TRUE)da base R? Ou df$sumrow <- Reduce(`+`, df)se você deseja replicar exatamente o que você fez dplyr.
David Arenburg,
7
Você pode fazer ambos com dplyrtambém df %>% mutate(sumrow = Reduce(`+`, .))oudf %>% mutate(sumrow = rowSums(.))
David Arenburg
2
Atualize para a dplyrversão mais recente e funcionará.
David Arenburg,
1
As sugestões de David Arenburg funcionaram após atualizar o pacote dplyr @DavidArenburg
amo
1
O comentário de @boern David Arenburg foi a melhor resposta e a solução mais direta. Sua resposta funcionaria, mas envolve uma etapa extra de substituição dos valores de NA por zero, o que pode não ser adequado em alguns casos.
amo

Respostas:

117

E se

some cada coluna

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

some cada linha

df %>%
   replace(is.na(.), 0) %>%
   mutate(sum = rowSums(.[1:5]))
Boern
fonte
8
summarise_eachsoma ao longo de cada coluna enquanto o que é necessário é a soma ao longo de cada linha
amo
1
Estou tentando fazer o mesmo, mas meu DF tem uma coluna que é um caractere, portanto, não consigo somar todas as colunas. Acho que deveria modificar a (.[1:5])parte, mas infelizmente não estou familiarizado com a sintaxe nem sei como procurar ajuda sobre ela. Tentei, mutate(sum = rowSums(is.numeric(.)))mas não funcionou.
ccamara
5
Eu vejo. Você pode querer dar df %>% replace(is.na(.), 0) %>% select_if(is.numeric) %>% summarise_each(funs(sum))uma chance?
Boern
2
Use em summarise_allvez de summarise_eachporque está obsoleto.
hmhensen
2
A sintaxe mutate(sum = rowSums(.[,-1]))pode ser útil se você não souber com quantas colunas precisa lidar.
Paulo S. Abreu
34

Se você quiser somar apenas algumas colunas, eu usaria algo assim:

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)

Desta forma, você pode usar dplyr::selecta sintaxe de.

Richard DiSalvo
fonte
Gosto dessa abordagem acima de outras, pois não exige a coerção de NAs para 0
Michael Bellhouse
E melhor do que grep porque é mais fácil de lidar com coisas como x4: x11
Dov Rosenberg
32

Eu usaria correspondência de expressão regular para somar variáveis ​​com determinados nomes de padrão. Por exemplo:

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

Desta forma, você pode criar mais de uma variável como uma soma de certo grupo de variáveis ​​de seu quadro de dados.

Erick Chacon
fonte
ótima solução! Eu estava procurando por uma função dplyr específica fazendo isso em lançamentos recentes, mas não consegui encontrar
agenis
Essa solução é ótima. Se houver colunas que você não deseja incluir, você simplesmente precisa projetar a instrução grep () para selecionar as colunas que correspondem a um padrão específico.
Trenton Hoffman
1
@TrentonHoffman aqui é o bit desmarcar colunas de um padrão específico. só preciso do -sinal:rowSums(.[-grep("x[3-5]", names(.))], na.rm = TRUE)
alexb523
25

Usar reduce()from purrré ligeiramente mais rápido do que rowSumse definitivamente mais rápido do que apply, uma vez que você evita a iteração em todas as linhas e apenas tira proveito das operações vetorizadas:

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

Veja isto para horários

skd
fonte
Eu gosto disso, mas como você faria quando precisassena.rm = TRUE
24 de
@ see24 Não tenho certeza se sei o que você quer dizer. Isso soma os vetores a + b + c, todos do mesmo comprimento. Uma vez que cada vetor pode ou não ter NA em locais diferentes, você não pode ignorá-los. Isso tornaria os vetores desalinhados. Se você quiser remover valores de NA você tem que fazê-lo mais tarde com, por exemplo, drop_na
SKD
Acabei fazendo rowSums(select(., matches("myregex")) , na.rm = TRUE))porque era disso que eu precisava para ignorar NAs. Então, se os números forem, sum(NA, 5)o resultado é 5. Mas você disse que reduzir é melhor do que rowSumsentão eu queria saber se há uma maneira de usá-lo nessa situação?
veja 24 de
Eu vejo. Se você deseja a soma e ignorar os valores NA, definitivamente a rowSumsversão é provavelmente a melhor. A principal desvantagem é que apenas rowSumse rowMeansestão disponíveis (é um pouco mais lento do que reduzir, mas não muito). Se você precisar realizar outra operação (não a soma), a reduceversão é provavelmente a única opção. Apenas evite usar applyneste caso.
skd de
23

Eu encontro esse problema com frequência, e a maneira mais fácil de fazer isso é usar a apply()função em um 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))

Aqui você pode usar o que quiser para selecionar as colunas usando os dplyrtruques padrão (por exemplo, starts_with()ou contains()). Fazendo todo o trabalho em um único mutatecomando, essa ação pode ocorrer em qualquer lugar dentro de um dplyrfluxo de etapas de processamento. Finalmente, usando oapply() função, você tem a flexibilidade de usar qualquer resumo de que precisar, incluindo sua própria função de resumo criada para o propósito.

Alternativamente, se a ideia de usar uma função não-tidyverse não for atraente, você pode reunir as colunas, resumi-las e, finalmente, juntar o resultado de volta ao quadro de dados 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, . )

Aqui eu usei a starts_with()função para selecionar as colunas e calculei a soma e você pode fazer o que quiser com os NAvalores. A desvantagem dessa abordagem é que, embora seja bastante flexível, ela realmente não se encaixa em um dplyrfluxo de etapas de limpeza de dados.

Derek Sonderegger
fonte
3
Parece bobo de usar applyquando rowSumsfoi projetado para isso.
zacdav
6
Nesse caso, rowSumsfunciona muito bem rowMeans, mas sempre me senti um pouco estranho pensando: "E se o que eu preciso calcular não for uma soma ou uma média?" No entanto, 99% das vezes eu tenho que fazer algo assim, é uma soma ou uma média, então talvez a parte extra de flexibilidade no uso da applyfunção geral não seja garantida.
Derek Sonderegger
8

Em versões mais novas do, dplyrvocê pode usar rowwise()junto com c_acrosspara realizar agregação por linha para funções que não possuem variantes específicas por linha, mas se a variante por linha existir, ela deve ser mais rápida.

Uma vez que rowwise()é apenas uma forma especial de agrupar e alterar a forma como os verbos funcionam, você provavelmente vai querer canalizá-lo paraungroup() após fazer sua operação de linha.

Para selecionar um intervalo de linhas:

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

No seu caso específico, existe uma variante de linha para que você possa fazer o seguinte (observe o uso de across):

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

Para obter mais informações, consulte a página sobre rowwise .

LMc
fonte