如何将函数及其输入数据存储在指定的 tibble 列中,然后遍历行以执行?

How to store both function and its input data inside designated tibble columns, then iterate over rows to execute?

我正在尝试使用 {purrr} 包中的工具 运行 tibble 中的数据整理程序。我的方法是将我需要的一切组织在一个小标题中:

我的问题:如何使用 purrr 的映射函数说“获取存储在列 x 中的函数并将其应用于列 y[=65 中的数据=]"?

下面是一个基于 mtcarsiris 的最小示例。我想在相同的工作流程中总结每个数据集:首先子集列,然后进行一些聚合。对于聚合部分,我抢先设置了2个函数,一个数据一个。

  • summarise_iris()
  • summarise_mtcars()

然后我将我需要的所有内容组织在一个小标题中(参见下面的 trb object)。

第一部分,子集,效果很好。从下面的trb_1可以看出,dat_selected是我变异的新列,里面存放的是子集步骤的输出。

但是,第二部分不起作用。我想使用列 summarise_func 中的函数并将其应用于存储在列 dat_selected 中的数据。但它不起作用。为什么不?我特意使用 map() 因为它只将 1 个输入映射到函数。

library(purrr)
library(tibble)
library(dplyr, warn.conflicts = FALSE)

summarise_iris <- function(.dat) {
  .dat %>%
    group_by(Species) %>%
    summarise(across(starts_with("Sepal"), ~ mean(.x, na.rm = TRUE)))
}
# to test: iris %>% summarise_iris()

summarise_mtcars <- function(.dat) {
  .dat %>%
    group_by(am) %>%
    summarise(mpg_median = median(mpg))
}
# to test: mtcars %>% summarise_mtcars()

trb <- 
  tribble(~original_data, ~cols_to_select,                               ~summarise_func,
          mtcars,         c("am", "disp", "mpg"),                        ~summarise_mtcars(.),
          iris,           c("Species", "Sepal.Length", "Sepal.Width"),   ~summarise_iris(.)
  )

trb_1 <- 
  trb %>%
  mutate(dat_selected   = map2(.x = original_data, .y = cols_to_select, .f = ~select(.x, all_of(.y)))) 

trb_1
#> # A tibble: 2 x 4
#>   original_data  cols_to_select summarise_func dat_selected  
#>   <list>         <list>         <list>         <list>        
#> 1 <df [32 x 11]> <chr [3]>      <formula>      <df [32 x 3]> 
#> 2 <df [150 x 5]> <chr [3]>      <formula>      <df [150 x 3]>

trb_1 %>%
  mutate(dat_summarised = map(.x = dat_selected, .f = summarise_func))
#> Error: Problem with `mutate()` column `dat_summarised`.
#> i `dat_summarised = map(.x = dat_selected, .f = summarise_func)`.
#> x Index 1 must have length 1, not 2

reprex package (v2.0.1.9000)

于 2021-12-02 创建

如何使用我尝试合并的 in-table 方法获得所需的输出(见下文)?即:

trb_1 %>%
  mutate(dat_summarised = map(.x = dat_selected, .f = summarise_func))

## to give the desired output that's equivalent to what we get if we run:
summar_mtcars <- mtcars %>% summarise_mtcars()
summar_iris   <- iris %>% summarise_iris()

trb_1 %>%
  tibble::add_column(dat_summarised = list(summar_mtcars, summar_iris))

## # A tibble: 2 x 5
##   original_data  cols_to_select summarise_func dat_selected   dat_summarised  
##   <list>         <list>         <list>         <list>         <list>          
## 1 <df [32 x 11]> <chr [3]>      <formula>      <df [32 x 3]>  <tibble [2 x 2]>
## 2 <df [150 x 5]> <chr [3]>      <formula>      <df [150 x 3]> <tibble [3 x 3]>

更新


我不知道下面的方向是否正确,但基于 ,我想利用 rlang::as_function() 这样:

trb_1 %>%
  mutate(dat_summarised = map(.x = dat_selected, .f = ~rlang::as_function(summarise_func)))

但它现在给出了不同的错误:

x Can't convert a list to function

我会将函数存储为字符串:

trb <- 
  tribble(~original_data, ~cols_to_select,                               ~summarise_func,
          mtcars,         c("am", "disp", "mpg"),                        "summarise_mtcars",
          iris,           c("Species", "Sepal.Length", "Sepal.Width"),   "summarise_iris"
  )

然后您只需在 map 调用中使用 do.call。或者,您可以使用 mutate:

即时将函数转换为字符串
trb_2 <- trb_1 %>%
  mutate(summarise_func = as.character(summarise_func)) %>% 
  mutate(dat_summarised = map2(summarise_func, original_data, ~ do.call(what = .x, args = list(.dat = .y))))

trb_2
#> # A tibble: 2 × 5
#>   original_data  cols_to_select summarise_func   dat_selected   dat_summarised  
#>   <list>         <list>         <chr>            <list>         <list>          
#> 1 <df [32 × 11]> <chr [3]>      summarise_mtcars <df [32 × 3]>  <tibble [2 × 2]>
#> 2 <df [150 × 5]> <chr [3]>      summarise_iris   <df [150 × 3]> <tibble [3 × 3]>

reprex package (v2.0.1)

于 2021-12-02 创建

更新:如果底层函数发生变化(我现在明白了),将函数或函数名称存储为字符串可能会出现问题。问题是首先将函数放入 tibble 中。您在问题中所做的是将其存储为公式。更好的方法是 (imo) 将其存储在列表列中:

