print, arithmetic, NumPy, pandas, and charts — then publish your notebook and commit the finished project.
print() and f-stringsThis is a taste, not a lesson plan for each topic — the following courses go deep on each.
Companion notebook: module-08b-taste-of-python.ipynb — worked examples and self-checking exercises.
uv add numpy pandas matplotlib seaborn
uv add --dev jupyterlab ruff # skip any you already added in Part A
code .
Then create notebooks\02_taste.ipynb with the .venv kernel and type along. Put a Markdown heading above each section as you go.
Typing (not pasting) the examples is the fastest way to learn. Make typos. Read the error messages. Fix them.
print() and Python as a calculator.
print("Hello, actuaries") # Hello, actuaries
print("PV =", 772.17) # PV = 772.17
print("a", "b", "c", sep=" | ") # a | b | c
name = "Jane"
rate = 0.0372
reserve = 1234567.891
print(f"Analyst: {name}") # Analyst: Jane
print(f"Lapse rate: {rate:.1%}") # Lapse rate: 3.7%
print(f"Reserve: {reserve:,.2f}") # Reserve: 1,234,567.89
print(f"{'Age':<5}{'qx':>8}") # Age qx
f-strings (f"…") insert values into text. After a colon, a format spec controls the look:
:.2f | 2 decimal places |
:,.2f | thousands separator too |
:.1% | as a percentage |
:<5 / :>8 | left / right align in a width |
| Operator | Example | Result |
|---|---|---|
+ - * | 3 * (2 + 4) | 18 |
/ | 7 / 2 | 3.5 |
// floor division | 7 // 2 | 3 |
% remainder | 7 % 2 | 1 |
** power | 1.05 ** 2 | 1.1025 |
^ is not “power” in Python (it’s bitwise XOR). Excel users: use **.
i = 0.05 # effective annual rate
n = 10 # term in years
v = 1 / (1 + i) # discount factor
# Accumulate 1,000 for 10 years
round(1000 * (1 + i) ** n, 2) # 1628.89
# Present value of 1,000 due in 10 years
round(1000 * v ** n, 2) # 613.91
# Annuity-immediate: a-angle-n at 5%
a_n = (1 - v ** n) / i
round(a_n, 4) # 7.7217
A vector and matrix calculator.
import numpy as np
t = np.arange(1, 11) # [ 1 2 3 ... 10]
v = 1.05 ** -t # 10 discount factors at once
cf = np.full(10, 100.0) # 100 paid at the end of each year
pv = (cf * v).sum() # element-wise multiply, then add up
print(round(pv, 2)) # 772.17
round(pv, 2) # np.float64(772.17): same value, NumPy type
cf @ v # same thing as a dot product
1.05 ** -t computes all ten powers@ is matrix / dot-product multiplicationSame answer as 100 * a_n from the previous slide — but this version handles any cash-flow pattern.
Three bonds, their cash flows at t = 1, 2, 3, and market prices. Which discount factors d1, d2, d3 price them all?
Each row: price = Σ cash flow × discount factor. That’s C d = P.
C = np.array([[105, 0, 0], # 1-yr bond, 5% coupon
[ 6, 106, 0], # 2-yr bond, 6% coupon
[ 4, 4, 104]]) # 3-yr bond, 4% coupon
P = np.array([100.00, 101.00, 96.50])
d = np.linalg.solve(C, P)
d.round(4) # array([0.9524, 0.8989, 0.8567])
# Implied spot rates, in %
t = np.arange(1, 4)
spot = d ** (-1 / t) - 1
(spot * 100).round(2) # array([5. , 5.47, 5.29])
np.linalg.solve handles 3 equations or 3,000 — the same one line. (You just bootstrapped a yield curve.)
Spreadsheets, programmable.
Save this as data\policies.csv (VS Code: New File, paste, save):
policy_id,issue_age,sex,product,sum_assured,annual_premium,status
P001,34,F,TERM,250000,310.50,IN_FORCE
P002,45,M,TERM,500000,980.00,LAPSED
P003,29,F,WHOLE_LIFE,100000,1450.00,IN_FORCE
P004,52,M,WHOLE_LIFE,200000,3890.00,IN_FORCE
P005,41,F,TERM,300000,455.25,LAPSED
P006,38,M,TERM,400000,620.00,IN_FORCE
P007,60,F,WHOLE_LIFE,150000,4210.00,DEATH_CLAIM
P008,33,M,TERM,350000,402.75,IN_FORCE
This file is small and synthetic, so it lives in data\ and gets committed. Real extracts go in data\raw\, which .gitignore keeps out of Git (Module 5).
import pandas as pd
df = pd.read_csv("../data/policies.csv")
df.head() # first 5 rows, as a table
df.shape # (8, 7) → rows, columns
df.dtypes # the type of each column
df.describe() # count, mean, std, min, max…
| policy_id | issue_age | product | annual_premium | status | |
|---|---|---|---|---|---|
| 0 | P001 | 34 | TERM | 310.50 | IN_FORCE |
| 1 | P002 | 45 | TERM | 980.00 | LAPSED |
| 2 | P003 | 29 | WHOLE_LIFE | 1450.00 | IN_FORCE |
(some columns omitted to fit the slide)
Excel files work too: pd.read_excel("../data/raw/extract.xlsx", sheet_name="Policies") — that’s what openpyxl is for.
# One column
df["annual_premium"].mean() # np.float64(1539.8125)
# Filter rows
df[df["issue_age"] >= 50] # P004, P007
# A new calculated column
df["rate_per_mille"] = (
df["annual_premium"] / df["sum_assured"] * 1000
)
# Count by category
df["status"].value_counts()
# Summarize by group (a pivot table)
df.groupby("product")["annual_premium"].mean().round(2)
status
IN_FORCE 5
LAPSED 2
DEATH_CLAIM 1
Name: count, dtype: int64
product
TERM 553.70
WHOLE_LIFE 3183.33
Name: annual_premium, dtype: float64
groupby is a pivot table you can re-run on next quarter’s extract without touching a single cell.
matplotlib for control, seaborn for statistical graphics.
import matplotlib.pyplot as plt
avg = df.groupby("product")["annual_premium"].mean()
fig, ax = plt.subplots(figsize=(6, 4))
ax.bar(avg.index, avg.values, color=["#0e6b6b", "#c26a1b"])
ax.set_title("Average annual premium by product")
ax.set_ylabel("Premium")
fig.savefig("../output/premium_by_product.png",
dpi=150, bbox_inches="tight")
In a notebook the chart appears below the cell automatically. savefig writes a file to output\ for reports.
import seaborn as sns
sns.set_theme(style="whitegrid")
sns.scatterplot(data=df, x="issue_age",
y="annual_premium", hue="product")
# other one-liners to try:
sns.boxplot(data=df, x="product", y="annual_premium")
sns.histplot(data=df, x="issue_age", bins=5)
sns.countplot(data=df, x="status")
seaborn understands DataFrames: name the columns, and it handles colors, legends, and axis labels.
HTML and PDF for people who don’t run Python.
# HTML (always works — jupyterlab already installed nbconvert)
uv run jupyter nbconvert --to html notebooks/02_taste.ipynb --output-dir output
# Re-run every cell first, so the export is guaranteed fresh
uv run jupyter nbconvert --to html --execute notebooks/02_taste.ipynb --output-dir output
# Hide the code — results and prose only, for managers and reviewers
uv run jupyter nbconvert --to html --no-input notebooks/02_taste.ipynb --output-dir output
# PDF via LaTeX (needs MiKTeX from Module 6)
uv run jupyter nbconvert --to pdf notebooks/02_taste.ipynb --output-dir output
# PDF via a headless browser (no LaTeX needed)
uv add --dev "nbconvert[webpdf]"
uv run jupyter nbconvert --to webpdf --allow-chromium-download notebooks/02_taste.ipynb --output-dir output
--execute + --no-input is a one-line, reproducible reporting pipeline: fresh numbers, no hidden state, no code on show.
output\Under the hood this uses the same nbconvert, running in your .venv kernel. PDF export needs a LaTeX distribution (MiKTeX).
No LaTeX? Export to HTML, open it in your browser, and use Print → Save as PDF.
actuarial_intro created with uv, standard folders, packages addedsrc\premium_summary.py, that uses pandasnotebooks\02_taste.ipynb, with prose, calculations, a table, and a chartoutput\.gitignoreAnyone can run:
git clone <your repo>
cd actuarial_intro
uv sync
uv run python src/premium_summary.py
…and get the same output as you.
src\premium_summary.py"""Summarize the sample policy file by product."""
from pathlib import Path
import pandas as pd
DATA = Path("data") / "policies.csv"
df = pd.read_csv(DATA)
print(f"{len(df)} policies loaded from {DATA}")
print()
summary = (
df.groupby("product")["annual_premium"]
.agg(["count", "mean"])
.round(2)
)
print(summary)
PS> uv run python src/premium_summary.py
8 policies loaded from data\policies.csv
count mean
product
TERM 5 553.70
WHOLE_LIFE 3 3183.33
Notice pathlib printed data\policies.csv — backslash on Windows — though we wrote /-style parts. Portable code, native display (Module 1).
Save → Ruff formats it. Any squiggles? Fix them.
notebooks\02_taste.ipynbBuild it from this deck’s examples, one section per heading:
a_npolicies.csv, describe(), value_counts(), groupbyoutput\) and the seaborn scatterBefore exporting: Restart kernel & Run All. If it fails top-to-bottom, fix it before anyone else sees it.
uv run jupyter nbconvert --to html --execute `
notebooks/02_taste.ipynb --output-dir output
start output\02_taste.html
The backtick ` at the end of a line continues a PowerShell command onto the next line.
git init # skip if uv already created .git
notepad .gitignore # add the Module 5 sections (output/, data/raw/, .env …)
git status # .venv, output\ must NOT be listed
git add .gitignore pyproject.toml uv.lock .python-version
git commit -m "Set up actuarial_intro with numpy, pandas, matplotlib, seaborn"
git add data/policies.csv src/
git commit -m "Add sample policy data and premium summary script"
git add notebooks/
git commit -m "Add taste-of-Python notebook"
git add README.md .vscode/
git commit -m "Document project and share editor settings"
git log --oneline
# Optional: create an empty private repo on GitHub, then
git remote add origin https://github.com/<you>/actuarial_intro.git
git push -u origin main
Your PC is now a programmer’s PC.
Next: Python fundamentals in depth — built on the workspace, tools, and habits you set up here.