【问题标题】:Dynamically construct function calls with varying arguments using dplyr and NSE使用 dplyr 和 NSE 动态构造具有不同参数的函数调用
【发布时间】:2020-03-29 16:55:41
【问题描述】:

我希望能够使用 dplyr 使用不同的分组变量/参数动态构造函数调用。函数调用的数量可能非常大,这意味着programming with dplyr 小插图中的示例不实用。理想情况下,我希望能够预先构造一个对象(例如列表),该对象存储要在每个函数调用中传递的参数/变量。下面是一个示例数据集,我们希望在其中应用一些基于更改分组变量的汇总函数。

set.seed(1)
df <- data.frame(values = sample(x = 1:10, size = 10),
                 grouping_var1 = sample(x = letters[1:2], size = 10, replace = TRUE),
                 grouping_var2 = sample(x = letters[24:26], size = 10, replace = TRUE),
                 grouping_var3 = sample(x = LETTERS[1:2], size = 10, replace = TRUE))

> df
   values grouping_var1 grouping_var2 grouping_var3
1       9             a             x             B
2       4             a             z             B
3       7             a             x             A
4       1             a             x             B
5       2             a             x             A
6       5             b             x             A
7       3             b             y             B
8      10             b             x             A
9       6             b             x             B
10      8             a             y             B

按照programming with dplyr 小插图,我们可以想出这样的解决方案:

f <- function(df, ...){
  group_var <- enquos(...)

  df %>%
    group_by(!!! group_var) %>%
    summarise_at(.vars = "values", .funs = sum) %>%
    print(n = 10)
}

> f(df, grouping_var1)
# A tibble: 2 x 2
  grouping_var1 values
  <fct>          <int>
1 a                 31
2 b                 24

> f(df, grouping_var1, grouping_var2)
# A tibble: 5 x 3
# Groups:   grouping_var1 [2]
  grouping_var1 grouping_var2 values
  <fct>         <fct>          <int>
1 a             x                 19
2 a             y                  8
3 a             z                  4
4 b             x                 21
5 b             y                  3

如果我想构造大量的调用,上面的例子是不切实际和不灵活的。另一个限制是我可能希望包含的其他信息不能轻易地一起传递或除了分组变量之外。

假设我有一个列表,其中包含我想在每个函数调用中传递的分组变量。还假设对于这些列表元素中的每一个,都有一个带有“id”的单独字段来描述所执行的分组。请参阅下面的示例:

list(group_vars = list(c("grouping_var1"),
                       c("grouping_var1", "grouping_var2"),
                       c("grouping_var1", "grouping_var3")),
     group_ids = list("var_1",
                      c("var_1_2"),
                      c("var_1_3")))

如何将这些参数/变量和 ID 列表动态传递给函数调用,并使用 dplyr 成功评估它们?假设我想在结果数据框中创建一个列,除了汇总数据之外还包含 group_ids。例如,如果我的 group_varsc("grouping_var1", "grouping_var2") 并且 group_ids"var_1_2" 对于特定的函数调用,我希望输出:

# A tibble: 5 x 4
# Groups:   grouping_var1 [2]
  grouping_var1 grouping_var2 values group_ids
  <fct>         <fct>          <int> <chr>    
1 a             x                 19 var_1_2  
2 a             y                  8 var_1_2  
3 a             z                  4 var_1_2  
4 b             x                 21 var_1_2  
5 b             y                  3 var_1_2 

我希望看到一个解决方案在没有的情况下使用现在已弃用的接受字符串的group_by_ 函数。

最后,我觉得在使用 NSE 的函数中使用 dplyr 进行编程具有这样的进入障碍,这令人相当沮丧。每当我遇到一些应该很简单的事情时,都需要花费数小时才能找到解决方案。