trb <- 
  tribble(~original_data, ~cols_to_select,                               ~summarise_func,
          mtcars,         c("am", "disp", "mpg"),                        list(fun = summarise_mtcars),
          iris,           c("Species", "Sepal.Length", "Sepal.Width"),   list(fun = summarise_iris)
  )

稍加改编,原来的答案是这样的:

trb_3 <- trb_1 %>%
  mutate(dat_summarised = map2(summarise_func, original_data, ~ do.call(what = .x$fun, args = list(.dat = .y))))


trb_3
#> # A tibble: 2 × 5
#>   original_data  cols_to_select summarise_func   dat_selected   dat_summarised  
#>   <list>         <list>         <list>           <list>         <list>          
#> 1 <df [32 × 11]> <chr [3]>      <named list [1]> <df [32 × 3]>  <tibble [2 × 2]>
#> 2 <df [150 × 5]> <chr [3]>      <named list [1]> <df [150 × 3]> <tibble [3 × 3]>

reprex package (v2.0.1)

于 2021-12-02 创建

我想你可以采取更简单的方法。首先,我们不需要 select 列,这是 summarize 固有的。让我们创建列来定义要分组的列、要汇总的列和要使用的函数。

library(purrr)
library(tibble)
library(dplyr, warn.conflicts = FALSE)


trb <- 
  tribble(~original_data, ~cols_to_group, ~cols_to_summarize,    ~summarise_func,
          mtcars,         "am",           "mpg",                 \(x) mean(x, na.rm = T),
          iris,           "Species",      ~starts_with("Sepal"), median
  )

\(x) mean(x, na.rm = TRUE) 语法是 R 4.1 中新的 anonymous function syntax。如果使用更早的版本,只需更改为 function(x) mean(...)

现在我们可以定义一个函数(最终在 pmap 中使用,它接受数据、分组列、要分析的列和汇总函数。

summarize_fun <- function(
  .dat, .group_cols, .summ_cols, .funs
) {
  .dat %>%
    group_by(across(!!.group_cols)) %>%
    summarize(across(!!.summ_cols, .funs))

}

现在我们可以在 mutate(pmap(...)) 中使用这些来获得我们想要的结果。我依靠 !! 来取消引用表达式,因为它适用于传递 ~starts_with("Sepal") 之类的东西,据我所知,它不适用于 {{ }}

trb_final <- trb %>%
  mutate(dat_summarized = pmap(
    list(
      .dat=original_data,
      .group_cols=cols_to_group,
      .summ_cols=cols_to_summarize,
      .funs=summarise_func
    ),
    summarize_fun
  ))

trb_final
#> # A tibble: 2 × 5
#>   original_data  cols_to_group cols_to_summarize summarise_func dat_summarized  
#>   <list>         <chr>         <list>            <list>         <list>          
#> 1 <df [32 × 11]> am            <chr [1]>         <fn>           <tibble [2 × 2]>
#> 2 <df [150 × 5]> Species       <formula>         <fn>           <tibble [3 × 3]>

trb_final$dat_summarized
#> [[1]]
#> # A tibble: 2 × 2
#>      am   mpg
#>   <dbl> <dbl>
#> 1     0  17.1
#> 2     1  24.4
#> 
#> [[2]]
#> # A tibble: 3 × 3
#>   Species    Sepal.Length Sepal.Width
#>   <fct>             <dbl>       <dbl>
#> 1 setosa              5           3.4
#> 2 versicolor          5.9         2.8
#> 3 virginica           6.5         3

一般函数

如果不是像评论中那样,我们只想应用通用函数来汇总,那么只需依靠 pmap 和 2 个参数,数据和汇总函数。

summarize_mtcars <- function(.dat) {
  .dat %>%
    group_by(am) %>%
    summarise(mpg_median = median(mpg))
}

summarize_iris <- function(.dat) {
  .dat %>%
    group_by(Species) %>%
    summarise(across(starts_with("Sepal"), ~ mean(.x, na.rm = TRUE)))
}

现在我们可以定义我们的数据框来使用原始数据和我们为数据集定义的两个 summarize_... 函数进行分析。

trb <- 
  tribble(~original_data, ~summarize_func,
          mtcars,         summarize_mtcars,
          iris,           summarize_iris
  )

然后像以前一样用pmap就可以了(当然也可以用map2

trb_final <- trb %>%
  mutate(dat_summarized = pmap(
    list(
      original_data,
      summarize_func
    ),
    \(.d, .f) .f(.d)
  ))

trb_final
#> # A tibble: 2 × 3
#>   original_data  summarize_func dat_summarized  
#>   <list>         <list>         <list>          
#> 1 <df [32 × 11]> <fn>           <tibble [2 × 2]>
#> 2 <df [150 × 5]> <fn>           <tibble [3 × 3]>

trb_final$dat_summarized
#> [[1]]
#> # A tibble: 2 × 2
#>      am mpg_median
#>   <dbl>      <dbl>
#> 1     0       17.3
#> 2     1       22.8
#> 
#> [[2]]
#> # A tibble: 3 × 3
#>   Species    Sepal.Length Sepal.Width
#>   <fct>             <dbl>       <dbl>
#> 1 setosa             5.01        3.43
#> 2 versicolor         5.94        2.77
#> 3 virginica          6.59        2.97