Суммирование по нескольким столбцам с помощью dplyr

98

Мой вопрос включает суммирование значений по нескольким столбцам фрейма данных и создание нового столбца, соответствующего этому суммированию, используя dplyr. Записи данных в столбцах являются двоичными (0,1). Я думаю о построчном аналоге функции summarise_eachили . Ниже приведен минимальный пример фрейма данных:mutate_eachdplyr

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

Я мог бы использовать что-то вроде:

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

но это потребует написания имен каждого из столбцов. У меня вроде 50 колонок. Кроме того, имена столбцов меняются на разных итерациях цикла, в котором я хочу реализовать эту операцию, поэтому я хотел бы попытаться избежать необходимости указывать какие-либо имена столбцов.

Как я могу сделать это наиболее эффективно? Будем очень благодарны любой помощи.

амо
источник
11
Почему dplyr? Почему не просто простой на df$sumrow <- rowSums(df, na.rm = TRUE)базе R? Или df$sumrow <- Reduce(`+`, df)если вы хотите в точности повторить то, что вы сделали dplyr.
Дэвид Аренбург
7
Вы можете делать и то, и другое, dplyrкак в df %>% mutate(sumrow = Reduce(`+`, .))илиdf %>% mutate(sumrow = rowSums(.))
Дэвид Аренбург
2
Обновите до последней dplyrверсии, и все будет работать.
Дэвид Аренбург
1
Предложения Дэвида Arenburg работали после пакета обновления dplyr @DavidArenburg
АМО
1
Комментарий @boern Дэвида Аренбурга был лучшим ответом и самым прямым решением. Ваш ответ будет работать, но он включает дополнительный шаг по замене значений NA на ноль, что может не подходить в некоторых случаях.
амо

Ответы:

112

Как насчет

суммировать каждый столбец

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

суммировать каждую строку

df %>%
   replace(is.na(.), 0) %>%
   mutate(sum = rowSums(.[1:5]))
Boern
источник
8
summarise_eachсуммирует по каждому столбцу, в то время как то, что требуется, является суммой по каждой строке
амо
1
Я пытаюсь добиться того же, но в моем DF есть столбец, который является символом, поэтому я не могу суммировать все столбцы. Думаю, мне следует изменить эту (.[1:5])часть, но, к сожалению, я не знаком с синтаксисом и не знаю, как искать по нему помощь. Пытался, mutate(sum = rowSums(is.numeric(.)))но не получилось.
ccamara
5
Понимаю. Вы можете df %>% replace(is.na(.), 0) %>% select_if(is.numeric) %>% summarise_each(funs(sum))попробовать?
Boern
2
Использовать summarise_allвместо summarise_eachустаревшего.
hmhensen
2
Синтаксис mutate(sum = rowSums(.[,-1]))может пригодиться, если вы не знаете, сколько столбцов вам нужно обработать.
Пауло С. Абреу
32

Если вы хотите суммировать только определенные столбцы, я бы использовал что-то вроде этого:

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)

Таким образом вы можете использовать dplyr::selectсинтаксис.

Ричард ДиСальво
источник
Мне этот подход нравится больше других, так как он не требует принуждения NA к 0
Майкл Беллхаус
И лучше, чем grep, потому что легче справляться с такими вещами, как x4: x11
Дов Розенберг
32

Я бы использовал сопоставление регулярных выражений для суммирования переменных с определенными именами шаблонов. Например:

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

Таким образом, вы можете создать более одной переменной как сумму определенной группы переменных вашего фрейма данных.

Эрик Чакон
источник
отличное решение! Я искал конкретную функцию dplyr, делающую это в последних выпусках, но не
смог
Это отличное решение. Если есть столбцы, которые вы не хотите включать, вам просто нужно разработать оператор grep () для выбора столбцов, соответствующих определенному шаблону.
Трентон Хоффман
1
@TrentonHoffman вот бит отмены выбора столбцов определенного шаблона. просто нужен -знак:rowSums(.[-grep("x[3-5]", names(.))], na.rm = TRUE)
alexb523
22

