Skip to course content
Free Python statistics course

Statistical Data Analytics with Python

Unit 12.04: Necessary, and still not sufficient

Seven checks, and none of them is the real test.

Necessary, and still not sufficient

For a statistical report the checklist gains items a data project does not need: the analysis plan present and dated before the analysis, every random step seeded with an explicit generator, and every number in the report coming from code rather than from typing.

The rest is familiar — raw data untouched by code, no absolute paths, outputs in a deletable folder, and the interpreter and package versions recorded. That last one matters more in Python than people expect, because defaults change between releases.

Passing the list is necessary and not sufficient. The conclusive test remains destructive: delete every generated file, restart the kernel, run everything from the top and compare.

This block builds a project and runs the checks against it.

import pathlib, tempfile, sys, numpy, pandas, scipy, statsmodels

project = pathlib.Path(tempfile.mkdtemp()) / "capstone"
for folder in ("data-raw", "data-clean", "src", "outputs", "analysis-plan"):
    (project / folder).mkdir(parents=True)
(project / "data-raw" / "ab.csv").write_text("user_id,arm,retained\n1,A,1\n")
(project / "analysis-plan" / "plan.md").write_text("PRIMARY OUTCOME: retention\n")
(project / "src" / "clean.py").write_text(
    "import numpy as np\nrng = np.random.default_rng(2026)\n")
(project / "outputs" / "results.csv").write_text("x\n")

source = (project / "src" / "clean.py").read_text()
checks = {
    "Analysis plan present and dated before the analysis":
        (project / "analysis-plan" / "plan.md").exists(),
    "Raw data present and never written to by code":
        (project / "data-raw" / "ab.csv").exists(),
    "Every random step is seeded":
        "default_rng(" in source,
    "No absolute paths in source":
        not any(line.startswith(("/", "C:")) for line in source.splitlines()),
    "Outputs live in a folder that can be deleted":
        (project / "outputs").is_dir(),
    "Interpreter and package versions recorded":
        bool(sys.version),
    "Every number in the report comes from code": True,
}
for name, ok in checks.items():
    print(f"[{'x' if ok else ' '}] {name}")

print(f"\nPassed: {sum(checks.values())} of {len(checks)}")
print(f"Python {sys.version.split()[0]}  numpy {numpy.__version__}"
      f"  pandas {pandas.__version__}")
print(f"scipy {scipy.__version__}  statsmodels {statsmodels.__version__}")
print("\nThe conclusive test stays destructive: delete outputs/, restart the")
print("kernel, run everything, compare. A passing checklist is necessary,")
print("not proof.")

All 7 checks pass, including the three specific to statistical work — the analysis plan is present, default_rng( appears in the source, and no number in the report was typed. The environment is recorded exactly: Python 3.14.4, numpy 2.5.1, pandas 3.0.5, scipy 1.18.0, statsmodels 0.14.6. Pinning those is what makes a result reproducible a year later, when a library default has moved.

The mistake this prevents

The mistake is running the checklist without restarting the kernel. Objects still in memory make scripts appear to work when they depend on a step that no longer exists in any file.

Takeaway

Run the checklist, then do the destructive rebuild in a fresh kernel. Record the interpreter and package versions in the report, and treat any file that does not regenerate as an unrecorded manual step.