Factor in R

Introduction to R Data Structures
Create factors in R to represent categorical data

Factors in R are used to represent categorical data. You can think about them as integer vectors in which each integer has an associated label. Note that using factors with labels is preferred than integer vectors, as labels are self-descriptive. In this lesson you will learn all about how to create a factor in R.

Quick answer

Use factor() to convert a vector to factor and the levels argument to set the order of the categories. The levels function returns the categories and nlevels the number of them.

x <- c("low", "high", "medium", "low", "high")

f <- factor(x, levels = c("low", "medium", "high"))
f

levels(f)
nlevels(f)
[1] low    high   medium low    high
Levels: low medium high

[1] "low"    "medium" "high"

[1] 3

To convert a data frame column, assign the result to the column, for instance df$x <- factor(df$x). To go back, use as.character(f) or, if the levels are numbers, as.numeric(as.character(f)).

What is a factor in R programming?

A factor in R is a data structure used to represent a vector as categorical data. Therefore, the factor object takes a bounded number of different values called levels. Factors are very useful when working with character columns of data frames, for creating barplots and creating statistical summaries for categorical variables.

The factor function

The factor function allows you to create factors in R. In the following block we show the arguments of the function with a summarized description.

factor(x = character(),         # Input vector data
       levels,                  # Input of unique x values (optional)
       labels = levels,         # Output labels for the levels (optional)
       exclude = NA,            # Values to be excluded from levels
       ordered = is.ordered(x), # Whether the input levels are ordered as given or not
       nmax = NA)               # Maximum number of levels

You can get a more detailed description of the function and its arguments calling ?factor or help(factor).

Convert character to factor in R

Now we will review an example where our input is a character vector. Suppose, for instance, that you have a vector containing the week days when some event happened. Thus, you can convert your character vector to factor with the factor function.

days <- c("Friday", "Tuesday", "Thursday", "Monday", "Wednesday", "Monday",
          "Wednesday", "Monday", "Monday", "Wednesday", "Sunday", "Saturday")

# Levels in alphabetical order
my_factor <- factor(days)
my_factor
Friday  Tuesday  Thursday  Monday  Wednesday  Monday
Wednesday  Monday  Monday  Wednesday  Sunday  Saturday 
Levels: Friday Monday Saturday Sunday Thursday Tuesday Wednesday

By default, converting a character vector to factor will order the levels alphabetically.

If you want to preserve the order of the levels as appear on the input data, specify in the levels argument the unique values of the data as follows:

factor(days, levels = unique(days))
Friday  Tuesday  Thursday  Monday  Wednesday  Monday
Wednesday  Monday  Monday  Wednesday  Sunday  Saturday 
Levels: Friday Tuesday Thursday Monday Wednesday Sunday Saturday

Note that you can return and convert the factor levels to character with the levels function.

levels(my_factor)
"Friday"  "Monday"  "Saturday"  "Sunday"  "Thursday"  "Tuesday"  "Wednesday"

Convert numeric to factor in R

Suppose you have registered the birth city of six individuals with the following codification:

  • 1: Dublin.
  • 2: London,
  • 3: Sofia.
  • 4: Pontevedra.

Hence, you will have something like the following data stored in a numeric vector:

city <- c(3, 2, 1, 4, 3, 2)

Now, you can call the factor to convert the data into factor and get it categorized for further analysis.

my_factor <- factor(city)
my_factor

The output will have the following structure:

3 2 1 4 3 2
Levels: 1 2 3 4

Change factor labels of the levels

If the input vector is numeric, as in the previous section, the corresponding label (the city) is not reflected. In order to solve this issue, you can store the data in a factor object using the factor function and indicate the corresponding labels of the levels in the labels argument, in order to rename the factor levels.

# Setting the labels in the corresponding order
factor_cities <- factor(city, labels = c("Dublin", "London", "Sofia", "Pontevedra"))

