使用dplyr对多列求和

wztqucjr  于 2023-02-17  发布在  其他
关注(0)|答案(8)|浏览(182)

我的问题涉及到对 Dataframe 中多列的值求和,并使用dplyr创建一个与此求和对应的新列。列中的数据项是二进制(0,1)。我考虑的是dplyrsummarise_eachmutate_each函数的行模拟。下面是 Dataframe 的一个最小示例:

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)

但是这会涉及到写出每一列的名字。2我有50列。3另外,在我想要实现这个操作的循环的不同迭代中,列的名字会改变,所以我想尽量避免给予任何列的名字。
我怎样才能最有效地做到这一点?任何帮助将不胜感激。

wnavrhmk

wnavrhmk1#

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

深度〈1.0.0

对每一行求和

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

使用superseededsummarise_all对每列求和:

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

gtlvzcf82#

深度〉= 1.0.0

在新版本的dplyr中,您可以将rowwise()c_across沿着使用,以便为没有特定行变量的函数执行行聚合,但是如果存在行变量,则应该比使用rowwise(例如rowSumsrowMeans)更快。
由于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 helpers,如starts_withends_withcontains等。

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页面。

更新了dplyr 1.1.0

请注意,从dplyr 1.1.0开始,添加了pick动词,目的是替换此处across的使用方式。across用于将函数应用于整理选择 Dataframe 的每列。pick用于为在整个 Dataframe 上操作的函数创建整理选择 Dataframe :

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

基准测试

rowwise使管道链可读性很强,对于较小的 Dataframe 也能很好地工作,但是效率很低。

rowwise与按行变量函数

对于本例,行方式变量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

**没有按行变量函数的大型 Dataframe **

如果你的函数没有一个行变量,并且你有一个很大的 Dataframe ,考虑一个长格式,它比rowwise更有效。虽然可能有更快的非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_横向与横向拾取

如上所述,pick是在dplyr 1.1.0中引入的,以替换这里使用across的方式,如果使用此版本或更新版本,请将across替换为pick
sum函数的特定情况下,acrossc_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的按行输出是一个单行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()

rowSumsrowMeans等可以将数字 Dataframe 作为第一个参数,这就是它们使用across的原因。

uttx8gqw

uttx8gqw3#

如果只想对某些列求和,我会使用如下代码:

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的语法。

9rbhqvlz

9rbhqvlz4#

我会使用正则表达式匹配来对具有特定模式名称的变量求和。

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

通过这种方式,您可以创建多个变量作为数据框中某组变量的总和。

vsnjm48y

vsnjm48y5#

使用purrr中的reduce()rowSums稍快,但肯定比apply快,因为您避免了对所有行进行迭代,而只是利用了矢量化操作:

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

有关计时,请参阅此内容

wooyq4lh

wooyq4lh6#

我经常遇到这个问题,最简单的方法是在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的数据清理步骤流。

rjjhvcjd

rjjhvcjd7#

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

由于很难在@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)有助于比较:

结论(主观!)

1.尽管nestrowwise/c_across可读性很好,但不推荐用于较大的数据集(〉100.000行或重复操作)
1.显式求和胜出,因为它在内部最好地利用了求和函数的矢量化,rowSums也利用了该矢量化,但计算开销很小

  1. purrr::reduce在tidyverse中相对较新(但在python中众所周知),并且作为base R中的Reduce非常高效,从而在Top3中赢得一席之地。由于显式形式编写起来很麻烦,而且除了rowSums/rowMeanscolSums/colMeans之外,矢量化方法并不多,我建议所有其他函数(例如sd)应用purrr::reduce
yptwkmov

yptwkmov8#

如果您想使用向量跨列或跨行求和,但在本例中修改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

按行顺序求和(向量+ Dataframe ):

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

按列顺序求和(向量+ Dataframe ):

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的说法相同

  • MARGIN = 1表示按列
  • MARGIN = 2是按行的。

和“是”。您可以将扫描与以下内容一起使用:

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)

相关问题