Somewhere in your future is a moment of quiet panic: a result you produced months ago, a stakeholder asking how, and no way to rerun it. Reproducibility is the discipline that prevents that moment. It means anyone with your project can run it and get the same answer, and it rests on four things being captured: the code, the data, the randomness, and the environment. Get those under control and your analysis stops being a personal artifact and becomes something the world can check, which is what turns a claim into evidence.
This is a doing skill, so the chapter is a five-notebook mini-course: reproducibility
(seeds, environments, data hashes), project structure, Git basics, Git branching and merging, and end-to-end
experiment tracking. The Git notebooks run real git commands against a throwaway
repository created just for the demo, so you watch an actual commit history and a real merge conflict form and
resolve, with nothing installed and nothing of yours touched.
The Four Pillars of a Reproducible Result
“It works on my machine” is the enemy. A result reproduces only when four separate things are the same as when it was first produced. Miss any one and the answer can quietly change.
Three of the four are quick habits. Set a seed (np.random.default_rng(42)) and random
operations repeat exactly. Pin the environment by listing every package at an exact version in a
requirements.txt, ideally inside a per-project virtual environment. And hash the data, a
short fingerprint of its exact contents, so you can prove later whether it changed. The fourth pillar, versioning the
code, is big enough to be the rest of this chapter.
Git: A History You Can Trust
Git is a version control system: it records your project as a sequence of snapshots called commits, each with a message, an author, and a timestamp. It answers what changed, when, why, and by whom, lets you return to any past state, and makes experimentation safe because nothing is ever truly lost.
The everyday loop is three steps. You edit files in your working folder. You stage the
changes you want to record with git add, choosing what goes in. And you commit them with
git commit, saving a snapshot with a message. That is 90 percent of Git.
git status, git diff, and git log let you see it all.
A companion habit lives here too: the .gitignore file, a list of things Git should never
track. Large data files, generated outputs, and above all secrets like API keys and passwords belong
out of version control. Notebook 3 shows a .gitignore making a committed secret and a data folder vanish
from Git's view, which is a mistake you very much want to prevent, not fix.
Branching, Merging, and Working Together
A branch is a parallel line of work. You branch off main to try an idea, commit freely
without risking the stable version, and when it is ready you merge it back. This is how one person
experiments safely and how a whole team builds in parallel without overwriting each other.
git switch -c add-feature # branch off and move onto it git commit -am "Add region feature" git switch main git merge --no-ff add-feature # bring the finished work back
main line keeps working while an idea grows on its own branch. When the branch is ready it
rejoins main at a merge commit, and the graph permanently records that the branch happened.
Sometimes two branches change the same line differently, and Git cannot know which is right. It stops and marks the spot, a merge conflict. This is not an error but a request for a human decision: Git kept both versions safe and is asking you to choose. You edit the file to the version you want, remove Git's marker lines, and commit the resolution. Notebook 4 creates a real conflict over a threshold value and walks the resolution end to end, finishing with the branching commit graph.
| Command | What it does |
|---|---|
git switch -c name | Create a branch and move onto it, to work without touching main. |
git merge name | Bring a branch's finished commits into the current branch. |
git log --graph | Draw the history, branches and merges included, as a graph. |
git clone / push / pull | Copy, upload, and download a repository shared on a host like GitHub. |
Scale this branch-commit-merge cycle across a team on a shared host, GitHub, GitLab, or Bitbucket, and you have how essentially all modern software and data science is built. A pull request proposes a branch's changes for review before they merge, adding a human check to the version history.
Everyday Version Control at the Command Line
Editors and desktop apps put a friendly face on Git, but underneath, and for most developers and statisticians day to day, version control lives in the command line. The terminal is universal (the same commands work on every machine and every host), scriptable (you can automate it), and it is where every tutorial, error message, and teammate's instructions are written. A handful of commands cover almost everything you will ever do.
Here is the whole everyday rhythm of working with a shared repository, from getting a copy to sending your work back, as it actually looks in a terminal.
# get your own copy of a project that lives on GitHub $ git clone https://github.com/acme/sales-analysis.git $ cd sales-analysis # before starting work, pull the latest changes teammates have pushed $ git pull # make a branch for your task, so main stays stable $ git switch -c add-region-breakdown # ...edit analysis.py, add a chart... then see what changed $ git status modified: analysis.py $ git diff # review the exact line changes # stage, commit with a clear message, and push the branch to GitHub $ git add -A $ git commit -m "Add revenue breakdown by region" $ git push -u origin add-region-breakdown # open a pull request on GitHub, get review, then it merges into main
That is the loop nearly everyone repeats many times a day: pull, branch, edit, add, commit, push, open a pull request. The one golden habit inside it is pull before you push, so you build on your teammates' latest work and avoid surprise conflicts. When a conflict does happen, it is the same event from the branching section, resolved the same way.
| Command | What it does |
|---|---|
git clone <url> | Copy a remote repository to your machine, ready to work on. |
git init | Start version control in a new, local folder (then add a remote to share it). |
git status / git diff | See what has changed since the last commit, by file and by line. |
git add / git commit | Stage changes, then save them as a snapshot with a message. |
git pull / git push | Download teammates' commits; upload yours to the shared host. |
git switch / git merge | Move between branches; bring a finished branch's work back in. |
git remote -v / git log | List the shared hosts this repo talks to; read its history. |
A developer runs these dozens of times a day almost without thinking. A statistician or data scientist uses the very same commands to version analysis scripts, notebooks, and reports, and to sync a project with collaborators through a shared remote. Learn these ten and you are fluent in the language every team, tool, and tutorial speaks.
Automating the Flow: CI/CD
Once your code lives in a shared repository, you can make a robot watch it. Continuous Integration (CI) means that every push automatically triggers a fresh build and a run of your tests, in a clean environment, so a broken change is caught in minutes rather than discovered in production. Continuous Delivery / Deployment (CD) goes one step further: when the tests pass, the result is automatically packaged and released, or even deployed live.
For this book the connection is direct. CI is reproducibility enforced by machine. The pipeline checks out your exact commit, rebuilds the pinned environment, reruns the analysis from scratch, and fails loudly if a number moves or a test breaks. If it cannot reproduce your result on a clean server, neither can anyone else.
The pipeline itself is just a file in your repository, so it is versioned like everything else. Here is a minimal GitHub Actions workflow that, on every push, sets up Python, installs the pinned dependencies, and runs the test suite.
name: tests on: [push] # run on every push jobs: test: runs-on: ubuntu-latest # a fresh, clean machine each time steps: - uses: actions/checkout@v4 # get the exact commit - run: pip install -r requirements.txt # rebuild the pinned environment - run: pytest # rerun the tests; fail loudly if anything broke
| Tool | What it is, and where you meet it |
|---|---|
| Jenkins | The long-established, self-hosted automation server; highly customizable through plugins, common in enterprises. |
| GitHub Actions | CI/CD built into GitHub, configured with a YAML file in the repo. The most common choice for open-source and new projects. |
| GitLab CI/CD | The same, native to GitLab, driven by a .gitlab-ci.yml file. |
| CircleCI / Travis CI | Popular hosted CI services that connect to your repository. |
| Azure Pipelines | Microsoft's CI/CD in Azure DevOps, widely used in enterprise and .NET shops. |
Data scientists lean on the same machinery: a pipeline can rerun a notebook end to end, validate that a model still clears an accuracy bar, regenerate a report, or trigger a retraining job. It is the automated backbone of the reproducible, monitored workflow the MLOps chapter describes.
Git Everywhere: Version Control in Your Tools
You rarely have to leave your editor to use Git, because version control is now built into nearly every serious development and analysis tool. The commands underneath are identical; the difference is a graphical front door, staging changes with a checkbox, seeing a colored diff in the margin, committing from a panel, and resolving a conflict in a visual three-way view.
| Tool or platform | How Git shows up in it |
|---|---|
| VS Code | A built-in Source Control panel for staging, committing, branching, and pushing; the popular GitLens extension adds inline blame and rich history. |
| Visual Studio | Full Git integration in the IDE: clone, branch, commit, and manage pull requests to GitHub or Azure DevOps without a terminal. |
| RStudio / Posit | A Git pane for staging and committing, and project-level integration with GitHub, the standard way R users version their analyses. |
| JupyterLab | The jupyterlab-git extension adds a Git panel and diff viewer for notebooks right in the browser. |
| SAS | SAS Studio, Enterprise Guide, and SAS Viya include built-in Git integration, so even a menu-driven analyst can clone, commit, and push program versions. |
| JetBrains (PyCharm, etc.) | Deep, first-class version control with a visual merge tool and change lists across the whole IDE. |
| Desktop clients | GitHub Desktop, GitKraken, Sourcetree: standalone graphical apps for people who prefer buttons to the terminal. |
The lesson is that Git is the shared plumbing beneath a dozen different front doors. A developer might commit from the VS Code sidebar, a statistician from the RStudio or SAS Studio Git pane, and a data engineer from the terminal, and they all push to the same GitHub repository and see the same history. That commercial statistical platforms like SAS now build in Git is the clearest sign of how universal version control has become: it is no longer a programmer's tool, but everyone's.
The Five-Notebook Mini-Course
Work through these in order. The Git notebooks run real commands against a disposable repository, so you see an actual history, branch, and conflict, not a description of one.
Reproducibility
The other three pillars: seeds for repeatable randomness, an environment report and requirements.txt, and a data hash that detects a single changed value.
Project Structure
Scaffold a standard project (data / src / notebooks / outputs), separate config from code, use relative paths, and run a pipeline that reproduces its output byte for byte.
Git Basics
Real git: init, add, commit, then status, diff, and log on a growing history, plus a .gitignore that hides a secret and a data folder from version control.
Branching & Merging
Branch off, commit, and merge back with a real merge commit, then create a genuine merge conflict over a threshold and resolve it, ending on the commit graph.
Experiment Tracking
Read a run log, prove which runs are reproducible by grouping on code, data, and seed, catch the unseeded run that is not, and chart accuracy across runs.
An experiment-tracking log, 20 model runs. Each row records what a
reproducible result needs: the code version (git_commit), the input fingerprint
(data_hash), the random seed, the environment (python_version,
sklearn_version), and the accuracy produced. Notebook 5 uses it to prove the point: runs
that share code, data, and seed give identical accuracy (runs 1 and 2 both score 0.8793),
while the single run that set no seed drifts off on its own, the classic reproducibility bug, found
in one line.
Notebooks run on pandas, numpy,
matplotlib, and the built-in subprocess plus a real git (preinstalled on Colab).
Reproducibility in Data Science, ML & AI
In machine learning the stakes are higher, because a model has more moving parts to pin: not just code and data but the exact split, the hyperparameters, the framework versions, and every seed. The field has built dedicated tools for it, and they are all doing, at scale, what the notebooks here do by hand.
| Tool or practice | What it versions, and where you meet it |
|---|---|
| Git + GitHub | The code and its history, plus collaboration through branches and pull requests. The universal base layer. |
| Experiment trackers | MLflow, Weights & Biases: log every run's parameters, metrics, and artifacts automatically, the run log of Notebook 5, industrialized. |
| Data & model versioning | DVC, model registries: version large datasets and trained models alongside the code that made them. |
| Environment capture | requirements.txt, conda, and Docker containers that freeze the entire software stack, not just Python packages. |
| Pipelines & CI | Automated, version-controlled pipelines (the MLOps chapter) that rerun the whole analysis on a trigger and fail loudly if a result changes. |
This is not housekeeping; it is a scientific crisis. A widely discussed 2016 Nature survey found that most researchers had failed to reproduce someone else's results, and many had failed to reproduce their own. In computational fields the fix is unusually tractable, because a result is its code, data, seed, and environment, and all four can be captured and shared. That is why version control and reproducible pipelines have moved from nice-to-have to the baseline of credible quantitative work. A result no one can rerun is, increasingly, not accepted as a result at all.
π Key Takeaways
- βReproducibility rests on four pillars: versioned code, versioned data, a fixed random seed, and a pinned environment. Miss one and the answer can silently change.
- βThree are quick habits: seed your randomness, pin dependencies in a requirements.txt inside a virtual environment, and hash your data to detect changes.
- βGit versions the code: the loop is edit,
add,commit, building a trustworthy history you inspect withstatus,diff, andlog. - βA
.gitignorekeeps the wrong things out: big data, generated outputs, and secrets like API keys must never be committed. - βBranches make work safe: experiment on a branch, merge it back, and treat a merge conflict as a request for your decision, not an error.
- βThe everyday loop lives at the command line: pull, branch, edit, add, commit, push, open a pull request; and always pull before you push to avoid surprise conflicts.
- βCI/CD automates reproducibility: every push triggers a fresh build, rerun, and test (Jenkins, GitHub Actions, GitLab CI), and only releases when it passes, so a result that cannot be reproduced never ships.
- βGit is built into your tools: VS Code, RStudio, JupyterLab, JetBrains, and even SAS Studio put a graphical front door on the same commands, all pushing to the same shared history.
- βAn experiment log makes reproducibility checkable: record code, data, seed, and environment per run, and identical inputs provably give identical results.
- βML scales this up with MLflow, DVC, and Docker, but they only automate the same four pillars. A result no one can rerun is not really a result.
Practice Challenges
Five exercises, one per notebook area. Full solutions are in the companion solutions notebook.
Prove determinism
Write a seeded random summary and show two calls with the same seed match, while two unseeded calls differ.
np.random.default_rng(seed), then compare the results.Detect a changed dataset
Hash a dataframe, change one value, and confirm the hash changes.
hashlib.sha256(df.to_csv(index=False).encode()).Scaffold a project
Create the standard folders and a .gitignore that excludes data and outputs.
Path.mkdir for folders, then write the ignore patterns.Build a commit history
Initialize a repository and make three commits, then print the one-line log.
add then commit each time.Audit a run
Given a run record, report whether all four pillars (code, data, seed, environment) are present.
Solutions notebook
All five challenges worked in code: a determinism proof, a data-hash change detector, a project scaffold with a .gitignore, a real three-commit git history, and a four-pillar reproducibility audit.
Quiz: Test Yourself
Eight questions on the four pillars, Git basics, branching, and experiment tracking. Answer them, hit Check Answers, and keep refining until you score 100%. Your progress is saved.
This part turned the statistics of the book into a working practice. Python for Data Analysis and R for Statistics gave you the two great open-source languages; Statistical Software read the output of the commercial packages; SQL & Databases got the data out at the source; Excel & BI Tools covered the spreadsheet and the dashboard; and this chapter made the whole workflow reproducible and version-controlled. Next, the Communicating Results & Data Ethics part turns from producing analysis to sharing it responsibly. Browse the full Contents for what is published and what is on the way.