A single unusually large measurement can make a small sample difficult to summarize with an average alone. Median absolute deviation, or MAD, describes the typical absolute distance from the sample median. This tutorial computes the unscaled statistic and keeps the input policy visible.
Prerequisites are Python 3.12 or later. No data-science packages are required. The example accepts ordinary finite integers and floats and is intended for modest in-memory measurement collections, such as durations from a local batch job.
Complete example
Save as mad.py:
from math import isfinite
from statistics import median
def describe(values):
xs = list(values)
if not xs or any(type(x) not in (int, float) or not isfinite(x) for x in xs):
raise ValueError('finite numeric sample required')
center = median(xs)
mad = median(abs(x - center) for x in xs)
return center, mad
assert describe([10, 11, 12, 13, 1000]) == (12, 1)
assert describe([4, 4, 4]) == (4, 0)
assert describe([1, 3]) == (2, 1)
for sample in [[], [float('nan')], [float('inf')], [True], ['3']]:
try:
describe(sample)
raise AssertionError('accepted invalid sample')
except ValueError:
pass
print('median=12 MAD=1; ties, even sample and invalid inputs passed')
Run
python3 mad.py
Expected output:
median=12 MAD=1; ties, even sample and invalid inputs passed
For the first fixture, the center is 12. The absolute deviations are 2, 1, 0, 1 and 988. Sorting those deviations gives a middle value of 1, so the returned MAD is 1. Both the center and the spread remain in the original measurement units.
The two-element fixture uses the arithmetic midpoint as its median. The identical-value fixture returns zero spread. These are intentional behaviors worth checking before using the function in a dashboard or alerting rule.
Input policy and interpretation
The function first materializes the input so it can use the same observations for both calculations. It rejects missing samples, strings, booleans and nonfinite floats rather than silently converting or dropping them. If your ingestion policy removes missing values, perform that step separately and report how many observations were removed.
This is an unscaled MAD. It is not a standard deviation estimate and does not apply a distribution-dependent conversion factor. Keep its name and units explicit in exported data. Do not divide by MAD without handling zero: repeated measurements can produce a zero MAD even when some other observations differ from the center.
Edge cases and troubleshooting
The implementation targets normal measurement magnitudes, not arbitrarily large integers or extreme floating-point arithmetic. Very large values can overflow conversion or intermediate calculations. Establish domain bounds before calling it when inputs are externally supplied.
A robust center and spread do not make a slow tail disappear. Retain sample counts and investigate unusual measurements rather than automatically deleting them. If an operational report needs tail behavior, add a separately defined percentile calculation with an explicit convention.
The fixtures validate the formula, ties, an even sample and rejected inputs. They do not validate an anomaly detector or establish an alert threshold.
Primary reference
Python documents median behavior and the problems caused by NaN values. The second-median calculation is shown directly in the example. Continue with data-science tutorials and Python examples.