Convertir clases de columna en data.table

118

Tengo un problema al usar data.table: ¿Cómo convierto clases de columna? Aquí hay un ejemplo simple: con data.frame no tengo problemas para convertirlo, con data.table simplemente no sé cómo:

df <- data.frame(ID=c(rep("A", 5), rep("B",5)), Quarter=c(1:5, 1:5), value=rnorm(10))
#One way: http://stackoverflow.com/questions/2851015/r-convert-data-frame-columns-from-factors-to-characters
df <- data.frame(lapply(df, as.character), stringsAsFactors=FALSE)
#Another way
df[, "value"] <- as.numeric(df[, "value"])

library(data.table)
dt <- data.table(ID=c(rep("A", 5), rep("B",5)), Quarter=c(1:5, 1:5), value=rnorm(10))
dt <- data.table(lapply(dt, as.character), stringsAsFactors=FALSE) 
#Error in rep("", ncol(xi)) : invalid 'times' argument
#Produces error, does data.table not have the option stringsAsFactors?
dt[, "ID", with=FALSE] <- as.character(dt[, "ID", with=FALSE]) 
#Produces error: Error in `[<-.data.table`(`*tmp*`, , "ID", with = FALSE, value = "c(1, 1, 1, 1, 1, 2, 2, 2, 2, 2)") : 
#unused argument(s) (with = FALSE)

¿Me pierdo algo obvio aquí?

Actualización debido a la publicación de Matthew: utilicé una versión anterior antes, pero incluso después de actualizar a 1.6.6 (la versión que uso ahora) todavía recibo un error.

Actualización 2: Digamos que quiero convertir cada columna de la clase "factor" en una columna de "carácter", pero no sé de antemano qué columna es de qué clase. Con un data.frame, puedo hacer lo siguiente:

classes <- as.character(sapply(df, class))
colClasses <- which(classes=="factor")
df[, colClasses] <- sapply(df[, colClasses], as.character)

¿Puedo hacer algo similar con data.table?

Actualización 3:

sessionInfo () R versión 2.13.1 (2011-07-08) Plataforma: x86_64-pc-mingw32 / x64 (64 bits)

locale:
[1] C

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] data.table_1.6.6

loaded via a namespace (and not attached):
[1] tools_2.13.1
Christoph_J
fuente
Los argumentos del operador "[" en los data.tablemétodos son diferentes a los que son para ellosdata.frame
IRTFM
1
Pegue el error real en lugar de #Produces error. +1 de todos modos. No recibo ningún error, ¿qué versión tienes? Sin embargo, hay un problema en esta área, se ha planteado antes, FR # 1224 y FR # 1493 son de alta prioridad para abordar. Sin embargo, la respuesta de Andrie es la mejor manera.
Matt Dowle
Lo siento @MatthewDowle por perderme eso en mi pregunta, actualicé mi publicación.
Christoph_J
1
@Christoph_J Gracias. ¿Estás seguro de ese invalid times argumenterror? Funciona bien para mi. ¿Qué versión tienes?
Matt Dowle
Actualicé mi publicación con sessionInfo (). Sin embargo, lo verifiqué en mi máquina de trabajo hoy. Ayer, en mi máquina doméstica (Ubuntu) ocurrió el mismo error. Actualizaré R y veré si el problema persiste.
Christoph_J

Respuestas:

104

Para una sola columna:

dtnew <- dt[, Quarter:=as.character(Quarter)]
str(dtnew)

Classes ‘data.table’ and 'data.frame':  10 obs. of  3 variables:
 $ ID     : Factor w/ 2 levels "A","B": 1 1 1 1 1 2 2 2 2 2
 $ Quarter: chr  "1" "2" "3" "4" ...
 $ value  : num  -0.838 0.146 -1.059 -1.197 0.282 ...

Usando lapplyy as.character:

dtnew <- dt[, lapply(.SD, as.character), by=ID]
str(dtnew)

Classes ‘data.table’ and 'data.frame':  10 obs. of  3 variables:
 $ ID     : Factor w/ 2 levels "A","B": 1 1 1 1 1 2 2 2 2 2
 $ Quarter: chr  "1" "2" "3" "4" ...
 $ value  : chr  "1.487145280568" "-0.827845218358881" "0.028977182770002" "1.35392750102305" ...
