¿Cómo convertir un factor a entero \ numérico sin pérdida de información?

600

Cuando convierto un factor a un valor numérico o entero, obtengo los códigos de nivel subyacentes, no los valores como números.

f <- factor(sample(runif(5), 20, replace = TRUE))
##  [1] 0.0248644019011408 0.0248644019011408 0.179684827337041 
##  [4] 0.0284090070053935 0.363644931698218  0.363644931698218 
##  [7] 0.179684827337041  0.249704354675487  0.249704354675487 
## [10] 0.0248644019011408 0.249704354675487  0.0284090070053935
## [13] 0.179684827337041  0.0248644019011408 0.179684827337041 
## [16] 0.363644931698218  0.249704354675487  0.363644931698218 
## [19] 0.179684827337041  0.0284090070053935
## 5 Levels: 0.0248644019011408 0.0284090070053935 ... 0.363644931698218

as.numeric(f)
##  [1] 1 1 3 2 5 5 3 4 4 1 4 2 3 1 3 5 4 5 3 2

as.integer(f)
##  [1] 1 1 3 2 5 5 3 4 4 1 4 2 3 1 3 5 4 5 3 2

Tengo que recurrir a pastepara obtener los valores reales:

as.numeric(paste(f))
##  [1] 0.02486440 0.02486440 0.17968483 0.02840901 0.36364493 0.36364493
##  [7] 0.17968483 0.24970435 0.24970435 0.02486440 0.24970435 0.02840901
## [13] 0.17968483 0.02486440 0.17968483 0.36364493 0.24970435 0.36364493
## [19] 0.17968483 0.02840901

¿Hay una mejor manera de convertir un factor a numérico?

Adam SO
fuente
66
Los niveles de un factor se almacenan como tipo de datos de caracteres de todos modos ( attributes(f)), por lo que no creo que haya nada malo as.numeric(paste(f)). Quizás sería mejor pensar por qué (en el contexto específico) está obteniendo un factor en primer lugar, y tratar de detenerlo. Por ejemplo, ¿el decargumento está read.tableconfigurado correctamente?
CJB
Si usa un marco de datos, puede usar convertir de hablar. df %>% convert(num(column)). O si tiene un vector de factores que puede usaras_reliable_num(factor_vector)
davsjob

Respuestas:

713

Vea la sección de Advertencia de ?factor:

En particular, as.numericaplicado a un factor no tiene sentido, y puede ocurrir por coerción implícita. Para transformar un factor fa aproximadamente sus valores numéricos originales, as.numeric(levels(f))[f]se recomienda y un poco más eficiente que as.numeric(as.character(f)).

Las preguntas frecuentes sobre R tienen consejos similares .


¿Por qué es as.numeric(levels(f))[f]más eficiente que as.numeric(as.character(f))?

as.numeric(as.character(f))es efectivo as.numeric(levels(f)[f]), por lo que está realizando la conversión a length(x)valores numéricos en lugar de nlevels(x)valores. La diferencia de velocidad será más evidente para los vectores largos con pocos niveles. Si los valores son en su mayoría únicos, no habrá mucha diferencia en la velocidad. Independientemente de cómo realice la conversión, es poco probable que esta operación sea el cuello de botella en su código, así que no se preocupe demasiado por eso.


Algunos horarios

library(microbenchmark)
microbenchmark(
  as.numeric(levels(f))[f],
  as.numeric(levels(f)[f]),
  as.numeric(as.character(f)),
  paste0(x),
  paste(x),
  times = 1e5
)
## Unit: microseconds
##                         expr   min    lq      mean median     uq      max neval
##     as.numeric(levels(f))[f] 3.982 5.120  6.088624  5.405  5.974 1981.418 1e+05
##     as.numeric(levels(f)[f]) 5.973 7.111  8.352032  7.396  8.250 4256.380 1e+05
##  as.numeric(as.character(f)) 6.827 8.249  9.628264  8.534  9.671 1983.694 1e+05
##                    paste0(x) 7.964 9.387 11.026351  9.956 10.810 2911.257 1e+05
##                     paste(x) 7.965 9.387 11.127308  9.956 11.093 2419.458 1e+05
Joshua Ulrich
fuente
44
Para conocer los horarios, consulte esta respuesta: stackoverflow.com/questions/6979625/…
Ari B. Friedman
3
Muchas gracias por tu solución. ¿Puedo preguntar por qué el as.numeric (niveles (f)) [f] es más preciso y rápido? Gracias.
Sam
77
@Sam as.character (f) requiere una "búsqueda primitiva" para encontrar la función as.character.factor (), que se define como as.numeric (niveles (f)) [f].
Jonathan
12
cuando se aplica as.numeric (niveles (f)) [f] O as.numeric (as.character (f)), tengo un mensaje de advertencia: Mensaje de advertencia: NAs introducidos por coerción. ¿Sabes dónde podría estar el problema? gracias !
maycca
@maycca superaste este problema?
user08041991
91

