statistics: stdev() Crashes on NaN, variance() Doesn't (tested)

A stray NaN in numeric data usually propagates quietly through arithmetic -- any operation touching it just produces another NaN. We measured whether Python's statistics module follows that pattern consistently across its variance-family functions, and it doesn't.

1. variance() propagates NaN cleanly; stdev() on the same data raises AttributeError

import statistics as st

nan = float("nan")
data = [1, 2, nan, 4]

print(st.variance(data))
print(st.stdev(data))
nan
Traceback (most recent call last):
  ...
  File ".../statistics.py", line 1211, in stdev
    return _float_sqrt_of_frac(mss.numerator, mss.denominator)
                               ^^^^^^^^^^^^^
AttributeError: 'float' object has no attribute 'numerator'

Measured: variance() on data containing a NaN returns nan -- the expected, quiet propagation. stdev() on the exact same list doesn't return nan; it raises AttributeError from deep inside the module's internals. stdev() computes variance as an exact Fraction internally and expects a .numerator attribute to take the square root precisely, but a NaN in the input collapses that internal value to a plain float, which has no such attribute -- the exception leaks an implementation detail instead of surfacing as a clean, catchable error.

2. The population variants have the exact same split

import statistics as st

nan = float("nan")
data = [1, 2, nan, 4]

print(st.pvariance(data))
print(st.pstdev(data))
nan
Traceback (most recent call last):
  ...
AttributeError: 'float' object has no attribute 'numerator'

Measured: pvariance() (population variance) also returns nan cleanly, while pstdev() raises the identical AttributeError as stdev(). The split is consistent across both the sample and population variants: the two functions that return variance directly handle NaN fine; the two that take a square root of it don't.

3. Position and list size don't matter -- any NaN anywhere triggers it

import statistics as st

nan = float("nan")

try:
    st.stdev([nan, 1, 2, 4])
except AttributeError as e:
    print("leading nan:", e)

try:
    st.stdev([1] * 100 + [nan])
except AttributeError as e:
    print("trailing nan in 101 values:", e)
leading nan: 'float' object has no attribute 'numerator'
trailing nan in 101 values: 'float' object has no attribute 'numerator'

Measured: the crash happens regardless of where the NaN sits in the list or how many other values surround it -- a single NaN anywhere is enough. Code that checks for bad data by catching StatisticsError around a stdev() call (the module's documented exception type) won't catch this -- AttributeError is a different exception class entirely, so the except clause silently lets it through as an unhandled crash.

Frequently asked questions

Does statistics.stdev() raise an error for NaN input?

Yes, but not the expected one -- we measured stdev([1,2,float('nan'),4]) raising AttributeError: 'float' object has no attribute 'numerator', not the module's own StatisticsError.

Does statistics.variance() handle NaN differently than stdev()?

Yes -- we measured variance() on data containing NaN returning nan cleanly, while stdev() on the identical data crashes with AttributeError. The same split holds for pvariance() (clean) versus pstdev() (crashes).

Will catching StatisticsError protect a stdev() call from NaN input?

No -- we measured the exception raised being AttributeError, not StatisticsError, so an except StatisticsError clause around stdev() won't catch it and the crash propagates as unhandled.

Why does stdev() crash on NaN when variance() doesn't?

stdev() computes variance internally as an exact Fraction and expects a .numerator attribute to take an exact square root. A NaN value collapses that internal computation to a plain float, which has no .numerator attribute, so the AttributeError comes from stdev()'s own implementation rather than from the input itself.

Written with AI assistance. Measured on 2026-10-04 with Python 3.13.5's built-in statistics module: called variance(), stdev(), pvariance(), and pstdev() on the same data containing a float('nan') value. PlainHeaders is our extension; Python's statistics module is built in.