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 a single-stage direct model and a two-stage decomposition. The GLM uses the classic frequency × per-claim severity recipe; TabPFN uses its natural hurdle-model analogue, occurrence × conditional aggregate cost. Both target the same quantity: expected annual claim cost. 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.
  • A rich conditional cost distribution per policy. Given that a claim occurs, TabPFN returns the expected cost and any quantile, with a shape that adapts per policy, handy for understanding the severity tail.
  • Calibration on par, with one optional adjustment. A single portfolio-level rescale, estimated on held-out training data, brings TabPFN’s Tweedie deviance close to the GLMs while leaving its stronger 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 × conditional aggregate cost

A hurdle-model analogue of the actuarial decomposition, with no Poisson/Gamma machinery, just two TabPFN models:
  • Stage 1 — occurrence: a TabPFNClassifier for P(claim > 0).
  • Stage 2 — conditional cost: a TabPFNRegressor for E[ClaimAmount | claim > 0], trained on positive-loss policy rows only.
Expected cost is P(claim) × conditional cost. 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.
The two-stage TabPFN solution achieves the strongest Gini in this held-out experiment.

Per-policy uncertainty: the conditional cost distribution

Conditional on a positive claim amount, both models give a distribution per policy. The difference is shape: a standard Gamma uses one dispersion φ for the whole portfolio, so every policy gets the same coefficient of variation and skew and its quantiles are just the mean rescaled. TabPFN’s distribution can change shape from policy to policy, and reading it off costs nothing extra, output_type="full" returns the mean plus any quantile. We pull the full output for a spread of example policies.
The chart below plots those policies ordered from lower → higher predicted conditional cost. The two lines are the conditional mean used as the severity component of the price (red, dashed) and the conditional median (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). The fan reaches higher amounts as predicted conditional cost increases, and the dashed mean rides above the median — that gap is the heavy tail at work. Using the conditional mean in P(claim) × conditional cost carries the cost of rare large claims, which a median-only summary would miss.
Per-policy uncertainty: the conditional cost distribution

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. The actuarial GLMs handle the portfolio level more naturally through their exposure-weighted likelihood and fitted intercept, although their balance can still depend on the model and regularization. We can give TabPFN one matching degree of freedom: a 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 adjusts the portfolio 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 move substantially closer to it, although some decile-level miscalibration remains. 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.
  • A rich conditional cost distribution per policy. Given a positive claim amount, TabPFN gives the conditional mean and quantiles with a shape that varies per policy; the Gamma benchmark has a constant-CV distribution.
  • Calibration is a simple adjustment away. Out of the box TabPFN under-prices the portfolio. A single factor estimated on held-out training data improves the balance from ~0.6 to ~0.96 and brings Tweedie deviance close to the GLMs, while provably leaving the ranking untouched.
  • The two-stage split helps TabPFN too. Occurrence × conditional aggregate cost is better calibrated than a single regressor on the raw zero-inflated amount — the actuarial intuition 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.