Structure a Jupyter notebook so that someone else can rerun it from top to bottom and get the same results.
Why this matters
Jupyter notebooks are the everyday tool of scientific Python: code, results, plots and explanation in one document. They are also notorious for being impossible to rerun. Cells were executed out of order, a variable was defined in a cell that was later deleted, a file path points to someone's desktop, the random seed was never set, and the package versions are unknown. Six months later, not even the author can reproduce Figure 3.
In engineering and science that is not a style issue. A supervisor checking a design calculation, a reviewer checking a paper, a regulator auditing a QC report or a colleague taking over your project all need to rerun your analysis and get the same answer. Reproducibility means: same inputs + same code + same environment = same outputs, every time.
The one rule: Restart and Run All
Before you share, commit or report from a notebook, choose Kernel > Restart Kernel and Run All Cells (JupyterLab) or Runtime > Restart and run all (Google Colab). If it finishes without errors and the results match what you reported, the notebook is at least reproducible on your machine. If it fails, it was relying on hidden state.
The execution counters ([1], [2], ...) next to each cell are a quick check: in a notebook ready to share they run 1, 2, 3 from top to bottom with no gaps or jumps.
A structure that works
1. Title and purpose What question does this answer? Author, date, data version.
2. Setup All imports, the random seed, constants, file paths, settings.
3. Load data Read raw files (never modified) and print their shape and head.
4. Clean Each cleaning step with a short comment and a row count.
5. Analyse Calculations, fits, simulations.
6. Results Final figures and tables, saved to an outputs/ folder.
7. Environment Package versions printed at the end.
Short markdown cells between sections explain the why, not just the what: "Rows with OVER are detector saturation; we drop them (2 of 480 rows)."
The setup cell
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pathlib import Path
SEED = 2026
rng = np.random.default_rng(SEED)
DATA_DIR = Path("data") # relative to the project folder
OUT_DIR = Path("outputs")
OUT_DIR.mkdir(exist_ok=True)
G = 9.81 # m/s^2
Everything that might change between runs (seed, paths, constants, thresholds) lives here, in one place, with units.
Habits that make notebooks rerunnable
- Relative paths only.
data/run42.csv, notC:\Users\me\Desktop\run42.csv. Keep data, notebook and outputs together in one project folder. - Never edit raw data. Load it, transform it in code, and save results separately.
- Set every seed (NumPy's
default_rng, plus any library-specific seeds). - Put reusable code in functions, and move long functions into a
.pymodule you import. Notebooks are for the narrative; modules are for tested, reusable logic. - No manual steps. If you had to click something or copy a number by hand, the notebook is not reproducible. Write it in code.
- Record the environment. Pin versions in a
requirements.txt(for examplenumpy==2.1.3) or anenvironment.yml, and print versions at the end:
import sys, numpy, pandas, matplotlib
print(sys.version)
print("numpy", numpy.__version__, "| pandas", pandas.__version__,
"| matplotlib", matplotlib.__version__)
- Version control. Keep the project in git. Notebooks store outputs inside the file, which makes diffs noisy; clearing outputs before committing (or using a tool such as
jupytextornbstripout) keeps history readable. - Save figures from code (
fig.savefig(OUT_DIR / "fig3_stress.pdf")) rather than copying them from the screen.
Sharing and running elsewhere
- Google Colab runs notebooks in the browser with numpy, pandas, matplotlib and SciPy preinstalled; a Google account is needed. Free tier limits apply, and files uploaded to a session disappear when it ends.
- JupyterLite runs a notebook entirely in your browser with no install and no account (it uses Pyodide, like this site's exercises).
- Local Jupyter via
pip install jupyterlabor the Anaconda distribution. - Binder can launch a public git repository as a live notebook, using your
requirements.txt.
Whichever you use, test the notebook in a fresh environment at least once: a new Colab session or a new virtual environment. That is the closest you can get to being the next person who runs it.
Common mistakes
- Out-of-order execution: results depend on a cell that was run, edited and never rerun.
- Deleted cells whose variables still live in memory.
- Absolute paths and files that exist only on your machine.
- Unseeded randomness in simulations, bootstraps or train/test splits.
- Giant notebooks with hundreds of cells and copy-pasted code instead of functions.
- Unknown versions: a function's default changed between library releases, and the numbers moved.
Quick recap
- Restart and Run All before you share; execution counters should run 1, 2, 3...
- Structure: purpose, setup (imports, seed, paths, constants), load, clean, analyse, results, environment.
- Relative paths, untouched raw data, seeds, functions, no manual steps, pinned versions, git.
- Test in a fresh environment.
Try it
The Do tab walks you through building a small notebook in Colab or JupyterLite that passes the Restart and Run All test.