Convierta columnas data.frame de factores a caracteres


352

Tengo un marco de datos. Vamos a llamarlo bob:

> head(bob)
                 phenotype                         exclusion
GSM399350 3- 4- 8- 25- 44+ 11b- 11c- 19- NK1.1- Gr1- TER119-
GSM399351 3- 4- 8- 25- 44+ 11b- 11c- 19- NK1.1- Gr1- TER119-
GSM399352 3- 4- 8- 25- 44+ 11b- 11c- 19- NK1.1- Gr1- TER119-
GSM399353 3- 4- 8- 25+ 44+ 11b- 11c- 19- NK1.1- Gr1- TER119-
GSM399354 3- 4- 8- 25+ 44+ 11b- 11c- 19- NK1.1- Gr1- TER119-
GSM399355 3- 4- 8- 25+ 44+ 11b- 11c- 19- NK1.1- Gr1- TER119-

Me gustaría concatenar las filas de este marco de datos (esta será otra pregunta). Pero mira:

> class(bob$phenotype)
[1] "factor"

BobLas columnas son factores. Así por ejemplo:

> as.character(head(bob))
[1] "c(3, 3, 3, 6, 6, 6)"       "c(3, 3, 3, 3, 3, 3)"      
[3] "c(29, 29, 29, 30, 30, 30)"

No empiezo a entender esto, pero supongo que estos son índices de los niveles de los factores de las columnas (de la corte del rey caractaco) de bob? No es lo que necesito.

Extrañamente puedo atravesar las columnas a bobmano y hacer

bob$phenotype <- as.character(bob$phenotype)

que funciona bien Y, después de escribir un poco, puedo obtener un data.frame cuyas columnas son caracteres en lugar de factores. Entonces mi pregunta es: ¿cómo puedo hacer esto automáticamente? ¿Cómo convierto un data.frame con columnas de factor en un data.frame con columnas de caracteres sin tener que pasar manualmente por cada columna?

Pregunta adicional: ¿por qué funciona el enfoque manual?


3
sería bueno que hicieras la pregunta reproducible, así que incluye la estructura de bob.
Jangorecki

Respuestas:


362

Solo siguiendo a Matt y Dirk. Si desea volver a crear su marco de datos existente sin cambiar la opción global, puede volver a crearlo con una instrucción de aplicación:

bob <- data.frame(lapply(bob, as.character), stringsAsFactors=FALSE)

Esto convertirá todas las variables a la clase "personaje", si solo desea convertir factores, consulte la solución de Marek a continuación .

Como señala @hadley, lo siguiente es más conciso.

bob[] <- lapply(bob, as.character)

En ambos casos, lapplygenera una lista; sin embargo, debido a las propiedades mágicas de R, el uso de []en el segundo caso mantiene la clase data.frame del bobobjeto, eliminando así la necesidad de volver a convertir a data.frame usando as.data.frameel argumento stringsAsFactors = FALSE.


27
Shane, eso también convertirá las columnas numéricas en caracteres.
Dirk Eddelbuettel

@Dirk: Eso es cierto, aunque no está claro si eso es un problema aquí. Claramente, crear cosas correctamente por adelantado es la mejor solución. No creo que sea fácil convertir automáticamente los tipos de datos en un marco de datos. Una opción es usar lo anterior pero luego usarlo type.convertdespués de enviar todo a character, luego factorsvolver a emitir characternuevamente.
Shane

Esto parece descartar los nombres de fila.
piccolbo

2
@piccolbo usaste bob[] <- en el ejemplo o bob <- ? el primero mantiene el data.frame; el segundo cambia el data.frame a una lista, dejando caer los nombres de las filas. Actualizaré la respuesta
David LeBauer

66
Una variante que solo convierte columnas de factores en caracteres mediante una función anónima: iris[] <- lapply(iris, function(x) if (is.factor(x)) as.character(x) else {x})
Stefan F

313

Para reemplazar solo factores:

i <- sapply(bob, is.factor)
bob[i] <- lapply(bob[i], as.character)

En el paquete dplyr en la versión 0.5.0 mutate_ifse introdujo una nueva función :

library(dplyr)
bob %>% mutate_if(is.factor, as.character) -> bob

El ronroneo del paquete de RStudio ofrece otra alternativa:

library(purrr)
library(dplyr)
bob %>% map_if(is.factor, as.character) %>% as_tibble -> bob

No funciona para mí, por desgracia. No se porque. ¿Probablemente porque tengo colnames?
Autumnsault

