Missing Data (NA)
Reviewed & published by Brayan K
NA is R's special marker for a missing or unknown value, and because it propagates through calculations you must handle it explicitly with tools like is.na() and na.rm = TRUE.
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.
This lesson shows how NA spreads through arithmetic, why you must test it with is.na() rather than ==, how na.rm and na.omit() let you compute and clean around it, and how NaN and Inf fit into the picture.
What You'll Learn in This Lesson
- How NA propagates through arithmetic and why
- Testing for missing values correctly with is.na()
- Using na.rm = TRUE in sum(), mean(), and friends
- Removing NAs with na.omit() and is.na() filtering
- Flagging complete records with complete.cases()
- How NaN and Inf differ from NA
1️⃣ How NA Propagates
Any calculation that touches an NA produces NA — this is intentional, so you never accidentally report a total that quietly ignored missing data. The big trap: you cannot test for NA with ==, because NA == NA is itself NA. Always use is.na().
# NA means "missing/unknown". Any arithmetic touching NA gives NA.
x <- c(10, NA, 30, 40)
sum(x) # one NA poisons the whole sum -> [1] NA
mean(x) # ...and the mean -> [1] NA
x + 5 # -> [1] 15 NA 35 45 (NA stays NA)
# You cannot test for NA with == ; it returns NA, not TRUE/FALSE!
NA == NA # -> [1] NA
is.na(x) # the CORRECT test -> [1] FALSE TRUE FALSE FALSE2️⃣ Computing Around NA with na.rm
Most summary functions accept na.rm = TRUE, which strips the NAs before computing. Note that mean(x, na.rm = TRUE) divides by the count of present values, not the original length. To count how many are missing, sum a logical: sum(is.na(x)).
# na.rm = TRUE tells a summary function to ignore the NAs first.
x <- c(10, NA, 30, 40)
sum(x, na.rm = TRUE) # 10 + 30 + 40 -> [1] 80
mean(x, na.rm = TRUE) # 80 / 3 (NA dropped) -> [1] 26.66667
max(x, na.rm = TRUE) # -> [1] 40
# Count how many values are missing
sum(is.na(x)) # is.na gives TRUE/FALSE; sum counts TRUEs -> [1] 13️⃣ Removing & Flagging Missing Values
To produce a cleaned vector, filter with x[!is.na(x)] or call na.omit(x). To flag which entries are complete (no NA), complete.cases() returns a logical vector — the same idea scales to data-frame rows.
# Two ways to drop missing values from a vector.
x <- c(10, NA, 30, NA, 50)
x[!is.na(x)] # keep where NOT missing -> [1] 10 30 50
na.omit(x) # same values (with an "attr" footnote) -> [1] 10 30 50
# complete.cases() flags rows/elements with no NA — great for tables.
complete.cases(x) # -> [1] TRUE FALSE TRUE FALSE TRUE4️⃣ NaN and Inf
NaN ("Not a Number") comes from undefined math like 0/0 and counts as NA. Inf and -Inf are infinity from things like 1/0 — they are real numbers, not missing, so is.na(Inf) is FALSE.
# NA is not the only "non-number". Know the family:
0 / 0 # undefined -> [1] NaN (Not a Number)
1 / 0 # -> [1] Inf (positive infinity)
-1 / 0 # -> [1] -Inf
is.nan(0/0) # -> [1] TRUE
is.infinite(1/0) # -> [1] TRUE
is.na(NaN) # NaN counts as NA too! -> [1] TRUEYour turn. Fill in the ___ blanks, run it, and compare with the expected output.
# 🎯 YOUR TURN — replace each ___ then run it.
scores <- c(88, NA, 92, 75, NA, 100)
# 1) Count how many scores are missing
print(sum(is.na(___))) # 👉 replace ___ with scores
# 2) Average the present scores (ignore the NAs)
print(mean(scores, ___ = TRUE)) # 👉 replace ___ with na.rm
# 3) Print just the non-missing scores using is.na()
print(scores[!is.na(scores)])
# ✅ Expected output:
# [1] 2
# [1] 88.75
# [1] 88 92 75 100Mini-Challenge: Clean Sensor Data
A realistic cleaning task: missing readings plus an undefined 0/0. Notice that is.na() catches both the NA and the NaN — that's exactly the behaviour you want when scrubbing data.
# 🎯 MINI-CHALLENGE: clean a sensor reading vector.
#
# readings <- c(21.5, NA, 22.1, 0/0, 23.4, NA, 22.8)
#
# 1. Report how many values are missing (is.na also catches the NaN).
# 2. Print the mean temperature, ignoring missing values.
# 3. Print the cleaned vector with all NA/NaN removed.
# 4. Print the proportion missing with mean(is.na(readings)).
#
# ✅ Example output:
# [1] 3
# [1] 22.45
# [1] 21.5 22.1 23.4 22.8
# [1] 0.4285714
# your code hereCommon Errors (and the fix)
- ❌ x == NA to find missing values (always returns NA). ✅ Use is.na(x).
- ❌ Forgetting na.rm = TRUE and getting NA from mean(x). ✅ Add the argument explicitly.
- ❌ Assuming Inf is missing. ✅ It's a valid number — test with is.infinite(), not is.na().
- ❌ Counting NAs with length(is.na(x)) (gives the vector length). ✅ Use sum(is.na(x)).
Pro Tips
- 💡 Proportion missing: mean(is.na(x)) gives the fraction of NAs (TRUE counts as 1).
- 💡 Replace NAs: x[is.na(x)] <- 0 swaps every missing value for 0 in place.
- 💡 anyNA() is fast: anyNA(x) tells you instantly whether any value is missing.
📋 Quick Reference — Missing Data
| Task | R Syntax |
|---|---|
| Test for missing | is.na(x) |
| Count missing | sum(is.na(x)) |
| Mean ignoring NA | mean(x, na.rm = TRUE) |
| Drop missing | x[!is.na(x)] |
| Complete records | complete.cases(x) |
| Test for NaN / Inf | is.nan(x); is.infinite(x) |
🎉 Lesson Complete!
- ✅ NA propagates — any calculation touching it returns NA
- ✅ Test missing with is.na(), never with ==
- ✅ na.rm = TRUE lets summaries skip the missing values
- ✅ Drop NAs with na.omit() or x[!is.na(x)]; flag with complete.cases()
Practice quiz
What does sum(c(10, NA, 30)) return?
- 40
- NA
- 0
- An error
Answer: NA. A single NA poisons the whole sum, so the result is NA.
Why can't you test for missing with x == NA?
- It is too slow
- It only checks the first value
- It deletes the value
- It returns NA, never TRUE/FALSE
Answer: It returns NA, never TRUE/FALSE. Comparing to an unknown is itself unknown, so == NA returns NA.
Which function correctly tests for missing values?
- is.na(x)
- missing(x)
- x == NA
- isNA(x)
Answer: is.na(x). is.na(x) returns a proper logical vector marking each missing slot.
What does mean(x, na.rm = TRUE) do?
- Replaces NAs with 0
- Returns NA
- Counts the NAs
- Averages only the present values
Answer: Averages only the present values. na.rm = TRUE strips NAs first, then divides by the count of present values.
How do you count how many values are missing?
- sum(is.na(x))
- length(is.na(x))
- count(NA)
- na.count(x)
Answer: sum(is.na(x)). is.na gives TRUE/FALSE and sum() counts the TRUEs.
Which expression keeps only the non-missing values?
- x[is.na(x)]
- x == NA
- x[!is.na(x)]
- drop(x)
Answer: x[!is.na(x)]. x[!is.na(x)] keeps the elements where NOT missing.
What does 0/0 produce in R?
- NaN
- 0
- Inf
- NA
Answer: NaN. 0/0 is undefined and yields NaN ('Not a Number').
Is is.na(Inf) TRUE or FALSE?
- TRUE
- FALSE
- NA
- It errors
Answer: FALSE. Inf is a valid (extreme) number, not missing, so is.na(Inf) is FALSE.
Is is.na(NaN) TRUE or FALSE?
- FALSE
- NA
- It errors
- TRUE
Answer: TRUE. NaN counts as a special kind of NA, so is.na(NaN) is TRUE.
What does mean(is.na(x)) give you?
- The count of missing
- The proportion of values that are missing
- The mean ignoring NA
- Always 0
Answer: The proportion of values that are missing. TRUE counts as 1, so the mean of is.na(x) is the fraction missing.
Continue this course
- Previous: Subsetting & Indexing ([ ], [[ ]], $)
- Next: Vectorization vs Loops — Write fast idiomatic R with vectorized operations, recycling and ifelse
- Quick reference: R cheat sheet
Frequently asked questions
Why does sum() return NA when my data has missing values?
By design, R treats NA as 'unknown', and the sum of known plus unknown is unknown — so a single NA makes the whole result NA. This is a safety feature: it stops you from silently reporting a total that ignored missing data without you realising. To compute over the non-missing values instead, add na.rm = TRUE: sum(x, na.rm = TRUE). The same argument works on mean(), max(), min(), sd(), var(), and median().
Why can't I test for NA with x == NA?
Because comparing anything to an unknown value is itself unknown, x == NA returns NA, not TRUE or FALSE — so it never works as a test and can quietly break your filtering. The correct test is the dedicated function is.na(x), which returns a proper logical vector marking each missing slot. Use it everywhere you would otherwise be tempted to write == NA, for example x[!is.na(x)] to drop missing values.
What is the difference between NA, NaN, and Inf?
NA is a missing or unknown value of any type. NaN ('Not a Number') is a specifically numeric undefined result, such as 0/0, and importantly is.na(NaN) is TRUE because NaN is considered a special kind of NA. Inf and -Inf are positive and negative infinity, produced by things like 1/0, and they are NOT missing — they are valid (if extreme) numbers, so is.na(Inf) is FALSE. Test them with is.nan() and is.infinite() respectively.
Should I use na.omit() or na.rm = TRUE?
Use na.rm = TRUE when you want a summary function to skip missing values for a single calculation while keeping the original data intact. Use na.omit() (or x[!is.na(x)]) when you want to actually produce a new, shorter object with the missing entries removed — for example before plotting, or to feed a function that cannot tolerate NA. For data frames, na.omit() drops any entire row containing an NA, which is the same idea as keeping only complete.cases().