Errors & Special Values (nan, inf)
What NumPy does on division by zero, what nan and inf are, how to detect them with isnan/isinf, control warnings with seterr/errstate, and compute a safe strike rate with np.where.
Strike rate = runs ÷ balls × 100. But in some matches a player did not bat — 0 runs off 0 balls. In plain Python, 0 / 0 crashes with ZeroDivisionError. NumPy does not crash: it puts a special value in that cell, prints a warning, and carries on with the other 17 cells. You need to understand these special values and how to handle them, otherwise they silently spoil your averages.
Definition
Special floating-point values are values defined by the IEEE 754 standard that NumPy uses instead of raising errors:
inf(infinity, e.g.5 / 0),-inf(e.g.-5 / 0orlog(0)) andnan("Not a Number", the result of an undefined operation such as0 / 0,inf - inforsqrt(-1)). When such an operation happens, NumPy emits aRuntimeWarning(not an exception) by default; this behaviour is controlled withnp.seterr(globally) ornp.errstate(temporarily, inside awithblock).
- inf — a number larger than any other float;
np.inf > 1e308is True. - nan — a placeholder for "no meaningful answer". It is a float (
type(np.nan)isfloat), so it cannot be stored in an int array. - nan is contagious — any arithmetic with nan gives nan (
nan + 1 = nan), andnp.meanof an array containing nan is nan. - nan is not equal to anything, even itself —
np.nan == np.nanisFalse. That's why you neednp.isnan. - RuntimeWarning — a message printed to the console (stderr); the code keeps running.
What gives what
| Operation (floats) | Result | Warning category |
|---|---|---|
5 / 0 | inf | divide |
-5 / 0 | -inf | divide |
0 / 0 | nan | invalid |
np.log(0) | -inf | divide |
np.log(-1), np.sqrt(-1) | nan | invalid |
np.inf - np.inf | nan | (none — no new error) |
np.exp(1000) | inf | over (overflow) |
5 // 0 on ints | 0 | divide |
Syntax
python
np.isnan(x) np.isinf(x) np.isfinite(x) # detect special values
np.nan np.inf # the constants
old = np.seterr(divide='ignore', invalid='ignore') # change global behaviour
np.seterr(**old) # restore it
np.geterr() # see current settings
with np.errstate(divide='ignore', invalid='ignore'): # temporary, only inside the block
result = a / b
np.nan_to_num(x, nan=0.0, posinf=None, neginf=None) # replace special values
np.nansum(x) np.nanmean(x) np.nanmax(x) # aggregations that skip nanParameter (seterr / errstate) | Meaning | Default |
|---|---|---|
all | set every category at once | — |
divide | division by zero (x/0, log(0)) | 'warn' |
over | result too large (overflow) | 'warn' |
under | result too small (underflow) | 'ignore' |
invalid | invalid operation (0/0, sqrt(-1)) | 'warn' |
| Mode | What happens |
|---|---|
'ignore' | do nothing, silently produce inf/nan |
'warn' | print a RuntimeWarning (default) |
'raise' | raise a FloatingPointError exception (stops the code) |
'call' / 'print' / 'log' | call a function / print / log the message (advanced) |
Example — division by zero does not crash
python
import numpy as np
runs = np.array([10, 0, 5, -5])
balls = np.array([2, 0, 0, 0])
result = runs / balls
print("result :", result)
print("isnan :", np.isnan(result))
print("isinf :", np.isinf(result))
print("finite :", np.isfinite(result))text
result : [ 5. nan inf -inf]
isnan : [False True False False]
isinf : [False False True True]
finite : [ True False False False]The console (stderr) also shows these two warnings, but the program continues:
text
RuntimeWarning: divide by zero encountered in divide
RuntimeWarning: invalid value encountered in divideHow it works: 10 / 2 = 5 is normal. 0 / 0 is undefined → nan (an invalid operation). 5 / 0 and -5 / 0 → inf and -inf (divide by zero). np.isfinite is True only for normal numbers.
Example — nan's strange behaviour
python
x = np.array([72.0, np.nan, 88.0])
print("nan == nan :", np.nan == np.nan)
print("x == np.nan :", x == np.nan) # never finds the nan!
print("np.isnan(x) :", np.isnan(x))
print("nan + 1 :", np.nan + 1)
print("mean :", x.mean())
print("nanmean :", np.nanmean(x))
print("nan_to_num :", np.nan_to_num(x))
print("type(np.nan) :", type(np.nan))text
nan == nan : False
x == np.nan : [False False False]
np.isnan(x) : [False True False]
nan + 1 : nan
mean : nan
nanmean : 80.0
nan_to_num : [72. 0. 88.]
type(np.nan) : <class 'float'>How it works: by definition nan is not equal to anything, so x == np.nan is always False — always use np.isnan. One nan poisons mean(); the np.nan* functions (nanmean, nansum, nanmax, …) skip nan values. np.nan_to_num replaces nan with 0 (and inf with a huge finite number).
Example — the strike-rate problem (from the course notebook)
python
runs = np.array([
[72, 45, 88, 31, 64, 0], # Virat
[56, 0, 74, 88, 32, 45], # Rohit
[45, 32, 0, 67, 18, 54], # Hardik
])
balls = np.array([
[48, 32, 55, 24, 41, 0],
[39, 0, 50, 56, 25, 33],
[31, 24, 0, 46, 15, 38],
])
np.seterr(divide='ignore', invalid='ignore') # silence the warnings globally
strike_rate = runs / balls * 100
print(strike_rate.round(1))
print("nan cells:", np.isnan(strike_rate).sum())
print("Mean per player:", strike_rate.mean(axis=1).round(1))text
[[150. 140.6 160. 129.2 156.1 nan]
[143.6 nan 148. 157.1 128. 136.4]
[145.2 133.3 nan 145.7 120. 142.1]]
nan cells: 3
Mean per player: [nan nan nan]How it works: each player has one match with 0 runs off 0 balls (did not bat) → 0/0 = nan. Because nan is contagious, every player's mean becomes nan — the whole analysis is ruined by three cells. np.seterr only hides the warning; it does not fix the data.
Example — the fix: np.where (notebook version)
python
runs = np.array([
[72, 45, 88, 31, 64, 0], # Virat
[56, 0, 74, 88, 32, 45], # Rohit
[45, 32, 0, 67, 18, 54], # Hardik
])
balls = np.array([
[48, 32, 55, 24, 41, 0],
[39, 0, 50, 56, 25, 33],
[31, 24, 0, 46, 15, 38],
])
strike_rate_clean = np.where(
balls > 0, # condition, checked element-wise
runs / np.maximum(balls, 1) * 100, # divide by at least 1 -> no 0/0
0, # value when the player did not bat
)
print(strike_rate_clean.round(1))
print("Mean per player:", strike_rate_clean.mean(axis=1).round(1))text
[[150. 140.6 160. 129.2 156.1 0. ]
[143.6 0. 148. 157.1 128. 136.4]
[145.2 133.3 0. 145.7 120. 142.1]]
Mean per player: [122.6 118.8 114.4]How it works: np.maximum(balls, 1) replaces every 0 in the denominator with 1, so the division never sees a zero and produces no warning at all. Then np.where puts 0 in the cells where balls == 0, and the real strike rate everywhere else. The means are now real numbers.
Note: If you write
np.where(balls > 0, runs / balls * 100, 0)the result is the same, but NumPy still computesruns / ballsfor every cell first (including 0/0), so the warning still appears. That's why the notebook divides bynp.maximum(balls, 1)— or you can wrap it innp.errstate.
Example — np.errstate and 'raise'
python
runs = np.array([
[72, 45, 88, 31, 64, 0], # Virat
[56, 0, 74, 88, 32, 45], # Rohit
[45, 32, 0, 67, 18, 54], # Hardik
])
balls = np.array([
[48, 32, 55, 24, 41, 0],
[39, 0, 50, 56, 25, 33],
[31, 24, 0, 46, 15, 38],
])
with np.errstate(divide='ignore', invalid='ignore'): # only inside this block
sr = np.where(balls > 0, runs / balls * 100, 0)
print(sr[0].round(1))
old = np.seterr(all='raise') # turn warnings into exceptions
try:
runs / balls
except FloatingPointError as e:
print("FloatingPointError:", e)
np.seterr(**old) # restore previous settings
print("restored:", np.geterr())text
[150. 140.6 160. 129.2 156.1 0. ]
FloatingPointError: invalid value encountered in divide
restored: {'divide': 'warn', 'over': 'warn', 'under': 'ignore', 'invalid': 'warn'}How it works: np.errstate is a context manager: the settings apply only inside the with block and are restored automatically — safer than np.seterr, which changes behaviour for the rest of the program. 'raise' is useful while debugging: the code stops exactly where the first bad division happens. np.seterr returns the old settings so you can restore them.
Comparison — Python vs NumPy on bad maths
| Plain Python numbers | NumPy arrays | |
|---|---|---|
1 / 0 | ZeroDivisionError (crash) | inf + RuntimeWarning |
0 / 0 | ZeroDivisionError | nan + RuntimeWarning |
math.sqrt(-1) / np.sqrt(-1) | ValueError: math domain error | nan + RuntimeWarning |
| Why | one value — stop immediately | millions of values — don't throw away the good results |
np.seterr | np.errstate |
|---|---|
| global — affects all later code | local — only inside the with block |
| you must restore it yourself | restores automatically |
| fine at the top of a notebook | preferred inside functions/libraries |
Common mistakes
- ❌
x == np.nanto find missing values — always False. ✅np.isnan(x). - ❌ Silencing warnings with
np.seterrand thinking the problem is solved. ✅ Silence and handle:np.where,np.nan_to_numornan*functions. - ❌
int_array[0] = np.nan→ValueError: cannot convert float NaN to integer. ✅ Convert to float first:arr.astype(float). - ❌ Using
arr.mean()on data with nan. ✅np.nanmean(arr). - ❌ Forgetting
np.seterr(all='raise')is still on later. ✅ Usenp.errstateor restore withnp.seterr(**old).
Interview tip
Q: Why does NumPy return nan/inf instead of raising an error on division by zero?
A: NumPy follows IEEE 754 floating-point rules and works on whole arrays; one bad cell shouldn't destroy millions of valid results. It returns inf/nan and emits a RuntimeWarning. You control this with np.seterr/np.errstate (ignore, warn, raise) and clean results with np.isnan, np.where or np.nan_to_num.
Key points to remember
- NumPy does not crash on division by zero: x/0 gives inf (or -inf), 0/0 gives nan, with a RuntimeWarning.
- nan means 'Not a Number'; it is a float, it spreads through arithmetic, and nan == nan is False.
- Detect special values with np.isnan, np.isinf and np.isfinite — never with == np.nan.
- np.seterr(divide=..., invalid=..., over=..., under=...) changes behaviour globally: 'ignore', 'warn', 'raise', 'call', 'print', 'log'.
- np.errstate(...) does the same only inside a with block and restores the old settings automatically.
- Hiding warnings doesn't fix data: use np.where(balls > 0, runs / np.maximum(balls, 1) * 100, 0) for a safe strike rate.
- np.nanmean / np.nansum skip nan values; np.nan_to_num replaces nan and inf.
Try it yourself
inf, nan and how to detect them
Catch the warnings and print them
Safe strike rate with np.where (notebook method)
Practice: ignore the 'did not bat' matches
Revision Flashcards
Tap a card to flip it and see the answer.
