Frequenze / proporzioni relative con dplyr


153

Supponiamo che io voglia calcolare la proporzione di valori diversi all'interno di ciascun gruppo. Ad esempio, utilizzando i mtcarsdati, come posso calcolare la frequenza relativa del numero di marce per am (automatica / manuale) in una volta sola 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 

Cosa vorrei ottenere:

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

1
Quelle percentuali sono i numeri reali che desideri? Da dove vengono, algebricamente? Ah, il 79% è 15 / (15 + 4), il 21% è 4 / (15 + 4) e quindi per am == 1 il 62% è 8 / (8 + 5) ecc.
Spacedman

1
@Spacedman Sì, quelli sono il numero che voglio e Frank ha ragione, si sommano al 100% con la variabile am (79 + 21) e (62 + 38) ..
jenswirf

2
Sembra proprio che stia cercando un'implementazione dplyr nativa di prop.table()/ sweep(). Inoltre, in altre domande alcune persone chiedono l'opzione per includere conteggi zero per variabili o interazioni variabili
smci

Risposte:


285

Prova questo:

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

Dalla vignetta dplyr :

Quando si raggruppano per più variabili, ciascun riepilogo si stacca da un livello del raggruppamento. Ciò semplifica il rollup progressivo di un set di dati.

Quindi, dopo il summarise, l'ultima variabile di raggruppamento specificata in group_by, 'gear', viene staccata. Nel mutatepassaggio, i dati sono raggruppati in base alle variabili di raggruppamento rimanenti, qui 'am'. È possibile verificare il raggruppamento in ogni passaggio con groups.

Il risultato del peeling dipende ovviamente dall'ordine delle variabili di raggruppamento nella group_bychiamata. Potresti voler fare un successivo group_by(am), per rendere il tuo codice più esplicito.

Per arrotondamenti e preimpostazioni, fare riferimento alla bella risposta di @Tyler Rinker.


5
Ho appena scoperto anche quella soluzione, ma non so perché sum(n)amgear
funzioni

7
Vedi la vignetta : "Quando esegui il raggruppamento per più variabili, ogni riepilogo si stacca da un livello del raggruppamento".
Henrik

7
Bello - se ti fermi dopo summariseciò, indica quali gruppi sono rimasti. Oh dplyr rocks ...
Spacedman

Semplice e chiaro Non avevo mai conosciuto la teoria del distacco prima, grazie!
Shixiang Wang,

simpatico. semplice ed efficace. ottimo lavoro!
user2550228

38

Puoi usare la count()funzione, che ha comunque un comportamento diverso a seconda della versione di dplyr:

  • dplyr 0.7.1: restituisce una tabella non raggruppata : è necessario raggruppare nuovamente peram

  • dplyr <0.7.1: restituisce una tabella raggruppata , quindi non è necessario raggruppare di nuovo, anche se potrebbe essere necessario ungroup()per manipolazioni successive

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

Ciò si traduce in una tabella raggruppata , se si desidera utilizzarlo per ulteriori analisi, potrebbe essere utile rimuovere l' attributo raggruppato con ungroup().


1
Questa sembra una risposta non valida su dplyr0.7.1. Esegue il calcolo della frequenza globale su "gear", anziché all'interno di ciascun livello di "am".
Edwin,

30

@ Henrik's è migliore per usabilità in quanto ciò renderà il carattere della colonna e non più numerico ma corrisponde a quello che hai chiesto ...

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 Perché Spacedman lo ha chiesto :-)

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
Puoi sempre creare una classe "percentuale" S3 con un formatmetodo che aggiunge un segno di percentuale ... #overkill
Spacedman

L'implementazione di questa potrebbe essere interessante anche: stackoverflow.com/questions/13483430/...
Spacedman

E se in questo esempio si calcolassero anche la media, sd e SE?
user3655531

6

Ecco una funzione generale che implementa la soluzione di Henrik su 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` non può essere convertito da numerico in carattere`
f0nzie,

5

Ho scritto una piccola funzione per questo compito ripetuto:

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

Posso quindi usarlo come:

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

Restituisce:

# 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

3

Nonostante le molte risposte, un altro approccio che utilizza prop.tablein combinazione con dplyro 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"]

1
Di gran lunga l'approccio più semplice
Serpentese

1

Questa risposta si basa sulla risposta di Matifou.

Innanzitutto l'ho modificato per essere sicuro di non restituire la colonna freq come colonna di notazione scientifica usando l'opzione scipen.

Quindi moltiplico la risposta per 100 per ottenere una percentuale anziché decimale per rendere la colonna freq più facile da leggere in percentuale.

getOption("scipen") 
options("scipen"=10) 
mtcars %>%
count(am, gear) %>% 
mutate(freq = (n / sum(n)) * 100)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.