3.3 Factores

Los factores son un tipo de dato especial en R que permite almacenar de forma eficiente datos categoricos: nominales u ordinales. Estos se construyen, al igual que las matrices, a partir de vectores. Y asi como las matrices, los factores tienen un atributo que los diferencia de vectores ordinarios. Este atributo se refiere a los niveles (levels) del factor: los valores posibles de un factor. En el análisis de datos, es de vital importancia ya que el comportameinto de modelos estadisticos depende del tipo de los valores de entrada, además de que muchas funciones estadísticas en R tratan los factores de manera distinta a como se tratan los vectores de carcateres.

Para crear un factor se usa la función factor:

rating <- c("Very Bad", "Medium", "Very Bad", "Good", "Medium", "Very Good", "Medium", "Good", "Medium", "Bad")
rating_factor <- factor(rating)
rating_factor
##  [1] Very Bad  Medium    Very Bad  Good      Medium    Very Good Medium   
##  [8] Good      Medium    Bad      
## Levels: Bad Good Medium Very Bad Very Good

El resultado muestra los elementos del factor, y debajo, las etiquetas de los niveles del factor. Podemos ver que este es distinto de un vector de caracteres:

typeof(rating_factor)
## [1] "integer"

El tipo de los elementos almacenados en el vector es integer. Esto es asi porque los factores se representan como enteros internamente. Los niveles son etiquetas asociadas a cada uno de esos enteros. Se puede saber los niveles de un factor usando la función levels:

levels(rating_factor)
## [1] "Bad"       "Good"      "Medium"    "Very Bad"  "Very Good"

la cual devuelve un vector de caracteres con las etiquetas de los niveles, en orden alfabético. Esto ocurre así por omisión: los niveles de un factor se almacenan en orden alfabético. Sin embargo, podemos especificar el orden en el que se almacenan las etiquetas al crear el factor:

rating_factor <- factor(rating, levels=c("Very Bad", "Bad", "Medium", "Good", "Very Good"))
rating_factor
##  [1] Very Bad  Medium    Very Bad  Good      Medium    Very Good Medium   
##  [8] Good      Medium    Bad      
## Levels: Very Bad Bad Medium Good Very Good

Esto es importante cuando se quiere especificar un nivel de referencia con el cual comparar los resultados de un modelo estadistico. Por ejemplo, si en un estudio se busca usar como variable independiente el habito de fumar, codificado como Fumador y No Fumador, tiene sentido usar el nivel de No Fumador como referencia, de forma que cualquier analisis que use un modelo estadistico compare el cambio en la respuesta como consecuencia del habito de fuumar. Sin embargo, al crear un factor, debido a que se almacenan alfabéticamente los niveles, el nivel Fumador quedaría como nivel de referencia:

smoker <- c("No Fumador", "Fumador", "No Fumador", "No Fumador", "Fumador", "Fumador", "No Fumador", "Fumador", "Fumador", "No Fumador")
smoker_factor <- factor(smoker)
smoker_factor
##  [1] No Fumador Fumador    No Fumador No Fumador Fumador    Fumador   
##  [7] No Fumador Fumador    Fumador    No Fumador
## Levels: Fumador No Fumador

Es por ello, que resulta conveniente el uso del argumento levels en la función factor:

smoker <- c("No Fumador", "Fumador", "No Fumador", "No Fumador", "Fumador", "Fumador", "No Fumador", "Fumador", "Fumador", "No Fumador")
smoker_factor <- factor(smoker, levels=c("No Fumador", "Fumador"))
smoker_factor
##  [1] No Fumador Fumador    No Fumador No Fumador Fumador    Fumador   
##  [7] No Fumador Fumador    Fumador    No Fumador
## Levels: No Fumador Fumador

Hasta este momento, hemos asumido que los factores que hemos creado corresponden a representaciones de variables catgoricas nominales. Para crear factores que correspondan a representaciones de variables catgoricas ordinales, se puede hacer especificando el argumento ordered=TRUE en la funcion factor; o se puede hacer directamente usando la función ordered:

# Este ordenamiento tiene sentido en escalas tipo Likert
rating_factor <- factor(rating, levels=c("Very Bad", "Bad", "Medium", "Good", "Very Good"), ordered=TRUE)
rating_factor <- ordered(rating, levels=c("Very Bad", "Bad", "Medium", "Good", "Very Good"))

El especificar de forma explicita los niveles tiene otra ventaja importante: Permite detectar tempranamente errores de codificación de elementos dentro de una variable. Por ejemplo, digamos que al definir el vector smoker cometimos un error de tipeo, y uno de los elementos lo escribimos "Funador" (tecleamos N en lugar de M). Al crear el factor con los niveles especificados:

smoker <- c("No Fumador", "Funador", "No Fumador", "No Fumador", "Fumador", "Fumador", "No Fumador", "Fumador", "Fumador", "No Fumador") # El segundo elemento tiene el error de tipeo
smoker_factor <- factor(smoker, levels=c("No Fumador", "Fumador"))
smoker_factor
##  [1] No Fumador <NA>       No Fumador No Fumador Fumador    Fumador   
##  [7] No Fumador Fumador    Fumador    No Fumador
## Levels: No Fumador Fumador

