Fréquences / proportions relatives avec dplyr

153

Supposons que je veuille calculer la proportion de valeurs différentes dans chaque groupe. Par exemple, en utilisant les mtcarsdonnées, comment calculer la fréquence relative du nombre de vitesses par heure (automatique / manuel) en une seule fois 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 

Ce que je souhaite réaliser:

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
la source
1
Ces pourcentages sont-ils les chiffres réels que vous voulez? D'où viennent-ils, algébriquement? Ah, 79% est 15 / (15 + 4), 21% est 4 / (15 + 4) et puis pour am == 1 62% est 8 / (8 + 5) etc.
Spacedman
1
@Spacedman Oui, ce sont les nombres que je veux et Frank a raison, ils totalisent 100% par la variable am (79 + 21) et (62 + 38) ..
jenswirf
2
Cela semble vraiment être à la recherche d'une implémentation native de dplyr de prop.table()/ sweep(). De plus, dans d'autres questions, certaines personnes demandent la possibilité d'inclure des comptes nuls pour les variables ou les interactions de variables
smci

Réponses:

286

Essaye ça:

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

À partir de la vignette dplyr :

Lorsque vous groupez par plusieurs variables, chaque résumé décolle un niveau du groupement. Cela facilite le cumul progressif d'un ensemble de données.

Ainsi, après le summarise, la dernière variable de regroupement spécifiée dans group_by«vitesse» est détachée. Dans l' mutateétape, les données sont regroupées par la ou les variables de regroupement restantes, ici «am». Vous pouvez vérifier le regroupement à chaque étape avec groups.

Le résultat du pelage dépend bien sûr de l'ordre des variables de regroupement dans l' group_byappel. Vous voudrez peut-être faire un suivant group_by(am), pour rendre votre code plus explicite.

Pour arrondir et embellir, veuillez vous référer à la belle réponse de @Tyler Rinker.

Henrik
la source
5
Je viens de découvrir cette solution aussi, mais je ne sais pas pourquoi sum(n)travaille sur le amgroupe et pas aussi sur le geargroupe ...
Spacedman
7
Voir la vignette : "Lorsque vous groupez par plusieurs variables, chaque résumé décolle un niveau du groupement."
Henrik
7
Bien - si vous vous arrêtez juste après, summarisecela indique quels groupes il reste. Oh dplyr rocks ...
Spacedman
Simple et clair. Je n'avais jamais connu la théorie des pelages auparavant, merci!
Shixiang Wang
agréable. simple et efficace. bon travail!
user2550228
38

Vous pouvez utiliser la count()fonction, qui a cependant un comportement différent selon la version de dplyr:

  • dplyr 0.7.1: renvoie une table non groupée : vous devez à nouveau grouper param

  • dplyr <0.7.1: renvoie une table groupée , donc pas besoin de regrouper à nouveau, même si vous voudrez peut-être le faire ungroup()pour des manipulations ultérieures

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

Cela donne un tableau groupé , si vous souhaitez l'utiliser pour une analyse plus approfondie, il peut être utile de supprimer l' attribut groupé avec ungroup().

Matifou
la source
1
Cela semble une réponse invalide sur dplyr0.7.1. Il fait le calcul de la fréquence globalement sur "gear", au lieu de chaque niveau de "am".
Edwin
30

@ Henrik est meilleur pour la convivialité car cela rendra le caractère de la colonne et non plus numérique mais correspond à ce que vous avez demandé ...

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 Parce que Spacedman l'a demandé :-)

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
la source
6
Vous pouvez toujours créer une classe S3 "pourcentage" avec une formatméthode qui ajoute un signe de pourcentage ... #overkill
Spacedman
La mise en œuvre de cela pourrait également être intéressante: stackoverflow.com/questions/13483430/…
Spacedman
Et si l'on calculait également la moyenne, la sd et l'ES dans cet exemple?
user3655531
6

Voici une fonction générale implémentant la solution de Henrik sur 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
}
Edwin
la source
Error in bind_rows_(x, .id) : Column am` ne peut pas être converti de numérique en caractère`
f0nzie
5

J'ai écrit une petite fonction pour cette tâche répétitive:

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

Je peux ensuite l'utiliser comme:

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

Il renvoie:

# 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
la source
3

Malgré les nombreuses réponses, une autre approche qui utilise prop.tableen combinaison avec 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
la source
1
De loin l'approche la plus simple
Fourchelangue
1

Cette réponse est basée sur la réponse de Matifou.

Tout d'abord, je l'ai modifié pour m'assurer que la colonne freq ne soit pas renvoyée en tant que colonne de notation scientifique en utilisant l'option scipen.

Ensuite, je multiplie la réponse par 100 pour obtenir un pourcentage plutôt qu'une décimale afin de rendre la colonne freq plus facile à lire en pourcentage.

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