web-dev-qa-db-fra.com

Convertir le type de plusieurs colonnes d'une base de données à la fois

Il semble que je passe beaucoup de temps à créer un cadre de données à partir d'un fichier, d'une base de données ou quelque chose du genre, puis à convertir chaque colonne en un type (numérique, facteur, caractère, etc.). Est-il possible de faire cela en une seule étape, éventuellement en donnant un vecteur de types?

foo<-data.frame(x=c(1:10), 
                y=c("red", "red", "red", "blue", "blue", 
                    "blue", "yellow", "yellow", "yellow", 
                    "green"),
                z=Sys.Date()+c(1:10))

foo$x<-as.character(foo$x)
foo$y<-as.character(foo$y)
foo$z<-as.numeric(foo$z)

au lieu des trois dernières commandes, j'aimerais faire quelque chose comme

foo<-convert.magic(foo, c(character, character, numeric))
36
PaulHurleyuk

Edit Voir this question connexe pour quelques simplifications et extensions sur cette idée de base.

Mon commentaire à la réponse de Brandon en utilisant switch:

convert.magic <- function(obj,types){
    for (i in 1:length(obj)){
        FUN <- switch(types[i],character = as.character, 
                                   numeric = as.numeric, 
                                   factor = as.factor)
        obj[,i] <- FUN(obj[,i])
    }
    obj
}

out <- convert.magic(foo,c('character','character','numeric'))
> str(out)
'data.frame':   10 obs. of  3 variables:
 $ x: chr  "1" "2" "3" "4" ...
 $ y: chr  "red" "red" "red" "blue" ...
 $ z: num  15254 15255 15256 15257 15258 ...

Pour de très grands cadres de données, vous pouvez utiliser lapply au lieu de la boucle for:

convert.magic1 <- function(obj,types){
    out <- lapply(1:length(obj),FUN = function(i){FUN1 <- switch(types[i],character = as.character,numeric = as.numeric,factor = as.factor); FUN1(obj[,i])})
    names(out) <- colnames(obj)
    as.data.frame(out,stringsAsFactors = FALSE)
}

Ce faisant, soyez conscient de certaines subtilités des données de coercition de R. Par exemple, la conversion de facteur en numérique implique souvent as.numeric(as.character(...)). Tenez également compte du comportement par défaut de data.frame() et as.data.frame()s lors de la conversion de caractère en facteur.

32
joran

Si vous souhaitez détecter automatiquement le type de données des colonnes plutôt que de le spécifier manuellement (par exemple, après le classement des données, etc.), la fonction type.convert() peut vous aider.

