Относительные частоты / пропорции с dplyr

153

Предположим, я хочу рассчитать долю различных значений в каждой группе. Например, используя 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
jenswirf
источник
1
Эти проценты являются фактическими числами, которые вы хотите? Откуда они, алгебраически? Ах, 79% - это 15 / (15 + 4), 21% - это 4 / (15 + 4), а затем для am == 1 62% - это 8 / (8 + 5) и т. Д. Понял.
Spacedman
1
@Spacedman Да, это те цифры, которые я хочу, и Фрэнк прав, они суммируют до 100% по переменным am (79 + 21) и (62 + 38) ..
jenswirf
2
Это действительно похоже на поиск нативной реализации dplyr prop.table()/ sweep(). Кроме того, в других вопросах некоторые люди просят опцию включения нулевого счета для переменных или переменных-взаимодействий
smci

Ответы:

286

Попробуй это:

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.

Хенрик
источник
5
Я только что обнаружил это решение, но я не знаю, почему sum(n)работает над amгруппой, а не над gearгруппой ...
Spacedman
7
Смотрите виньетку : «Когда вы группируете по нескольким переменным, каждая сводка снимает один уровень группировки».
Хенрик
7
Хорошо - если вы просто остановитесь после того, summariseкак он скажет, какие группы остались. О, dplyr качается ...
Spacedman
Просто и понятно. Я никогда не знал теорию отслоения прежде, спасибо!
Шиксян Ван,
отлично. просто и эффективно. прекрасная работа!
user2550228
38

Вы можете использовать count()функцию, которая, однако, имеет другое поведение в зависимости от версии dplyr:

  • dplyr 0.7.1: возвращает несгруппированную таблицу: вам нужно снова сгруппировать поam

  • dplyr <0.7.1: возвращает сгруппированную таблицу, так что нет необходимости снова группировать, хотя вы можете захотеть ungroup()для последующих манипуляций

dplyr 0.7.1

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

dplyr <0.7.1

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

В результате получается сгруппированная таблица. Если вы хотите использовать ее для дальнейшего анализа, может быть полезно удалить сгруппированный атрибут ungroup().

Matifou
источник
1
Это кажется неверным ответом на dplyr0.7.1. Это делает вычисление частоты в целом по «шестерне», а не в пределах каждого уровня «я».
Эдвин
30

@ 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%

РЕДАКТИРОВАТЬ, потому что 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%
Тайлер Ринкер
источник
6
Вы всегда можете создать S3 "процентный" класс с formatметодом, который добавляет знак процента ... #overkill
Spacedman
Реализация этого также может быть интересной: stackoverflow.com/questions/13483430/…
Spacedman
Что если в этом примере вычислить среднее значение, sd и SE?
user3655531
6

Вот общая функция, реализующая решение Хенрика на dplyr0.7.1.

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` не может быть преобразован из числового в символьный
f0nzie
5

Я написал небольшую функцию для этой повторяющейся задачи:

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
slhck
источник
3

Несмотря на множество ответов, еще один подход, который использует prop.tableв сочетании с dplyrили data.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"]
TimTeaFan
источник
1
Безусловно самый простой подход
Parseltongue
1

Этот ответ основан на ответе Матифу.

Сначала я изменил его, чтобы не возвращать столбец freq в качестве столбца научной нотации с помощью параметра scipen.

Затем я умножаю ответ на 100, чтобы получить процент, а не десятичную дробь, чтобы облегчить чтение столбца freq в процентах.

getOption("scipen") 
options("scipen"=10) 
mtcars %>%
count(am, gear) %>% 
mutate(freq = (n / sum(n)) * 100)
Jazzmine
источник