使用dplyr在多个列之间求和

amo*_*amo 75 r dplyr

我的问题涉及在数据框的多个列中汇总值,并使用创建与此求和相对应的新列dplyr.列中的数据条目是二进制(0,1).我正在考虑一个行summarise_each或类的mutate_each函数dplyr.以下是数据框的最小示例:

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
Run Code Online (Sandbox Code Playgroud)

我可以使用类似的东西:

df <- df %>% mutate(sumrow= x1 + x2 + x3 + x4 + x5)
Run Code Online (Sandbox Code Playgroud)

但这将涉及写出每个列的名称.我有50个专栏.此外,列名称在我想要实现此操作的循环的不同迭代中发生更改,因此我想尝试避免必须提供任何列名.

我怎样才能最有效地做到这一点?任何帮助将不胜感激.

Boe*_*ern 81

怎么样

总结每一栏

df %>%
   replace(is.na(.), 0) %>%
   summarise_all(funs(sum))
Run Code Online (Sandbox Code Playgroud)

总结每一行

df %>%
   replace(is.na(.), 0) %>%
   mutate(sum = rowSums(.[1:5]))
Run Code Online (Sandbox Code Playgroud)

  • `summarise_each`在每列中总和,而所需的是每行的总和 (7认同)
  • 我知道了.你可能想给'df%>%替换(is.na(.),0)%>%select_if(is.numeric)%>%summarise_each(funs(sum))`一个镜头? (5认同)
  • 不推荐使用`summarise_all`而不是`summarise_each`。 (2认同)
  • 如果您不知道需要处理多少列,语法 `mutate(sum = rowSums(.[,-1]))` 可能会派上用场。 (2认同)

Eri*_*con 26

我会使用正则表达式匹配来对具有某些模式名称的变量求和.例如:

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

这样,您可以创建多个变量作为数据框的某些变量组的总和.


LMc*_*LMc 23

dplyr >= 1.0.0

在较新版本中,dplyr您可以使用rowwise()withc_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()
Run Code Online (Sandbox Code Playgroud)

按类型选择:

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()
Run Code Online (Sandbox Code Playgroud)

按列名选择:

您可以使用任意数量的整齐选择助手喜欢starts_withends_withcontains,等。

df %>%
    dplyr::rowwise() %>% 
    dplyr::mutate(sum_startswithx = sum(c_across(starts_with("x")), na.rm = T))
# %>% dplyr::ungroup() # you'll likely want to ungroup after using rowwise()
Run Code Online (Sandbox Code Playgroud)

按列索引选择:

df %>% 
  dplyr::rowwise() %>% 
  dplyr::mutate(sumindex = sum(c_across(c(1:4, 5)), na.rm = T))
# %>% dplyr::ungroup() # you'll likely want to ungroup after using rowwise()
Run Code Online (Sandbox Code Playgroud)

rowise()将适用于任何汇总功能。但是,在您的特定情况下,存在按行的变体 ( rowSums),因此您可以执行以下操作(请注意使用across替代),这样会更快:

df %>%
  dplyr::mutate(sumrow = rowSums(dplyr::across(x1:x5), na.rm = T))
Run Code Online (Sandbox Code Playgroud)

有关更多信息,请参阅rowwise页面。


基准测试

在这个例子中, row-wise 变体rowSums大约需要一半的时间:

library(microbenchmark)

microbenchmark(
  df %>%
    dplyr::rowwise() %>% 
    dplyr::mutate(sumrange = sum(dplyr::c_across(x1:x5), na.rm = T)),
  df %>%
    dplyr::mutate(sumrow = rowSums(dplyr::across(x1:x5), na.rm = T)),
  times = 1000L
)

    min    lq     mean  median      uq     max neval cld
 5.5256 6.256 7.024232 6.58885 7.02325 22.1911  1000   b
 2.7011 3.112 3.661106 3.41070 3.71975 32.6282  1000  a 
Run Code Online (Sandbox Code Playgroud)


Ric*_*lvo 22

如果你只想对某些列求和,我会使用这样的东西:

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)
Run Code Online (Sandbox Code Playgroud)

这样你就可以使用dplyr::select语法了.


Der*_*ger 13

我经常遇到这个问题,最简单的方法是使用命令中的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))
Run Code Online (Sandbox Code Playgroud)

