【问题标题】:How to calculate percentile with group by?如何用分组计算百分位数?
【发布时间】:2021-11-29 11:28:03
【问题描述】:

我有一个超过一万行的 data.table,它看起来像这样:

DT1 <- data.table(ID = 1:10,
                  result_2010 = c("TRUE", "FALSE", "TRUE", "FALSE", "FALSE", "TRUE", "FALSE", "FALSE", "TRUE", "FALSE"),
                  result_2011 = c("FALSE", "TRUE", "FALSE", "FALSE", "FALSE", "FALSE", "TRUE", "FALSE", "FALSE", "TRUE"),
                  years = c(15, 16.5, 31, 1, 40.2, 0.3, 12, 22.7, 19, 12))

    ID result_2010 result_2011 years
 1:  1        TRUE       FALSE  15.0
 2:  2       FALSE        TRUE  16.5
 3:  3        TRUE       FALSE  31.0
 4:  4       FALSE       FALSE   1.0
 5:  5       FALSE       FALSE  40.2
 6:  6        TRUE       FALSE   0.3
 7:  7       FALSE        TRUE  12.0
 8:  8       FALSE       FALSE  22.7
 9:  9        TRUE       FALSE  19.0
10: 10       FALSE        TRUE  12.0

对于“result_2010”和“result_2011”,我想对“年”进行百分位分析,但前提是个人的值为“TRUE”。我尝试的代码似乎可以工作,但它为“result_2010”和“result_2011”返回相同的结果,这肯定是不正确的:

DT1 %>%
  group_by(result_2010 == "TRUE") %>%
  summarise("10.quantile"= round(quantile(years,c(.10)),digits=1),
            "25.quantile"= round(quantile(years,c(.25)),digits=1),
            "Median"= round(quantile(years,c(.50)),digits=1),
            "75.quantile"= round(quantile(years,c(.75)),digits=1),
            "90.quantile"= round(quantile(years,c(.90)),digits=1),
            "Mean" = round(mean(years),digits=1))
DT1 %>%
  group_by(result_2011 == "TRUE") %>%
  summarise("10.quantile"= round(quantile(years,c(.10)),digits=1),
            "25.quantile"= round(quantile(years,c(.25)),digits=1),
            "Median"= round(quantile(years,c(.50)),digits=1),
            "75.quantile"= round(quantile(years,c(.75)),digits=1),
            "90.quantile"= round(quantile(years,c(.90)),digits=1),
            "Mean" = round(mean(years),digits=1))

谁能帮助我更正我的代码?

