[英]Sum across multiple columns with dplyr
我的问题涉及对数据框的多个列的值求和,并使用dplyr
创建与该求和相对应的新列。 列中的数据条目是二进制 (0,1)。 我正在考虑 dplyr 的 summarise_each 或mutate_each
summarise_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
我可以使用类似的东西:
df <- df %>% mutate(sumrow= x1 + x2 + x3 + x4 + x5)
但这将涉及写出每一列的名称。 我有 50 列。 此外,列名在我想要实现此操作的循环的不同迭代中发生变化,因此我想尽量避免必须提供任何列名。
我怎样才能最有效地做到这一点? 任何帮助将不胜感激。
使用rowSums
对每一行rowSums
( rowwise
适用于任何rowwise
,但速度较慢)
df %>%
replace(is.na(.), 0) %>%
mutate(sum = rowSums(across(where(is.numeric))))
总结每一列
df %>%
summarise(across(everything(), ~ sum(., is.na(.), 0)))
总结每一行
df %>%
replace(is.na(.), 0) %>%
mutate(sum = rowSums(.[1:5]))
使用superseed summarise_all 对每一列summarise_all
:
df %>%
replace(is.na(.), 0) %>%
summarise_all(funs(sum))
如果你只想对某些列求和,我会使用这样的东西:
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
的语法。
我会使用正则表达式匹配来对具有特定模式名称的变量求和。 例如:
df <- df %>% mutate(sum1 = rowSums(.[grep("x[3-5]", names(.))], na.rm = TRUE),
sum_all = rowSums(.[grep("x", names(.))], na.rm = TRUE))
通过这种方式,您可以创建多个变量作为数据框的某些变量组的总和。
使用来自purrr
reduce()
比rowSums
略快,并且肯定比apply
快,因为您避免迭代所有行并仅利用矢量化操作:
library(purrr)
library(dplyr)
iris %>% mutate(Petal = reduce(select(., starts_with("Petal")), `+`))
看这个时间
在较新版本的dplyr
您可以使用rowwise()
和c_across
为没有特定行变体的函数执行行聚合,但如果存在行变体,它应该更快。
由于rowwise()
只是一种特殊的分组形式并改变了动词的工作方式,因此您可能希望在执行逐行操作后将其通过管道传递给ungroup()
。
要按名称选择范围:
df %>%
rowwise() %>%
mutate(sumrange = sum(c_across(x1:x5), na.rm = T))
# %>% ungroup() # you'll likely want to ungroup after using rowwise()
按类型选择:
df %>%
rowwise() %>%
mutate(sumnumeric = sum(c_across(where(is.numeric)), na.rm = T))
# %>% ungroup() # you'll likely want to ungroup after using rowwise()
按列名选择:
您可以使用任意数量的tidy selection helper,如starts_with
、 ends_with
、 contains
等。
df %>%
rowwise() %>%
mutate(sum_startswithx = sum(c_across(starts_with("x")), na.rm = T))
# %>% ungroup() # you'll likely want to ungroup after using rowwise()
按列索引选择:
df %>%
rowwise() %>%
mutate(sumindex = sum(c_across(c(1:4, 5)), na.rm = T))
# %>% ungroup() # you'll likely want to ungroup after using rowwise()
rowise()
将适用于任何汇总函数。 然而,在特定情况下,在行变体的话( rowSums
),所以你可以做以下的(请注意使用的across
代替),这会更快:
df %>%
mutate(sumrow = rowSums(across(x1:x5), na.rm = T))
有关更多信息,请参阅rowwise页面。
基准测试
对于此示例,行式变体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
c_across 与跨
在的特定情况下sum
函数, across
和c_across
给出相同的输出为多上面的代码的:
sum_across <- df %>%
rowwise() %>%
mutate(sumrange = sum(across(x1:x5), na.rm = T))
sum_c_across <- df %>%
rowwise() %>%
mutate(sumrange = sum(c_across(x1:x5), na.rm = T)
all.equal(sum_across, sum_c_across)
[1] TRUE
的逐行输出c_across
是一个矢量(因此c_
),而在行输出across
是1行tibble
对象:
df %>%
rowwise() %>%
mutate(c_across = list(c_across(x1:x5)),
across = list(across(x1:x5)),
.keep = "unused") %>%
ungroup()
# A tibble: 10 x 2
c_across across
<list> <list>
1 <dbl [5]> <tibble [1 x 5]>
2 <dbl [5]> <tibble [1 x 5]>
3 <dbl [5]> <tibble [1 x 5]>
4 <dbl [5]> <tibble [1 x 5]>
5 <dbl [5]> <tibble [1 x 5]>
6 <dbl [5]> <tibble [1 x 5]>
7 <dbl [5]> <tibble [1 x 5]>
8 <dbl [5]> <tibble [1 x 5]>
9 <dbl [5]> <tibble [1 x 5]>
10 <dbl [5]> <tibble [1 x 5]>
您要应用的功能将需要您使用哪个动词。 正如上图所示sum
,你几乎可以互换使用。 然而, mean
和许多其他常见函数都期望一个(数字)向量作为它的第一个参数:
class(df[1,])
"data.frame"
sum(df[1,]) # works with data.frame
[1] 4
mean(df[1,]) # does not work with data.frame
[1] NA
Warning message:
In mean.default(df[1, ]) : argument is not numeric or logical: returning NA
class(unname(unlist(df[1,])))
"numeric"
sum(unname(unlist(df[1,]))) # works with numeric vector
[1] 4
mean(unname(unlist(df[1,]))) # works with numeric vector
[1] 0.8
忽略均值 ( rowMean
) 存在的逐行变体,则在这种情况下应使用c_across
:
df %>%
rowwise() %>%
mutate(avg = mean(c_across(x1:x5), na.rm = T)) %>%
ungroup()
# A tibble: 10 x 6
x1 x2 x3 x4 x5 avg
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 1 1 0 1 1 0.8
2 0 1 1 0 1 0.6
3 0 NA 0 NA NA 0
4 NA 1 1 1 1 1
5 0 1 1 0 1 0.6
6 1 0 0 0 1 0.4
7 1 NA NA NA NA 1
8 NA NA NA 0 1 0.5
9 0 0 0 0 0 0
10 1 1 1 1 1 1
# Does not work
df %>%
rowwise() %>%
mutate(avg = mean(across(x1:x5), na.rm = T)) %>%
ungroup()
rowSums
, rowMeans
等可采取一个数字数据帧作为第一个参数,这就是为什么它们一起工作across
。
我经常遇到这个问题,最简单的方法是在mutate
命令中使用apply()
函数。
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
流。
由于很难在@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
可视化这一点(没有离群值sum.across
)有助于比较:
nest
和rowwise
/ c_across
rowSums
也利用了它,但计算开销很小purrr::reduce
在 tidyverse 中相对较新(但在 python 中众所周知),并且作为基础 R 中的Reduce
非常高效,因此在 Top3 中占有一席之地。 因为显式形式写起来很麻烦,而且除了rowSums
/ rowMeans
、 colSums
/ colMeans
之外没有太多矢量化方法,我建议所有其他函数(例如sd
)应用purrr::reduce
。如果您想使用向量对列或行求和,但在这种情况下修改 df 而不是向 df 添加新列。
可以使用扫一扫function:
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
按行顺序求和(向量+数据帧):
vector = 1:5
sweep(df, MARGIN=2, vector, `+`)
x1 x2 x3 x4 x5
1 2 3 3 5 6
2 1 3 4 4 6
3 1 NA 3 NA NA
4 NA 3 4 5 6
5 1 3 4 4 6
6 2 2 3 4 6
7 2 NA NA NA NA
8 NA NA NA 4 6
9 1 2 3 4 5
10 2 3 4 5 6
按列顺序求和(向量+数据帧):
vector <- 1:10
sweep(df, MARGIN=1, vector, `+`)
x1 x2 x3 x4 x5
1 2 2 1 2 2
2 2 3 3 2 3
3 3 NA 3 NA NA
4 NA 5 5 5 5
5 5 6 6 5 6
6 7 6 6 6 7
7 8 NA NA NA NA
8 NA NA NA 8 9
9 9 9 9 9 9
10 11 11 11 11 11
这与vector+df
相同
是的。 您可以使用扫描:
sweep(df, MARGIN=2, vector, `-`)
sweep(df, MARGIN=2, vector, `*`)
sweep(df, MARGIN=2, vector, `/`)
sweep(df, MARGIN=2, vector, `^`)
另一种方法是按列使用 Reduce:
vector = 1:5
.df <- list(df, vector)
Reduce('+', .df)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.