Relative Frequenzen / Proportionen mit dplyr

151

Angenommen, ich möchte den Anteil verschiedener Werte innerhalb jeder Gruppe berechnen. Um zum Beispiel der Verwendung von mtcarsDaten, wie berechne ich die relative Häufigkeit der Anzahl der Gänge von Uhr (automatisch / manuell) in einem Rutsch mit 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 

Was ich erreichen möchte:

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
quelle
1
Sind diese Prozentsätze die tatsächlichen Zahlen, die Sie wollen? Woher kommen sie algebraisch? Ah, 79% sind 15 / (15 + 4), 21% sind 4 / (15 + 4) und dann für am == 1 62% sind 8 / (8 + 5) usw. Verstanden.
Spacedman
1
@Spacedman Ja, das ist die Zahl, die ich will und Frank ist richtig, sie summieren sich zu 100% durch die Variable am (79 + 21) und (62 + 38).
jenswirf
2
Dies scheint wirklich nach einer nativen dplyr-Implementierung von prop.table()/ zu suchen sweep(). Auch in anderen Fragen fragen
Nullzählungen

Antworten:

283

Versuche dies:

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

Aus der dplyr-Vignette :

Wenn Sie nach mehreren Variablen gruppieren, wird bei jeder Zusammenfassung eine Ebene der Gruppierung entfernt. Das macht es einfach, einen Datensatz schrittweise aufzurollen.

Somit wird nach dem summarisedie letzte in group_by'Zahnrad' angegebene Gruppierungsvariable abgezogen. Im mutateSchritt werden die Daten nach den verbleibenden Gruppierungsvariablen gruppiert, hier 'bin'. Sie können die Gruppierung in jedem Schritt mit überprüfen groups.

Das Ergebnis des Peelings hängt natürlich von der Reihenfolge der Gruppierungsvariablen im group_byAufruf ab. Möglicherweise möchten Sie eine nachfolgende group_by(am)Aktion ausführen, um Ihren Code deutlicher zu machen.

Informationen zur Rundung und Verschönerung finden Sie in der netten Antwort von @Tyler Rinker.

Henrik
quelle
5
Ich habe gerade auch diese Lösung entdeckt, aber ich weiß nicht, warum sum(n)über die amGruppe und nicht auch über die Gruppe funktioniert gear...
Spacedman
7
Siehe die Vignette : "Wenn Sie nach mehreren Variablen gruppieren, schält jede Zusammenfassung eine Ebene der Gruppierung ab."
Henrik
7
Schön - wenn Sie nur nach dem anhalten summarise, wird angezeigt, welche Gruppen noch übrig sind. Oh dplyr rockt ...
Spacedman
Einfach und klar. Ich habe die Peel-off-Theorie noch nie gekannt, danke!
Shixiang Wang
nett. einfach und effektiv. gut gemacht!
user2550228
37

Sie können die count()Funktion verwenden, die sich jedoch je nach Version von unterschiedlich verhält dplyr:

  • dplyr 0.7.1: Gibt eine nicht gruppierte Tabelle zurück: Sie müssen erneut nach gruppierenam

  • dplyr <0.7.1: Gibt eine gruppierte Tabelle zurück, sodass keine erneute Gruppierung erforderlich ist, obwohl Sie dies möglicherweise ungroup()für spätere Manipulationen möchten

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

Dies führt zu einer gruppierten Tabelle . Wenn Sie sie für weitere Analysen verwenden möchten, kann es hilfreich sein, das gruppierte Attribut mit zu entfernen ungroup().

Matifou
quelle
1
Dies scheint eine ungültige Antwort auf dplyr0.7.1 zu sein. Die Frequenzberechnung wird insgesamt für "Gang" durchgeführt, anstatt innerhalb jeder Stufe von "am".
Edwin
30

@ Henrik's ist besser für die Benutzerfreundlichkeit, da dadurch das Spaltenzeichen und nicht mehr numerisch wird, sondern mit dem übereinstimmt, wonach Sie gefragt haben ...

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 Da Spacedman danach gefragt :-)

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
quelle
6
Sie können jederzeit eine S3 "Prozent" -Klasse mit einer formatMethode erstellen, die ein Prozentzeichen hinzufügt ... #overkill
Spacedman
Die Implementierung könnte auch interessant sein: stackoverflow.com/questions/13483430/…
Spacedman
Was wäre, wenn man in diesem Beispiel auch den Mittelwert, sd und SE berechnen würde?
user3655531
6

Hier ist eine allgemeine Funktion zur Implementierung der Henrik-Lösung auf 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
quelle
Error in bind_rows_(x, .id) : Column am` kann nicht von numerisch in Zeichen umgewandelt
werden`
5

Ich habe eine kleine Funktion für diese sich wiederholende Aufgabe geschrieben:

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

Ich kann es dann wie folgt verwenden:

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

Es gibt zurück:

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

Trotz der vielen Antworten ein weiterer Ansatz, der prop.tablein Kombination mit dplyroder verwendet wird 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
quelle
1
Mit Abstand der einfachste Ansatz
Parseltongue
1

Diese Antwort basiert auf Matifous Antwort.

Zuerst habe ich es geändert, um sicherzustellen, dass die freq-Spalte nicht als wissenschaftliche Notationsspalte zurückgegeben wird, indem ich die scipen-Option verwende.

Dann multipliziere ich die Antwort mit 100, um ein Prozent statt einer Dezimalstelle zu erhalten, damit die Freq-Spalte als Prozentsatz leichter lesbar ist.

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