Here's a suggested generalized workflow to deal with typos. It's pretty simple, based on number of characters off, so it won't manage data sets with subtly different categories. (For instance if you have real values "Food" and "Foot", those are only 1 character apart, so this wouldn't distinguish between those real values and a wrong value of "Fooz.")
Here, I first count the times each category value appears. I will assume here that correct values appear more than wrong values.
library(dplyr)
df_counts <- df %>%
count(category)
Now I look for pairs of categories where the values are unequal, but "not far" (here I arbitrarily used 5 character replacements as the max), and noted the more frequent one:
replacements <- fuzzyjoin::stringdist_left_join(df_counts, df_counts, by = "category",
max_dist = 5, distance_col = "dist") %>%
filter(n.x > n.y) %>%
select(category = category.y, category_new = category.x)
Finally, we can replace the typos with their more frequent correct (I assume) version:
df %>%
left_join(replacements) %>%
mutate(category = coalesce(category_new, category))
In my example data, it replaces "Driedd Fruits and Veg" with "Dried Fruits & Veg".
Joining, by = "category"
category X2021 X2020 X2023 category_new
1 Grain 890 900 978 <NA>
2 Dried Fruits & Veg 45 55 58 Dried Fruits & Veg
3 Dried Fruits & Veg 66 74 88 <NA>
4 Dried Fruits & Veg 21 22 23 <NA>
Depending on your data, it might make sense to run a unifying step (like replacing "&" with "and") first on your data before any of these steps, to bring the typo categories closer to their correct counterparts, so that you can use a more picky join distance to avoid false matches.
My fake data for demonstration:
df <- data.frame(
stringsAsFactors = FALSE,
category = c("Grain",
"Driedd Fruits and Veg","Dried Fruits & Veg", "Dried Fruits & Veg"),
"2021" = c(890L, 45L, 66L, 21L),
"2020" = c(900L, 55L, 74L, 22L),
"2023" = c(978L, 58L, 88L, 23L)
)