Factores en R: ¿más que una molestia?

95

Uno de los tipos de datos básicos en R son los factores. En mi experiencia, los factores son básicamente un dolor y nunca los uso. Siempre me convierto en personajes. Extrañamente siento que me estoy perdiendo algo.

¿Hay algunos ejemplos importantes de funciones que usan factores como variables de agrupación donde el tipo de datos de factor se vuelve necesario? ¿Existen circunstancias específicas en las que debería utilizar factores?

JD Long
fuente
7
Estoy agregando este comentario para usuarios principiantes de R que probablemente encuentren esta pregunta. Recientemente escribí una publicación de blog que recopila gran parte de la información de las respuestas a continuación en un tutorial instructivo sobre cuándo, cómo y por qué usar factores en R. gormanalysis.com/?p=115
Ben
Siempre había asumido que los factores se almacenaban de manera más eficiente que los caracteres, como si cada entrada fuera un indicador del nivel. Pero al probarlo para escribir esto, ¡descubrí que no es cierto!
isomorphismes
2
@isomorphismes bueno, eso solía ser cierto, en los primeros días de R, pero eso ha cambiado. Vea esta publicación de blog: Simplystatistics.org/2015/07/24/…
MichaelChirico
4
Más de 5 años después, se escribió "stringsAsFactors: Una biografía no autorizada": Simplystatistics.org/2015/07/24/…
JD Long

Respuestas:

49

Deberías usar factores. Sí, pueden ser una molestia, pero mi teoría es que el 90% de por qué son una molestia se debe a que en read.tabley read.csv, el argumento stringsAsFactors = TRUEpor defecto (y la mayoría de los usuarios pierden esta sutileza). Digo que son útiles porque los paquetes de ajuste de modelos como lme4 usan factores y factores ordenados para ajustar de manera diferencial los modelos y determinar el tipo de contrastes a utilizar. Y los paquetes de gráficos también los usan para agrupar. ggploty la mayoría de las funciones de ajuste de modelos coaccionan vectores de caracteres a factores, por lo que el resultado es el mismo. Sin embargo, terminas con advertencias en tu código:

lm(Petal.Length ~ -1 + Species, data=iris)

# Call:
# lm(formula = Petal.Length ~ -1 + Species, data = iris)

# Coefficients:
#     Speciessetosa  Speciesversicolor   Speciesvirginica  
#             1.462              4.260              5.552  

iris.alt <- iris
iris.alt$Species <- as.character(iris.alt$Species)
lm(Petal.Length ~ -1 + Species, data=iris.alt)

# Call:
# lm(formula = Petal.Length ~ -1 + Species, data = iris.alt)

# Coefficients:
#     Speciessetosa  Speciesversicolor   Speciesvirginica  
#             1.462              4.260              5.552  

Mensaje de advertencia: En model.matrix.default(mt, mf, contrasts):

variable Speciesconvertida afactor

Una cosa complicada es todo drop=TRUE. En vectores, esto funciona bien para eliminar niveles de factores que no están en los datos. Por ejemplo:

s <- iris$Species
s[s == 'setosa', drop=TRUE]
#  [1] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [11] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [21] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [31] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [41] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# Levels: setosa
s[s == 'setosa', drop=FALSE]
#  [1] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [11] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [21] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [31] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [41] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# Levels: setosa versicolor virginica

