Hypothesis Tests
Two-Sample t Statistic (Pooled)
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.
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))
import math
1import math2s1 = [2, 4, 5, 7]s1 ← [2, 4, 5, 7]
1import math2s1 = [2, 4, 5, 7]3s2 = [1, 2, 3, 4]values this step[2, 4, 5, 7]s1s2 ← [1, 2, 3, 4]
2s1 = [2, 4, 5, 7]3s2 = [1, 2, 3, 4]4n1 = len(s1)values this step[1, 2, 3, 4]s2n1 ← 4
3s2 = [1, 2, 3, 4]4n1 = len(s1)5n2 = len(s2)values this step4n1n2 ← 4
4n1 = len(s1)5n2 = len(s2)6total1 = 0.0values this step4n2total1 ← 0.0
5n2 = len(s2)6total1 = 0.07for v in s1:values this step0.0total1v ← 2, total1 ← 2.0
pass 1 of 46total1 = 0.07for v in s1:8 total1 = total1 + v9mean1 = total1 / n1values this step2v0.0 → 2.0total1All 4 passes — pass 1 is the card above pass vtotal11 2 0.0 → 2.0 2 2 → 4 2.0 → 6.0 3 4 → 5 6.0 → 11.0 4 5 → 7 11.0 → 18.0 for v in s1:
6total1 = 0.07for v in s1:8 total1 = total1 + vmean1 ← 4.5
8 total1 = total1 + v9mean1 = total1 / n110total2 = 0.0values this step4.5mean1total2 ← 0.0
9mean1 = total1 / n110total2 = 0.011for v in s2:values this step0.0total2v ← 1, total2 ← 1.0
pass 1 of 410total2 = 0.011for v in s2:12 total2 = total2 + v13mean2 = total2 / n2values this step7 → 1v0.0 → 1.0total2All 4 passes — pass 1 is the card above pass vtotal21 7 → 1 0.0 → 1.0 2 1 → 2 1.0 → 3.0 3 2 → 3 3.0 → 6.0 4 3 → 4 6.0 → 10.0 for v in s2:
10total2 = 0.011for v in s2:12 total2 = total2 + vmean2 ← 2.5
12 total2 = total2 + v13mean2 = total2 / n214sq1 = 0.0values this step2.5mean2sq1 ← 0.0
13mean2 = total2 / n214sq1 = 0.015for v in s1:values this step0.0sq1v ← 2, sq1 ← 6.25
pass 1 of 414sq1 = 0.015for v in s1:16 sq1 = sq1 + (v - mean1) ** 217sq2 = 0.0values this step4 → 2v0.0 → 6.25sq1All 4 passes — pass 1 is the card above pass vsq11 4 → 2 0.0 → 6.25 2 2 → 4 6.25 → 6.5 3 4 → 5 6.5 → 6.75 4 5 → 7 6.75 → 13.0 for v in s1:
14sq1 = 0.015for v in s1:16 sq1 = sq1 + (v - mean1) ** 2sq2 ← 0.0
16 sq1 = sq1 + (v - mean1) ** 217sq2 = 0.018for v in s2:values this step0.0sq2v ← 1, sq2 ← 2.25
pass 1 of 417sq2 = 0.018for v in s2:19 sq2 = sq2 + (v - mean2) ** 220sp = math.sqrt((sq1 + sq2) / (n1 + n2 - 2))values this step7 → 1v0.0 → 2.25sq2All 4 passes — pass 1 is the card above pass vsq21 7 → 1 0.0 → 2.25 2 1 → 2 2.25 → 2.5 3 2 → 3 2.5 → 2.75 4 3 → 4 2.75 → 5.0 for v in s2:
17sq2 = 0.018for v in s2:19 sq2 = sq2 + (v - mean2) ** 2sp ← 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.7320508075688772spt ← 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.6329931618554518tstdout ← 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.
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.