R PROGRAMMING • SYNTAX AND CORE TYPES

NA, NaN & Inf — Work with missing values (NA) and NaN/Inf

Master R's sentinel values for missing data, undefined arithmetic, and infinite quantities to write robust analytical code.

Historical Context & Motivation

Every real-world dataset contains gaps — sensors fail, respondents skip survey questions, and database joins produce unmatched rows. Before statistical computing languages formalized the concept, analysts resorted to ad-hoc placeholders such as -999 or empty strings to represent missing information, a fragile practice that routinely corrupted calculations. The need for a first-class sentinel value — a value that propagates correctly through arithmetic, comparison, and aggregation — drove language designers to build missingness directly into their type systems. R's NA (Not Available) was one of the earliest and most influential implementations, emerging from the S language lineage at Bell Labs. Meanwhile, the IEEE 754 floating-point standard independently addressed undefined arithmetic with NaN (Not a Number) and Inf (Infinity), giving hardware-level semantics to operations like 0/0 and 1/0.

1976
S Language at Bell Labs
John Chambers and colleagues at Bell Labs begin developing the S language, introducing the concept of NA as a first-class missing-value indicator for statistical computation — a paradigm shift from ad-hoc sentinel numbers.
1985
IEEE 754 Standard Ratified
The IEEE 754 standard for floating-point arithmetic formally defines NaN and ±Inf, giving processors a universal way to represent undefined results and overflow conditions at the hardware level.
1993
R Language Created
Ross Ihaka and Robert Gentleman create R at the University of Auckland, inheriting S's NA semantics and layering IEEE 754 NaN/Inf support into R's numeric type to produce a unified missing-data framework.
2000
R 1.0.0 Released
The first stable release of R codifies typed NA variants (NA_integer_, NA_real_, NA_complex_, NA_character_), ensuring type safety across all atomic vector classes.
2010s
Tidyverse & Modern NA Handling
The tidyverse ecosystem (dplyr, tidyr) standardizes functions like drop_na(), replace_na(), and fill(), making NA management idiomatic in data-wrangling pipelines.

Understanding how R distinguishes between statistical missingness (NA), mathematically undefined results (NaN), and infinite quantities (Inf) is essential for writing correct data pipelines. The central question this lesson addresses is: how do these three sentinel values behave under arithmetic, comparison, and aggregation, and what strategies exist for detecting, propagating, and resolving them in production R code?

Core Principles & Definitions

R's type system embeds three distinct categories of non-standard values, each serving a different semantic role. While beginners often conflate them, they arise from fundamentally different situations and propagate through computations according to distinct rules. Mastering these distinctions is the foundation of defensive R programming.

1

NA — Not Available

Represents a missing observation — a value that exists in theory but is unknown. NA is a logical constant that R coerces into the appropriate type (integer, double, character, complex). Any arithmetic or comparison involving NA yields NA, enforcing the principle that unknown inputs produce unknown outputs.
2

NaN — Not a Number

Signals a mathematically undefined result such as 0/0 or √(−1) in real arithmetic. NaN is a special IEEE 754 double value. It is technically also NA in R (is.na(NaN) returns TRUE), but the converse is not true — NA is not NaN.
3

Inf / −Inf — Infinity

Represents positive or negative overflow in floating-point arithmetic, such as 1/0 or log(0). Inf participates normally in comparisons (Inf > 1e308 is TRUE) and propagates through arithmetic, but is neither NA nor NaN.
4

NULL — The Absent Object

Often confused with NA, NULL represents the absence of an entire object, not a missing element within one. NULL has length zero and disappears when combined with vectors, whereas NA occupies a slot and preserves vector length.
KEY TAKEAWAY
Think of NA like a sealed envelope in a mailbox: the slot is occupied, but you cannot read the letter inside. NaN is a returned envelope marked 'address does not exist' — the delivery itself failed. Inf is a letter that traveled so far it crossed the horizon. And NULL is a mailbox that was never installed. Each represents a different failure mode, and R provides dedicated predicates — is.na(), is.nan(), is.finite(), is.infinite(), is.null() — so you can detect each one precisely.

