Skip to main content
ByPhilipp Singer
Better risk ranking and per-policy uncertainty from the raw dataframe, no feature engineering. Insurance claim data is zero-inflated: most policies never claim, and the few that do produce heavily right-skewed amounts. Pricing it well is the core actuarial problem, and the classic recipe is a two-stage generalized linear model (GLM): a frequency model (how often do claims happen?) times a severity model (how large are they?). Making a GLM work takes real feature engineering, binning, one-hot encoding, log-scaling, and choosing link functions and a Tweedie power by hand. TabPFN is a tabular foundation model that learns in a single forward pass with no per-dataset training and no preprocessing, you hand it the raw dataframe (mixed numeric and string columns, missing values and all). This notebook puts TabPFN head-to-head with matching scikit-learn GLM baselines on the standard French Motor Third-Party Liability (freMTPL2) dataset. For both the GLM and TabPFN we run the two classic recipes, a single-stage direct model and the two-stage frequency × severity (occurrence × severity) decomposition, so every comparison is like-for-like. Bottom line up front:
  • Better risk ranking, zero feature engineering. Straight from the 9-column raw dataframe, TabPFN reaches an exposure-weighted Gini of ~0.32 vs ~0.31 for the best hand-built GLM, better separation of safe from risky policies, which is what drives pricing.
  • Per-policy uncertainty for free. TabPFN returns a full predictive distribution per policy, so you can read the expected claim and the tail (“how bad could this one get?”), the GLM gives you only a fitted mean.
  • Calibration on par, with one optional line. With a single portfolio-level rescale — the same balancing a log-link GLM does implicitly — TabPFN matches the GLM on calibration while leaving the ranking untouched.
We score with the metrics actuaries actually use: the exposure-weighted Gini (ranking / lift) and Tweedie deviance / D² (calibration, the standard loss for zero-inflated cost; both are defined in the Metrics section below).
Requirements. This notebook runs on the hosted TabPFN client (tabpfn-client), so no local GPU is needed; you just need a free API token (see the setup cells below). Baselines, dataset explanation and metrics follow scikit-learn’s Tweedie regression on insurance claims example which motivated this tutorial.
In this notebook, we use the TabPFN client to access the API, so no hardware is required to run the model.

The data

Each row is an insurance policy — a contract between the insurer and a policyholder — described by driver age, vehicle age, vehicle power, region, and so on. A claim is a request for compensation; the claim amount is what the insurer pays out. Exposure is how long the policy was actually observed, measured in years: a policy on the books for the full period has Exposure = 1.0, one observed for six months has Exposure ≈ 0.5. It matters because a policy watched for two months has had far less opportunity to claim than one watched all year, so every rate below is expressed per unit of exposure. Our target is the pure premium: the expected claim cost per unit of exposure, i.e., the break-even price of the risk. There are two classic ways to model it:
  • Single-stage: model the total claim cost per unit exposure directly.
  • Two-stage: model the claim frequency (claims per year) and the severity (average amount per claim) separately, then multiply.
The column reMTPL2freq holds one row per policy (risk features, claim count ClaimNb, and Exposure), freMTPL2sev holds the individual claim amounts. We aggregate claim amounts per policy and join; policies with no claim get ClaimAmount = 0. We apply the same light cleaning as the scikit-learn example (cap extreme values, make ClaimNb consistent with ClaimAmount) and derive the targets Frequency = ClaimNb / Exposure, AvgClaimAmount = ClaimAmount / ClaimNb, and PurePremium = ClaimAmount / Exposure. Note the feature columns: VehBrand, VehGas, Region, Area are strings, the rest are numeric on very different scales. A GLM cannot consume this directly; TabPFN can.
The data We hold out 25% of policies for testing with a random split. A production pricing model would usually be validated out of time (train on older policy years, test on newer) to catch drift, but freMTPL2 ships without a usable time index, so we follow the scikit-learn example and split at random; a simplification worth remembering for similar use cases.

Metrics

