Dictionary of Applied Machine Learning · supervised learning
Updated on 2026-11-03
Numerical companion to the entry supervised learning: it recomputes what the entry states and prints one line per check
Numerical companion to the glossary entry 'supervisedlearning'. The GeoSphere Austria weather station Krems (station id 3805) records the minimum and the maximum air temperature of each day; this script downloads the records for 2024 from the GeoSphere data hub (dataset klima-v2-1d) and writes them to supervisedlearning_weather.csv.
Run it without installing anything:uv run https://dictionaryofml.org/terms/supervisedlearning.py
uv downloads this script and the pinned NumPy and Matplotlib it needs, then runs it; the script fetches any input file it uses. To keep the output files, download supervisedlearning.py into a folder and run uv run supervisedlearning.py there. With NumPy and Matplotlib already installed, python3 supervisedlearning.py, from any directory — it writes its output files into the current directory. Fixed seeds, so the printed numbers reproduce exactly. Download supervisedlearning.py · Notebook · Open in Colab
Before running a downloaded copy, scan it: uvx bandit -q -t B102,B307,B602,B605 supervisedlearning.py (B102,B307,B602,B605 are the exec, eval and shell checks of bandit) reports nothing: the script runs no shell command and evaluates no generated code.
One cell per block of the script: the code, and what that code printed when it last ran here
"""Supervised learning on a year of days at Krems: the same days carry a
numeric label and a categorical one, so the same data points pose a
regression and a classification problem.
Purpose
-------
Numerical companion to the glossary entry 'supervisedlearning'. The
GeoSphere Austria weather station Krems (station id 3805) records the
minimum and the maximum air temperature of each day; this script
downloads the records for 2024 from the GeoSphere data hub (dataset
klima-v2-1d) and writes them to supervisedlearning_weather.csv.
Ten of those days -- the 25th of each month from January to October --
are labeled twice, which is what the entry's figure shows side by side.
Ten days keep the figure readable; the day of the month was picked so
that both classes of the second labeling occur, and the dates are
stated in the caption so the selection is checkable:
regression the feature of a day is its minimum temperature and
the label is its maximum temperature, a number. ERM
with the squared error loss over the polynomials of
degree two learns a hypothesis, drawn as a curve.
classification the features of a day are its minimum and its maximum
temperature, and the label is +1 if the next day is at
least two degrees warmer and -1 otherwise.
Both labels are known for a recorded day because both were measured,
which is what makes the setting supervised. The two classes overlap
heavily in the feature space, so the figure shows a labeled dataset and
not a separable one.
Deterministic: the data are a fixed archive year and the hypothesis is
a least-squares solution. Self-contained: numpy + matplotlib only
(stdlib urllib for the download).
Blocks
------
[B-data] Download the 366 days of 2024 with their minimum and
maximum temperature, and keep the 25th of each month from
January to October as ten labeled days.
[B-regress] The numeric label: ERM over the polynomials of degree two
with the squared error loss. Check that the learned
hypothesis beats the constant prediction given by the
sample mean of the labels.
[B-classify] The categorical label: +1 if the next day is at least two
degrees warmer, -1 otherwise. Check that both classes
occur and that no straight line separates them, searched
exactly over directions and offsets rather than by fitting
one line.
[B-fig] Preview: the two labelings of the same days, side by side.
Outputs
-------
supervisedlearning_weather.csv : date, tmin, tmax for the 366 days
supervisedlearning_days.csv : tmin, tmax of the ten labeled days
supervisedlearning_fit.csv : tmin, tmax of the learned hypothesis
supervisedlearning_pos.csv : tmin, tmax of the days labeled +1
supervisedlearning_neg.csv : tmin, tmax of the days labeled -1
supervisedlearning.png : preview (checking only)
"""
# /// script
# requires-python = ">=3.10"
# dependencies = ["matplotlib==3.10.7", "numpy==2.3.4"]
# ///
import json
import urllib.request
from pathlib import Path
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
OUT_DIR = Path(__file__).parent
report = []
def check(name, ok):
report.append((name, bool(ok)))
print(f" [{'ok' if ok else 'FAIL'}] {name}")
Download the 366 days of 2024 with their minimum and maximum temperature, and keep the 25th of each month from January to October as ten labeled days.
URL = ("https://dataset.api.hub.geosphere.at/v1/station/historical/"
"klima-v2-1d?parameters=tlmin,tlmax&station_ids=3805"
"&start=2024-01-01&end=2024-12-31")
with urllib.request.urlopen(URL, timeout=120) as resp:
payload = json.load(resp)
params = payload["features"][0]["properties"]["parameters"]
stamps = [t[:10] for t in payload["timestamps"]]
tmin = np.array(params["tlmin"]["data"], dtype=float)
tmax = np.array(params["tlmax"]["data"], dtype=float)
with open(OUT_DIR / "supervisedlearning_weather.csv", "w") as f:
f.write("date,tmin,tmax\n")
for day, lo, hi in zip(stamps, tmin, tmax):
f.write(f"{day},{lo},{hi}\n")
check("[B-data] 366 labeled days downloaded for 2024", len(tmin) == 366)
check("[B-data] the record matches the archive (Feb 1: -3.8 to 10.4 deg)",
stamps[31] == "2024-02-01" and np.isclose(tmin[31], -3.8)
and np.isclose(tmax[31], 10.4))
# the 25th of each month from January to October; each has a successor in
# the record, which the categorical label below needs
keep = [i for i, day in enumerate(stamps) if day[8:10] == "25"][:10]
x, y = tmin[keep], tmax[keep]
nxt = tmax[np.array(keep) + 1]
m = len(x)
np.savetxt(OUT_DIR / "supervisedlearning_days.csv", np.column_stack([x, y]),
delimiter=",", header="tmin,tmax", comments="", fmt="%.1f")
check("[B-data] ten days carry both labels", m == 10)
check("[B-data] the ten days are the 25th of January to October",
[stamps[i][:7] for i in keep]
== [f"2024-{mth:02d}" for mth in range(1, 11)])
[ok] [B-data] 366 labeled days downloaded for 2024 [ok] [B-data] the record matches the archive (Feb 1: -3.8 to 10.4 deg) [ok] [B-data] ten days carry both labels [ok] [B-data] the ten days are the 25th of January to October
The numeric label: ERM over the polynomials of degree two with the squared error loss. Check that the learned hypothesis beats the constant prediction given by the sample mean of the labels.
coeff = np.polyfit(x, y, 2)
curve_x = np.linspace(x.min(), x.max(), 120)
np.savetxt(OUT_DIR / "supervisedlearning_fit.csv",
np.column_stack([curve_x, np.polyval(coeff, curve_x)]),
delimiter=",", header="tmin,tmax", comments="", fmt="%.3f")
learned = float(((y - np.polyval(coeff, x)) ** 2).mean())
constant = float(((y - y.mean()) ** 2).mean())
check("[B-regress] the learned hypothesis beats the constant prediction",
learned < constant)
print(f" average squared error loss: {learned:.1f} for the learned "
f"hypothesis against {constant:.1f} for the sample mean")
[ok] [B-regress] the learned hypothesis beats the constant prediction average squared error loss: 7.9 for the learned hypothesis against 58.3 for the sample mean
The categorical label: +1 if the next day is at least two degrees warmer, -1 otherwise. Check that both classes occur and that no straight line separates them, searched exactly over directions and offsets rather than by fitting one line.
label = np.where(nxt - y >= 2.0, 1, -1)
X = np.column_stack([x, y])
np.savetxt(OUT_DIR / "supervisedlearning_pos.csv", X[label == 1],
delimiter=",", header="tmin,tmax", comments="", fmt="%.1f")
np.savetxt(OUT_DIR / "supervisedlearning_neg.csv", X[label == -1],
delimiter=",", header="tmin,tmax", comments="", fmt="%.1f")
check("[B-classify] the label takes only the two values",
set(np.unique(label)) == {-1, 1})
check("[B-classify] both classes occur among the ten days",
min((label == 1).sum(), (label == -1).sum()) >= 3)
print(f" {int((label == 1).sum())} of the ten days are followed by one at "
f"least two degrees warmer, {int((label == -1).sum())} are not")
# no straight line separates them, searched exactly: every direction, and
# every offset that matters, is one of the finitely many that put a
# different subset of the days on each side
best = 0.0
for angle in np.linspace(0.0, np.pi, 2000, endpoint=False):
scores = X @ np.array([np.cos(angle), np.sin(angle)])
for offset in np.unique(scores):
for side in (1.0, -1.0):
hit = (np.sign(side * (scores - offset + 1e-12)) == label).mean()
best = max(best, float(hit))
check("[B-classify] no straight line separates the two classes", best < 1.0)
print(f" the best straight line gets {best:.0%} of the ten days right")
[ok] [B-classify] the label takes only the two values [ok] [B-classify] both classes occur among the ten days 4 of the ten days are followed by one at least two degrees warmer, 6 are not [ok] [B-classify] no straight line separates the two classes the best straight line gets 90% of the ten days right
Preview: the two labelings of the same days, side by side.
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10.0, 4.2))
ax1.plot(x, y, "o", ms=5, mfc="none", color="black", label="day")
ax1.plot(curve_x, np.polyval(coeff, curve_x), "-", lw=1.6, color="black",
label="learned hypothesis")
ax1.set_xlabel("feature: daily minimum temperature (degrees C)")
ax1.set_ylabel("label: daily maximum temperature (degrees C)")
ax1.set_title("(a) a numeric label: regression", y=-0.30)
ax1.legend(frameon=False, fontsize=8, loc="upper left")
ax2.plot(X[label == -1, 0], X[label == -1, 1], "s", ms=5, mfc="none",
color="black", label="next day not 2 deg warmer")
ax2.plot(X[label == 1, 0], X[label == 1, 1], "o", ms=5, color="black",
label="next day at least 2 deg warmer")
ax2.set_xlabel("feature: daily minimum temperature (degrees C)")
ax2.set_ylabel("feature: daily maximum temperature (degrees C)")
ax2.set_title("(b) a categorical label: classification", y=-0.30)
ax2.legend(frameon=False, fontsize=8, loc="upper left")
fig.tight_layout()
fig.savefig(OUT_DIR / "supervisedlearning.png", dpi=110)
plt.close(fig)
check("[B-fig] the preview was written",
(OUT_DIR / "supervisedlearning.png").exists())
bad = [n for n, ok in report if not ok]
print(f"\n{len(report) - len(bad)}/{len(report)} checks pass")
if bad:
raise SystemExit("FAILED: " + "; ".join(bad))
[ok] [B-fig] the preview was written 9/9 checks pass

B-fig writes when the script runs