Project structure, your first script, and notebooks in a managed environment — every tool from this course, working together.
src, data, notebooks, output, testshello.py with uv runprint()≈ 40 min, including a checkpoint exercise
print(), arithmetic, NumPy, pandas≈ 90 min, including the lab
Companion notebook: module-08a-first-project.ipynb — worked examples and self-checking exercises.
C:\actuarial_workToday the tools fade into the background, and Python takes center stage.
A place for everything, and everything in its place.
actuarial_intro\
├── .venv\ managed by uv — never touch
├── .vscode\ editor config (Module 7)
├── data\
│ └── raw\ inputs, read-only, not in Git
├── notebooks\ exploration & reports
├── output\ charts, exports — regenerable
├── src\ reusable Python code
├── tests\ checks that code is right
├── .gitignore
├── .python-version
├── pyproject.toml
├── README.md
└── uv.lock
| Folder | Rule of thumb |
|---|---|
data\raw | Never edited by hand or by code. The source of truth. |
src | Scripts and functions you trust and reuse |
notebooks | Numbered: 01_load.ipynb, 02_explore.ipynb… |
output | Anything code produces. Safe to delete and rebuild. |
tests | Automated checks (future courses) |
You can always rerun from the original extract. No “which version of the CSV did I clean?”.
Delete output\, rerun, and get the same numbers. That’s reproducibility.
A colleague — or a reviewer — opens any project and knows where to look.
This is the same discipline as a well-built Excel model: separate input, calculation, and output tabs — enforced by folders instead of goodwill.
cd C:\actuarial_work
uv init actuarial_intro --python 3.12
cd actuarial_intro
mkproj . # your Module 3 script!
mkdir data\raw
No mkproj? Plain PowerShell does it too:
mkdir src, data\raw, notebooks, output, tests
Git doesn’t track empty folders. Drop an empty placeholder file in any folder you want to keep:
ni tests\.gitkeep, output\.gitkeep
uv init made a main.py at the top level. We’ll keep code in src\, so delete it: rm main.py
hello.py, run the uv way.
In VS Code: right-click src → New File… → hello.py
"""hello.py — my first Python script."""
name = "Actuarial Student"
years_to_exam = 0.5
print(f"Hello, {name}!")
print("Weeks until exam:", years_to_exam * 52)
In the terminal (Ctrl+`), from the project root:
PS C:\actuarial_work\actuarial_intro> uv run python src/hello.py
Hello, Actuarial Student!
Weeks until exam: 26.0
Forward slash in src/hello.py — works on every OS (Module 1). The ▷ button in VS Code does the same, using the .venv interpreter.
.venv matches uv.lock.venv starts"""…""" on line 1 is a docstring — a description of the filename = … creates a variable: a label attached to a valuef"…{name}…" is an f-string: values dropped into textprint() writes to the terminalA script runs fresh, top to bottom, every time. That makes it predictable — the opposite of a notebook’s hidden state.
Two front-ends, one environment.
Script (.py) | Notebook (.ipynb) | |
|---|---|---|
| Runs | Top to bottom, every time | Cell by cell, in any order |
| Best for | Repeatable jobs, reusable functions | Exploring data, explaining results |
| Output | Terminal text, files | Rich tables and charts, saved inline |
| In Git | Clean, readable diffs | JSON — noisier diffs |
| Lives in | src\ | notebooks\ |
Explore in a notebook; once something works, move it into a script.
uv add --dev jupyterlab
uv run jupyter lab
Navigate to notebooks\ → New → Notebook.
.venvnotebooks\01_hello.ipynbNeeds ipykernel in the environment — jupyterlab brings it along.
Either way, the kernel is the project’s .venv Python, so the notebook sees exactly the packages in uv.lock.
Relative paths start from the current working directory (Module 1) — and it differs:
| Running… | CWD is… | Data path |
|---|---|---|
a script via uv run from the root | project root | data/raw/x.csv |
a notebook in notebooks\ | notebooks\ | ../data/raw/x.csv |
Check any time:
from pathlib import Path
Path.cwd()
# WindowsPath('C:/actuarial_work/actuarial_intro/notebooks')
A robust habit in notebooks:
DATA = Path("..") / "data" / "raw"
df = pd.read_csv(DATA / "policies.csv")
print()Out[n] labelprint() output appears with no Out label — and works anywhere in the cellrate * 2And yes, 0.15000000000000002 — computers store decimals in binary. Round for display: round(rate * 3, 4).
| age | qx | |
|---|---|---|
| 0 | 30 | 0.0008 |
| 1 | 40 | 0.0016 |
| Want… | Use |
|---|---|
| A formatted table | End the cell with df |
| Several tables from one cell | display(df.head())display(df.describe()) |
| Plain text (as a script would print) | print(df) |
To hide an unwanted Out | End the line with ; |
display() is built into Jupyter — no import needed. In a .py script, only print() shows anything.
actuarial_intro with uv init and the standard folderssrc\hello.py; run it with uv run python src/hello.pyuv add --dev jupyterlab; create notebooks\01_hello.ipynb using the .venv kernelprint(), two expressions in one cell, a semicolonPath.cwd() from both the notebook and the script. Explain the difference.Keep this project — Part B builds on it, and the final lab commits it to Git.
Stretch: git init (if uv didn’t), extend .gitignore from Module 5, and make a first commit now.
Arithmetic, NumPy, pandas, and charts — then publish your notebook and commit the finished project.