Appendix B — Cheat Sheets

Picture a small note stuck on the workshop wall: not for learning from scratch, but to remind you “how was that syntax again?”. That is a cheat sheet. This appendix gives three sheets, one per level of the book: Basic, Intermediate, and Advanced. Every item names its source chapter, so if a line is unclear you know where to read.

All the checking scripts of this appendix are in the kode/ch72/ folder. On the web version, you can also download them all at once (zip).

How to read the sheets

Every line of code on a sheet is really executed by a script. The number after the -> sign is not typed by hand but is what Python produced when the script ran. If a line is wrong (for example a function was renamed in a new version), the script stops and the sheet is not made.

%%{init: {"flowchart": {"diagramPadding": 40}}}%%
flowchart TD
    A["Sheet content:<br/>code + comment"] --> B["Run one by one<br/>in a temporary folder"]
    B --> C["Real result<br/>(after the -> sign)"]
    C --> D["Sheet text in the book"]
    C --> E["Print-ready A4 PDF"]

From the sheet’s content to the text in the book and the PDF.

Mark Meaning
(no mark) The line was really executed; the result after -> was produced by Python. numpy numbers are written without the np.float64(...) wrapper to keep it short.
[chk] A command option or function was checked to exist (through --help or inspect) but not fully run.
[ref] Reference: not run here. The reason is in its comment (for example Docker is not installed, it needs the internet, or it needs a big download).
$ A command for the terminal, not Python code.
WarningWARNING

These sheets were made on the author’s computer (Windows 11, Python 3.14, an NVIDIA graphics card). Lines that depend on hardware, for example cupy (needs an NVIDIA GPU) or torch on 'cuda', will differ or fail on a computer without that hardware. Numbers such as the 1563 m distance or the 0.93 accuracy come from the library versions installed here; on your computer they may shift a little.

Sheet 1: Basic (Chapters 1-15)

Print-ready PDF (A4, one page). It covers: terminal, variables and types, operators, conditions, loops, data structures, functions, modules, errors, files, CSV/JSON, Excel and PDF.

=== Cheat sheet Basic (Ch. 1-15) ===

## Terminal and Python  [Ch. 3-4]
$ cd project_folder  # change folder [ref]
$ python hello.py  # run a script [ref]
$ python --version  # check version
print('Hello', 3, sep=', ', end='!')  # print -> Hello, 3!
# comment: a note for humans

## Variables and data types  [Ch. 5]
n, h, ok = 42, 1.5, True  # int, float, bool
name = 'Teak'  # str (text)
type(h).__name__  # type name -> 'float'
int('42') + 1  # text to number -> 43
float('2.5')  # text to decimal -> 2.5
str(7) + ' kg'  # number to text -> '7 kg'
bool(0), bool('a')  # empty/zero = False -> (False, True)
f'{3.14159:.2f}'  # two decimals -> '3.14'
f'{1234567:,}'  # thousands separator -> '1,234,567'
x = None  # no value

## Input  [Ch. 5-6]
age = int(input('Age? '))  # input() is always text!
age + 1  # after converting to int -> 43

## Operators  [Ch. 6]
7 / 2, 7 // 2, 7 % 2  # divide, floor, rest -> (3.5, 3, 1)
2 ** 10  # power -> 1024
divmod(1000, 30)  # (quotient, rest) -> (33, 10)
round(3.14159, 2)  # rounding -> 3.14
1 < 2 < 3  # chained comparison -> True
(1 > 2) or not (3 > 4)  # and, or, not -> True
'ea' in 'Teak'  # contained in? -> True
n = 5  # add and store
n += 2

## Conditions  [Ch. 7]
n = 520  # if / elif / else
if n >= 500:
    s = 'ready'
elif n >= 400:
    s = 'almost'
else:
    s = 'not yet'
s  # -> 'ready'
'ready' if n >= 500 else 'not yet'  # one-liner -> 'ready'
kind = 'cattle'  # pattern matching
match kind:
    case 'cattle' | 'goat':
        k = 'ruminant'
    case _:
        k = 'other'
k  # -> 'ruminant'

## Loops  [Ch. 8]
total = 0  # accumulator
for b in [3, 5, 7]:
    total += b
total  # -> 15
list(range(0, 10, 3))  # start, stop (excluded), step -> [0, 3, 6, 9]
list(enumerate('ab', 1))  # index + item -> [(1, 'a'), (2, 'b')]
g = 1800  # while + break
while True:
    g += 150
    if g >= 2400:
        break
