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?


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.


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

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


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

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)

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

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)

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


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í ... =)


6

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


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" 

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)

2

Esta função faz o truque

df <- stacomirtools::killfactor(df)

2

Talvez uma opção mais nova?

library("tidyverse")

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

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   

1

Com o dplyrpacote carregado, use

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

se você quiser alterar a phenotypecoluna-especificamente.


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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.