web-dev-qa-db-fra.com

Conversion des colonnes data.frame de facteurs en caractères

J'ai un cadre de données. Appelons-le 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-

Je voudrais concaténer les lignes de ce cadre de données (ce sera une autre question). Mais regarde:

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

Les colonnes de Bob sont des facteurs. Donc, par exemple:

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

Je ne commence pas à comprendre cela, mais je suppose que ce sont des indices dans les niveaux des facteurs des colonnes (de la cour du roi caractacus) de bob? Pas ce dont j'ai besoin.

Étrangement, je peux parcourir les colonnes de bob à la main et faire

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

qui fonctionne bien. Et, après quelques saisies, je peux obtenir un nom data.frame dont les colonnes sont des caractères plutôt que des facteurs. Ma question est donc: comment puis-je faire cela automatiquement? Comment convertir un data.frame avec des colonnes factorielles en un data.frame avec des colonnes de caractères sans devoir parcourir manuellement chaque colonne?

Question bonus: pourquoi l'approche manuelle fonctionne-t-elle?

319
Mike Dewar

Je ne fais que suivre Matt et Dirk. Si vous souhaitez recréer votre cadre de données existant sans modifier l'option globale, vous pouvez le recréer avec une instruction apply:

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

Ceci convertira toutes les variables en classe "character", si vous voulez uniquement convertir des facteurs, voir solution de Marek ci-dessous .

Comme @hadley le souligne, ce qui suit est plus concis.

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

Dans les deux cas, lapply génère une liste; toutefois, en raison des propriétés magiques de R, l’utilisation de [] dans le second cas conserve la classe data.frame de l’objet bob, éliminant ainsi la nécessité de reconvertir en data.frame avec as.data.frame avec l'argument stringsAsFactors = FALSE.

341
Shane

Pour remplacer uniquement les facteurs:

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

Dans le package dplyr dans la version 0.5.0, nouvelle fonction mutate_if a été introduite :

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

Le paquet purrr de RStudio donne une autre alternative:

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

(gardez à l'esprit que c'est un nouveau paquet)

280
Marek

L'option globale

stringsAsFactors: paramètre par défaut des arguments de data.frame et de read.table.

peut être quelque chose que vous voulez définir sur FALSE dans vos fichiers de démarrage (par exemple, ~/.Rprofile). Veuillez voir help(options).

39

Si vous comprenez comment les facteurs sont stockés, vous pouvez éviter d'utiliser des fonctions basées sur une application pour accomplir cela. Ce qui ne veut pas dire que les solutions à appliquer ne fonctionnent pas bien.

Les facteurs sont structurés sous forme d'indices numériques liés à une liste de "niveaux". Cela se voit si vous convertissez un facteur en numérique. Alors:

> 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

Les nombres retournés dans la dernière ligne correspondent aux niveaux du facteur.

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

Notez que levels() renvoie un tableau de caractères. Vous pouvez utiliser ce fait pour convertir facilement et de manière compacte des facteurs en chaînes ou en chiffres comme ceci:

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

Cela fonctionne également pour les valeurs numériques, à condition que vous envelopper votre expression dans 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
22
Kikapp

Si vous voulez un nouveau bloc de données bobcchaque vecteur facteur dans bobf est converti en vecteur de caractère, essayez ceci:

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

Si vous souhaitez ensuite le reconvertir, vous pouvez créer un vecteur logique dont les colonnes sont des facteurs et l'utiliser pour appliquer de manière sélective le facteur.

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

Je réalise généralement cette fonction en dehors de tous mes projets. Rapide et facile.

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

Une autre façon est de le convertir en appliquant

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

Et un meilleur (le précédent est de classe 'matrice')

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

Ou vous pouvez essayer transform:

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

Assurez-vous simplement de mettre tous les facteurs que vous souhaitez convertir en caractère.

Ou vous pouvez faire quelque chose comme ça et tuer tous les parasites d'un seul coup:

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)

C'est pas bonne idée de déplacer les données dans le code comme ceci, je pourrais faire la partie sapply séparément ( en fait, c'est beaucoup plus facile de le faire comme ça), mais vous comprenez le point ... Je n'ai pas vérifié le code, parce que je ne suis pas à la maison, alors j'espère que ça marche! =)

Cependant, cette approche a un inconvénient ... vous devez réorganiser les colonnes par la suite, alors qu'avec transform vous pouvez faire ce que vous voulez, mais au prix de "code de style piéton -écriture " ...

Alors là ... =)

7
aL3xa

Mise à jour: voici un exemple de quelque chose qui ne fonctionne pas. Je pensais que ce serait le cas, mais je pense que l'option stringsAsFactors ne fonctionne que sur les chaînes de caractères - elle laisse les facteurs seuls.

Essaye ça:

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

De manière générale, lorsque vous rencontrez des problèmes avec des facteurs qui doivent être des caractères, il existe un paramètre stringsAsFactors pour vous aider (y compris un paramètre global).

7
Matt Parker

Au début de votre trame de données, incluez stringsAsFactors = FALSE pour ignorer tous les malentendus.

6
user5462317

Si vous utilisez le package data.table pour les opérations sur data.frame, le problème n’est pas présent.

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

Si vous avez déjà un facteur de colonnes dans votre jeu de données et que vous souhaitez les convertir en caractères, vous pouvez procéder comme suit.

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" 
4
jangorecki

Cette fonction fait l'affaire

df <- stacomirtools::killfactor(df)
2
Cedric

Cela fonctionne pour moi - j'ai finalement pensé à un one liner

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

Vous devez utiliser convert dans hablar qui donne une syntaxe lisible compatible avec les tubes tidyverse:

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

ce qui vous donne:

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

Peut-être une nouvelle option?

library("tidyverse")

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

Cela fonctionne en transformant tout en caractère, puis le numérique en numérique:

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)
}

Adapté de: Obtenir automatiquement les types de colonne de la feuille Excel

0
Ferroao