NumPy·Lesson 19·10 min·0/4 exercises

NaN, inf and missing values

Why one gap poisons a whole sum, and why `== np.nan` never finds anything.

np.nannp.isnannp.nanmeannp.nansumnp.isfinitenp.nan_to_num

Watch it happen

Play it through, or step back and forth yourself.

x
0
1
2
3
4
5
3.0
nan
7.5
2.0
nan
9.0
shape (6,)dtype float64

np.nan is how a float array says "I don't know". A sensor dropped out, a form wasn't filled in, a division didn't work. It's a float, so an integer array can't hold one — reading data with gaps always gives you floats.

The idea

np.nan — "not a number" — is how a float array says I don't know. A sensor dropped a reading, a field was blank, a division didn't work.

It's a float, always. An integer array cannot hold one, which is why loading a CSV with gaps hands you float columns whether you wanted them or not.

It spreads, and that's correct

x = np.array([3.0, np.nan, 7.5])
x.sum()      # nan
x.mean()     # nan
x.max()      # nan

Not a bug. If one value is genuinely unknown then the true total is genuinely unknown, and NumPy refuses to guess. The alternative — silently pretending the gap is a zero — would give you a confidently wrong answer, which is worse.

Hand-drawn notes showing one nan turning the whole mean into nan, while nanmean ignores the gap and returns a number.

The equality trap

Two unknowns aren't equal to each other, so:

np.nan == np.nan    # False
x == np.nan         # all False — finds nothing, raises nothing

That's the dangerous one: you write a filter, it silently matches zero rows, and nothing tells you. Use the function that asks the right question:

np.isnan(x)              # True where the gaps are
x[~np.isnan(x)]          # keep only the real values
np.isnan(x).sum()        # how many are missing
np.isnan(m).any(axis=1)  # which rows have any gap at all
Hand-drawn notes showing that nan is not equal to itself, so comparing with double equals finds nothing and np.isnan is the only way to locate gaps.

The nan-functions

Rather than masking every time, most reductions have a gap-skipping twin:

np.nansum(x)     np.nanmean(x)    np.nanstd(x)
np.nanmax(x)     np.nanmin(x)     np.nanmedian(x)

np.nanmean divides by the number of real values, so it matches what you'd get by dropping the gaps first. Worth being deliberate about that: skipping a missing value and treating it as zero are different claims about your data, and only one of them is usually true.

Filling gaps

x[np.isnan(x)] = 0                  # explicit
np.nan_to_num(x)                    # nan->0, inf->a huge finite number
np.nan_to_num(x, nan=np.nanmean(x)) # fill with the mean instead

inf is a different thing

np.inf isn't unknown — it's unbounded. Dividing by zero in an array produces it with a RuntimeWarning rather than raising:

np.array([1.0]) / 0    # [inf]  — with a warning
np.array([0.0]) / 0    # [nan]  — 0/0 really is undefined

Before feeding data to anything that will choke on it, the check you usually want catches both problems at once:

np.isfinite(x).all()      # True only if no nan and no inf
x[np.isfinite(x)]         # keep only usable values

Practice

Write it yourself. The answer is there when you want it.

Putting the kettle on…

Starting up…

Write it yourself

not graded

Print x, then x.mean() and np.nanmean(x) next to each other. Show that x == np.nan finds nothing while np.isnan(x) finds both gaps. Print x with the gaps dropped, and finish with np.nan_to_num(x).

Write something and press Run — the output appears here.

Your turn

4 exercises. Write the code yourself, then press Check — a nudge and the answer are there if you want them.

How many values in x are missing? Return the count.

your answer

Return x with the missing values removed.

your answer

Return the mean of x, ignoring the gaps.

your answer

Return x with every missing value replaced by 0.0, keeping the shape.

your answer