r语言 - 与dplyr的相对频率/比例



假设我想计算每个组中不同值的比例。例如,使用mtcars数据,如何通过am(自动/手动)与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

试试这个:

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小插图:

按多个变量分组时,每个摘要剥离分组的一个层次。这使得逐步上卷数据集变得容易。

因此,在summarise之后,去掉group_by中指定的最后一个分组变量"gear"。在mutate步骤中,数据按照剩余的分组变量进行分组,这里是"am"。您可以使用groups在每个步骤中检查分组。

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

关于四舍五入和修饰,请参考@Tyler Rinker的回答

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

  • dplyr 0.7.1:返回未分组的表:您需要通过am

  • 重新分组
  • dplyr & lt;0.7.1:返回一个分组的表,因此不需要再次分组,尽管您可能希望在以后的操作中使用ungroup()

dplyr是0.7.1

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

dplyr & lt;是0.7.1

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

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

@Henrik's对于可用性更好,因为这将使列字符不再是数字,而是符合您的要求…

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%

EDIT因为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%

尽管答案很多,但还有一种方法是将prop.table与'dplyr'或'data.table'结合使用。

由于'dplyr' v.>= 1.1.0,我们可以在mutate中使用.by参数:

library(dplyr)
mtcars %>%
  count(am, gear) %>% 
  mutate(freq = prop.table(n), .by = am)
#>   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' v. <1.1.0的一种方法是:

mtcars %>%
  group_by(am, gear) %>% 
  tally() %>% 
  mutate(freq = prop.table(n))
#> # A tibble: 4 × 4
#> # Groups:   am [2]
#>      am  gear     n  freq
#>   <dbl> <dbl> <int> <dbl>
#> 1     0     3    15 0.789
#> 2     0     4     4 0.211
#> 3     1     4     8 0.615
#> 4     1     5     5 0.385

的数据。我们可以做的表:

library(data.table)
cars_dt <- as.data.table(mtcars)
cars_dt[, .(n = .N), keyby = .(am, gear)][, freq := prop.table(n), by = "am"][]
#>    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

创建于2022-10-22使用reprex v2.0.2

为了这个流行问题的完整性,从dplyr的1.0.0版本开始,参数。groups控制group_by汇总帮助后summarise函数的分组结构。

对于.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",删除所有级别的分组。结果被转换成一个独立的代码,没有之前的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",每一行都是它自己的组。它相当于"keep";在这种情况下

# .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包(v0.3.0)创建于2020-11-09

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

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

下面是在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
}

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

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

以下是使用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

这个答案是基于马福的回答。

首先,我修改了它,以确保不会通过使用scipen选项获得作为科学符号列返回的频率列。

然后我将答案乘以100,得到一个百分比,而不是小数,使频率列更容易作为百分比阅读。

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

最新更新