【问题标题】:Why does my dplyr percentile calculation not work with tidy evaluation?为什么我的 dplyr 百分位数计算不适用于 tidy 评估?
【发布时间】:2020-06-06 06:23:51
【问题描述】:

我有一个学生测试数据,我希望使用dplyr 将这些数据转换为百分位数。为了有一个最小的例子,想象一下三个学生的以下设置。

require(tidyverse)

tbl <- tibble(Name = c("Alice", "Bob", "Cat"), Test = c(16, 13, 15))

以下代码有效并产生所需的输出。

tbl %>% mutate(TestPercentile = cume_dist(Test) * 100)

# A tibble: 3 x 3
  Name   Test TestPercentile
  <chr> <dbl>          <dbl>
1 Alice    16          100  
2 Bob      13           33.3
3 Cat      15           66.7

但是,我实际上想以编程方式进行,因为有很多这样的列。

colname <- "Test"
percname <- str_c(colname, "Percentile")
tbl %>% mutate({{percname}} := cume_dist({{colname}}) * 100)

# A tibble: 3 x 3
  Name   Test TestPercentile
  <chr> <dbl>          <dbl>
1 Alice    16            100
2 Bob      13            100
3 Cat      15            100

当我尝试像这样使用 tidy 评估时,为什么 cume_dist 将所有学生的百分位数设为 100? (理想情况下,如果允许我提出第二个问题,我该如何解决?)

【问题讨论】:

    标签: r dplyr tidyeval


    【解决方案1】:

    如果您通过编程表示您想编写自己的函数,您可以这样做:

    calculate_percentile <- function(data, colname) {
    
       data %>% 
        mutate("{{colname}}Percentile" := cume_dist({{colname}} * 100))
    
    }
    
    tbl %>% 
      calculate_percentile(Test)
    
     # A tibble: 3 x 3
      Name   Test TestPercentile
      <chr> <dbl>          <dbl>
    1 Alice    16          1    
    2 Bob      13          0.333
    3 Cat      15          0.667
    

    编辑多列 新数据

    tbl <- tibble(Name = c("Alice", "Bob", "Cat"), Test = c(16, 13, 15), Test_math = c(16, 30, 55), Test_music = c(3, 78, 34))
    
    calculate_percentile <- function(data, colnames) {
    
      data %>% 
    
        mutate(across({{colnames}}, ~cume_dist(.) * 100, .names = "{col}Percentile"))
    
    }
    
    test_columns <- c("Test_math", "Test_music")
    tbl %>% 
      calculate_percentile(test_columns) 
    
    # A tibble: 3 x 6
      Name   Test Test_math Test_music Test_mathPercentile Test_musicPercentile
      <chr> <dbl>     <dbl>      <dbl>               <dbl>                <dbl>
    1 Alice    16        16          3                33.3                 33.3
    2 Bob      13        30         78                66.7                100  
    3 Cat      15        55         34               100                   66.7
    

    为什么您的解决方案不起作用?因为您的解决方案将cume_dist 逐字应用于字符串“test”:

    tbl %>% mutate({{percname}} := print({{colname}}))
    
    [1] "Test"
    # A tibble: 3 x 5
      Name   Test Test_math Test_music TestPercentile
      <chr> <dbl>     <dbl>      <dbl> <chr>         
    1 Alice    16        16          3 Test          
    2 Bob      13        30         78 Test          
    3 Cat      15        55         34 Test 
    

    为什么TestPercentile 的值为 100?因为“test”的cume_dist是1:

    cume_dist("test")
    #[1] 1
    

    所以我们需要 R 告诉我们不要评估字符串“test”本身,而是寻找具有此名称的变量,我们可以这样做:

    tbl %>% mutate({{percname}} := cume_dist(!!parse_quo(colname, env = global_env())) * 100)
    
    # A tibble: 3 x 5
      Name   Test Test_math Test_music TestPercentile
      <chr> <dbl>     <dbl>      <dbl>          <dbl>
    1 Alice    16        16          3          100  
    2 Bob      13        30         78           33.3
    3 Cat      15        55         34           66.7
    
    #Check that this uses the values of "Test" and not "Test" per se:
    tbl %>% mutate({{percname}} := print(!!parse_quo(colname, env = global_env())))
    
    [1] 16 13 15
    # A tibble: 3 x 5
      Name   Test Test_math Test_music TestPercentile
      <chr> <dbl>     <dbl>      <dbl>          <dbl>
    1 Alice    16        16          3             16
    2 Bob      13        30         78             13
    3 Cat      15        55         34             15
    

    【讨论】:

    • 谢谢——我发现问题是我使用的是带引号的字符串“Test”,而不是直接输入 Test。问题是它来自表示列名的字符串列表(在我尝试编写的实际代码中,而不是这个最小的示例)。到目前为止我能看到的最好的不是超级优雅:tbl %&gt;% mutate({{percname}} := cume_dist(tbl[[colname]]) * 100)
    • 好的。很难在没有看到您的数据的情况下提供帮助。如果你再次卡住,也许可以问另一个问题。
    • 是否有可能使您的函数不接受测试,而是接受可能从另一个来源传递的“测试”之类的字符串?
    • 恐怕这不会返回 OP 的预期输出。
    • 抱歉弄丢了。请参阅我更新的答案,了解为什么您的方法不起作用。希望这能回答您的问题。
    【解决方案2】:

    将列名作为字符串传递:

    library(dplyr)
    library(rlang)
    
    return_percentile <- function(data, colname) {
       percname <- paste0(colname, "Percentile")
       data %>% mutate({{percname}} := cume_dist(!!sym(colname)) * 100)
    }
    
    tbl %>% return_percentile("Test")
    
    # A tibble: 3 x 3
    #  Name   Test TestPercentile
    #  <chr> <dbl>          <dbl>
    #1 Alice    16          100  
    #2 Bob      13           33.3
    #3 Cat      15           66.7
    

    传递不带引号的列名:

    return_percentile <- function(data, colname) {
      percname <- paste0(deparse(substitute(colname)), "Percentile")
      data %>% mutate({{percname}} := cume_dist({{colname}}) * 100)
    }
    
    tbl %>% return_percentile(Test)
    
    # A tibble: 3 x 3
    #  Name   Test TestPercentile
    #  <chr> <dbl>          <dbl>
    #1 Alice    16          100  
    #2 Bob      13           33.3
    #3 Cat      15           66.7
    

    【讨论】:

      猜你喜欢
      • 2011-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-12
      • 2011-12-29
      • 2013-06-20
      相关资源
      最近更新 更多