【问题讨论】:

    标签: r dplyr rlang nse


    【解决方案1】:

    我不确定这里的“标准”tidyverse 方法是什么,因为当我尝试为我的典型工作流程编写通用的 tidyverse 函数时,我从来没有真正意识到自己是否“做对了”,但这里是另一种方法。*

    首先,我们可以生成分组列组合的列表,而不是对它们进行硬编码。在这种情况下,列表包括 1、2 或 3 个分组列的所有可能组合,但可以根据需要缩减。

    library(tidyverse)
    
    # Generate a list of combinations of grouping variables.
    groups.list = map(1:3, ~combn(names(df)[map_lgl(df, ~!is.numeric(.))], .x, simplify=FALSE)) %>% 
      flatten
    

    下面是一个使用group_by_at的汇总函数,它可以接受字符串,所以不需要非标准的求值。此外,我们从group_vars 本身获得group.ids 值,因此我们不需要单独的参数或参数(尽管这可能需要调整,具体取决于您对分组列名称的期望)。

    # Summarise for each combination of groups
    # Generate group.ids from group_vars itself
    f2 <- function(data, group_vars) {
    
      data %>%
        group_by_at(group_vars) %>%
        summarise(values=sum(values)) %>% 
        mutate(group.ids=paste0("var_", paste(str_extract(group_vars, "[0-9]"), collapse="_")))
    
      }
    

    现在我们可以对group.list的每个元素运行run函数了

    map(groups.list, ~f2(df, .x))
    
    [[1]]
    # A tibble: 2 x 3
      grouping_var1 values group.ids
      <fct>          <int> <chr>    
    1 a                 31 var_1    
    2 b                 24 var_1    
    
    [[2]]
    # A tibble: 3 x 3
      grouping_var2 values group.ids
      <fct>          <int> <chr>    
    1 x                 40 var_2    
    2 y                 11 var_2    
    3 z                  4 var_2    
    
    [[3]]
    # A tibble: 2 x 3
      grouping_var3 values group.ids
      <fct>          <int> <chr>    
    1 A                 24 var_3    
    2 B                 31 var_3    
    
    [[4]]
    # A tibble: 5 x 4
    # Groups:   grouping_var1 [2]
      grouping_var1 grouping_var2 values group.ids
      <fct>         <fct>          <int> <chr>    
    1 a             x                 19 var_1_2  
    2 a             y                  8 var_1_2  
    3 a             z                  4 var_1_2  
    4 b             x                 21 var_1_2  
    5 b             y                  3 var_1_2  
    
    [[5]]
    # A tibble: 4 x 4
    # Groups:   grouping_var1 [2]
      grouping_var1 grouping_var3 values group.ids
      <fct>         <fct>          <int> <chr>    
    1 a             A                  9 var_1_3  
    2 a             B                 22 var_1_3  
    3 b             A                 15 var_1_3  
    4 b             B                  9 var_1_3  
    
    [[6]]
    # A tibble: 4 x 4
    # Groups:   grouping_var2 [3]
      grouping_var2 grouping_var3 values group.ids
      <fct>         <fct>          <int> <chr>    
    1 x             A                 24 var_2_3  
    2 x             B                 16 var_2_3  
    3 y             B                 11 var_2_3  
    4 z             B                  4 var_2_3  
    
    [[7]]
    # A tibble: 7 x 5
    # Groups:   grouping_var1, grouping_var2 [5]
      grouping_var1 grouping_var2 grouping_var3 values group.ids
      <fct>         <fct>         <fct>          <int> <chr>    
    1 a             x             A                  9 var_1_2_3
    2 a             x             B                 10 var_1_2_3
    3 a             y             B                  8 var_1_2_3
    4 a             z             B                  4 var_1_2_3
    5 b             x             A                 15 var_1_2_3
    6 b             x             B                  6 var_1_2_3
    7 b             y             B                  3 var_1_2_3
    

    或者,如果你想合并所有的结果,你可以这样做:

    map(groups.list, ~f2(df, .x)) %>% 
      bind_rows() %>% 
      mutate_if(is.factor, fct_explicit_na, na_level="All") %>% 
      select(group.ids, matches("grouping"), values)
    
       group.ids grouping_var1 grouping_var2 grouping_var3 values
       <chr>     <fct>         <fct>         <fct>          <int>
     1 var_1     a             All           All               31
     2 var_1     b             All           All               24
     3 var_2     All           x             All               40
     4 var_2     All           y             All               11
     5 var_2     All           z             All                4
     6 var_3     All           All           A                 24
     7 var_3     All           All           B                 31
     8 var_1_2   a             x             All               19
     9 var_1_2   a             y             All                8
    10 var_1_2   a             z             All                4
    11 var_1_2   b             x             All               21
    12 var_1_2   b             y             All                3
    13 var_1_3   a             All           A                  9
    14 var_1_3   a             All           B                 22
    15 var_1_3   b             All           A                 15
    16 var_1_3   b             All           B                  9
    17 var_2_3   All           x             A                 24
    18 var_2_3   All           x             B                 16
    19 var_2_3   All           y             B                 11
    20 var_2_3   All           z             B                  4
    21 var_1_2_3 a             x             A                  9
    22 var_1_2_3 a             x             B                 10
    23 var_1_2_3 a             y             B                  8
    24 var_1_2_3 a             z             B                  4
    25 var_1_2_3 b             x             A                 15
    26 var_1_2_3 b             x             B                  6
    27 var_1_2_3 b             y             B                  3
    
    • 这个问题被交叉发布到RStudio Community,我也在那里添加了这个答案。

    【讨论】:

      【解决方案2】:

      在传递我们希望用作分组变量的字符串列表时,一种解决方案是使用rlang::syms()

      f <- function(df, group_var){
        # Allows us to pass dplyr variables as strings in a list
        my_group_vars <- syms(group_var$group_var) 
      
        df %>%
          group_by(!!! my_group_vars) %>%
          summarise_at(.vars = "values", .funs = sum) %>%
          mutate(group_ids = group_var$group_ids)
      }
      
      params_list <- list(
        list(group_var = c("grouping_var1"), group_ids = "var_1"),
        list(group_var = c("grouping_var1", "grouping_var2"), group_ids = "var_1_2"),
        list(group_var = c("grouping_var1", "grouping_var3"), group_ids = "var_1_3")
        )
      
      lapply(params_list, f, df = df)
      
      [[1]]
      # A tibble: 2 x 3
        grouping_var1 values group_ids
        <fct>          <int> <chr>    
      1 a                 31 var_1    
      2 b                 24 var_1    
      
      [[2]]
      # A tibble: 5 x 4
      # Groups:   grouping_var1 [2]
        grouping_var1 grouping_var2 values group_ids
        <fct>         <fct>          <int> <chr>    
      1 a             x                 19 var_1_2  
      2 a             y                  8 var_1_2  
      3 a             z                  4 var_1_2  
      4 b             x                 21 var_1_2  
      5 b             y                  3 var_1_2  
      
      [[3]]
      # A tibble: 4 x 4
      # Groups:   grouping_var1 [2]
        grouping_var1 grouping_var3 values group_ids
        <fct>         <fct>          <int> <chr>    
      1 a             A                  9 var_1_3  
      2 a             B                 22 var_1_3  
      3 b             A                 15 var_1_3  
      4 b             B                  9 var_1_3 
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-02-02
        • 2019-02-01
        • 1970-01-01
        • 1970-01-01
        • 2013-12-11
        • 1970-01-01
        相关资源
        最近更新 更多