Menu

R Data Types: numeric, character, logical (typeof & class)

The atomic data types in R - double, integer, character, logical - plus how typeof() and class() differ, and how conversion and coercion work.

This page includes runnable editors - edit, run, and see output instantly.

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 L suffix: 42L.
  • character - text in quotes: "hello".
  • logical - the two truth values TRUE and FALSE (plus NA, 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, which print or summary to 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.14 is a double that behaves as numeric.
  • Check with is.numeric() and friends; convert with as.numeric() and friends - failed conversions give NA with 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".

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED