Converter colunas data.frame de fatores em caracteres

352

Eu tenho um quadro de dados. Vamos chamá-lo 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-

Eu gostaria de concatenar as linhas desse quadro de dados (essa será outra pergunta). Mas olhe:

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

BobAs colunas de são fatores. Então, por exemplo:

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

Não começo a entender isso, mas acho que esses são índices nos níveis dos fatores das colunas (da corte do rei caractacus) de bob? Não é o que eu preciso.

Estranhamente, posso passar pelas colunas bobà mão e fazer

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

o que funciona bem E, depois de digitar, posso obter um data.frame cujas colunas são caracteres e não fatores. Então, minha pergunta é: como posso fazer isso automaticamente? Como converter um data.frame com colunas de fator em um data.frame com colunas de caracteres sem ter que passar manualmente por cada coluna?

Pergunta de bônus: por que a abordagem manual funciona?

Mike Dewar
fonte
3
seria bom se você tornasse a pergunta reproduzível, portanto inclua a estrutura de bob.
Jangorecki

Respostas:

362

Apenas seguindo Matt e Dirk. Se você deseja recriar seu quadro de dados existente sem alterar a opção global, é possível recriá-lo com uma instrução apply:

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

Isso converterá todas as variáveis ​​na classe "caractere"; se você quiser converter apenas fatores, consulte a solução de Marek abaixo .

Como @hadley aponta, o seguinte é mais conciso.

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

Nos dois casos, lapplygera uma lista; no entanto, devido às propriedades mágicas de R, a utilização de [], no segundo caso mantém a classe data.frame do bobobjeto, eliminando assim a necessidade de converter de novo em data.frame usando as.data.framecom o argumento stringsAsFactors = FALSE.

Shane
fonte
27
Shane, isso também transformará colunas numéricas em caracteres.
Dirk Eddelbuettel
@Dirk: Isso é verdade, embora não esteja claro se isso é um problema aqui. Claramente, a criação correta das coisas é a melhor solução. Não acho fácil converter automaticamente tipos de dados em um quadro de dados. Uma opção é usar o acima, mas, em seguida, usar type.convertapós a fundição de tudo para character, em seguida, reformulação factorsde volta para characternovamente.
Shane
Isso parece descartar nomes de linhas.
piccolbo
2
@piccolbo você usou bob[] <- no exemplo ou bob <- ?; o primeiro mantém o data.frame; o segundo altera o data.frame para uma lista, eliminando os nomes de usuário. Atualizarei a resposta #
David LeBauer
6
Uma variante que converte apenas colunas de fator em caractere usando uma função anônima: iris[] <- lapply(iris, function(x) if (is.factor(x)) as.character(x) else {x})
Stefan F /
313

Para substituir apenas fatores:

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

No pacote dplyr na versão 0.5.0, mutate_iffoi introduzida uma nova função :

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

O pacote purrr do RStudio oferece outra alternativa:

library(purrr)
library(dplyr)
bob %>% map_if(is.factor, as.character) %>% as_tibble -> bob
Marek
fonte
Infelizmente não está funcionando para mim. Não sei porque. Provavelmente porque tenho colnames?
Autumnsault 18/07/2014
@mohawkjohn Não deve ser problema. Você obteve erro ou resultados não como o esperado?
Marek
2
Nota: A purrrlinha retorna uma lista, não a data.frame!
RoyalTS 15/08/16
Isso também funciona se você já tiver ium vetor de colnames().
verbamour 19/12/19
39

A opção global

stringsAsFactors: a configuração padrão para argumentos de data.frame e read.table.

pode ser algo que você deseja definir FALSEnos arquivos de inicialização (por exemplo, ~ / .Rprofile). Por favor veja help(options).

Dirk Eddelbuettel
fonte
5
O problema é que, quando você executa seu código em um ambiente em que esse arquivo .Rprofile está ausente, você recebe bugs!
waferthin
4
Costumo chamá-lo no início dos scripts, em vez de a configuração estar no .Rprofile.
Gregmacfarlane
22

Se você entender como os fatores são armazenados, poderá evitar o uso de funções baseadas em aplicação para fazer isso. O que não significa que as soluções de aplicação não funcionem bem.

Os fatores são estruturados como índices numéricos vinculados a uma lista de 'níveis'. Isso pode ser visto se você converter um fator para numérico. Assim:

> 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

Os números retornados na última linha correspondem aos níveis do fator.

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

Observe que levels()retorna uma matriz de caracteres. Você pode usar esse fato para converter de maneira fácil e compacta fatores em cadeias ou números como este:

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