La fonction type.convert() prend en compte un vecteur de caractère et tente de déterminer le type optimal pour tous les éléments (ce qui signifie qu'il doit être appliqué une fois par colonne). 

df[] <- lapply(df, function(x) type.convert(as.character(x)))

Depuis que j'aime dplyr, je préfère:

library(dplyr)
df <- df %>% mutate_all(funs(type.convert(as.character(.))))
17
Luke Hankins

Je trouve que je rencontre beaucoup cela aussi. Ceci concerne la manière dont vous importez des données. Toutes les fonctions read ... () ont un type d’option pour spécifier de ne pas convertir les chaînes de caractères en facteur. Cela signifie que les chaînes de texte resteront des caractères et que les éléments qui ressemblent à des nombres resteront sous forme de nombres. Un problème survient lorsque vous avez des éléments vides et non NA. Mais encore une fois, na.strings = c ("", ...) devrait également résoudre ce problème. Je commencerais par examiner de près votre processus d'importation et de l'ajuster en conséquence.

Mais vous pouvez toujours créer une fonction et pousser cette chaîne à travers.

convert.magic <- function(x, y=NA) {
for(i in 1:length(y)) { 
if (y[i] == "numeric") { 
x[i] <- as.numeric(x[[i]])
}
if (y[i] == "character")
x[i] <- as.character(x[[i]])
}
return(x)
}

foo <- convert.magic(foo, c("character", "character", "numeric"))

> str(foo)
'data.frame':   10 obs. of  3 variables:
 $ x: chr  "1" "2" "3" "4" ...
 $ y: chr  "red" "red" "red" "blue" ...
 $ z: num  15254 15255 15256 15257 15258 ...
7

Je sais que je suis assez en retard pour répondre, mais utiliser une boucle avec la fonction d'attributs est une solution simple à votre problème.

names <- c("x", "y", "z")
chclass <- c("character", "character", "numeric")

for (i in (1:length(names))) {
  attributes(foo[, names[i]])$class <- chclass[i]
}
5
SeaJane

Je viens de rencontrer quelque chose comme ceci avec la méthode d'extraction RSQLite ... les résultats sont renvoyés sous forme de types de données atomiques. Dans mon cas, c’était un horodatage qui me causait de la frustration ..__ J'ai trouvé que la fonction setAs est très utile pour aider à faire fonctionner as comme prévu. Voici mon petit exemple. 

##data.frame conversion function
convert.magic2 <- function(df,classes){
  out <- lapply(1:length(classes),
                FUN = function(classIndex){as(df[,classIndex],classes[classIndex])})
  names(out) <- colnames(df)
  return(data.frame(out))
}

##small example case
tmp.df <- data.frame('dt'=c("2013-09-02 09:35:06", "2013-09-02 09:38:24", "2013-09-02 09:38:42", "2013-09-02 09:38:42"),
                     'v'=c('1','2','3','4'),
                     stringsAsFactors=FALSE)
classes=c('POSIXct','numeric')
str(tmp.df)
#confirm that it has character datatype columns
##  'data.frame':  4 obs. of  2 variables:
##    $ dt: chr  "2013-09-02 09:35:06" "2013-09-02 09:38:24" "2013-09-02 09:38:42" "2013-09-02 09:38:42"
##    $ v : chr  "1" "2" "3" "4"

##is the dt column coerceable to POSIXct?
canCoerce(tmp.df$dt,"POSIXct")
##  [1] FALSE

##and the conver.magic2 function fails also:
tmp.df.n <- convert.magic2(tmp.df,classes)

##  Error in as(df[, classIndex], classes[classIndex]) : 
##    no method or default for coercing “character” to “POSIXct” 

##ittle reading reveals the setAS function
setAs('character', 'POSIXct', function(from){return(as.POSIXct(from))})

##better answer for canCoerce
canCoerce(tmp.df$dt,"POSIXct")
##  [1] TRUE

##better answer from conver.magic2
tmp.df.n <- convert.magic2(tmp.df,classes)

##column datatypes converted as I would like them!
str(tmp.df.n)

##  'data.frame':  4 obs. of  2 variables:
##    $ dt: POSIXct, format: "2013-09-02 09:35:06" "2013-09-02 09:38:24" "2013-09-02 09:38:42" "2013-09-02 09:38:42"
##   $ v : num  1 2 3 4
2
Osunderdog

Une solution un peu simple de data.table, bien que quelques étapes soient nécessaires si vous passez à beaucoup de types de colonnes différents.

dt <- data.table( x=c(1:10), y=c(10:20), z=c(10:20), name=letters[1:10])

dt <- dt[, lapply(.SD, as.numeric), by= name]

Ceci changera toutes les colonnes sauf celles spécifiées dans by en numérique (ou tout ce que vous avez défini dans lapply)

1
moman822

Ajout à la réponse de @ joran, dans laquelle convert.magic ne conserverait pas les valeurs numériques dans la conversion facteur-à-numérique:

convert.magic <- function(obj,types){
    out <- lapply(1:length(obj),FUN = function(i){FUN1 <- switch(types[i],
    character = as.character,numeric = as.numeric,factor = as.factor); FUN1(obj[,i])})
    names(out) <- colnames(obj)
    as.data.frame(out,stringsAsFactors = FALSE)
}

foo<-data.frame(x=c(1:10), 
                    y=c("red", "red", "red", "blue", "blue", 
                        "blue", "yellow", "yellow", "yellow", 
                        "green"),
                    z=Sys.Date()+c(1:10))

foo$x<-as.character(foo$x)
foo$y<-as.character(foo$y)
foo$z<-as.numeric(foo$z)

str(foo)
# 'data.frame': 10 obs. of  3 variables:
# $ x: chr  "1" "2" "3" "4" ...
# $ y: chr  "red" "red" "red" "blue" ...
# $ z: num  16777 16778 16779 16780 16781 ...

foo.factors <- convert.magic(foo, rep("factor", 3))

str(foo.factors) # all factors

foo.numeric.not.preserved <- convert.magic(foo.factors, c("numeric", "character", "numeric"))

str(foo.numeric.not.preserved)
# 'data.frame': 10 obs. of  3 variables:
# $ x: num  1 3 4 5 6 7 8 9 10 2
# $ y: chr  "red" "red" "red" "blue" ...
# $ z: num  1 2 3 4 5 6 7 8 9 10

# z comes out as 1 2 3...

Les éléments suivants doivent conserver les valeurs numériques:

## as.numeric function that preserves numeric values when converting factor to numeric

as.numeric.mod <- function(x) {
    if(is.factor(x))
      as.numeric(levels(x))[x]
  else
      as.numeric(x)
}

## The same than in @joran's answer, except for as.numeric.mod
convert.magic <- function(obj,types){
    out <- lapply(1:length(obj),FUN = function(i){FUN1 <- switch(types[i],
    character = as.character,numeric = as.numeric.mod, factor = as.factor); FUN1(obj[,i])})
    names(out) <- colnames(obj)
    as.data.frame(out,stringsAsFactors = FALSE)
}

foo.numeric <- convert.magic(foo.factors, c("numeric", "character", "numeric"))

str(foo.numeric)
# 'data.frame': 10 obs. of  3 variables:
# $ x: num  1 2 3 4 5 6 7 8 9 10
# $ y: chr  "red" "red" "red" "blue" ...
# $ z: num  16777 16778 16779 16780 16781 ...

# z comes out with the correct numeric values
0
Mikko

La transformation est ce que vous semblez décrire:

foo <- transform(foo, x=as.character(x), y=as.character(y), z=as.numeric(z))
0
leo277