Andrie
fuente
2
@Christoph_J Por favor, muestre el comando de agrupación con el que está luchando (el problema real). Piense que puede haberse perdido algo simple. ¿Por qué intenta convertir clases de columna?
Matt Dowle
1
@Christoph_J Si tiene dificultades para manipular data.tables, ¿por qué no simplemente convertirlos temporalmente en data.frames, hacer la limpieza de datos y luego convertirlos de nuevo en data.tables?
Andrie
17
¿Cuál es la forma idiomática de hacer esto para un subconjunto de columnas (en lugar de todas)? He definido un vector convcolsde caracteres de columnas. dt[,lapply(.SD,as.numeric),.SDcols=convcols]es casi instantáneo mientras que dt[,convcols:=lapply(.SD,as.numeric),.SDcols=convcols]casi se congela R, así que supongo que lo estoy haciendo mal. Gracias
Frank
4
@Frank Vea el comentario de Matt Dowle a la respuesta de Geneorama a continuación ( stackoverflow.com/questions/7813578/… ); fue útil y lo suficientemente idiomático para mí [comienzo de la cita] Otra forma más fácil es usar, set()por ejemplo, for (col in names_factors) set(dt, j=col, value=as.factor(dt[[col]]))[final de la cita]
swihart
4
¿Por qué utiliza la opción by = ID?
skan
48

Prueba esto

DT <- data.table(X1 = c("a", "b"), X2 = c(1,2), X3 = c("hello", "you"))
changeCols <- colnames(DT)[which(as.vector(DT[,lapply(.SD, class)]) == "character")]

DT[,(changeCols):= lapply(.SD, as.factor), .SDcols = changeCols]
Nera
fuente
7
ahora puede usar la Filterfunción para identificar las columnas, por ejemplo: changeCols<- names(Filter(is.character, DT))
David Leal
1
En mi opinión, esta es la mejor respuesta, por la razón que di en la respuesta elegida.
James Hirschorn
1
o más concisamente: changeCols <- names(DT)[sapply(DT, is.character)].
sindri_baldur
8

