使用 dplyr 跨多列求和
Sum across multiple columns with dplyr
我的问题涉及对数据框的多个列的值求和,并使用 dplyr
创建与该求和相对应的新列。列中的数据条目是二进制 (0,1)。我正在考虑 dplyr
的 summarise_each
或 mutate_each
函数的逐行模拟。以下是数据框的最小示例:
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 列。
此外,列名在我要实现它的循环的不同迭代中发生变化
操作,所以我想尽量避免提供任何列名。
我怎样才能最有效地做到这一点?
任何帮助将不胜感激。
dplyr >= 1.0.0 使用跨
使用 rowSums
对每一行求和(rowwise
适用于任何聚合,但速度较慢)
df %>%
replace(is.na(.), 0) %>%
mutate(sum = rowSums(across(where(is.numeric))))
对每一列求和
df %>%
summarise(across(everything(), ~ sum(., is.na(.), 0)))
dplyr < 1.0.0
对每一行求和
df %>%
replace(is.na(.), 0) %>%
mutate(sum = rowSums(.[1:5]))
使用 superseeded 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))
通过这种方式,您可以创建多个变量作为数据框中某组变量的总和。
我经常遇到这个问题,最简单的方法是在 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))
在这里您可以使用任何您想要的东西 select 使用标准 dplyr
技巧(例如 starts_with()
或 contains()
)的列。通过在单个 mutate
命令中完成所有工作,此操作可以在 dplyr
处理步骤流中的任何位置发生。最后,通过使用 apply()
函数,您可以灵活地使用您需要的任何摘要,包括您自己专门构建的摘要函数。
或者,如果使用 non-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()
函数来 select 列并计算总和,你可以用 NA
值做任何你想做的事情。这种方法的缺点是虽然它非常灵活,但它并不真正适合 dplyr
数据清理步骤流。
使用 purrr
中的 reduce()
比 rowSums
稍快,而且肯定比 apply
快,因为您避免遍历所有行而只是利用矢量化操作:
library(purrr)
library(dplyr)
iris %>% mutate(Petal = reduce(select(., starts_with("Petal")), `+`))
请参阅 this 了解时间安排
dplyr >= 1.0.0
在较新版本的 dplyr
中,您可以使用 rowwise()
和 c_across
对没有特定 row-wise 变体的函数执行 row-wise 聚合, 但是 如果存在 row-wise 变体,它应该比使用 rowwise
更快(例如 rowSums
, rowMeans
).
由于 rowwise()
只是一种特殊形式的分组并改变了动词的工作方式,您可能希望在完成 row-wise 操作后将其通过管道传递给 ungroup()
。
至 select 一个 名称范围 :
df %>%
rowwise() %>%
mutate(sumrange = sum(c_across(x1:x5), na.rm = T))
# %>% ungroup() # you'll likely want to ungroup after using rowwise()
至 select 按类型:
df %>%
rowwise() %>%
mutate(sumnumeric = sum(c_across(where(is.numeric)), na.rm = T))
# %>% ungroup() # you'll likely want to ungroup after using rowwise()
到select按列名:
您可以使用任意数量的 tidy selection helpers,例如 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()
至select 按列索引:
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()
将适用于 任何汇总函数 。但是,在您的特定情况下,存在 row-wise 变体 (rowSums
),因此您可以执行以下操作(请注意使用 across
),这样会更快:
df %>%
mutate(sumrow = rowSums(across(x1:x5), na.rm = T))
有关详细信息,请参阅 rowwise 上的页面。
基准测试
rowwise
使管道链非常可读并且适用于较小的数据帧。然而,它是低效的。
rowwise
对比 row-wise 变体函数
对于此示例,row-wise 变体 rowSums
快得多:
library(microbenchmark)
set.seed(1)
large_df <- slice_sample(df, n = 1E5, replace = T) # 100,000 obs
microbenchmark(
large_df %>%
rowwise() %>%
mutate(sumrange = sum(c_across(x1:x5), na.rm = T)),
large_df %>%
mutate(sumrow = rowSums(across(x1:x5), na.rm = T)),
times = 10L
)
Unit: milliseconds
min lq mean median uq max neval cld
11108.459801 11464.276501 12144.871171 12295.362251 12690.913301 12918.106801 10 b
6.533301 6.649901 7.633951 7.808201 8.296101 8.693101 10 a
没有row-wise变量函数的大数据框
如果您的函数没有 row-wise 变体并且您有一个大数据框,请考虑 long-format,它比 rowwise
更有效。虽然可能有更快的 non-tidyverse 选项,但这里有一个 tidyverse 选项(使用 tidyr::pivot_longer
):
library(tidyr)
tidyr_pivot <- function(){
large_df %>%
mutate(rn = row_number()) %>%
pivot_longer(cols = starts_with("x")) %>%
group_by(rn) %>%
summarize(std = sd(value, na.rm = T), .groups = "drop") %>%
bind_cols(large_df, .) %>%
select(-rn)
}
dplyr_rowwise <- function(){
large_df %>%
rowwise() %>%
mutate(std = sd(c_across(starts_with("x")), na.rm = T)) %>%
ungroup()
}
microbenchmark(dplyr_rowwise(),
tidyr_pivot(),
times = 10L)
Unit: seconds
expr min lq mean median uq max neval cld
dplyr_rowwise() 12.845572 13.48340 14.182836 14.30476 15.155155 15.409750 10 b
tidyr_pivot() 1.404393 1.56015 1.652546 1.62367 1.757428 1.981293 10 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
的 row-wise 输出是向量(因此 c_
),而 across
的 row-wise 输出是 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
) 存在的 row-wise 变体,那么在这种情况下应使用 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
.
的原因
如果您想使用向量对列或行求和,但在这种情况下修改 df 而不是向 df 添加新列。
可以使用扫一扫功能:
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
按row-wise顺序求和(向量+数据帧):
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
按column-wise顺序求和(向量+数据帧):
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
- 保证金 = 1 是 column-wise
- 保证金 = 2 是 row-wise。
是的。您可以使用扫描:
sweep(df, MARGIN=2, vector, `-`)
sweep(df, MARGIN=2, vector, `*`)
sweep(df, MARGIN=2, vector, `/`)
sweep(df, MARGIN=2, vector, `^`)
另一种方法是使用 Reduce 和 column-wise:
vector = 1:5
.df <- list(df, vector)
Reduce('+', .df)
对(几乎)所有选项进行基准测试以跨列求和
由于很难在@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
不推荐用于较大的数据集(> 100.000 行或重复操作)
- 显式求和获胜,因为它在内部最好地利用了求和函数的矢量化,
rowSums
也利用了它,但计算开销很小
purrr::reduce
相对new in the tidyverse(但在python中广为人知),而作为Reduce
在base R中非常高效,因此在Top3中占有一席之地.因为显式写起来很麻烦,而且除rowSums
/rowMeans
、colSums
/colMeans
外向量化的方法不多,其他的函数我都推荐(例如 sd
) 应用 purrr::reduce
.
我的问题涉及对数据框的多个列的值求和,并使用 dplyr
创建与该求和相对应的新列。列中的数据条目是二进制 (0,1)。我正在考虑 dplyr
的 summarise_each
或 mutate_each
函数的逐行模拟。以下是数据框的最小示例:
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 列。 此外,列名在我要实现它的循环的不同迭代中发生变化 操作,所以我想尽量避免提供任何列名。
我怎样才能最有效地做到这一点? 任何帮助将不胜感激。
dplyr >= 1.0.0 使用跨
使用 rowSums
对每一行求和(rowwise
适用于任何聚合,但速度较慢)
df %>%
replace(is.na(.), 0) %>%
mutate(sum = rowSums(across(where(is.numeric))))
对每一列求和
df %>%
summarise(across(everything(), ~ sum(., is.na(.), 0)))
dplyr < 1.0.0
对每一行求和
df %>%
replace(is.na(.), 0) %>%
mutate(sum = rowSums(.[1:5]))
使用 superseeded 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))
通过这种方式,您可以创建多个变量作为数据框中某组变量的总和。
我经常遇到这个问题,最简单的方法是在 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))
在这里您可以使用任何您想要的东西 select 使用标准 dplyr
技巧(例如 starts_with()
或 contains()
)的列。通过在单个 mutate
命令中完成所有工作,此操作可以在 dplyr
处理步骤流中的任何位置发生。最后,通过使用 apply()
函数,您可以灵活地使用您需要的任何摘要,包括您自己专门构建的摘要函数。
或者,如果使用 non-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()
函数来 select 列并计算总和,你可以用 NA
值做任何你想做的事情。这种方法的缺点是虽然它非常灵活,但它并不真正适合 dplyr
数据清理步骤流。
使用 purrr
中的 reduce()
比 rowSums
稍快,而且肯定比 apply
快,因为您避免遍历所有行而只是利用矢量化操作:
library(purrr)
library(dplyr)
iris %>% mutate(Petal = reduce(select(., starts_with("Petal")), `+`))
请参阅 this 了解时间安排
dplyr >= 1.0.0
在较新版本的 dplyr
中,您可以使用 rowwise()
和 c_across
对没有特定 row-wise 变体的函数执行 row-wise 聚合, 但是 如果存在 row-wise 变体,它应该比使用 rowwise
更快(例如 rowSums
, rowMeans
).
由于 rowwise()
只是一种特殊形式的分组并改变了动词的工作方式,您可能希望在完成 row-wise 操作后将其通过管道传递给 ungroup()
。
至 select 一个 名称范围 :
df %>%
rowwise() %>%
mutate(sumrange = sum(c_across(x1:x5), na.rm = T))
# %>% ungroup() # you'll likely want to ungroup after using rowwise()
至 select 按类型:
df %>%
rowwise() %>%
mutate(sumnumeric = sum(c_across(where(is.numeric)), na.rm = T))
# %>% ungroup() # you'll likely want to ungroup after using rowwise()
到select按列名:
您可以使用任意数量的 tidy selection helpers,例如 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()
至select 按列索引:
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()
将适用于 任何汇总函数 。但是,在您的特定情况下,存在 row-wise 变体 (rowSums
),因此您可以执行以下操作(请注意使用 across
),这样会更快:
df %>%
mutate(sumrow = rowSums(across(x1:x5), na.rm = T))
有关详细信息,请参阅 rowwise 上的页面。
基准测试
rowwise
使管道链非常可读并且适用于较小的数据帧。然而,它是低效的。
rowwise
对比 row-wise 变体函数
对于此示例,row-wise 变体 rowSums
快得多:
library(microbenchmark)
set.seed(1)
large_df <- slice_sample(df, n = 1E5, replace = T) # 100,000 obs
microbenchmark(
large_df %>%
rowwise() %>%
mutate(sumrange = sum(c_across(x1:x5), na.rm = T)),
large_df %>%
mutate(sumrow = rowSums(across(x1:x5), na.rm = T)),
times = 10L
)
Unit: milliseconds
min lq mean median uq max neval cld
11108.459801 11464.276501 12144.871171 12295.362251 12690.913301 12918.106801 10 b
6.533301 6.649901 7.633951 7.808201 8.296101 8.693101 10 a
没有row-wise变量函数的大数据框
如果您的函数没有 row-wise 变体并且您有一个大数据框,请考虑 long-format,它比 rowwise
更有效。虽然可能有更快的 non-tidyverse 选项,但这里有一个 tidyverse 选项(使用 tidyr::pivot_longer
):
library(tidyr)
tidyr_pivot <- function(){
large_df %>%
mutate(rn = row_number()) %>%
pivot_longer(cols = starts_with("x")) %>%
group_by(rn) %>%
summarize(std = sd(value, na.rm = T), .groups = "drop") %>%
bind_cols(large_df, .) %>%
select(-rn)
}
dplyr_rowwise <- function(){
large_df %>%
rowwise() %>%
mutate(std = sd(c_across(starts_with("x")), na.rm = T)) %>%
ungroup()
}
microbenchmark(dplyr_rowwise(),
tidyr_pivot(),
times = 10L)
Unit: seconds
expr min lq mean median uq max neval cld
dplyr_rowwise() 12.845572 13.48340 14.182836 14.30476 15.155155 15.409750 10 b
tidyr_pivot() 1.404393 1.56015 1.652546 1.62367 1.757428 1.981293 10 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
的 row-wise 输出是向量(因此 c_
),而 across
的 row-wise 输出是 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
) 存在的 row-wise 变体,那么在这种情况下应使用 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
.
如果您想使用向量对列或行求和,但在这种情况下修改 df 而不是向 df 添加新列。
可以使用扫一扫功能:
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
按row-wise顺序求和(向量+数据帧):
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
按column-wise顺序求和(向量+数据帧):
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
- 保证金 = 1 是 column-wise
- 保证金 = 2 是 row-wise。
是的。您可以使用扫描:
sweep(df, MARGIN=2, vector, `-`)
sweep(df, MARGIN=2, vector, `*`)
sweep(df, MARGIN=2, vector, `/`)
sweep(df, MARGIN=2, vector, `^`)
另一种方法是使用 Reduce 和 column-wise:
vector = 1:5
.df <- list(df, vector)
Reduce('+', .df)
对(几乎)所有选项进行基准测试以跨列求和
由于很难在@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
不推荐用于较大的数据集(> 100.000 行或重复操作) - 显式求和获胜,因为它在内部最好地利用了求和函数的矢量化,
rowSums
也利用了它,但计算开销很小 purrr::reduce
相对new in the tidyverse(但在python中广为人知),而作为Reduce
在base R中非常高效,因此在Top3中占有一席之地.因为显式写起来很麻烦,而且除rowSums
/rowMeans
、colSums
/colMeans
外向量化的方法不多,其他的函数我都推荐(例如sd
) 应用purrr::reduce
.