Jak przekonwertować współczynnik na liczbę całkowitą \ numeryczną bez utraty informacji?

598

Kiedy przekształcam czynnik na wartość liczbową lub całkowitą, otrzymuję kody poziomu podstawowego, a nie wartości jako liczby.

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

Muszę pasteskorzystać z prawdziwych wartości:

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

Czy jest lepszy sposób na konwersję współczynnika na liczbę?

Adam SO
źródło
6
Poziomy współczynnika i tak są przechowywane jako typ danych znakowych ( attributes(f)), więc nie sądzę, że coś jest nie tak as.numeric(paste(f)). Być może lepiej byłoby pomyśleć, dlaczego (w konkretnym kontekście) dostajesz czynnik w pierwszej kolejności i spróbować go zatrzymać. Np. Czy decargument jest read.tableustawiony poprawnie?
CJB
Jeśli używasz ramki danych, możesz użyć konwersji z hablara. df %>% convert(num(column)). Lub jeśli masz wektor czynnikowy, którego możesz użyćas_reliable_num(factor_vector)
davsjob

Odpowiedzi:

711

Zobacz sekcję Ostrzeżenie w ?factor:

W szczególności as.numericzastosowanie do czynnika jest bez znaczenia i może nastąpić w wyniku domniemanego przymusu. Aby przekształcić współczynnik fw przybliżeniu do jego pierwotnych wartości liczbowych, as.numeric(levels(f))[f]jest zalecane i nieco bardziej wydajne niż as.numeric(as.character(f)).

FAQ na temat R zawiera podobne porady .


Dlaczego jest as.numeric(levels(f))[f]bardziej skuteczny niż as.numeric(as.character(f))?

as.numeric(as.character(f))jest efektywny as.numeric(levels(f)[f]), dlatego konwersja na length(x)wartości liczbowe jest przeprowadzana raczej na nlevels(x)wartościach niż na wartościach. Różnica prędkości będzie najbardziej widoczna dla długich wektorów z kilkoma poziomami. Jeśli wartości są w większości unikalne, nie będzie dużej różnicy prędkości. Jakkolwiek wykonasz konwersję, jest mało prawdopodobne, aby ta operacja była wąskim gardłem w twoim kodzie, więc nie przejmuj się tym zbytnio.


Niektóre czasy

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
źródło
4
Aby zobaczyć czasy, zobacz tę odpowiedź: stackoverflow.com/questions/6979625/...
Ari B. Friedman,
3
Wielkie dzięki za twoje rozwiązanie. Czy mogę zapytać, dlaczego as.numeric (poziomy (f)) [f] jest bardziej precyzyjny i szybszy? Dzięki.
Sam
7
@Sam as.character (f) wymaga „prymitywnego wyszukiwania”, aby znaleźć funkcję as.character.factor (), która jest zdefiniowana jako as.numeric (poziomy (f)) [f].
Jonathan
12
gdy stosuje się jako numeryczny (poziomy (f)) [f] LUB jako numeryczny (jako znak (f)), pojawia się komunikat ostrzegawczy: Komunikat ostrzegawczy: NA wprowadzone przez przymus. Czy wiesz, gdzie może być problem? Dziękuję Ci !
maycca
@maycca pokonałeś ten problem?
user08041991
91

R ma wiele (nieudokumentowanych) funkcji ułatwiających przeliczanie czynników:

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

Ale irytujące jest to, że nie można nic poradzić na czynnik -> konwersja liczbowa . Jako rozszerzenie odpowiedzi Joshuy Ulricha proponuję przezwyciężyć to pominięcie, definiując własną funkcję idiomatyczną:

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

które możesz zapisać na początku skryptu, a nawet lepiej w .Rprofilepliku.

