The Data Types in R
Every value in R has one of a small set of atomic types. Four of them do almost all the work:
- double - decimal numbers:
3.14,-2,1e6. This is what R means by "numeric", and it's the default for any number you type. - integer - whole numbers stored exactly, written with an
Lsuffix:42L. - character - text in quotes:
"hello". - logical - the two truth values
TRUEandFALSE(plusNA, the missing value - see missing values).
There's also complex (2 + 3i) for the occasional bit of signal-processing math; you can go years without needing it. typeof() tells you which type a value has:
Note the first line: a plain number is a double even if it looks whole. typeof(42) is "double"; only 42L is an integer. In day-to-day analysis the difference rarely matters - R converts between them silently - but it explains a lot of otherwise-confusing output from typeof().
typeof() vs class(): Why They Disagree
R has two functions that both look like they answer "what is this?", and they answer subtly different questions.
typeof()reports the storage type: how the value sits in memory.class()reports the behavioral type: the label R's method dispatch uses to decide, say, whichprintorsummaryto run.
For simple values the answers differ only in vocabulary:
typeof() says double (the storage), class() says numeric (the behavior). For structures, the gap widens:
The matrix is stored as a plain integer vector, but it behaves as a matrix - class() is what tells R to print it as a grid. The same split shows up with data frames (typeof says "list", class says "data.frame") and dates (typeof says "double", class says "Date").
The practical rule: use class() when you want to know what something is in the everyday sense, and typeof() when you're debugging what R is doing under the hood.
Checking Types with is.*()
Each type has a matching predicate that returns TRUE or FALSE - handy inside functions that need to validate their input:
One trap worth knowing: is.numeric() is TRUE for both doubles and integers (they're both numbers), but is.integer() checks storage, so it's FALSE for a plain 42:
If you've ever been surprised that is.integer(7) is FALSE for a number that is obviously an integer in the math sense - this is why. 7 is a double; 7L is an integer.
Converting Types with as.*()
Explicit conversion uses the as.* family, one function per target type:
Two behaviors to remember. as.integer() truncates toward zero rather than rounding - 7.9 becomes 7. And when a conversion makes no sense, R doesn't throw an error; it returns NA and emits a warning:
Run that and you'll see NA plus a "NAs introduced by coercion" warning. This is genuinely useful - convert a whole column of messy text to numbers and the unparseable entries become NA instead of crashing your script - but it also means a silent NA can slip into your data. When a conversion matters, check for new NAs afterward.
Logicals convert to and from numbers the way you'd hope: as.numeric(TRUE) is 1, as.logical(0) is FALSE. That's why sum() of a logical vector counts the TRUEs:
Implicit Coercion: The One-Type Rule for Vectors
An atomic vector in R can hold only one type. Combine mixed values with c() and R silently promotes everything to the most flexible type present, following a fixed ladder:
logical → integer → double → character
The logicals became 1 and 0, the integers became doubles, and in the last line everything became text - character always wins, because any value can be written as a string. You can confirm with typeof():
Coercion is the number-one source of "why is my column text?" bugs: one stray "N/A" string in a column of numbers coerces the entire vector to character. When that happens, fix the offending value first, then convert back with as.numeric().
What You Take Away
- Four workhorse types: double ("numeric"), integer (
42L), character, logical. typeof()= how it's stored;class()= how it behaves.3.14is adoublethat behaves asnumeric.- Check with
is.numeric()and friends; convert withas.numeric()and friends - failed conversions giveNAwith a warning, not an error. - Vectors hold one type only; mixing coerces along logical → integer → double → character.
Next up: a closer look at the numeric types - rounding, math functions, and the floating-point surprises every language shares.
Frequently Asked Questions
What are the basic data types in R?
The atomic types are double (decimal numbers, what R calls "numeric"), integer (whole numbers written with an L suffix, like 42L), character (text in quotes), logical (TRUE/FALSE), and the rarely-used complex. Everything bigger - vectors, data frames, lists - is built out of these.
What is the difference between typeof() and class() in R?
typeof() reports how a value is stored in memory (its low-level type); class() reports what the value behaves as, which controls which methods R dispatches. For a plain decimal they disagree on wording - typeof(3.14) is "double" while class(3.14) is "numeric" - and for structures like data frames typeof() says "list" while class() says "data.frame".
How do you convert types in R?
Use the as.* family: as.numeric("3.5") gives 3.5, as.character(42) gives "42", as.integer(7.9) truncates to 7. When a conversion is impossible, R returns NA and raises a warning rather than an error - as.numeric("abc") is NA.
What is coercion in R?
When you combine different types in one vector, R silently converts everything to the most flexible type, following the order logical → integer → double → character. So c(1, 2, "three") becomes a character vector: "1" "2" "three".