Como converter um fator para inteiro \ numérico sem perda de informação?

599

Quando converto um fator em um numérico ou número inteiro, obtenho os códigos de nível subjacentes, não os valores como números.

f <- factor(sample(runif(5), 20, replace = TRUE))
##  [1] 0.0248644019011408 0.0248644019011408 0.179684827337041 
##  [4] 0.0284090070053935 0.363644931698218  0.363644931698218 
##  [7] 0.179684827337041  0.249704354675487  0.249704354675487 
## [10] 0.0248644019011408 0.249704354675487  0.0284090070053935
## [13] 0.179684827337041  0.0248644019011408 0.179684827337041 
## [16] 0.363644931698218  0.249704354675487  0.363644931698218 
## [19] 0.179684827337041  0.0284090070053935
## 5 Levels: 0.0248644019011408 0.0284090070053935 ... 0.363644931698218

as.numeric(f)
##  [1] 1 1 3 2 5 5 3 4 4 1 4 2 3 1 3 5 4 5 3 2

as.integer(f)
##  [1] 1 1 3 2 5 5 3 4 4 1 4 2 3 1 3 5 4 5 3 2

Eu tenho que recorrer pastepara obter os valores reais:

as.numeric(paste(f))
##  [1] 0.02486440 0.02486440 0.17968483 0.02840901 0.36364493 0.36364493
##  [7] 0.17968483 0.24970435 0.24970435 0.02486440 0.24970435 0.02840901
## [13] 0.17968483 0.02486440 0.17968483 0.36364493 0.24970435 0.36364493
## [19] 0.17968483 0.02840901

Existe uma maneira melhor de converter um fator para numérico?

Adam SO
fonte
6
Os níveis de um fator são armazenados como o tipo de dados de caracteres de qualquer maneira ( attributes(f)), então não acho que haja algo errado as.numeric(paste(f)). Talvez seja melhor pensar por que (no contexto específico) você está recebendo um fator em primeiro lugar e tentar impedir isso. Por exemplo, o decargumento está read.tabledefinido corretamente?
CJB
Se você usa um quadro de dados, pode usar convert from hablar. df %>% convert(num(column)). Ou se você tiver um vetor fator que você pode usaras_reliable_num(factor_vector)
davsjob

Respostas:

711

Consulte a seção Aviso de ?factor:

Em particular, as.numericaplicar a um fator não tem sentido e pode acontecer por coerção implícita. Para transformar um fator fem aproximadamente seus valores numéricos originais, as.numeric(levels(f))[f]é recomendado e um pouco mais eficiente que as.numeric(as.character(f)).

O FAQ sobre R tem conselhos semelhantes .


Por que é as.numeric(levels(f))[f]mais eficaz que as.numeric(as.character(f))?

as.numeric(as.character(f))é efetivamente as.numeric(levels(f)[f]), portanto, você está realizando a conversão em length(x)valores numéricos em vez de nlevels(x)valores. A diferença de velocidade será mais aparente para vetores longos com poucos níveis. Se os valores forem principalmente únicos, não haverá muita diferença de velocidade. Seja como for a conversão, é improvável que essa operação seja o gargalo no seu código, portanto, não se preocupe muito com isso.


Alguns horários

library(microbenchmark)
microbenchmark(
  as.numeric(levels(f))[f],
  as.numeric(levels(f)[f]),
  as.numeric(as.character(f)),
  paste0(x),
  paste(x),
  times = 1e5
)
## Unit: microseconds
##                         expr   min    lq      mean median     uq      max neval
##     as.numeric(levels(f))[f] 3.982 5.120  6.088624  5.405  5.974 1981.418 1e+05
##     as.numeric(levels(f)[f]) 5.973 7.111  8.352032  7.396  8.250 4256.380 1e+05
##  as.numeric(as.character(f)) 6.827 8.249  9.628264  8.534  9.671 1983.694 1e+05
##                    paste0(x) 7.964 9.387 11.026351  9.956 10.810 2911.257 1e+05
##                     paste(x) 7.965 9.387 11.127308  9.956 11.093 2419.458 1e+05
Joshua Ulrich
fonte
4
Para saber os horários, veja esta resposta: stackoverflow.com/questions/6979625/…
Ari B. Friedman
3
Muito obrigado pela sua solução. Posso perguntar por que o as.numeric (levels (f)) [f] é mais preciso e mais rápido? Obrigado.
Sam
7
@Sam as.character (f) requer uma "pesquisa primitiva" para encontrar a função as.character.factor (), que é definida como.numeric (levels (f)) [f].
Jonathan
12
quando aplicar como numérico (níveis (f)) [f] OU como numérico (como caractere (f)), tenho uma mensagem de aviso: Mensagem de aviso: NAs introduzidas por coerção. Você sabe onde pode estar o problema? obrigado !
maycca
@maycca você superou esse problema?
user08041991
91

R possui várias funções de conveniência (não documentadas) para fatores de conversão:

  • as.character.factor
  • as.data.frame.factor
  • as.Date.factor
  • as.list.factor
  • as.vector.factor
  • ...