g  # -> 2400
[i for i in range(5) if i % 2 == 0]  # list comprehension -> [0, 2, 4]

## Lists and slicing  [Ch. 9]
xs = [3, 1, 2]  # list
xs.append(4)  # add at the end
xs[0], xs[-1]  # first, last -> (3, 4)
xs[1:3]  # slice [start:stop) -> [1, 2]
sorted(xs)  # sorted (original kept) -> [1, 2, 3, 4]
xs.sort(reverse=True)  # sort in place
xs[::-1]  # reversed -> [1, 2, 3, 4]
len(xs), sum(xs), max(xs)  # count, sum, largest -> (4, 10, 4)

## Dict, tuple, set  [Ch. 9]
d = {'Teak': 32.5, 'Mahogany': 28.0}  # dict: key -> value
d['Meranti'] = 45.2  # add/change
d.get('Sengon', 0)  # default if missing -> 0
{k: v for k, v in d.items() if v > 30}
    # dict comprehension -> {'Teak': 32.5, 'Meranti': 45.2}
latitude, longitude = (-6.6, 106.8)  # tuple: immutable
{1, 2, 3} & {2, 3, 4}  # set: intersection -> {2, 3}
{1, 2} | {2, 3}  # set: union -> {1, 2, 3}

## Functions  [Ch. 10]
def area(r, pi=3.14):  # default value pi
    return pi * r * r
area(2)  # uses the default -> 12.56
area(r=2, pi=3)  # named arguments -> 12
times = lambda a, b: a * b  # tiny function
times(3, 4)  # -> 12
sorted(['bb', 'a'], key=len)  # sort key -> ['a', 'bb']
# no return means the result is None

## Modules  [Ch. 11]
import math  # import a module
round(math.pi, 3)  # -> 3.142
from datetime import date, timedelta  # import parts
date(2026, 1, 15) + timedelta(days=270)
    # date arithmetic -> datetime.date(2026, 10, 12)
date(2026, 10, 12).strftime('%d-%m-%Y')  # format a date -> '12-10-2026'
import random  # seeded random
random.seed(7)
random.sample(range(1, 13), 3)  # -> [6, 3, 7]

## Errors  [Ch. 12]
def to_number(t):  # catch the error
    try:
        return float(t)
    except ValueError:
        return None
to_number('12.5'), to_number('abc')  # safe from bad text -> (12.5, None)
try:  # raise, except, finally
    raise ValueError('broken')
except (ValueError, KeyError) as e:
    message = str(e)
finally:
    pass
message  # -> 'broken'
# your own error
class DataError(Exception):
    pass
assert 1 + 1 == 2, 'must be 2'  # check an assumption
import logging  # log a warning
logging.warning('pH odd')

## Files and Path  [Ch. 13]
from pathlib import Path  # file path
p = Path('a.txt')
# w overwrite, a append, r read
with open(p, 'w', encoding='utf-8') as f:
    f.write('1\n2\n')
# read line by line
with open(p, encoding='utf-8') as f:
    total = sum(int(b) for b in f)
total  # -> 3
p.read_text(encoding='utf-8')  # read all -> '1\n2\n'
q = Path('data/b.txt')  # parts of a path
q.name, q.suffix, q.stem  # -> ('b.txt', '.txt', 'b')
p.exists()  # does it exist? -> True

## CSV and JSON  [Ch. 14]
import csv, json  # built-in modules
# newline='' required
with open('t.csv', 'w', newline='',
          encoding='utf-8') as f:
    w = csv.DictWriter(f, ['k', 'n'])
    w.writeheader()
    w.writerow({'k': 'A', 'n': 800})
# all values become text!
with open('t.csv', newline='',
          encoding='utf-8') as f:
    rows = list(csv.DictReader(f))
rows  # -> [{'k': 'A', 'n': '800'}]
json.dumps({'a': 'Ø'}, ensure_ascii=False)
    # object to JSON text -> '{"a": "Ø"}'
json.loads('{"a": [1, 2]}')['a']  # JSON text to object -> [1, 2]
# NEVER pickle.load from a stranger!

## Excel and PDF  [Ch. 15]
import openpyxl as xl  # Excel library
wb = xl.Workbook()  # write .xlsx
ws = wb.active
ws.append(['pond', 'kg'])
ws.append(['A', 432.4])
wb.save('a.xlsx')
# read a cell
wb2 = xl.load_workbook('a.xlsx')
wb2.active['B2'].value  # -> 432.4
# write a PDF
from reportlab.pdfgen import canvas
c = canvas.Canvas('a.pdf')
c.drawString(72, 750, 'Report')
c.save()
from pypdf import PdfReader  # read a PDF
len(PdfReader('a.pdf').pages)  # -> 1