# Print the result
factor_cities
Sofia London Dublin Pontevedra Sofia London    
Levels: Dublin London Sofia Pontevedra   # <- Dublin: 1, London: 2, Sofia: 3, Pontevedra: 4

In the previous code block you can see the final output. As you can observe, now the data is categorized using the cities as labels.

Difference between levels and labels in R

It is common to get confused between labels and levels arguments of the R factor function. Consider the following vector with a unique group and create a factor from it with default arguments:

gender <- c("female", "female", "female", "female")
factor(gender)
female  female  female  female
Levels: female

On the one hand, the labels argument allows you to modify the factor levels names. Hence, the labels argument it is related to output. Note that the length of the vector passed to the labels argument must be of the same length of the number of unique groups of the input vector.

factor(gender, labels = c("f"))
f f f f
Levels: f

On the other hand, the levels argument is related to input. This argument allows you to specify how the levels are coded. Moreover, this argument allows you to add new levels to the factor:

factor(gender, levels = c("male", "female"))
female female female female
Levels: male female

Note you have to specify at least the same names of the input vector groups, or the output won’t be as expected:

factor(gender, levels = c("male", "f"))
<NA> <NA> <NA> <NA>
Levels: male f

Relevel and reorder factor levels

You may be wondering how to change the levels order (which can be important, for instance, in some graphical representations). The factor levels order can be changed in various ways, described in the following subsections.

Custom order of factor levels

In case you want create a custom order for the levels you will have to create a vector with the desired order and pass it to the levels argument.

# Create a vector with the desired order
order <- c("London", "Sofia", "Dublin", "Pontevedra")

# Indicate the order in the 'levels' argument
factor_cities <- factor(factor_cities, levels = order)
factor_cities
Sofia London Dublin Pontevedra Sofia London
Levels: London Sofia Dublin Pontevedra                  # <- Ordered as specified

In addition, you can order the levels of the factor alphabetically making use of the sort function:

# Alphabetical order
factor(factor_cities, levels = sort(levels(factor_cities)))
Sofia      London     Dublin     Pontevedra Sofia      London
Levels: Dublin London Pontevedra Sofia                  # <- Alphabetical order

Reorder factor levels

The reorder function is designed to order the levels of a factor based on a statistical measure of another variable. To demonstrate, consider a data frame where each row represents an individual, the ‘city’ column represents the city where they were born and the ‘salary’ column represents their current annual wage in thousands of dollars.

set.seed(1)
df <- data.frame(city = factor_cities, salary = sample(20:50, 6))
df
        city salary
1      Sofia     44
2     London     23
3     Dublin     26
4 Pontevedra     20
5      Sofia     21
6     London     42

You can reorder the factor based, for example, on the mean wage of the individuals using the reorder function as follows:

reorder(df$city, df$salary, mean)
Sofia      London     Dublin     Pontevedra Sofia      London
attr(,"scores")
    London      Sofia     Dublin Pontevedra
      32.5       32.5       26.0       20.0
Levels: Pontevedra Dublin London Sofia    # <- Ordered from lower to higher mean salary

Reverse order of levels

Recall that you can use the levels function to obtain the levels of a factor. At this point, the levels of the factor are the following:

levels(factor_cities)
"London"  "Sofia"  "Dublin"  "Pontevedra"

With this in mind, you can reverse the order of levels of a factor with the rev function. Pass the result to the levels argument, not to labels, or you will rename the values instead of reordering them:

factor(factor_cities, levels = rev(levels(factor_cities)))
Sofia      London     Dublin     Pontevedra Sofia      London
Levels: Pontevedra Dublin Sofia London     # <- Reversed order

Relevel function

Moreover, if you want to move just one level and put it first you can use the relevel function. For example, if you want the level ‘London’ appearing first and maintain the order of the others you can use:

# Setting the level 'London' first
factor_cities <- relevel(factor_cities, "London")
factor_cities
Sofia      London     Dublin     Pontevedra Sofia      London
Levels: London Sofia Dublin Pontevedra

