Skip to Content
Course content

51: forcats for Factor Manipulation

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you've spent any time plotting data in R, you've likely hit the "alphabetical wall." You create a beautiful bar chart of customer satisfaction levels—"Low", "Medium", "High"—and R helpfully decides to display them as "High", "Low", "Medium" because that's how the alphabet works. It's frustrating, and for a long time, the solution felt like a chore.

The Alphabetical Nightmare

The naive way to handle this is to let R treat your categories as characters and then try to force the order at the last second. You might try to manually rename the levels using base R, like this:

# The tedious way
data$satisfaction <- factor(data$satisfaction, 
                            levels = c("Low", "Medium", "High"))

This works fine for three categories. But imagine you're dealing with a survey with fifteen different priority levels or a list of twenty different product regions. Writing out a character vector of fifteen strings in the exact order you want is not just boring; it's a liability. One typo in a string, and R will silently turn that level into an NA. I've spent way too many hours debugging a "missing data" problem that was actually just a lowercase "l" where an uppercase "L" should have been in a levels() call.

The Fragility of Manual Leveling

Another common approach I see is trying to use match() or creating a separate lookup table to map names to integers. While this is more "programmatic," it adds a layer of complexity to your script that your future self—or your teammates—will hate. You end up with a mapping variable floating around your environment, and if the raw data changes (say, "Medium" becomes "Average"), your mapping breaks and your plots shift without warning.

This is where forcats comes in. It treats factors as first-class citizens. Instead of you telling R exactly what the levels are, you tell R how the levels should behave.

Ordering by Value, Not by Name

In the real world, we rarely want things alphabetical; we usually want them ordered by some other metric. If you want to order your categories by the mean of another variable—say, ordering "Regions" by their "Total Sales"—don't manually sort the sales, find the names, and pass them to factor(). Use fct_reorder().

library(forcats)

# Order regions by the value of sales
data <- data %>% 
  mutate(region = fct_reorder(region, sales))

I love this because it's dynamic. If next month's data shows that the "North" region jumped from last to first, your code doesn't change, and your plot updates automatically. You've decoupled the ordering logic from the data values themselves.

Cleaning Up the Long Tail

Finally, there's the problem of the "Long Tail." You might have a column for "City," and while you have 500 cities, 90% of your revenue comes from the top five. A bar chart with 500 bars is useless. The naive approach is to write a massive case_when()` statement to manually group the small cities into an "Other" category.

That's a waste of your time. fct_lump() does this in one line. It looks at the frequency of the levels and collapses everything below a certain threshold into a single category.

# Keep the top 5 cities, lump everything else into "Other"
data <- data %>% 
  mutate(city = fct_lump(city, n = 5))

By using forcats, you stop treating factors as a weird R quirk you have to manage with manual lists and start treating them as a data type you can manipulate with intent. It turns a tedious manual process into a reproducible pipeline.




📋 Practical Task

Exercise: Organizing Product Category Performance

You have a dataset of product sales across various categories. Some categories are very rare, and the current alphabetical ordering makes the performance report hard to read.

Your Goal: Use the forcats package to clean up the category factor in the provided data frame. You must:

  • Collapse all categories that appear fewer than 3 times into a single category called "Other".
  • Reorder the remaining categories so they are sorted by the average_spend in descending order (highest spend first).
# Initial Data
library(tidyverse)
library(forcats)

product_data <- data.frame(
  category = c("Electronics", "Electronics", "Electronics", "Clothing", "Clothing", 
               "Home", "Home", "Books", "Books", "Beauty", "Toys", "Garden", "Sports"),
  average_spend = c(120, 150, 130, 40, 50, 80, 90, 20, 25, 60, 30, 45, 70)
)

# Your code here:
# 1. Lump the categories
# 2. Reorder the categories by average_spend
Rating
0 0

There are no comments for now.

to be the first to leave a comment.