Elevando el comentario de Matt Dowle a la respuesta de Geneorama ( https://stackoverflow.com/a/20808945/4241780 ) para hacerlo más obvio (como se recomienda), puede usar for(...)set(...).


library(data.table)

DT = data.table(a = LETTERS[c(3L,1:3)], b = 4:7, c = letters[1:4])
DT1 <- copy(DT)
names_factors <- c("a", "c")

for(col in names_factors)
  set(DT, j = col, value = as.factor(DT[[col]]))

sapply(DT, class)
#>         a         b         c 
#>  "factor" "integer"  "factor"

Creado el 12/02/2020 por el paquete reprex (v0.3.0)

Vea otro de los comentarios de Matt en https://stackoverflow.com/a/33000778/4241780 para obtener más información.

Editar.

Como se indica en Espen y en help(set), jpuede ser "Nombre (s) de columna (carácter) o número (s) (entero) al que se le asignará un valor cuando las columnas ya existan". Así names_factors <- c(1L, 3L)también funcionará.

JWilliman
fuente
Es posible que desee agregar lo que names_factorsestá aquí. Supongo que se tomó de stackoverflow.com/a/20808945/1666063, por lo que es names_factors = c('fac1', 'fac2')en este caso, que son los nombres de las columnas, pero también podrían ser números de columna, por ejemplo 1; ncol (dt), que convertiría todas las columnas
Espen Riskedal
@EspenRiskedal Gracias, buen punto, he editado la publicación para que sea más obvia.
JWilliman
2

¡Esta es una MALA manera de hacerlo! Solo dejo esta respuesta en caso de que resuelva otros problemas extraños. Estos mejores métodos son probablemente en parte el resultado de versiones más recientes de data.table ... por lo que vale la pena documentarlo de esta manera. Además, este es un buen ejemplo de eval substitutesintaxis para la sintaxis.

library(data.table)
dt <- data.table(ID = c(rep("A", 5), rep("B",5)), 
                 fac1 = c(1:5, 1:5), 
                 fac2 = c(1:5, 1:5) * 2, 
                 val1 = rnorm(10),
                 val2 = rnorm(10))

names_factors = c('fac1', 'fac2')
names_values = c('val1', 'val2')

for (col in names_factors){
  e = substitute(X := as.factor(X), list(X = as.symbol(col)))
  dt[ , eval(e)]
}
for (col in names_values){
  e = substitute(X := as.numeric(X), list(X = as.symbol(col)))
  dt[ , eval(e)]
}

str(dt)

que te da

Classes ‘data.table’ and 'data.frame':  10 obs. of  5 variables:
 $ ID  : chr  "A" "A" "A" "A" ...
 $ fac1: Factor w/ 5 levels "1","2","3","4",..: 1 2 3 4 5 1 2 3 4 5
 $ fac2: Factor w/ 5 levels "2","4","6","8",..: 1 2 3 4 5 1 2 3 4 5
 $ val1: num  0.0459 2.0113 0.5186 -0.8348 -0.2185 ...
 $ val2: num  -0.0688 0.6544 0.267 -0.1322 -0.4893 ...
 - attr(*, ".internal.selfref")=<externalptr> 
geneorama
fuente
42
Otra forma más fácil es usar, set()por ejemplofor (col in names_factors) set(dt, j=col, value=as.factor(dt[[col]]))
Matt Dowle
1
Creo que mi respuesta logra esto en una línea, para todas las versiones. Sin setembargo, no estoy seguro de si es más apropiado.
Ben Rollert
1
Más información for(...)set(...)aquí: stackoverflow.com/a/33000778/403310
Matt Dowle
1
@skan Buena pregunta. Si no puede encontrarlo antes, haga una nueva pregunta. Ayuda a otros en el futuro.
Matt Dowle
1
@skan así es como lo hice: github.com/geneorama/geneorama/blob/master/R/…
geneorama
0

Probé varios enfoques.

# BY {dplyr}
data.table(ID      = c(rep("A", 5), rep("B",5)), 
           Quarter = c(1:5, 1:5), 
           value   = rnorm(10)) -> df1
df1 %<>% dplyr::mutate(ID      = as.factor(ID),
                       Quarter = as.character(Quarter))
# check classes
dplyr::glimpse(df1)
# Observations: 10
# Variables: 3
# $ ID      (fctr) A, A, A, A, A, B, B, B, B, B
# $ Quarter (chr) "1", "2", "3", "4", "5", "1", "2", "3", "4", "5"
# $ value   (dbl) -0.07676732, 0.25376110, 2.47192852, 0.84929175, -0.13567312,  -0.94224435, 0.80213218, -0.89652819...

, o de otro modo

# from list to data.table using data.table::setDT
list(ID      = as.factor(c(rep("A", 5), rep("B",5))), 
     Quarter = as.character(c(1:5, 1:5)), 
     value   = rnorm(10)) %>% setDT(list.df) -> df2
class(df2)
# [1] "data.table" "data.frame"
uribo
fuente
0

Proporciono una forma más general y segura de hacer estas cosas,

".." <- function (x) 
{
  stopifnot(inherits(x, "character"))
  stopifnot(length(x) == 1)
  get(x, parent.frame(4))
}


set_colclass <- function(x, class){
  stopifnot(all(class %in% c("integer", "numeric", "double","factor","character")))
  for(i in intersect(names(class), names(x))){
    f <- get(paste0("as.", class[i]))
    x[, (..("i")):=..("f")(get(..("i")))]
  }
  invisible(x)
}

La función ..se asegura de que obtengamos una variable fuera del alcance de data.table; set_colclass establecerá las clases de tus cols. Puedes usarlo así:

dt <- data.table(i=1:3,f=3:1)
set_colclass(dt, c(i="character"))
class(dt$i)
liqg3
fuente
-1

Si tiene una lista de nombres de columna en data.table, desea cambiar la clase de hacer:

convert_to_character <- c("Quarter", "value")

dt[, convert_to_character] <- dt[, lapply(.SD, as.character), .SDcols = convert_to_character]
Emil Lykke Jensen
fuente
Esta respuesta es esencialmente una mala versión de la respuesta de @ Nera a continuación. Simplemente haga dt[, c(convert_to_character) := lapply(.SD, as.character), .SDcols=convert_to_character]para asignar por referencia, en lugar de usar la asignación de data.frame más lenta.
altabq
-3

tratar:

dt <- data.table(A = c(1:5), 
                 B= c(11:15))

x <- ncol(dt)

for(i in 1:x) 
{
     dt[[i]] <- as.character(dt[[i]])
}
usuario151444
fuente