Zazdrość
źródło
14
Nie ma nic, co mogłoby poradzić sobie z konwersją współczynnika na liczbę całkowitą (lub liczbową), ponieważ oczekuje się, że as.integer(factor)zwróci podstawowe kody liczb całkowitych (jak pokazano w sekcji przykładów ?factor). Prawdopodobnie dobrze jest zdefiniować tę funkcję w globalnym środowisku, ale możesz powodować problemy, jeśli faktycznie zarejestrujesz ją jako metodę S3.
Joshua Ulrich
1
To dobra uwaga i zgadzam się: całkowita redefinicja współczynnika> konwersji numerycznej może zepsuć wiele rzeczy. Znalazłem się pisania uciążliwe factor->numerickonwersję dużo przed zdając sobie sprawy, że w rzeczywistości jest to wada R: niektóre funkcje wygoda powinny być dostępne ... Wywołanie to as.numeric.factorma sens dla mnie, ale YMMV.
Jealie
4
Jeśli często to robisz, powinieneś zrobić coś z góry, aby tego uniknąć.
Joshua Ulrich
2
as.numeric.factor zwraca NA?
JO.
@ jO .: w przypadkach, w których użyłeś czegoś takiego v=NA;as.numeric.factor(v)lub v='something';as.numeric.factor(v), to powinno, w przeciwnym razie gdzieś dzieje się coś dziwnego.
Jealie
33

Najłatwiejszym sposobem byłoby użycie unfactorfunkcji z pakietu varhandle

unfactor(your_factor_variable)

Ten przykład może być szybkim początkiem:

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
źródło
W unfactorpierwszym, a następnie konwertuje funkcyjne do wpisywania danych charakter nawróceni powrotem do numerycznej. Pisz unfactorw konsoli, a zobaczysz go w środku funkcji. Dlatego tak naprawdę nie daje lepszego rozwiązania niż to, które już pytał.
CJB
Powiedziawszy to, poziomy współczynnika i tak mają charakter postaci, więc dzięki temu podejściu nic nie jest stracone.
CJB
unfactorFunkcja dba o rzeczy, które nie mogą być konwertowane na numeryczną. Sprawdź przykłady whelp("unfactor")
Mehrad Mahmoudian
2
@Selrac Wspomniałem, że ta funkcja jest dostępna w pakiecie varhandle , co oznacza, że ​​najpierw należy załadować pakiet ( library("varhandle")) (jak wspomniałem w pierwszym wierszu mojej odpowiedzi !!)
Mehrad Mahmoudian
1
@Gregor dodając lekką zależność zwykle nie szkodzi i oczywiście, jeśli szukasz najbardziej wydajnego sposobu, pisząc kod, który sam może wykonać szybciej. ale jak widać w komentarzu, nie jest to trywialne, ponieważ również ułożyłeś as.numeric()i as.character()w niewłaściwej kolejności;) To, co robi twoja część kodu, polega na przekształceniu wskaźnika poziomu czynnika w matrycę znaków, więc to, co będziesz miał na jest wektorem znaków, który zawiera pewne liczby, które zostały kiedyś przypisane do określonego poziomu twojego współczynnika. Funkcje zawarte w tym pakiecie zapobiegają takim pomyłkom
Mehrad Mahmoudian,
23

Uwaga: ta konkretna odpowiedź nie służy do konwersji czynników o wartościach liczbowych na liczby, lecz do konwersji czynników kategorycznych na odpowiadające im liczby poziomów.


Każda odpowiedź w tym poście nie generowała dla mnie wyników, NA były generowane.

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

Dla mnie zadziałało to -

as.integer(y2)
# [1] 1 2 3 4 1
Ind
źródło
Czy na pewno miałeś jakiś czynnik? Spójrz na ten przykład. y<-factor(c("5","15","20","2")); unclass(y) %>% as.numericZwraca 4,1,3,2, a nie 5,15,20,2. To wydaje się niepoprawne informacje.
MrFlick
Ok, to jest podobne do tego, co próbowałem dzisiaj zrobić: - y2 <-factor (c („A”, „B”, „C”, „D”, „A”)); as.numeric (poziomy (y2)) [y2] [1] NA NA NA NA NA Ostrzeżenie NA: NA wprowadzone przez przymus, natomiast unclass (y2)%>% as.numeric dał mi wyniki, których potrzebowałem.
Indi
4
OK, cóż, to nie jest pytanie, które zostało zadane powyżej. W tym pytaniu wszystkie poziomy czynników są „numeryczne”. W twoim przypadku as.numeric(y)powinno być dobrze, bez potrzeby unclass(). Ale znowu nie o to chodziło w tym pytaniu. Ta odpowiedź nie jest tutaj odpowiednia.
MrFlick
3
Naprawdę mam nadzieję, że pomoże to komuś, kto spieszył się jak ja i przeczytał tylko tytuł!
Indi
1
Jeśli masz znaki reprezentujące liczby całkowite jako czynniki, polecam to. to jedyny, który dla mnie działał.
aimme
9

