Względne częstotliwości / proporcje z dplyr

153

Załóżmy, że chcę obliczyć proporcje różnych wartości w każdej grupie. Na przykład, przy użyciu mtcarsdanych, jak mogę obliczyć względną częstotliwość liczby biegów przez rano (automatyczny / manualny) w jednej porcji z 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 

Co chciałbym osiągnąć:

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
źródło
1
Czy te procenty to rzeczywiste liczby, które chcesz? Skąd one pochodzą algebraicznie? Ach, 79% to 15 / (15 + 4), 21% to 4 / (15 + 4), a dla am == 1 62% to 8 / (8 + 5) itd. Rozumiem.
Spacedman
1
@Spacedman Tak, to jest liczba, którą chcę, a Frank ma rację, sumują się do 100% przez zmienną am (79 + 21) i (62 + 38) ..
jenswirf
2
Wydaje się, że to naprawdę szuka natywnej implementacji dplyr prop.table()/ sweep(). Również w innych pytaniach niektórzy ludzie proszą o opcję uwzględnienia zliczeń zerowych dla zmiennych lub interakcji zmiennych
smci

Odpowiedzi:

285

Spróbuj tego:

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

Z winiety dplyr :

W przypadku grupowania według wielu zmiennych każde podsumowanie oddziela jeden poziom grupowania. Ułatwia to stopniowe rozwijanie zbioru danych.

W ten sposób po summariseostatniej zmiennej grupującej podanej w group_by„gear” jest usuwana. Na tym mutateetapie dane są grupowane według pozostałych zmiennych grupujących, tutaj „am”. Możesz sprawdzić grupowanie w każdym kroku za pomocą groups.

Wynik peelingu jest oczywiście zależny od kolejności zmiennych grupujących w group_bywywołaniu. Możesz chcieć zrobić następną group_by(am), aby uczynić swój kod bardziej przejrzystym.

Aby uzyskać informacje o zaokrąglaniu i upiększaniu, zapoznaj się z miłą odpowiedzią @Tyler Rinker.

Henrik
źródło
5
Właśnie odkryłem to rozwiązanie, ale nie wiem, dlaczego pracuję sum(n)nad amgrupą, a nie nad geargrupą ...
Spacedman
7
Zobacz winietę : „Kiedy grupujesz według wielu zmiennych, każde podsumowanie oddziela jeden poziom grupowania”.
Henrik,
7
Fajnie - jeśli po prostu zatrzymasz się po tym summarise, powie, które grupy zostały. Och, dplyr rządzi ...
Spacedman
Proste i przejrzyste. Nigdy wcześniej nie znałem teorii złuszczania, dzięki!
Shixiang Wang,
miły. proste i skuteczne. dobra robota!
user2550228
38

Możesz użyć count()funkcji, która zachowuje się jednak inaczej w zależności od wersji dplyr:

  • dplyr 0.7.1: zwraca niezgrupowaną tabelę: musisz ponownie pogrupować wedługam

  • dplyr <0.7.1: zwraca zgrupowaną tabelę, więc nie ma potrzeby ponownego grupowania, chociaż możesz chcieć to zrobić ungroup()dla późniejszych manipulacji

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

Powoduje to utworzenie zgrupowanej tabeli , jeśli chcesz jej użyć do dalszej analizy, może być przydatne usunięcie zgrupowanego atrybutu za pomocą ungroup().

Matifou
źródło
1
Wydaje się, że to nieprawidłowa odpowiedź w wersji dplyr0.7.1. Oblicza częstotliwość ogólnie na „biegu” zamiast na każdym poziomie „am”.
Edwin,
30

@ Henrik jest lepszy pod względem użyteczności, ponieważ sprawi, że kolumna będzie znakowa i nie będzie już numeryczna, ale będzie pasować do tego, o co prosiłeś ...

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%

EDYTUJ Ponieważ Spacedman poprosił o to :-)

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
źródło
6
Zawsze możesz utworzyć klasę „procentową” S3 za pomocą formatmetody, która dodaje znak procentu ... #overkill
Spacedman
Wdrożenie tego też może być interesujące: stackoverflow.com/questions/13483430/…
Spacedman
A gdyby tak obliczyć średnią, SD i SE w tym przykładzie?
user3655531
6

Oto ogólna funkcja implementująca rozwiązanie Henrika w wersji 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
źródło
Error in bind_rows_(x, .id) : Column am` nie może zostać przekonwertowane z liczbowego na
znakowy`
5

Napisałem małą funkcję dla tego powtarzającego się zadania:

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

Mogę wtedy używać go tak:

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

Zwraca:

# 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
źródło
3

Pomimo wielu odpowiedzi, jeszcze jedno podejście, które wykorzystuje prop.tablew połączeniu z dplyrlub 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
źródło
1
Zdecydowanie najprostsze podejście
Parseltongue
1

Ta odpowiedź jest oparta na odpowiedzi Matifou.

Najpierw zmodyfikowałem go, aby upewnić się, że kolumna freq nie zostanie zwrócona jako kolumna notacji naukowej, używając opcji scipen.

Następnie mnożę odpowiedź przez 100, aby uzyskać procent, a nie liczbę dziesiętną, aby ułatwić odczytanie kolumny freq jako wartości procentowej.

getOption("scipen") 
options("scipen"=10) 
mtcars %>%
count(am, gear) %>% 
mutate(freq = (n / sum(n)) * 100)
Jazzmine
źródło