Converti le classi di colonne in data.table


118

Ho un problema con data.table: come posso convertire le classi di colonne? Ecco un semplice esempio: con data.frame non ho problemi a convertirlo, con data.table non so proprio come:

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)

Mi manca qualcosa di ovvio qui?

Aggiornamento dovuto al post di Matthew: ho usato una versione precedente in precedenza, ma anche dopo l'aggiornamento alla 1.6.6 (la versione che uso ora) ricevo ancora un errore.

Aggiornamento 2: diciamo che voglio convertire ogni colonna di classe "fattore" in una colonna "carattere", ma non so in anticipo quale colonna è di quale classe. Con un data.frame, posso fare quanto segue:

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

Posso fare qualcosa di simile con data.table?

Aggiornamento 3:

sessionInfo () R versione 2.13.1 (2011-07-08) Piattaforma: x86_64-pc-mingw32 / x64 (64 bit)

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

Gli argomenti dell'operatore "[" nei data.tablemetodi sono diversi da quelli per cui sonodata.frame
IRTFM

1
Si prega di incollare l'errore effettivo anziché #Produces error. +1 comunque. Non ricevo alcun errore, quale versione hai? Tuttavia, c'è un problema in quest'area, è stato sollevato in precedenza, FR # 1224 e FR # 1493 hanno la massima priorità da affrontare. La risposta di Andrie è il modo migliore, però.
Matt Dowle

Scusa @ MatthewDowle per aver perso questo nella mia domanda, ho aggiornato il mio post.
Christoph_J

1
@Christoph_J Grazie. Sei sicuro di invalid times argumentquell'errore? Funziona bene per me. Quale versione hai?
Matt Dowle

Ho aggiornato il mio post con sessionInfo (). Tuttavia, oggi l'ho controllato sulla mia macchina da lavoro. Ieri, sulla mia macchina domestica (Ubuntu) si è verificato lo stesso errore. Aggiornerò R e vedrò se il problema è ancora lì.
Christoph_J

Risposte:


104

Per una singola colonna:

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 ...

Utilizzando lapplye 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" ...

2
@Christoph_J Per favore mostra il comando di raggruppamento con cui stai lottando (il vero problema). Pensa di esserti perso qualcosa di semplice. Perché stai cercando di convertire le classi di colonne?
Matt Dowle

1
@Christoph_J Se hai difficoltà a manipolare data.tables, perché non convertirli semplicemente temporaneamente in data.frames, eseguire la pulizia dei dati e poi riconvertirli in data.tables?
Andrie

17
Qual è il modo idiomatico di farlo per un sottoinsieme di colonne (invece che per tutte)? Ho definito un vettore convcolsdi caratteri di colonne. dt[,lapply(.SD,as.numeric),.SDcols=convcols]è quasi istantaneo mentre dt[,convcols:=lapply(.SD,as.numeric),.SDcols=convcols]quasi si blocca R, quindi immagino di aver sbagliato. Grazie
Frank

4
@Frank Vedi il commento di Matt Dowle alla risposta di Geneorama di seguito ( stackoverflow.com/questions/7813578/… ); è stato utile e abbastanza idiomatico per me [citazione iniziale] Un altro modo più semplice è usare set()ad esempio for (col in names_factors) set(dt, j=col, value=as.factor(dt[[col]]))[citazione finale]
swihart

4
Perché usi l'opzione by = ID?
skan

48

Prova questo

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]

7
ora puoi usare la Filterfunzione per identificare le colonne, ad esempio: changeCols<- names(Filter(is.character, DT))
David Leal

1
IMO questa è la risposta migliore, per il motivo che ho dato nella risposta scelta.
James Hirschorn

1
o più conciso: changeCols <- names(DT)[sapply(DT, is.character)].
sindri_baldur

8

Sollevando il commento di Matt Dowle alla risposta di Geneorama ( https://stackoverflow.com/a/20808945/4241780 ) per renderlo più ovvio (come incoraggiato), puoi usarlo 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"

Creato il 12/02/2020 dal pacchetto reprex (v0.3.0)

Vedi un altro dei commenti di Matt su https://stackoverflow.com/a/33000778/4241780 per maggiori informazioni.

Modificare.

Come notato da Espen e in help(set), jpuò essere "Nome / i colonna (carattere) o numero / i (intero) a cui assegnare un valore quando le colonne esistono già". Quindi names_factors <- c(1L, 3L)funzionerà anche.


Potresti voler aggiungere quello che names_factorsc'è qui. Immagino sia preso da stackoverflow.com/a/20808945/1666063 quindi è names_factors = c('fac1', 'fac2')in questo caso, che sono i nomi delle colonne, ma potrebbero anche essere i numeri di colonna per esempio 1; ncol (dt) che convertirà tutte le colonne
Espen Riskedal

@EspenRiskedal Grazie buon punto, ho modificato il post per renderlo più ovvio.
JWilliman

2

Questo è un modo MALE per farlo! Lascio questa risposta solo nel caso in cui risolva altri strani problemi. Questi metodi migliori sono probabilmente in parte il risultato delle nuove versioni di data.table ... quindi vale la pena documentare in questo modo difficile. Inoltre, questo è un bell'esempio di sintassi per la eval substitutesintassi.

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)

che ti dà

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> 

42
Un altro e più semplice modo è usare, set()ad esempiofor (col in names_factors) set(dt, j=col, value=as.factor(dt[[col]]))
Matt Dowle

1
Penso che la mia risposta compia questo in una riga, per tutte le versioni. Non sono sicuro se setsia più appropriato però.
Ben Rollert

1
Maggiori informazioni for(...)set(...)qui: stackoverflow.com/a/33000778/403310
Matt Dowle

1
@skan Bella domanda. Se non riesci a trovare la domanda precedente, fai una nuova domanda. Aiuta gli altri in futuro.
Matt Dowle


0

Ho provato diversi approcci.

# 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 altrimenti

# 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"

0

Fornisco un modo più generale e più sicuro per fare queste cose,

".." <- 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 funzione si ..assicura di ottenere una variabile fuori dall'ambito di data.table; set_colclass imposterà le classi dei tuoi cols. Puoi usarlo in questo modo:

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

-1

Se hai un elenco di nomi di colonne in data.table, vuoi cambiare la classe di do:

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

dt[, convert_to_character] <- dt[, lapply(.SD, as.character), .SDcols = convert_to_character]

Questa risposta è essenzialmente una brutta versione della risposta di @ Nera di seguito. Basta fare dt[, c(convert_to_character) := lapply(.SD, as.character), .SDcols=convert_to_character]per assegnare per riferimento, piuttosto che usare l'assegnazione data.frame più lenta.
altabq

-3

provare:

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]])
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.