R tiene varias funciones de conveniencia (no documentadas) para convertir factores:

  • as.character.factor
  • as.data.frame.factor
  • as.Date.factor
  • as.list.factor
  • as.vector.factor
  • ...

Pero molestamente, no hay nada para manejar el factor -> conversión numérica . Como una extensión de la respuesta de Joshua Ulrich, sugeriría superar esta omisión con la definición de su propia función idiomática:

as.numeric.factor <- function(x) {as.numeric(levels(x))[x]}

que puede almacenar al comienzo de su secuencia de comandos, o incluso mejor en su .Rprofilearchivo.

Jealie
fuente
14
No hay nada para manejar la conversión de factor a entero (o numérico) porque se espera que as.integer(factor)devuelva los códigos enteros subyacentes (como se muestra en la sección de ejemplos de ?factor). Probablemente esté bien definir esta función en su entorno global, pero puede causar problemas si realmente la registra como un método S3.
Joshua Ulrich
1
Ese es un buen punto y estoy de acuerdo: una redefinición completa del factor-> conversión numérica es probable que estropee muchas cosas. Me encontré escribiendo el engorroso factor->numericconversión mucho antes de darse cuenta de que en realidad es un defecto de R: Con un poco de función de conveniencia debería estar disponible ... Llamarlo as.numeric.factortiene sentido para mí, pero tu caso es distinto.
Jealie
44
Si te encuentras haciendo eso mucho , entonces debes hacer algo corriente arriba para evitarlo por completo.
Joshua Ulrich
2
as.numeric.factor devuelve NA?
jO.
@jO .: en los casos en que usaste algo como v=NA;as.numeric.factor(v)o v='something';as.numeric.factor(v), entonces debería, de lo contrario tienes algo extraño en alguna parte.
Jealie
33

La forma más fácil sería usar la unfactorfunción del paquete varhandle

unfactor(your_factor_variable)

Este ejemplo puede ser un comienzo rápido:

x <- rep(c("a", "b", "c"), 20)
y <- rep(c(1, 1, 0), 20)

class(x)  # -> "character"
class(y)  # -> "numeric"

x <- factor(x)
y <- factor(y)

class(x)  # -> "factor"
class(y)  # -> "factor"

library(varhandle)
x <- unfactor(x)
y <- unfactor(y)

class(x)  # -> "character"
class(y)  # -> "numeric"
Mehrad Mahmoudian
fuente
La unfactorfunción convierte primero al tipo de datos de caracteres y luego vuelve a convertir a numérico. Escriba unfactoren la consola y podrá verlo en el medio de la función. Por lo tanto, en realidad no ofrece una solución mejor que la que ya tenía el autor de la pregunta.
CJB
Dicho esto, los niveles de un factor son del tipo de personaje de todos modos, por lo que este enfoque no pierde nada.
CJB
La unfactorfunción se encarga de cosas que no se pueden convertir a numérico. Mira los ejemplos enhelp("unfactor")
Mehrad Mahmoudian
2
@Selrac He mencionado que esta función está disponible en el paquete varhandle , lo que significa que library("varhandle")primero debe cargar el paquete ( ) (¡como mencioné en la primera línea de mi respuesta!)
Mehrad Mahmoudian
1
@Gregor agregar una dependencia de la luz no suele dañar y, por supuesto, si está buscando la forma más eficiente, escribir el código por su cuenta podría ser más rápido. pero como también puede ver en su comentario, esto no es trivial ya que también pone el orden as.numeric()y as.character()en un orden incorrecto;) Lo que hace su fragmento de código es convertir el índice de nivel del factor en una matriz de caracteres, así que lo que tendrá en el y es un vector de caracteres que contiene algunos números que alguna vez se asignaron a cierto nivel de su factor. Las funciones en ese paquete están ahí para evitar estas confusiones
Mehrad Mahmoudian
23

Nota: esta respuesta en particular no es para convertir factores de valor numérico a numéricos, es para convertir factores categóricos a sus números de nivel correspondientes.


Cada respuesta en esta publicación no pudo generar resultados para mí, se estaban generando NA.

y2<-factor(c("A","B","C","D","A")); 
as.numeric(levels(y2))[y2] 
[1] NA NA NA NA NA Warning message: NAs introduced by coercion

Lo que funcionó para mí es esto:

as.integer(y2)
# [1] 1 2 3 4 1
Indi
fuente
¿Estás seguro de que tienes un factor? Mira este ejemplo. y<-factor(c("5","15","20","2")); unclass(y) %>% as.numericEsto devuelve 4,1,3,2, no 5,15,20,2. Esto parece información incorrecta.
MrFlick
Ok, esto es similar a lo que estaba tratando de hacer hoy: - y2 <-factor (c ("A", "B", "C", "D", "A")); as.numeric (niveles (y2)) [y2] [1] NA NA NA NA NA Mensaje de advertencia: NAs introducidos por coerción mientras que la clase (y2)%>% as.numeric me dio los resultados que necesitaba.
Indi
44
Bien, esa no es la pregunta que se hizo anteriormente. En esta pregunta, los niveles de los factores son todos "numéricos". En su caso, as.numeric(y)debería haber funcionado bien, sin necesidad de unclass(). Pero de nuevo, de eso no se trataba esta pregunta. Esta respuesta no es apropiada aquí.
MrFlick
3
Bueno, ¡realmente espero que ayude a alguien que tenía prisa como yo y leyó solo el título!
Indi
1
Si tiene caracteres que representan los enteros como factores, este es el que recomendaría. Este es el único que funcionó para mí.
aimme
9

