Data Types & Classes
Reviewed & published by Brayan K
A data type is the kind of value a piece of R data holds — numeric, integer, character, or logical — and it determines what operations you can perform on it.
Part of the free R course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
In this lesson you'll learn to identify every basic type with class() and typeof(), test types with the is.*() family, convert between them with as.*(), and understand R's coercion rules plus the crucial difference between NA and NULL.
What You'll Learn in This Lesson
- The four core atomic types: numeric, integer, character, logical
- How class() and typeof() differ — and when each matters
- Testing types with is.numeric(), is.character(), is.na()
- Converting between types with as.numeric() and friends
- R's automatic coercion rules in mixed vectors
- The difference between NA (missing) and NULL (absent)
1️⃣ The Four Core Types
Almost everything in R is built from four atomic types. A numeric (also called a double) is any number and is the default — even 5 is numeric. An integer is a whole number you create with an L suffix, like 42L. A character is text wrapped in quotes, and a logical is TRUE or FALSE.
# R has a handful of "atomic" types. Here are the four you meet daily.
x <- 3.14 # numeric (double, the default for any number)
n <- 42L # integer (the L suffix forces a whole-number integer)
s <- "hello" # character (text, in quotes)
b <- TRUE # logical (TRUE or FALSE)
class(x) # -> [1] "numeric"
class(n) # -> [1] "integer"
class(s) # -> [1] "character"
class(b) # -> [1] "logical"2️⃣ Inspecting Types: class(), typeof(), is.*()
Three tools answer three different questions. class() gives the conceptual type you usually reason about. typeof() reveals R's internal storage — note that a plain number is stored as a "double" even though its class is "numeric". The is.*() functions return TRUE/FALSE, perfect for an if.
# class() tells you the high-level type; typeof() tells you the
# low-level storage; is.*() asks a yes/no question.
class(42L) # -> [1] "integer"
typeof(42L) # -> [1] "integer"
typeof(3.14) # -> [1] "double" (numeric is stored as a double)
is.numeric(3.14) # -> [1] TRUE
is.character("a") # -> [1] TRUE
is.logical(TRUE) # -> [1] TRUE
is.numeric("a") # -> [1] FALSE3️⃣ Coercion: Converting Between Types
You convert on purpose with the as.*() family. Watch the gotchas: as.integer(3.9) truncates to 3 (it does not round), and as.logical(0) is FALSE while any non-zero number is TRUE. R also coerces automatically when you mix types in one vector, promoting everything to the most flexible type: logical < integer < numeric < character.
# Coercion: turning one type into another with as.*()
as.numeric("10") # text -> number -> [1] 10
as.character(10) # number -> text -> [1] "10"
as.integer(3.9) # truncates (no rounding!) -> [1] 3
as.logical(0) # 0 is FALSE -> [1] FALSE
as.logical(1) # any non-zero is TRUE -> [1] TRUE
# Automatic coercion in a mixed vector: everything becomes the
# most flexible type present. Logical < integer < numeric < character.
c(1, TRUE, "x") # all become character -> [1] "1" "TRUE" "x"The mental model: when types collide, R picks the type that can represent all the values without losing information — and character can represent anything, so it always wins.
4️⃣ NA vs NULL
NA means a value is missing — the slot exists but the data is unknown, so it still counts toward length(). NULL means a value is absent — there is no slot at all, so c(1, NULL, 3) drops it and leaves length 2. Use is.na() to find missing values (you'll go deep on this in the Missing Data lesson).
# NA = "a value is missing here" (still a slot in the vector).
# NULL = "there is no value at all" (the slot does not exist).
x <- c(1, NA, 3)
length(x) # NA still counts as an element -> [1] 3
is.na(x) # -> [1] FALSE TRUE FALSE
y <- c(1, NULL, 3) # NULL is dropped entirely
length(y) # -> [1] 2
y # -> [1] 1 3Your turn. Fill in the ___ blanks, run it, and compare with the expected output.
# 🎯 YOUR TURN — replace each ___ then run it.
# 1) Store the number of planets as an INTEGER (use the L suffix)
planets <- ___ # 👉 replace ___ with 8L
# 2) Confirm its class is "integer"
print(class(planets)) # should print [1] "integer"
# 3) Coerce the text "3.5" into a number and double it
val <- as.numeric("3.5") * 2
print(val)
# ✅ Expected output:
# [1] "integer"
# [1] 7Mini-Challenge: Type Detective
Write it from the outline, run it, and predict each result before you see it. The goal is to internalise the coercion hierarchy so it never surprises you again.
# 🎯 MINI-CHALLENGE: a type detective.
#
# 1. Create a mixed vector: c(10, "20", TRUE)
# 2. Print its class() — what did R coerce everything to, and why?
# 3. Pull out the second element and coerce it back to a number.
# 4. Print is.na() on c(5, NA, 9) to find the missing slot.
#
# ✅ Example output:
# [1] "character"
# [1] 20
# [1] FALSE TRUE FALSE
# your code hereCommon Errors (and the fix)
- ❌ "5" + 1 → error: non-numeric argument. ✅ Coerce first: as.numeric("5") + 1.
- ❌ Expecting as.integer(3.9) to give 4. ✅ It truncates to 3 — use round() first if you want rounding.
- ❌ Forgetting the L: class(42) is "numeric", not integer. ✅ Write 42L for a true integer.
- ❌ Treating NA like NULL. ✅ NA occupies a slot; NULL is dropped from a vector entirely.
Pro Tips
- 💡 Reach for str(): str(x) prints type and a preview of contents in one line — the fastest way to "see" any object.
- 💡 Compare with care: 1L == 1 is TRUE because R coerces them before comparing.
- 💡 Always check after as.numeric(): if a value can't convert (like as.numeric("abc")) you get NA with a warning, not an error.
📋 Quick Reference — Types
| Task | R Syntax |
|---|---|
| Conceptual type | class(x) |
| Storage type | typeof(x) |
| Make an integer | 42L |
| Text → number | as.numeric("10") |
| Test for missing | is.na(x) |
| Summarise object | str(x) |
🎉 Lesson Complete!
- ✅ The four core types are numeric, integer (with L), character, and logical
- ✅ class() gives the concept, typeof() the storage, is.*() a TRUE/FALSE test
- ✅ as.*() converts on purpose; mixed vectors coerce to the most flexible type
- ✅ NA is a missing value in a slot; NULL is the absence of a slot
Practice quiz
What class does the literal 5 have by default in R?
- integer
- character
- numeric
- logical
Answer: numeric. Plain numbers are numeric (doubles) unless you add the L suffix.
How do you create a true integer in R?
- Add an L suffix, e.g. 42L
- Wrap it in quotes, e.g. "42"
- Prefix with i, e.g. i42
- Use 42.0
Answer: Add an L suffix, e.g. 42L. The L suffix forces integer storage; 42 alone is numeric.
What does typeof(3.14) return?
- "numeric"
- "float"
- "integer"
- "double"
Answer: "double". Numeric values are stored as doubles, so typeof gives "double".
What does as.integer(3.9) produce?
- 4 (it rounds)
- 3 (it truncates)
- 3.9
- An error
Answer: 3 (it truncates). as.integer() truncates toward zero; use round() first to round.
What does c(1, TRUE, "x") coerce all elements to?
- character
- numeric
- logical
- list
Answer: character. A vector holds one type; character is the most flexible, so all become character.
What is the coercion hierarchy from least to most flexible?
- character < numeric < integer < logical
- numeric < logical < character < integer
- logical < integer < numeric < character
- integer < character < numeric < logical
Answer: logical < integer < numeric < character. When types mix, R promotes toward character, the most general type.
What does as.logical(0) return?
- TRUE
- FALSE
- 0
- NA
Answer: FALSE. 0 coerces to FALSE; any non-zero number coerces to TRUE.
How does NA differ from NULL?
- They are identical
- NULL counts toward length but NA does not
- NA only appears in characters
- NA occupies a slot and counts toward length; NULL is dropped
Answer: NA occupies a slot and counts toward length; NULL is dropped. NA is a present-but-missing value; NULL is the absence of a value.
What does length(c(1, NA, 3)) return?
- 2
- 3
- 1
- 0
Answer: 3. NA still occupies an element, so the length is 3.
Which function returns TRUE/FALSE for each missing value in a vector?
- na.rm(x)
- missing(x)
- is.na(x)
- is.null(x)
Answer: is.na(x). is.na() tests each element for being NA, returning a logical vector.
Continue this course
- Previous: Statistical Functions
- Next: Subsetting & Indexing ([ ], [[ ]], $) — Positional, logical and name-based indexing for vectors and lists
- Quick reference: R cheat sheet
Frequently asked questions
What is the difference between numeric and integer in R?
By default, every number you type in R — even 5 — is stored as a 'double' (a numeric value that can hold decimals). To get a true integer you must add the L suffix, like 5L. In practice the distinction rarely matters for everyday analysis because R converts between them automatically, but it matters for memory, for interfacing with C/database code, and when a function specifically expects an integer. Use is.integer() to check and as.integer() to convert.
Why did my numbers turn into text with quotes?
A vector can only hold one type, so when you mix types R 'coerces' them all to the most flexible one present. The hierarchy is logical, then integer, then numeric, then character — and character wins. So c(1, 2, "3") becomes all character ("1", "2", "3"). If a numeric vector suddenly prints with quotes, a stray piece of text crept in. Check with class() and fix the source value or use as.numeric().
What is the difference between NA and NULL?
NA means 'a value should be here but it is missing or unknown' — it still occupies a position in the vector and counts toward length(). NULL means 'there is genuinely nothing here' — it is the absence of a value, has length 0, and is dropped when you put it inside c(). Use NA for missing observations in your data and NULL to represent an empty or non-existent object, such as an argument you want to leave unset.
How do I check what type something is?
Use class() for the conceptual type you usually care about ("numeric", "character", "logical", "factor", "data.frame"), typeof() for R's internal storage type ("double", "integer", "character", "list"), and the is.*() family (is.numeric(), is.character(), is.na()) for a TRUE/FALSE test you can use in an if statement. str() is also excellent: it prints a compact summary of the type and contents of any object.