Convertir les colonnes data.frame de facteurs en caractères

352

J'ai une trame 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 bloc de données (ce sera une autre question). Mais regarde:

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

BobLes colonnes de sont des facteurs. Ainsi, 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 la bobmain et faire

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

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

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

Mike Dewar
la source
3
ce serait bien si vous pouviez rendre la question reproductible, alors incluez la structure de bob.
jangorecki

Réponses:

362

Juste après Matt et Dirk. Si vous souhaitez recréer votre bloc 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)

Cela convertira toutes les variables en classe "caractère", si vous souhaitez uniquement convertir les facteurs, voir la solution de Marek ci-dessous .

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

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

Dans les deux cas, lapplygénère une liste; cependant, en raison des propriétés magiques de R, l'utilisation de []dans le deuxième cas conserve la classe data.frame de l' bobobjet, éliminant ainsi la nécessité de reconvertir en data.frame en utilisant as.data.frameavec l'argument stringsAsFactors = FALSE.

Shane
la source
27
Shane, cela transformera également les colonnes numériques en caractères.
Dirk Eddelbuettel
@Dirk: C'est vrai, bien qu'il ne soit pas clair si c'est un problème ici. De toute évidence, créer des choses correctement à l'avance est la meilleure solution. Je ne pense pas qu'il soit facile de convertir automatiquement des types de données sur une trame de données. Une option consiste à utiliser ce qui précède, mais à utiliser type.convertaprès avoir tout casté character, puis à refaire factorsune characternouvelle fois.
Shane
Cela semble supprimer les noms de ligne.
piccolbo
2
@piccolbo avez - vous utilisé bob[] <- dans l'exemple ou bob <- ?; le premier conserve le data.frame; le second modifie le data.frame en une liste, supprimant les noms de domaine. Je
mettrai à
6
Une variante qui convertit uniquement les colonnes de facteurs en caractères à l'aide d'une fonction anonyme: iris[] <- lapply(iris, function(x) if (is.factor(x)) as.character(x) else {x})
Stefan F
313

Pour remplacer uniquement les facteurs:

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

Dans le package dplyr de la version 0.5.0, une nouvelle fonction a mutate_ifé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_tibble -> bob
Marek
la source
Ne travaille pas pour moi, malheureusement. Je ne sais pas pourquoi. Probablement parce que j'ai des noms de famille?
Autumnsault
@mohawkjohn ne devrait pas être un problème. Vous avez obtenu une erreur ou des résultats non conformes à vos attentes?
Marek
2
Remarque: la purrrligne renvoie une liste, pas un data.frame!
RoyalTS
Cela fonctionne également si vous en avez déjà un iqui est un vecteur de colnames().
verbamour
39

L'option globale

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

peut être quelque chose que vous souhaitez définir FALSEdans vos fichiers de démarrage (par exemple ~ / .Rprofile). Veuillez voir help(options).

Dirk Eddelbuettel
la source
5
Le problème est que lorsque vous exécutez votre code dans un environnement où ce fichier .Rprofile est manquant, vous obtiendrez des bogues!
waferthin
4
J'ai tendance à l'appeler au début des scripts plutôt que de le définir dans le .Rprofile.
gregmacfarlane
22

Si vous comprenez comment les facteurs sont stockés, vous pouvez éviter d'utiliser des fonctions basées sur les applications pour y parvenir. Ce qui ne signifie nullement que les solutions appliquées ne fonctionnent pas bien.

Les facteurs sont structurés comme des indices numériques liés à une liste de «niveaux». Cela peut être vu si vous convertissez un facteur en numérique. Donc:

> 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 renvoyé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 celui-ci:

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

Cela fonctionne également pour les valeurs numériques, à condition d'envelopper votre expression 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
Kikapp
la source
Cette réponse ne résout pas le problème, qui est de savoir comment convertir toutes les colonnes de facteur de mon bloc de données en caractères. as.character(f), est meilleure à la fois en termes de lisibilité et d'efficacité levels(f)[as.numeric(f)]. Si vous vouliez être intelligent, vous pouvez utiliser à la levels(f)[f]place. Notez que lors de la conversion d'un facteur avec des valeurs numériques, vous obtenez un avantage as.numeric(levels(f))[f], par exemple as.numeric(as.character(f)), mais c'est parce que vous n'avez qu'à convertir les niveaux en numérique, puis en sous-ensemble. as.character(f)est très bien comme ça.
De Novo
20