Isso também funciona para valores numéricos, desde que você envolva sua expressão 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
fonte
Esta resposta não soluciona o problema, e é como converto todas as colunas de fatores no meu quadro de dados em caracteres. as.character(f), é melhor em legibilidade e eficiência para levels(f)[as.numeric(f)]. Se você quisesse ser esperto, poderia usar levels(f)[f]. Observe que, ao converter um fator com valores numéricos, você obtém alguns benefícios de as.numeric(levels(f))[f]mais, por exemplo as.numeric(as.character(f)), mas isso ocorre porque você só precisa converter os níveis em numérico e subconjunto. as.character(f)está bem como está.
De Novo
20

Se você deseja um novo quadro de dados em bobcque todos os vetores de fatores bobfsejam convertidos em um vetor de caracteres, tente o seguinte:

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

Se você desejar convertê-lo novamente, poderá criar um vetor lógico cujas colunas são fatores e usá-lo para aplicar seletivamente o fator

f <- sapply(bobf, class) == "factor"
bobc[,f] <- lapply(bobc[,f], factor)
scentoni
fonte
2
+1 para fazer apenas o necessário (ou seja, não converter todo o arquivo data.frame em caractere). Essa solução é robusta para um data.frame que contém tipos mistos.
Joshua Ulrich
3
Este exemplo deve estar na seção `Exemplos 'para rapply, como em: stat.ethz.ch/R-manual/R-devel/library/base/html/rapply.html . Alguém sabe como solicitar que seja assim?
precisa saber é
Se você quiser acabar com um quadro de dados, envoltório simples da rapply em uma chamada data.frame (usando as stringsAsFactors estabelecidos ao argumento falso)
taylored Web Sites
13

Normalmente, faço essa função para além de todos os meus projetos. Rápido e fácil.

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

Outra maneira é convertê-lo usando o aplicativo

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

E uma melhor (a anterior é da classe 'matriz')

bob2 <- as.data.frame(as.matrix(bob),stringsAsFactors=F)
George Dontas
fonte
Seguindo o comentário de @ Shane: a fim de obter data.frame, façaas.data.frame(lapply(...
aL3xa 17/05
7

Atualização: Aqui está um exemplo de algo que não funciona. Eu pensei que sim, mas acho que a opção stringsAsFactors funciona apenas em cadeias de caracteres - deixa os fatores em paz.

Tente o seguinte:

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

De um modo geral, sempre que você estiver tendo problemas com fatores que deveriam ser caracteres, há uma stringsAsFactorsconfiguração em algum lugar para ajudá-lo (incluindo uma configuração global).

Matt Parker
fonte
11
Isso funciona, se ele definir ao criar, bobpara começar (mas não depois do fato).
Shane
Direita. Só queria deixar claro que isso não resolve o problema, por si só - mas obrigado por observar que ele o impede.
Matt Parker
7

Ou você pode tentar transform:

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

Apenas certifique-se de colocar todos os fatores que você deseja converter em caracteres.

Ou você pode fazer algo assim e matar todas as pragas com um golpe:

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)

Não é uma boa ideia colocar os dados em um código como este, eu poderia fazer osapply parte separadamente (na verdade, é muito mais fácil fazê-lo assim), mas você entendeu o ponto ... Eu não verifiquei o código, porque Não estou em casa, então espero que funcione! =)

Essa abordagem, no entanto, tem uma desvantagem ... você deve reorganizar as colunas posteriormente, enquanto transformvocê pode fazer o que quiser, mas com o custo de "redação de código no estilo de pedestre" ...

Então aí ... =)

aL3xa
fonte
6

No início do seu quadro de dados, stringsAsFactors = FALSEignore todos os mal-entendidos.


fonte
4

Se você usasse o data.tablepacote para as operações no data.frame, o problema não está presente.

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

Se você já possui um fator de colunas no conjunto de dados e deseja convertê-las em caracteres, pode fazer o seguinte.

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
fonte
O DT contorna a correção sapply proposta por 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.É mais fácil corrigir o DF e recriar o DT.
Matt Chambers
2

Isso funciona para mim - eu finalmente imaginei um forro

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

Esta função faz o truque

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

Talvez uma opção mais nova?

library("tidyverse")

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

Você deve usar convertno hablarque dá sintaxe legível compatível com tidyversetubos:

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

o que lhe dá:

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

Com o dplyrpacote carregado, use

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

se você quiser alterar a phenotypecoluna-especificamente.

nexonvantec
fonte
0

Isso funciona transformando tudo em caractere e, em seguida, numérico em numérico:

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

Adaptado de: obtenha tipos de colunas da folha do Excel automaticamente

Ferroao
fonte