【问题讨论】:

  • 您可能想使用filter 而不是group_byfilter(result_2010 == "TRUE")
  • 您使用"TRUE"/"FALSE" 而不是更直接的TRUE/FALSE 是否有特殊原因?我发现高效的处理通常始于高效的数据。
  • Gabesz,任何(所有?)答案都解决了您的问题吗?
  • 我们似乎用解决方案的数量及其复杂性来压倒提问者。 Gabesh 一定害怕尝试做所有这些事情。更不用说决定给谁 15 点声望点了。他自己的问题得到了+30分:-(!PS。我再次检查了你的解决方案,当DT1中的变量result_2010result_2011logicalcharacter类型时,每次我得到错误“错误...找不到对象'值'”。

标签: r data.table percentile


【解决方案1】:

使用meltaggregate

library(data.table)
melt(DT1, c(1, 4), 2:3) |>
  transform(variable=substring(variable, 8)) |>
  subset(value == TRUE) |>
  with(aggregate(list(q=years), list(year=variable), \(x)
                 c(quantile(x), mean=mean(x))))
#   year   q.0%  q.25%  q.50%  q.75% q.100% q.mean
# 1 2010  0.300 11.325 17.000 22.000 31.000 16.325
# 2 2011 12.000 12.000 12.000 14.250 16.500 13.500

注意:请使用R>=4.1 表示|&gt; 管道和\(x) 函数简写符号(或写成function(x))。

【讨论】:

  • 我特别喜欢 dplyr 式的管道流,没有 dplyr。太糟糕了(在我看来)group-wise transform(这里没有使用,只是一般情况下)看起来不那么顺利(即,需要使用ave)。
  • @r2evans 你有没有检查过ave 代码,将lapply(split()) 隐藏在里面。
  • 是的,过去我也看过,`split&lt;-`的使用颇有启发。一般来说,dplyr 的 group_by(grp) %&gt;% mutate(a = ...) 似乎不能很好地转换为 transform(a = ave(a, grp, FUN = \(x) ...)),并且在一次转换多个变量时效果更差。
【解决方案2】:

您可以编写一个函数并在每个result 列上运行它。

library(tidyverse)

cols <- grep('result_', names(DT1), value = TRUE)

get_stats_fun <- function(DT, col) {
  DT %>%
    filter(.data[[col]] == "TRUE") %>%
    summarise("quantile" = list(round(quantile(years,c(.10,.25,.50,.75,.90)),1)),
              "median" = round(median(years), 1),
              "Mean" = round(mean(years),1)) %>%
    unnest_wider(quantile)
}

map_df(cols, ~get_stats_fun(DT1, .x), .id = 'Year') %>%
  mutate(Year = cols)

#  Year        `10%` `25%` `50%` `75%` `90%` median  Mean
#  <chr>       <dbl> <dbl> <dbl> <dbl> <dbl>  <dbl> <dbl>
#1 result_2010   4.7  11.3    17  22    27.4     17  16.3
#2 result_2011  12    12      12  14.2  15.6     12  13.5

【讨论】:

    【解决方案3】:

    melt/dcast 选项:

    library(data.table)
    tmp <- melt(DT1, c("ID", "years"), variable.name = "year"
      )[ value == "TRUE",
       ][, .(variable = c(paste0("q", c(10, 25, 50, 75, 90)), "mu"),
             value = c(quantile(years, c(0.1, 0.25, 0.5, 0.75, 0.9)), 
                      mean(years)))
        , by = .(year)]
    tmp
    #            year variable  value
    #          <fctr>   <char>  <num>
    #  1: result_2010      q10  4.710
    #  2: result_2010      q25 11.325
    #  3: result_2010      q50 17.000
    #  4: result_2010      q75 22.000
    #  5: result_2010      q90 27.400
    #  6: result_2010       mu 16.325
    #  7: result_2011      q10 12.000
    #  8: result_2011      q25 12.000
    #  9: result_2011      q50 12.000
    # 10: result_2011      q75 14.250
    # 11: result_2011      q90 15.600
    # 12: result_2011       mu 13.500
    
    dcast(tmp, year ~ variable, value.var = "value")
    #           year     mu   q10    q25   q50   q75   q90
    #         <fctr>  <num> <num>  <num> <num> <num> <num>
    # 1: result_2010 16.325  4.71 11.325    17 22.00  27.4
    # 2: result_2011 13.500 12.00 12.000    12 14.25  15.6
    

    您可以完全控制名称,只需在 "variable" 列中指定(按顺序)(您可以选择更好的名称)。

    或者一个单独的melt

    melt(DT1, c("ID", "years"), variable.name = "year"
      )[ value == "TRUE",
       ][, setNames(as.list(c(quantile(years, c(0.1, 0.25, 0.5, 0.75, 0.9)), 
                              mean(years))),
                    c(paste0("q", c(10, 25, 50, 75, 90)), "mu"))
        , by = .(year)][]
    #           year   q10    q25   q50   q75   q90     mu
    #         <fctr> <num>  <num> <num> <num> <num>  <num>
    # 1: result_2010  4.71 11.325    17 22.00  27.4 16.325
    # 2: result_2011 12.00 12.000    12 14.25  15.6 13.500
    

    名称再次被轻松控制,现在在setNames 的第二个参数中。前提是在data.table处理中返回named-list会将其转换为命名列,因此任何这样做的函数都很容易使用。

    【讨论】:

      【解决方案4】:
      library(tidyverse)
      DT1 <- tibble(ID = 1:10,
                        result_2010 = c(TRUE, FALSE, TRUE, FALSE, FALSE, TRUE, FALSE, FALSE, TRUE, FALSE),
                        result_2011 = c(FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, TRUE),
                        years = c(15, 16.5, 31, 1, 40.2, 0.3, 12, 22.7, 19, 12))
      
      fQuantMean = function(x) t(quantile(x)) %>% 
        as_tibble() %>% bind_cols(mean = mean(x))
      
      tibble(
        year = c(2010, 2011),
        data = list(DT1$years[DT1$result_2010],
                    DT1$years[DT1$result_2011])
      ) %>% group_by(year) %>% 
        group_modify(~fQuantMean(.x$data[[1]]))
      
      

      输出

      # A tibble: 2 x 7
      # Groups:   year [2]
         year  `0%` `25%` `50%` `75%` `100%`  mean
        <dbl> <dbl> <dbl> <dbl> <dbl>  <dbl> <dbl>
      1  2010   0.3  11.3    17  22     31    16.3
      2  2011  12    12      12  14.2   16.5  13.5
      

      任何有兴趣的人的更新!

      亲爱的同事们好。如您所见,每个任务都可以通过几种不同的方式来解决。所以我决定比较这里提出的方法。由于@Gabesz 提到他有 10000 次观察,我决定检查每个解决方案的性能。

      n=10000
      set.seed(1234)
      DT1 <- tibble(ID = 1:n,
                    result_2010 = sample(c(TRUE, FALSE), n, replace = TRUE),
                    result_2011 = sample(c(TRUE, FALSE), n, replace = TRUE),
                    years = rnorm(n, 20, 5))
      

      然后我做了一个小基准测试

      fQuantMean = function(x) t(quantile(x)) %>% 
        as_tibble() %>% bind_cols(mean = mean(x))
      
      fFiolka = function(){
        tibble(
          year = c(2010, 2011),
          data = list(DT1$years[DT1$result_2010],
                      DT1$years[DT1$result_2011])
        ) %>% group_by(year) %>% 
          group_modify(~fQuantMean(.x$data[[1]]))
      }
      fFiolka()
      # # A tibble: 2 x 7
      # # Groups:   year [2]
      #    year     `0%` `25%` `50%` `75%` `100%`  mean
      #    <dbl>    <dbl> <dbl> <dbl> <dbl>  <dbl> <dbl>
      # 1  2010 -0.00697  16.4  19.9  23.3   38.6  19.9
      # 2  2011 -0.633    16.5  20.0  23.4   38.6  20.0
      
      library(data.table)
      
      fjay_sf = function(){
        melt(DT1, c(1, 4), 2:3) |>
          transform(variable=substring(variable, 8)) |>
          subset(value == TRUE) |>
          with(aggregate(list(q=years), list(year=variable), \(x)
                         c(quantile(x), mean=mean(x))))
      }
      fjay_sf()
      # year         q.0%        q.25%        q.50%        q.75%       q.100%       q.mean
      # 1 2010 -0.006968224 16.447077579 19.947385976 23.348571278 38.636456902 19.944574420
      # 2 2011 -0.633138113 16.530534403 20.043636844 23.424378551 38.636456902 20.013130400
      # Warning message:
      #   In melt(DT1, c(1, 4), 2:3) :
      #   The melt generic in data.table has been passed a tbl_df and will attempt to redirect 
      #   to the relevant reshape2 method; please note that reshape2 is deprecated, and this 
      #   redirection is now deprecated as well. To continue using melt methods from reshape2
      #    while both libraries are attached, e.g. melt.list, you can prepend the namespace 
      #    like reshape2::melt(DT1). In the next version, this warning will become an error.
      
      
      cols <- grep('result_', names(DT1), value = TRUE)
      
      get_stats_fun <- function(DT, col) {
        DT %>%
          filter(.data[[col]] == "TRUE") %>%
          summarise("quantile" = list(round(quantile(years,c(.10,.25,.50,.75,.90)),1)),
                    "median" = round(median(years), 1),
                    "Mean" = round(mean(years),1)) %>%
          unnest_wider(quantile)
      }
      
      fShah = function(){
      map_df(cols, ~get_stats_fun(DT1, .x), .id = 'Year') %>%
        mutate(Year = cols)
      }
      fShah()
      # # A tibble: 2 x 8
      #   Year        `10%` `25%` `50%` `75%` `90%` median  Mean
      #   <chr>       <dbl> <dbl> <dbl> <dbl> <dbl>  <dbl> <dbl>
      # 1 result_2010  13.5  16.4  19.9  23.3  26.4   19.9  19.9
      # 2 result_2011  13.4  16.5  20    23.4  26.6   20    20  
      
      library(microbenchmark)
      ggplot2::autoplot(microbenchmark(fFiolka(), fjay_sf(), fShah(), times=100))
      

      希望上面的图表能说明一切。

      @r2evans 请不要怪我跳过了你的解决方案,但它给我带来了一些错误。

      【讨论】:

        【解决方案5】:

        这将是我的第一个答案,所以如果我做错了,请原谅我。通过仔细阅读您的问题,您希望有人帮助您改进您的代码。 请给我。

        library(tidyverse)
        library(data.table)
        
        DT1 <- data.table(ID = 1:10,
                          result_2010 = c("TRUE", "FALSE", "TRUE", "FALSE", "FALSE", "TRUE", "FALSE", "FALSE", "TRUE", "FALSE"),
                          result_2011 = c("FALSE", "TRUE", "FALSE", "FALSE", "FALSE", "FALSE", "TRUE", "FALSE", "FALSE", "TRUE"),
                          years = c(15, 16.5, 31, 1, 40.2, 0.3, 12, 22.7, 19, 12))
        DT1 %>%
          filter(result_2010 == "TRUE") %>%
          summarise("10.quantile"= round(quantile(years,c(.10)),digits=1),
                    "25.quantile"= round(quantile(years,c(.25)),digits=1),
                    "Median"= round(quantile(years,c(.50)),digits=1),
                    "75.quantile"= round(quantile(years,c(.75)),digits=1),
                    "90.quantile"= round(quantile(years,c(.90)),digits=1),
                    "Mean" = round(mean(years),digits=1))
        DT1 %>%
          filter(result_2011 == "TRUE") %>%
          summarise("10.quantile"= round(quantile(years,c(.10)),digits=1),
                    "25.quantile"= round(quantile(years,c(.25)),digits=1),
                    "Median"= round(quantile(years,c(.50)),digits=1),
                    "75.quantile"= round(quantile(years,c(.75)),digits=1),
                    "90.quantile"= round(quantile(years,c(.90)),digits=1),
                    "Mean" = round(mean(years),digits=1))
        

        在第一种情况下,它返回值 4.7、11.3、17、22、27.4、16.3。在第二种情况下,它返回 12、12、12、14.2、15.6、13.5。 我在这里看到了很多不同的答案。虽然老实说我承认其中一些我不明白(还)。我真的很喜欢 quantile%>% tibble%>% bind_cols 的解决方案。但是,请不要说我认为这很有帮助。

        【讨论】:

          猜你喜欢
          • 2021-02-26
          • 2013-08-31
          • 1970-01-01
          • 2020-10-07
          • 2011-12-29
          • 2013-06-20
          • 1970-01-01
          • 2021-09-22
          • 2017-05-15
          相关资源
          最近更新 更多