【问题标题】:Group and mutate with function and conditional functions arguments in R在 R 中使用函数和条件函数参数进行分组和变异
【发布时间】:2026-02-12 16:45:02
【问题描述】:

请考虑以下几点:

自定义函数CustomFun 接受多个数字参数。参数名称存储在resp 中,与函数参数名称相对应。参数值存储在 val 列中。

data.frame 包含多个患者 (id) 的信息,因此数据需要按 id 分组。

问题:

我们如何将自定义函数应用于分组 data.framedata.table,从同一数据结构中的列中获取参数?

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
library(data.table)
#> 
#> Attaching package: 'data.table'
#> The following objects are masked from 'package:dplyr':
#> 
#>     between, first, last

# The data
df.x <- data.frame(id = rep(c(1:2), each = 5),
                resp = c("val.a", "val.b", "val.c", "val.d", "val.e"),
                val = c(10, 15, NA, NA, NA,
                        1, 5, NA, NA, NA))
df.x
#>    id  resp val
#> 1   1 val.a  10
#> 2   1 val.b  15
#> 3   1 val.c  NA
#> 4   1 val.d  NA
#> 5   1 val.e  NA
#> 6   2 val.a   1
#> 7   2 val.b   5
#> 8   2 val.c  NA
#> 9   2 val.d  NA
#> 10  2 val.e  NA

# A simple function (minimal replicable example)
CustomFun <- function(a,b){
        a+b
}

期望的输出:

# Desired output
df.x %>% mutate(res = c(25, 25, NA, NA, NA, 6, 6, NA, NA, NA))
#>    id  resp val res
#> 1   1 val.a  10  25
#> 2   1 val.b  15  25
#> 3   1 val.c  NA  NA
#> 4   1 val.d  NA  NA
#> 5   1 val.e  NA  NA
#> 6   2 val.a   1   6
#> 7   2 val.b   5   6
#> 8   2 val.c  NA  NA
#> 9   2 val.d  NA  NA
#> 10  2 val.e  NA  NA

自己的方法:

这种方法在没有组时有效 (id)。对于所有非val.aval.b,在val 中没有NA 不会有问题,因为它们可以在第二步中被过滤掉。

# Approach without the need of grouping: one id only, problem: NA also assigned to val in df.z[3:5, ]

# dplyr
df.z <- df.x %>% slice(1:5)
df.z
#>   id  resp val
#> 1  1 val.a  10
#> 2  1 val.b  15
#> 3  1 val.c  NA
#> 4  1 val.d  NA
#> 5  1 val.e  NA

df.z %>% mutate(test = CustomFun(a = df.z %>% filter(resp == "val.a") %>% pull(val),
     b = df.z %>% filter(resp == "val.b") %>% pull(val))
)
#>   id  resp val test
#> 1  1 val.a  10   25
#> 2  1 val.b  15   25
#> 3  1 val.c  NA   25
#> 4  1 val.d  NA   25
#> 5  1 val.e  NA   25

# data.table
setDT(df.z)[, .(test= CustomFun(a = setDT(df.z)[resp == "val.a", val],
                        b = setDT(df.z)[resp == "val.b", val])),
         by = .(id, val, resp)]
#>    id val  resp test
#> 1:  1  10 val.a   25
#> 2:  1  15 val.b   25
#> 3:  1  NA val.c   25
#> 4:  1  NA val.d   25
#> 5:  1  NA val.e   25

# NOT working for groups =====================================

# data.frame
df.x %>%
        group_by(id) %>% 
        mutate(test = CustomFun(a = df.x %>% filter(resp == "val.a") %>% pull(val),
                                 b = df.x %>% filter(resp == "val.b") %>% pull(val))
)
#> Error in mutate_impl(.data, dots): Column `test` must be length 5 (the group size) or one, not 2

# data.table
setDT(df.x)[, .(test= CustomFun(a = setDT(df.x)[resp == "val.a", val],
                                b = setDT(df.x)[resp == "val.b", val])),
            by = .(id, val, resp)]
#>     id val  resp test
#>  1:  1  10 val.a   25
#>  2:  1  10 val.a    6
#>  3:  1  15 val.b   25
#>  4:  1  15 val.b    6
#>  5:  1  NA val.c   25
#>  6:  1  NA val.c    6
#>  7:  1  NA val.d   25
#>  8:  1  NA val.d    6
#>  9:  1  NA val.e   25
#> 10:  1  NA val.e    6
#> 11:  2   1 val.a   25
#> 12:  2   1 val.a    6
#> 13:  2   5 val.b   25
#> 14:  2   5 val.b    6
#> 15:  2  NA val.c   25
#> 16:  2  NA val.c    6
#> 17:  2  NA val.d   25
#> 18:  2  NA val.d    6
#> 19:  2  NA val.e   25
#> 20:  2  NA val.e    6

reprex package (v0.2.1) 于 2018 年 11 月 13 日创建

非常感谢!

【问题讨论】:

    标签: r dplyr data.table


    【解决方案1】:

    有 2 个不同的问题:您在 data.table 中添加了不需要的分组变量,并且您在两个版本中都错误地对数据进行了子集化。

    调整data.table

    setDT(df.x)[!is.na(val), test := CustomFun(a = val[resp == "val.a"],
                                               b = val[resp == "val.b"]), by = id]
    

    无需按respval 分组,只需按id

    对于dplyr,您可以这样做:

    df.x %>%
      group_by(id) %>% 
      mutate(test = if_else(!is.na(val), CustomFun(a = val[resp == "val.a"],
                                                   b = val[resp == "val.b"]), NA_real_)
      )
    

    两种情况下的输出:

        id  resp val test
     1:  1 val.a  10   25
     2:  1 val.b  15   25
     3:  1 val.c  NA   NA
     4:  1 val.d  NA   NA
     5:  1 val.e  NA   NA
     6:  2 val.a   1    6
     7:  2 val.b   5    6
     8:  2 val.c  NA   NA
     9:  2 val.d  NA   NA
    10:  2 val.e  NA   NA
    

    【讨论】:

      【解决方案2】:

      我们可以按组对值进行子集化(假设每个“id”只有一个“val.a”、“val.b”并添加

      library(dplyr)
      df.x %>%
          group_by(id) %>%
          mutate(res = (val[resp == 'val.a'] + val[resp == 'val.b']) * NA^(is.na(val)))
      # A tibble: 10 x 4
      # Groups:   id [2]
      #      id resp    val   res
      #   <int> <fct> <dbl> <dbl>
      # 1     1 val.a    10    25
      # 2     1 val.b    15    25
      # 3     1 val.c    NA    NA
      # 4     1 val.d    NA    NA
      # 5     1 val.e    NA    NA
      # 6     2 val.a     1     6
      # 7     2 val.b     5     6
      # 8     2 val.c    NA    NA
      # 9     2 val.d    NA    NA
      #10     2 val.e    NA    NA
      

      或者另一种选择是filter,按组执行summarize,然后加入原始数据集

      df.x %>% 
         filter(resp %in% c('val.a', 'val.b')) %>% 
         group_by(id) %>% 
         summarise(res = sum(val)) %>%
         right_join(df.x) %>%
         mutate(res = replace(res, is.na(val), NA))
      

      【讨论】:

      • 感谢您的回答。尽管它返回了正确的结果,但它并没有考虑到自定义函数,这是我努力的重要组成部分。