Outliers and Ranges
Z-Score Flag
Flag values that are more than k standard deviations from the mean. Compute the mean and sample standard deviation of the series, calculate each value's z-score as (v − mean) / std, then mark any value with |z| > k as an outlier. By hand, use two loops (one for mean, one for std) then a third to flag. With pandas, the same operations vectorize in one expression.
By hand
First loop: accumulate total to compute mean = total / n. Second loop:
accumulate squared deviations, then std = (sq_diff / (n-1)) ** 0.5.
Dividing by n-1 (not n) gives the sample standard deviation — the same
formula pandas .std() uses by default. Third loop: flag values where
abs(v - mean) / std > k.
values = [5, 5, 6, 5, 5, 40]
n = len(values)
total = 0.0
for v in values:
total = total + v
mean = total / n
sq_diff = 0.0
for v in values:
sq_diff = sq_diff + (v - mean) ** 2
std = (sq_diff / (n - 1)) ** 0.5
k = 2
result = []
for v in values:
result.append(abs(v - mean) / std > k)
print('RESULT:', result)
values ← [5, 5, 6, 5, 5, 40]
1values = [5, 5, 6, 5, 5, 40]2n = len(values)values this step[5, 5, 6, 5, 5, 40]valuesn ← 6
1values = [5, 5, 6, 5, 5, 40]2n = len(values)3total = 0.0values this step6ntotal ← 0.0
2n = len(values)3total = 0.04for v in values:values this step0.0totalv ← 5, total ← 5.0
pass 1 of 63total = 0.04for v in values:5 total = total + v6mean = total / nvalues this step5v0.0 → 5.0totalAll 6 passes — pass 1 is the card above pass vtotal1 5 0.0 → 5.0 2 — 5.0 → 10.0 3 5 → 6 10.0 → 16.0 4 6 → 5 16.0 → 21.0 5 — 21.0 → 26.0 6 5 → 40 26.0 → 66.0 for v in values:
3total = 0.04for v in values:5 total = total + vmean ← 11.0
5 total = total + v6mean = total / n7sq_diff = 0.0values this step11.0meansq_diff ← 0.0
6mean = total / n7sq_diff = 0.08for v in values:values this step0.0sq_diffv ← 5, sq_diff ← 36.0
pass 1 of 67sq_diff = 0.08for v in values:9 sq_diff = sq_diff + (v - mean) ** 210std = (sq_diff / (n - 1)) ** 0.5values this step40 → 5v0.0 → 36.0sq_diffAll 6 passes — pass 1 is the card above pass vsq_diff1 40 → 5 0.0 → 36.0 2 — 36.0 → 72.0 3 5 → 6 72.0 → 97.0 4 6 → 5 97.0 → 133.0 5 — 133.0 → 169.0 6 5 → 40 169.0 → 1010.0 for v in values:
7sq_diff = 0.08for v in values:9 sq_diff = sq_diff + (v - mean) ** 2std ← 14.212670403551895
9 sq_diff = sq_diff + (v - mean) ** 210std = (sq_diff / (n - 1)) ** 0.511k = 2values this step14.212670403551895stdk ← 2
10std = (sq_diff / (n - 1)) ** 0.511k = 212result = []values this step2kresult ← []
11k = 212result = []13for v in values:values this step[]resultv ← 5, result ← [False]
pass 1 of 612result = []13for v in values:14 result.append(abs(v - mean) / std > k)15print('RESULT:', result)values this step40 → 5v[] → [False]resultAll 6 passes — pass 1 is the card above pass vresult1 40 → 5 [] → [False] 2 — [False] → [False, False] 3 5 → 6 [False, False] → [False, False, False] 4 6 → 5 [False, False, False] → [False, False, False, False] 5 — [False, False, False, False] → [False, False, False, False, False] 6 5 → 40 [False, False, False, False, False] → [False, False, False, False, False, True] for v in values:
12result = []13for v in values:14 result.append(abs(v - mean) / std > k)stdout ← RESULT: [False, False, False, False, False, True]
14 result.append(abs(v - mean) / std > k)15print('RESULT:', result)values this stepRESULT: [False, False, False, False, False, True]stdout
With pandas
df['x'].mean() and df['x'].std() compute the mean and sample standard
deviation (ddof=1). Subtracting the mean and dividing by std gives a
Series of z-scores; .abs() > k produces the boolean flags.
import pandas as pd
from dalib.display import set_display
set_display()
values = [5, 5, 6, 5, 5, 40]
k = 2
df = pd.DataFrame({'x': values})
mean = df['x'].mean()
std = df['x'].std()
z = (df['x'] - mean) / std
flags = z.abs() > k
result = flags.tolist()
print('mean:', round(mean, 4))
print('std:', round(std, 4))
print('z:', [round(v, 2) for v in z.tolist()])
print('RESULT:', result)
mean: 11.0
std: 14.2127
z: [-0.42, -0.42, -0.35, -0.42, -0.42, 2.04]
RESULT: [False, False, False, False, False, True]
Implementation notes
- pandas
.std()usesddof=1(sample std, divides by n−1) by default. numpy.std()usesddof=0(population std, divides by n). Using the wrong ddof produces a slightly different std and breaks parity — the naive half must match whichever the library call uses. - k=2 is a common threshold; k=3 is stricter and flags fewer values.
- Z-scores assume the data is roughly normally distributed; IQR fences
(
iqr-outlier-flags, this chapter) are more robust to heavy-tailed distributions. - Cross-reference:
stats-z-scores(statistics chapter) for standardization and the normal distribution context.