Mas irritantemente, não há nada para lidar com o fator -> conversão numérica . Como uma extensão da resposta de Joshua Ulrich, sugiro superar essa omissão com a definição de sua própria função idiomática:

as.numeric.factor <- function(x) {as.numeric(levels(x))[x]}

que você pode armazenar no início do seu script, ou ainda melhor no seu .Rprofilearquivo.

Jealie
fonte
14
Não há nada para lidar com a conversão de fator para número inteiro (ou numérico) porque é esperado que as.integer(factor)retorne os códigos inteiros subjacentes (como mostrado na seção de exemplos de ?factor). Provavelmente, não há problema em definir essa função em seu ambiente global, mas você poderá causar problemas se realmente registrá-la como um método S3.
Joshua Ulrich
1
Esse é um bom argumento e concordo: uma redefinição completa da conversão fator-> numérica provavelmente bagunçará muitas coisas. Eu encontrei-me escrever o incómodo factor->numericde conversão muito antes de perceber que na verdade é uma lacuna de R: alguns função de conveniência deve estar disponível ... Chamar isso as.numeric.factorfaz sentido para mim, mas YMMV.
Jealie
4
Se você se encontra fazendo muito isso , deve fazer algo a montante para evitar tudo junto.
Joshua Ulrich
2
as.numeric.factor retorna NA?
jO.
@jO .: nos casos em que você usou algo como v=NA;as.numeric.factor(v)ou v='something';as.numeric.factor(v), então deveria, caso contrário, você tem algo estranho acontecendo em algum lugar.
Jealie
33

A maneira mais fácil seria usar a unfactorfunção do pacote varhandle

unfactor(your_factor_variable)

Este exemplo pode ser um começo rápido:

x <- rep(c("a", "b", "c"), 20)
y <- rep(c(1, 1, 0), 20)

class(x)  # -> "character"
class(y)  # -> "numeric"

x <- factor(x)
y <- factor(y)

class(x)  # -> "factor"
class(y)  # -> "factor"

library(varhandle)
x <- unfactor(x)
y <- unfactor(y)

class(x)  # -> "character"
class(y)  # -> "numeric"
Mehrad Mahmoudian
fonte
A unfactorfunção converte primeiro para o tipo de dados de caractere e depois converte novamente para numérico. Digite unfactorno console e você poderá vê-lo no meio da função. Portanto, ele realmente não dá uma solução melhor do que o que o solicitante já tinha.
CJB
Dito isto, os níveis de um fator são do tipo de caractere, portanto, nada é perdido por essa abordagem.
CJB
A unfactorfunção cuida de coisas que não podem ser convertidas para numéricas. Veja os exemplos emhelp("unfactor")
Mehrad Mahmoudian
2
@Selrac eu mencionei que esta função está disponível em varhandle pacote, ou seja, você deve carregar o pacote ( library("varhandle")) primeiro (como mencionei na primeira linha da minha resposta !!)
Mehrad Mahmoudian
1
O @Gregor, que adiciona uma dependência leve, geralmente não é prejudicial e, é claro, se você está procurando a maneira mais eficiente, escrevendo o código que você pode executar com mais rapidez. mas como você também pode ver no seu comentário, isso não é trivial, pois você também coloca a ordem as.numeric()e as.character()em uma ordem errada;) O que o seu pedaço de código faz é transformar o índice de nível do fator em uma matriz de caracteres, então o que você terá no e é um vetor de caractere que contém alguns números que já foram atribuídos a determinado nível do seu fator. Funções em que o pacote está lá para evitar essas confusões
Mehrad Mahmoudian
23

Nota: esta resposta específica não é para converter fatores de valor numérico em numéricos, é para converter fatores categóricos em seus números de nível correspondentes.


Todas as respostas deste post falharam em gerar resultados para mim, os NAs estavam sendo gerados.

y2<-factor(c("A","B","C","D","A")); 
as.numeric(levels(y2))[y2] 
[1] NA NA NA NA NA Warning message: NAs introduced by coercion

O que funcionou para mim é isso -

as.integer(y2)
# [1] 1 2 3 4 1
Indi
fonte
Tem certeza de que teve um fator? Veja este exemplo. y<-factor(c("5","15","20","2")); unclass(y) %>% as.numericIsso retorna 4,1,3,2, não 5,15,20,2. Parece informação incorreta.
MrFlick
Ok, isso é semelhante ao que eu estava tentando fazer hoje: - y2 <fator (c ("A", "B", "C", "D", "A")); as.numeric (níveis (y2)) [y2] [1] NA NA NA NA NA Mensagem de aviso: NAs introduzidas por coerção enquanto que unclass (y2)%>% as.numeric me deu os resultados que eu precisava.
Indi
4
OK, bem, essa não é a pergunta que foi feita acima. Nesta questão, os níveis dos fatores são todos "numéricos". No seu caso, as.numeric(y)deveria ter funcionado bem, sem a necessidade de unclass(). Mas, novamente, não é disso que se trata essa pergunta. Esta resposta não é apropriada aqui.
MrFlick
3
Bem, eu realmente espero que ajude alguém que estava com pressa como eu e leia apenas o título!
Indi
1
Se você tiver caracteres representando os números inteiros como fatores, este é o que eu recomendaria. este é o único que funcionou para mim.
AIMME
9

