What You'll Build
In this second stage of the capstone you will build the inferential heart of the analysis, running the appropriate hypothesis tests to determine whether the training regime genuinely works. You will check assumptions, run a paired test on the within-player improvement, a two-group test comparing treatment against control, and a chi-square test on whether injury is associated with group, reporting effect sizes and interpreting every p-value correctly. This stage exists because it answers the central question the whole trial was designed to address — does the regime have a real effect? — and it demands the disciplined test-selection and interpretation skills from Modules 4 and 5. By the end you will have rigorous, assumption-checked verdicts on the regime's effects.
Prerequisites
- Python 3.10 or later with NumPy, SciPy, and pandas installed.
- The capstone dataset and the descriptive findings from Lesson 32.
- Mastery of hypothesis testing, p-values, and error types from Module 4.
- Command of t-tests, chi-square, and non-parametric tests from Module 5.
- Understanding of effect sizes and the significance-versus-importance distinction.
Setup & Project Structure
You will create a hypothesis-testing module in the capstone directory that loads the dataset and the improvement column, then runs the suite of tests. Keeping the inference stage separate from the descriptive stage maintains the clean, reviewable structure of the growing analysis. The descriptive findings from the previous stage — particularly the skewness of fitness — directly inform the assumption checks and test choices here.
# In the capstone project directory
touch hypothesis_tests.py
# Confirm prior stage available
python -c "import pandas as pd; df = pd.read_csv('cricket_trial.csv'); \
print('improvement range:', (df.strike_after - df.strike_before).min().round(1), \
'to', (df.strike_after - df.strike_before).max().round(1))"
Step 1 — Foundation
Step 1 builds the assumption-checking foundation that governs every test choice: functions to test normality and equal variance, applied to the variables before any test is run. This is the foundation because, per the standards from Module 4, no test may be run without first verifying its assumptions, and the results here determine whether parametric or non-parametric tests are used. Checking assumptions first embodies the disciplined workflow that separates rigorous from naive analysis.
# hypothesis_tests.py
import numpy as np
import pandas as pd
from scipy import stats
trial = pd.read_csv("cricket_trial.csv")
trial["improvement"] = trial["strike_after"] - trial["strike_before"]
def check_normality(sample):
if len(sample) < 3:
return False
return stats.shapiro(sample)[1] > 0.05
def check_equal_var(a, b):
return stats.levene(a, b)[1] > 0.05
treat = trial[trial.group == "treatment"]
ctrl = trial[trial.group == "control"]
print(f"Treatment improvement normal? {check_normality(treat['improvement'])}")
print(f"Control improvement normal? {check_normality(ctrl['improvement'])}")
print(f"Equal variances? {check_equal_var(treat['improvement'], ctrl['improvement'])}")
Step 2 — Core Logic
Step 2 runs the core hypothesis tests with effect sizes: a paired test comparing each player's after against before to confirm within-player improvement, and a two-group test comparing the treatment group's improvement against the control's to isolate the regime's effect beyond any general change. This is the analytical heart because these two tests directly answer whether the regime works, and computing Cohen's d alongside each ensures the practical magnitude is reported, not just statistical significance.
# hypothesis_tests.py (continued)
def cohens_d(a, b):
pooled = np.sqrt((np.var(a, ddof=1) + np.var(b, ddof=1)) / 2)
return (np.mean(a) - np.mean(b)) / pooled
# PAIRED test: did players improve vs their own baseline (treatment group)?
t_paired, p_paired = stats.ttest_rel(treat["strike_after"], treat["strike_before"])
print(f"Paired (treatment within-player): t={t_paired:.2f}, p={p_paired:.4g}")
# TWO-GROUP test: treatment improvement vs control improvement (Welch)
t_grp, p_grp = stats.ttest_ind(treat["improvement"], ctrl["improvement"],
equal_var=False)
d_grp = cohens_d(treat["improvement"].values, ctrl["improvement"].values)
print(f"Two-group (treatment vs control): t={t_grp:.2f}, p={p_grp:.4g}, "
f"Cohen's d={d_grp:.2f}")
print(f" Practical: {'meaningful' if abs(d_grp) >= 0.5 else 'modest'} effect size")
Step 3 — Integration & Enhancement
Step 3 adds the categorical analysis and a power check, completing the inferential suite. You will run a chi-square test on whether injury occurrence is associated with treatment group, applying Module 5's categorical method, and perform a power analysis confirming the study was adequately sized to detect the effects found. This integration ensures the analysis covers every variable type and verifies that any non-significant findings reflect true absence rather than insufficient power, meeting the capstone's rigour standards.
# hypothesis_tests.py (continued)
from statsmodels.stats.power import TTestIndPower
# CHI-SQUARE: is injury associated with treatment group?
contingency = pd.crosstab(trial["group"], trial["injured"]).values
chi2, p_chi2, dof, _ = stats.chi2_contingency(contingency)
print(f"Chi-square (group vs injury): chi2={chi2:.2f}, p={p_chi2:.4f}")
print(f" Injury {'associated with' if p_chi2 < 0.05 else 'independent of'} group")
# POWER CHECK: was the study large enough to detect the observed effect?
analysis = TTestIndPower()
d_obs = abs(cohens_d(treat["improvement"].values, ctrl["improvement"].values))
power = analysis.power(effect_size=d_obs, nobs1=len(treat), alpha=0.05)
print(f"Achieved power for the group comparison: {power:.3f}")
print(f" {'Adequately powered' if power >= 0.8 else 'Underpowered'}")
Step 4 — Testing & Verification
Now you will run the full inferential suite and verify the verdicts match the dataset's known construction. Confirm the paired test detects within-player improvement, the two-group test finds the treatment effect significant with a meaningful effect size, the chi-square reflects the built-in injury-rate difference, and the power check confirms adequacy. Each result recovering the embedded ground truth verifies your inference is correct.
# Run the full inferential analysis
from hypothesis_tests import (treat, ctrl, trial, cohens_d, stats)
from statsmodels.stats.power import TTestIndPower
import pandas as pd, numpy as np
t_grp, p_grp = stats.ttest_ind(treat["improvement"], ctrl["improvement"],
equal_var=False)
d = cohens_d(treat["improvement"].values, ctrl["improvement"].values)
print(f"Treatment vs control: p={p_grp:.4g}, Cohen's d={d:.2f}")
# CORRECT interpretation reminder:
verdict = "significant" if p_grp < 0.05 else "not significant"
print(f" Interpretation: IF the regime had no effect, data this extreme would")
print(f" occur with probability {p_grp:.4g}. Result is {verdict}.")
print(f" This is NOT 'the probability the regime does nothing'.")
# Expected: significant (p small), d ~ 0.5-1.0 (meaningful), adequately powered.
Warning: When reporting your p-values, never describe them as the probability that the regime has no effect or that the result is due to chance. A p-value is the probability of data at least as extreme as observed, assuming the regime has no effect. Reversing this — the most common error in applied statistics — would misrepresent your central finding to whoever reads the analysis, so state the interpretation in the correct direction every time.
Extension Challenge: Extend the inferential stage to run the analysis non-parametrically as a robustness check — use the Wilcoxon signed-rank test for the paired comparison and the Mann-Whitney test for the two-group comparison — and confirm the conclusions hold regardless of distributional assumptions. As a stretch goal, apply a multiple-comparisons correction across all the tests you ran, since running several tests inflates the family-wise error rate.
- The inferential stage answers the trial's central question: does the regime genuinely work?
- Assumption checks for normality and equal variance must precede every test, determining parametric versus non-parametric choice.
- A paired test confirms within-player improvement; a two-group test isolates the regime's effect beyond general change.
- Effect sizes like Cohen's d accompany every test so practical magnitude is reported, not just significance.
- A chi-square test addresses the categorical injury outcome, covering every variable type.
- A power check confirms the study was large enough, so non-significant results reflect true absence not insufficient power.
- Always interpret p-values in the correct direction: the probability of the data under the null, never the probability the null is true.