Visual Taxonomy of Sentinel Values

The Venn-style diagram shows that NaN is a strict subset of NA — calling is.na(NaN) returns TRUE, but is.nan(NA) returns FALSE. Inf and NULL sit entirely outside the NA region and require their own detection functions.

The diagram above captures the most critical insight for working with R's sentinel values: the subset relationship between NaN and NA. Because NaN is technically a special case of NA (it is a value that is 'not available' because the arithmetic was undefined), is.na() catches both NA and NaN. If you need to distinguish them, you must test with is.nan() first. Inf and NULL live entirely outside this hierarchy: is.na(Inf) returns FALSE, and is.na(NULL) returns logical(0) (a zero-length logical vector), not TRUE or FALSE.

How Sentinel Values Propagate

Understanding the propagation rules is what separates beginner R users from those who can debug data pipelines efficiently. Each sentinel value follows its own algebraic contract when it encounters arithmetic operators, logical operators, and aggregation functions.

NA Propagation — The 'Unknown' Algebra

R treats NA as an unknown value. The propagation rule follows directly from three-valued logic: if the result of an expression depends on the unknown operand, the result is also unknown (NA). If the result is determined regardless of the unknown operand, R returns the known result. This is why TRUE | NA yields TRUE (because TRUE OR anything is TRUE), but FALSE | NA yields NA (the result depends on the unknown).

NA ARITHMETIC PROPAGATION
NA + x = NA, NA × x = NA, NA > x = NA
For any value x, arithmetic and relational operations with NA yield NA. The only exceptions arise from logical short-circuit evaluation.

NaN Propagation — IEEE 754 Rules

NaN IDENTITY
NaN ≠ NaN (NaN == NaN returns NA in R)
A defining property of IEEE 754 NaN is that it is not equal to itself. In R, NaN == NaN returns NA (since NaN is also NA), reinforcing that you should never test for NaN via ==.

Inf Propagation — Extended Real Arithmetic

INF ARITHMETIC
Inf + x = Inf, Inf × (−1) = −Inf, Inf − Inf = NaN, Inf / Inf = NaN
Inf behaves like a real number at the limit: it absorbs finite additions and multiplications, but indeterminate forms like ∞ − ∞ and ∞/∞ collapse to NaN.
⚠️ The na.rm Parameter
Most aggregation functions in R (sum(), mean(), max(), etc.) accept na.rm = TRUE to strip NA and NaN values before computing. Without it, a single NA in a 10-million-element vector will cause mean() to return NA. Note that na.rm = TRUE does not remove Inf — you need is.finite() filtering for that.

Detection Functions & Truth Table

R provides five primary predicate functions for testing sentinel values. Because of the subset relationship between NaN and NA, the return values of these predicates form a non-trivial truth table that every R programmer should have committed to memory. The table below is the single most referenced resource in this lesson.

Detection predicate truth table for R sentinel values
Test \ ValueNANaNInf−InfNULL42
is.na()TRUETRUEFALSEFALSElogical(0)FALSE
is.nan()FALSETRUEFALSEFALSElogical(0)FALSE
is.finite()FALSEFALSEFALSEFALSElogical(0)TRUE
is.infinite()FALSEFALSETRUETRUElogical(0)FALSE
is.null()FALSEFALSEFALSEFALSETRUEFALSE
The recommended detection order: test for NULL first (since it has length 0), then is.nan() (most specific NA subtype), then is.na() (catches remaining pure NAs), then is.infinite(). If all tests fail, the value is an ordinary finite number.

A critical subtlety shown in the flowchart is that the order of tests matters. If you test is.na() before is.nan(), you will classify NaN values as plain NA and lose the information that they arose from undefined arithmetic. Similarly, an is.finite() call is the strongest filter: it returns TRUE only for values that are not NA, not NaN, and not ±Inf, making it the ideal guard for arithmetic operations that require well-defined numeric inputs.

Worked Example — Cleaning a Sensor Data Vector

Suppose you receive a numeric vector of temperature readings from an IoT sensor array, and the data contains missing readings, division-by-zero artifacts from a calibration formula, and overflow values. Your task is to compute the mean temperature of valid readings and report counts of each sentinel type.