vemos como el factor contiene un NA donde ocurrió el error de tipeo. Esto es un problema, ya que pueden introducirse de forma inesperada datos faltantes donde no los hay, y, peor aún, R no nos advierte de nada de esto. Es posible evitar este comportamiento usando parse_factor en el paquete readr (que usaremos mas adelante para importar datos). Ahora, al crear el factor, R nos muestra una advertencia de que algo no salió como se esperaba, haciendonos sospechar del resultado y que seamos cuidadosos.

library(readr)

smoker_factor <- parse_factor(smoker, levels=c("No Fumador", "Fumador")) # Genera una advertencia
## Warning: 1 parsing failure.
## row col           expected  actual
##   2  -- value in level set Funador
smoker_factor # Imprime el resultado anterior, pero tambien la advertencia, y donde ocurrió
##  [1] No Fumador <NA>       No Fumador No Fumador Fumador    Fumador   
##  [7] No Fumador Fumador    Fumador    No Fumador
## attr(,"problems")
## # A tibble: 1 × 4
##     row   col expected           actual 
##   <int> <int> <chr>              <chr>  
## 1     2    NA value in level set Funador
## Levels: No Fumador Fumador

Buenas prácticas de programación. De forma general, al crear factores, siempre es mejor hacer explicitos los niveles y, aunque las advertencias son adecuadas para manjar con cuidado resultados inesperados como los del ejemplo anterior, siempre es preferible no crear factores que de entrada sabemos son erroneos.

Para evitar la creación de factores mal especificados, se utiliza la función fct en el paquete forcats (un paquete diseñado especificamente para trabajar con factores), el cual se diferencia de factor en que los niveles se crean en el orden en que aparecen en el vector de entrada, y no alfabéticamente.

library(forcats)

smoker <- c("No Fumador", "Fumador", "No Fumador", "No Fumador", "Fumador", "Fumador", "No Fumador", "Fumador", "Fumador", "No Fumador")
smoker_factor <- fct(smoker, levels=c("No Fumador", "Fumador"))

Crear factores ordenados no es tan directo, pero el beneficio de crear factores correctos desde el inicio es más importante:

# Especificando los niveles
rating_factor <- fct(rating, levels=c("Very Bad", "Bad", "Medium", "Good", "Very Good")) %>% 
    lvls_reorder(1:5, ordered=TRUE)

El simbolo %>% es un operador, el operador de tuberia (de la palabra inglesa pipe), el cual se carga en la sesión de R al cargar el paquete forcats. El uso de este operador se difiere hasta el siguiente capitulo. En este momento es suficiente que entienda que cada vez que aparezca una expresión de la forma objeto %>% funcion(primer_arg, segundo_arg, ...), R lo interpreta como funcion(objeto, primer_arg, segundo_arg, ...).

Otras funciones importantes para manejar factores, junto con un ejemplo de su uso, se presenta a continuación20:

  • fct_relevel: nos permite jugar con el orden de los niveles de un factor. Por ejemplo:
fct(c("Fuego", "Tierra", "Agua", "Aire")) %>% 
    fct_relevel("Agua", "Tierra", "Fuego")
  • fct_inorder, fct_infreq: estas se encargan de ordenar los niveles de un factor en el orden en el que aparecen (fct_inorder) o en el orden especificado por la frecuencia con que aprece cada etiqieta (fct_infreq).
vertebrates <- fct(sample(c("Mamifero", "Ave", "Reptil", "Pez", "Anfibio"), 250, replace=TRUE))
invertebrate <- fct(sample(c("Diptero", "Odonata", "Porifera", "Cnidario", "Aracnido"), 25, replace=TRUE))
animal_cat <- fct_c(vertebrates, invertebrate) # fct_c permite combinar factores, preservando los niveles de los factores de entrada

animal_cat %>% fct_infreq()
  • fct_lump y fct_lump_*: estas funciones permiten agrupar niveles, preservando un numero especificado de veeces o solo aquellos niveles de baja frecuencia.
animal_cat %>% 
    fct_lump_lowfreq(other_level = "Invertebrados")
  • fct_expand: permite añadir niveles adicionales a un factor, ya sea que existan o no representastes de estos niveles en el factor.
animal_cat %>% 
    fct_expand("Himenoptero", "Platelmintos", "Equinodermos", "Anfioxo", after=5)
  • fct_collapse: permite agrupar los niveles en niveles nuevos manualmente definidos.
animal_cat %>% 
    fct_collapse(
        Vertebrados=c("Mamifero", "Ave", "Reptil", "Pez", "Anfibio"),
        Invertebrados=c("Diptero", "Odonata", "Porifera", "Cnidario", "Aracnido"))
  • fct_drop: devuelve un factor cuyos niveles son solo aquellos representados dentro del factor.
animal_cat %>% 
    fct_expand("Angiospermas") %>% # Añade un nivel llamado Angiospermas
    fct_drop() 
  • fct_other: Reemplaza los niveles especificados con un nivel especificado (por omisión, “other”).
animal_cat %>% 
    fct_expand("Himenoptero", "Platelmintos", "Equinodermos", "Anfioxo", after=5) %>%
    fct_other(keep = c("Mamifero", "Ave", "Reptil", "Pez", "Anfibio", "Anfioxo"), 
        other_level="Sin notocordio") # Deja los cordados
  • fct_count: cuenta el numero de elmentos del factor que pertenece a cada nivel. El resultadoe s una estructura de datos especial llamada tibble que veremos en la siguiente sección.
animal_cat %>% 
    fct_count(sort=TRUE)

  1. Más información esta disponible en la documentación.↩︎