Python for Actuarial Students · Course 1
Lesson 1

Python Fundamentals & Financial Logic

Types, arithmetic, the truth about floating-point money, f-strings, and decisions — the building blocks of every actuarial calculation.

Lesson 1 · OverviewBy the end of this lesson you can…

  • Run Python three ways: the REPL, a script, and a notebook
  • Use the four core types: int, float, str, bool
  • Predict arithmetic results — including floating-point surprises
  • Convert safely between types
  • Produce clean financial output with f-strings
  • Write decisions with if / elif / else and guard inputs with assert
75 min
Concepts & worked examples
75 min
Lab: deductibles, payroll tax, tax brackets

Companion notebook: lesson-01-python-fundamentals.ipynb — worked examples and self-checking exercises.

SetupOne project for the whole course

cd ~/actuarial_work
uv init foundations --package --python 3.12
cd foundations
uv add numpy scipy matplotlib
uv add --dev jupyterlab pytest ruff
mkdir notebooks tests
code .

Works as-is in PowerShell (Course 0W) and zsh (Course 0M).

--package makes the project an installable package. Code you put in src/foundations/ can be imported from anywhere in the project — notebooks included:

foundations/
├── notebooks/      lesson01.ipynb, lesson01_lab.ipynb…
├── src/foundations/ your reusable code
├── tests/          Lesson 8
└── pyproject.toml

Commit to Git at the end of every session. By Lesson 8 this is a tested library.

Part 1

The Python Runtime

What actually runs your code, and three ways to talk to it.

RuntimePython is an interpreter

Your codetext in a .py file or a notebook cell
→
The interpreterpython reads it one statement at a time
→
Executionobjects created in memory; output produced

REPL

uv run python — type a line, see the result. Quit with exit().

Read–Eval–Print Loop

Script

uv run python calc.py — runs top to bottom, then exits.

Repeatable jobs

Notebook

Cells run one at a time; results stay in memory between cells.

Exploration & explanation

The prompt >>> in examples means “typed into the REPL”. In notebooks, the last expression in a cell is displayed automatically (Course 0).

Part 2

Values, Types & Arithmetic

Everything in Python is an object with a type.

TypesThe four core types

TypeHoldsExamples
intWhole numbers, any size360, -5, 1_000_000
floatReal numbers (approximately!)0.05, 1.5e6, -0.0
strText"TERM", 'P001'
boolTruth valuesTrue, False
>>> type(360)
<class 'int'>
>>> type(0.05)
<class 'float'>
>>> 2 ** 100          # ints never overflow
1267650600228229401496703205376

Underscores make big numbers readable: sum_assured = 250_000. Python ignores them.

VariablesNames point at values

annual_rate = 0.05          # a float
term_years = 10             # an int
product = "TERM"            # a str
is_smoker = False           # a bool

v = 1 / (1 + annual_rate)
pv = 1000 * v ** term_years

term_years = "ten"          # legal... but a bad idea
  • = means “make this name refer to that value” — not mathematical equality
  • Python is dynamically typed: a name can point to any type, and can be re-pointed
  • Names: snake_case, descriptive, no spaces, can’t start with a digit

annual_premium beats ap beats x. Your reviewer — and future you — will thank you.

ArithmeticOperators and precedence

ExpressionResultNote
7 / 23.5/ always gives a float
7 // 23floor division
-7 // 2-4floor, not truncation!
7 % 21remainder
2 ** 0.51.4142135623730951power
True + True2bool is a kind of int

Precedence, highest first:

  1. **
  2. unary -
  3. * / // %
  4. + -

-2 ** 2 is -4, not 4. When in doubt, add parentheses: (1 + i) ** -n.

Floating pointWhy 0.1 + 0.2 != 0.3

>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1 + 0.2 == 0.3
False
>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True

Floats are stored in binary. Just as ⅓ has no exact decimal form, 0.1 has no exact binary form. Every float carries about 15–17 significant digits.

Never test floats with ==. Use math.isclose(a, b) or a tolerance: abs(a - b) < 1e-9.

For valuation and pricing, 15 significant digits is plenty — errors at the 16th digit are far below any materiality threshold. The danger is comparisons and cents, not precision.

Floating pointRounding, and exact money with Decimal

>>> round(2.5), round(3.5)    # banker's rounding
(2, 4)
>>> round(2.675, 2)           # 2.675 is really 2.67499999...
2.67

round() rounds halves to the even neighbor, and works on the binary value, which may sit just below the half.

from decimal import Decimal, ROUND_HALF_UP

>>> Decimal("0.10") + Decimal("0.20")
Decimal('0.30')
>>> Decimal("2.675").quantize(Decimal("0.01"),
...                           rounding=ROUND_HALF_UP)
Decimal('2.68')

Build Decimals from strings, not floats.

Rule of thumb: compute with float; round only when presenting. Use Decimal where cents must reconcile exactly — ledgers, billing, statutory filings.

ConversionsMoving between types

ExpressionResult
int("42")42
float("3.5")3.5
str(1.5)'1.5'
int(3.9)3 — truncates toward zero
int(-3.9)-3
int("3.5")ValueError

Truthiness: what counts as False?

>>> bool(0), bool(0.0), bool(""), bool([]), bool(None)
(False, False, False, False, False)
>>> bool("0")        # a non-empty string!
True

Data read from files arrives as text. "0" is truthy and "100" + "5" is "1005". Convert first.

Part 3

Formatting Financial Output

f-strings and the format-spec mini-language.

f-stringsThe format spec, piece by piece

f"{value:>12,.2f}"

Align

< left · > right · ^ center

Width