Si vous voulez un nouveau bloc de données bobcchaque vecteur de facteur bobfest converti en vecteur de caractères, 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 sélectivement le facteur

f <- sapply(bobf, class) == "factor"
bobc[,f] <- lapply(bobc[,f], factor)
scentoni
la source
2
+1 pour ne faire que ce qui était nécessaire (c'est-à-dire ne pas convertir l'intégralité du data.frame en caractère). Cette solution est robuste à un data.frame qui contient des types mixtes.
Joshua Ulrich
3
Cet exemple devrait être dans la section `Exemples 'pour rapply, comme sur: stat.ethz.ch/R-manual/R-devel/library/base/html/rapply.html . Quelqu'un sait-il demander qu'il en soit ainsi?
mpettis
Si vous voulez vous retrouver avec une trame de données, enveloppez simplement le rapply dans un appel data.frame (en utilisant l'argument stringsAsFactors défini sur FALSE)
Taylored Web Sites
13

Je fais 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)
}
by0
la source
8

Une autre façon est de le convertir en utilisant apply

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

Et une meilleure (la précédente est de classe 'matrice')

bob2 <- as.data.frame(as.matrix(bob),stringsAsFactors=F)
George Dontas
la source
Suite au commentaire de @ Shane: pour obtenir data.frame, faitesas.data.frame(lapply(...
aL3xa
7

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, chaque fois que vous rencontrez des problèmes avec des facteurs qui devraient être des caractères, il existe un stringsAsFactorsparamètre quelque part pour vous aider (y compris un paramètre global).

Matt Parker
la source
1
Cela fonctionne, s'il le définit lors de la création bobpour commencer (mais pas après coup).
Shane
Droite. Je voulais juste être clair que cela ne résout pas le problème en soi - mais merci d'avoir noté que cela l'empêche.
Matt Parker
7

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

Ou vous pouvez faire quelque chose comme ça et tuer tous les ravageurs 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)

Ce n'est pas une bonne idée de pousser les données dans un code comme celui-ci, je pourrais faire lesapply partie séparément (en fait, c'est beaucoup plus facile de le faire comme ça), mais vous obtenez le point ... Je n'ai pas vérifié le code, parce que Je ne suis pas chez moi, donc j'espère que ça marche! =)

Cette approche, cependant, a un inconvénient ... vous devez réorganiser les colonnes par la suite, tandis qu'avec transformvous, vous pouvez faire ce que vous voulez, mais au prix de "l'écriture de code de style piéton" ...

Alors là ... =)

aL3xa
la source
6

Au début de votre bloc de données, vous devez stringsAsFactors = FALSEignorer tous les malentendus.


la source
4

Si vous souhaitez utiliser le data.tablepackage 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à une colonne de facteurs 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" 
jangorecki
la source
DT contourne le correctif sévère proposé par Marek: In [<-.data.table(*tmp*, sapply(bob, is.factor), : Coerced 'character' RHS to 'double' to match the column's type. Either change the target column to 'character' first (by creating a new 'character' vector length 1234 (nrows of entire table) and assign that; i.e. 'replace' column), or coerce RHS to 'double' (e.g. 1L, NA_[real|integer]_, as.*, etc) to make your intent clear and for speed. Or, set the column type correctly up front when you create the table and stick to it, please.il est plus facile de réparer le DF et de recréer le DT.
Matt Chambers du
2

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

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

Cette fonction fait l'affaire

df <- stacomirtools::killfactor(df)
Cédric
la source
2

Peut-être une option plus récente?

library("tidyverse")

bob <- bob %>% group_by_if(is.factor, as.character)
rachelette
la source
1

Vous devez utiliser convertdans hablarlequel donne une syntaxe lisible compatible avec les tidyversecanaux:

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   
davsjob
la source
1

Avec l' dplyrutilisation du paquet chargé

bob=bob%>%mutate_at("phenotype", as.character)

si vous souhaitez uniquement modifier la phenotypecolonne-spécifiquement.

nexonvantec
la source
0

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: Obtenez automatiquement les types de colonne de feuille Excel

Ferroao
la source