Two questions matter for a pricing model, and they need different metrics. Ranking / lift — does the model sort safe from risky policies? This is what segmentation and pricing rely on, and our headline metric. We use the exposure-weighted Gini. Sort policies from safest to riskiest by predicted premium, then trace the cumulative share of actual claim cost against the cumulative share of exposure — the Lorenz curve L(u)L(u). The Gini is (twice) the area between that curve and the diagonal: Gini=1201L(u)du\mathrm{Gini} = 1 - 2\int_0^1 L(u)\,du Higher is better; a model with no information scores 0. In code it is 1 - 2 * auc(cum_exposure, cum_claims). Calibration — is the predicted premium level right? We use the Tweedie deviance (lower is better) and its normalized cousin (higher is better, an R²-style score), the standard loss for zero-inflated, right-skewed cost targets. This is exactly the GLM’s own training objective. We deliberately skip MAE (degenerate on a 96%-zero target — it rewards predicting near-zero for everyone) and RMSE (dominated by a handful of catastrophic claims).

GLM baselines

A GLM (generalized linear model) is a linear model with a link function — here a log link, so predictions stay positive. Being linear, every feature has to be hand-engineered into a numeric design matrix: bin the skewed numerics, one-hot the categoricals, log-and-scale the heavy-tailed one. On that shared design matrix we fit the two classic recipes for the pure premium — a single-stage direct Tweedie model, then the two-stage frequency × severity product. The hyperparameters (Tweedie power, regularization strengths) are taken straight from the scikit-learn tutorial and left untuned; in practice you would grid-search them. That cuts both ways in this comparison — TabPFN is also run untuned, on its defaults.

Single-stage GLM: direct Tweedie on pure premium

The simplest recipe: one TweedieRegressor fit straight on PurePremium, weighted by Exposure. No occurrence/severity split — a single model for the whole target. We set power=1.9 following the scikit-learn tutorial.

Two-stage GLM: frequency × severity

The classic actuarial product: a Poisson frequency model (how often claims happen, weighted by exposure) times a Gamma severity model (average amount per claim, fit on claim rows only and weighted by claim count). Their product is the expected cost per unit exposure. Again, we follow the scikit-learn tutorial and set the hyperparameters accordingly.

TabPFN

Now the same problem with TabPFN — but we feed the raw dataframe: strings, integers and floats together, no encoding, no scaling, no binning, no per-dataset tuning. GLMs fold exposure in through sample_weight and an offset. TabPFN has neither, so we hand it the information directly instead:
  • Exposure becomes an ordinary input feature. TabPFN learns its effect from the data rather than you specifying it as an offset. We model the bounded ClaimAmount rather than the exploding per-exposure PurePremium (dividing a claim by a tiny exposure blows up the tail).
  • The pure premium is read off with a counterfactual query. We predict every test policy at a full year of exposure (Exposure = 1.0), so the output is already the expected cost per unit of exposure, no division needed.

Single-stage TabPFN: regress the claim amount directly

Start with the simplest thing that could work: hand a single TabPFNRegressor the raw, 96%-zero ClaimAmount and query at full exposure. One .fit(), one .predict(), default settings — nothing else. .predict() returns the mean of TabPFN’s predicted distribution, the estimator of expected cost.
The ranking is already competitive, the Gini lands in the same range as the two stage GLM, but the calibration is off: the Tweedie deviance is enormous and D² is deeply negative. TabPFN’s default target handling is not built for a target that is 96% zeros with a heavy tail on the rest. The fix is the same one the GLMs bake in through their log link: model the target on a log scale. TabPFN exposes this as a target transform, 1_plus_log (fit on log(1 + y), invert on output), the natural choice for monetary amounts. This is the one modeling knob we touch.
Results show that the ranking quality stays intact, while we severely improve the calibration quality.

Two-stage TabPFN: occurrence × severity

The actuarial decomposition, with no Poisson/Gamma machinery, just two TabPFN models:
  • Stage 1 — occurrence: a TabPFNClassifier for P(claim > 0).
  • Stage 2 — severity: a TabPFNRegressor for E[ClaimAmount | claim > 0], trained on claim rows only.
Expected cost is P(claim) × severity. Both stages are queried at Exposure = 1.0, so the product is already the pure premium. We use each stage’s mean here; the next section pulls stage 2’s full predictive distribution for a handful of example policies.
We can now see that the two stage TabPFN solution clearly outperforms all other solutions in the important Gini metric.

Per-policy uncertainty: the whole distribution, not just a number

