Compute the pooled (equal-variance) two-sample t statistic: t = (mean1 − mean2) / (sp × √(1/n1 + 1/n2)), where sp = pooled std = √(((n1−1)s1² + (n2−1)s2²) / (n1+n2−2)). Four loops: two means, two squared-deviation sums. Library: scipy.stats.ttest_ind(s1, s2) with default equal_var=True (pooled) — snapshot shows statistic+pvalue; RESULT is the statistic only.

By hand

s1=[2,4,5,7] (mean=4.5, n=4), s2=[1,2,3,4] (mean=2.5, n=4). Squared deviation sums: sq1=13, sq2=5. sp=√(18/6)=√3≈1.7321. t=(4.5−2.5)/ (1.7321×√0.5) = 2/1.2247 ≈ 1.633.

naive.py
Replay: real traced execution (multi-file project)
import math
s1 = [2, 4, 5, 7]
s2 = [1, 2, 3, 4]
n1 = len(s1)
n2 = len(s2)
total1 = 0.0
for v in s1:
    total1 = total1 + v
mean1 = total1 / n1
total2 = 0.0
for v in s2:
    total2 = total2 + v
mean2 = total2 / n2
sq1 = 0.0
for v in s1:
    sq1 = sq1 + (v - mean1) ** 2
sq2 = 0.0
for v in s2:
    sq2 = sq2 + (v - mean2) ** 2
sp = math.sqrt((sq1 + sq2) / (n1 + n2 - 2))
t = (mean1 - mean2) / (sp * math.sqrt(1 / n1 + 1 / n2))
print('RESULT:', round(t, 4))
  1. import math

    1import math2s1 = [2, 4, 5, 7]
  2. s1 ← [2, 4, 5, 7]

    1import math2s1 = [2, 4, 5, 7]3s2 = [1, 2, 3, 4]
    values this step[2, 4, 5, 7]s1
  3. s2 ← [1, 2, 3, 4]

    2s1 = [2, 4, 5, 7]3s2 = [1, 2, 3, 4]4n1 = len(s1)
    values this step[1, 2, 3, 4]s2
  4. n1 ← 4

    3s2 = [1, 2, 3, 4]4n1 = len(s1)5n2 = len(s2)
    values this step4n1
  5. n2 ← 4

    4n1 = len(s1)5n2 = len(s2)6total1 = 0.0
    values this step4n2
  6. total1 ← 0.0

    5n2 = len(s2)6total1 = 0.07for v in s1:
    values this step0.0total1
  7. v ← 2, total1 ← 2.0

    pass 1 of 4
    6total1 = 0.07for v in s1:8    total1 = total1 + v9mean1 = total1 / n1
    values this step2v0.0 2.0total1
    All 4 passes — pass 1 is the card above
    passvtotal1
    120.0 2.0
    22 42.0 6.0
    34 56.0 11.0
    45 711.0 18.0
  8. for v in s1:

    6total1 = 0.07for v in s1:8    total1 = total1 + v
  9. mean1 ← 4.5

    8    total1 = total1 + v9mean1 = total1 / n110total2 = 0.0
    values this step4.5mean1
  10. total2 ← 0.0

    9mean1 = total1 / n110total2 = 0.011for v in s2:
    values this step0.0total2
  11. v ← 1, total2 ← 1.0

    pass 1 of 4
    10total2 = 0.011for v in s2:12    total2 = total2 + v13mean2 = total2 / n2
    values this step7 1v0.0 1.0total2
    All 4 passes — pass 1 is the card above
    passvtotal2
    17 10.0 1.0
    21 21.0 3.0
    32 33.0 6.0
    43 46.0 10.0
  12. for v in s2:

    10total2 = 0.011for v in s2:12    total2 = total2 + v
  13. mean2 ← 2.5

    12    total2 = total2 + v13mean2 = total2 / n214sq1 = 0.0
    values this step2.5mean2
  14. sq1 ← 0.0

    13mean2 = total2 / n214sq1 = 0.015for v in s1:
    values this step0.0sq1
  15. v ← 2, sq1 ← 6.25

    pass 1 of 4
    14sq1 = 0.015for v in s1:16    sq1 = sq1 + (v - mean1) ** 217sq2 = 0.0
    values this step4 2v0.0 6.25sq1
    All 4 passes — pass 1 is the card above
    passvsq1
    14 20.0 6.25
    22 46.25 6.5
    34 56.5 6.75
    45 76.75 13.0
  16. for v in s1:

    14sq1 = 0.015for v in s1:16    sq1 = sq1 + (v - mean1) ** 2
  17. sq2 ← 0.0

    16    sq1 = sq1 + (v - mean1) ** 217sq2 = 0.018for v in s2:
    values this step0.0sq2
  18. v ← 1, sq2 ← 2.25

    pass 1 of 4
    17sq2 = 0.018for v in s2:19    sq2 = sq2 + (v - mean2) ** 220sp = math.sqrt((sq1 + sq2) / (n1 + n2 - 2))
    values this step7 1v0.0 2.25sq2
    All 4 passes — pass 1 is the card above
    passvsq2
    17 10.0 2.25
    21 22.25 2.5
    32 32.5 2.75
    43 42.75 5.0
  19. for v in s2:

    17sq2 = 0.018for v in s2:19    sq2 = sq2 + (v - mean2) ** 2
  20. sp ← 1.7320508075688772

    19    sq2 = sq2 + (v - mean2) ** 220sp = math.sqrt((sq1 + sq2) / (n1 + n2 - 2))21t = (mean1 - mean2) / (sp * math.sqrt(1 / n1 + 1 / n2))
    values this step1.7320508075688772sp
  21. t ← 1.6329931618554518

    20sp = math.sqrt((sq1 + sq2) / (n1 + n2 - 2))21t = (mean1 - mean2) / (sp * math.sqrt(1 / n1 + 1 / n2))22print('RESULT:', round(t, 4))
    values this step1.6329931618554518t
  22. stdout ← RESULT: 1.633

    21t = (mean1 - mean2) / (sp * math.sqrt(1 / n1 + 1 / n2))22print('RESULT:', round(t, 4))
    values this stepRESULT: 1.633stdout

