R (4.6) Module 2 – Vectors, types and missing values
Missing values, NaN, Inf and NULL
Tell NA, NaN, Inf and NULL apart in R, handle gaps with na.rm, complete.cases() and na.omit(), filter tables without rows of NA, and report what is missing.
What you will learn
- Distinguish NA, NaN, Inf and NULL, and test for each with the right function
- Handle missing values in summaries with na.rm, complete.cases() and na.omit()
- Avoid the rows of NA that logical filtering of a data frame produces
- Report missing values with counts, shares and table(useNA = "ifany")
Before you start
What you’ll learn · Before you start
What you will learn
- Distinguish NA, NaN, Inf and NULL, and test for each with the right function
- Handle missing values in summaries with na.rm, complete.cases() and na.omit()
- Avoid the rows of NA that logical filtering of a data frame produces
- Report missing values with counts, shares and table(useNA = "ifany")
Before you start
On this page
mean(c(72, NA, 85)) is NA: one missing mark makes the whole average missing, and R is right not to guess. Missing
values cause most of the quiet mistakes in data work: a test with == that never finds them, a count that leaves them
out without saying so, and a filter that adds rows of NA to a table.
On runs of R 4.6.1 you will tell NA, NaN, Inf and NULL apart and pick the right test for each, handle missing
values in summaries with na.rm, complete.cases() and na.omit(), filter a table without rows of NA, and report
missing values so that a reader can see them.
NA spreads through a calculation unless the answer cannot depend on it
NA means “not available”: a value that exists but is not known. The R Language Definition says numeric and logical
calculations with NA generally return NA, and that is what keeps an unknown from turning into a confident
answer. mean(c(72, NA, 85)) is NA, because the mean depends on the missing mark; na.rm = TRUE asks R to leave
missing values out, and gives 78.5 from the other two.
from session import run, version
version()
# R reads na_basics.R as if each line were typed at the console.
run("""
R --vanilla --quiet -f na_basics.R
""") Output
# R version 4.6.1 -- "Happy Hop"
$ R --vanilla --quiet -f na_basics.R
> # One missing mark makes the average missing.
> mean(c(72, NA, 85))
[1] NA
> mean(c(72, NA, 85), na.rm = TRUE)
[1] 78.5
> NA + 1
[1] NA
> NA * 0
[1] NA
> # Unless the answer is the same whatever NA stands for.
> NA^0
[1] 1
> NA & FALSE
[1] FALSE
> # NA has a type, like every value.
> typeof(NA)
[1] "logical"
> typeof(NA_real_)
[1] "double"
> typeof(NA_character_)
[1] "character"
> # x == NA is NA everywhere; is.na() is the test.
> x <- c(4, NA, 7, NA)
> x == NA
[1] NA NA NA NA
> is.na(x)
[1] FALSE TRUE FALSE TRUE
> sum(is.na(x))
[1] 2
> anyNA(x)
[1] TRUE
> # Text is different: paste() writes NA as two letters.
> paste("ward", NA)
[1] "ward NA"
> nchar(c("ab", NA))
[1] 2 NA
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 na_basics_session.py
The run shows the exceptions and the traps:
- An answer that cannot depend on the unknown is given.
NA^0is 1, because the help page?Arithmeticsaysy ^ 0is 1 always, andNA & FALSEisFALSE.NA * 0staysNA, since the missing value could beInf, andInf * 0has no answer. NAhas a type. A bareNAis logical, andNA_real_,NA_integer_andNA_character_are missing values of the other types, which is why one gap never changes a vector’s type.x == NAis never the test. Comparing anything with an unknown gives an unknown, sox == NAisNAfor every element.is.na(x)givesTRUEorFALSEfor each,sum(is.na(x))counts the gaps andanyNA(x)asks whether there is at least one.- Text turns
NAinto letters.paste("ward", NA)is"ward NA", a string that is no longer missing, whilenchar()keeps a missing string missing. Checkis.na()before you build labels from data.
Testing for missing values with ==
if (x == NA) stops with “missing value where TRUE/FALSE needed”, and x[x == NA] returns only NA values.
is.na(x) is the only test that answers for every element.
Checkpoint
Check: How NA spreads
NaN, Inf and NULL are not NA, and each has its own test
0 / 0 is NaN, “not a number”, and 1 / 0 is Inf. Both are values of the IEEE 754 arithmetic that R’s doubles
follow: NaN is the result of arithmetic with no defined answer, such as 0 / 0, sqrt(-1) or Inf - Inf, while
Inf and -Inf stand for results too large for a double, such as 10^400, and for a non-zero number divided by zero,
such as 1 / 0. NULL is something else again, the absence of any value: it has length 0, and inside c() it
disappears. R prints four tests against the five kinds of value:
from session import run, version
version()
# R reads special_values.R as if each line were typed at the console.
run("""
R --vanilla --quiet -f special_values.R
""") Output
# R version 4.6.1 -- "Happy Hop"
$ R --vanilla --quiet -f special_values.R
> # NaN, "not a number", comes from arithmetic with no answer.
> 0 / 0
[1] NaN
> sqrt(-1)
[1] NaN
Warning message:
In sqrt(-1) : NaNs produced
> Inf - Inf
[1] NaN
> Inf * 0
[1] NaN
> # Inf stands for results beyond the largest double.
> 1 / 0
[1] Inf
> -1 / 0
[1] -Inf
> 10^400
[1] Inf
> # Four tests against five kinds of value.
> v <- c(1, NA, NaN, Inf, -Inf)
> names(v) <- c("1", "NA", "NaN", "Inf", "-Inf")
> rbind(is.na = is.na(v), is.nan = is.nan(v),
+ is.finite = is.finite(v), is.infinite = is.infinite(v))
1 NA NaN Inf -Inf
is.na FALSE TRUE TRUE FALSE FALSE
is.nan FALSE FALSE TRUE FALSE FALSE
is.finite TRUE FALSE FALSE FALSE FALSE
is.infinite FALSE FALSE FALSE TRUE TRUE
> # The largest value of nothing is -Inf, with a warning.
> max(numeric(0))
[1] -Inf
Warning message:
In max(numeric(0)) : no non-missing arguments to max; returning -Inf
> # NULL is no value at all: length 0, gone inside c().
> length(NULL)
[1] 0
> c(1, NULL, 2)
[1] 1 2
> is.null(NULL)
[1] TRUE
> is.na(NULL)
logical(0)
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 special_values_session.py
Read the table by column. is.na() is TRUE for NA and for NaN, which R treats as a kind of missing value, while
is.nan() is TRUE for NaN only, so is.nan(NA) is FALSE. is.finite() is TRUE for ordinary numbers alone.
max(numeric(0)) returns -Inf with a warning, the maximum of nothing, so a group with no values can put an
infinity into a table without an error. NULL passes none of these tests: is.na(NULL) is logical(0), an empty
answer, and is.null() is the test for it.
Which test you need depends on which values you want to find. Say which, and the table rules out the rest:
Answer the question above: the table then marks what fits your case and says why the rest does not.
| Test | Finds NA | Finds NaN | Finds Inf |
|---|---|---|---|
| is.na(x) TRUE for: NA and NaN | Good TRUE for NA | Good TRUE for NaN as well | Poor FALSE for Inf and -Inf |
| is.nan(x) TRUE for: NaN only | Poor FALSE for NA: is.nan(NA) is FALSE | Good TRUE for NaN only | Poor FALSE for Inf and -Inf |
| !is.finite(x) TRUE for: NA, NaN, Inf and -Inf | Good TRUE for NA | Good TRUE for NaN | Good TRUE for Inf and -Inf |
| x == NA TRUE for: nothing: every answer is NA | Poor NA, never TRUE | Poor NA, never TRUE | Poor NA, never TRUE |
How the answers decide
- “Which values must your test pick out?” Missing values: NA and NaN: rules out is.nan(x), !is.finite(x) and x == NA. is.na() is TRUE for NA and NaN and FALSE for Inf. is.nan() misses NA, !is.finite() also flags Inf, and x == NA finds nothing.
- “Which values must your test pick out?” Only NaN, from arithmetic that failed: rules out is.na(x), !is.finite(x) and x == NA. Only is.nan() is TRUE for NaN and FALSE for an ordinary NA.
- “Which values must your test pick out?” Anything that is not a finite number: rules out is.na(x), is.nan(x) and x == NA. !is.finite() is TRUE for NA, NaN, Inf and -Inf: every value a sum or a mean cannot use.
Key idea
NA is an unknown value, NaN an impossible result, Inf an overflow or a division by zero, and NULL no value at
all; is.na() finds NA and NaN, is.nan() only NaN, !is.finite() everything that is not an ordinary
number, and is.null() finds NULL.
Checkpoint
Check: NaN, Inf and NULL
Summaries leave missing values out only when you ask
sum(), mean() and max() return NA when their input has a gap, until you add na.rm = TRUE. That default is
the safe one: leaving values out changes the answer, so R makes you ask for it. For a whole table, a row with no
missing value in any column is a complete case: A row of a table with no missing value in any column. complete.cases() marks such rows, and na.omit() keeps only them, so a complete-case analysis uses them alone.: complete.cases() marks them, and
na.omit() keeps only them and records the rows it dropped in an attribute called na.action.
A data frame, which a later lesson covers in full, is a table whose columns are vectors of one length, and
survey$income picks one column. Here is a missingness report for a seeded survey of 200 people:
from session import run, version
version()
# R reads missingness_report.R as if each line were typed at the console.
run("""
R --vanilla --quiet -f missingness_report.R
""") Output
# R version 4.6.1 -- "Happy Hop"
$ R --vanilla --quiet -f missingness_report.R
> # A seeded survey of 200 people, with gaps in every column.
> set.seed(7)
> survey <- data.frame(
+ age = sample(c(18:70, NA), 200, replace = TRUE),
+ income = ifelse(runif(200) < 0.12, NA,
+ round(rlnorm(200, 10, 0.5))),
+ district = sample(c("North", "South", NA), 200,
+ replace = TRUE, prob = c(0.5, 0.45, 0.05))
+ )
> # Missing values per column, as a count and a percentage.
> n_missing <- colSums(is.na(survey))
> n_missing
age income district
3 23 10
> round(100 * n_missing / nrow(survey), 1)
age income district
1.5 11.5 5.0
> # The rows a complete-case analysis would keep.
> sum(complete.cases(survey))
[1] 166
> nrow(na.omit(survey))
[1] 166
> # table() leaves NA out unless you ask for it.
> table(survey$district)
North South
108 82
> table(survey$district, useNA = "ifany")
North South <NA>
108 82 10
> # summary() in R 4.6 counts missing values as NAs,
> # and describes text in more detail.
> summary(survey$income)
Min. 1st Qu. Median Mean 3rd Qu. Max. NAs
6318 15493 21535 25232 30896 101529 23
> summary(survey$district)
Length N.unique N.blank Min.nchar Max.nchar NAs
200 2 0 5 5 10
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 missingness_report_session.py
No column misses more than 11.5 % of its values, yet a complete-case analysis keeps only 166 of the 200 rows and
drops 34, 17 %, because the gaps fall on different rows. Report that number before you analyse what na.omit()
left. table() leaves NA out unless you ask with useNA = "ifany", which shows the 10 people with no district.
New in R 4.6.0: summary() says NAs
R 4.6.0 changed the label of the missing-value count in summary() from NA's to NAs, and gave character vectors
a more informative summary: their length, the number of distinct values and blanks, the lengths of the shortest and
longest strings, and the count of NAs. Output pasted from an older R looks different for that reason only.
Checkpoint
Check: Summaries and complete cases
A filter with NA in its condition adds rows of NA
scores[scores$score > 15, ] should return the students who scored above 15. When some scores are missing, the
condition is NA for them, and the row index of a data frame treats NA the way x[NA] did for a vector in the
indexing lesson: it selects a row whose contents are unknown, and R fills it with NA. Step through the filter:
The drawing shows the whole scene; its 4 steps are listed below.
Every step as text
- The table. scores has five rows, and the scores of rows 2 and 5 are missing.
- scores$score > 15. The condition has one value per row. It is FALSE for 12 and TRUE for 18 and 20, and NA for the two missing scores, because nobody knows whether they are above 15.
- scores[condition, ]. As a row index, TRUE keeps a row and FALSE drops it, but NA selects a row whose contents are unknown. R adds a row of NA for each, named NA and NA.1, so the result has four rows and two of them are made up.
- subset() or which(). subset(scores, score > 15) and scores[which(scores$score > 15), ] keep only the rows where the condition is TRUE, so they return rows 3 and 4 alone.
The same filter, the rows it counts, and the two forms that avoid the rows of NA, typed at the console:
from session import run, version
version()
# R reads na_rows.R as if each line were typed at the console.
run("""
R --vanilla --quiet -f na_rows.R
""") Output
# R version 4.6.1 -- "Happy Hop"
$ R --vanilla --quiet -f na_rows.R
> scores <- data.frame(id = 1:5, score = c(12, NA, 18, 20, NA))
> # The condition is NA where the score is missing ...
> scores$score > 15
[1] FALSE NA TRUE TRUE NA
> # ... and each NA in a row index gives a row of NA.
> scores[scores$score > 15, ]
id score
NA NA NA
3 3 18
4 4 20
NA.1 NA NA
> # nrow() counts the made-up rows as well.
> nrow(scores[scores$score > 15, ])
[1] 4
> # subset() and which() keep only the rows that are TRUE.
> subset(scores, score > 15)
id score
3 3 18
4 4 20
> scores[which(scores$score > 15), ]
id score
3 3 18
4 4 20
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 na_rows_session.py
The result has four rows, two of them, named NA and NA.1, made of nothing. nrow() counts them, 4 rows for 2
real students, and a mean of the column then includes the gaps again. subset() keeps only the rows where its condition is TRUE, which its help page states,
and which() turns the condition into the positions of its TRUE values, so both give the two real rows. Use one of
them whenever the column you filter on can be missing.
Filter with subset() or which() when a column can be missing
subset(df, score > 15) and df[which(df$score > 15), ] both leave out the rows whose condition is NA. If those
rows matter, count them first with sum(is.na(df$score)), so the report says how many could not be checked.
Checkpoint
Check: Rows of NA in filters
Practise: predict, then check
One vector with a missing value and a zero touches every idea above. Note your answers, then run the lines.
Six lines to predict
For x <- c(3, NA, 0, 6), predict sum(x), sum(x, na.rm = TRUE), x / 0, is.na(x / 0), mean(is.na(x)) and
length(c(x, NULL)). Then type them in R and see how many you had right.
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(3, NA, 0, 6) > sum(x) [1] NA > sum(x, na.rm = TRUE) [1] 9 > x / 0 [1] Inf NA NaN Inf > is.na(x / 0) [1] FALSE TRUE TRUE FALSE > mean(is.na(x)) [1] 0.25 > length(c(x, NULL)) [1] 4
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 gap made the plain sum NA; na.rm = TRUE added the other three; dividing by zero gave Inf twice, NA for the
gap and NaN for 0 / 0; is.na() is TRUE for both the NA and the NaN; a quarter of the values are missing;
and the NULL added nothing.
For the questions, the first comes from the idea you know least so far, every answer is explained on the spot, and the level rises with each right answer:
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 is the difference between NA and NULL in R?
NA is a value that is missing: it holds a place in a vector, has a type (NA is logical, and NA_real_,
NA_integer_ and NA_character_ are its numeric, integer and text forms), and counts towards the length, so
length(c(1, NA)) is 2. Calculations with it usually give NA, and is.na() finds it. NULL is no value at all: it
has length 0, it has no type of the six atomic kinds, and inside c() it disappears, so c(1, NULL) is just 1.
is.null() finds it, while is.na(NULL) returns an empty answer. In practice, NA marks a gap in data, such as a
survey answer that was not given, while NULL marks the absence of an object, such as an optional argument that was
not supplied.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
A survey of 2,000 people has 8 questions, and each question is unanswered by about 4 % of people, scattered at random. An analyst runs na.omit() first and reports every result on the rows that remain. About how many rows remain, why is that a problem, and what would you do instead?
-
Fresher to mid
A report counts anaemic patients with nrow(df[df$hb < 11, ]) and gets 148, while a colleague's nrow(subset(df, hb < 11)) gives 131 on the same data, in which some haemoglobin values are missing. Explain the difference, say which count is right, and what the report should say.
Key takeaways
NAis an unknown value with a type; most calculations with it returnNA, except when the answer cannot depend on it, as withNA^0andNA & FALSE, andis.na()is the only reliable test.NaNcomes from arithmetic with no answer and counts as missing foris.na(),Inffrom overflow or division by zero, andNULLis no value at all, of length 0;max()of nothing is-Infwith a warning.- Summaries return
NAuntil you addna.rm = TRUE;complete.cases()andna.omit()work on whole rows, and a complete-case analysis can drop many more rows than any one column misses. - Report missing values:
sum(is.na(x))and its share,table(x, useNA = "ifany"), and theNAscount ofsummary(), which R 4.6.0 relabelled fromNA's. - Filtering a data frame with a condition that is
NAadds rows ofNA;subset()andwhich()keep only the rows where the condition isTRUE.
Terms in this lesson
- complete case
- A row of a table with no missing value in any column. complete.cases() marks such rows, and na.omit() keeps only them, so a complete-case analysis uses them alone.
References
- 'Not Available' / Missing Values (NA) (The R Core Team)
- Finite, Infinite and NaN Numbers (is.finite) (The R Core Team)
- The Null Object (NULL) (The R Core Team)
- Arithmetic Operators (Arithmetic: 1 ^ y and y ^ 0) (The R Core Team)
- Maxima and Minima (Extremes) (The R Core Team)
- Find Complete Cases (complete.cases) (The R Core Team)
- Handle Missing Values in Objects (na.omit) (The R Core Team)
- Cross Tabulation and Table Creation (table) (The R Core Team)
- Object Summaries (summary) (The R Core Team)
- Subsetting Vectors, Matrices and Data Frames (subset) (The R Core Team)
- Count the Number of Characters (nchar) (The R Core Team)
- R Language Definition: NA handling (section 3.3.4) (The R Core Team)
- R News: changes in R 4.6.0 (summary() labels NAs) (The R Core Team)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress