When I convert a factor to a numeric or integer, I get the underlying level codes, not the values as numbers.
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
I have to resort to paste
to get the real values:
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
Is there a better way to convert a factor to numeric?
It is possible only in the case when the factor labels match the original values. I will explain it with an example.
Assume the data is vector x
:
x <- c(20, 10, 30, 20, 10, 40, 10, 40)
Now I will create a factor with four labels:
f <- factor(x, levels = c(10, 20, 30, 40), labels = c("A", "B", "C", "D"))
1) x
is with type double, f
is with type integer. This is the first unavoidable loss of information. Factors are always stored as integers.
> typeof(x)
[1] "double"
> typeof(f)
[1] "integer"
2) It is not possible to revert back to the original values (10, 20, 30, 40) having only f
available. We can see that f
holds only integer values 1, 2, 3, 4 and two attributes - the list of labels ("A", "B", "C", "D") and the class attribute "factor". Nothing more.
> 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"
To revert back to the original values we have to know the values of levels used in creating the factor. In this case c(10, 20, 30, 40)
. If we know the original levels (in correct order), we can revert back to the original values.
> orig_levels <- c(10, 20, 30, 40)
> x1 <- orig_levels[f]
> all.equal(x, x1)
[1] TRUE
And this will work only in case when labels have been defined for all possible values in the original data.
So if you will need the original values, you have to keep them. Otherwise there is a high chance it will not be possible to get back to them only from a factor.
You can use hablar::convert
if you have a data frame. The syntax is easy:
Sample df
library(hablar)
library(dplyr)
df <- dplyr::tibble(a = as.factor(c("7", "3")),
b = as.factor(c("1.5", "6.3")))
Solution
df %>%
convert(num(a, b))
gives you:
# A tibble: 2 x 2
a b
<dbl> <dbl>
1 7. 1.50
2 3. 6.30
Or if you want one column to be integer and one numeric:
df %>%
convert(int(a),
num(b))
results in:
# A tibble: 2 x 2
a b
<int> <dbl>
1 7 1.50
2 3 6.30
type.convert(f)
on a factor whose levels are completely numeric is another base option.
Performance-wise it's about equivalent to as.numeric(as.character(f))
but not nearly as quick as as.numeric(levels(f))[f]
.
identical(type.convert(f), as.numeric(levels(f))[f])
[1] TRUE
That said, if the reason the vector was created as a factor in the first instance has not been addressed (i.e. it likely contained some characters that could not be coerced to numeric) then this approach won't work and it will return a factor.
levels(f)[1] <- "some character level"
identical(type.convert(f), as.numeric(levels(f))[f])
[1] FALSE
late to the game, accidently, I found trimws()
can convert factor(3:5)
to c("3","4","5")
. Then you can call as.numeric()
. That is:
as.numeric(trimws(x_factor_var))
The most easiest way would be to use unfactor
function from package varhandle which can accept a factor vector or even a dataframe:
unfactor(your_factor_variable)
This example can be a quick start:
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"
You can also use it on a dataframe. For example the iris
dataset:
sapply(iris, class)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species "numeric" "numeric" "numeric" "numeric" "factor"
# load the package
library("varhandle")
# pass the iris to unfactor
tmp_iris <- unfactor(iris)
# check the classes of the columns
sapply(tmp_iris, class)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species "numeric" "numeric" "numeric" "numeric" "character"
# check if the last column is correctly converted
tmp_iris$Species
[1] "setosa" "setosa" "setosa" "setosa" "setosa" [6] "setosa" "setosa" "setosa" "setosa" "setosa" [11] "setosa" "setosa" "setosa" "setosa" "setosa" [16] "setosa" "setosa" "setosa" "setosa" "setosa" [21] "setosa" "setosa" "setosa" "setosa" "setosa" [26] "setosa" "setosa" "setosa" "setosa" "setosa" [31] "setosa" "setosa" "setosa" "setosa" "setosa" [36] "setosa" "setosa" "setosa" "setosa" "setosa" [41] "setosa" "setosa" "setosa" "setosa" "setosa" [46] "setosa" "setosa" "setosa" "setosa" "setosa" [51] "versicolor" "versicolor" "versicolor" "versicolor" "versicolor" [56] "versicolor" "versicolor" "versicolor" "versicolor" "versicolor" [61] "versicolor" "versicolor" "versicolor" "versicolor" "versicolor" [66] "versicolor" "versicolor" "versicolor" "versicolor" "versicolor" [71] "versicolor" "versicolor" "versicolor" "versicolor" "versicolor" [76] "versicolor" "versicolor" "versicolor" "versicolor" "versicolor" [81] "versicolor" "versicolor" "versicolor" "versicolor" "versicolor" [86] "versicolor" "versicolor" "versicolor" "versicolor" "versicolor" [91] "versicolor" "versicolor" "versicolor" "versicolor" "versicolor" [96] "versicolor" "versicolor" "versicolor" "versicolor" "versicolor" [101] "virginica" "virginica" "virginica" "virginica" "virginica" [106] "virginica" "virginica" "virginica" "virginica" "virginica" [111] "virginica" "virginica" "virginica" "virginica" "virginica" [116] "virginica" "virginica" "virginica" "virginica" "virginica" [121] "virginica" "virginica" "virginica" "virginica" "virginica" [126] "virginica" "virginica" "virginica" "virginica" "virginica" [131] "virginica" "virginica" "virginica" "virginica" "virginica" [136] "virginica" "virginica" "virginica" "virginica" "virginica" [141] "virginica" "virginica" "virginica" "virginica" "virginica" [146] "virginica" "virginica" "virginica" "virginica" "virginica"
Looks like the solution as.numeric(levels(f))[f] no longer work with R 4.0.
Alternative solution:
factor2number <- function(x){
data.frame(levels(x), 1:length(levels(x)), row.names = 1)[x, 1]
}
factor2number(yourFactor)
Note: this particular answer is not for converting numeric-valued factors to numerics, it is for converting categorical factors to their corresponding level numbers.
Every answer in this post failed to generate results for me , NAs were getting generated.
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
What worked for me is this -
as.integer(y2)
# [1] 1 2 3 4 1
From the many answers I could read, the only given way was to expand the number of variables according to the number of factors. If you have a variable "pet" with levels "dog" and "cat", you would end up with pet_dog and pet_cat.
In my case I wanted to stay with the same number of variables, by just translating the factor variable to a numeric one, in a way that can applied to many variables with many levels, so that cat=1 and dog=0 for instance.
Please find the corresponding solution below:
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)
}
)
R has a number of (undocumented) convenience functions for converting factors:
as.character.factor
as.data.frame.factor
as.Date.factor
as.list.factor
as.vector.factor
But annoyingly, there is nothing to handle the factor -> numeric conversion. As an extension of Joshua Ulrich's answer, I would suggest to overcome this omission with the definition of your own idiomatic function:
as.numeric.factor <- function(x) {as.numeric(levels(x))[x]}
that you can store at the beginning of your script, or even better in your .Rprofile
file.
Source: Stackoverflow.com