NaN-safe Functions
Handle missing data (np.nan) correctly — why one NaN poisons sum and mean, and how nansum, nanmean, nanmax and friends ignore it.
Real data has gaps. A match is washed out by rain, a batter is injured, a scorecard is lost. We can't put 0 there — 0 would mean "he batted and got a duck", which drags his average down unfairly. NumPy's marker for "no value" is np.nan. The catch: normal aggregates don't skip it.
Definition
np.nan("Not a Number") is a special floating-point value that represents missing or undefined data. Any arithmetic involving NaN produces NaN, sosum,mean,maxetc. returnnanif even one value is missing. The NaN-safe functions (np.nansum,np.nanmean,np.nanmax, …) do the same job but ignore the NaN values.
- Floating-point — NaN only exists in float arrays. An
intarray cannot hold NaN; puttingnp.nanin a list of ints makes the whole arrayfloat64. - Poisons —
5 + nan = nan, so a single NaN turns the total into NaN. - Ignore —
nanmeanuses only the real values: it divides by the number of non-NaN values, not the full length. - NaN ≠ NaN —
np.nan == np.nanisFalse! Always test withnp.isnan().
Syntax
python
np.nansum(a, axis=None) np.nanprod(a, axis=None)
np.nanmean(a, axis=None) np.nanmedian(a, axis=None)
np.nanstd(a, axis=None, ddof=0) np.nanvar(a, axis=None, ddof=0)
np.nanmin(a, axis=None) np.nanmax(a, axis=None)
np.nanargmin(a, axis=None) np.nanargmax(a, axis=None)
np.nanpercentile(a, q, axis=None) np.nanquantile(a, q, axis=None)
np.nancumsum(a, axis=None) np.nancumprod(a, axis=None)
np.isnan(a) # boolean mask: True where the value is NaN| Parameter | Meaning | Default |
|---|---|---|
a | input array (float, possibly containing NaN) | — required |
axis | None / 0 / 1, same as the normal versions | None |
ddof | (nanstd/nanvar) divisor is count_of_non_nan - ddof | 0 |
q | (nanpercentile/nanquantile) the percentile(s) / quantile(s) | — required |
Note: the NaN-safe versions are function-only — there is no
a.nanmean()method.
Example — one washed-out match
python
import numpy as np
hardik = np.array([45, 32, np.nan, 67, 18]) # match 3 washed out
print("array :", hardik, hardik.dtype)
print("sum :", hardik.sum())
print("mean :", hardik.mean())
print("nansum :", np.nansum(hardik))
print("nanmean :", np.nanmean(hardik))
print("nanmax :", np.nanmax(hardik))
print("nanmedian:", np.nanmedian(hardik))text
array : [45. 32. nan 67. 18.] float64
sum : nan
mean : nan
nansum : 162.0
nanmean : 40.5
nanmax : 67.0
nanmedian: 38.5How it works: the plain sum/mean see the NaN and give up. nansum adds the 4 real scores (162). nanmean divides by 4 (real values), not 5: 162 / 4 = 40.5.
Example — find, count and replace NaN
python
import numpy as np
hardik = np.array([45, 32, np.nan, 67, 18])
print("nan == nan ?", np.nan == np.nan)
mask = np.isnan(hardik)
print("mask :", mask)
print("missing count:", mask.sum())
print("played count :", (~mask).sum())
print("real scores :", hardik[~mask])
print("nan -> 0 :", np.nan_to_num(hardik, nan=0.0))
print("nan -> mean :", np.where(mask, np.nanmean(hardik), hardik))text
nan == nan ? False
mask : [False False True False False]
missing count: 1
played count : 4
real scores : [45. 32. 67. 18.]
nan -> 0 : [45. 32. 0. 67. 18.]
nan -> mean : [45. 32. 40.5 67. 18. ]How it works: np.isnan gives a boolean mask. mask.sum() counts Trues. ~mask flips it to select the real values. np.nan_to_num replaces NaN with a number you choose, and np.where fills each gap with the player's average (a common "imputation" trick).
Example — NaN in a 2D table
python
import numpy as np
scores = np.array([
[72, 45, 88, np.nan, 64],
[56, 0, 74, 88, 32],
[45, 32, np.nan, 67, 18],
])
print("mean per player :", scores.mean(axis=1))
print("nanmean per player:", np.nanmean(scores, axis=1).round(2))
print("matches played :", (~np.isnan(scores)).sum(axis=1))
print("best match index :", np.nanargmax(scores, axis=1))text
mean per player : [nan 50. nan]
nanmean per player: [67.25 50. 40.5 ]
matches played : [4 5 4]
best match index : [2 3 3]Common mistakes
- ❌ Filling missing matches with 0. ✅ 0 means "a duck"; use
np.nanfor "did not play" and the nan-functions to ignore it. - ❌
if x == np.nan:— alwaysFalse. ✅np.isnan(x). - ❌
np.array([1, 2, 3])thena[0] = np.nan→ValueError: cannot convert float NaN to integer. ✅ Make the array float first:a = a.astype(float). - ❌ Calling
np.nanmeanon a row that is all NaN — you getnanand aRuntimeWarning: Mean of empty slice. ✅ Checknp.isnan(row).all()first. - ❌ Using
len(a)as the count when there are NaNs. ✅np.count_nonzero(~np.isnan(a)).
Comparison — normal vs NaN-safe
| Normal | NaN-safe | With [10, nan, 20] normal → | NaN-safe → |
|---|---|---|---|
np.sum | np.nansum | nan | 30.0 |
np.mean | np.nanmean | nan | 15.0 |
np.max | np.nanmax | nan | 20.0 |
np.argmax | np.nanargmax | 1 (the NaN!) | 2 |
np.std | np.nanstd | nan | 5.0 |
np.cumsum | np.nancumsum | [10, nan, nan] | [10, 10, 30] (NaN treated as 0) |
Interview tip
Q: Why does np.nan == np.nan return False, and how do you test for NaN?
A: By the IEEE-754 floating-point standard, NaN is not equal to anything, including itself, because "unknown" cannot be compared. Use np.isnan(x) (or math.isnan for one Python float). Pandas' isna() is built on the same idea.
Key points to remember
- np.nan marks missing data; it only exists in float arrays.
- Any arithmetic with NaN gives NaN, so normal sum/mean/max return nan if one value is missing.
- nansum, nanmean, nanmedian, nanstd, nanvar, nanmin, nanmax, nanargmin, nanargmax, nanpercentile and nancumsum ignore NaN.
- nanmean divides by the count of non-NaN values, not the full length.
- np.nan == np.nan is False — always test with np.isnan().
- np.isnan(a).sum() counts missing values; a[~np.isnan(a)] keeps only real ones.
- np.nan_to_num or np.where(np.isnan(a), fill, a) replaces NaN with a value.
Try it yourself
Normal vs NaN-safe aggregates
Count and fill missing matches
NaN in a 2D squad table
Practice: fair averages with rain-outs
Revision Flashcards
Tap a card to flip it and see the answer.
