Convertir les classes de colonnes dans data.table

118

J'ai un problème avec data.table: comment convertir des classes de colonnes? Voici un exemple simple: Avec data.frame je n'ai pas de problème à le convertir, avec data.table je ne sais pas comment:

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)

Est-ce que je rate quelque chose d'évident ici?

Mise à jour due à la publication de Matthew: J'ai utilisé une version plus ancienne avant, mais même après la mise à jour vers la 1.6.6 (la version que j'utilise maintenant), j'obtiens toujours une erreur.

Mise à jour 2: Disons que je veux convertir chaque colonne de classe "facteur" en colonne "caractère", mais je ne sais pas à l'avance quelle colonne appartient à quelle classe. Avec un data.frame, je peux faire ce qui suit:

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

Puis-je faire quelque chose de similaire avec data.table?

Mise à jour 3:

sessionInfo () R version 2.13.1 (08/07/2011) Plate-forme: 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
la source
Les arguments de l'opérateur "[" dans les data.tableméthodes sont différents de ce qu'ils sont pourdata.frame
IRTFM
1
Veuillez coller l'erreur réelle plutôt que #Produces error. +1 de toute façon. Je n'obtiens aucune erreur, quelle version avez-vous? Il y a un problème dans ce domaine cependant, il a déjà été soulevé, FR # 1224 et FR # 1493 sont une priorité élevée à résoudre. La réponse d'Andrie est cependant le meilleur moyen.
Matt Dowle
Désolé @MatthewDowle d'avoir manqué cela dans ma question, j'ai mis à jour mon message.
Christoph_J
1
@Christoph_J Merci. Êtes-vous sûr de cette invalid times argumenterreur? Fonctionne bien pour moi. Quelle version avez-vous?
Matt Dowle
J'ai mis à jour mon message avec le sessionInfo (). Cependant, je l'ai vérifié sur ma machine de travail aujourd'hui. Hier, sur ma machine domestique (Ubuntu), la même erreur s'est produite. Je vais mettre à jour R et voir si le problème est toujours là.
Christoph_J

Réponses:

104

Pour une seule colonne:

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

Utilisation lapplyet 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
la source
2
@Christoph_J Veuillez montrer la commande de regroupement avec laquelle vous vous débattez (le vrai problème). Pensez que vous avez peut-être manqué quelque chose de simple. Pourquoi essayez-vous de convertir des classes de colonnes?
Matt Dowle
1
@Christoph_J Si vous avez du mal à manipuler data.tables, pourquoi ne pas simplement les convertir temporairement en data.frames, nettoyer les données puis les reconvertir en data.tables?
Andrie
17
Quelle est la manière idiomatique de faire cela pour un sous-ensemble de colonnes (au lieu de toutes)? J'ai défini un vecteur convcolsde caractères de colonnes. dt[,lapply(.SD,as.numeric),.SDcols=convcols]est presque instantané alors qu'il dt[,convcols:=lapply(.SD,as.numeric),.SDcols=convcols]gèle presque R, donc je suppose que je le fais mal. Merci
Frank
4
@Frank Voir le commentaire de Matt Dowle à la réponse de Geneorama ci-dessous ( stackoverflow.com/questions/7813578/… ); c'était utile et assez idiomatique pour moi [citation de départ] Un autre moyen plus simple est d'utiliser set()par exemple for (col in names_factors) set(dt, j=col, value=as.factor(dt[[col]]))[end quote]
swihart
4
Pourquoi utilisez-vous l'option by = ID?
skan
48

Essaye ça

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
la source
7
maintenant vous pouvez utiliser la Filterfonction pour identifier les colonnes, par exemple: changeCols<- names(Filter(is.character, DT))
David Leal
1
OMI c'est la meilleure réponse, pour la raison que j'ai donnée dans la réponse choisie.
James Hirschorn
1
ou de façon plus concise: changeCols <- names(DT)[sapply(DT, is.character)].
sindri_baldur
8

En soulevant le commentaire de Matt Dowle sur la réponse de Geneorama ( https://stackoverflow.com/a/20808945/4241780 ) pour le rendre plus évident (comme encouragé), vous pouvez utiliser 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"

Créé le 2020-02-12 par le package reprex (v0.3.0)

Voir un autre des commentaires de Matt à https://stackoverflow.com/a/33000778/4241780 pour plus d'informations.

Éditer.

Comme noté par Espen et dans help(set), jpeut être "Nom (s) de colonne (caractère) ou numéro (s) (entier) à attribuer une valeur lorsque la ou les colonnes existent déjà". Cela names_factors <- c(1L, 3L)fonctionnera également.

JWilliman
la source
Vous voudrez peut-être ajouter ce qui se names_factorstrouve ici. Je suppose que c'est tiré de stackoverflow.com/a/20808945/1666063, donc c'est names_factors = c('fac1', 'fac2')dans ce cas - qui sont les noms de colonnes, mais cela pourrait aussi être des numéros de colonne par exemple 1; ncol (dt) qui convertirait toutes les colonnes
Espen Riskedal
@EspenRiskedal Merci bon point, j'ai édité l'article pour le rendre plus évident.
JWilliman le
2

C'est une mauvaise façon de le faire! Je ne laisse cette réponse qu'au cas où cela résoudrait d'autres problèmes étranges. Ces meilleures méthodes sont probablement en partie le résultat de versions plus récentes de data.table ... il vaut donc la peine de documenter cette manière difficile. De plus, c'est un bel exemple de eval substitutesyntaxe pour la syntaxe.

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)

qui te donne

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
la source
42
Un autre moyen plus simple consiste à utiliser set()par exemplefor (col in names_factors) set(dt, j=col, value=as.factor(dt[[col]]))
Matt Dowle
1
Je pense que ma réponse accomplit cela en une seule ligne, pour toutes les versions. Je ne sais pas si setc'est plus approprié.
Ben Rollert
1
Plus d'informations for(...)set(...)ici: stackoverflow.com/a/33000778/403310
Matt Dowle
1
@skan Bonne question. Si vous ne parvenez pas à la trouver, posez une nouvelle question. Aide les autres à l'avenir.
Matt Dowle
1
@skan voici comment je l'ai fait: github.com/geneorama/geneorama/blob/master/R/…
geneorama
0

J'ai essayé plusieurs approches.

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

, ou autrement

# 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
la source
0

Je propose un moyen plus général et plus sûr de faire ces choses,

".." <- 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 fonction ..s'assure que nous obtenons une variable hors de la portée de data.table; set_colclass définira les classes de vos cols. Vous pouvez l'utiliser comme ceci:

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

Si vous avez une liste de noms de colonnes dans data.table, vous souhaitez changer la classe de do:

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

dt[, convert_to_character] <- dt[, lapply(.SD, as.character), .SDcols = convert_to_character]
Emil Lykke Jensen
la source
Cette réponse est essentiellement une mauvaise version de la réponse de @ Nera ci-dessous. dt[, c(convert_to_character) := lapply(.SD, as.character), .SDcols=convert_to_character]Faites simplement pour attribuer par référence, plutôt que d'utiliser l'attribution plus lente de data.frame.
altabq
-3

essayer:

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]])
}
user151444
la source