Type Conversion in Practice
In Lesson 4 you built grids of pure numbers. But real data rarely arrives in the type you want. Priya runs a small bakery and exports each day's sales to a spreadsheet. When she reads the file back into R, the price column, full of numbers like 3.50 and 2.75, comes back as text, and now she cannot add up a single day's takings.
This lesson is the fix: how to change a value's type on purpose, and how to repair a column that imported as the wrong type.
By the end of this lesson you will be able to:
- Diagnose the type of every column with
str()andclass() - Convert on purpose with
as.numeric(),as.character(),as.integer()andas.factor() - Spot the two classic traps: text that will not become a number, and a factor that turns into meaningless codes
Prerequisites: Lessons 1-4 of this course. You can build a vector and a data frame, and you know an atomic vector holds one type, so mixing types silently coerces the whole vector up the ladder logical < integer < double < character. The box below holds three of Priya's prices as numbers. Press a + "hi" (text) button and watch all of them turn into text, the very accident we are about to learn to undo.
Diagnose first: what type is each column?
Before you can fix a type, you have to see it. Let us bring Priya's sales into R exactly the way she does, by reading a small CSV. Each lesson runs in a fresh R session, so we write the file in-session first, then read it back (run this once; later steps reuse sales):
str() is your x-ray from Lesson 3. Read the tag after each column name: item is chr (character), sold is int (integer), but look at price, it is chr too. Those are text, even though they read like money. The reason is the rule from Lesson 2: one cell in that column, the missing latte price, was the text n/a. The moment a non-number sits in the column, R cannot keep the column numeric, so it coerces the whole column to character on import.
To check just the kinds, ask each column for its class. sapply() simply runs class() on every column and lines the answers up for you:
3.50 reads like a number) but behaves wrong (you cannot multiply it). str() and class() are how you catch it before it bites.The toggles below show the same idea on a clean frame: each column is its own type, and you can read that type under the table.
Convert on purpose: the as.* family
R gives you a small family of functions whose only job is to change a value's type. You name the type you want, and R rebuilds the values as that type. Each is as. followed by the target:
This is the deliberate mirror image of Lesson 2. There, mixing types caused R to coerce a vector silently, behind your back. Here, you are in charge: you state the target type, and the conversion happens only where you ask for it.
as.* function. Same machinery, opposite intent: one is a silent accident, the other is a named decision.The widget shows Priya's price column making exactly the move she needs, from text (look at the quotes) to real numbers she can multiply.
Which conversion do you reach for?
A colleague sends you three ages that arrived as text: c("29", "41", "35"). You want their average. Which function turns them into something mean() can work with?
as.numeric(c("29","41","35")) gives the doubles 29 41 35, so mean() returns 35. Naming the target type is the whole move.Rescue numbers stuck as text
Priya also kept this week's order counts, but they arrived as text: c("12", "8", "15", "20"). As characters you cannot add them. Fill the blank with the conversion that turns them into real numbers, so sum() can total them. The answer should be 55.
Show answer
counts <- c("12", "8", "15", "20")
sum(as.numeric(counts))
#> [1] 55When a value is not really a number
What happens to Priya's n/a? as.numeric() cannot invent a number for it, so it does the only safe thing: it returns NA (R's marker for "missing") for that cell and a warning, not an error. The conversion still succeeds for every real number:
Read that warning as a gift, not a scolding. The exact phrase, "NAs introduced by coercion", is R telling you precisely which values were not really numbers. To find which cells turned into NA, ask with is.na():
n/a, a sold out, a $ sign, or a 1,200 with a comma. Track that value down before you trust the column. The conversion is doing you a favour by flagging it.Categories become factors
Not every text column should become a number. Some columns are categories, a value drawn from a fixed, repeating set: a day of the week, a size, a product name. R has a purpose-built type for these called a factor. You make one with as.factor() (or factor() for more control):
A factor looks like text, but underneath it stores two things: a dictionary of the distinct values (its levels), and, for each element, a small integer code pointing into that dictionary. Reveal the codes with as.integer():
When categories have a natural order (small < medium < large), say so with factor(..., ordered = TRUE), and R will respect that order in comparisons and plots:
This "text in, levels and codes out" pipeline is exactly what as.factor() builds for you:
The one factor trap everyone hits
Here is the mistake that catches every R user once. Suppose a year column was read as a factor (it happens with older import settings). You try to turn it back into numbers the obvious way, and get nonsense:
1 3 2? Those are not years, they are the integer codes from the step before. as.numeric() on a factor returns what the factor actually stores (the codes), not what the labels look like. The fix is to step through text first, so R reads the labels rather than the codes:
as.numeric() directly on a factor of numbers. You will silently get the codes 1, 2, 3, ... instead of the real values, and nothing will error to warn you. Always go through as.character() first: as.numeric(as.character(f)).Why the wrong years?
A year column was read as a factor, factor(c("2019", "2021", "2020")). You run as.numeric() on it directly and get 1 3 2 instead of the years. What happened?
2019, 2020, 2021, so the codes are 1, 2, 3; your three values map to codes 1, 3, 2. Convert through as.character() first to read the labels.as.character(years) would show the real "2019" "2021" "2020". The codes appear only because you converted the factor to numbers directly.Fix the column and finish the job
Back to Priya. Her sales frame is still as it imported: price is text. Your job is the full repair, the exact workflow you will use on real data:
- Diagnose (done:
str()showedpriceischr) - Convert the column to the right type
- Verify by doing the arithmetic you needed all along
Fill the blank to convert price to numbers. The n/a becomes NA (that is fine, na.rm = TRUE skips it), and the day's revenue comes out as 94.
Show answer
sales$price <- as.numeric(sales$price)
revenue <- sales$price * sales$sold
sum(revenue, na.rm = TRUE)
#> [1] 94References
A few trustworthy places to take this further, all free:
- Advanced R (2e): Vectors - the definitive account of R's types and the coercion rules that conversion undoes.
- R for Data Science (2e): Factors - a clear, example-led chapter on factors, levels and ordering, the structure behind today's trap.
- R documentation: factor() - every argument of
factor(), includinglevelsandordered, straight from the source. - An Introduction to R: objects, their modes and attributes - the canonical reference on R's modes and how a value's type is recorded.
Lesson 5 complete
You learned to change a value's type on purpose: diagnose every column with str() and class(), then convert with the as.* family, as.numeric(), as.character(), as.integer(), as.logical() and as.factor(). You saw the two traps that catch everyone: converting genuinely non-numeric text gives NA and the warning "NAs introduced by coercion" (a flag, not noise), and calling as.numeric() straight on a factor returns its hidden integer codes, so you go through as.character() first. Finally you fixed a real imported column end to end, diagnose, convert, verify, and got Priya her day's takings.
That completes R Foundations: Data Structures. You can now build, inspect and repair every core R object: vectors, lists, data frames, matrices and factors. Next in the foundations track comes Programming, where you start acting on these structures: subsetting and replacing their elements, branching with if/else, looping, and writing your own functions.