Cleaning Sensor Data and Computing a Robust Mean
1
Step 1 — Inspect the Raw DataCreate the vector containing a realistic mixture of values: temps <- c(22.1, NA, 23.4, NaN, Inf, 21.8, NA, -Inf, 24.0, 0/0, 1/0, 22.5) Call print(temps) to see: 22.1 NA 23.4 NaN Inf 21.8 NA -Inf 24.0 NaN Inf 22.5. Note that 0/0 became NaN and 1/0 became Inf at construction time.
Vector has 12 elements with NA, NaN, Inf, and −Inf mixed in.
2
Step 2 — Count Each Sentinel TypeUse vectorized predicates to count each category: n_na_pure <- sum(is.na(temps) & !is.nan(temps)) → 2 (pure NAs) n_nan <- sum(is.nan(temps)) → 2 (NaN values) n_inf <- sum(is.infinite(temps)) → 3 (two Inf + one −Inf) n_finite <- sum(is.finite(temps)) → 5 (valid readings)
2 pure NA, 2 NaN, 3 Inf/−Inf, 5 finite values. Total: 12 ✓
3
Step 3 — Filter to Valid ReadingsUse is.finite() as a single guard to exclude all non-standard values: valid <- temps[is.finite(temps)] This produces c(22.1, 23.4, 21.8, 24.0, 22.5) — a clean vector of five readings. Note that is.finite() simultaneously excludes NA, NaN, and ±Inf, making it the most convenient one-function filter for numeric data.
valid = c(22.1, 23.4, 21.8, 24.0, 22.5)
4
Step 4 — Compute the MeanApply mean() to the clean vector: mean(valid) Alternatively, you can skip the filtering step and use mean(temps[is.finite(temps)]) in a single expression. Be aware that mean(temps, na.rm = TRUE) would return Inf because it removes NA and NaN but not ±Inf.
mean(valid) = (22.1 + 23.4 + 21.8 + 24.0 + 22.5) / 5 = 22.76
5
Step 5 — Verify with na.rm Pitfall DemonstrationRun mean(temps, na.rm = TRUE) to confirm the pitfall: ## [1] Inf Because Inf is a valid numeric value (not NA), na.rm does not remove it. The sum becomes Inf, and Inf/n = Inf. This is a common bug in production pipelines.
Lesson: na.rm = TRUE is insufficient when Inf values are possible; use is.finite() filtering instead.

Comparing NA, NaN, Inf, and NULL

While the previous sections explored each sentinel value individually, production code often requires choosing the right strategy for handling each one. The following comparison table summarizes how each value behaves across common operations, giving you a quick reference for decision-making in data-cleaning workflows.

Behavioral comparison of R sentinel values
PropertyNANaNInfNULL
Semantic meaningUnknown / missingUndefined arithmeticOverflow / limitAbsent object
Has length?Yes (length 1)Yes (length 1)Yes (length 1)No (length 0)
Stored typelogical (coerced)doubledoubleNULL type
Removed by na.rm?YesYesNoN/A (length 0)
Participates in ==Returns NAReturns NATRUE if Inf==InfError / unexpected
In c() combinationPreserved in vectorPreserved in vectorPreserved in vectorSilently dropped
Typical sourceMissing data, joins0/0, sqrt(-1)1/0, exp(1e308)Empty list slots
KEY TAKEAWAY
In software engineering terms, think of these four values as different HTTP status codes. NA is a 204 No Content — the server slot exists but returned nothing. NaN is a 422 Unprocessable Entity — your request was syntactically valid but semantically nonsensical. Inf is a 413 Payload Too Large — the result exceeded representable bounds. NULL is a 404 Not Found — the endpoint itself doesn't exist. Each requires a different error-handling strategy, and conflating them will produce silent bugs.

Connection to Advanced Data Handling

The basic NA/NaN/Inf semantics covered so far form the foundation for more sophisticated missing-data strategies used in real-world data science and statistical modeling. Understanding how R's sentinel values map to advanced concepts prepares you for graduate-level coursework and production analytics.