minimum characters — lines up columns

Grouping

, or _ thousands separator

Precision & type

.2f fixed · .1% percent · .3e scientific
rate, premium, claims, n = 0.0425, 1234.5, -5321.789, 360

f"{rate:.2%}"        # '4.25%'
f"{premium:,.2f}"    # '1,234.50'
f"{claims:+,.0f}"    # '-5,322'        + forces a sign
f"{n:>6}"           # '   360'
f"{premium:>12,.2f}" # '    1,234.50'
f"{12345678.9:.3e}"  # '1.235e+07'
f"{rate=}"           # 'rate=0.0425'   handy for debugging

f-stringsA tidy results table

i = 0.05
print(f"{'Year':>4}  {'v^t':>8}  {'PV of 1,000':>12}")
for t in (1, 5, 10, 20):
    v_t = (1 + i) ** -t
    print(f"{t:>4}  {v_t:>8.5f}  {1000 * v_t:>12,.2f}")
Year       v^t   PV of 1,000
   1   0.95238        952.38
   5   0.78353        783.53
  10   0.61391        613.91
  20   0.37689        376.89

Loops (for) arrive properly in the next lesson — for now, read it as “do the indented lines for each value of t”.

Match header widths to value widths and every column lines up — no spreadsheet needed.

Part 4

Decisions

Comparisons, if / elif / else, and guarding inputs.

ConditionalsBranching on conditions

age = 47
smoker = False

if age < 18:
    rating = "Decline"
elif age <= 50 and not smoker:
    rating = "Preferred"
elif age <= 65:
    rating = "Standard"
else:
    rating = "Refer to underwriter"

print(rating)     # Preferred
  • Conditions are checked top to bottom; the first true branch runs, the rest are skipped
  • The colon and indentation (4 spaces) define the block — there are no braces
  • Comparisons: == != < <= > >=
  • Combine with and, or, not
  • Chain ranges naturally: 18 <= age < 65

= assigns; == compares. if age = 47: is a syntax error.

ConditionalsGuarding inputs with assert

loss = -250.0
deductible = 500.0

assert loss >= 0, f"loss must be non-negative, got {loss}"
assert deductible >= 0, "deductible must be non-negative"
AssertionError: loss must be non-negative, got -250.0
  • assert condition, message stops the program if the condition is false
  • A crash with a clear message beats a silently wrong reserve

Assertions can be switched off (python -O). Use them for sanity checks while developing. For validating real inputs, you’ll raise ValueError — Lesson 3.

Actuarial Lab · 75 minFinancial logic with conditionals

In notebooks/lesson01_lab.ipynb, build three calculators. Each needs a Markdown cell explaining the rule, code, and a formatted output table.

1 · Deductible & limit

What does the insurer pay on a loss, given a per-claim deductible and a policy limit?

2 · Payroll tax

Social Security tax up to the wage base; Medicare on everything, with a surcharge above a threshold.

3 · Tax brackets

Progressive income tax: each slice of income taxed at its own rate.

Write the rule on paper with a worked example before coding. Then check your code reproduces your hand calculation.

Lab · Part 1Deductible and limit

Insurer pays the loss above the deductible d, but never more than the limit u:

payment = min(max(loss − d, 0), u)

deductible = 500
limit = 10_000

loss = 2_500
if loss <= deductible:
    payment = 0
elif loss - deductible >= limit:
    payment = limit
else:
    payment = loss - deductible
LossPaymentBranch
3000below deductible
2,5002,000in the layer
12,00010,000capped at limit

Tasks

  1. Write the if/elif/else version; check all three rows
  2. Write the one-line min/max version; confirm they agree
  3. Add asserts: loss ≥ 0, deductible ≥ 0, limit > 0
  4. Edge cases: loss exactly 500? Exactly 10,500?

Lab · Part 2Social Security & Medicare (employee share)

ComponentRateApplies to
Social Security6.2%wages up to the wage base ($184,500 for 2026)
Medicare1.45%all wages
Additional Medicare0.9%wages above $200,000

US rules, simplified to a single filer with one employer. Check the current wage base each year — it’s a parameter, not a constant.

WagesSoc. Sec.MedicareTotal
60,0003,720.00870.004,590.00
184,50011,439.002,675.2514,114.25
250,00011,439.004,075.0015,514.00

Store thresholds and rates in clearly named variables at the top of the cell (WAGE_BASE = 184_500). Next year you change one line.

Lab · Part 3Progressive tax brackets

Illustrative brackets — each rate applies only to the income within its band:

Taxable incomeRate
0 – 10,00010%
10,000 – 40,00015%
40,000 – 100,00025%
over 100,00035%

Worked example, 55,000: 1,000 + 4,500 + 3,750 = 9,250

IncomeTaxAverage rate
8,000800.0010.00%
55,0009,250.0016.82%
150,00038,000.0025.33%

Tasks

  1. Solve it with an if/elif chain — one branch per bracket
  2. Print tax, marginal rate, and average rate with f-strings
  3. Stretch: notice the repetition? In the next lesson, loops remove it.

RecapLesson 1 checklist

  • I can run code in the REPL, a script, and a notebook
  • I know int, float, str, bool and check with type()
  • I can predict /, //, %, ** results and precedence
  • I never compare floats with ==
  • I know when to reach for Decimal
  • I convert text to numbers before calculating
  • I can format currency, percentages, and aligned columns
  • I can write if/elif/else logic and guard it with assert
Up next
Lesson 2

Collections, Iteration & Mathematical Series

Lists, dictionaries, and loops — and using them to sum infinite series, value perpetuities, and amortize loans.