É possível apenas no caso em que os rótulos dos fatores correspondam aos valores originais. Vou explicar com um exemplo.

Suponha que os dados sejam vetoriais x:

x <- c(20, 10, 30, 20, 10, 40, 10, 40)

Agora vou criar um fator com quatro rótulos:

f <- factor(x, levels = c(10, 20, 30, 40), labels = c("A", "B", "C", "D"))

1) xé do tipo double, fé do tipo inteiro. Esta é a primeira perda inevitável de informações. Os fatores são sempre armazenados como números inteiros.

> typeof(x)
[1] "double"
> typeof(f)
[1] "integer"

2) Não é possível voltar aos valores originais (10, 20, 30, 40) apenas fdisponíveis. Podemos ver que fpossui apenas valores inteiros 1, 2, 3, 4 e dois atributos - a lista de rótulos ("A", "B", "C", "D") e o atributo de classe "fator". Nada mais.

> str(f)
 Factor w/ 4 levels "A","B","C","D": 2 1 3 2 1 4 1 4
> attributes(f)
$levels
[1] "A" "B" "C" "D"

$class
[1] "factor"

Para voltar aos valores originais, precisamos conhecer os valores dos níveis usados ​​na criação do fator. Nesse caso c(10, 20, 30, 40). Se conhecermos os níveis originais (na ordem correta), podemos voltar aos valores originais.

> orig_levels <- c(10, 20, 30, 40)
> x1 <- orig_levels[f]
> all.equal(x, x1)
[1] TRUE

E isso funcionará apenas no caso em que os rótulos tenham sido definidos para todos os valores possíveis nos dados originais.

Portanto, se você precisar dos valores originais, precisará mantê-los. Caso contrário, há uma grande chance de que não seja possível retornar a eles apenas por um fator.

djhurio
fonte
2

Você pode usar hablar::convertse você tiver um quadro de dados. A sintaxe é fácil:

Amostra df

library(hablar)
library(dplyr)

df <- dplyr::tibble(a = as.factor(c("7", "3")),
                    b = as.factor(c("1.5", "6.3")))

Solução

df %>% 
  convert(num(a, b))

da-te:

# A tibble: 2 x 2
      a     b
  <dbl> <dbl>
1    7.  1.50
2    3.  6.30

Ou se você deseja que uma coluna seja inteira e uma numérica:

df %>% 
  convert(int(a),
          num(b))

resulta em:

# A tibble: 2 x 2
      a     b
  <int> <dbl>
1     7  1.50
2     3  6.30
davsjob
fonte
0

Parece que a solução como.numeric (levels (f)) [f] não funciona mais com o R 4.0.

Solução alternativa:

factor2number <- function(x){
    data.frame(levels(x), 1:length(levels(x)), row.names = 1)[x, 1]
}

factor2number(yourFactor)
Etapas da vida útil
fonte
-1

Das muitas respostas que pude ler, a única maneira dada era expandir o número de variáveis ​​de acordo com o número de fatores. Se você tiver uma variável "pet" com os níveis "dog" e "cat", você terminará com pet_dog e pet_cat.

No meu caso, eu queria ficar com o mesmo número de variáveis, apenas convertendo a variável fator para uma numérica, de uma maneira que possa ser aplicada a muitas variáveis ​​com muitos níveis, de modo que cat = 1 e dog = 0, por exemplo.

Encontre a solução correspondente abaixo:

crime <- data.frame(city = c("SF", "SF", "NYC"),
                    year = c(1990, 2000, 1990),
                    crime = 1:3)

indx <- sapply(crime, is.factor)

crime[indx] <- lapply(crime[indx], function(x){ 
  listOri <- unique(x)
  listMod <- seq_along(listOri)
  res <- factor(x, levels=listOri)
  res <- as.numeric(res)
  return(res)
}
)
Xavier Prudent
fonte
-2

tarde para o jogo, acidentalmente, eu encontrei trimws()pode converter factor(3:5)para c("3","4","5"). Então você pode ligar as.numeric(). Isso é:

as.numeric(trimws(x_factor_var))
Jerry T
fonte
3
Existe um motivo pelo qual você recomendaria usar trimwsmais, as.characterconforme descrito na resposta aceita? Parece-me que, a menos que você realmente tenha um espaço em branco que precise remover, trimwsapenas fará um monte de trabalhos desnecessários de expressão regular para retornar o mesmo resultado.
MrFlick
as.numeric (levels (f)) [f] is pode ser um pouco confuso e difícil de lembrar para iniciantes. trimws não faz mal.
Jerry T