Frecuencias / proporciones relativas con dplyr

153

Supongamos que quiero calcular la proporción de diferentes valores dentro de cada grupo. Por ejemplo, usando los mtcarsdatos, ¿cómo calculo la frecuencia relativa del número de engranajes por am (automático / manual) de una 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 

Lo que me gustaría lograr:

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
fuente
1
¿Son esos porcentajes los números reales que desea? ¿De dónde vienen, algebraicamente? Ah, 79% es 15 / (15 + 4), 21% es 4 / (15 + 4) y luego para am == 1 62% es 8 / (8 + 5) etc. Entendido.
Spacedman
1
@Spacedman Sí, ese es el número que quiero y Frank es correcto, suman 100% por la variable am (79 + 21) y (62 + 38) ..
jenswirf
2
Esto realmente parece estar buscando una implementación nativa de dplyr de prop.table()/ sweep(). Además, en otras preguntas, algunas personas piden la opción de incluir recuentos cero para variables o interacciones variables
smci

Respuestas:

285

Prueba esto:

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

De la viñeta dplyr :

Cuando agrupa por múltiples variables, cada resumen despega un nivel de la agrupación. Eso facilita la acumulación progresiva de un conjunto de datos.

Por lo tanto, después de , se despega summarisela última variable de agrupación especificada en group_by'engranaje'. En el mutatepaso, los datos se agrupan por las variables de agrupación restantes, aquí 'am'. Puede verificar la agrupación en cada paso con groups.

El resultado del pelado depende, por supuesto, del orden de las variables de agrupación en la group_byllamada. Es posible que desee hacer un posterior group_by(am), para que su código sea más explícito.

Para redondear y prettificar, consulte la buena respuesta de @Tyler Rinker.

Henrik
fuente
55
Acabo de descubrir esa solución también, pero no sé por qué sum(n)funciona en el amgrupo y no en el geargrupo también ...
Spacedman
77
Ver el viñeta : "Cuando agrupa por múltiples variables, cada resumen despega un nivel de la agrupación".
Henrik
77
Agradable: si te detienes después de summariseeso, dice qué grupos quedan. Oh dplyr rocks ...
Spacedman
Simple y claro. Nunca antes conocí la teoría de las cáscaras, ¡gracias!
Shixiang Wang
bonito. Simple y efectivo. ¡gran trabajo!
user2550228
38

Puede usar la count()función, que tiene un comportamiento diferente según la versión de dplyr:

  • dplyr 0.7.1: devuelve una tabla desagrupada : debe agrupar nuevamente poram

  • dplyr <0.7.1: devuelve una tabla agrupada , por lo que no es necesario agrupar nuevamente, aunque es posible que desee realizar ungroup()manipulaciones posteriores

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

Esto da como resultado una tabla agrupada , si desea usarla para un análisis posterior, puede ser útil eliminar el atributo agrupado conungroup() .

Matifou
fuente
1
Esto parece una respuesta no válida en dplyr0.7.1. Realiza el cálculo de frecuencia en general en "marcha", en lugar de dentro de cada nivel de "am".
Edwin
30

@ Henrik's es mejor para la usabilidad, ya que esto hará que el carácter de la columna ya no sea numérico, sino que coincida con lo que solicitó ...

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 lo pidió :-)

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
fuente
66
Siempre puede crear una clase de "porcentaje" S3 con un formatmétodo que agregue un signo de porcentaje ... #overkill
Spacedman
Implementar esto también puede ser interesante: stackoverflow.com/questions/13483430/…
Spacedman
¿Qué pasaría si uno calculara la media, SD y SE también en este ejemplo?
user3655531
6

Aquí hay una función general que implementa la solución de Henrik en 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
fuente
Error in bind_rows_(x, .id) : Column am` no se puede convertir de numérico a carácter`
f0nzie
5

Escribí una pequeña función para esta tarea repetitiva:

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

Entonces puedo usarlo como:

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

Vuelve:

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

A pesar de las muchas respuestas, un enfoque más que se utiliza prop.tableen combinación 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"]
TimTeaFan
fuente
1
Con mucho, el enfoque más simple
Lengua de Parsel
1

Esta respuesta se basa en la respuesta de Matifou.

Primero lo modifiqué para asegurarme de que no se devuelve la columna de frecuencia como una columna de notación científica mediante la opción scipen.

Luego multiplico la respuesta por 100 para obtener un porcentaje en lugar de un decimal para que la columna de frecuencia sea más fácil de leer como porcentaje.

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