@mohawkjohn No debería ser problema. ¿Recibió un error o los resultados no fueron los que esperaba?
Marek

2
Nota: ¡La purrrlínea devuelve una lista, no un data.frame!
RoyalTS

Esto también funciona si ya tienes un ivector de colnames().
verbamour

39

La opción global

stringsAsFactors: la configuración predeterminada para argumentos de data.frame y read.table.

puede ser algo que desee configurar FALSEen sus archivos de inicio (por ejemplo, ~ / .Rprofile). Por favor mira help(options).


55
El problema con esto es que cuando ejecuta su código en un entorno donde falta ese archivo .Rprofile, ¡obtendrá errores!
waferthin

44
Tiendo a llamarlo al comienzo de los scripts en lugar de establecerlo en .Rprofile.
gregmacfarlane

22

Si comprende cómo se almacenan los factores, puede evitar el uso de funciones basadas en aplicar para lograr esto. Lo que no implica en absoluto que las soluciones de aplicación no funcionen bien.

Los factores se estructuran como índices numéricos vinculados a una lista de 'niveles'. Esto se puede ver si convierte un factor a numérico. Entonces:

> fact <- as.factor(c("a","b","a","d")
> fact
[1] a b a d
Levels: a b d

> as.numeric(fact)
[1] 1 2 1 3

Los números devueltos en la última línea corresponden a los niveles del factor.

> levels(fact)
[1] "a" "b" "d"

Tenga en cuenta que levels()devuelve una matriz de caracteres. Puede usar este hecho para convertir factores de forma fácil y compacta a cadenas o números como este:

> fact_character <- levels(fact)[as.numeric(fact)]
> fact_character
[1] "a" "b" "a" "d"

Esto también funciona para valores numéricos, siempre que envuelva su expresión as.numeric().

> num_fact <- factor(c(1,2,3,6,5,4))
> num_fact
[1] 1 2 3 6 5 4
Levels: 1 2 3 4 5 6
> num_num <- as.numeric(levels(num_fact)[as.numeric(num_fact)])
> num_num
[1] 1 2 3 6 5 4

Esta respuesta no aborda el problema, que es cómo convierto todas las columnas de factores en mi marco de datos a caracteres. as.character(f), es mejor tanto en legibilidad como en eficiencia levels(f)[as.numeric(f)]. Si quisieras ser inteligente, podrías usarlo levels(f)[f]en su lugar. Tenga en cuenta que al convertir un factor con valores numéricos, obtiene algún beneficio de as.numeric(levels(f))[f]over, por ejemplo as.numeric(as.character(f)), pero esto se debe a que solo tiene que convertir los niveles a numéricos y luego a subconjuntos. as.character(f)está bien como está.
De Novo

20

Si desea un nuevo marco de datos bobcdonde cada vector de factor bobfse convierta en un vector de caracteres, intente esto:

bobc <- rapply(bobf, as.character, classes="factor", how="replace")

Si luego desea volver a convertirlo, puede crear un vector lógico de las columnas que son factores, y usarlo para aplicar factor selectivamente

f <- sapply(bobf, class) == "factor"
bobc[,f] <- lapply(bobc[,f], factor)

2
+1 para hacer solo lo que era necesario (es decir, no convertir todo el data.frame a carácter). Esta solución es robusta para un data.frame que contiene tipos mixtos.
Joshua Ulrich

3
Este ejemplo debe estar en la sección 'Ejemplos' para rapply, como en: stat.ethz.ch/R-manual/R-devel/library/base/html/rapply.html . ¿Alguien sabe cómo pedir que sea así?
mpettis

Si desea terminar con un marco de datos, simplemente envuelva la respuesta rápidamente en una llamada data.frame (usando las cadenas AsFactors establecidas en el argumento FALSO)
Sitios web de Taylored

13

Normalmente hago esta función aparte de todos mis proyectos. Rapido y Facil.

unfactorize <- function(df){
  for(i in which(sapply(df, class) == "factor")) df[[i]] = as.character(df[[i]])
  return(df)
}

8

Otra forma es convertirlo usando apply

bob2 <- apply(bob,2,as.character)

Y uno mejor (el anterior es de clase 'matriz')

bob2 <- as.data.frame(as.matrix(bob),stringsAsFactors=F)

Siguiendo el comentario de @ Shane: para obtener data.frame, hagaas.data.frame(lapply(...
aL3xa

7

Actualización: Aquí hay un ejemplo de algo que no funciona. Pensé que lo haría, pero creo que la opción stringsAsFactors solo funciona en cadenas de caracteres: deja solo los factores.

Prueba esto:

bob2 <- data.frame(bob, stringsAsFactors = FALSE)

En términos generales, cada vez que tiene problemas con factores que deberían ser caracteres, hay una stringsAsFactorsconfiguración en algún lugar para ayudarlo (incluida una configuración global).


1
Esto funciona, si lo establece al crear bobpara empezar (pero no después del hecho).
Shane

Derecha. Solo quería dejar en claro que esto no resuelve el problema, per se, pero gracias por señalar que sí lo evita.
Matt Parker

7

O puedes probar transform:

newbob <- transform(bob, phenotype = as.character(phenotype))

Solo asegúrese de poner todos los factores que desea convertir en caracteres.

O puedes hacer algo como esto y matar a todas las plagas de un solo golpe:

newbob_char <- as.data.frame(lapply(bob[sapply(bob, is.factor)], as.character), stringsAsFactors = FALSE)
newbob_rest <- bob[!(sapply(bob, is.factor))]
newbob <- cbind(newbob_char, newbob_rest)

No es una buena idea meter los datos en un código como este, podría hacer elsapply parte por separado (en realidad, es mucho más fácil hacerlo así), pero entiendes el punto ... No he verificado el código, porque No estoy en casa, ¡así que espero que funcione! =)

Este enfoque, sin embargo, tiene un inconveniente ... debe reorganizar las columnas después, mientras transformpuede hacer lo que quiera, pero a costa de "escribir código de estilo peatonal" ...

Entonces allí ... =)


6

Al comienzo de su marco de datos, incluya stringsAsFactors = FALSEignorar todos los malentendidos.


4

Si usaría el data.tablepaquete para las operaciones en data.frame, entonces el problema no está presente.

library(data.table)
dt = data.table(col1 = c("a","b","c"), col2 = 1:3)
sapply(dt, class)
#       col1        col2 
#"character"   "integer" 

Si ya tiene columnas de factor en su conjunto de datos y desea convertirlas en caracteres, puede hacer lo siguiente.

library(data.table)
dt = data.table(col1 = factor(c("a","b","c")), col2 = 1:3)
sapply(dt, class)
#     col1      col2 
# "factor" "integer" 
upd.cols = sapply(dt, is.factor)
dt[, names(dt)[upd.cols] := lapply(.SD, as.character), .SDcols = upd.cols]
sapply(dt, class)
#       col1        col2 
#"character"   "integer" 

DT evita la solución de sapply propuesta por Marek: In [<-.data.table(*tmp*, sapply(bob, is.factor), : Coerced 'character' RHS to 'double' to match the column's type. Either change the target column to 'character' first (by creating a new 'character' vector length 1234 (nrows of entire table) and assign that; i.e. 'replace' column), or coerce RHS to 'double' (e.g. 1L, NA_[real|integer]_, as.*, etc) to make your intent clear and for speed. Or, set the column type correctly up front when you create the table and stick to it, please.es más fácil arreglar el DF y recrear el DT.
Matt Chambers

2

Esto funciona para mí: finalmente pensé en un trazador de líneas

df <- as.data.frame(lapply(df,function (y) if(class(y)=="factor" ) as.character(y) else y),stringsAsFactors=F)

2

Esta función hace el truco

df <- stacomirtools::killfactor(df)

2

Tal vez una nueva opción?

library("tidyverse")

bob <- bob %>% group_by_if(is.factor, as.character)

1

Debe usar converten hablarque da sintaxis legible compatible con tidyversetuberías:

library(dplyr)
library(hablar)

df <- tibble(a = factor(c(1, 2, 3, 4)),
             b = factor(c(5, 6, 7, 8)))

df %>% convert(chr(a:b))

que te da:

  a     b    
  <chr> <chr>
1 1     5    
2 2     6    
3 3     7    
4 4     8   

1

Con el dplyruso de paquete cargado

bob=bob%>%mutate_at("phenotype", as.character)

si solo quieres cambiar la phenotypecolumna específicamente.


0

Esto funciona transformando todo a carácter y luego el numérico a numérico:

makenumcols<-function(df){
  df<-as.data.frame(df)
  df[] <- lapply(df, as.character)
  cond <- apply(df, 2, function(x) {
    x <- x[!is.na(x)]
    all(suppressWarnings(!is.na(as.numeric(x))))
  })
  numeric_cols <- names(df)[cond]
  df[,numeric_cols] <- sapply(df[,numeric_cols], as.numeric)
  return(df)
}

Adaptado de: Obtener tipos de columna de hoja de Excel automáticamente

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.