Proof: 14 groups; 80 lines really executed,
0 [chk] lines (option/API checked to exist), 2 [ref] lines (not executed).

Sheet 2: Intermediate (Chapters 16-43)

Print-ready PDF (A4, one page). It covers: OOP, environments, pytest, ruff, git, APIs, regex, pandas, SQL and SQLAlchemy, statistics and statsmodels, generators, decorators, type hints, Pydantic, async, FastAPI and Flask, GeoPandas and Rasterio, plus a little automation.

=== Cheat sheet Intermediate (Ch. 16-43) ===

## OOP: classes and inheritance  [Ch. 16]
class Tree:  # blueprint
    def __init__(self, kind, d):
        self.kind, self.d = kind, d
    def __str__(self):
        return f'{self.kind} {self.d}'
str(Tree('Teak', 32.5))  # make an object -> 'Teak 32.5'
class Big(Tree):  # inheritance
    def __str__(self):
        return 'BIG ' + super().__str__()
str(Big('Teak', 45))  # -> 'BIG Teak 45'
class Pond:  # property: no ()
    @property
    def ph(self):
        return 7.0
Pond().ph  # -> 7.0
# dataclass
from dataclasses import dataclass
@dataclass
class Fish:
    name: str
Fish('catfish')  # automatic __repr__ -> Fish(name='catfish')

## Environments, pytest, ruff, logging  [Ch. 17-19]
$ python -m venv .venv  # your own Python room [chk]
$ .venv\Scripts\activate  # Linux: source .venv/bin/activate [ref]
$ uv add pandas  # add a dependency [chk]
$ uv run pytest  # run in the project env [chk]
$ uv sync  # match uv.lock [chk]
$ pytest -x -k name  # stop at first failure [chk]
import pytest  # test_*.py
P = [(1, 1), (2, 4)]
@pytest.mark.parametrize('n,h', P)
def test_k(n, h):
    assert n * n == h
def test_e():
    with pytest.raises(ZeroDivisionError):
        1 / 0  # -> 3 passed
$ ruff check . --fix  # lint + auto-fix [chk]
$ ruff format .  # format code [chk]
import logging, sys  # levelled log
logging.basicConfig(level=20, force=True,
                    stream=sys.stdout)
logging.info('start')  # -> INFO:root:start

## Git and GitHub  [Ch. 20]
$ git init -b main  # start a repo (a.txt exists)
$ git add -A  # stage changes
$ git commit -m 'message'  # save a snapshot
$ git switch -c feature  # new branch
$ git switch main  # go back
$ git merge feature  # merge a branch
$ git log --oneline  # short history
$ git push -u origin main  # send to GitHub [chk]
# .gitignore: .venv/ .env (secrets!)

## APIs with requests  [Ch. 21]
import requests  # url = API address (here: a local server)
# always set a timeout
r = requests.get(url, params={'q': 1},
                 timeout=10)
r.status_code, r.json()  # -> (200, {'n': 3})
# missing = 404
r2 = requests.get(url + '/x', timeout=10)
r2.status_code  # -> 404
r.raise_for_status()  # error on 4xx/5xx

## Regex  [Ch. 22]
import re
t = 'JT-0042, MR-0107, xx-12'
re.findall(r'\b[A-Z]{2}-\d{4}\b', t)  # all codes -> ['JT-0042', 'MR-0107']
re.search(r'd=(\d+)', 'd=45 cm')[1]  # capture a group () -> '45'
re.sub(r'\s+', ' ', 'a   b  c')  # replace -> 'a b c'
# named groups
m = re.search(r'(?P<k>\w+)=(?P<n>\d+)', 't=14')
m.groupdict()  # -> {'k': 't', 'n': '14'}
re.fullmatch(r'\d{3}', '123') is not None  # whole match? -> True

## numpy and pandas  [Ch. 23]
import numpy as np
a = np.array([480, 500, 510])
a[a > 490]  # boolean mask -> array([500, 510])
import pandas as pd  # table
df = pd.DataFrame({'k': list('ABC'),
                   'j': ['catfish', 'tilapia', 'catfish'],
                   'n': [800, 1250, 600]})
df.shape  # -> (3, 3)
df['kg'] = df.n * 0.5  # new column
df[df.kg > 300].k.tolist()  # filter rows -> ['A', 'B']
df.groupby('j')['kg'].sum().to_dict()
    # group summary -> {'catfish': 700.0, 'tilapia': 625.0}
