使用 mutate_at 在每列之后插入相对值(相对于 tibble 的第二列)

Using mutate_at to insert relative values after each column (relative to the second column of the tibble)

我有一个包含多列的数据框 (tibble),对于前两列之后的每一列,我想保留绝对值,但也插入相对于第二列的值。 例如,我从以下数据框开始(列名可能不同!):

df = tibble(val1 = 5:10, val2 = 10:15, val3 = 15:20); df
# A tibble: 6 x 3
   val1  val2  val3
  <int> <int> <int>
1     5    10    15
2     6    11    16
3     7    12    17
4     8    13    18
5     9    14    19
6    10    15    20

现在,对于 val2val3 列,我还想在 val2 之后和 val3 之后插入一列,显示相对于 val1。我该怎么做???

生成的小标题应如下所示:

dfrel = tibble(val1 = 5:10, val2 = 10:15, rel2 = val2/val1, val3 = 15:20, rel3 = val3/val1)
dfrel
# A tibble: 6 x 5
   val1  val2  rel2  val3  rel3
  <int> <int> <dbl> <int> <dbl>
1     5    10  2.00    15  3.00
2     6    11  1.83    16  2.67
3     7    12  1.71    17  2.43
4     8    13  1.62    18  2.25
5     9    14  1.56    19  2.11
6    10    15  1.50    20  2.00

不幸的是,我无法编写正确的 mutate_at 调用来在每个值列之后插入该相关列。事实上,我无法使用 funs() 编写 mutate_at 来通过访问其他列(按位置而不是名称)来修改列。

用相对值替换 val2 和 val3 可行(使用 lambda 函数而不是 funs),但不会按要求保留原始 val2 和 val3 列:

df %>%
     mutate_at(vars(-1), function(v) v/.[[1]])
# A tibble: 6 x 3
   val1  val2  val3
  <int> <dbl> <dbl>
1     5  2.00  3.00
2     6  1.83  2.67
3     7  1.71  2.43
4     8  1.62  2.25
5     9  1.56  2.11
6    10  1.50  2.00

我所有使用 funs() 的尝试都失败了:

df %>%
     mutate_at(vars(-1), funs(./.tbl[[1]]))
Error in mutate_impl(.data, dots) : 
  Evaluation error: object '.tbl' not found.

df %>%
     mutate_at(vars(-1), funs(function(v) v/.[[1]]))
Error in mutate_impl(.data, dots) : 
  Column `val2` is of unsupported type function

相比,一个复杂的问题是我的 val1 列没有固定名称(即它并不总是被称为 val1),所以我不能在 funs 参数中按名称使用它.另一个复杂的问题是 tibble 是动态创建的(使用大量管道运算符)并且通常不存储在变量中,所以我不能简单地除以 df[[1]]...

那么,在每列之后插入相关列(即第一列的百分比)的正确 dplyr 方法是什么?

通过将函数包装在列表中为其命名,这样 mutate_at 将创建新列。类似于以下内容(列名称可能不太理想,因此您可能需要根据需要重命名它们):

df %>% mutate_at(vars(-1), list(rel = function(v) v / .[[1]]))

# A tibble: 6 x 5
#   val1  val2  val3 val2_rel val3_rel
#  <int> <int> <int>    <dbl>    <dbl>
#1     5    10    15     2.00     3.00
#2     6    11    16     1.83     2.67
#3     7    12    17     1.71     2.43
#4     8    13    18     1.62     2.25
#5     9    14    19     1.56     2.11
#6    10    15    20     1.50     2.00

在 Psidom 的帮助下,这是我对问题的最终解决方案:

interleaveColumns = function(v) { 
    c(1, unlist(split(2:length(v), 1:((length(v)-1)/2)), use.names = FALSE)) 
}

df = tibble(val1 = 5:10, val2 = 10:15, val3 = 15:20, val4 = 25:30, val5 = 1:6);

# mutate_at can be given a named list to create a new column 
# for each existing columnt (appended to the end => we need 
# to reorder the columns and interleave the new columns with 
# the old columns using the interleaveColumns function)

df %>%
     mutate_at(vars(-1), list(rel = function(v) v/.[[1]])) %>% 
     select(interleaveColumns(.))

# A tibble: 6 x 9
   val1  val2 val2_rel  val3 val3_rel  val4 val4_rel  val5 val5_rel
  <int> <int>    <dbl> <int>    <dbl> <int>    <dbl> <int>    <dbl>
1     5    10     2.00    15     3.00    25     5.00     1    0.200
2     6    11     1.83    16     2.67    26     4.33     2    0.333
3     7    12     1.71    17     2.43    27     3.86     3    0.429
4     8    13     1.62    18     2.25    28     3.50     4    0.500
5     9    14     1.56    19     2.11    29     3.22     5    0.556
6    10    15     1.50    20     2.00    30     3.00     6    0.600