Come convertire un fattore in numero intero \ numerico senza perdita di informazioni?


599

Quando converto un fattore in un numero o in un numero intero, ottengo i codici di livello sottostanti, non i valori come numeri.

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

Devo ricorrere a pasteper ottenere i valori reali:

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

C'è un modo migliore per convertire un fattore in numerico?


6
I livelli di un fattore sono comunque memorizzati come tipo di dati carattere ( attributes(f)), quindi non credo che ci sia qualcosa di sbagliato in as.numeric(paste(f)). Forse sarebbe meglio pensare perché (nel contesto specifico) stai ottenendo un fattore in primo luogo, e provare a fermarlo. Ad esempio, l' decargomento è read.tableimpostato correttamente?
CJB,

Se usi un dataframe puoi usare convert da hablar. df %>% convert(num(column)). O se hai un vettore fattore che puoi usareas_reliable_num(factor_vector)
davsjob

Risposte:


712

Vedi la sezione Avvertenza di ?factor:

In particolare, as.numericapplicato a un fattore non ha senso e può accadere per coercizione implicita. Per trasformare un fattore fapprossimativamente nei suoi valori numerici originali, as.numeric(levels(f))[f]è consigliato e leggermente più efficiente di as.numeric(as.character(f)).

Le FAQ su R hanno consigli simili .


Perché è as.numeric(levels(f))[f]più efficace di as.numeric(as.character(f))?

as.numeric(as.character(f))è efficace as.numeric(levels(f)[f]), quindi stai eseguendo la conversione in length(x)valori numerici , anziché in nlevels(x)valori. La differenza di velocità sarà più evidente per i vettori lunghi con pochi livelli. Se i valori sono per lo più unici, non ci sarà molta differenza nella velocità. Comunque tu faccia la conversione, è improbabile che questa operazione sia il collo di bottiglia nel tuo codice, quindi non preoccuparti troppo.


Alcuni tempi

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

4
Per i tempi vedere questa risposta: stackoverflow.com/questions/6979625/…
Ari B. Friedman

3
Mille grazie per la tua soluzione. Posso chiederti perché as.numeric (livelli (f)) [f] è più preciso e più veloce? Grazie.
Sam,

7
@Sam as.character (f) richiede una "ricerca primitiva" per trovare la funzione as.character.factor (), definita come as.numeric (livelli (f)) [f].
Jonathan,

12
quando si applica as.numeric (livelli (f)) [f] OPPURE as.numeric (as.character (f)), ho un messaggio di avviso: Messaggio di avviso: NA introdotti dalla coercizione. Sai dove potrebbe essere il problema? grazie !
Maycca,

@maycca hai superato questo problema?
user08041991

91

R ha una serie di funzioni di convenienza (non documentate) per la conversione di fattori:

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

Ma fastidiosamente, non c'è nulla per gestire il fattore -> conversione numerica . Come estensione della risposta di Joshua Ulrich, suggerirei di superare questa omissione con la definizione della tua funzione idiomatica:

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

che puoi memorizzare all'inizio del tuo script o, meglio, nel tuo .Rprofilefile.


14
Non c'è nulla per gestire la conversione da fattore a numero intero (o numerico) perché si prevede che as.integer(factor)restituisca i codici interi sottostanti (come mostrato nella sezione degli esempi di ?factor). Probabilmente va bene definire questa funzione nel tuo ambiente globale, ma potresti causare problemi se la registri effettivamente come metodo S3.
Joshua Ulrich,

1
Questo è un buon punto e sono d'accordo: una completa ridefinizione della conversione fattore-> numerica probabilmente guasterà molte cose. Mi sono ritrovato a scrivere la factor->numericconversione ingombrante molto prima di rendermi conto che in realtà è un difetto di R: alcune funzioni di convenienza dovrebbero essere disponibili ... Chiamare ha as.numeric.factorsenso per me, ma YMMV.
Jealie,

4
Se ti ritrovi a fare molto , allora dovresti fare qualcosa a monte per evitarlo tutti insieme.
Joshua Ulrich,

2
as.numeric.factor restituisce NA?
jO.

@jO .: nei casi in cui hai usato qualcosa di simile v=NA;as.numeric.factor(v)o v='something';as.numeric.factor(v), allora dovrebbe, altrimenti hai qualcosa di strano che sta succedendo da qualche parte.
Jealie,

33

Il modo più semplice sarebbe usare la unfactorfunzione dal pacchetto varhandle

unfactor(your_factor_variable)

Questo esempio può essere un avvio rapido:

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"

La unfactorfunzione si converte prima in un tipo di dati carattere e poi si converte nuovamente in valore numerico. Digita unfactorsulla console e puoi vederlo al centro della funzione. Pertanto, in realtà non offre una soluzione migliore di quella che aveva già il richiedente.
CJB,

Detto questo, i livelli di un fattore sono comunque di tipo carattere, quindi questo approccio non perde nulla.
CJB,

La unfactorfunzione si occupa di cose che non possono essere convertite in numeriche. Controlla gli esempi inhelp("unfactor")
Mehrad Mahmoudian,

2
@Selrac Ho detto che questa funzione è disponibile nel pacchetto varhandle , il che significa che dovresti prima caricare il pacchetto ( library("varhandle")) (come ho già detto nella prima riga della mia risposta !!)
Mehrad Mahmoudian,

