R (4.6) Module 2 – Vectors, types and missing values
Atomic vectors, types and coercion
R's six atomic types, typeof(), class() and mode(), what c() does to mixed values, converting with as.numeric() without silent NAs, and names on vectors.
What you will learn
- Name the six atomic types and tell typeof(), class() and mode() apart
- Predict the type c() gives a vector that mixes values of different types
- Convert between types with as.*() and find the NAs a conversion introduced
- Add, change and remove the names of a vector's elements
Before you start
What you’ll learn · Before you start
What you will learn
- Name the six atomic types and tell typeof(), class() and mode() apart
- Predict the type c() gives a vector that mixes values of different types
- Convert between types with as.*() and find the NAs a conversion introduced
- Add, change and remove the names of a vector's elements
Before you start
On this page
c(12, 7.5, "N/A") looks like three amounts, but R stores it as three pieces of text, "12", "7.5" and "N/A",
and sum() of it stops with an error. One value of another type changed all three, because an R vector holds values of
a single type, and R chooses that type by a few strict rules.
The runs on this page show those rules at work in R 4.6.1. You will name R’s six atomic types and tell typeof(),
class() and mode() apart, predict the type c() gives mixed values, convert text to numbers on purpose while
catching every value that turns into NA, and label the elements of a vector with names.
Even one number is a vector, and a vector has one type
Type 42 at the console and R answers [1] 42. The [1] is the position of the first value on that line, because
even a single number is a vector, of length 1. A vector is a row of values of one type, and R has six such types. The
R Language Definition calls them the basic, or atomic, vector types, so a vector made of them is an
atomic vector: A vector whose elements all have one of R's six basic types: logical, integer, double, complex, character or raw. A single value such as 42 is an atomic vector of length 1.. Here is each type with the way you type a value of it and what class()
reports:
typeof() |
Typed as | class() |
|---|---|---|
"logical" |
TRUE, FALSE |
"logical" |
"integer" |
7L, 1:3 |
"integer" |
"double" |
7, 2.5, 1e6 |
"numeric" |
"complex" |
2+3i |
"complex" |
"character" |
"seven" |
"character" |
"raw" |
as.raw(255) |
"raw" |
Logical vectors hold TRUE, FALSE and NA. Integers are whole numbers stored exactly in 32 bits, up to
2,147,483,647 either way. Doubles are real numbers in binary floating point with 53 bits of precision, about 15
significant digits, and they are what R makes of any number you type without an L. Character vectors hold text.
Complex numbers and raw bytes exist for special work and rarely appear in an analysis.
The run below asks R about each type, and then asks the same values three different questions:
from session import run, version
version()
# R reads types.R as if each line were typed at the console.
run("""
R --vanilla --quiet -f types.R
""") Output
# R version 4.6.1 -- "Happy Hop"
$ R --vanilla --quiet -f types.R
> # One number is a vector of length 1.
> x <- 42
> length(x)
[1] 1
> # typeof() names the type R stores the values in.
> typeof(7)
[1] "double"
> typeof(7L)
[1] "integer"
> typeof(TRUE)
[1] "logical"
> typeof("seven")
[1] "character"
> typeof(2+3i)
[1] "complex"
> typeof(as.raw(255))
[1] "raw"
> # mode() calls both integer and double "numeric".
> mode(7L)
[1] "numeric"
> mode(7)
[1] "numeric"
> # class() is what functions look at to decide what to do.
> class(7)
[1] "numeric"
> class(1:3)
[1] "integer"
> class(matrix(1:4, nrow = 2))
[1] "matrix" "array"
> # A whole number typed without L is still a double.
> is.integer(7)
[1] FALSE
> is.integer(7L)
[1] TRUE
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 types_session.py
The three functions answer different questions:
typeof()names how the values are stored:"double"for 7,"integer"for7L.mode()is an older, coarser grouping kept from the S language, which calls both integers and doubles"numeric".class()is what functions such asprint()andsummary()look at to decide how to treat an object. A plain vector has an implicit class,"numeric"for a double, and a matrix of the same numbers isc("matrix", "array").
Use typeof() when storage matters (a whole number that overflows, a column read as text), class() when you want to
know how functions will treat an object, and mode() rarely.
Expecting 7 to be an integer
typeof(7) is "double" and is.integer(7) is FALSE, as the run shows. A whole number you type is an integer
only when you write it with an L, as in 7L; 1:3 and as.integer() make integers too. is.integer() tests the
storage, not whether the value happens to be whole.
Checkpoint
Check: The six atomic types
c() gives mixed values the most flexible type
c(1, TRUE, FALSE) returns 1 1 0, numbers, and c(1, "a", TRUE) returns "1" "a" "TRUE", text. A vector cannot
hold a number beside a piece of text, so coercion: Converting values from one type to another. c() does it by itself when it combines values of different types; as.numeric(), as.character() and the other as.*() functions do it on request. converts every value to one type. The help
page of c() gives the order R picks from:
- logical < integer < double < complex < character
The result takes the highest type among its values. A type higher up can hold every value of the types below it,
written its own way: TRUE is 1 as a number and "TRUE" as text, and a number becomes its digits written as text.
Raw sits below logical in the same order, and the lists of a later lesson sit above character.
Step through a vector that gains one value at a time, v <- c(v, value), and watch it climb:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- The ladder of types. The help page of c() orders the atomic types from least to most flexible. A type higher up can hold every value of the types below it, written its own way.
- v <- TRUE. v starts as one logical value, on the lowest rung.
- v <- c(v, 1L). 1L is an integer, so the whole vector moves up to integer. TRUE becomes 1, the number it stands for in arithmetic, and v prints as 1 1.
- v <- c(v, 2.5). 2.5 is a double, so v moves up again. The values stay 1 and 1; only the way they are stored changes, and v prints as 1.0 1.0 2.5.
- v <- c(v, "a"). Text sits on the top rung. Every number is written out as text, so the first value, once TRUE, is now the string "1".
- All at once. c(TRUE, 1L, 2.5, "a") converts each value straight to text, so TRUE becomes "TRUE". A conversion that has happened is never undone, so the order of the steps decided the first value.
The first value of v ended as "1", while the same values combined in one call give "TRUE". Each step converts
the values it has, and a conversion is never undone. Here are those lines, with the opening’s amounts and a few more
mixtures, typed at the console:
from session import run, version
version()
# R reads coercion.R as if each line were typed at the console.
run("""
R --vanilla --quiet -f coercion.R
""") Output
# R version 4.6.1 -- "Happy Hop"
$ R --vanilla --quiet -f coercion.R
> # Three amounts, one of them typed as text.
> amounts <- c(12, 7.5, "N/A")
> amounts
[1] "12" "7.5" "N/A"
> try(sum(amounts))
Error in sum(amounts) : invalid 'type' (character) of argument
> # c() gives every value the most flexible type among them.
> c(1, TRUE, FALSE)
[1] 1 1 0
> c(1L, 2.5)
[1] 1.0 2.5
> c(2.5, 1+2i)
[1] 2.5+0i 1.0+2i
> c(1, "a", TRUE)
[1] "1" "a" "TRUE"
> # One value at a time, the vector climbs the ladder.
> v <- TRUE
> v <- c(v, 1L)
> v
[1] 1 1
> v <- c(v, 2.5)
> v
[1] 1.0 1.0 2.5
> v <- c(v, "a")
> v
[1] "1" "1" "2.5" "a"
> # Once v is text, mean() has no numbers to average.
> mean(v)
[1] NA
Warning message:
In mean.default(v) : argument is not numeric or logical: returning NA
> # All at once, TRUE goes straight to text.
> c(TRUE, 1L, 2.5, "a")
[1] "TRUE" "1" "2.5" "a"
> # NA takes the type of the vector it joins.
> typeof(c(1.5, NA))
[1] "double"
> typeof(c("a", NA))
[1] "character"
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 coercion_session.py
Marking a missing number with text
A loop that collects results with v <- c(v, value) and stores "missing" for a failed value turns every number in
v into text, and mean(v) then returns NA with a warning, as it did for the v of the run. Store NA for a
missing number, so the vector stays a double.
NA, the missing value, takes the type of the vector it joins: in c(1.5, NA) it is a missing double, in
c("a", NA) a missing piece of text. So one gap never changes a vector’s type, while one piece of text does.
Key idea
A vector has one type: c() converts every value to the most flexible type among them, in the order logical,
integer, double, complex, character, and a conversion that has happened is never undone.
Checkpoint
Check: How c() picks one type
The as.*() functions convert on purpose, and NA marks what failed
as.numeric(" 42 ") is 42, while as.numeric("1,234") is NA with the warning “NAs introduced by coercion”. The
functions as.logical(), as.integer(), as.numeric(), as.character() and as.complex() convert a vector to one
type when you ask, and their help pages give the rules:
- Text to numbers reads optional spaces, then a decimal or hexadecimal number, or one of the words
InfandNaNin any case:" 42 "gives 42,"0x1A"26 and"1e3"1000. Anything else, a comma included, becomesNAwith a warning, and an empty string becomesNAwith no warning at all. as.integer()drops the fraction towards zero: 3.7 gives 3, and -3.7 gives -3.as.logical()reads"TRUE","true","True"and"T"asTRUE, the four matching spellings asFALSE, and everything else,"yes"and"1"included, asNA. For numbers, 0 isFALSEand every other numberTRUE.as.character()writes a number with up to 15 significant digits, in e-notation when that is shorter, so 1/3 becomes"0.333333333333333"and 100000 becomes"1e+05".paste0()does the same, which can turn an identifier such asid_100000intoid_1e+05.
The run tries each rule, then finds which entries a conversion turned into NA:
from session import run, version
version()
# R reads convert.R as if each line were typed at the console.
run("""
R --vanilla --quiet -f convert.R
""") Output
# R version 4.6.1 -- "Happy Hop"
$ R --vanilla --quiet -f convert.R
> # as.integer() drops the fraction, towards zero.
> as.integer("3.7")
[1] 3
> as.integer(-3.7)
[1] -3
> # as.numeric() trims spaces and reads hex and exponents.
> as.numeric(" 42 ")
[1] 42
> as.numeric("0x1A")
[1] 26
> as.numeric("1e3")
[1] 1000
> # It also reads the words Inf and NaN, in any case.
> as.numeric(c("Inf", "-inf", "NaN"))
[1] Inf -Inf NaN
> # Text it cannot read becomes NA, with a warning.
> as.numeric("1,234")
[1] NA
Warning message:
NAs introduced by coercion
> # An empty string becomes NA without any warning.
> as.numeric("")
[1] NA
> # as.logical() knows four spellings of each value.
> as.logical(c("TRUE", "true", "T", "yes", "1"))
[1] TRUE TRUE TRUE NA NA
> as.logical(0:2)
[1] FALSE TRUE TRUE
> # as.character() and paste() write up to 15 digits,
> # or e-notation when that is shorter.
> as.character(1/3)
[1] "0.333333333333333"
> paste0("id_", 100000)
[1] "id_1e+05"
> format(100000, scientific = FALSE)
[1] "100000"
> # Which entries did the conversion turn into NA?
> raw <- c("12", "7.5", "seven", NA, "")
> num <- suppressWarnings(as.numeric(raw))
> num
[1] 12.0 7.5 NA NA NA
> raw[is.na(num) & !is.na(raw)]
[1] "seven" ""
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 convert_session.py
The warning says that something failed, not what. The last line answers that: an entry that is NA after the
conversion but was not NA before is one that failed, here "seven" and the empty string. Keep that line for every
conversion of typed data, because the blank never warned. These are the results from the run that surprise people
most:
| Call | Result | Warning |
|---|---|---|
as.numeric("1,234") |
NA |
yes |
as.numeric("") |
NA |
none |
as.integer(-3.7) |
-3 |
none |
as.logical("yes") |
NA |
none |
paste0("id_", 100000) |
"id_1e+05" |
none |
Before you convert typed amounts, remove what is not part of the number, and remove it precisely. A pattern that keeps only digits and dots also keeps the dot of an abbreviation and throws away a minus sign:
from session import run, version
version()
# R reads clean_amounts.R as if each line were typed at the console.
run("""
R --vanilla --quiet -f clean_amounts.R
""") Output
# R version 4.6.1 -- "Happy Hop"
$ R --vanilla --quiet -f clean_amounts.R
> amounts <- c("₹1,25,000", "3,450.50", "N/A", "Rs. 450", "-1,000")
> # Keeping only digits and dots looks safe. It is not.
> as.numeric(gsub("[^0-9.]", "", amounts))
[1] 125000.00 3450.50 NA 0.45 1000.00
> # Remove the currency first, then the commas, then convert.
> no_currency <- sub("^(Rs\\.?|₹)\\s*", "", amounts)
> cleaned <- gsub(",", "", no_currency)
> cleaned
[1] "125000" "3450.50" "N/A" "450" "-1000"
> values <- suppressWarnings(as.numeric(cleaned))
> values
[1] 125000.0 3450.5 NA 450.0 -1000.0
> # What could not be read is reported, not hidden.
> amounts[is.na(values)]
[1] "N/A"
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 clean_amounts_session.py
Amounts typed with ₹, Rs. and lakh groupings
Indian amounts are grouped in twos after the last three digits, so "₹1,25,000" is one lakh twenty-five thousand,
125,000. Removing every comma handles both groupings. The trap is "Rs. 450": keeping only digits and dots leaves
".450", which R reads as 0.45, with no warning, and "-1,000" loses its minus sign. Removing the currency at the
start first, with sub("^(Rs\\.?|₹)\\s*", "", x), and then the commas gives 450 and -1000.
Checkpoint
Check: Converting with as.*()
Names label the elements of a vector
c(Mon = 31.5, Tue = 33.0, Wed = 29.8) prints each value under its day. The names are not part of the values: they
are an attribute: Extra information R stores beside a vector's values, such as its names, its dimensions or its class. attributes(x) lists them., extra information R stores beside a vector, which attributes() lists
and str() reports as a Named num. Step through what each call does to them:
The drawing shows the whole scene; its 4 steps are listed below.
Every step as text
- c(Mon = 31.5, ...). Writing name = value pairs inside c() makes one vector of three doubles and stores the three names beside it. str() calls it a Named num.
- names(temps). names() reads the attribute and returns it as a character vector of the same length as the values.
- names(temps)[2] <- "Tues". Assigning to one position of names() changes that name alone. The values are untouched.
- unname(temps). unname(temps) returns the values without the attribute, as drawn here, and as.numeric() does the same, because it drops every attribute. temps itself keeps its names until you assign the result.
The same calls, and two more, typed at the console:
from session import run, version
version()
# R reads names.R as if each line were typed at the console.
run("""
R --vanilla --quiet -f names.R
""") Output
# R version 4.6.1 -- "Happy Hop"
$ R --vanilla --quiet -f names.R
> # Names can be given as the vector is made.
> temps <- c(Mon = 31.5, Tue = 33.0, Wed = 29.8)
> temps
Mon Tue Wed
31.5 33.0 29.8
> names(temps)
[1] "Mon" "Tue" "Wed"
> str(temps)
Named num [1:3] 31.5 33 29.8
- attr(*, "names")= chr [1:3] "Mon" "Tue" "Wed"
> # Arithmetic keeps the names.
> temps * 9 / 5 + 32
Mon Tue Wed
88.70 91.40 85.64
> # One name can be changed in place.
> names(temps)[2] <- "Tues"
> temps
Mon Tues Wed
31.5 33.0 29.8
> # Too few names: the rest become NA.
> rain <- c(12, 0, 7)
> names(rain) <- c("Mon", "Tue")
> rain
Mon Tue <NA>
12 0 7
> # unname() and as.numeric() return the values alone.
> unname(temps)
[1] 31.5 33.0 29.8
> as.numeric(temps)
[1] 31.5 33.0 29.8
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 names_session.py
The run shows four rules:
names(x) <- valuesets them, andnames(x)[2] <- "Tues"changes one.names(x)reads them back as a character vector.- Too few names are filled with
NA. Two names for three rainfall values leave the third named<NA>. - Arithmetic keeps the names.
temps * 9 / 5 + 32gives Fahrenheit values still labelled Mon, Tue and Wed. unname()andas.numeric()drop them, becauseas.numeric(), like the otheras.*()conversions of this page, strips every attribute.
Names make results readable and let you pick values by label, as in temps["Wed"], which the lesson on indexing
covers.
Checkpoint
Check: Names on a vector
Practise: predict, then check
Each line below mixes the ideas of this page. Before you look at the run, write down what you expect each line to print, with its type.
Predict first
For x <- c(score = 7, total = "10"), predict typeof(x), x, as.numeric(x) and c(x, extra = TRUE), then
as.integer(c("4.9", "-4.9", "four")) and as.logical(c("False", "no", "0")). Then type them in your own R and
compare with the run.
from session import run, version
version()
# R reads practice.R as if each line were typed at the console.
run("""
R --vanilla --quiet -f practice.R
""") Output
# R version 4.6.1 -- "Happy Hop"
$ R --vanilla --quiet -f practice.R
> x <- c(score = 7, total = "10")
> typeof(x)
[1] "character"
> x
score total
"7" "10"
> as.numeric(x)
[1] 7 10
> c(x, extra = TRUE)
score total extra
"7" "10" "TRUE"
> as.integer(c("4.9", "-4.9", "four"))
[1] 4 -4 NA
Warning message:
NAs introduced by coercion
> as.logical(c("False", "no", "0"))
[1] FALSE NA NA
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 practice_session.py
The text "10" made the whole vector character, names included; as.numeric() read both values back and dropped the
names; TRUE arrived as "TRUE"; as.integer() truncated towards zero and failed on "four"; and of the three
spellings only "False" is one as.logical() knows.
Then check yourself on types, coercion, conversion and names. Each question explains its answer as soon as you choose, and the next one goes to whichever idea needs more practice:
Practice that adapts to you
Practise this lesson
11 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up: what do typeof(), class() and mode() each tell you about an R value?
typeof() names the type R stores the values in: for an atomic vector one of "logical", "integer", "double",
"complex", "character" or "raw", and other types such as "list" or "closure" for other objects. class()
names what functions use to decide how to treat an object: its class attribute if it has one, such as "factor" or
"data.frame", otherwise an implicit class, "numeric" for a double and c("matrix", "array") for a matrix.
mode(), kept from the S language, reports integers and doubles alike as "numeric". So typeof(7) is "double"
while class(7) and mode(7) are both "numeric". Check typeof() when storage matters, such as a column read as
text or a whole number that must be an integer, and class() when you need to know what print() or summary() will
do.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
A file of 50,000 payments loads with its amount column as text, and as.numeric() on it gives 312 NA values and a single warning. How do you work out what went wrong, convert the column safely, and show that no amount was changed without anyone noticing?
-
Fresher to mid
A loop collects 200 measurements with v <- c(v, value) and stores the word "failed" whenever a measurement fails. At the end mean(v) returns NA with a warning. Explain what happened to the values that were collected before the first failure, and how you would collect the results instead.
Key takeaways
- Even a single number is a vector in R, of length 1, and an atomic vector holds values of one of six types: logical, integer, double, complex, character or raw.
typeof()reports the storage type,class()what functions dispatch on, andmode()an older grouping that calls integers and doubles"numeric"; a number typed withoutLis a double.c()converts mixed values to the most flexible type, in the order logical, integer, double, complex, character, and a conversion that has happened is never undone.as.numeric()and its relatives convert on purpose and giveNAfor what they cannot read, with a warning except for empty strings, so compareis.na()before and after a conversion to find what failed.- Names are an attribute:
names()reads and sets them, arithmetic keeps them, andunname()and conversions such asas.numeric()drop them.
Terms in this lesson
- atomic vector
- A vector whose elements all have one of R's six basic types: logical, integer, double, complex, character or raw. A single value such as 42 is an atomic vector of length 1.
- attribute
- Extra information R stores beside a vector's values, such as its names, its dimensions or its class. attributes(x) lists them.
- coercion
- Converting values from one type to another. c() does it by itself when it combines values of different types; as.numeric(), as.character() and the other as.*() functions do it on request.
References
- R Language Definition: Vectors (section 2.1.1) (The R Core Team)
- An Introduction to R: Objects, their modes and attributes (The R Core Team)
- The Type of an Object (typeof) (The R Core Team)
- The (Storage) Mode of an Object (mode) (The R Core Team)
- Object Classes (class) (The R Core Team)
- Combine Values into a Vector or List (c) (The R Core Team)
- Vectors: Creation, Coercion, etc (vector) (The R Core Team)
- Integer Vectors (integer) (The R Core Team)
- Double-Precision Vectors (double) (The R Core Team)
- Logical Vectors (logical) (The R Core Team)
- The Names of an Object (names) (The R Core Team)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress