Standardize each value as its distance from the mean in standard-deviation units: z = (x − mean) / std. Z-scores let you compare values across datasets with different scales. By hand, three loops: mean, then sample std (ddof=1), then one z-score per element. With scipy, stats.zscore(values, ddof=1) computes all z-scores in one vectorised call.

By hand

Loop 1: mean. Loop 2: sample std (ddof=1, dividing by n−1 — same as statistics.stdev). Loop 3: for each value, compute and append round((v - mean) / std, 4). With mean=8.0 and std=3.0 the z-scores are exact fractions of 1/3: −4/3, 0, −2/3, 5/3, 2/3, −2/3, 1/3.

naive.py
Replay: real traced execution (multi-file project)
import math
values = [4, 8, 6, 13, 10, 6, 9]
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 = math.sqrt(sq_diff / (n - 1))
result = []
for v in values:
    result.append(round((v - mean) / std, 4))
print('RESULT:', result)
  1. import math

    1import math2values = [4, 8, 6, 13, 10, 6, 9]
  2. values ← [4, 8, 6, 13, 10, 6, 9]

    1import math2values = [4, 8, 6, 13, 10, 6, 9]3n = len(values)
    values this step[4, 8, 6, 13, 10, 6, 9]values
  3. n ← 7

    2values = [4, 8, 6, 13, 10, 6, 9]3n = len(values)4total = 0.0
    values this step7n
  4. total ← 0.0

    3n = len(values)4total = 0.05for v in values:
    values this step0.0total
  5. v ← 4, total ← 4.0

    pass 1 of 7
    4total = 0.05for v in values:6    total = total + v7mean = total / n
    values this step4v0.0 4.0total
    All 7 passes — pass 1 is the card above
    passvtotal
    140.0 4.0
    24 84.0 12.0
    38 612.0 18.0
    46 1318.0 31.0
    513 1031.0 41.0
    610 641.0 47.0
    76 947.0 56.0
  6. for v in values:

    4total = 0.05for v in values:6    total = total + v
  7. mean ← 8.0

    6    total = total + v7mean = total / n8sq_diff = 0.0
    values this step8.0mean
  8. sq_diff ← 0.0

    7mean = total / n8sq_diff = 0.09for v in values:
    values this step0.0sq_diff
  9. v ← 4, sq_diff ← 16.0

    pass 1 of 7
    8sq_diff = 0.09for v in values:10    sq_diff = sq_diff + (v - mean) ** 211std = math.sqrt(sq_diff / (n - 1))
    values this step9 4v0.0 16.0sq_diff
    All 7 passes — pass 1 is the card above
    passvsq_diff
    19 40.0 16.0
    24 8
    38 616.0 20.0
    46 1320.0 45.0
    513 1045.0 49.0
    610 649.0 53.0
    76 953.0 54.0
  10. for v in values:

    8sq_diff = 0.09for v in values:10    sq_diff = sq_diff + (v - mean) ** 2
  11. std ← 3.0

    10    sq_diff = sq_diff + (v - mean) ** 211std = math.sqrt(sq_diff / (n - 1))12result = []
    values this step3.0std
  12. result ← []

    11std = math.sqrt(sq_diff / (n - 1))12result = []13for v in values:
    values this step[]result
  13. v ← 4, result ← [-1.3333]

    pass 1 of 7
    12result = []13for v in values:14    result.append(round((v - mean) / std, 4))15print('RESULT:', result)
    values this step9 4v[] [-1.3333]result
    All 7 passes — pass 1 is the card above
    passvresult
    19 4[] [-1.3333]
    24 8[-1.3333] [-1.3333, 0.0]
    38 6[-1.3333, 0.0] [-1.3333, 0.0, -0.6667]
    46 13[-1.3333, 0.0, -0.6667] [-1.3333, 0.0, -0.6667, 1.6667]
    513 10[-1.3333, 0.0, -0.6667, 1.6667] [-1.3333, 0.0, -0.6667, 1.6667, 0.6667]
    610 6[-1.3333, 0.0, -0.6667, 1.6667, 0.6667] [-1.3333, 0.0, -0.6667, 1.6667, 0.6667, -0.6667]
    76 9[-1.3333, 0.0, -0.6667, 1.6667, 0.6667, -0.6667] [-1.3333, 0.0, -0.6667, 1.6667, 0.6667, -0.6667, 0.3333]
  14. for v in values:

    12result = []13for v in values:14    result.append(round((v - mean) / std, 4))
  15. stdout ← RESULT: [-1.3333, 0.0, -0.6667, 1.6667, 0.6667, -0.6667, 0.3333]

    14    result.append(round((v - mean) / std, 4))15print('RESULT:', result)
    values this stepRESULT: [-1.3333, 0.0, -0.6667, 1.6667, 0.6667, -0.6667, 0.3333]stdout

With the library

scipy.stats.zscore defaults to ddof=0 (population std). Passing ddof=1 makes it use the sample std, matching the naive half. The snapshot shows mean and std so the denominator choice is visible.

library.py
import numpy as np
from scipy import stats
from dalib.display import set_display
set_display()

values = [4, 8, 6, 13, 10, 6, 9]
z = stats.zscore(values, ddof=1)
result = [round(float(v), 4) for v in z]
print('mean:', float(np.mean(values)))
print('std (ddof=1):', round(float(np.std(values, ddof=1)), 4))
print('RESULT:', result)
mean: 8.0
std (ddof=1): 3.0
RESULT: [-1.3333, 0.0, -0.6667, 1.6667, 0.6667, -0.6667, 0.3333]

Implementation notes

  • scipy.stats.zscore defaults to ddof=0 — always pass ddof=1 explicitly when you want sample-std standardization to match statistics.stdev or np.std(ddof=1).
  • Z-scores sum to 0 and have mean 0; their sample std (ddof=1, np.std(z, ddof=1)) equals 1.0. The population std (ddof=0) is less than 1 for any finite sample. Use np.std(z, ddof=1) to verify.
  • Cross-reference: standard-deviation (this chapter) for the ddof details; zscore-flag (python-data-cleaning ch06) for applying z-scores to outlier detection.