Es posible solamente en el caso en que las etiquetas de los factores coinciden con los valores originales. Lo explicaré con un ejemplo.

Suponga que los datos son vectores x:

x <- c(20, 10, 30, 20, 10, 40, 10, 40)

Ahora crearé un factor con cuatro etiquetas:

f <- factor(x, levels = c(10, 20, 30, 40), labels = c("A", "B", "C", "D"))

1) xes con tipo doble, fes con tipo entero. Esta es la primera pérdida inevitable de información. Los factores siempre se almacenan como enteros.

> typeof(x)
[1] "double"
> typeof(f)
[1] "integer"

2) No es posible volver a los valores originales (10, 20, 30, 40) que solo están fdisponibles. Podemos ver que fsolo contiene valores enteros 1, 2, 3, 4 y dos atributos: la lista de etiquetas ("A", "B", "C", "D") y el atributo de clase "factor". Nada mas.

> str(f)
 Factor w/ 4 levels "A","B","C","D": 2 1 3 2 1 4 1 4
> attributes(f)
$levels
[1] "A" "B" "C" "D"

$class
[1] "factor"

Para volver a los valores originales, debemos conocer los valores de los niveles utilizados para crear el factor. En este caso c(10, 20, 30, 40). Si conocemos los niveles originales (en el orden correcto), podemos volver a los valores originales.

> orig_levels <- c(10, 20, 30, 40)
> x1 <- orig_levels[f]
> all.equal(x, x1)
[1] TRUE

Y esto solo funcionará en caso de que se hayan definido etiquetas para todos los valores posibles en los datos originales.

Entonces, si necesita los valores originales, debe conservarlos. De lo contrario, existe una alta probabilidad de que no sea posible volver a ellos solo por un factor.

djhurio
fuente
2

Puede usar hablar::convertsi tiene un marco de datos. La sintaxis es fácil:

Muestra df

library(hablar)
library(dplyr)

df <- dplyr::tibble(a = as.factor(c("7", "3")),
                    b = as.factor(c("1.5", "6.3")))

Solución

df %>% 
  convert(num(a, b))

te dio:

# A tibble: 2 x 2
      a     b
  <dbl> <dbl>
1    7.  1.50
2    3.  6.30

O si desea que una columna sea entera y una numérica:

df %>% 
  convert(int(a),
          num(b))

resultados en:

# A tibble: 2 x 2
      a     b
  <int> <dbl>
1     7  1.50
2     3  6.30
davsjob
fuente
0

Parece que la solución como.numeric (niveles (f)) [f] ya no funciona con R 4.0.

Solución alternativa:

factor2number <- function(x){
    data.frame(levels(x), 1:length(levels(x)), row.names = 1)[x, 1]
}

factor2number(yourFactor)
Life_Searching_Steps
fuente
-1

De las muchas respuestas que pude leer, la única forma dada fue expandir el número de variables de acuerdo con el número de factores. Si tiene una "mascota" variable con los niveles "perro" y "gato", terminaría con pet_dog y pet_cat.

En mi caso, quería quedarme con el mismo número de variables, simplemente traduciendo la variable del factor a una numérica, de manera que pueda aplicarse a muchas variables con muchos niveles, de modo que cat = 1 y dog ​​= 0, por ejemplo.

Encuentre la solución correspondiente a continuación:

crime <- data.frame(city = c("SF", "SF", "NYC"),
                    year = c(1990, 2000, 1990),
                    crime = 1:3)

indx <- sapply(crime, is.factor)

crime[indx] <- lapply(crime[indx], function(x){ 
  listOri <- unique(x)
  listMod <- seq_along(listOri)
  res <- factor(x, levels=listOri)
  res <- as.numeric(res)
  return(res)
}
)
Xavier Prudent
fuente
-2

tarde al juego, accidentalmente, me encontré trimws()convertido lata factor(3:5)a c("3","4","5"). Entonces puedes llamar as.numeric(). Es decir:

as.numeric(trimws(x_factor_var))
Jerry T
fuente
3
¿Hay una razón por la que recomendaría el uso de trimwsmás de as.charactercomo se describe en la respuesta aceptada? Me parece que, a menos que en realidad tuviera un espacio en blanco que necesitara eliminar, trimwssolo va a hacer un montón de trabajo innecesario de expresión regular para devolver el mismo resultado.
MrFlick
as.numeric (niveles (f)) [f] puede ser un poco confuso y difícil de recordar para principiantes. trimws no hace daño.
Jerry T