df.sort_values('kg').k.iloc[-1]  # the largest -> 'B'
# join tables
h = pd.DataFrame({'j': ['catfish', 'tilapia'],
                  'rp': [3, 2]})
df.merge(h, on='j').shape  # -> (3, 5)
df.to_csv('a.csv', index=False)  # save CSV
pd.read_csv('a.csv').shape  # read CSV -> (3, 4)

## SQL: sqlite3 and SQLAlchemy  [Ch. 24]
import sqlite3  # ? stops injection
c = sqlite3.connect(':memory:')
c.execute('CREATE TABLE p (j TEXT, d REAL)')
r = [('Teak', 32.5), ('Mahogany', 28.0)]
c.executemany('INSERT INTO p VALUES (?,?)', r)
# parameterised query
q = 'SELECT j FROM p WHERE d > ?'
c.execute(q, (30,)).fetchall()  # -> [('Teak',)]
# ORM: class = table
import sqlalchemy as sa
from sqlalchemy.orm import (DeclarativeBase,
    Mapped, Session, mapped_column)
class B(DeclarativeBase):
    pass
class Sector(B):
    __tablename__ = 's'
    id: Mapped[int] = mapped_column(
        primary_key=True)
    name: Mapped[str]
e = sa.create_engine('sqlite://')
B.metadata.create_all(e)
with Session(e) as s:
    s.add(Sector(name='Fish'))
    s.commit()
    n = len(s.scalars(sa.select(Sector)).all())
n  # -> 1
with e.connect() as k:  # raw SQL
    q = sa.text('SELECT COUNT(*) FROM s')
    cnt = k.execute(q).scalar()
cnt  # -> 1

## Statistics and statsmodels  [Ch. 26-27]
from scipy import stats  # two-group t-test
a, b = [30, 32, 31, 29], [35, 36, 34, 37]
stats.ttest_ind(a, b).pvalue < 0.05  # -> True
round(stats.norm.cdf(30, 25, 5), 3)  # normal probability -> 0.841
round(stats.pearsonr(a, b)[0], 2)  # correlation -> -0.4
# OLS: [intercept, slope]
import statsmodels.api as sm
X = sm.add_constant([1, 2, 3, 4, 5])
y = [2.1, 3.9, 6.2, 7.8, 10.1]
res = sm.OLS(y, X).fit()
res.params.round(2).tolist()  # -> [0.05, 1.99]
round(res.rsquared, 3)  # R squared -> 0.997
# logistic: odds ratio
import numpy as np, pandas as pd
import statsmodels.formula.api as smf
d = pd.DataFrame({'x': range(1, 9),
                  'y': [0, 0, 1, 0, 1, 0, 1, 1]})
r = smf.logit('y ~ x', d).fit(disp=0)
round(float(np.exp(r.params['x'])), 2)  # -> 1.81

## Generators, decorators, closures  [Ch. 30-31]
def evens(n):  # yield = one at a time
    for i in range(2, n + 1, 2):
        yield i
list(evens(10))  # -> [2, 4, 6, 8, 10]
# generator expression
g = (i * i for i in range(3))
next(g), next(g)  # -> (0, 1)
def multiplier(k):  # closure remembers k
    return lambda x: x * k
multiplier(3)(7)  # -> 21
import functools  # decorator + wraps
def count_calls(f):
    @functools.wraps(f)
    def b(*a):
        b.n += 1
        return f(*a)
    b.n = 0
    return b
@count_calls
def add(a, b):
    return a + b
add(1, 2), add.n  # n = number of calls -> (3, 1)

## Type hints and Pydantic  [Ch. 33-34]
# int or None
def find(xs: list[str], x: str) -> int | None:
    return xs.index(x) if x in xs else None
find(['a', 'b'], 'b'), find(['a'], 'z')  # -> (1, None)
$ mypy --strict app.py  # check types before running [chk]
# model = gatekeeper
from pydantic import BaseModel, Field
class Tree(BaseModel):
    kind: str
    d: float = Field(gt=0)
Tree(kind='Teak', d='32.5')
    # '32.5' becomes a number -> Tree(kind='Teak', d=32.5)
try:  # tidy error
    Tree(kind='Teak', d=-5)
except Exception as e:
    message = e.errors()[0]['msg']
message  # -> 'Input should be greater than 0'
# settings (can come from env)
from pydantic_settings import BaseSettings
class Config(BaseSettings):
    folder: str = 'result'