Jest to możliwe tylko w przypadku, gdy etykiety czynników odpowiadają oryginalnym wartościom. Wyjaśnię to na przykładzie.

Załóżmy, że dane są wektorem x:

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

Teraz utworzę czynnik z czterema etykietami:

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

1) xjest z typem double, fjest z typem integer. To pierwsza nieunikniona utrata informacji. Czynniki są zawsze przechowywane jako liczby całkowite.

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

2) Nie ma możliwości powrotu do pierwotnych wartości (10, 20, 30, 40), które są fdostępne tylko . Widzimy, że fzawiera tylko wartości całkowite 1, 2, 3, 4 i dwa atrybuty - listę etykiet („A”, „B”, „C”, „D”) i atrybut klasy „współczynnik”. Nic więcej.

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

Aby powrócić do pierwotnych wartości, musimy znać wartości poziomów zastosowanych przy tworzeniu współczynnika. W tym przypadku c(10, 20, 30, 40). Jeśli znamy oryginalne poziomy (w odpowiedniej kolejności), możemy wrócić do pierwotnych wartości.

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

Będzie to działać tylko w przypadku, gdy zdefiniowano etykiety dla wszystkich możliwych wartości w oryginalnych danych.

Więc jeśli będziesz potrzebować oryginalnych wartości, musisz je zachować. W przeciwnym razie istnieje duża szansa, że ​​nie będzie można do nich wrócić tylko z jednego powodu.

djhurio
źródło
2

Możesz użyć, hablar::convertjeśli masz ramkę danych. Składnia jest łatwa:

Próbka df

library(hablar)
library(dplyr)

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

Rozwiązanie

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

daje Ci:

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

Lub jeśli chcesz, aby jedna kolumna była liczbą całkowitą i jedną liczbą:

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

prowadzi do:

# A tibble: 2 x 2
      a     b
  <int> <dbl>
1     7  1.50
2     3  6.30
davsjob
źródło
0

Wygląda na to, że rozwiązanie as.numeric (poziomy (f)) [f] nie działa już z R 4.0.

Alternatywne rozwiązanie:

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

factor2number(yourFactor)
Life_Searching_Steps
źródło
-1

Z wielu odpowiedzi, które mogłem przeczytać, jedynym możliwym sposobem było zwiększenie liczby zmiennych zgodnie z liczbą czynników. Jeśli masz zmienną „zwierzak” z poziomami „pies” i „kot”, skończyłbyś na pet_dog i pet_cat.

W moim przypadku chciałem pozostać przy tej samej liczbie zmiennych, po prostu tłumacząc zmienną czynnikową na zmienną liczbową, w sposób, który można zastosować do wielu zmiennych o wielu poziomach, na przykład cat = 1 i dog = 0.

Poniżej znajdziesz odpowiednie rozwiązanie:

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 Roztropny
źródło
-2

późno do gry, przypadkowo, znalazłem trimws()można konwertować factor(3:5)do c("3","4","5"). Następnie możesz zadzwonić as.numeric(). To jest:

as.numeric(trimws(x_factor_var))
Jerry T.
źródło
3
Czy istnieje powód, dla którego zaleciłbyś użycie trimwsw as.charactersposób opisany w zaakceptowanej odpowiedzi? Wydaje mi się, że chyba, że ​​rzeczywiście miałeś spację, którą musiałeś usunąć, trimwspo prostu wykonam mnóstwo niepotrzebnej pracy z wyrażeniami regularnymi, aby zwrócić ten sam wynik.
MrFlick
as.numeric (poziomy (f)) [f] może być nieco mylące i trudne do zapamiętania dla początkujących. trimws nie szkodzi.
Jerry T