Я часто сталкиваюсь с этой проблемой, и самый простой способ сделать это - использовать apply()функцию вmutate команде.

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

Здесь вы можете использовать все, что хотите, чтобы выбрать столбцы, используя стандартные dplyrприемы (например, starts_with()или contains()). Выполняя всю работу в рамках одной mutateкоманды, это действие может происходить где угодно в dplyrпотоке шагов обработки. Наконец, с помощью этой apply()функции у вас есть возможность использовать любую необходимую сводку, включая вашу собственную специально созданную функцию суммирования.

В качестве альтернативы, если идея использования функции, отличной от tidyverse, непривлекательна, вы можете собрать столбцы, суммировать их и, наконец, присоединить результат к исходному фрейму данных.

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

Здесь я использовал starts_with()функцию для выбора столбцов и вычисления суммы, и вы можете делать все, что хотите, со NAзначениями. Обратной стороной этого подхода является то, что, хотя он довольно гибкий, он не вписывается в dplyrпоток шагов по очистке данных.

Дерек Сондреггер
источник
3
Кажется глупым использовать, applyкогда это rowSumsбыло предназначено.
zacdav
6
В этом случае rowSumsработает очень хорошо rowMeans, но я всегда чувствовал себя немного странно, задаваясь вопросом: «Что, если вещь, которую мне нужно вычислить, не является суммой или средним значением?» Однако в 99% случаев мне приходится делать что-то подобное, это либо сумма, либо среднее значение, поэтому, возможно, дополнительная гибкость при использовании общей applyфункции не оправдана.
Дерек Сондреггер
22

Использование reduce()from purrrнемного быстрее rowSumsи определенно быстрее, чем apply, поскольку вы избегаете итерации по всем строкам и просто пользуетесь преимуществами векторизованных операций:

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

Смотрите это тайминги

skd
источник
Мне это нравится, но как бы вы это сделали, когда вам нужноna.rm = TRUE
24
@ see24 Я не уверен, что понимаю, о чем вы. Это суммирует векторы a + b + c одинаковой длины. Поскольку каждый вектор может иметь или не иметь NA в разных местах, вы не можете игнорировать их. Это сделало бы векторы невыровненными. Если вы хотите удалить значения NA, вы должны сделать это позже , например, с помощью drop_na
skd
В конце концов я поступил rowSums(select(., matches("myregex")) , na.rm = TRUE))так, потому что это то, что мне нужно с точки зрения игнорирования НП. Итак, если числаsum(NA, 5) то результат равен 5. Но вы сказали, что сокращение лучше, чем rowSumsпоэтому мне было интересно, есть ли способ использовать его в этой ситуации?
24
Понимаю. Если вы хотите получить сумму и определенно игнорировать значения NA, rowSumsверсия, вероятно, является лучшей. Главный недостаток в том, что доступны только rowSumsи rowMeans(это немного медленнее, чем сокращение, но не намного). Если вам нужно выполнить другую операцию (не сумму), тоreduce версия, вероятно, единственный вариант. Просто избегайте использования applyв этом случае.
skd
2

В более новых версиях dplyrвы можете использовать rowwise()вместе сc_across для выполнения построчной агрегации для функций, не имеющих конкретных построчных вариантов, но если построчный вариант существует, он должен быть быстрее.

Поскольку rowwise()это просто особая форма группировки и меняет способ работы глаголов, вы, вероятно, захотите передать ее по конвейеру ungroup()после выполнения построчной операции.

Чтобы выбрать диапазон строк:

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

Чтобы выбрать строки по типу:

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

В вашем конкретном случае существует построчный вариант, поэтому вы можете сделать следующее (обратите внимание на использование acrossвместо):

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

Для получения дополнительной информации см. Страницу строкам .

LMc
источник