Config().folder  # -> 'result'

## Async and threads  [Ch. 35]
import asyncio  # concurrently
async def f(n):
    await asyncio.sleep(0.01)
    return n * 2
async def main():
    return await asyncio.gather(f(1), f(2))
asyncio.run(main())  # -> [2, 4]
async def g():  # TaskGroup
    async with asyncio.TaskGroup() as tg:
        t = tg.create_task(f(1))
    return t.result()
asyncio.run(g())  # -> 2
# threads for I/O waits
import concurrent.futures as cf
with cf.ThreadPoolExecutor(2) as p:
    result = list(p.map(abs, [-1, -2]))
result  # -> [1, 2]

## Web: FastAPI and Flask  [Ch. 36]
# path + parameter
from fastapi import FastAPI
from fastapi.testclient import TestClient
app = FastAPI()
@app.get('/pond/{n}')
def pond(n: str, ph: float = 7.0):
    return {'n': n, 'ph': ph}
c = TestClient(app)
c.get('/pond/A?ph=6.8').json()  # -> {'n': 'A', 'ph': 6.8}
c.get('/pond/A?ph=x').status_code  # wrong type = 422 -> 422
$ uvicorn app:app --reload  # start the server [chk]
# Flask: the small one
from flask import Flask
w = Flask(__name__)
@w.get('/hello')
def hello():
    return {'p': 'hi'}
w.test_client().get('/hello').json  # -> {'p': 'hi'}

## GeoPandas and Rasterio  [Ch. 38-39]
import geopandas as gpd  # points + CRS
from shapely.geometry import Point
pts = [Point(110, -7), Point(110.01, -7.01)]
g = gpd.GeoDataFrame({'n': [1, 2]}, geometry=pts,
                     crs='EPSG:4326')
g.crs.to_epsg()  # -> 4326
u = g.to_crs(32749)  # to metres (UTM 49S)
round(u.geometry[0].distance(u.geometry[1]))  # distance (m) -> 1563
round(u.buffer(100).area[0])  # buffer area (m2) -> 31365
g.to_file('a.gpkg')  # save/read GeoPackage
gpd.read_file('a.gpkg').shape  # -> (2, 2)
# write a GeoTIFF
import numpy as np, rasterio
from rasterio.transform import from_origin
a = np.arange(4, dtype='float32').reshape(2, 2)
t = from_origin(110, -7, 0.1, 0.1)
with rasterio.open('a.tif', 'w', driver='GTiff',
        height=2, width=2, count=1,
        dtype='float32', crs='EPSG:4326',
        transform=t) as d:
    d.write(a, 1)
# read band 1 + CRS
with rasterio.open('a.tif') as s:
    b, e = s.read(1), s.crs.to_epsg()
(b.mean().item(), e)  # -> (1.5, 4326)
# NDVI
n, r = np.array([.8, .6]), np.array([.2, .3])
np.round((n - r) / (n + r), 2).tolist()  # -> [0.6, 0.33]

## Context managers and automation  [Ch. 32, 41-43]
# ignore one error type
from contextlib import suppress
with suppress(FileNotFoundError):
    open('missing').close()
# run another program
import subprocess, sys
cmd = [sys.executable, '-c', 'print(2)']
p = subprocess.run(cmd, text=True,
                   capture_output=True)
p.stdout.strip()  # -> '2'
import argparse  # command-line arguments
ap = argparse.ArgumentParser()
ap.add_argument('--n', type=int, default=3)
ap.parse_args(['--n', '5']).n  # -> 5
$ streamlit run app.py  # dashboard in the browser [chk]

Proof: 14 groups; 79 lines really executed,
11 [chk] lines (option/API checked to exist), 1 [ref] lines (not executed).

Sheet 3: Advanced (Chapters 44-68)

Print-ready PDF (A4, one page). It covers: profiling, packaging, Python 3.12 to 3.14, Docker and CI (reference), security, Polars and DuckDB, STAC and Dask, scikit-learn, PyTorch and GPU, YOLO, Optuna and NSGA-II, SHAP and MLflow, RAG, agents and MCP, descriptors and bytecode, numba, Cython and CuPy, and Celery.

=== Cheat sheet Advanced (Ch. 44-68) ===

