Converti colonne data.frame da fattori a caratteri


352

Ho un frame di dati. Chiamiamolo 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-

Vorrei concatenare le righe di questo frame di dati (questa sarà un'altra domanda). Ma guarda:

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

BobLe colonne sono fattori. Quindi, per esempio:

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

Non comincio a capirlo, ma suppongo che questi siano indici nei livelli dei fattori delle colonne (della corte del re caractacus) di bob? Non è quello di cui ho bisogno.

Stranamente posso passare attraverso le colonne di bobmano e farlo

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

che funziona benissimo. E, dopo aver digitato un po ', posso ottenere un data.frame le cui colonne sono caratteri piuttosto che fattori. Quindi la mia domanda è: come posso farlo automaticamente? Come faccio a convertire un data.frame con colonne fattore in un data.frame con colonne carattere senza dover passare manualmente attraverso ogni colonna?

Domanda bonus: perché funziona l'approccio manuale?


3
sarebbe bello se la domanda fosse riproducibile, quindi includi la struttura di bob.
— jangorecki,

Risposte:


362

Sto solo seguendo Matt e Dirk. Se si desidera ricreare il frame di dati esistente senza modificare l'opzione globale, è possibile ricrearlo con un'istruzione apply:

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

In questo modo tutte le variabili verranno convertite in "carattere" di classe, se si desidera convertire solo i fattori, vedere la soluzione di Marek di seguito .

Come sottolinea @hadley, quanto segue è più conciso.

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

In entrambi i casi, lapplygenera un elenco; tuttavia, a causa delle proprietà magiche di R, l'uso di []nel secondo caso mantiene la classe data.frame bobdell'oggetto, eliminando così la necessità di riconvertire in un data.frame usando as.data.framecon l'argomento stringsAsFactors = FALSE.


27
Shane, trasformerà anche le colonne numeriche in caratteri.
— Dirk Eddelbuettel,

@Dirk: è vero, anche se non è chiaro se questo sia un problema qui. Chiaramente, creare le cose correttamente in anticipo è la soluzione migliore. Non penso che sia facile convertire automaticamente i tipi di dati in un frame di dati. Un'opzione è quella di utilizzare quanto sopra ma quindi utilizzare type.convertdopo aver lanciato tutto per character, quindi rifondere factorsnuovamentecharacter nuovo.
— Shane,

Questo sembra scartare i nomi delle righe.
— piccolbo,

2
@piccolbo hai usato bob[] <- nell'esempio o bob <- ?; il primo mantiene data.frame; il secondo modifica data.frame in un elenco, eliminando i rownames. Aggiornerò la risposta
— David LeBauer l'

6
Una variante che converte solo le colonne fattoriali in carattere usando una funzione anonima: iris[] <- lapply(iris, function(x) if (is.factor(x)) as.character(x) else {x})
— Stefan F

313

Per sostituire solo i fattori:

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

Nel pacchetto dplyr nella versione 0.5.0 è mutate_ifstata introdotta una nuova funzione :

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

Il pacchetto purrr di RStudio offre un'altra alternativa:

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

Purtroppo non funziona per me. Non so perché. Probabilmente perché ho i nomi dei nomi?
— Autumnsault,

@mohawkjohn Non dovrebbe essere un problema. Hai ricevuto errori o risultati non come previsto?
— Marek,

2
Nota: la purrrriga restituisce un elenco, non un data.frame!
— RoyalTS

Questo funziona anche se hai già iun vettore di colnames().
— verbamour

39

L'opzione globale

stringsAsFactors: l'impostazione predefinita per gli argomenti di data.frame e read.table.

potrebbe essere qualcosa che si desidera impostare FALSEnei file di avvio (ad es. ~ / .Rprofile). Si prega di consultare help(options).


5
Il problema è che quando esegui il tuo codice in un ambiente in cui manca quel file .Rprofile otterrai dei bug!
— waferthin

4
Tendo a chiamarlo all'inizio degli script piuttosto che l'impostazione è nel .profilo.
— gregmacfarlane,

22

Se si capisce come vengono memorizzati i fattori, è possibile evitare di utilizzare le funzioni basate sull'applicazione per ottenere questo risultato. Il che non significa affatto che le soluzioni di applicazione non funzionino bene.

I fattori sono strutturati come indici numerici legati a un elenco di "livelli". Questo può essere visto se si converte un fattore in numerico. Così:

> 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

I numeri restituiti nell'ultima riga corrispondono ai livelli del fattore.

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

Si noti che levels()restituisce una matrice di caratteri. Puoi usare questo fatto per convertire facilmente e in modo compatto i fattori in stringhe o numeri come questo:

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

Questo funziona anche con valori numerici, a condizione che tu abbia inserito la tua espressione 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

Questa risposta non risolve il problema, che è come convertire in carattere tutte le colonne dei fattori nel mio frame di dati. as.character(f), è migliore sia in leggibilità che in efficienza levels(f)[as.numeric(f)]. Se volessi essere intelligente, potresti usare levels(f)[f]invece. Si noti che quando si converte un fattore con valori numerici, si ottiene un certo vantaggio da as.numeric(levels(f))[f]over, ad esempio as.numeric(as.character(f)), ma ciò è dovuto al fatto che è necessario convertire solo i livelli in numerici e quindi sottoinsiemi. as.character(f)va bene così com'è.
— De Novo,

20

Se vuoi un nuovo frame di dati in bobccui ogni vettore di fattore bobfviene convertito in un vettore di caratteri, prova questo:

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

Se si desidera quindi riconvertirlo, è possibile creare un vettore logico di quali colonne sono fattori e utilizzarlo per applicare selettivamente il fattore

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

2
+1 per fare solo ciò che era necessario (cioè non convertire l'intero data.frame in carattere). Questa soluzione è affidabile per un data.frame che contiene tipi misti.
— Joshua Ulrich,

3
Questo esempio dovrebbe essere nella sezione "Esempi" per rapply, come in: stat.ethz.ch/R-manual/R-devel/library/base/html/rapply.html . Qualcuno sa come richiedere che sia così?
— mpettis,

Se vuoi finire con un frame di dati, avvolgi semplicemente il rapply in una chiamata data.frame (usando le stringheAsFactors impostato sull'argomento FALSE)
— Taylored Web Sites

13

In genere faccio questa funzione a parte tutti i miei progetti. Facile e veloce.

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

8

Un altro modo è quello di convertirlo usando Apply

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

E uno migliore (il precedente è di classe 'matrice')

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

Seguendo il commento di @Shane: per ottenere data.frame, fareas.data.frame(lapply(...
— aL3xa

7

Aggiornamento: ecco un esempio di qualcosa che non funziona. Ho pensato che sarebbe successo, ma penso che l'opzione stringheAsFactors funziona solo su stringhe di caratteri - lascia da soli i fattori.

Prova questo:

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

In generale, ogni volta che si verificano problemi con fattori che dovrebbero essere personaggi, c'è stringsAsFactorsun'impostazione da qualche parte per aiutarti (inclusa un'impostazione globale).


1
Questo funziona, se lo imposta durante la creazione bob(ma non dopo il fatto).
— Shane,

Giusto. Volevo solo essere chiaro che questo non risolve il problema, di per sé - ma grazie per aver notato che lo impedisce.
— Matt Parker,

7

Oppure puoi provare transform:

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

Assicurati di inserire tutti i fattori che desideri convertire in personaggio.

Oppure puoi fare qualcosa del genere e uccidere tutti i parassiti con un colpo:

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)

Non è una buona idea inserire i dati nel codice in questo modo, potrei fare la sapplyparte separatamente (in realtà, è molto più facile farlo in quel modo), ma ottieni il punto ... Non ho controllato il codice, perché Non sono a casa, quindi spero che funzioni! =)

Questo approccio, tuttavia, ha un rovescio della medaglia ... devi riorganizzare le colonne in seguito, mentre con transformte puoi fare quello che vuoi, ma a costo di "scrivere codice in stile pedonale" ...

Quindi lì ... =)


6

All'inizio del frame di dati includere stringsAsFactors = FALSEignorare tutti i malintesi.


4

Se si utilizza il data.tablepacchetto per le operazioni su data.frame, il problema non è presente.

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

Se hai già un fattore colonne nel tuo set di dati e vuoi convertirle in carattere, puoi fare quanto segue.

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" 

DT elude la correzione sapply proposta da 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.è più facile riparare il DF e ricreare il DT.
— Matt Chambers,

2

Questo funziona per me - ho finalmente immaginato una fodera

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

2

Questa funzione fa il trucco

df <- stacomirtools::killfactor(df)

2

Forse un'opzione più recente?

library("tidyverse")

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

1

Si dovrebbe usare convertin hablarcui fornisce sintassi leggibile compatibile con le tidyversepipe:

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

che ti dà:

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

1

Con l' dplyruso del pacchetto caricato

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

se si desidera modificare phenotypespecificamente solo la colonna.


0

Questo funziona trasformando tutto in carattere e poi il numerico in numerico:

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

Adattato da: ottieni automaticamente i tipi di colonna del foglio Excel

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.