Attribution · 16 min read · Interactive
Which marketing channels actually drive sales, and which just show up at the close? The Markov chain removal effect
It is the last Thursday of the quarter, and the budget sheet is up on the big screen. Search sits at the top in green. Display sits near the bottom with a little under one sale in ten to its name, and somebody has already typed cut? in the comment box beside it.
The person who runs display is sure the number is wrong and cannot say why. I have been that person, holding a cold coffee and a hunch. This post is about swapping the hunch for something you can show the room.
The short answer
The channels that drive sales are the ones whose loss would cost you sales, and they are often not the ones a customer touched last. Last-click reporting gives the whole sale to the final touch, so it starves the channels that start journeys. A fairer test asks how far your conversion rate would fall if a channel vanished from your customers' paths. That drop is the removal effect of a Markov chain model, from a 2016 study by Anderl and colleagues. To use it, export your channel paths and let the model take each channel out in turn. On four large advertisers' data, it gave paid search and direct visits less credit than last-click did.
The method cannot prove that a channel causes sales. In big Facebook experiments, attribution disagreed with a randomised test by a factor of three in half the studies.
Why the last touch gets the money
Budgets follow per-channel results, so the question of what each channel contributed decides what gets funded next quarter. Last-click answers it with one rule: the final touch before the sale takes 100% of the credit and everything earlier takes nothing. First-click, linear, position-based and time-decay each swap that rule for another fixed rule. None of them measures anything. Each assumes a shape and stamps it on every journey.
The failure is specific. A display ad that primes a later branded search, or a podcast mention that ends in a typed-in visit, is almost never the last touch, so last-click records it as idle. Cut it, and the channels further down quietly lose the warm traffic they were closing. The report never saw it coming, because it never recorded what the first channel was doing. That is the number near the bottom of the big screen.
The idea, in plain words
Picture each customer journey as a walk through a building. Everyone comes in through the same entrance hall (START). Each channel is a room. There are two exits, one where the customer buys (CONVERSION) and one where they leave without buying (NULL), and once through either door the walk is over. Statisticians call the rooms states, the exits absorbing states and the whole building a Markov chain.
From your journeys you count how often people walk from each room into each other room. That table of chances is the transition matrix.
Then comes the test. For each channel, lock its door. Anyone who would have walked into it is lost: their step goes to NULL instead. Recompute the chance that a fresh journey ends in a sale. If it drops a lot, the channel was holding the building up. If it barely moves, the channel was easy to replace. That is the published rule [1]. The other choice, sending those customers on through the other doors, is the far end of the range I come to later.
The formula, term by term
Here is the only maths in the post. It takes five lines, and you can skip to the example without losing the thread.
Split the transition matrix into two blocks: Q, the moves between rooms, and R, the moves out through the exits. One piece of linear algebra, the fundamental matrix of an absorbing chain [2], does the heavy lifting:
N = (I − Q)−1
- Q
- the moves between the transient states (START and the channels).
- I
- the identity matrix, the same size as Q.
- N
- entry N[i][j] is the expected number of visits to state j by a journey that starts at state i. This is exact. No simulation, no loop, no waiting for anything to settle.
Multiply by R for the chance of ending at each exit, and read off the baseline:
B = N RPconv(full) = B[START, CONVERSION]
- B[i][j]
- the chance that a journey starting at i ends at exit j.
- Pconv(full)
- the chance that a fresh journey eventually buys, with every channel in place.
Now remove channel c. Every move that used to go into c goes to NULL instead. Recompute on the smaller network and take the drop, then rescale the drops into shares that add up to 100%:
Tc[i][NULL] = T[i][NULL] + T[i][c], Tc[i][c] = 0
RE(c) = Pconv(full) − Pconv(c removed)
Credit(c) = RE(c) / Σ RE(c′) × 100
- T[i][c]
- the share of state i's traffic that went to c. Adding it to the move to NULL keeps each row summing to one, and every other move stays as it was.
- RE(c)
- the removal effect: what losing the channel, and the customers it carried, costs your conversion rate. This is the honest quantity. The credit share is a convenience for putting channels on one scale.
Under this rule the drop is exactly the share of journeys that pass through c and go on to buy, so it is never negative and never larger than the conversion rate itself. Anderl and colleagues write it as RE(c) = Visit(c) × Eventual Conversion(c): how often journeys reach the channel, times how often they buy from there. They take both factors from the adgraph model of Archak, Mirrokni and Muthukrishnan [3]. Using the drop as a measure of credit is their own addition [1].
A worked example synthetic data
The notebook builds 4,000 journeys across three channels (synthetic, built so the flow that generated it is known in advance). Display starts most journeys and mostly hands people on to search. Search is where the sales happen. Email is a weaker feeder whose visitors mostly drift off. About one journey in three ends in a sale.

