【问题标题】:Relative frequencies / proportions with dplyrdplyr 的相对频率/比例
【发布时间】:2014-08-25 22:10:34
【问题描述】:

假设我想计算每个组中不同值的比例。例如,使用mtcars 数据,我如何计算 am(自动/手动)中 gears 数量的相对频率与dplyr一起去吗?

library(dplyr)
data(mtcars)
mtcars <- tbl_df(mtcars)

# count frequency
mtcars %>%
  group_by(am, gear) %>%
  summarise(n = n())

# am gear  n
#  0    3 15 
#  0    4  4 
#  1    4  8  
#  1    5  5 

我想要达到的目标:

am gear  n rel.freq
 0    3 15      0.7894737
 0    4  4      0.2105263
 1    4  8      0.6153846
 1    5  5      0.3846154

【问题讨论】:

  • 这些百分比是您想要的实际数字吗?他们从哪里来,代数?啊,79% 是 15/(15+4),21% 是 4/(15+4),然后对于 am==1 62% 是 8/(8+5) 等等。知道了。
  • @Spacedman 是的,这些是我想要的数字,Frank 是正确的,它们通过 am 变量 (79+21) 和 (62+38) 求和为 100%..
  • 这似乎真的在寻找prop.table()/sweep() 的原生 dplyr 实现。另外,在其他问题中,有些人是asking for the option to include zero-counts for variables or variable-interactions

标签: r group-by dplyr frequency


【解决方案1】:

试试这个:

mtcars %>%
  group_by(am, gear) %>%
  summarise(n = n()) %>%
  mutate(freq = n / sum(n))

#   am gear  n      freq
# 1  0    3 15 0.7894737
# 2  0    4  4 0.2105263
# 3  1    4  8 0.6153846
# 4  1    5  5 0.3846154

来自dplyr vignette

当您按多个变量分组时,每个摘要都会剥离一个分组级别。这使得逐步汇总数据集变得容易。

因此,在summarise 之后,group_by 中指定的最后一个分组变量“gear”被剥离。在mutate 步骤中,数据按剩余的分组变量分组,此处为“am”。您可以使用groups 在每个步骤中检查分组。

剥离的结果当然取决于group_by 调用中分组变量的顺序。您可能希望执行后续的group_by(am),以使您的代码更加明确。

关于四舍五入和美化,请参考@Tyler Rinker 的精彩回答。

【讨论】:

  • 我也刚刚发现了这个解决方案,但我不知道为什么sum(n) 可以在am 组上工作,而不是gear 组...
  • 参见vignette:“当您按多个变量分组时,每个摘要都会剥离一层分组。”
  • 很好 - 如果您在 summarise 之后停下来,它会说明还剩下哪些组。哦,dplyr 岩石...
  • 简单明了。我以前从不知道剥离理论,谢谢!
  • 不错。简单有效。干得好!
【解决方案2】:

@Henrik 的可用性更好,因为这将使列字符不再是数字,但与您要求的匹配...

mtcars %>%
  group_by (am, gear) %>%
  summarise (n=n()) %>%
  mutate(rel.freq = paste0(round(100 * n/sum(n), 0), "%"))

##   am gear  n rel.freq
## 1  0    3 15      79%
## 2  0    4  4      21%
## 3  1    4  8      62%
## 4  1    5  5      38%

编辑因为 Spacedman 要求 :-)

as.rel_freq <- function(x, rel_freq_col = "rel.freq", ...) {
    class(x) <- c("rel_freq", class(x))
    attributes(x)[["rel_freq_col"]] <- rel_freq_col
    x
}

print.rel_freq <- function(x, ...) {
    freq_col <- attributes(x)[["rel_freq_col"]]
    x[[freq_col]] <- paste0(round(100 * x[[freq_col]], 0), "%")   
    class(x) <- class(x)[!class(x)%in% "rel_freq"]
    print(x)
}

mtcars %>%
  group_by (am, gear) %>%
  summarise (n=n()) %>%
  mutate(rel.freq = n/sum(n)) %>%
  as.rel_freq()

## Source: local data frame [4 x 4]
## Groups: am
## 
##   am gear  n rel.freq
## 1  0    3 15      79%
## 2  0    4  4      21%
## 3  1    4  8      62%
## 4  1    5  5      38%

【讨论】:

  • 您始终可以使用添加百分号的 format 方法创建一个 S3“百分比”类...#overkill
  • 实现这个可能也很有趣:stackoverflow.com/questions/13483430/…
  • 如果在本例中也计算均值、sd 和 SE 会怎样?
