I was surprised to see that R will coerce factors into a number when concatenating vectors. This happens even when the levels are the same. For example:
>
Here's another way to add to a factor variable when the setup is slightly different:
facs <- factor(1:3, levels=1:9,
labels=c("i", "want", "to", "be", "a", "factor", "not", "an", "integer"))
facs
# [1] i want to be a factor not an integer
# Levels: a an be factor i integer not to want
facs[4:6] <- levels(facs)[4:6]
facs
# [1] i want to be a factor
# Levels: i want to be a factor not an integer
Use fct_c
from the forcats
package (part of the tidyverse).
> library(forcats)
> facs <- as.factor(c("i", "want", "to", "be", "a", "factor", "not", "an", "integer"))
> fct_c(facs[1:3], facs[4:5])
[1] i want to be a
Levels: a an be factor i integer not to want
fct_c
isn't fooled by concatenations of factors with discrepant numerical codings:
> x <- as.factor(c('c', 'z'))
> x
[1] c z
Levels: c z
> y <- as.factor(c('a', 'b', 'z'))
> y
[1] a b z
Levels: a b z
> c(x, y)
[1] 1 2 1 2 3
> fct_c(x, y)
[1] c z a b z
Levels: c z a b
> as.numeric(fct_c(x, y))
[1] 1 2 3 4 2
Wow, I never realized it did that. Here is a work-around:
x <- c(facs[1 : 3], facs[4 : 5])
x <- factor(x, levels=1:nlevels(facs), labels=levels(facs))
x
With the output:
[1] i want to be a
Levels: a an be factor i integer not to want
It will only work if the two vectors have the same levels as here.
An alternate workaround is to convert the factor to be a character vector, then convert back when you are finshed concatenating.
cfacs <- as.character(facs)
x <- c(cfacs[1:3], cfacs[4:5])
# Now choose between
factor(x)
# and
factor(x, levels = levels(facs))
From the R Mailing list:
unlist(list(facs[1 : 3], facs[4 : 5]))
To 'cbind' factors, do
data.frame(facs[1 : 3], facs[4 : 5])
This is a really bad R gotcha. Along those lines, here's one that just swallowed several hours of my time.
x <- factor(c("Yes","Yes","No", "No", "Yes", "No"))
y <- c("Yes", x)
> y
[1] "Yes" "2" "2" "1" "1" "2" "1"
> is.factor(y)
[1] FALSE
It appears to me the better fix is Richie's, which coerces to character.
> y <- c("Yes", as.character(x))
> y
[1] "Yes" "Yes" "Yes" "No" "No" "Yes" "No"
> y <- as.factor(y)
> y
[1] Yes Yes Yes No No Yes No
Levels: No Yes
As long as you get the levels set properly, as Richie mentions.