What a Factor Actually Is
A factor is R's answer to categorical data - values that come from a fixed menu of possibilities: treatment groups, survey answers, T-shirt sizes. It looks like a character vector when printed, but it isn't one. Internally, a factor is a vector of integer codes plus a lookup table of labels called levels:
The printout shows the labels, then a Levels: line listing the menu. as.integer() exposes the machinery: the codes come back as 3 1 3 2, because each value is really an index into the levels table - and the levels sort alphabetically by default (large, medium, small), not in the order the data arrived. So small is code 3 and large is code 1, which is nobody's intuition. Remember this alphabetical default; it drives two of the traps below.
Why bother with this two-layer design instead of plain strings? Because statistics needs it. A model can't multiply "small" by a coefficient - but it can dummy-code three known levels into columns of 0s and 1s. Functions like lm(), glm(), table(), and the machinery behind ANOVA all key off factors to know a variable is categorical, what the complete set of categories is (including ones absent from the data), and which category is the baseline. Plain character vectors carry none of that.
Creating Factors: levels, labels and table()
By default factor() takes the distinct values it finds and sorts them alphabetically into levels. You'll often want control over both the set and the order - pass levels =:
Now the levels run in their natural size order, and table() - the one-line frequency count you'll use constantly with factors - reports counts in that order too. nlevels() counts categories.
Two more things levels = buys you. Values in the data that aren't in your levels list become NA (good: typos surface instead of becoming their own category). And levels with zero occurrences still exist, so a summary of survey responses shows "strongly disagree: 0" instead of pretending the option never existed.
labels = renames the levels at creation time, which is handy when the raw data uses codes:
And as.factor(x) is the quick converter for an existing vector when the defaults are fine.
Ordered Factors for Ordinal Data
Plain factors treat categories as unordered - "red" is not less than "blue". But some categorical scales have a genuine ranking: low/medium/high, disagree/neutral/agree. Declare that with ordered = TRUE:
The printout now shows Levels: low < medium < high, and comparison operators work: you can ask whether one rating exceeds another, or filter to everything at or above "medium" - both errors (well, warnings and NAs) on an unordered factor. Ordered factors also change how models code the variable (polynomial contrasts instead of dummy variables), which is usually what you want for ordinal predictors.
Use ordered = TRUE only when the ranking is real. Encoding ordinary groups as ordered changes model output in ways that are easy to misread.
The Reference Level and relevel()
The first level of a factor is special: model functions treat it as the reference category, the baseline that every other level's coefficient is measured against. Since default level order is alphabetical, your baseline is chosen by the alphabet unless you intervene - and "control" losing to "aspirin" alphabetically is not a scientific decision.
relevel() promotes a level to first place:
Before: control happens to be first only by alphabetical luck. After relevel(..., ref = "control") it's first on purpose. In a linear regression with this predictor, the treatment coefficient now answers "how does treatment differ from control?" - the question you actually asked. Whenever a model's categorical coefficients look confusing, check the reference level first.
(For full reordering - not just the first slot - pass a complete levels = vector to factor() again.)
The Classic Trap: Converting a Factor to Numeric
Sometimes numbers arrive as factors - typically a CSV column that contained a stray non-numeric value. Converting back looks obvious and goes memorably wrong:
as.numeric(f) returns 2 1 3. Not 20, 10, 30 - the level codes. The levels sort alphabetically to "10", "20", "30", so "20" is level 2 and converts to... 2. No error, no warning, plausible-looking small integers quietly replacing your data. Analyses have been retracted over this one.
The correct route goes through character: as.numeric(as.character(f)) first recovers the labels as text, then parses the text as numbers - 20 10 30. Burn this idiom in: factor to numeric always goes via as.character().
droplevels() and the stringsAsFactors Story
Subsetting a factor keeps the full level set, even for categories that no longer appear:
After filtering out large, table() still reports it - with a count of 0. Sometimes that's exactly right (you want the empty category visible). When it isn't - zero-count groups clutter plots and can break stratified analyses - droplevels() discards levels with no observations.
A historical note you'll need when reading older code or Stack Overflow answers: before R 4.0 (2020), data.frame() and read.csv() converted every character column to a factor automatically - stringsAsFactors = TRUE was the default. A decade of tutorials is littered with workarounds for factors nobody asked for. Since R 4.0 the default is FALSE: strings stay strings, and you create factors deliberately, where a variable in your data frame is genuinely categorical. That's the right habit - explicit factors, on purpose, with levels you chose.
What You Take Away
- A factor = integer codes + level labels; it's what tells statistical functions a variable is categorical.
- Control the category set and order with
levels =, rename withlabels =, count withtable(). ordered = TRUEenables comparisons for genuinely ordinal scales.- The first level is the model baseline - set it deliberately with
relevel(f, ref = ...). - Never
as.numeric(f)directly - alwaysas.numeric(as.character(f)). droplevels()clears unused levels after subsetting; since R 4.0, strings stay strings unless you make factors yourself.
Next up: data frames - where factors, numbers, and text live together as columns of one table.
Frequently Asked Questions
What is a factor in R?
A factor is R's type for categorical data - values drawn from a fixed set of possibilities called levels. Internally it's a vector of integer codes plus a table of level labels, which is what lets statistical functions treat categories correctly (counting them, dummy-coding them in models) instead of treating them as free-form text.
How do you convert a factor to numeric in R?
Go through character: as.numeric(as.character(f)). Calling as.numeric(f) directly returns the internal level codes (1, 2, 3, ...), not the values the labels show - so a factor displaying "20" can come back as 2. This is one of the most common silent bugs in R.
What does relevel() do in R?
It moves a chosen level to the first position: relevel(group, ref = "control"). The first level is the reference (baseline) category that model functions like lm() and glm() compare every other level against, so choosing it deliberately makes regression coefficients mean what you intend.
Why does my factor still show levels I removed?
Subsetting a factor keeps the full level set even when some levels no longer occur, so table() shows zero-count categories and models still reserve space for them. Run droplevels() on the subset to discard the unused levels.