A GLM’s severity model hands you one number per policy — the fitted mean. TabPFN’s severity model returns a full predictive distribution: for each policy we can read the expected claim and any quantile of how large it might be. In insurance that tail is not a nice-to-have — the 90th- or 99th-percentile claim is what drives risk loading, capital and reinsurance. We only need this for a few illustrative policies, so we ask stage 2 for the full output (output_type="full") on a small subset. (The hosted client caps full regression output at a few hundred rows per call — plenty here, since we just want a spread of examples across the risk range.)
The chart below plots those policies ordered safest → riskiest by predicted severity. The two lines are the mean TabPFN would price on (red, dashed) and the median claim (dark); the shaded fan is the predictive interval — the inner band the 25th–75th percentile (the typical range), the outer the 10th–99th (reaching into the tail). Two things to read off it: the fan widens toward the risky end (TabPFN is telling you the outcome there is less certain, not just larger), and the dashed mean rides above the median — that gap is the heavy tail at work. Pricing on the mean already carries the cost of rare large claims, which is exactly what a median- or mean-only summary hides.
The same idea as a table: for a few of those policies, the mean claim you would price on versus the 90th-percentile claim you would reserve capital for. TabPFN gives you both from a single .predict; the GLM’s Gamma gives you only the first.

Optional: matching the GLM on calibration

The two-stage TabPFN ranks well but under-prices the portfolio: summed over the test set it charges only ~60% of the actual claim cost. A log-link GLM never has this problem, fitting an intercept automatically balances its predicted total against the observed total on the training data. We can give TabPFN the same single degree of freedom: one constant factor = actual claim cost ÷ predicted cost, estimated on a calibration slice held out of training (never the test set). Because it is a constant, c · (p × s) orders policies exactly like p × s — so it fixes the level without moving the ranking one bit. One TabPFN-specific catch: TabPFN predicts by conditioning on its training rows in-context, so it effectively memorizes them — a policy it saw at fit time gets an unrealistically good prediction. Estimating the factor on such rows would under-correct, so we re-fit both stages on a subset (df_fit) and score the held-out slice (df_cal) they never saw.
The rescale is easiest to see decile by decile: bin the test policies by predicted premium and plot the predicted premium (x) against the actual claim cost (y) in each bin. Before, the two-stage points sit consistently above the diagonal — actual cost exceeds what the model charged, i.e. under-pricing; after the single constant factor they land on it. The GLM is drawn faintly in both panels for reference, and the ranking is identical between them — only the level moves.
Optional: matching the GLM on calibration

Results

All five models side by side, then the Lorenz curves for the ranking view (calibration was covered in the section above).
Lorenz curves. Policies are sorted safest → riskiest by predicted premium; we plot the cumulative share of actual claim cost against the cumulative share of exposure. A lower, more bowed curve means better risk separation. Two reference lines frame it: random (the diagonal) and the oracle (sorted by realized losses — Gini ~0.98, not 1.0, because claim-bearing policies still occupy a few percent of the exposure axis). No expected-risk model can reach the oracle: realized claims are mostly luck, and a model can only rank who is riskier on average. The recalibrated two-stage curve overlays the uncalibrated one exactly — constant rescaling never reorders a single policy.
Results

Takeaways

  • Better ranking, zero feature engineering. From the raw 9-column dataframe — strings and all, one .fit() / .predict() — TabPFN reaches Gini ~0.32 vs ~0.31 for the best GLM. The GLM needed a hand-built 75-column design matrix (binning, one-hot, log-scaling) and several modeling choices to get there.
  • Uncertainty comes built in. TabPFN returns a full predictive distribution per policy, so you read off the expected claim and its tail (the P90 “bad case”) directly — exactly what risk loading and capital planning need. The GLM gives a mean and stops.
  • Calibration is a one-liner away. Out of the box TabPFN under-prices the portfolio (it has no exposure offset, a genuine convenience the GLMs keep). A single portfolio-level factor — the same balancing a log-link GLM performs implicitly — restores the level (balance ~0.6 → ~0.96) and brings the Tweedie deviance in line with the GLMs, while provably leaving the ranking untouched.
  • The two-stage split helps TabPFN too. Occurrence × severity is better calibrated than a single regressor on the raw zero-inflated amount — the classic actuarial decomposition still pays off.
  • Match the metric to the question. Judge ranking with the exposure-weighted Gini (the headline for pricing) and calibration with Tweedie deviance / D² and the decile chart. MAE and RMSE are actively misleading on a 96%-zero, heavy-tailed target.