From basic sentinel handling to advanced patterns
Basic ConceptAdvanced ExtensionContext
na.rm = TRUEMultiple imputation (mice package)Instead of discarding NA values, impute plausible values from the observed data distribution to preserve statistical power.
is.na() filteringMissingness mechanism analysis (MCAR/MAR/MNAR)Determine whether data is Missing Completely At Random, Missing At Random, or Missing Not At Random to select valid inference methods.
Inf in log-likelihoodLog-sum-exp trick for numerical stabilityAvoid Inf overflow in probabilistic models by reformulating sums in log space with offset subtraction.
Typed NA variantsvctrs package and type-stable tidyverse pipelinesThe vctrs package enforces strict type coercion rules, using typed NAs to prevent silent type promotion in column operations.
NaN detectionGradient debugging in ML optimizationNaN gradients in neural network training signal exploding weights; detecting NaN early enables gradient clipping or learning rate reduction.

As you progress into courses on statistical learning, Bayesian inference, or machine learning engineering, you will encounter increasingly nuanced scenarios where the distinction between NA and NaN has practical consequences — for example, a Gibbs sampler that produces NaN indicates a bug in the proposal distribution, whereas NA outputs might legitimately represent censored observations. Building a strong mental model of R's sentinel value hierarchy now will save you significant debugging time in those advanced contexts.

Practice Problems

PROBLEM 1CONCEPTUAL
Explain why is.na(NaN) returns TRUE but is.nan(NA) returns FALSE. What design principle in R's type system does this asymmetry reflect?
PROBLEM 2BASIC CALCULATION
Given x <- c(5, NA, 10, Inf, 3), predict the output of each expression: (a) sum(x), (b) sum(x, na.rm = TRUE), (c) sum(x[is.finite(x)]).
PROBLEM 3INTERMEDIATE
Write a function classify_values(x) that takes a numeric vector and returns a named list with counts of finite values, pure NAs (not NaN), NaN values, positive Inf, and negative Inf. Demonstrate your function on c(1, NA, NaN, Inf, -Inf, 2, 0/0, NA, 1/0).
PROBLEM 4APPLIED
You are building a data pipeline that reads daily stock returns from a CSV file. Some entries are blank (read as NA), some contain '#DIV/0!' (which R may parse as NaN or NA_character_ depending on column type), and extreme leverage events produce returns exceeding 1e308 (Inf). Write R code that: (1) reads the CSV with read.csv(), (2) converts the returns column to numeric, (3) logs the count of each sentinel type for an audit trail, and (4) computes the trimmed mean of valid returns.
PROBLEM 5CRITICAL THINKING
R's NA propagation rule states that NA & FALSE returns FALSE (not NA), while NA & TRUE returns NA. Similarly, NA | TRUE returns TRUE, while NA | FALSE returns NA. Prove that these results are logically consistent by reasoning about what value the unknown operand would need to take, and then argue whether R should or should not apply the same short-circuit logic to NA * 0 (which currently returns NA rather than 0).

Lesson Summary

R provides three distinct sentinel values for non-standard data: NA (Not Available) represents a missing observation that could be of any type, NaN (Not a Number) signals mathematically undefined results like 0/0, and Inf (Infinity) represents overflow from operations like 1/0. The critical structural insight is that NaN ⊂ NA: every NaN is also NA, but the converse is false. This means is.na() catches both NA and NaN, while is.nan() catches only NaN. NULL is fundamentally different — it represents an absent object with length zero and is detected by is.null().

For robust data cleaning, is.finite() is the strongest single filter — it returns TRUE only for values that are not NA, not NaN, and not ±Inf. The common na.rm = TRUE pitfall is that it removes NA and NaN but leaves Inf values intact, which can silently corrupt aggregation results. When testing sentinel types, always check is.nan() before is.na() to preserve the distinction between undefined arithmetic and genuinely missing data. These fundamentals connect forward to advanced topics like multiple imputation, numerical stability techniques, and type-safe pipeline design with vctrs.

Varsity Tutors • R Programming • NA, NaN & Inf — Work with missing values (NA) and NaN/Inf