Forecasting transaction volumes: lessons from a top-3 national finish
Predicting how often customers will transact sounds simple until the data is skewed, seasonal, and 18 million rows deep. Here's what actually moved the needle.
Forecasting how many transactions a customer will make next quarter sounds like a tidy regression problem. It isn't. The target is brutally skewed, most customers transact a handful of times while a few transact hundreds. The window that matters most is the holiday season, exactly when behaviour stops looking like the rest of the year. And the raw data runs to millions of rows. We took this problem on in Nedbank's national ML challenge and finished 3rd out of 251 entrants. This is what actually made the difference, written for any team facing skewed, seasonal count forecasting.
Start by respecting the distribution
The single most common mistake with count data is treating it as if it's normally distributed. It isn't, it's heavily right-skewed, and a model trained on raw counts will obsess over the handful of huge values and do poorly on everyone else.
The fix is a log transform (specifically log1p, which handles zeros). Modelling in log space stabilises the variance and means the error metric penalises *relative* errors evenly, being off by 10 transactions matters very differently for a 5-transaction customer than a 500-transaction one. Getting this one decision right matters more than any model choice that follows.
Build a temporal panel, not a single snapshot
The instinct is to compute features at one point in time and predict forward. That throws away most of your signal and invites leakage. Instead, build a temporal panel: pick multiple historical cutoff dates, and for each one, compute features using only data before that date and the target using the window after it.
This does two things. It multiplies your training data, every customer becomes many training rows, one per cutoff. And it forces discipline: because each row's features are strictly bounded by its cutoff, you physically cannot leak future information, which is the error that quietly inflates validation scores and then collapses in production. We used nine cutoffs spanning two years.
Engineer features from first principles
There's no shortcut here, and it's where most of the work lives. The features that carried the most weight were the obvious-in-hindsight ones:
- Recent activity, the count over the last 1, 3, 6, 12 months. Recent behaviour is the strongest predictor of near-future behaviour.
- Lags, last month, the month before, going back two years.
- Year-on-year seasonal features, what did this customer do in the *same* window last year? Essential for a holiday-period forecast.
- Rolling statistics and trend slopes, is this customer accelerating or winding down?
Together with demographics, financials, and calendar seasonality, this came to a few hundred features. But raw count isn't the interesting part.
The breakthrough: leave-one-cutoff-out target encoding
High-cardinality categoricals, like residential city with hundreds of levels, are hard. One-hot encoding explodes the feature space; plain label encoding is meaningless to a tree. Target encoding (replacing a category with the average target for that category) is powerful but notoriously leaky if done naively, because a row's own target bleeds into its own feature.
The disciplined version, and the thing that moved our score most, is leave-one-cutoff-out target encoding: to encode a row at a given cutoff, compute the category average using only the *other* cutoffs, then blend it toward the global average with a smoothing factor so rare categories don't get overconfident estimates. Done this way, the residential-city encoding became one of our most important features, with no leakage. The lesson generalises: target encoding is a scalpel, and the leakage discipline is the whole game.
Diversity beats a single clever model
We trained LightGBM, XGBoost, and CatBoost, three gradient-boosting libraries that make slightly different structural choices, plus a model using a Tweedie objective (a loss designed for exactly this kind of skewed, non-negative data). Each was bagged across several random seeds and tuned with Bayesian optimisation.
Here's the honest nuance: the three main models were highly correlated (their features dominate), so blending them equally only helped a little. The Tweedie model, despite being weaker on its own, used a *different loss* and so made *different mistakes*, and that uncorrelated slice of error is what a blend actually feeds on. Diversity in a blend comes from different inductive biases, not from training the same model five more times.
Segmented blending: one size doesn't fit all customers
The final gain came from recognising that a sparse customer and a heavy user are different prediction problems. So instead of one global set of blend weights, we segmented customers by recent activity (low, medium, high) and optimised blend weights separately per segment against a held-out month. Heavy users, with stable patterns, leaned more on the Tweedie model; sparse users leaned on the seasonal year-on-year features. The per-segment blend beat the global blend measurably.
Honesty is the method, not an add-on
Two principles ran through all of it. First, validate on data the model has never seen and let that, not the public leaderboard, drive decisions, chasing leaderboard noise is how you overfit in public. Second, make it reproducible: the entire result runs from a single command in about 30 minutes, with automatic checks on every prediction (no NaNs, no negatives, complete coverage). A result you can't reproduce is a result you can't trust, and in a bank, trust is the product.
That discipline is why the finish was 3rd of 251, judged purely on numbers against 251 teams. If you're forecasting skewed, seasonal count data, respect the distribution, build a leak-proof temporal panel, encode categories carefully, blend genuinely diverse models, and segment where behaviour differs. The techniques aren't exotic. The discipline is the hard part, and it's the part that wins.
Key takeaways
- →For skewed count data, model in log space (log1p) first, it stabilises variance and penalises relative errors evenly. This decision outweighs model choice.
- →Build a temporal panel over multiple historical cutoffs: it multiplies training data and makes future-information leakage structurally impossible.
- →Leave-one-cutoff-out target encoding with smoothing unlocks high-cardinality categoricals without leakage, it was the single biggest score gain.
- →Blend genuinely diverse models (a different loss like Tweedie adds more than another correlated model), and segment blend weights by customer behaviour. Validate on held-out data and keep it reproducible.
Sources & further reading
- 1.Nedbank ML & Data Science Challenge 2026, national competition hosted on Zindi; finished 3rd of 251 active entrants (private RMSE 0.3631)
- 2.Nullius Technologies case study, Nedbank transaction-volume forecasting, 3rd of 251, 0.3631 private / 0.3753 public RMSE
Related service
Data Science & Machine Learning
Turn data you already have into models, forecasts, and dashboards that drive decisions.