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

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 FALSE

2️⃣ 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] 1

3️⃣ 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  TRUE

4️⃣ 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] TRUE

Your 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 100

Mini-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 here

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Missing Data

TaskR Syntax
Test for missingis.na(x)
Count missingsum(is.na(x))
Mean ignoring NAmean(x, na.rm = TRUE)
Drop missingx[!is.na(x)]
Complete recordscomplete.cases(x)
Test for NaN / Infis.nan(x); is.infinite(x)

🎉 Lesson Complete!

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

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().