From one number to a function
Everything up to session 6 produced a single figure for the whole list. That figure is what a policy needs when the policy is "send it or do not". The moment the policy becomes "send it to some people", the object of interest stops being a number and becomes a function of the customer, and almost every practical property changes with it: what can be validated, what can be plotted, and what a wrong answer costs.
★ What you walk out with tonight. Three working meta-learners on the Kestrelmark table, each with its documented weakness demonstrated rather than described. The one distinction that decides how much any of them can deliver: the segment effect here is almost fully recoverable, at an R-squared of 0.986 from a single field, and the individual effect is not recoverable at all, because inside every segment there is still a 3.0 coin flip no feature predicts. And the reason a covariate can belong in a CATE model without belonging in the adjustment set.
LiveWhy the average stopped being enough3 min▶
A budget that covers the whole list makes heterogeneity a curiosity. A budget that covers a fifth of it makes heterogeneity the entire problem, because the question is no longer whether the campaign works but which fifth to spend it on. Those two questions can have opposite answers: a campaign with a healthy average can be worth running on half the list and worth withholding from the other half, and the average will never say so.
Kestrelmark is that case, measurably. The file carries a recency segment, and the email is worth a different amount in each of its four values: +2.10 to a customer who is still active, +29.09 to one who lapsed 30 to 90 days ago, +1.04 to one dormant a year, and -8.87 to one who has already cancelled once. Those four average to the 6.23 the bench recovers, and the 6.23 describes none of them.
There is a second layer underneath, and it is the one that sets the ceiling on every model tonight. Inside each of those four groups the effect still takes one of two values, 3.0 apart, according to whether the email is what tipped that person into opening. Nothing observed predicts which. So the honest shape of the answer is: the group is knowable, the person is not.
The version that reaches a planning meeting is a proposal to cut a campaign whose average lift has fallen. Half the time the average fell because a large low-response segment was added to the list, and the original segment is performing exactly as it always did. Nothing in an average distinguishes those two stories.
Self-studyThe vocabulary, kept straight3 min▶
- ATE - the average treatment effect over the whole population. 6.23 here.
- CATE - the average effect among customers sharing a given set of covariate values. A function of those covariates. At Kestrelmark, conditioning on
recencyalone gives +2.10, +29.09, +1.04 and -8.87. - Individual effect - one person's own difference between two worlds. Session 6, and it needs the structural equations.
- Effect modifier - a variable that changes the size of the effect. It need not cause the treatment, and if it does not, it is not a confounder.
recencyis the one here. - Uplift - the industry word for a predicted CATE used for targeting. Same object, different room.
- Meta-learner - a recipe that turns any ordinary regression algorithm into a CATE estimator by arranging what it is fitted on.
- Base learner - the ordinary algorithm doing the fitting inside a meta-learner. Gradient boosting, ridge, a forest: the choice matters and it is a separate literature.
The distinction that causes the most trouble in practice is the second against the third. A CATE describes a group defined by covariates and can be estimated from data. An individual effect describes a person and cannot. A targeting system that quietly slides from one to the other is making rung-3 claims on rung-2 evidence.
Conditional average treatment effects
9 min liveThe definition is short: the average treatment effect conditional on covariates. That makes it a function rather than a number, and the average effect of 6.23 is its mean over the population. On this data the function is far from flat, and one field carries almost all of it.
LiveA function, not a number3 min▶
Write the estimand out and the change is obvious: instead of one difference of means over the whole list, there is a difference of means inside every covariate cell, and the collection of those differences is the object. Averaging the function over the population returns the 6.23 from the bench, which is the first and cheapest check any CATE model should pass.
- The covariates that define the cells are a choice. Condition on season alone and you get two numbers. Condition on
recencyalone and you get the four that matter here. The finer the conditioning, the closer to an individual and the noisier every cell. - The rule is not "only what you adjusted for". It is "nothing that was forbidden". Colliders, mediators and anything downstream of the treatment stay out, for exactly the reasons session 2 gave. A pre-treatment variable that does not cause the treatment was never forbidden, and here it turns out to be the whole story.
- The function can be flat, and it is worth knowing that in advance. A finding that the effect does not vary with anything you measured is a result, not a failed project. On this data it is emphatically not flat: the widest gap between two segments is 29.09 against -8.87.
Check the mean before looking at anything else. If the average of your predicted effects is far from the average effect you already trust, the per-person numbers are not going to rescue it. That check costs one line and it catches most wiring mistakes.
LiveAn effect modifier is not a confounder3 min▶
This is the point that decides what goes into the feature list, and it is the one most often got wrong in both directions. recency shifts a customer's baseline spend and it sets how much the email is worth to her. What it does not do is influence whether she was sent the email. No arrow runs from recency into the treatment, so it opens no back door, and the adjustment set defended in session 3 is unchanged: {season, tenure}.
That is the difference between the two jobs a covariate can hold. Closing a back door is about identification and the set is forced on you. Describing how the effect varies is about the estimand you chose, and the set there is a modelling decision. A variable can hold the second job without the first.
LiveThe target is never observed, for anybody4 min▶
This is the property that makes the whole area strange, and it is worth saying slowly. In ordinary supervised learning every training row carries the answer: the price the house sold for, the class of the image. A model is fitted to that answer and scored against held-out copies of it.
There is no such column here. A row records what the customer spent under the treatment she received, and the treatment effect is a difference between that and what she would have spent under the other one. No row contains both. The quantity being modelled has never been observed for a single customer in the table, and it never will be.
- So there is no loss function on the target. Every method builds an indirect construction: a difference of two fitted surfaces, a transformed outcome, a pseudo-effect. What gets minimised is always a proxy.
- And there is no honest held-out score. A validation set has the same missing column, so the usual habit of trusting the leaderboard does not transfer. Session 8 covers what can be done instead by ranking rather than by scoring.
- Which is why model comparison here is unusually hard, and why running three learners and comparing their disagreement is not a luxury. Disagreement is one of the few signals available.
The most expensive version of this mistake is an uplift model reported with an R-squared on spend. The number is computed against observed outcomes, not against effects, so it describes how well the outcome was predicted and says nothing at all about the targeting. Models with excellent fit routinely rank customers no better than chance.
LiveHow far the recovery goes, and exactly where it stops4 min▶
Because Kestrelmark is generated from a written model, the per-person effect can be computed rather than estimated, which is what session 6 did. Doing it for all 5,000 customers gives eight values and no others. Every one of them is a segment's own direct effect, plus 3.0 when the email is the thing that tipped that person into opening.
| Individual effect | Customers | Segment |
|---|---|---|
| -10.0 | 245 | sleeping dog |
| -7.0 | 148 | sleeping dog |
| 0.0 | 1,614 | lost cause |
| +1.0 | 709 | sure thing |
| +3.0 | 851 | lost cause |
| +4.0 | 411 | sure thing |
| +28.0 | 650 | persuadable |
| +31.0 | 372 | persuadable |
Regress that column on recency alone and the R-squared is 0.986. Almost all of the variation in who the email helps is one field in the warehouse, which is unusually kind and is the reason a CATE model has anything to find here at all. What is left over is the part no feature can reach.
- The segment effect is the ceiling. A perfect model, given every observed column, converges on +2.10, +29.09, +1.04 and -8.87. That is the best any of tonight's three learners could possibly do, and it is enough for every decision session 8 makes.
- The individual effect is out of reach, and not for want of data. Two persuadables with identical covariates get identical predictions, while one of them truly gets 28.0 and the other 31.0. What separates them is whether the email happened to be the thing that made them open it, and that is an unobserved draw rather than a missing column.
- So the right unit of a targeting promise is a group. "Customers who lapsed 30 to 90 days ago respond at 29.09" is defensible. "This customer will respond at 29.09" is not, and the second sentence is what a ranked file with a name against every score invites people to say.
Say the ceiling out loud before you build. Ask what mechanism in the world would make the effect vary, and whether any column you hold could capture it. Here the answer was a field the business already understood. When the answer is "nothing obvious", a flat function is the likely finding and a beautiful predicted spread would be the model describing itself.
Self-studyWhat the four numbers are worth, and where that argument goes3 min▶
Estimating the effect per person is tonight. Turning it into a send list is session 8. It is still worth seeing why the estimate matters, because the gap is not marginal.
Leader session 5 puts Kestrelmark's fully loaded cost at 7.00 per send, in the same units as spend. Against that bar, only the persuadable segment clears: 29.09 against 7.00. Sure things at 2.10, lost causes at 1.04 and sleeping dogs at -8.87 all lose money, the last group twice over. A blanket send to the whole file is worth -3,848; sending only to the 20.4 percent persuadable segment is worth +22,578.
That comparison is the payoff of estimating a function instead of a number, and it is the reason to get the segments right rather than merely to get a ranking. How to choose the depth of the send, how to score one ranking against another, and how to read the curve that finds the stopping point, all belong to session 8, which consumes the per-customer predictions this session produces.
Three meta-learners and their documented weaknesses
10 min liveThe three below are the standard family (Künzel and co-authors, 2019). They are recipes rather than algorithms: each one says how to arrange the data and what to fit on it, and any ordinary regression can sit inside. Each also comes with a weakness that is documented rather than folklore, and the weaknesses do not overlap, which is exactly why running all three is informative.
LiveThe S-learner, and the effect it can lose entirely3 min▶
Fit one model on the covariates with the treatment included as an extra feature, then score every row twice, once with that feature set to 1 and once with it set to 0. The difference is the predicted effect. It is the simplest recipe, it uses all the rows for one fit, and it is usually the first thing anybody writes.
- The documented weakness is regularisation. Because the base learner is normally regularised, the estimated treatment effect is biased toward zero, and the treatment column is just one feature competing with all the others for a share of the penalty budget.
- If the treatment is weak relative to the other features, it can be ignored entirely. A tree-based learner may never split on it; a penalised linear one may shrink its coefficient to nothing. The output is then a predicted effect of zero everywhere, which looks like a clean finding of no heterogeneity.
- It is worst exactly where it matters. A strong treatment survives the penalty; a marginal one, which is the case where targeting could actually help, is the one that disappears.
The failure is silent in the same way a mediator is silent in session 2. Nothing errors, the outcome fit looks respectable, and the targeting recommendation is "it makes no difference who you send to".
LiveThe T-learner, and what it trades for that safety3 min▶
Fit one model on the treated rows only and another on the control rows only, then difference their predictions for every customer. The treatment can no longer be shrunk away, because it is never a feature: it is the reason there are two models.
- It avoids missing a weak effect, which is the specific fault it exists to fix, and that is enough reason to always run it alongside an S-learner.
- It still suffers regularisation, now in both fits at once, and the two penalties do not cancel. Their difference can wander in ways neither surface would on its own.
- And it fits two models on smaller samples. Each learner sees only its own arm, so where one arm is thin the surface there is poorly estimated and the difference inherits that noise directly.
Look at the arm sizes before choosing, segment by segment. A campaign sent to 15 percent of the list gives the treated model a seventh of the rows, and the T-learner's predictions will be worst precisely in the covariate regions the campaign has rarely reached, which are the regions a targeting exercise is about to expand into. Sleeping dogs are 7.9 percent of Kestrelmark's file to begin with, so their cells are thin in both arms.
LiveThe X-learner, built for the imbalance that is normal outside experiments4 min▶
Three moves. First, impute the counterfactual outcome for every row using the other arm's model: for a treated customer, what the control surface says she would have spent. Second, take the difference between her observed outcome and that imputed one, which gives a pseudo-effect for every row, and regress on those. Third, combine the two resulting fits, weighting them by propensity so that each region leans on the arm that has more data there.
- It is particularly suited to treated and control imbalance, which is the norm outside experiments. Observational campaign data is almost never balanced, and this is the recipe that was designed around that fact.
- The propensity model here is a component, not a topic. It appears as a weight between two fits. Fitting one as an estimator in its own right, with inverse probability weighting or doubly robust estimation on top, belongs to
learn-experimentation-with-phoebeand is deliberately not done here. - The cost is two extra moving parts. The imputation is a model, the weighting is a model, and an error in either propagates into every per-person number. More structure buys more places to be wrong.
Fitting any of these three well, choosing base learners, tuning them without a validation target, and getting honest variance out of them, is a modelling exercise with its own substantial literature; what matters here is knowing which recipe fails in which direction.
LiveAll three inherit every assumption from sessions 2 and 33 min▶
Nothing in any of these recipes revisits identification. They take an adjustment set as given and produce a function on top of it, which means the covariate set on the inside is the same object that sessions 2 and 3 spent their time defending. Put the wrong columns in and the model does not fail loudly; it personalises the failure.
- A collider in the feature set is still a collider. Adding the loyalty score moves the average from 6.56 to 5.26, and there is no reason it improves per segment. It biases every cell and the biases need not point the same way.
- A mediator changes the estimand at every level of granularity. Including the open flag drops the average to 5.48, close to the mean direct effect of 5.16, so every predicted number becomes a conditional direct effect and the whole surface answers the subject-line question rather than the campaign question.
- A confounder left out is left out everywhere. The naive comparison reads 10.54 against a truth of 6.23, and that +4.31 of leakage does not distribute itself evenly; it lands in whichever segments are most affected by season and tenure, which is the shape of a targeting rule built on nothing.
- Anything downstream of the treatment is the worst of them. Adding the support ticket takes the average to 2.59. A post-treatment column in a feature list is a targeting model conditioning on the future.
The reason this is worth insisting on is that per-person output looks far more authoritative than an average. A single lift figure invites the question "compared with what". A ranked list of 5,000 customers with a score against each name invites action, and the assumption set that produced it never travels with the file.
Self-studyWhat a negative predicted segment does and does not mean3 min▶
Every learner will produce some customers with negative predicted effects, and the two available mistakes are equal and opposite: naming a harmed segment that is only variance, and dismissing a real one as variance. Kestrelmark punishes the second. The sleeping dogs are 7.9 percent of the file at -8.87 each, and their individual effects are -10.0 or -7.0. A negative region here is a finding, and a model that smooths it away has lost the most valuable thing in the data.
- The first check is whether the negative region survives a change of learner. If the S-, T- and X-learners nominate different customers, there is no segment, only variance. If all three land on the same covariate values, that is about as much validation as this problem allows.
- The second check is whether a mechanism exists. Harm needs a story: an unsubscribe, an irritation, a cannibalised purchase. Here the story is in the segment definition itself, since these are customers who have already cancelled once and are being reminded of it.
- The third is sample size in the cell. Small cells produce extreme predictions in both directions, and the most extreme predictions in any uplift model are usually its least reliable ones. The smallest true segment is also the harmed one, which is the awkward combination.
So the discipline is not scepticism about negatives, it is the same discipline as everywhere else: a named group, a stated mechanism, and a number that holds up when the recipe changes.
Three meta-learners on the same table
★ 18 min · everyone buildsAll three learners, on the back-door set plus the effect modifier, checked against the known average of 6.23 before anything else happens. Then the segment recovery against the four true values, then the two segments where the learners disagree.
Fix the feature set first, in writing, and say why each column is there. Season and tenure because they close the back doors. Recency because the effect varies with it and nothing forbids it. Loyalty, opened and ticket stay out for the same reasons they stayed out of the average, and writing that line before any code prevents the feature-selection habit from quietly reopening session 2.
Hold out 1,500 rows and do not touch them. Session 8 scores a ranking on those rows and every learner tonight must be blind to them, or the curve next session is measuring memory.
Build the S-learner. One base learner on the covariates plus the treatment column, scored twice per row. Keep the fit ordinary; the point is not a good model, it is a comparable one.
Build the T-learner. Two fits, one per arm, differenced over the whole table. Note how many rows each arm actually has, because that number explains most of what you see later.
Build the X-learner. Impute the other arm for every row, form the pseudo-effects, fit on them, then weight the two fits by propensity. Three more lines than the T-learner and two more chances to be wrong.
Sanity check every one against 6.23 before looking at any individual number. The mean of the predicted effects should land near the average effect you already trust. A learner that fails this has a wiring problem, not a heterogeneity finding.
Score the segments, not the customers. Group the predictions by recency and compare the four means against +2.10, +29.09, +1.04 and -8.87. That is the only honest scoreboard available tonight, and it is available only because this data is generated.
Make the S-learner's documented weakness visible. Add noise columns, turn the regularisation up, and watch the S-learner's mean slide toward zero while the T-learner's holds. That drift is the published failure mode reproduced on your own screen.
Find the disagreement segment and say what you would do about it. Write it into the record with the covariate values that define it. A disagreement segment is a request for more data, not a targeting list.
The mean is a wiring check, not a result. Landing near 6.23 says the plumbing is right and says nothing about whether the per-customer spread is real. Landing far from it says stop, because no amount of segment analysis repairs a learner that cannot recover the number you already know.
Report the segment, promise the segment. The four means above are what a learner can be held to. The per-row numbers underneath them are the same four means with noise on top, and the moment they are exported with a customer id beside each one, somebody downstream will read them as a prediction about a person.
Write the segments down by their covariate values, not by customer id. A list of ids is a snapshot that ages out within a quarter and cannot be argued with. "Lapsed 30 to 90 days" is a rule, it can be checked next quarter, and somebody can disagree with it in a review, which is the only thing a written rule is for.
Before session 8
30 min- List the covariates you would put in a CATE model for one intervention you own, and split the list in two: the ones that are there because they close a back door, and the ones that are there because you believe the effect varies with them. Anything that fits neither column comes out.
- For each variable in the second list, write the one sentence of mechanism that says why the effect would be larger or smaller there. A variable with no sentence is a variable you are hoping about.
- Run all three learners with the adjustment set you defended in session 3, and record each one's mean predicted effect beside the average effect you already trust.
- Reproduce the S-learner drift on your own data: raise the regularisation in three steps and record what happens to the S mean and the T mean.
- Count the rows in each arm, within each candidate segment rather than overall. The segment with the thinnest treated cell is where all three learners will be least trustworthy, and it is often the small harmed group that matters most.
- Name one segment your model puts below zero. Say whether you believe it, and give the mechanism or the reason you do not. Bring it to session 8, where the targeting rule gets scored against sending at random.
Three questions
An S-learner with a heavily regularised base learner returns a predicted effect of almost exactly zero for every customer, while the T-learner on the same covariates returns a mean near 6.23. What is the most likely explanation?
The treatment is one column among many inside an S-learner, so regularisation biases the effect toward zero. The T-learner never puts the treatment in a penalty because it is the reason there are two models, which is exactly the fault it exists to avoid. On this data a flat zero would be badly wrong: the true segments run from -8.87 to +29.09.
A learner recovers Kestrelmark's four segment effects almost exactly. Two persuadable customers with identical covariates get the same prediction, but one truly gains 28.0 and the other 31.0. What should be concluded?
Inside every segment the effect takes one of two values 3.0 apart, according to whether the email is what tipped that person into opening. No observed feature predicts that draw, so identical covariates must give identical predictions. The group is the unit a targeting promise can be made about; the person is not.
A team adds the loyalty score to the covariate set of an X-learner, reasoning that a segment description can use any column since nothing is being adjusted away. What follows?
A meta-learner takes the adjustment set as given and builds a function on top of it. Every identification assumption from sessions 2 and 3 is inherited, and a bad set does not fail loudly here; it distributes the failure across a ranked list of names. Note the contrast with recency, which is safe precisely because it causes nothing about the treatment.