## Performance and profiling  [Ch. 44]
$ python -m cProfile -o out.prof app.py  # who is slow? [chk]
$ kernprof -l -v app.py  # line by line [chk]
import timeit  # measure, don't guess
tm = timeit.timeit
l = tm('99 in x', 'x = list(range(100))')
s = tm('99 in x', 'x = set(range(100))')
s < l  # -> True
import tracemalloc  # watch memory
tracemalloc.start()
x = [0] * 100_000
tracemalloc.get_traced_memory()[0] > 10**5  # -> True

## Packaging  [Ch. 45]
# pyproject.toml (from Ch. 45) [ref]
[build-system]
requires = ["setuptools>=77"]
build-backend = "setuptools.build_meta"
[project]
name = "field-monitor-example"
version = "0.1.0"
[project.scripts]
field-monitor = "field_monitor.cli:main"
$ python -m build  # build sdist + wheel [chk]
$ twine check dist/*  # check metadata [chk]

## Python 3.12 - 3.14  [Ch. 47]
# 3.12: generics + type
def first[T](xs: list[T]) -> T:
    return xs[0]
type Items[T] = list[T]
first([3, 1])  # -> 3
# 3.12: same quotes in f-string
j = ['Teak', 'Mahogany']
f"{", ".join(j)}"  # -> 'Teak, Mahogany'
import copy, datetime  # 3.13: copy.replace
copy.replace(datetime.date(2026, 1, 1), month=6)
    # -> datetime.date(2026, 6, 1)
name = 'Teak'  # 3.14: t-string
t = t'Tree {name}'
type(t).__name__, t.strings  # -> ('Template', ('Tree ', ''))
class Node:  # 3.14: forward ref, no quotes
    def __init__(self, nxt: Node | None):
        self.nxt = nxt
Node(Node(None)).nxt.nxt is None  # -> True
# 3.14: sub-interpreters [chk]
from concurrent.futures import (
    InterpreterPoolExecutor)

## Docker and CI (reference)  [Ch. 48]
# Dockerfile (from Ch. 48) [ref]
FROM python:3.14-slim
WORKDIR /app
USER appuser
EXPOSE 8000
$ docker build -t monitor .  # Docker is not installed here [ref]
$ docker run -p 8000:8000 monitor  # open localhost:8000 [ref]
on:  # GitHub Actions (from Ch. 48) [ref]
  push:
jobs:
  test:
    runs-on: ubuntu-latest

## Security  [Ch. 49]
# safe random token
import hashlib, hmac, secrets
len(secrets.token_hex(16))  # -> 32
hashlib.sha256(b'abc').hexdigest()[:12]  # data fingerprint -> 'ba7816bf8f01'
# passwords: scrypt
k = dict(salt=b'salt', n=2**14, r=8, p=1)
h = hashlib.scrypt(b'secret', **k)
h2 = hashlib.scrypt(b'secret', **k)
hmac.compare_digest(h, h2)  # -> True
# reject ../ (traversal)
from pathlib import Path
d = Path('data').resolve()
(d / '../secret.txt').resolve().is_relative_to(d)  # -> False
import html  # stop HTML injection
html.escape('<script>')  # -> '&lt;script&gt;'
$ pip-audit -r requirements.txt  # find vulnerable packages [chk]
$ bandit -r src  # find risky patterns [chk]

## Polars, DuckDB, Parquet  [Ch. 50]
import polars as pl  # fast tables
d = pl.DataFrame(dict(k=list('aba'), v=[1, 2, 3]))
g = d.group_by('k').agg(pl.col('v').sum())
g.sort('k').to_dicts()  # -> [{'k': 'a', 'v': 4}, {'k': 'b', 'v': 2}]
d.lazy().filter(pl.col('v') > 1).collect().shape  # lazy plan -> (2, 2)
# Parquet: columnar, compressed
d.write_parquet('a.parquet')
pl.read_parquet('a.parquet').shape  # -> (3, 2)
import duckdb  # SQL on a file
q = "SELECT k, SUM(v) FROM 'a.parquet' GROUP BY k"
sorted(duckdb.sql(q).fetchall())  # -> [('a', 4), ('b', 2)]

## STAC, COG, Dask, xarray  [Ch. 51, 53]
import pystac  # one scene = one Item
from datetime import datetime, timezone
t = datetime(2026, 1, 1, tzinfo=timezone.utc)
i = pystac.Item('s2-1', None, None, t, {})
i.to_dict()['type']  # -> 'Feature'
from pystac_client import Client  # search a STAC catalogue [chk]
s.read(1, window=Window(2, 2, 3, 3))
    # COG: read a window (needs a network) [ref]
# chunks + lazy compute
import dask.array as da
a = da.ones((1000, 1000), chunks=500)
float(a.sum().compute())  # -> 1000000.0
# labelled arrays
import numpy as np, xarray as xr
a = np.arange(6).reshape(2, 3)
x = xr.DataArray(a, dims=('t', 'k'))
x.isel(t=1).sum().item()  # -> 12

## scikit-learn and boosting  [Ch. 54]
# synthetic data
from sklearn.datasets import make_classification
X, y = make_classification(200, random_state=0)
# 5-fold cross-validation
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
m = RandomForestClassifier(50, random_state=0)
s = cross_val_score(m, X, y, cv=5)
round(float(s.mean()), 2)  # -> 0.93
int(m.fit(X, y).feature_importances_.argmax())  # top feature -> 14
import xgboost as xgb  # boosting
g = xgb.XGBClassifier(n_estimators=20)
g.fit(X, y).score(X, y) > 0.9  # -> True

## PyTorch and GPU  [Ch. 55]
# train: zero, loss, backward, step
import torch
x = torch.tensor([[1.], [2.], [3.]])
net = torch.nn.Linear(1, 1)
opt = torch.optim.SGD(net.parameters(), lr=.05)
for _ in range(200):
    opt.zero_grad()
    loss = ((net(x) - 2 * x) ** 2).mean()
    loss.backward()
    opt.step()
round(net.weight.item(), 1)  # -> 2.0
d = 'cuda' if torch.cuda.is_available() else 'cpu'  # GPU if present
net.to(d)(x.to(d)).shape  # model + data on one device -> torch.Size([3, 1])

## Computer vision: YOLO  [Ch. 56]
# IoU: box overlap
ar = lambda b: (b[2] - b[0]) * (b[3] - b[1])
def iou(a, b):
    w = min(a[2], b[2]) - max(a[0], b[0])
    h = min(a[3], b[3]) - max(a[1], b[1])
    i = max(w, 0) * max(h, 0)
    return i / (ar(a) + ar(b) - i)
round(iou((0, 0, 10, 10), (5, 5, 15, 15)), 3)  # -> 0.143
from ultralytics import YOLO  # object detection [chk]
m = YOLO('yolo26n.pt')  # weights are downloaded [ref]
m.train(data='data.yaml', epochs=50)  # train (GPU advised) [chk]
m.predict('photo.jpg', conf=0.25)  # detect [chk]

## Optuna and NSGA-II  [Ch. 57]
import optuna  # best x (true = 2)
optuna.logging.set_verbosity(40)
def f(t):
    return (t.suggest_float('x', -5, 5) - 2) ** 2
s = optuna.samplers.TPESampler(seed=0)
st = optuna.create_study(sampler=s)
st.optimize(f, 30)
round(st.best_params['x'], 1)  # -> 2.0
# 2 goals = Pareto front
from pymoo.algorithms.moo.nsga2 import NSGA2
from pymoo.problems import get_problem
from pymoo.optimize import minimize
p = get_problem('zdt1')
r = minimize(p, NSGA2(pop_size=20), ('n_gen', 20), seed=1)
r.F.shape[1]  # -> 2

## SHAP and MLflow  [Ch. 58]
# synthetic data
from sklearn.datasets import make_classification
X, y = make_classification(200, random_state=0)
# each feature's share
import numpy as np, shap
from sklearn.ensemble import RandomForestClassifier
m = RandomForestClassifier(20, random_state=0)
e = shap.TreeExplainer(m.fit(X, y))
np.shape(e.shap_values(X[:5]))  # -> (5, 20, 2)
import mlflow  # track experiments
mlflow.set_tracking_uri('sqlite:///m.db')
with mlflow.start_run():
    mlflow.log_metric('accuracy', 0.9)
r = mlflow.search_runs(search_all_experiments=True)
len(r)  # -> 1

## RAG, vectors, agents, MCP  [Ch. 60, 62]
# nearest neighbour
import faiss, numpy as np
v = np.random.default_rng(0).random((9, 4))
v = v.astype('float32')
ix = faiss.IndexFlatL2(4)
ix.add(v)
int(ix.search(v[:1], 2)[1][0][0])  # -> 0
import chromadb  # vector database
c = chromadb.Client().create_collection('pond')
c.add(ids=['1', '2'], documents=['rice', 'fish'],
      embeddings=[[1, 0], [0, 1]])
q = dict(query_embeddings=[[1, .1]], n_results=1)
c.query(**q)['documents']  # -> [['rice']]
# RAG: context goes in the prompt
context, question = 'Pond A: pH 7.2', 'pH pond A?'
f'Answer from context only.\n{context}\n{question}'
    # -> 'Answer from context only.\nPond A: pH 7.2\npH po...
# resp = client.generate(prompt)  # needs a key [disamarkan]; not run [ref]
# agent: loop until done/limit
def agent(model, tool, limit=5):
    for _ in range(limit):
        a = model()
        if 'answer' in a:
            return a['answer']
        tool[a['tool']](a['arg'])
# a scripted model answers
s = iter([{'tool': 't', 'arg': 2}, {'answer': '5'}])
agent(lambda: next(s), {'t': lambda a: a + 3})  # -> '5'
# MCP server + client (in memory)
import asyncio
from mcp import Client
from mcp.server import MCPServer
sv = MCPServer('pond')
@sv.tool()
def biomass(n: int, g: float) -> float:
    return round(n * g / 1000, 2)
async def main():
    async with Client(sv) as k:
        a = {'n': 800, 'g': 540.5}
        h = await k.call_tool('biomass', a)
        return h.structured_content
asyncio.run(main())  # -> {'result': 432.4}

## Descriptors, bytecode, internals  [Ch. 64-65]
# descriptor: attribute guard
class Positive:
    def __set_name__(self, o, n):
        self.n = '_' + n
    def __get__(self, o, t=None):
        return getattr(o, self.n)
    def __set__(self, o, v):
        if v <= 0:
            raise ValueError('must be > 0')
        setattr(o, self.n, v)
class Tree:
    d = Positive()
    def __init__(self, d):
        self.d = d
Tree(32.5).d  # -> 32.5
type('B', (), {'x': 1}).x  # class made at run time -> 1
import dis  # bytecode
f = lambda a: a + 1
[i.opname for i in dis.get_instructions(f)][-2:]
    # -> ['BINARY_OP', 'RETURN_VALUE']
import sys  # ref count (x + the argument)
x = []
sys.getrefcount(x)  # -> 2

## numba, Cython, CuPy  [Ch. 66]
# JIT: C-speed for-loops
from numba import njit
@njit
def sum_of_squares(n):
    s = 0
    for i in range(n):
        s += i * i
    return s
sum_of_squares(1000)  # -> 332833500
$ cythonize -i ndvi_cy.pyx  # compile .pyx (needs a C compiler) [chk]
import cupy as cp  # NVIDIA GPU like numpy
float(cp.asarray([1., 2., 3.]).sum())  # -> 6.0

## Celery (task queue)  [Ch. 67]
# eager mode: test without a broker
from celery import Celery
app = Celery('t', broker='memory://',
             backend='cache+memory://')
app.conf.task_always_eager = True
@app.task
def add(a, b):
    return a + b
add.delay(2, 3).get()  # -> 5
# automatic retries
@app.task(bind=True, max_retries=3)
def fragile(self, x):
    return x
fragile.delay(7).get()  # -> 7
$ celery -A tugas worker --concurrency=3  # three worker processes [chk]
redis.Redis.from_url(url, protocol=2)  # Redis 5 (needs a server) [ref]

Proof: 16 groups; 46 lines really executed,
13 [chk] lines (option/API checked to exist), 9 [ref] lines (not executed).

What is deliberately missing from the sheets

One A4 page holds only so much. The following chapters have no lines on the sheets (their content is better read in the chapter): Chapters 1-2 (concepts), 25 (charts and basic scikit-learn; scikit-learn is on the Advanced sheet), 28-29 (qualitative methods and reproducible research), 37 (Python in the browser), 40 (PostGIS and QGIS), 46 (design patterns), 52 (IoT and drones), 59 (ML for natural resources), 61 (transformers), 63 (licences and law), and 68 (portfolio). Chapters 32 and 41 to 43 are only represented in small part.

How to re-check the sheets

Each sheet’s checking script is short (01_basic.py below). The sheet’s content is in a data file in the same folder (data_basic.py, data_intermediate.py, data_advanced.py), and the checking engine is lembar_jalan.py.

01_basic.py
# Chapter 72 - Cheat sheet: every line is really executed
from data_basic import GRUP
from lembar_jalan import cetak_laporan

cetak_laporan(GRUP, "en", "Cheat sheet Basic (Ch. 1-15)", "data_basic")
NoteTIP

Want your own sheet? Copy a data file, replace its content with the lines of code you keep forgetting, and run the script. A wrong line is caught at once because the script stops with an error message. The PDF maker is alat/lembar_contekan.py in the book’s project folder.