In the following sections we will review how to convert factors to other data types in the more efficient way.

Ordered factors and comparisons

By default factors are nominal: the order of the levels is only used to display them, and comparisons such as > or < are not allowed. If the categories have a natural order (low, medium, high; small, medium, large
) set ordered = TRUE. The levels will be printed with < between them:

satisfaction <- c("low", "high", "medium", "low", "high")

sat <- factor(satisfaction, levels = c("low", "medium", "high"), ordered = TRUE)
sat
[1] low    high   medium low    high
Levels: low < medium < high

Now you can compare the values, filter them or compute the minimum and the maximum according to the order of the levels:

sat > "low"
sat[sat >= "medium"]
max(sat)
[1] FALSE  TRUE  TRUE FALSE  TRUE

[1] high   medium high
Levels: low < medium < high

[1] high
Levels: low < medium < high

If you try the same with a factor that is not ordered you will get NA and a warning:

f <- factor(satisfaction)
f > "low"
[1] NA NA NA NA NA
Warning message:
In Ops.factor(f, "low") : '>' not meaningful for factors

You can check if a factor is ordered with is.ordered. The ordered function is a shortcut for factor(..., ordered = TRUE).

Drop unused levels with droplevels

When you subset a factor or a data frame, the levels that no longer appear are kept. This is a common source of empty categories in tables and plots:

sizes <- factor(c("S", "M", "L", "XL", "M", "S"), levels = c("S", "M", "L", "XL"))

small <- sizes[sizes %in% c("S", "M")]
small
table(small)
[1] S M M S
Levels: S M L XL

small
 S  M  L XL
 2  2  0  0

The droplevels function removes the levels without observations:

droplevels(small)
[1] S M M S
Levels: S M

It also works with data frames, in which case it drops the unused levels of all the factor columns:

df <- data.frame(size = sizes, price = c(10, 12, 15, 18, 12, 10))

df2 <- subset(df, size != "XL")
levels(df2$size)

df2 <- droplevels(df2)
levels(df2$size)
[1] "S"  "M"  "L"  "XL"

[1] "S" "M" "L"

Work with factors with forcats

The forcats package (part of the tidyverse) provides functions starting with fct_ that make the most common operations with factors easier. All of them return a new factor and keep the values unchanged, so they are safe to use inside mutate. The following table shows the base R equivalents:

forcats What it does Base R
fct_relevel Move one or more levels to the front (or to any position) relevel, factor(x, levels = 
)
fct_reorder Order the levels by a summary of other variable reorder
fct_infreq Order the levels by frequency factor(x, levels = names(sort(table(x), decreasing = TRUE)))
fct_rev Reverse the order of the levels factor(x, levels = rev(levels(x)))
fct_recode Rename levels levels(x)[levels(x) == “old”] <- “new”
fct_collapse Merge several levels into one levels(x) <- list(new = c(“a”, “b”), 
)
fct_lump_n Keep the most frequent levels and group the rest as “Other” -
fct_drop Drop unused levels droplevels

Consider the following factor:

# install.packages("forcats")
library(forcats)

city <- factor(c("Sofia", "London", "Dublin", "Pontevedra", "Sofia", "London", "London"))
salary <- c(28, 31, 36, 45, 25, 43, 40)

The functions to change the order of the levels are the following:

fct_relevel(city, "London")                # 'London' first
fct_infreq(city)                           # Most frequent first
fct_rev(city)                              # Reversed order
fct_reorder(city, salary, .fun = mean)     # By mean salary
[1] Sofia      London     Dublin     Pontevedra Sofia      London     London
Levels: London Dublin Pontevedra Sofia

[1] Sofia      London     Dublin     Pontevedra Sofia      London     London
Levels: London Sofia Dublin Pontevedra