在这里,您可以使用任何想要使用标准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, . )
Run Code Online (Sandbox Code Playgroud)

在这里,我使用该starts_with()函数来选择列并计算总和,您可以使用NA值执行任何操作.这种方法的缺点是虽然它非常灵活,但它并不适合dplyr数据清理步骤.

  • 在这种情况下,`rowSums`和`rowMeans`的效果非常好,但我总觉得有点奇怪,"如果我需要计算的东西不是一个总和或一个平均值怎么办?" 然而,99%的时间我必须做这样的事情,它要么是一个总和,要么是一个意思,所以使用一般的`apply`函数的额外灵活性可能不会被证明. (4认同)
  • 当这是'rowSums`的设计时,似乎很难使用`apply`. (3认同)

skd*_*skd 11

使用reduce()from purrr的速度比rowSums最终要快apply,并且肯定比快,因为您避免了遍历所有行,而只是利用了矢量化操作:

library(purrr)
library(dplyr)
iris %>% mutate(Petal = reduce(select(., starts_with("Petal")), `+`))
Run Code Online (Sandbox Code Playgroud)

看到这个计时


Agi*_*ean 8

对(几乎)所有选项进行基准测试以跨列求和

由于很难在 @skd、@LMc 和其他人给出的所有有趣答案中做出决定,因此我对所有相当长的替代方案进行了基准测试。

与其他示例的区别在于,我使用了更大的数据集(10.000 行)和来自现实世界数据集(菱形)的数据集,因此结果可能更多地反映了现实世界数据的方差。

可重现的基准测试代码是:

set.seed(17)
dataset <- diamonds %>% sample_n(1e4)
cols <- c("depth", "table", "x", "y", "z")

sum.explicit <- function() {
  dataset %>%
    mutate(sum.cols = depth + table + x + y + z)
}

sum.rowSums <- function() {
  dataset %>%
    mutate(sum.cols = rowSums(across(cols)))
}

sum.reduce <- function() {
  dataset %>%
    mutate(sum.cols = purrr::reduce(select(., cols), `+`))
}

sum.nest <- function() {
  dataset %>%
  group_by(id = row_number()) %>%
  nest(data = cols) %>%
  mutate(sum.cols = map_dbl(data, sum))
}

# NOTE: across with rowwise doesn't work with all functions!
sum.across <- function() {
  dataset %>%
    rowwise() %>%
    mutate(sum.cols = sum(across(cols)))
}

sum.c_across <- function() {
  dataset %>%
  rowwise() %>%
  mutate(sum.cols = sum(c_across(cols)))
}

sum.apply <- function() {
  dataset %>%
    mutate(sum.cols = select(., cols) %>%
             apply(1, sum, na.rm = TRUE))
}

bench <- microbenchmark::microbenchmark(
  sum.nest(),
  sum.across(),
  sum.c_across(),
  sum.apply(),
  sum.explicit(),
  sum.reduce(),
  sum.rowSums(),
  times = 10
)

bench %>% print(order = 'mean', signif = 3)
Unit: microseconds
           expr     min      lq    mean  median      uq     max neval
 sum.explicit()     796     839    1160     950    1040    3160    10
  sum.rowSums()    1430    1450    1770    1650    1800    2980    10
   sum.reduce()    1650    1700    2090    2000    2140    3300    10
    sum.apply()    9290    9400    9720    9620    9840   11000    10
 sum.c_across()  341000  348000  353000  356000  359000  360000    10
     sum.nest()  793000  827000  854000  843000  871000  945000    10
   sum.across() 4810000 4830000 4880000 4900000 4920000 4940000    10
Run Code Online (Sandbox Code Playgroud)

可视化这一点(没有异常值sum.across)有助于比较:

在此输入图像描述

结论(主观!)

  1. 尽管可读性很好,但不建议将nestrowwise/用于较大的数据集(> 100.000 行或重复操作)c_across
  2. 显式求和获胜是因为它在内部利用了求和函数的最佳向量化,这也被利用,但rowSums计算开销很小
  3. 在 tidyverse 中purrr::reduce相对较新(但在 python 中众所周知),并且在基础 R 中非常高效,因此在 Top3 中赢得了一席之地。因为显式形式写起来很麻烦,而且除了/ 、/之外没有太多向量化方法,所以我建议所有其他函数(例如)都应用。ReducerowSumsrowMeanscolSumscolMeanssdpurrr::reduce