With the library

scipy.stats.ttest_ind(s1, s2) defaults to equal_var=True (pooled formula), matching the naive computation. The snapshot shows both statistic and pvalue; RESULT is the statistic.

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

s1 = [2, 4, 5, 7]
s2 = [1, 2, 3, 4]
result = stats.ttest_ind(s1, s2)  # equal_var=True by default (pooled)
print('mean1:', round(float(np.mean(s1)), 4))
print('mean2:', round(float(np.mean(s2)), 4))
print('t_stat:', round(float(result.statistic), 4))
print('pvalue:', round(float(result.pvalue), 4))
print('RESULT:', round(float(result.statistic), 4))
mean1: 4.5
mean2: 2.5
t_stat: 1.633
pvalue: 0.1536
RESULT: 1.633

Honesty

This lesson shows the computation exactly, on a tiny pinned sample. The arithmetic is correct and reproducible, but with a sample this small the result is not a valid statistical finding — it demonstrates the mechanism, not evidence. Real inference needs an adequate sample size and assumption checks (e.g. independence, approximate normality, and equal variances for the pooled form); the p-value / interval here should be read as "how the formula is computed," not as a conclusion about a population.

Implementation notes

  • Design rule §7: RESULT = statistic only; p-value is snapshot-only.
  • Pooled assumption (equal_var=True): both groups share one population variance estimate, giving df = n1+n2−2 = 6.
  • Welch's t (equal_var=False) uses separate variance estimates and a fractional df (Welch-Satterthwaite). Prefer Welch when group variances differ; it's the safer default in practice.
  • Here sp=√3 cancels cleanly: t = 2√(2/3) = 2/√1.5 ≈ 1.633.
  • Cross-reference: one-sample-t-stat (this chapter) for the single-group version and the ddof=1 rule.