【问题标题】:Pass function arguments by column position to mutate_at按列位置将函数参数传递给 mutate_at
【发布时间】:2019-04-16 16:55:18
【问题描述】:

我正在尝试收紧%>% 管道工作流,我需要将相同的函数应用于多个列,但每次都更改一个参数。我觉得 purrrmapinvoke 函数应该有所帮助,但我无法理解它。

我的数据框包含预期寿命、贫困率和家庭收入中位数列。我可以将所有这些列名传递给mutate_at 中的vars,使用round 作为应用于每个列的函数,并可选择提供digits 参数。但我想不出一种方法(如果存在)为与每一列关联的digits 传递不同的值。我希望预期寿命四舍五入为 1,贫困四舍五入为 2,收入四舍五入为 0。

我可以在每一列上调用mutate,但考虑到我可能有更多的列都接收相同的函数,只改变了一个额外的参数,我想要更简洁的东西。

library(tidyverse)

df <- tibble::tribble(
        ~name, ~life_expectancy,          ~poverty, ~household_income,
  "New Haven", 78.0580437642378, 0.264221051111753,  42588.7592521085
  )

在我的想象中,我可以这样做:

df %>%
  mutate_at(vars(life_expectancy, poverty, household_income), 
            round, digits = c(1, 2, 0))

但得到错误

mutate_impl(.data, dots) 中的错误: 列 life_expectancy 的长度必须为 1(行数),而不是 3

使用mutate_at 而不是mutate 只是为了拥有与我理想情况下相同的语法:

df %>%
  mutate_at(vars(life_expectancy), round, digits = 1) %>%
  mutate_at(vars(poverty), round, digits = 2) %>%
  mutate_at(vars(household_income), round, digits = 0)
#> # A tibble: 1 x 4
#>   name      life_expectancy poverty household_income
#>   <chr>               <dbl>   <dbl>            <dbl>
#> 1 New Haven            78.1    0.26            42589

对数字进行映射使用 each 列的每个 digits 选项,而不是按位置,给我 3 行,每行四舍五入到不同的位数。

df %>%
  mutate_at(vars(life_expectancy, poverty, household_income), 
            function(x) map(x, round, digits = c(1, 2, 0))) %>%
  unnest()
#> # A tibble: 3 x 4
#>   name      life_expectancy poverty household_income
#>   <chr>               <dbl>   <dbl>            <dbl>
#> 1 New Haven            78.1    0.3            42589.
#> 2 New Haven            78.1    0.26           42589.
#> 3 New Haven            78      0              42589

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