【解决方案3】:

您可以使用count() 函数,但是根据dplyr 的版本,它的行为会有所不同:

  • dplyr 0.7.1:返回一个未分组表:您需要再次分组am

  • dplyr grouped 表,因此无需再次分组,尽管您可能需要 ungroup() 以供以后操作

dplyr 0.7.1

mtcars %>%
  count(am, gear) %>%
  group_by(am) %>%
  mutate(freq = n / sum(n))

dplyr

mtcars %>%
  count(am, gear) %>%
  mutate(freq = n / sum(n))

这会生成一个分组表,如果您想将其用于进一步分析,使用ungroup() 删除 grouped 属性可能会很有用。

【讨论】:

  • 这似乎是dplyr 0.7.1 上的无效答案。它对“gear”进行整体频率计算,而不是在“am”的每个级别内。
【解决方案4】:

此答案基于 Matifou 的答案。

首先,我对其进行了修改,以确保我不会使用 scipen 选项将频率列作为科学记数法列返回。

然后我将答案乘以 100 以获得百分比而不是小数,以使频率列更易于以百分比形式读取。

getOption("scipen") 
options("scipen"=10) 
mtcars %>%
count(am, gear) %>% 
mutate(freq = (n / sum(n)) * 100)

【讨论】:

    【解决方案5】:

    这是在dplyr 0.7.1 上实现 Henrik 解决方案的通用函数。

    freq_table <- function(x, 
                           group_var, 
                           prop_var) {
      group_var <- enquo(group_var)
      prop_var  <- enquo(prop_var)
      x %>% 
        group_by(!!group_var, !!prop_var) %>% 
        summarise(n = n()) %>% 
        mutate(freq = n /sum(n)) %>% 
        ungroup
    }
    

    【讨论】:

    • Error in bind_rows_(x, .id) : Column am` 无法从数字转换为字符`
    【解决方案6】:

    我为这个重复任务写了一个小函数:

    count_pct <- function(df) {
      return(
        df %>%
          tally %>% 
          mutate(n_pct = 100*n/sum(n))
      )
    }
    

    然后我可以像这样使用它:

    mtcars %>% 
      group_by(cyl) %>% 
      count_pct
    

    返回:

    # A tibble: 3 x 3
        cyl     n n_pct
      <dbl> <int> <dbl>
    1     4    11  34.4
    2     6     7  21.9
    3     8    14  43.8
    

    【讨论】:

      【解决方案7】:

      尽管有很多答案,但还有一种方法将prop.tabledplyrdata.table 结合使用。

      library("dplyr")
      mtcars %>%
          group_by(am, gear) %>%
          summarise(n = n()) %>%
          mutate(freq = prop.table(n))
      
      library("data.table")
      cars_dt <- as.data.table(mtcars)
      cars_dt[, .(n = .N), keyby = .(am, gear)][, freq := prop.table(n) , by = "am"]
      

      【讨论】:

      • 迄今为止最简单的方法
      【解决方案8】:

      这是使用 aggregateave 的基本 R 答案:

      df1 <- with(mtcars, aggregate(list(n = mpg), list(am = am, gear = gear), length))
      df1$prop <- with(df1, n/ave(n, am, FUN = sum))
      #Also with prop.table
      #df1$prop <- with(df1, ave(n, am, FUN = prop.table))
      df1
      
      #  am gear  n      prop
      #1  0    3 15 0.7894737
      #2  0    4  4 0.2105263
      #3  1    4  8 0.6153846
      #4  1    5  5 0.3846154 
      

      我们也可以使用prop.table,但输出显示不同。

      prop.table(table(mtcars$am, mtcars$gear), 1)
         
      #            3         4         5
      #  0 0.7894737 0.2105263 0.0000000
      #  1 0.0000000 0.6153846 0.3846154
      

      【讨论】:

        【解决方案9】:

        为了这个热门问题的完整性,由于dplyr的1.0.0版本,参数.groups控制summarise函数在group_by之后的分组结构summarise help .

        使用.groups = "drop_last"summarise 会丢弃最后一层分组。这是 1.0.0 版本之前获得的唯一结果。

        library(dplyr)
        library(scales)
        
        original <- mtcars %>%
          group_by (am, gear) %>%
          summarise (n=n()) %>%
          mutate(rel.freq =  scales::percent(n/sum(n), accuracy = 0.1))
        #> `summarise()` regrouping output by 'am' (override with `.groups` argument)
        
        original
        #> # A tibble: 4 x 4
        #> # Groups:   am [2]
        #>      am  gear     n rel.freq
        #>   <dbl> <dbl> <int> <chr>   
        #> 1     0     3    15 78.9%   
        #> 2     0     4     4 21.1%   
        #> 3     1     4     8 61.5%   
        #> 4     1     5     5 38.5%
        
        new_drop_last <- mtcars %>%
          group_by (am, gear) %>%
          summarise (n=n(), .groups = "drop_last") %>%
          mutate(rel.freq =  scales::percent(n/sum(n), accuracy = 0.1))
        
        dplyr::all_equal(original, new_drop_last)
        #> [1] TRUE
        

        使用.groups = "drop",所有级别的分组都将被删除。结果变成了一个独立的tibble,没有之前group_by的痕迹

        # .groups = "drop"
        new_drop <- mtcars %>%
          group_by (am, gear) %>%
          summarise (n=n(), .groups = "drop") %>%
          mutate(rel.freq =  scales::percent(n/sum(n), accuracy = 0.1))
        
        new_drop
        #> # A tibble: 4 x 4
        #>      am  gear     n rel.freq
        #>   <dbl> <dbl> <int> <chr>   
        #> 1     0     3    15 46.9%   
        #> 2     0     4     4 12.5%   
        #> 3     1     4     8 25.0%   
        #> 4     1     5     5 15.6%
        

        如果.groups = "keep",与.data 相同的分组结构(本例中为mtcars)。 summarise 不会剥离 group_by 中使用的任何变量。

        最后,.groups = "rowwise",每一行都是它自己的组。在这种情况下相当于“保持”

        # .groups = "keep"
        new_keep <- mtcars %>%
          group_by (am, gear) %>%
          summarise (n=n(), .groups = "keep") %>%
          mutate(rel.freq =  scales::percent(n/sum(n), accuracy = 0.1))
        
        new_keep
        #> # A tibble: 4 x 4
        #> # Groups:   am, gear [4]
        #>      am  gear     n rel.freq
        #>   <dbl> <dbl> <int> <chr>   
        #> 1     0     3    15 100.0%  
        #> 2     0     4     4 100.0%  
        #> 3     1     4     8 100.0%  
        #> 4     1     5     5 100.0%
        
        # .groups = "rowwise"
        new_rowwise <- mtcars %>%
          group_by (am, gear) %>%
          summarise (n=n(), .groups = "rowwise") %>%
          mutate(rel.freq =  scales::percent(n/sum(n), accuracy = 0.1))
        
        dplyr::all_equal(new_keep, new_rowwise)
        #> [1] TRUE
        

        另一个有趣的地方是,有时在应用group_bysummarise 之后,摘要行会有所帮助。

        # create a subtotal line to help readability
        subtotal_am <- mtcars %>%
          group_by (am) %>% 
          summarise (n=n()) %>%
          mutate(gear = NA, rel.freq = 1)
        #> `summarise()` ungrouping output (override with `.groups` argument)
        
        mtcars %>% group_by (am, gear) %>%
          summarise (n=n()) %>% 
          mutate(rel.freq = n/sum(n)) %>%
          bind_rows(subtotal_am) %>%
          arrange(am, gear) %>%
          mutate(rel.freq =  scales::percent(rel.freq, accuracy = 0.1))
        #> `summarise()` regrouping output by 'am' (override with `.groups` argument)
        #> # A tibble: 6 x 4
        #> # Groups:   am [2]
        #>      am  gear     n rel.freq
        #>   <dbl> <dbl> <int> <chr>   
        #> 1     0     3    15 78.9%   
        #> 2     0     4     4 21.1%   
        #> 3     0    NA    19 100.0%  
        #> 4     1     4     8 61.5%   
        #> 5     1     5     5 38.5%   
        #> 6     1    NA    13 100.0%
        

        reprex package (v0.3.0) 于 2020 年 11 月 9 日创建

        希望这个答案对您有用。

        【讨论】:

          【解决方案10】:

          另外,请尝试add_count()(绕过讨厌的 group_by .groups)。

          mtcars %>% 
            count(am, gear) %>% 
            add_count(am, wt = n, name = "nn") %>% 
            mutate(proportion = n / nn)
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2020-04-24
            • 2022-08-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-02-23
            • 2018-07-04
            相关资源
            最近更新 更多