Reproducible Character Columns in R: Managing stringsAsFactors
How R converts text to factors in data.frame(), why the default changed in R 4.0.0, and how to force consistent behavior across versions.
11 Apr 2026, 07:17 UTC

Silent factor conversion breaks joins and comparisons
When you pass text to data.frame(), R may convert character vectors into factors. A factor stores text as integer codes plus a set of level labels. Because levels are often sorted alphabetically, two identical-looking strings can compare unequal, joins can fail, and code that worked on one R version can behave differently on another.
Useful takeaway
Set stringsAsFactors = FALSE explicitly in data.frame() and read.csv() calls, or set it once for the session with options(stringsAsFactors = FALSE). Since R 4.0.0 this is the built-in default, but explicit control makes scripts portable to R 3.x and reproducible across environments.
How the conversion works
A character vector holds plain text. A factor is a categorical vector: each unique value becomes a level, and the column stores integers pointing to those levels. By default, factor levels are assigned in alphabetical order, not in the order the values first appear.
The default changed in R 4.0.0
Before R 4.0.0, data.frame() converted character columns to factors by default. Starting with R 4.0.0, the default is stringsAsFactors = FALSE, so text stays as character unless you ask for factors. read.csv() and read.table() follow the same session default.
Worked configuration
Run the following in the R console or save it as an R script and run with Rscript check.R. No special permissions are required.
# 1. Confirm the active default for this session
getOption('stringsAsFactors')
# 2. Build a data frame with explicit control
df <- data.frame(
name = c('alpha', 'beta', 'gamma'),
code = c('A', 'B', 'C'),
stringsAsFactors = FALSE
)
# 3. Inspect column types
sapply(df, class)
Check that sapply(df, class) reports character for the text columns. If it reports factor, either the call did not take effect or the session is running an R version older than 4.0.0 with a different default.
Session-wide default with rollback
Setting options() changes the current R session, so save the previous value and restore it when you are done. This matters in shared interactive sessions where other code may rely on the default.
# Save current state, set explicit default, then restore
old <- options(stringsAsFactors = FALSE)
# Your analysis code here
options(old) # rollback to previous session setting
To make the setting persistent, add options(stringsAsFactors = FALSE) to your .Rprofile or source it at the top of each script. Verify it took effect by running getOption('stringsAsFactors') after sourcing.
read.csv follows the same rule
df <- read.csv('survey.csv', stringsAsFactors = FALSE)
Without the explicit argument, read.csv() uses whatever stringsAsFactors default is active in the current R session.
Preserve raw text in formulas and matrices
Use I() to inhibit conversion inside model formulas, or as.character() when you need plain text from a column that might be a factor.
# Keep name as character inside a model matrix
model.matrix(~ I(name), data = df)
# Convert a possibly-factor column back to plain text
as.character(df$name)
Limits and common mistakes
as.character()on a factor returns the level labels, not the underlying integer codes. Useas.integer()if you need the codes.options()affects only the current session. A new R process starts fresh unless you set the option in.Rprofileor in every script.- A global session setting can change behavior for packages or scripts running in the same R process. Prefer explicit arguments in function calls for reusable code.
- Factor levels are sorted alphabetically by default, so categories such as
low,medium,highmay be ordered unexpectedly.
Verify the result
- Run
R --versionorR.version.stringin the console to confirm the R version. - Run
getOption('stringsAsFactors')to see the active default. - Create
data.frame(x = c('a', 'b'), stringsAsFactors = FALSE)and runclass(df$x). It should returncharacter.
If step 3 returns factor, check the R version and whether an earlier options() call overrode the default.