churn column, and no
feature matrix. Supervised learning needs both, and normally that means weeks of
bespoke joins and window aggregations.
TabPFN-Rel lets you declare them instead. You write two small YAML files — one
describing your tables, one asking your question as a SQL query — and the harness
builds the labels, aggregates each entity’s history across the foreign-key graph
with Deep Feature Synthesis (DFS), keeps the future out of the features, and runs
TabPFN on the result.
This cookbook does that end to end on the raw Olist Brazilian
E-Commerce CSVs, in a
few minutes on CPU.
📃 Read the full report on RelArena, TabPFN-Rel and RPI here
The shape of the problem
One idea does most of the work here: the anchor time. A prediction is always made as of some momentt. The model sees the database truncated at t, and the
label comes from the window after it.
t.
Two consequences worth holding onto. First, there are many anchors: the same
question is asked at 30-day intervals stepping back through history, so one seller
contributes many training rows. Second, the splits are cuts in time, not random
samples — you pick the two dates where train ends and test begins.
RelArena follows the RelBench protocol for final prediction: predict() defaults
to the task’s test_timestamp, and the feature database remains frozen at that
cutoff. Configuring a later at_timestamp changes the prediction anchor and emits
a warning, but it does not expose database rows written after test_timestamp.
Setup
RelArena—the package that ships TabPFN-Rel—is available as an alpha release on PyPI. The cell below installs it directly from the standard PyPI index. No GitHub token or local checkout is required. Thetabpfn-rel-api extra is the no-GPU stack: featurization runs locally,
while model fitting and prediction use the hosted TabPFN API.
The TabPFN token
Because the fit runs on the hosted API, you need a Prior Labs token. Grab one from ux.priorlabs.ai and add it as the Colab secretTABPFN_TOKEN (You can add a secret via the key icon on the right sidebar). Locally you can skip this — tabpfn-client opens a browser on the
first fit and caches the token.
This cell runs before every other import because tabpfn-client reads the token
once, at import time.
The database
Olist is a real Brazilian marketplace; the public dataset covers roughly 100k orders from 2016–2018 and downloads anonymously, no Kaggle account needed. Seven of its CSVs matter here, and the foreign keys between them are what TabPFN-Rel walks:Giving order_items a real event time
Event tables need a trustworthy timestamp, because that is the column the harness
censors on. orders and order_reviews have one; order_items only has a shipping
deadline. The cell below joins each item’s purchase time on from its order.
This is the only table that needs any code — everything else is read straight from
the raw CSVs.
The Database Schema
Now the first YAML: where each table lives, what its key is, which column carries its event time, and which foreign keys point where. That is the graph DFS will walk. The one judgement call iscolumns, an allow-list of the fields that become
features. orders carries delivery dates and a status that are only filled in
after the purchase — keeping them would hand the model facts that do not exist at
prediction time, so they are left out. Time censoring is automatic; deciding which
columns were backfilled is on you.
The Task File
The second YAML is the question. It names the entity you predict over, the target, the 30-day window, and the two dates that cut train from validation from test — then computes the label in SQL.test_timestamp is also the default prediction anchor.
An optional later at_timestamp changes the anchor but does not expose database
rows beyond test_timestamp.
That query is the heart of it, and it reads as two windows around each anchor time:
the WHERE EXISTS at the bottom looks backwards to keep only sellers who were
active in the previous 30 days, and the NOT EXISTS at the top looks forwards
and fires when no order arrives in the next 30. Churn only means something for
sellers who had started, which is why both are needed.
The rules for writing your own — which columns to emit, what timestamp_df is, how
{timedelta} gets substituted — are in the predictive-task
guide.
Check the labels before spending compute
Loading the spec validates both files, and constructing aPredictiveQuery runs
your SQL once per split. The table that comes out is your supervised dataset, so
it is worth a look before featurizing anything — if it is wrong, nothing downstream
will save it.
seller_id is internal — RelArena reindexes keys, and predict maps
them back.
Fit and predict
fit featurizes and trains; predict then scores the task’s configured prediction
anchor. Because this task does not set at_timestamp, the anchor defaults to
test_timestamp. The feature database is frozen at that cutoff, so rows from the
forward 30-day label window are not visible to the model. Later,
compute_test_labels() uses the complete source database only to evaluate the
historical predictions.
Two things to know. The model is a string, not part of the spec, so swapping
tabpfn-rel-client for constant-global or constant-per-entity runs a different
model against an identical task. tabpfn-rel-local provides the local GPU-backed
variant when its corresponding package extra is installed. And n_trials=0 skips
hyperparameter tuning, which is the right default for a first run against a hosted
API.
Most of the wall-clock time is spent on DFS rather than model inference—expect a
couple of minutes. Stronger CPUs with more CPU cores can speed up this step
significantly. If you are interested in caching the features created during this
step (and speeding up re-runs), see the optional caching example below.
Optional: Caching to reuse relational features
Depending on the strength of your CPU, TabPFN-Rel can spend a significant part of its runtime constructing relational features with deep feature synthesis (DFS), rather than running the model. Pass a localcache_dir to fit() to persist those features. Missing entries
are computed and stored as the query runs; later identical operations reuse
them. Nothing in the cache is uploaded.
This can be helpful in many scenarios, like:
- You might want to play with different levels of tuning, e.g.
n_trials=1andn_trials=3. Caching is independent of tuning so you can reuse the relational features. Note that forn_trials=0we don’t need to compute the cache for the inner split we tune, so going fromn_trials=0ton_trials>0will still require some extra cache computation. - If you use the local version of
tabpfn-rel, then you might want to run the cache pre-computation on a beefy CPU node for large datasets, while running the actual predictions oftabpfn-relusing a good GPU.
data_version identifies the exact database snapshot behind the cache keys.
Keep it stable while the CSV contents are unchanged, and bump it whenever the
row content changes. In Colab, ./relarena-cache lasts for the current runtime.
To retain it across runtime restarts, use a directory on a mounted Google Drive.
For more details, see the
predictive-interface caching documentation.
The caching API is optional and experimental.
First, configure the cache and fit the model. fit() computes and stores the
relational features it needs for training.
fit() is a lot faster even though it runs 3x the tuning than the first cell.
Caching also works for predict(), as you can see in the cell below:
Note: The speed-up is only calculated correctly the first time you run this cell. Delete/Rename/Change the cache directory in the cell above to rerun the measurements.
Does it beat simple baselines?
The Olist database contains the complete 30-day label window after its historical test timestamp,2018-06-15. We can therefore materialize the
held-out churn outcomes and compare the existing TabPFN-Rel predictions with
two learning-free baselines on the same sellers.
constant-globalpredicts the global churn rate observed during training.constant-per-entityuses each seller’s own history when available and falls back to the global rate otherwise.
preds and the fitted TabPFN-Rel query pq already exist from the previous
section.
predict() scores every seller by default, whereas the churn task evaluates
only sellers active during the preceding 30 days. The prediction frames are
therefore larger than test_labels. Joining from the labels selects the
relevant cohort and checks that every held-out seller received a prediction.
n_trials=3) can give another nice boost.
Where to go next
Your own database. None of this is Olist-specific — a schema file and one label query is the whole setup. The two things to get right are a real event-time column on every event table, and acolumns allow-list that excludes anything backfilled
after the fact.
If a fit hangs, the fan-out is too wide: DFS cost grows with how many
tables an entity links out to. Prune the schema or use a lower featurization depth.
Regression works identically — set task_type: regression and return a numeric
target (“how much will this seller sell”) instead of a 0/1 one.
- RelArena predictive-task guide — label queries, split timestamps, leakage
- The Olist example in the RelArena repo — the same task as a plain script
- TabPFN and tabpfn-client