Frequências / proporções relativas com dplyr

153

Suponha que eu queira calcular a proporção de valores diferentes dentro de cada grupo. Por exemplo, usando os mtcarsdados, como faço para calcular a frequência relativa do número de marchas por am (automático / manual) de uma só vez 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 

O que eu gostaria de alcançar:

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
fonte
1
Essas porcentagens são os números reais que você deseja? De onde eles vêm algebricamente? Ah, 79% é 15 / (15 + 4), 21% é 4 / (15 + 4) e, em seguida, para am == 1 62% é 8 / (8 + 5) etc.
Spacedman
1
@Spacedman Sim, aqueles são o número que eu quero e Frank está correta, eles somam 100% pela variável am (79 + 21) e (62 + 38) ..
jenswirf
2
Isso realmente parece estar procurando uma implementação dplyr nativa de prop.table()/ sweep(). Além disso, em outras questões algumas pessoas estão pedindo a opção de incluir zero-contagens para variáveis ou variáveis-interações
SMCI

Respostas:

285

Tente o seguinte:

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

Na vinheta dplyr :

Quando você agrupa por várias variáveis, cada resumo separa um nível do agrupamento. Isso facilita o roll-up progressivo de um conjunto de dados.

Assim, após o summarise, a última variável de agrupamento especificada em group_by'gear' é removida. Na mutateetapa, os dados são agrupados pelas variáveis ​​de agrupamento restantes, aqui 'sou'. Você pode verificar o agrupamento em cada etapa com groups.

Obviamente, o resultado do peeling depende da ordem das variáveis ​​de agrupamento na group_bychamada. Você pode querer fazer uma subseqüente group_by(am), para tornar seu código mais explícito.

Para arredondamento e pré-certificação, consulte a boa resposta de @Tyler Rinker.

Henrik
fonte
5
Eu só descobri que a solução também, mas eu não sei por que sum(n)obras sobre o amgrupo e não o geargrupo também ...
Spacedman
7
Veja a vinheta : "Quando você agrupa por várias variáveis, cada resumo separa um nível do agrupamento".
Henrik
7
Bom - se você parar depois de summarisedizer quais grupos restam. Oh dplyr rochas ...
Spacedman
Simples e claro. Eu nunca conheci a teoria da casca antes, obrigado!
Shixiang Wang 07/07/19
legais. simples e eficaz. bom trabalho!
user2550228 6/07
38

Você pode usar a count()função, que possui um comportamento diferente, dependendo da versão do dplyr:

  • dplyr 0.7.1: retorna uma tabela desagrupada : você precisa agrupar novamente poram

  • dplyr <0.7.1: retorna uma tabela agrupada , portanto não há necessidade de agrupar novamente, embora você possa querer ungroup()manipular posteriormente

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))

Isso resulta em uma tabela agrupada . Se você deseja usá-la para análises adicionais, pode ser útil remover o atributo agrupadoungroup() .

Matifou
fonte
1
Parece uma resposta inválida no dplyr0.7.1. Ele faz o cálculo da frequência geral em "engrenagem", em vez de dentro de cada nível de "am".
Edwin
30

O @ Henrik's é melhor para a usabilidade, pois isso tornará o caractere da coluna e não será mais numérico, mas corresponde ao que você pediu ...

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%

EDITAR Porque Spacedman pediu :-)

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%
Tyler Rinker
fonte
6
Você pode sempre criar uma classe S3 "porcentagem" com um formatmétodo que adiciona um sinal de porcentagem ... #overkill
Spacedman
A implementação disso também pode ser interessante: stackoverflow.com/questions/13483430/…
Spacedman
E se alguém calculasse a média, sd e SE também neste exemplo?
precisa saber é o seguinte
6

Aqui está uma função geral que implementa a solução de Henrik na dplyrversão 0.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
}
Edwin
fonte
Error in bind_rows_(x, .id) : Column am` não pode ser convertido de numérico para character`
f0nzie
5

Eu escrevi uma pequena função para esta tarefa repetida:

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

Eu posso então usá-lo como:

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

Retorna:

# 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
fonte
3

Apesar das muitas respostas, mais uma abordagem usada prop.tableem combinação com dplyrou 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
fonte
1
De longe a abordagem mais simples
Parseltongue 25/01
1

Esta resposta é baseada na resposta de Matifou.

Primeiro, modifiquei-o para garantir que não receba a coluna freq retornada como uma coluna de notação científica usando a opção scipen.

Em seguida, multiplico a resposta por 100 para obter uma porcentagem em vez de decimal para facilitar a leitura da coluna freq como porcentagem.

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