【问题标题】:Mutate data using dynamic column names in dplyr or data.table使用 dplyr 或 data.table 中的动态列名改变数据
【发布时间】:2019-12-05 16:08:50
【问题描述】:

我有一个包含许多行和以下列的数据集:id 列,一组列显示多个值的一轮测量结果(val1.xval2.xval3.x、 ...)和另一组列显示相同值的另一轮测量结果(val1.yval2.yval3.y、...)。这是一个简化的工作示例:

d <- data.table(
  id = 1:10,
  val1.x = c(1, 0, 0, 1, 0, 1, 0, 0, 1, 0),
  val2.x = c(1, 0, 1, 1, 0, 0, 0, 0, 0, 0),
  val1.y = c(0, 0, 0, 1, 0, NA, NA, 0, 1, 0),
  val2.y = c(1, 0, 0, NA, 0, 1, 0, 0, 1, 0)
)

我的目标是获得一个列出相同列的数据集,以及每个值的两个测量值中的最大值。这是上述示例所需的输出

    id val1.x val2.x val1.y val2.y val1.max val2.max
 1:  1      1      1      0      1        1        1
 2:  2      0      0      0      0        0        0
 3:  3      0      1      0      0        0        1
 4:  4      1      1      1     NA        1        1
 5:  5      0      0      0      0        0        0
 6:  6      1      0     NA      1        1        1
 7:  7      0      0     NA      0        0        0
 8:  8      0      0      0      0        0        0
 9:  9      1      0      1      1        1        1
10: 10      0      0      0      0        0        0

从示例中可以明显看出,我的最大意思是 max(..., na.rm = T)。我还有一个变量cols 已经准备好这个值了:

cols <- c('val1', 'val2')

目标

我想使用这个变量动态循环遍历列并计算最大值。

有什么好的dplyr 方法可以实现这一目标?

有什么好的data.table 方法可以实现这一目标?

注意:我确实想要使用列的顺序(因此不希望使用按顺序引用列的解决方案(例如2:3)。输入可能会更改,并且可能会更改其他列添加到值的左侧,所以我需要使用列的名称来进行计算。id 列将始终是每行唯一的。

到目前为止我已经尝试过什么

我可以像这样使用as.symbol 使等式的右侧成为动态的:

d[, .(val1.max := pmax(eval(as.symbol('val1.x')), eval(as.symbol('val2.x'))))]

但我无法让左侧变为动态。

我也尝试实现基于this SO question 的解决方案,但它给了我一个错误:

left <- "va1.x"
right <- "va1.y"
new <- "val1.max"
expr <- bquote(.(as.name(new)):=pmax(as.name(left), as.name(right), na.rm=T))
d[, eval(expr)]

【问题讨论】:

  • 如果你一个一个来做,是不是比melt的方法更手动

标签: r dplyr data.table


【解决方案1】:

data.table 中的一个选项是melt

library(data.table)
d[melt(d, measure = patterns(cols))[,
    lapply(.SD, max, na.rm = TRUE), .(id), 
    .SDcols = value1:value2], paste0(cols, ".max") :=
         .(value1, value2), on = .(id)][]
#    id val1.x val2.x val1.y val2.y val1.max val2.max
# 1:  1      1      1      0      1        1        1
# 2:  2      0      0      0      0        0        0
# 3:  3      0      1      0      0        0        1
# 4:  4      1      1      1     NA        1        1
# 5:  5      0      0      0      0        0        0
# 6:  6      1      0     NA      1        1        1
# 7:  7      0      0     NA      0        0        0
# 8:  8      0      0      0      0        0        0
# 9:  9      1      0      1      1        1        1
#10: 10      0      0      0      0        0        0

或者没有melting 的另一种选择是根据“cols”中的值对列进行子集化,并使用pmax

d[,  paste0(cols, ".max") := lapply(cols, function(pat)
     do.call(pmax, c(.SD[, grep(paste0('^', pat, '$'), 
           names(.SD)), with =  FALSE], na.rm = TRUE)))]
#    id val1.x val2.x val1.y val2.y val1.max val2.max
# 1:  1      1      1      0      1        1        1
# 2:  2      0      0      0      0        0        0
# 3:  3      0      1      0      0        0        1
# 4:  4      1      1      1     NA        1        1
# 5:  5      0      0      0      0        0        0
# 6:  6      1      0     NA      1        1        1
# 7:  7      0      0     NA      0        0        0
# 8:  8      0      0      0      0        0        0
# 9:  9      1      0      1      1        1        1
#10: 10      0      0      0      0        0        0

或使用tidyverse,使用pivot_longer 重塑为'long',通过maxsummarise_at 中的多个列进行分组并与原始数据集连接

library(dplyr)
library(tidyr)
d %>%
   pivot_longer(cols = -id, names_sep="[.]", names_to = c(".value", "group")) %>% 
   group_by(id) %>%
   summarise_at(vars(starts_with('val')),
     list(max = ~max(., na.rm = TRUE))) %>% 
   left_join(d, .)
#   id val1.x val2.x val1.y val2.y val1_max val2_max
#1   1      1      1      0      1        1        1
#2   2      0      0      0      0        0        0
#3   3      0      1      0      0        0        1
#4   4      1      1      1     NA        1        1
#5   5      0      0      0      0        0        0
#6   6      1      0     NA      1        1        1
#7   7      0      0     NA      0        0        0
#8   8      0      0      0      0        0        0
#9   9      1      0      1      1        1        1
#10 10      0      0      0      0        0        0

【讨论】:

  • 这是一个合理的解决方法,但我特意寻找使用动态列引用的解决方案。例如,我知道我可以使用 d[, .(val1.max := pmax(eval(as.symbol('val1.x')), eval(as.symbol('val2.x'))))] 之类的东西使计算的右侧动态化,但我不能让左侧动态化
  • 另外,请注意,您的 data.table 解决方案正在硬编码它可以处理的列数(在这种情况下,只有两列,因为您指定了 value1, value2)。我试图避免任何硬编码。
  • @Merik 我还提供了一个tidyverse 解决方案
  • @Merik 你可以通过pmax查看我更新的data.table解决方案
  • 另外,我认为应该将 grep 修改为 grep(paste0('^', pat, '$'), names(.SD)),这样如果我们碰巧有其他名称部分匹配的列,它们就不会被拾取
猜你喜欢
  • 2018-05-06
  • 2013-02-02
  • 2014-02-20
  • 2012-07-29
  • 1970-01-01
  • 1970-01-01
  • 2014-11-18
  • 2018-10-03
  • 2021-06-13
相关资源
最近更新 更多