【问题讨论】:

  • 过去,当我遇到这个问题时,我最终收集了我的列,对它们进行分组,对它们进行变异,然后将它们散开。另见How do I sweep specific columns with dplyr?
  • @KonradRudolph 谢谢,我也在考虑这个问题,这是我以前使用过的一种方法,但我正在尝试找出是否可以实现超级简单的单行版本跨度>
  • @Henrik 您可能正在做某事。使用map2_dfc 可以工作,但这需要删除name 列,然后可能重新加入。我试图想象一个 map2_dfc / map_at 混合
  • 当您能够将函数列表传递给 summarise_at/mutate_at 时,似乎会更容易:github.com/tidyverse/dplyr/issues/3433。这似乎还行不通。
  • mutate 支持!!! 所以我认为最简单的方法是通过map2 或(对我来说更干净)imap 以编程方式重新创建详细的mutate 调用(不是mutate_at

标签: r dplyr purrr


【解决方案1】:

2 个解决方案


mutate!!!

invoke 是个好主意,但现在大多数 tidyverse 函数都支持 !!! 运算符,因此您不需要它,您可以这样做:

digits <- c(life_expectancy = 1, poverty = 2, household_income = 0)  
df %>% mutate(!!!imap(digits, ~round(..3[[.y]], .x),.))
# # A tibble: 1 x 4
#          name life_expectancy poverty household_income
#         <chr>           <dbl>   <dbl>            <dbl>
#   1 New Haven            78.1    0.26            42589

..3 是初始数据帧,通过调用结束时的点作为第三个参数传递给函数。

写得更明确:

df %>% mutate(!!!imap(
  digits, 
  function(digit, name, data) round(data[[name]], digit),
  data = .))

如果您需要从旧界面开始(尽管我建议的界面会更灵活),请先这样做:

digits <- setNames(c(1, 2, 0), c("life_expectancy", "poverty", "household_income"))

mutate_at&lt;&lt;-

在这里,我们尽量避免使用&lt;&lt;- 的良好做法,但可读性很重要,而且这篇文章真的很容易阅读。

digits <- c(1, 2, 0)
i <- 0
df %>%
  mutate_at(vars(life_expectancy, poverty, household_income), ~round(., digits[i<<- i+1]))
# A tibble: 1 x 4
#     name      life_expectancy poverty household_income
#     <chr>               <dbl>   <dbl>            <dbl>
#   1 New Haven            78.1    0.26            42589

(或者只是df %&gt;% mutate_at(names(digits), ~round(., digits[i&lt;&lt;- i+1])),如果您在我的第一个解决方案中使用命名向量)

【讨论】:

  • 这是正确的做法。我删除了我的答案,因为虽然控制台中的输出与 OP 结果匹配,但运行 apply(df, 1, print) 显示这些值均四舍五入到小数点后两位。
  • 这太疯狂了!那么imap 是映射到digits 及其名称,然后应用round 函数,但还要获取... 中的原始数据框?我说对了吗?
  • 另外,我很欣赏 GH 的评论!
  • 是的,你完全明白了,将lhs 传递给... 是我非常喜欢的一个技巧,为了清楚起见,我添加了一个更明确的版本。
【解决方案2】:

按照 Henrik 的评论,这是一个 map2 解决方案。然后,您可以将其包装在自定义函数中。我提供了一个粗略的第一次尝试,但我做了最少的测试,所以如果评估很奇怪,它可能会在各种情况下中断。它也没有为.at 使用tidyselect,但modify_at 也没有...

library(tidyverse)

df <- tibble::tribble(
  ~name, ~life_expectancy,          ~poverty, ~household_income,
  "New Haven", 78.0580437642378, 0.264221051111753,  42588.7592521085,
  "New York", 12.349685329, 0.324067934, 32156.230974623
)

rounded <- df %>%
  select(life_expectancy, poverty, household_income) %>%
  map2_dfc(
    .y = c(1, 2, 0),
    .f = ~ round(.x, digits = .y)
  )
df %>%
  select(-life_expectancy, -poverty, -household_income) %>%
  bind_cols(rounded)
#> # A tibble: 2 x 4
#>   name      life_expectancy poverty household_income
#>   <chr>               <dbl>   <dbl>            <dbl>
#> 1 New Haven            78.1    0.26            42589
#> 2 New York             12.3    0.32            32156


modify2_at <- function(.x, .y, .at, .f) {
  modified <- .x[.at] %>%
    map2(.y, .f)
  .x[.at] <- modified
  return(.x)
}

df %>%
  modify2_at(
    .y = c(1, 2, 0),
    .at = c("life_expectancy", "poverty", "household_income"),
    .f = ~ round(.x, digits = .y)
  )
#> # A tibble: 2 x 4
#>   name      life_expectancy poverty household_income
#>   <chr>               <dbl>   <dbl>            <dbl>
#> 1 New Haven            78.1    0.26            42589
#> 2 New York             12.3    0.32            32156

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

【讨论】:

    【解决方案3】:

    tidyeval 的乐趣:

    prepared_pairs <- 
      map2(
        set_names(syms(list("life_expectancy", "poverty", "household_income"))),
        c(1, 2, 0), 
        ~expr(round(!!.x, digits = !!.y))
      )
    
    mutate(df, !!! prepared_pairs)
    
    # # A tibble: 1 x 4
    #   name      life_expectancy poverty household_income
    #   <chr>               <dbl>   <dbl>            <dbl>
    # 1 New Haven            78.1    0.26            42589
    

    【讨论】:

    • 有趣。以这种方式对整个表达式使用 expr 与在单个变量上使用 enquo 相当吗?我仍然掌握不同的 tidyeval 动词
    • (在我所说的所有内容前加上“据我所知”):expr 更“裸露”一点,因为它不带有环境。 expr 就像没有环境的打火机quo(不是enquo
    • 我认为expr 只是quote 只是它理解!!
    • 这是一个很酷的解决方案,如果您使用我使用的 digits 的定义,它会更容易阅读:prepared_pairs &lt;- imap(digits, ~expr(round(!!.y, digits = !!.x)))。由于额外的步骤,它比我的类似解决方案更具可读性。它还具有支持分组数据的优点。我当前的解决方案没有通过点获取原始 df。
    • 谢谢!使您的数据帧对分组数据帧具有鲁棒性的一个想法是将其包装在 do 中,例如 df %&gt;% do(mutate(., !!!imap(digits, ~round(..3[[.y]], .x),.)))
    猜你喜欢
    • 2019-01-23
    • 1970-01-01
    • 1970-01-01
    • 2013-01-27
    • 1970-01-01
    • 2021-11-13
    • 2018-03-13
    • 1970-01-01
    • 2015-01-17
    相关资源
    最近更新 更多