Sin embargo , con data.frames, el comportamiento de [.data.frame()es diferente: vea este correo electrónico o ?"[.data.frame". Usar drop=TRUEen data.frames no funciona como te imaginas:

x <- subset(iris, Species == 'setosa', drop=TRUE)  # susbetting with [ behaves the same way
x$Species
#  [1] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [11] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [21] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [31] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [41] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# Levels: setosa versicolor virginica

Afortunadamente, puede eliminar factores fácilmente droplevels()para eliminar los niveles de factor no utilizados para un factor individual o para cada factor en a data.frame(desde R 2.12):

x <- subset(iris, Species == 'setosa')
levels(x$Species)
# [1] "setosa"     "versicolor" "virginica" 
x <- droplevels(x)
levels(x$Species)
# [1] "setosa"

Así es como evitar que los niveles seleccionados entren en ggplotleyendas.

Internamente, factors son números enteros con un vector de caracteres de nivel de atributo (ver attributes(iris$Species)y class(attributes(iris$Species)$levels)), que es limpio. Si tuviera que cambiar el nombre de un nivel (y estuviera usando cadenas de caracteres), esta sería una operación mucho menos eficiente. Y cambio mucho los nombres de los niveles, especialmente para las ggplotleyendas. Si falsifica factores con vectores de caracteres, existe el riesgo de que cambie solo un elemento y cree accidentalmente un nuevo nivel separado.

Vince
fuente
1
stringsAsFactorsno es una función.
IRTFM
30

Los factores ordenados son increíbles, si me encantan las naranjas y odio las manzanas, pero no me importan las uvas, no necesito administrar un índice extraño para decirlo:

d <- data.frame(x = rnorm(20), f = sample(c("apples", "oranges", "grapes"), 20, replace = TRUE, prob = c(0.5, 0.25, 0.25)))
d$f <- ordered(d$f, c("apples", "grapes", "oranges"))
d[d$f >= "grapes", ]
mdsumner
fuente
esa es una buena aplicación. Nunca pensé en eso.
JD Long
¿Qué hizo el d$f <- ordered(d$f, c("apples", "grapes", "oranges"))? Habría adivinado que los ordenó en el marco de datos, pero después de ejecutar esa línea e imprimir el marco de datos, nada cambia. ¿Impone simplemente un orden interno aunque el orden impreso no cambie?
Addem el
... Sí, creo que lo que escribí fue algo así como una oración correcta. Si entiendo su punto, nos está mostrando que puede asignar un orden en factores, que es algo que no puede hacer con cadenas.
Addem el
4
Order () crea un orden arbitrario a partir de cualquier valor, en el orden en que dice que están ordenados. Es una pena que haya utilizado valores ordenados lexicográficamente, es una coincidencia. Por ejemplo, utilizo esto para datos en los que "Z" es malo, "3" es bueno, pero las etiquetas no son numéricas ni alfabéticas, por lo que sí ordenado (datos, c ("Z", "B", "A", " 0 "," 1 "," 2 "," 3 ")) y entonces puedo simplemente hacer datos>" A "y son días felices.
mdsumner
19

A factores muy análogo a un tipo enumerado en otros idiomas. Su uso apropiado es para una variable que solo puede tomar uno de los conjuntos de valores prescritos. En estos casos, no todos los posibles valores permitidos pueden estar presentes en un conjunto particular de datos y los niveles "vacíos" lo reflejan con precisión.

Considere algunos ejemplos. Para algunos datos que se recopilaron en todo Estados Unidos, el estado debe registrarse como un factor. En este caso, el hecho de que no se recopilaron casos de un estado en particular es relevante. Podría haber datos de ese estado, pero sucedió (por cualquier motivo, que puede ser un motivo de interés) que no los hubo. Si se recogiera la ciudad natal, no sería un factor. No hay un conjunto preestablecido de posibles lugares de origen. Si los datos se recopilaran de tres ciudades en lugar de a nivel nacional, la ciudad sería un factor: hay tres opciones que se dieron al principio y si no se encontraron casos / datos relevantes en una de esas tres ciudades, eso es relevante.

Otros aspectos de factors, como proporcionar una forma de dar un orden de clasificación arbitrario a un conjunto de cadenas, son características secundarias útiles de factors, pero no son la razón de su existencia.

Brian Diggs
fuente
3
+1. Brian, creo que has dado en el clavo al capturar niveles que no están presentes en los datos.
Ricardo Saporta
13

Los factores son fantásticos cuando uno está haciendo análisis estadístico y explorando los datos. Sin embargo, antes de eso, cuando uno está leyendo, limpiando, solucionando problemas, fusionando y generalmente manipulando los datos, los factores son un dolor total. Más recientemente, como en los últimos años, muchas de las funciones han mejorado para manejar mejor los factores. Por ejemplo, rbind juega muy bien con ellos. Todavía me resulta una molestia total haber dejado niveles vacíos después de una función de subconjunto.

#drop a whole bunch of unused levels from a whole bunch of columns that are factors using gdata
require(gdata)
drop.levels(dataframe)

Sé que es sencillo recodificar los niveles de un factor y reajustar las etiquetas y también hay formas maravillosas de reordenar los niveles. Mi cerebro simplemente no puede recordarlos y tengo que volver a aprenderlos cada vez que lo uso. La recodificación debería ser mucho más fácil de lo que es.

Las funciones de cadena de R son bastante fáciles y lógicas de usar. Entonces, cuando manipulo, generalmente prefiero los personajes a los factores.

Farrel
fuente
1
¿Tiene ejemplos de análisis de estadísticas que utilizan factores?
JD Long
3
ahora hay una función base-R droplevels(). Y no reordena los factores por defecto.
Ben Bolker
6

¡Qué título sarcástico!

Creo que muchas funciones de estimación te permiten usar factores para definir fácilmente variables ficticias ... pero no las uso para eso.

Los uso cuando tengo vectores de caracteres muy grandes con pocas observaciones únicas. Esto puede reducir el consumo de memoria, especialmente si las cadenas del vector de caracteres son más largas.

PD: estoy bromeando sobre el título. Vi tu tweet. ;-)