| Channel | Last-click | Markov | Removal effect RE(c) |
|---|---|---|---|
| Display | 9.8% | 35.7% | 0.191 |
| Search | 78.6% | 47.7% | 0.254 |
| 11.6% | 16.6% | 0.088 |
Display more than triples. Last-click barely credits it because it is almost never the final touch, but take it out of the network and the searches it feeds never happen. Search falls by almost two-fifths, from 78.6% to 47.7%. It closes most sales, but many of the people it closes came through display or email first. Last-click was scoring position in the journey. The removal effect scores whether the channel carries weight.

With the customers lost, removing Search drops the chance of a sale hardest, because it is the closer. Removing Display does nearly as much damage, because display feeds search. The lighter bars send the same customers on through the other channels instead. Every drop there is smaller, and removing Email actually raises the modelled rate, because its visitors reroute onto channels that sell better. That negative drop exists only at that end of the range, and it is clipped to zero there: a channel cannot earn negative credit.
| The example at a glance | |
|---|---|
| Journeys | 4,000 |
| Share that end in a sale | 32.0% |
| Modelled chance of a sale from START | 0.320 |
| Buying journeys with a single touch | 34.6% |
| All journeys that touch a single channel | 54.0% |
Move the journey yourself
Caveats to watch out for
Every method has situations where it gives a confident wrong answer. These are this one’s.
The removal effect measures how the pieces of your journey data connect, not proven cause and effect. A channel can carry a lot of journeys and still not be causing extra sales. Field experiments at Facebook found that attribution read from observed data can disagree with a randomised test on the same advertiser’s data, in half of their studies by a factor of three, across every method they tried [4]. In my reading that is enough to flip a budget call. Before a number moves real budget, back it with an experiment such as a geo holdout.
A competitor outbidding you for the keenest buyers leaves your channel reaching less keen ones, and the model reads that as the channel's own weakness. Gordon and colleagues point out that an advertiser can end up with this bias through what another advertiser does, with no fault of its own [4]. None of your data records their bids, so nothing here can correct for it. Only an experiment measures a channel net of the competition.
Branded search and direct visits sit at the end of almost every journey, and the critique is that removing the channel everyone passes through collapses sales even when its extra contribution was small [5]. The published evidence points the other way: on four real data sets, the Markov model gave less to paid search and direct type-ins than last-click did, and more to the channels that start journeys [1]. On this example it depends on the assumption. Search takes 47.7% with lost customers, well under its last-click share, and 82.8% if every channel’s customers would come back another way. Read a dominant closing share with suspicion, not satisfaction.
With lost customers, a channel gets zero credit only if no buying journey ever passes through it. Its range can still reach zero. Email’s does, because if its visitors would all have reached a sale through the other channels, removing it costs nothing. Whether they would is not in the journey data. Email also catches people who have not bought yet, through a newsletter or a contact sign-up, so it is more than a way back for existing customers. Zero at one end of a range describes an assumption, not the channel.
The model assumes your next step depends only on where you are now, so a cold search and a search that a display ad warmed up count as the same state. Models that remember the last few steps, called higher-order models, can capture the sequence. In the published comparison the version without memory predicted no better than last-click, the gain came from the models with memory, and their results grew less stable as the memory grew. Anderl and colleagues recommend three steps of memory for standard analyses on rich data [1]. On the thin journey data a mid-sized advertiser usually has, I start without memory, the ChannelAttribution package’s own default [6], and let the data argue for more. That is a compromise, not a free lunch.
The method credits the journeys it can see. Cookie loss, people switching devices, consent refusals and dark social (word of mouth and shared links that show up as Direct) all break journeys up or hide them, and credit drifts towards whatever channel is still visible at the close. Watch your share of single-touch sales, but read it for what it is. A sale with one touch needs no model, so a high share is not by itself a sign of broken tracking. In the four real data sets of the published study, 66% to 86% of journeys had a single touch [1]. What a high share does is make the Markov credit mostly last-click with extra steps. The notebook counts it over buying journeys (about a third of them in my example), and above seven in ten it tells you to read the credit beside a rule-based split and to check whether journeys are being split apart.
One transition matrix is fitted to every journey pooled together, so its credit describes the most common journey (here the 54.0% of all journeys that touch a single channel) rather than the customer who is worth the most. A channel that matters only on the long, valuable journeys can be under-credited. Split the journeys by segment or by order value, fit each, and see whether the credit moves.
The transition matrix is estimated over a window and assumes customer behaviour stays the same across it. A new campaign, a seasonal shift or an audience that changes halfway through blends two different worlds into one average.
What I added: credit as a range, not one convention
The removal effect hides a choice, and most tools make it silently. When you delete a channel, what happens to the customers who would have arrived through it? The published rule says they are lost [1]. The other choice sends them on to the other channels, in proportion to where everyone else went, which assumes they find another way to buy. Some tools use that second rule by default.
Neither is right for every channel. Pull out one of several interchangeable awareness channels and its customers mostly find another route, so the second rule fits. Pull out a channel that some customers use as their only way in, and those customers are gone, so the first rule fits.
So my headline uses the published rule, and I add one dial, a substitution rate s, to show how far the answer moves. At s = 0 the removed channel's customers are lost, which is the headline. At s = 1 they all come back another way. For each channel I take two extremes: its credit when its own customers would all come back while every other channel's are lost, and the reverse. The range runs between them.
| Channel | If its customers come back | Headline (customers lost) | If only its customers are lost | Range |
|---|---|---|---|---|
| Display | 9.2% | 35.7% | 53.2% | 9 to 53% |
| Search | 37.5% | 47.7% | 88.0% | 38 to 88% |
| 0.0% | 16.6% | 30.4% | 0 to 30% |
Each end asks a different question in each row, so neither end column adds up to 100%. The headline is the one consistent scenario, and it sits inside every range.
Read the widths. Every range here is wide, and display's top end is above search's bottom end. So which of the two earns more depends on how many customers would come back, and the journeys cannot tell you that. When two ranges overlap like that, the model has narrowed the question without answering it, and the next step is a test. When a pair does not overlap, the order holds whatever the assumption, and you can act on it.
What I deliberately do not do is place each channel at a point inside its range from the same journeys. A channel whose visitors mostly move on looks replaceable, and one whose visitors keep coming back to it looks less so. But any such score is another reading of the same transition matrix that produced the range, so it would narrow the answer without adding any new evidence. I measured one such rule on simulated accounts with a known truth (treat the channels that mostly bring people back as fully replaceable), and it helped in some and hurt in others. Placing a channel inside its range needs data on what happens when a channel goes quiet, which is the subject of the next section.
From correlation to cause: why you triangulate
Everything so far reads the journeys you already have. It can tell you which channels carry the journeys, but carrying journeys is not the same as causing extra sales. A channel can sit on every winning path and still be a passenger.
The only way to know whether a channel causes sales is to change it and watch. A geo holdout switches a channel off in some regions and leaves it on in others, then compares. The channel causes the difference, because nothing else differed by region on purpose. Google and Meta will also run this for you at the level of individual users (a held-out group sees no ads, and the gap in sales is the lift), though only for spend on their own platform.
Neither replaces attribution. Attribution is cheap and runs on data you already have, so it is the right tool for narrowing the field to the two or three channels whose credit is surprising and worth the price of a test. An experiment is slow and expensive, so you spend it on those few. Running more than one independent method and seeing where they land is called triangulation. When the removal effect, a media-mix model and a holdout test all point the same way on a channel, you can move budget on it. When they disagree, the disagreement names the channel to test next. The confidence comes from independent methods agreeing, and from stating the range when they do not.
How the industry uses it
The removal effect comes from marketing science. Anderl and colleagues introduced the framework and applied it to four large data sets from real advertisers, comparing models with and without memory [1]. The most widely used open-source version is the R package ChannelAttribution, which its CRAN page calls "a probabilistic algorithm for the attribution problem" [6]. Commercial vendors build on the same idea: Windsor.ai, for one, sells Markov models as an attribution product [7].
One point is easy to get wrong. Google's data-driven attribution is not a Markov model. Google Analytics 4 describes it as machine learning with a counterfactual approach [8]. Google's methodology page names the mechanism outright: it "computes the counterfactual gains of each marketing touchpoint" using the Shapley value from cooperative game theory [9]. Neither page mentions Markov chains. The two are cousins, since both ask what would happen if a touchpoint were not there, which is exactly why they get confused. If you compare your own Markov result with Google's number, you are comparing two different methods, not checking one against itself.
Run it on your own data
Both downloads run entirely on your machine, and your data never leaves it. They take a table of conversion paths, the same format the open-source tools accept:
| column | meaning | example |
|---|---|---|
path | channels in order, separated by > | Display > Search > Email |
conversions | journeys that followed this path and converted | 120 |
non_conversions | journeys that followed it and did not | 380 |
Both warn you in plain words when the data is too thin to trust, instead of printing a confident number they cannot support. As rough rules of thumb they flag fewer than about 300 buying journeys and any channel seen in fewer than about 30 journeys. They also flag more than seven in ten buying journeys with a single touch. The notebook prints every channel's range beside its headline credit and names the pairs whose ranges overlap.
Getting your data into that shape
Almost nobody has a paths table lying around. What you can export is a raw touch log: one row per touch, saying who, when, which channel and whether they bought. The notebook's consolidate_touchpoints helper does the tedious middle step. It groups each person's touches into sessions, orders them, starts a fresh journey after each purchase, folds repeats together and returns the paths table. A synthetic touch log run through it reproduces the exact credit above. It reads GA4's microsecond timestamps and BigQuery's UTC timestamps as they come.
What the helper cannot do is fetch the touches. No ad platform will give you the sequence across channels: Google Ads sees only Google touches, Meta only Meta. The joined-up journey lives in your analytics, which for most people means the free, event-level BigQuery export of Google Analytics 4. The notebook ships a ready query that turns that export into a touch log with one row per session, and it states the caveats plainly. A user_pseudo_id is one browser on one device (a login part-way through a visit no longer splits it). Conversions that GA4 models for consent never reach BigQuery. Paid social gets its own channel. Direct is mostly people coming back, and it is not a budget line. A model built on GA4 alone under-credits paid social, which leans on impressions. Assembling and stitching that data is the part my platform automates. Here I show how to do it by hand.
This part is technical, but you do not have to do it alone. Download the give-it-to-your-AI brief and hand it to ChatGPT, Claude or any capable assistant. It walks you through getting your data and running the analysis one step at a time, even if you have never written a line of code.
Common questions
Is this the same as Google Analytics' data-driven attribution?
Does a high Markov score mean the channel caused those sales?
How much data do I need?
Why does one of my channels’ range reach 0%?
Does the order of touches matter?
Can I use this to set budgets directly?
Before the next budget meeting
Export last quarter's journeys, run them through the notebook and find the channel that last-click puts near the bottom. Look at its range. If the whole range sits well above its last-click share, that channel opens journeys. If the range overlaps its neighbour's, you have found your next test. Either way, the next time somebody types cut? beside it, you have a range and a test plan to show the room instead of a hunch.
Running this on your own account
The worked example above is synthetic. The notebook runs on your data instead, and every number it gives you carries its range. If you want a hand with the method, or with the paid media underneath it, I take on freelance work, and I answer questions about anything published here.
Get in touchReferences
- [1] Anderl, E., Becker, I., von Wangenheim, F. and Schumann, J. H. (2016). Mapping the customer journey: lessons learned from graph-based online attribution modeling. International Journal of Research in Marketing, 33(3), 457 to 474. doi:10.1016/j.ijresmar.2016.03.001. Accessed 16 July 2026. https://ideas.repec.org/a/eee/ijrema/v33y2016i3p457-474.html
- [2] Grinstead, C. M. and Snell, J. L. Introduction to Probability, chapter 11, Absorbing Markov Chains. American Mathematical Society. https://math.dartmouth.edu/~prob/prob/prob.pdf
- [3] Archak, N., Mirrokni, V. S. and Muthukrishnan, S. (2010). Mining advertiser-specific user behavior using adfactors. Proceedings of the 19th International Conference on World Wide Web (WWW 2010), 31 to 40. https://doi.org/10.1145/1772690.1772695
- [4] Gordon, B. R., Zettelmeyer, F., Bhargava, N. and Chapsky, D. (2019). A comparison of approaches to advertising measurement: evidence from big field experiments at Facebook. Marketing Science, 38(2), 193 to 225. https://doi.org/10.1287/mksc.2018.1135
- [5] Ryherd, S. (2015). Attribution chains, not credit. MarTech. https://martech.org/attribution-chains-not-credit/
- [6] Altomare, D. and Loris, D. ChannelAttribution: Markov model for online multi-channel attribution. R package, CRAN. Accessed 16 July 2026. https://cran.r-project.org/package=ChannelAttribution
- [7] Windsor.ai. Shapley value vs Markov model in marketing attribution. Accessed 16 July 2026. https://windsor.ai/shapley-value-vs-markov-model-in-marketing-attribution/
- [8] Google. Data-driven attribution in Google Analytics 4. Analytics Help. Accessed 16 July 2026. https://support.google.com/analytics/answer/10596866
- [9] Google. MCF Data-Driven Attribution methodology. Analytics Help. Accessed 16 July 2026. https://support.google.com/analytics/answer/3191594
From Stochastic Strata. I build marketing measurement that shows its working: every number carries its range, and when the data cannot answer a question, I say so and tell you what would. The worked example here is synthetic. The method is the real thing, and you can run it on your own data with the notebook or spreadsheet above. Nothing in this post is financial advice.