ifelse makes factor 'forget' its levels order

Viewed 66

I have a data frame with two factors, like this one:

data <- data.frame(
  x = factor(rep(letters[1:3], 2)),
  y = factor(rep(c('z','x','y'), each=2), c('z','x','y'))
)

 data
  x y
1 a z
2 b z
3 c x
4 a x
5 b y
6 c y

I want to turn all the ys for which x is a into NAs. So I try:

factor(ifelse(data$x=='a', NA, as.character(data$y)))
<NA> z    x    <NA> y    y   
Levels: x y z

to get different levels order than in original data, which was:

data$y
z z x x y y
Levels: z x y

Can you suggest any way to keep original ordering, other than brute force like this:

factor(ifelse(data$x=='a', NA, as.character(data$y)), c('z','x','y'))
<NA> z    x    <NA> y    y   
Levels: z x y
3 Answers

You could also use [] to preserve the factor attributes:

data$y[] <- ifelse(data$x=='a', NA, as.character(data$y)) 
str(data$y)
# Factor w/ 3 levels "z","x","y": NA 1 2 NA 3 3

Your method looks well. If you don't want to set new levels manually, you can take levels of data$y as reference.

factor(ifelse(data$x == 'a', NA, as.character(data$y)), levels(data$y))

# [1] <NA> z    x    <NA> y    y   
# Levels: z x y

You can also use replace(), which doesn't reset levels.

replace(data$y, data$x == 'a', NA)

# [1] <NA> z    x    <NA> y    y   
# Levels: z x y

Based on Roland's comment, which is excellent solution, I came with tidyverse solution:

library(tidyverse)
library(magrittr)

data %>% 
  mutate(y = y %>% inset(x=='a', value=NA)) %>% 
  pull(y)

<NA> z    x    <NA> y    y   
Levels: z x y 

Maybe it would be useful for someone :)

Another option, thanks to Darren Tsai:

data %>% 
  mutate(y = y %>% replace(x=='a', NA)) %>% 
  pull(y)

<NA> z    x    <NA> y    y   
Levels: z x y 
Related