【问题标题】:Turn dplyr group_by/summarize/spread into a function将 dplyr group_by/summarize/spread 变成一个函数
【发布时间】:2019-12-20 23:51:39
【问题描述】:

我有以下数据(一些组织、kpi 测量、一长串变量(我在示例中给出了两个)。

   df <- tibble::tribble(
  ~ORG_NM, ~KPI_NM,        ~NUMR_VAL,       ~DENO_VAL,
    "AAA",   "xxx",                8,              10,
    "AAA",   "xxx",               10,              10,
    "BBB",   "xxx",                1,               7,
    "CCC",   "xxx",                9,               3,
    "CCC",   "yyy",                9,               4,
    "DDD",   "xxx",                1,               7,
    "AAA",   "yyy",                8,               3,
    "BBB",   "yyy",                6,               1
  )

我想汇总每个变量并生成一个宽表,以便每个组织只有一条记录。到目前为止,我的方法是使用需要更改的重复代码, - 使用要汇总的变量名称和应处理该变量的函数来汇总部分, - 展开部分 - value = 更改新列的名称, - rename_at - 为所有带有值的展开列添加有意义的后缀,显示用于实现它们的函数。 最后,我需要更改 full_join 中数据框的名称以附加新列。

library(tidyverse)    


df_numrtr <- df %>%    
  group_by(ORG_NM, KPI_NM) %>%    
  summarise(mean_NUM_VAL = mean(NUMR_VAL)) %>%    
  spread(key = c(KPI_NM),  mean_NUM_VAL) %>%      
  ungroup() %>%    
  rename_at(vars(-ORG_NM), function(x) paste0(x, "_num_mean"))


df_denom  <- df %>%   
  group_by(ORG_NM, KPI_NM) %>%    
  summarise(mean_DENOM_VAL = mean(DENO_VAL)) %>%    
  spread(key = c(KPI_NM),  mean_DENOM_VAL) %>%      
  ungroup() %>%    
  rename_at(vars(-ORG_NM), function(x)    
    paste0(x, "_den_mean"))



df_final <-    
  df_numrtr %>%     
  full_join(df_denom) %>%     
  select(ORG_NM, sort(names(.))) 

  ORG_NM xxx_den_mean xxx_num_mean yyy_den_mean yyy_num_mean
  <chr>         <dbl>        <dbl>        <dbl>        <dbl>
1 AAA              10            9            3            8
2 BBB               7            1            1            6
3 CCC               3            9            4            9
4 DDD               7            1           NA           NA

我想摆脱重复的代码,并拥有一个函数,该函数将接受一个变量和一个函数的名称。我想要的伪代码函数看起来像

fnSummarize <- function(df, my_org_var, my_kpi_var, my_var, my_fun ){
  df_output<-df %>%    
    group_by({{my_groupby_var}}) %>%    
    summarise(paste0({{my_var}},"_",{{my_fun}}) = my_fun({{my_var}})) %>%    
    spread(key = {{my_kpi_var}},  paste0(my_var, my_fun)) %>%      
    ungroup()  %>% 
    rename_at(vars(-{{ my_org_var}}), function(x) paste0(x, {{myfun}}))
  return(df_output)
}

如何正确地将列名和要在该过程中使用的函数(如均值、求和、中位数、标准差)注入到这样的函数中。

【问题讨论】:

    标签: r dplyr tidyverse tidyr rlang


    【解决方案1】:

    你很接近。问题是列名的组成,我将其拉到单独的行中:

    fnSummarize <- function(df, my_org_var, my_kpi_var, my_var, my_fun ){
      colName <- str_c( rlang::enexpr(my_var),"_",rlang::enexpr(my_fun) )
    
      df %>%
        group_by( {{my_org_var}}, {{my_kpi_var}} ) %>%
        summarise( !!colName := {{my_fun}}({{my_var}}) ) %>%
        spread( key = {{my_kpi_var}}, colName ) %>%
        ungroup() %>%
        rename_at( vars(-{{my_org_var}}), str_c, "_", colName )
    }
    

    列名与df_numrtrdf_denom 略有不同,但这可以通过额外的字符串操作轻松调整。我把它放在外面是为了保持干净。

    fnSummarize( df, ORG_NM, KPI_NM, NUMR_VAL, mean )
    # # A tibble: 4 x 3
    #    ORG_NM xxx_NUMR_VAL_mean yyy_NUMR_VAL_mean
    #    <chr>              <dbl>             <dbl>
    #  1 AAA                    9                 8
    #  2 BBB                    1                 6
    #  3 CCC                    9                 9
    #  4 DDD                    1                NA
    
    ## Demonstrating using sum instead of mean
    fnSummarize( df, ORG_NM, KPI_NM, DENO_VAL, sum )
    #  # A tibble: 4 x 3
    #    ORG_NM xxx_DENO_VAL_sum yyy_DENO_VAL_sum
    #    <chr>             <dbl>            <dbl>
    #  1 AAA                  20                3
    #  2 BBB                   7                1
    #  3 CCC                   3                4
    #  4 DDD                   7               NA
    

    我还想指出,您可以通过纯 dplyr 操作来解决您的任务,而无需 rlang。例如,以下是您将如何同时应用summean

    df %>% group_by( ORG_NM, KPI_NM ) %>%
      summarize_at( c("NUMR_VAL", "DENO_VAL"), list(mean=mean,sum=sum) ) %>%
      ungroup() %>% gather( "Variable", "Value", -ORG_NM, -KPI_NM ) %>% 
      mutate( Variable = map2_chr(Variable, KPI_NM, ~str_replace(.x,"VAL",.y)) ) %>%
      select( -KPI_NM ) %>% spread( Variable, Value )
    # # A tibble: 4 x 9
    #    ORG_NM DENO_xxx_mean DENO_xxx_sum DENO_yyy_mean DENO_yyy_sum NUMR_xxx_mean
    #    <chr>          <dbl>        <dbl>         <dbl>        <dbl>         <dbl>
    #  1 AAA               10           20             3            3             9
    #  2 BBB                7            7             1            1             1
    #  3 CCC                3            3             4            4             9
    #  4 DDD                7            7            NA           NA             1
    #  # … with 3 more variables: NUMR_xxx_sum <dbl>, NUMR_yyy_mean <dbl>,
    #  #   NUMR_yyy_sum <dbl>
    

    【讨论】:

    • @Artem_Sokolov 我非常感谢您的回答,将从中学到很多东西。关于你最后一个非函数纯 dplyr ...如果你想把它变成函数并传递函数(平均值,总和),你会如何处理它?将整个列表(mean=mean,sum=sum)作为参数传递还是有更优雅的方式?我也想通过 c("NUMR_VAL", "DENO_VAL")。是否应该将其作为整个论点传递? fnSummarize2(df, ORG_NM, KPI_NM, c("NUMR_VAL", "DENO_VAL"), list(mean=mean,sum=sum)) 。由于不确定如何传递 list() 东西,我使用了三点语法“...”
    • @JacekKotowski:是的,差不多。对于summarize_at,要在每列附加_sum_mean 后缀,需要命名函数列表。馈送list(mean=mean, sum=sum) 是最直接的方法,但使用一点 NSE 魔法,您也可以从 list(mean, sum) 推断函数名称。
    猜你喜欢
    • 2019-12-20
    • 2021-07-20
    • 1970-01-01
    • 2014-06-04
    • 2018-05-12
    • 2021-11-23
    • 1970-01-01
    • 2017-02-24
    • 2020-01-07
    相关资源
    最近更新 更多