如何使用 pipe/dplyr 为数据框的算术运算编写可读代码?

How to write a readable code for arithmetic operations on data frame using pipe/dplyr?

我想从我的整个数据集中减去一个值,同时从该操作中排除第一列。虽然有很多方法可以做到这一点,但我正在寻找一个非常易读的代码。

我从 magrittr 包中遇到 subtract(),但我无法以合理的方式将其合并到管道中。

我的数据

set.seed(12)
df <- data.frame(replicate(10,sample(1:100,10,rep=TRUE)))
df[1] <- 1:10
colnames(df) <- c("ID", "A", "B", "C", "D", "E", "F", "G", "H", "I")

> df
#    ID  A  B  C  D  E  F  G  H  I
# 1   1 91 57 26 91 83 73 14 75 16
# 2   2 82 72 32 37 18 52 80 22 59
# 3   3 82 43 84 87 85 56 74 67 38
# 4   4 38 46 20 48 55 53 66 12 18
# 5   5 90 30 64 71 58 39 12  5 66
# 6   6 48 37 19 27 88 28 42 76 83
# 7   7 13 34 84 77 13 40 40 67 10
# 8   8 56 39  4 84 32 59 37  5 50
# 9   9 68 78 13 91 40 15 80 86 79
# 10 10 24 71 77  5 88  7  5 42  6

尝试从整个数据集中减去 5,第一列除外

library(magrittr)
library(dplyr)

## first attempt
df %>%
  mutate_at(vars(-ID), funs(subtract(5)))
#    ID  A  B  C  D  E  F  G  H  I   ## while first column remains intact,
# 1   1 -5 -5 -5 -5 -5 -5 -5 -5 -5   ## the rest just gets assigned with -5.
# 2   2 -5 -5 -5 -5 -5 -5 -5 -5 -5   ## not good.
# 3   3 -5 -5 -5 -5 -5 -5 -5 -5 -5
# 4   4 -5 -5 -5 -5 -5 -5 -5 -5 -5
# 5   5 -5 -5 -5 -5 -5 -5 -5 -5 -5
# 6   6 -5 -5 -5 -5 -5 -5 -5 -5 -5
# 7   7 -5 -5 -5 -5 -5 -5 -5 -5 -5
# 8   8 -5 -5 -5 -5 -5 -5 -5 -5 -5
# 9   9 -5 -5 -5 -5 -5 -5 -5 -5 -5
# 10 10 -5 -5 -5 -5 -5 -5 -5 -5 -5

## second attempt
df %>%
  subtract(5)
#    ID  A  B  C  D  E  F  G  H  I   ## subtracts correctly, simple and sweet.
# 1  -4 86 52 21 86 78 68  9 70 11   ## however, there's no specification to 
# 2  -3 77 67 27 32 13 47 75 17 54   ## skip the first column.
# 3  -2 77 38 79 82 80 51 69 62 33
# 4  -1 33 41 15 43 50 48 61  7 13
# 5   0 85 25 59 66 53 34  7  0 61
# 6   1 43 32 14 22 83 23 37 71 78
# 7   2  8 29 79 72  8 35 35 62  5
# 8   3 51 34 -1 79 27 54 32  0 45
# 9   4 63 73  8 86 35 10 75 81 74
# 10  5 19 66 72  0 83  2  0 37  1

## third attempt
b2i_minus_five <- df[, -1] -5 
cbind(df[1], b2i_minus_five)
#    ID  A  B  C  D  E  F  G  H  I   ## gets the job done, but ugly code,
# 1   1 86 52 21 86 78 68  9 70 11   ## at least in my opinion.
# 2   2 77 67 27 32 13 47 75 17 54
# 3   3 77 38 79 82 80 51 69 62 33
# 4   4 33 41 15 43 50 48 61  7 13
# 5   5 85 25 59 66 53 34  7  0 61
# 6   6 43 32 14 22 83 23 37 71 78
# 7   7  8 29 79 72  8 35 35 62  5
# 8   8 51 34 -1 79 27 54 32  0 45
# 9   9 63 73  8 86 35 10 75 81 74
# 10 10 19 66 72  0 83  2  0 37  1

有没有办法本着第二次尝试的精神完成这项工作,希望只是增加一点点接触?

同样,这里的动机是写一个简单明了的代码,这也是我坚持使用subtract()而不是-5的原因。

谢谢!

我认为问题在于您调用 subtract() 的方式。无论如何,最新版本的 dplyr 0.8.0 有一种处理这些调用的新方法,用 list() 而不是 funs()。使用新版本,您将获得您想要获得的东西:

set.seed(12)
df <- data.frame(replicate(10,sample(1:100,10,rep=TRUE)))
df[1] <- 1:10
colnames(df) <- c("ID", "A", "B", "C", "D", "E", "F", "G", "H", "I")

library(magrittr)
library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union
packageVersion("dplyr")
#> [1] '0.8.3'

## first attempt
df %>%
  mutate_at(vars(-ID), list(~subtract(., 5)))
#>    ID  A  B  C  D  E  F  G  H  I
#> 1   1 86 52 21 86 78 68  9 70 11
#> 2   2 77 67 27 32 13 47 75 17 54
#> 3   3 77 38 79 82 80 51 69 62 33
#> 4   4 33 41 15 43 50 48 61  7 13
#> 5   5 85 25 59 66 53 34  7  0 61
#> 6   6 43 32 14 22 83 23 37 71 78
#> 7   7  8 29 79 72  8 35 35 62  5
#> 8   8 51 34 -1 79 27 54 32  0 45
#> 9   9 63 73  8 86 35 10 75 81 74
#> 10 10 19 66 72  0 83  2  0 37  1