Joshua Ulrich
fuente
1
Así que realmente los usa para ahorrar espacio de almacenamiento. Eso tiene sentido.
JD Long
13
Bueno, al menos solía hacerlo ;-). Pero hace unas pocas versiones de R, el almacenamiento de caracteres se reescribió para tener un hash interno, por lo que parte de este argumento histórico ahora es nulo. Aún los factores son muy útiles para agrupar y modelar.
Dirk Eddelbuettel
1
Según ?factorera R-2.6.0 y dice: "Los valores enteros se almacenan en 4 bytes, mientras que cada referencia a una cadena de caracteres necesita un puntero de 4 u 8 bytes". ¿Ahorraría espacio convirtiendo a factor si la cadena de caracteres necesita 8 bytes?
Joshua Ulrich
2
N <- 1000; a <- muestra (c ("a", "b", "c"), N, reemplazar = VERDADERO); imprimir (objeto.tamaño (a), unidades = "Kb"); print (objeto.tamaño (factor (a)), unidades = "Kb"); 8 Kb 4,5 Kb por lo que todavía parece ahorrar algo de espacio.
Eduardo Leoni
2
@Eduardo obtuve 4Kb frente a 4.2Kb. Pues N=100000tengo 391,5 Kb frente a 391,8 Kb. Entonces, el factor requiere poca más memoria.
Marek
1

Los factores son un excelente motor de identificación de "casos únicos". He recreado esto mal muchas veces, y a pesar de un par de arrugas ocasionalmente, son extremadamente poderosas.

library(dplyr)
d <- tibble(x = sample(letters[1:10], 20, replace = TRUE))

## normalize this table into an indexed value across two tables
id <- tibble(x_u = sort(unique(d$x))) %>% mutate(x_i = row_number())
di <- tibble(x_i = as.integer(factor(d$x)))


## reconstruct d$x when needed
d2 <- inner_join(di, id) %>% transmute(x = x_u)
identical(d, d2)
## [1] TRUE

Si hay una mejor manera de hacer esta tarea, me encantaría verla, no veo esta capacidad de factordiscutir.

mdsumner
fuente
-2

La aplicación (y la agregación ) dependen de factores. La relación entre información y esfuerzo de estas funciones es muy alta.

Por ejemplo, en una sola línea de código (la llamada para aplicar a continuación) puede obtener el precio medio de los diamantes por corte y color:

> data(diamonds, package="ggplot2")

> head(dm)

   Carat     Cut    Clarity Price Color
1  0.23     Ideal     SI2   326     E
2  0.21   Premium     SI1   326     E
3  0.23      Good     VS1   327     E


> tx = with(diamonds, tapply(X=Price, INDEX=list(Cut=Cut, Color=Color), FUN=mean))

> a = sort(1:diamonds(tx)[2], decreasing=T)  # reverse columns for readability

> tx[,a]

         Color
Cut         J    I    H    G    F    E    D
Fair      4976 4685 5136 4239 3827 3682 4291
Good      4574 5079 4276 4123 3496 3424 3405
Very Good 5104 5256 4535 3873 3779 3215 3470
Premium   6295 5946 5217 4501 4325 3539 3631
Ideal     4918 4452 3889 3721 3375 2598 2629
doug
fuente
7
Este no es un buen ejemplo, porque todos esos ejemplos también funcionarían con cadenas.
hadley