library(tidyverse)
library(datos)
15 Factores
15.1 Introducción
En R, los factores se usan para trabajar con variables categóricas, es decir, variables que tienen un conjunto fijo y conocido de valores posibles. También son útiles cuando quieres mostrar vectores de caracteres en un orden no alfabético.
Históricamente, los factores eran más sencillos de trabajar que los caracteres. Como resultado, muchas de las funciones de R base automáticamente convierten los caracteres a factores. Esto significa que, a menudo, los factores aparecen en lugares donde no son realmente útiles. Afortunadamente, no tienes que preocuparte de eso en el tidyverse y puedes concentrarte en situaciones en las que los factores son genuinamente útiles.
15.1.1 Prerrequisitos
Para trabajar con factores, vamos a usar el paquete forcats, que es parte del tidyverse. Este paquete provee herramientas para lidiar con variables categóricas (¡y es un anagrama de factores en inglés!) y ofrece un amplio rango de ayudas para trabajar con factores.
15.1.2 Aprendiendo más
Si quieres aprender más sobre factores, te recomendamos leer el artículo de Amelia McNamara y Nicholas Horton, Wrangling categorical data in R (el nombre significa Domando/Manejando Datos Categóricos en R). Este artículo cuenta parte de la historia discutida en stringsAsFactors: An unauthorized biography (del inglés cadenasComoFactores: Una Biografía No Autorizada) y stringsAsFactors = <sigh> (del inglés cadenasComoFactores = <suspiro>), y compara las propuestas tidy para los datos categóricos demostrados en este libro, en comparación a los métodos de R base. Una versión temprana de este artículo ayudó a motivar y definir el alcance del paquete forcats. ¡Gracias Amelia y Nick!
15.2 Creando factores
Imagina que tienes una variable que registra meses:
<- c("Dic", "Abr", "Ene", "Mar") x1
Usar una cadena de caracteres (o string, en inglés) para guardar esta variable tiene dos problemas:
- Solo hay doce meses posibles y no hay nada que te resguarde de errores de tipeo:
<- c("Dic", "Abr", "Eme", "Mar") x2
- No se ordena de una forma útil:
sort(x1)
[1] "Abr" "Dic" "Ene" "Mar"
Puedes solucionar ambos problemas con un factor. Para crearlo, debes empezar definiendo una lista con los niveles válidos:
<- c(
niveles_meses "Ene", "Feb", "Mar", "Abr", "May", "Jun",
"Jul", "Ago", "Sep", "Oct", "Nov", "Dic"
)
Ahora puedes crear un factor:
<- factor(x1, levels = niveles_meses)
y1 y1
[1] Dic Abr Ene Mar
Levels: Ene Feb Mar Abr May Jun Jul Ago Sep Oct Nov Dic
sort(y1)
[1] Ene Mar Abr Dic
Levels: Ene Feb Mar Abr May Jun Jul Ago Sep Oct Nov Dic
Cualquier valor no fijado en el conjunto será convertido a NA
de forma silenciosa:
<- factor(x2, levels = niveles_meses)
y2 y2
[1] Dic Abr <NA> Mar
Levels: Ene Feb Mar Abr May Jun Jul Ago Sep Oct Nov Dic
Si quieres una advertencia, puedes usar readr::parse_factor()
(segmentar un factor, en inglés):
<- parse_factor(x2, levels = niveles_meses) y2
Warning: 1 parsing failure.
row col expected actual
3 -- value in level set Eme
Si omites los niveles, se van a definir a partir de los datos en orden alfabético:
factor(x1)
[1] Dic Abr Ene Mar
Levels: Abr Dic Ene Mar
A veces es preferible que el orden de los niveles se corresponda con su primera aparición en los datos. Puedes hacer esto cuando creas el factor, al definir los niveles con unique(x)
único) o después con fct_inorder()
(factores en orden):
<- factor(x1, levels = unique(x1))
f1 f1
[1] Dic Abr Ene Mar
Levels: Dic Abr Ene Mar
<- x1 %>% factor() %>% fct_inorder()
f2 f2
[1] Dic Abr Ene Mar
Levels: Dic Abr Ene Mar
Si alguna vez necesitas acceso directo al conjunto de niveles válidos, puedes hacerlo con levels()
(niveles):
levels(f2)
[1] "Dic" "Abr" "Ene" "Mar"
15.4 Modificar el orden de los factores
A menudo resulta útil cambiar el orden de los niveles de factores en una visualización. Por ejemplo, imagina que quieres explorar el número promedio de horas consumidas mirando televisión por día, para cada religión:
<- encuesta %>%
resumen_religion group_by(religion) %>%
summarise(
edad = mean(edad, na.rm = TRUE),
horas_tv = mean(horas_tv, na.rm = TRUE),
n = n()
)
ggplot(resumen_religion, aes(horas_tv, religion)) + geom_point()
Este gráfico resulta dificil de interpretar porque no hay un patrón general. Podemos mejorarlo al ordenar los niveles de religion
usando fct_reorder()
( reordenar factores). fct_reorder()
requiere tres argumentos:
f
, el factor cuyos niveles quieres modificar.x
, un vector numérico que quieres usar para reordenar los niveles.- Opcionalmente,
fun
, una función que se usa si hay múltiples valores dex
para cada valor def
. El valor por defecto esmedian
(mediana).
ggplot(resumen_religion, aes(horas_tv, fct_reorder(religion, horas_tv))) +
geom_point()
Reordenar la columna religión (religion
) hace que sea más sencillo ver que las personas en la categoría “No sabe” ven más televisión, mientras que “Hinduismo” y “Otra religión oriental” ven mucho menos.
Cuando haces transformaciones más complicadas, recomendamos que las remuevas de aes()
hacia un paso de transformación separado usando mutate()
. Por ejemplo, puedes reescribir el gráfico anterior de la siguiente forma:
%>%
resumen_religion mutate(religion = fct_reorder(religion, horas_tv)) %>%
ggplot(aes(horas_tv, religion)) +
geom_point()
¿Qué sucede si creamos un gráfico para observar cómo varía la edad promedio para cada ingreso reportado?
<- encuesta %>%
resumen_ingreso group_by(ingreso) %>%
summarise(
edad = mean(edad, na.rm = TRUE),
horas_tv = mean(horas_tv, na.rm = TRUE),
n = n()
)
ggplot(resumen_ingreso, aes(edad, fct_reorder(ingreso, edad))) + geom_point()
En ete caso, ¡reordenar los niveles arbitrariamente no es una buena idea! Eso es porque ingreso
ya tiene un orden basado en un principio determinado, con el cual no deberíamos meternos. Reserva fct_reorder()
para factores cuyos niveles están ordenados arbitrariamente.
Sin embargo, sí tiene sentido mover “No Aplica” al frente, junto a los otros niveles especiales. Puedes usar fct_relevel()
(cambiar niveles). Esta función recibe como argumento un factor, f
y luego cualquier número de niveles que quieras mover al principio de la línea.
ggplot(resumen_ingreso, aes(edad, fct_relevel(ingreso, "No aplica"))) +
geom_point()
¿Por qué crees que la edad promedio para “No aplica” es tan alta?
Es otro el tipo de reordenamiento que resulta útil cuando estás coloreando las líneas de un gráfico. fct_reorder2()
reordena el factor mediante los valores y
asociados con los valores x
más grandes. Esto hace que el gráfico sea más sencillo de leer, porque los colores de líneas se ajustan con la leyenda.
<- encuesta %>%
por_edad filter(!is.na(edad)) %>%
count(edad, estado_civil) %>%
group_by(edad) %>%
mutate(prop = n / sum(n))
ggplot(por_edad, aes(edad, prop, colour = estado_civil)) +
geom_line(na.rm = TRUE)
ggplot(por_edad, aes(edad, prop, colour = fct_reorder2(estado_civil, edad, prop))) +
geom_line() +
labs(colour = "estado_civil")
Finalmente, para los gráficos de barra puedes usar fct_infreq()
(frecuencia incremental de factores) para ordenar los niveles incrementalmente según su frecuencia: este es el ordenamiento más sencillo porque no requiere de variables adicionales. Puedes querer combinarlo con fct_rev()
(invertir factores).
%>%
encuesta mutate(estado_civil = estado_civil %>% fct_infreq() %>% fct_rev()) %>%
ggplot(aes(estado_civil)) +
geom_bar()
15.4.1 Ejercicios
Hay algunos números sospechosamente grandes en
horas_tv
. ¿Es la media un buen resumen?Identifica para cada factor en
encuesta
si el orden de los niveles es arbitrario o responde a algún principio.¿Por qué mover “No aplica” al inicio de los niveles lo llevó al final del gráfico?
15.5 Modificar los niveles de los factores
Más poderoso que cambiar el orden de los niveles es cambiar sus valores. Esto te permite clarificar etiquetas para publicación y colapsar niveles para visualizaciones de alto nivel. La herramienta más general y más poderosa es fct_recode()
(recodificar factores). Esta función te permite recodificar o cambiar el valor de cada nivel. Por ejemplo, toma la columna encuesta$partido
:
%>% count(partido) encuesta
# A tibble: 10 × 2
partido n
<fct> <int>
1 Sin respuesta 154
2 No sabe 1
3 Otro partido 393
4 Fuertemente republicano 2314
5 No fuertemente republicano 3032
6 Ind, pro rep 1791
7 Independiente 4119
8 Ind, pro dem 2499
9 No fuertemente demócrata 3690
10 Fuertemente demócrata 3490
Los niveles son concisos e inconsistentes. Modifiquémoslos un poco para que sean más largos y para poder usar una construcción paralela.
%>%
encuesta mutate(partido = fct_recode(partido,
"Republicano duro" = "Fuertemente republicano",
"Republicano moderado" = "No fuertemente republicano",
"Independiente pro republicano" = "Ind, pro rep",
"Independiente pro demócrata" = "Ind, pro dem",
"Demócrata moderado" = "No fuertemente demócrata",
"Demócrata duro" = "Fuertemente demócrata"
%>%
)) count(partido)
# A tibble: 10 × 2
partido n
<fct> <int>
1 Sin respuesta 154
2 No sabe 1
3 Otro partido 393
4 Republicano duro 2314
5 Republicano moderado 3032
6 Independiente pro republicano 1791
7 Independiente 4119
8 Independiente pro demócrata 2499
9 Demócrata moderado 3690
10 Demócrata duro 3490
fct_recode()
no modificará los niveles que no han sido mencionados explícitamente y te advertirá si accidentalmente te refieres a un nivel que no existe.
Para combinar grupos, puedes asignar múltiples niveles viejos al mismo nivel nuevo:
%>%
encuesta mutate(partido = fct_recode(partido,
"Republicano duro" = "Fuertemente republicano",
"Republicano moderado" = "No fuertemente republicano",
"Independiente pro republicano" = "Ind, pro rep",
"Independiente pro demócrata" = "Ind, pro dem",
"Demócrata moderado" = "No fuertemente demócrata",
"Demócrata duro" = "Fuertemente demócrata",
"Otro" = "Sin respuesta",
"Otro" = "No sabe",
"Otro" = "Otro partido"
%>%
)) count(partido)
# A tibble: 8 × 2
partido n
<fct> <int>
1 Otro 548
2 Republicano duro 2314
3 Republicano moderado 3032
4 Independiente pro republicano 1791
5 Independiente 4119
6 Independiente pro demócrata 2499
7 Demócrata moderado 3690
8 Demócrata duro 3490
Debes usar esta técnica con cuidado: si agrupas categorías que son realmente diferentes, obtendrás resultados confusos y/o engañosos.
Si quieres colapsar muchos niveles, fct_collapse()
(colapsar factores) es una variante muy útil de fct_recode()
. Para cada nueva variable puedes proveer un vector de niveles viejos:
%>%
encuesta mutate(partido = fct_collapse(partido,
otro = c("Sin respuesta", "No sabe", "Otro partido"),
republicano = c("Fuertemente republicano", "No fuertemente republicano"),
independiente = c("Ind, pro rep", "Independiente", "Ind, pro dem"),
= c("No fuertemente demócrata", "Fuertemente demócrata")
demócrata %>%
)) count(partido)
# A tibble: 4 × 2
partido n
<fct> <int>
1 otro 548
2 republicano 5346
3 independiente 8409
4 demócrata 7180
A veces, simplemente quieres agrupar todos los grupos pequeños para simplificar un gráfico o tabla. Ese es un trabajo para fct_lump()
(agrupar factores):
%>%
encuesta mutate(religion = fct_lump(religion, other_level = "Otra")) %>%
count(religion)
# A tibble: 2 × 2
religion n
<fct> <int>
1 Protestante 10846
2 Otra 10637
El comportamiento por defecto es agrupar los grupos pequeños de forma progresiva, asegurando que la agregación continúa siendo el grupo más pequeño. En este caso, esto no resulta demasiado útil: es cierto que la mayoría de los estadounidenses en esta encuesta son protestantes, pero probablemente hemos colapsado en exceso.
En cambio, podemos usar el parámetro n
para especificar cuántos grupos (excluyendo otros) queremos colapsar:
%>%
encuesta mutate(religion = fct_lump(religion, n = 10, other_level = "Otra")) %>%
count(religion, sort = TRUE) %>%
print(n = Inf)
# A tibble: 10 × 2
religion n
<fct> <int>
1 Protestante 10846
2 Católica 5124
3 Ninguna 3523
4 Cristiana 689
5 Otra 458
6 Judía 388
7 Budismo 147
8 Inter o no confesional 109
9 Musulmana/Islam 104
10 Cristiana ortodoxa 95
15.5.1 Ejercicios
¿Cómo han cambiado en el tiempo las proporciones de personas que se identifican como demócratas, republicanas e independientes?
¿Cómo podrías colapsar
ingreso
en un grupo más pequeño de categorías?