1
@Gregor l'aggiunta di una dipendenza leggera non fa male di solito e, naturalmente, se stai cercando il modo più efficiente, scrivendo il codice che il tuo sé potrebbe eseguire più velocemente. ma come puoi anche vedere nel tuo commento, questo non è banale dal momento che hai anche messo il as.numeric()e as.character()in un ordine sbagliato;) Quello che fa il tuo pezzo di codice è di trasformare l'indice di livello del fattore in una matrice di caratteri, quindi quello che avrai al e è un vettore di caratteri che contiene alcuni numeri che sono stati assegnati una volta a un determinato livello del fattore. Le funzioni di quel pacchetto servono a prevenire queste confusioni
Mehrad Mahmoudian,

23

Nota: questa particolare risposta non è per convertire i fattori con valori numerici in numerici, ma per convertire i fattori categorici nei corrispondenti numeri di livello.


Ogni risposta in questo post non è riuscita a generare risultati per me, i NA venivano generati.

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

Ciò che ha funzionato per me è questo -

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

Sei sicuro di avere un fattore? Guarda questo esempio. y<-factor(c("5","15","20","2")); unclass(y) %>% as.numericCiò restituisce 4,1,3,2, non 5,15,20,2. Sembra un'informazione errata.
MrFlick

Ok, questo è simile a quello che stavo cercando di fare oggi: - y2 <-factor (c ("A", "B", "C", "D", "A")); as.numeric (livelli (y2)) [y2] [1] NA NA NA NA NA Messaggio di avviso: NA introdotte per coercizione mentre unclass (y2)%>% as.numeric mi ha dato i risultati di cui avevo bisogno.
Indi,

4
OK, non è questa la domanda che è stata posta sopra. In questa domanda i livelli dei fattori sono tutti "numerici". Nel tuo caso, as.numeric(y)avrebbe dovuto funzionare bene, non è necessario unclass(). Ma ancora una volta, non è questo il problema. Questa risposta non è appropriata qui.
MrFlick,

3
Beh, spero davvero che aiuti qualcuno che ha fretta come me e legga solo il titolo!
Indi,

1
Se hai dei caratteri che rappresentano gli interi come fattori, questo è quello che consiglierei. questo è l'unico che ha funzionato per me.
aimme

9

È possibile solo nel caso in cui le etichette dei fattori corrispondano ai valori originali. Lo spiegherò con un esempio.

Supponiamo che i dati siano vettoriali x:

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

Ora creerò un fattore con quattro etichette:

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

1) xè con tipo double, fè con tipo integer. Questa è la prima inevitabile perdita di informazioni. I fattori sono sempre memorizzati come numeri interi.

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

2) Non è possibile ripristinare i valori originali (10, 20, 30, 40) solo fdisponibili. Possiamo vedere che fcontiene solo valori interi 1, 2, 3, 4 e due attributi: l'elenco delle etichette ("A", "B", "C", "D") e l'attributo di classe "fattore". Niente di più.

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

Per ripristinare i valori originali, dobbiamo conoscere i valori dei livelli utilizzati nella creazione del fattore. In questo caso c(10, 20, 30, 40). Se conosciamo i livelli originali (nell'ordine corretto), possiamo ripristinare i valori originali.

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

E questo funzionerà solo nel caso in cui siano state definite etichette per tutti i possibili valori nei dati originali.

Quindi, se avrai bisogno dei valori originali, devi mantenerli. Altrimenti c'è un'alta probabilità che non sarà possibile tornare a loro solo da un fattore.


2

È possibile utilizzare hablar::convertse si dispone di un frame di dati. La sintassi è semplice:

Esempio df

library(hablar)
library(dplyr)

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

Soluzione

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

ti dà:

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

O se vuoi che una colonna sia intera e una numerica:

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

risulta in:

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

0

Sembra che la soluzione as.numeric (livelli (f)) [f] non funzioni più con R 4.0.

Soluzione alternativa:

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

factor2number(yourFactor)

-1

Dalle molte risposte che ho potuto leggere, l'unico modo dato era espandere il numero di variabili in base al numero di fattori. Se hai una variabile "pet" con livelli "dog" e "cat", finiresti con pet_dog e pet_cat.

Nel mio caso volevo rimanere con lo stesso numero di variabili, semplicemente traducendo la variabile fattore in una numerica, in un modo che possa essere applicato a molte variabili con molti livelli, in modo che cat = 1 e dog = 0 per esempio.

Di seguito trovi la soluzione corrispondente:

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

-2

tardi al gioco, per caso, ho scoperto che trimws()può essere convertito factor(3:5)in c("3","4","5"). Quindi puoi chiamare as.numeric(). Questo è:

as.numeric(trimws(x_factor_var))

3
C'è un motivo che si consiglia di utilizzare trimwspiù di as.charactercome descritto nella risposta accettata? Mi sembra che a meno che tu non abbia effettivamente uno spazio bianco che devi rimuovere, trimwsfarà solo un mucchio di inutili lavori di espressione regolare per restituire lo stesso risultato.
MrFlick

as.numeric (livelli (f)) [f] potrebbe essere un po 'confuso e difficile da ricordare per i principianti. trimws non fa male.
Jerry T
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.