[1] Sofia      London     Dublin     Pontevedra Sofia      London     London
Levels: Sofia Pontevedra London Dublin

[1] Sofia      London     Dublin     Pontevedra Sofia      London     London
Levels: Sofia Dublin London Pontevedra

The functions to rename or group levels are the following. In fct_recode the new name goes on the left and the old one on the right:

fct_recode(city, "Dub" = "Dublin", "Lon" = "London")
fct_collapse(city, Ireland = "Dublin", UK = "London", Other = c("Sofia", "Pontevedra"))
fct_lump_n(city, n = 1)
[1] Sofia      Lon        Dub        Pontevedra Sofia      Lon        Lon
Levels: Dub Lon Pontevedra Sofia

[1] Other   UK      Ireland Other   Other   UK      UK
Levels: Ireland UK Other

[1] Other  London Other  Other  Other  London London
Levels: London Other

fct_infreq and fct_reorder are very useful to sort the bars of a bar plot by frequency or by value.

Convert factor in R to numeric

If you have a factor in R that you want to convert to numeric, the most efficient way is illustrated in the following block code, using the as.numeric and levels functions for indexing the levels by the index of the corresponding factor.

my_data <- c(0, 2, 0, 5, 1, 9, 9, 4)
my_factor <- factor(my_data)

as.numeric(levels(my_factor))[my_factor]
0 2 0 5 1 9 9 4

A simpler alternative, easier to remember, is to convert the factor to character first and then to numeric:

as.numeric(as.character(my_factor))
[1] 0 2 0 5 1 9 9 4

Note what happens if you use as.numeric directly. You get the internal integer codes of the levels (1 for the first level, 2 for the second
) and not the original numbers, and R doesn’t show any warning:

as.numeric(my_factor)
[1] 1 3 1 5 2 6 6 4

If you want to convert the factor to the original numbers never use as.numeric(my_factor), as it returns the codes of the levels, not the values. Use as.numeric(as.character(my_factor)) or as.numeric(levels(my_factor))[my_factor] instead.

Convert factor to string

You may need to convert a factor to string. For that purpose, you can make use of the as.character function.

my_factor_2 <- factor(c("June", "July", "January", "June"))

as.character(my_factor_2)
"June"  "July"  "January"  "June"

Note that if you use the levels function, the output will return a character vector with the unique strings ordered alphabetically, as we show in one of the previous sections.

levels(my_factor_2)
"January"  "July"  "June" 

Convert factor to date

Also, if you need to change your factor object to date, you can use the as.Date function, specifying in the format argument the date format you are working with.

my_date_factor <- factor(c("03/21/2020",
                           "03/22/2020",
                           "03/23/2020"))

as.Date(my_date_factor, format = "%m/%d/%Y")
"2020-03-21" "2020-03-22" "2020-03-23"

Common warnings with factors

invalid factor level, NA generated

This warning appears when you assign a value that is not one of the levels of the factor. The value is replaced by NA:

colors <- factor(c("red", "green", "red"))
colors[2] <- "blue"
colors
Warning message:
In `[<-.factor`(`*tmp*`, 2, value = "blue") :
  invalid factor level, NA generated

[1] red  <NA> red
Levels: green red

To fix it, add the new level first and then assign the value:

colors <- factor(c("red", "green", "red"))

levels(colors) <- c(levels(colors), "blue")
colors[2] <- "blue"
colors
[1] red  blue red
Levels: green red blue

Another option is to convert the factor to character, modify it and convert it back to factor. The same warning appears when you add rows with new categories to a data frame with factor columns, for instance with rbind. In that case, convert the column to character before combining the data.

‘>’ not meaningful for factors

You will see this warning, and an NA result, if you use >, <, >= or <= on a factor that is not ordered, as shown in the ordered factors section. Create the factor with ordered = TRUE to compare the levels. Functions such as mean or sum don’t work with factors either: if the factor contains numbers, convert it to numeric first.