Where we are: from fixed rules to learned credit
In B2 you coded the six heuristics, and in B3 you watched them fail - they assert a rule (last touch wins, recent wins) instead of asking the data what actually moved the needle. This session is the leap. Markov attribution treats every customer journey as a walk through a chain of states and scores each channel by a question no heuristic can answer: if this channel vanished, how much conversion would we lose? That is the removal effect, and it is the first genuinely data-driven credit you will build by hand.
Journeys as a first-order Markov chain 7 min live
A Markov chain is a set of states and the probabilities of hopping between them, where the next hop depends only on where you are now - not how you got there. For attribution the states are: a synthetic Start, one state per channel, an absorbing Conversion, and an absorbing Null (the journey ended without buying). We estimate every transition probability by counting observed hops in Lumen's path data. That is the whole model.
LiveThe states, and where the probabilities come from3 min▶
We will teach the whole method on a tiny, hand-checkable slice of Lumen so you can verify every number, then scale it up in the demos. The slice has just two channels and four observed journeys:
| Observed path | Count | Outcome |
|---|---|---|
| Start → A → B | 2 | Conversion |
| Start → B → A | 1 | Conversion |
| Start → A | 1 | Null (no sale) |
Four journeys start; three convert. So the base conversion probability is 3 / 4 = 0.75. Now read the transitions straight off the counts:
- From Start: A is entered 3 times (2 + 1), B once. So Start→A = 0.75, Start→B = 0.25.
- From A: A appears 4 times total - it goes to B twice, to Conversion once, to Null once. So A→B = 0.50, A→Conv = 0.25, A→Null = 0.25.
- From B: B appears 3 times - to Conversion twice, to A once. So B→Conv = 0.667, B→A = 0.333.
Every row sums to 1, because probabilities out of a state have to go somewhere. That is your transition matrix, estimated purely by counting.
Self-studyWhy the sequence matters, not just the frequency3 min read▶
The heuristics only ever saw which channels appeared and where in a single path. Markov sees the full graph of how channels hand off to each other across every journey at once. A channel that always appears right before conversion, and a channel that appears early but reliably passes people onward to a closer, get scored differently - because the chain captures the sequence of transitions, not just a tally of touches.
On Lumen's real data, display and CTV rarely take the last click, so last-touch scores them near zero. But the Markov chain shows that removing them collapses the transitions that feed paid search - the closers dry up because nobody arrives to close. Sequence-awareness is exactly what surfaces those upstream engines.
The removal effect, worked by hand 6 min live
Here is the idea that makes Markov attribution learned rather than asserted. To score a channel, we remove it - route every one of its transitions to Null, as if that channel never existed - recompute the probability of conversion from Start, and measure the fractional drop. A channel whose removal barely dents conversion earned little; a channel whose removal collapses conversion earned a lot.
LiveSolve the absorbing chain, remove each channel4 min▶
To get P(conversion | Start) we solve the chain. Let f(X) be the probability of eventually reaching Conversion starting from state X, with f(Conv) = 1 and f(Null) = 0. Then each channel state is a weighted average of where it hops:
- f(A) = 0.50·f(B) + 0.25·1 + 0.25·0
- f(B) = 0.667·1 + 0.333·f(A)
Substitute the second into the first and solve: f(A) = 0.70, f(B) = 0.90. Then f(Start) = 0.75·f(A) + 0.25·f(B) = 0.75·0.70 + 0.25·0.90 = 0.75 - which reconciles exactly with the base rate we counted. Good sign: the chain agrees with reality.
Now remove each channel (send all its visits to Null) and re-solve:
- Remove A: f(A) = 0, so f(B) = 0.667, and f(Start) = 0.25·0.667 = 0.167. RE(A) = 1 − 0.167/0.75 = 0.778.
- Remove B: f(B) = 0, so f(A) = 0.25, and f(Start) = 0.75·0.25 = 0.1875. RE(B) = 1 − 0.1875/0.75 = 0.75.
Why you MUST normalize, and higher-order chains 5 min live
Look at the two removal effects: RE(A) = 0.778 and RE(B) = 0.75. They sum to 1.528, not 1. This is not a bug - it is the single most misunderstood point in Markov attribution, and getting it wrong will make your credit numbers meaningless.
LiveRemoval effects overlap - that is why they don't sum to 13 min▶
Journeys share channels. When you remove A, some of the conversions you lose were paths that also used B - and when you remove B, you lose some of those same shared paths again. The effects overlap, so the raw removal effects double-count and overshoot 1. To turn them into a credit split that actually sums to 100% of conversions, you normalize each channel by the total:
- credit(A) = RE(A) / ΣRE = 0.778 / 1.528 = 50.9%
- credit(B) = RE(B) / ΣRE = 0.75 / 1.528 = 49.1%
A team once reported "our channels have 320% removal effect" as if the model were broken. It was not - nine channels sharing long journeys will happily overlap to a raw sum well above 1. They just skipped the normalization step. Normalize, and 320% becomes a clean 100% credit split you can multiply against revenue.
Self-studyFirst-order vs higher-order (k-order) Markov2 min read▶
We built a first-order chain: the next hop depends only on the current channel. That is the memoryless default and it is remarkably robust. But it cannot tell "email after paid social" apart from "email after display" - both collapse to "in email". A higher-order (k-order) chain fixes that by making each state the last k channels, so it remembers short sequences.
- The cost: the state space explodes. Order-2 over 9 channels is up to 81 states; order-3 is up to 729 - and each needs enough observed paths to estimate its transitions, or the counts get too sparse to trust.
- The tool: the ChannelAttribution package exposes
order=1,2,3,...so you can raise it and watch whether credit shifts enough to justify the extra data hunger. Usually order 1 is plenty; go higher only when short sequences are clearly load-bearing.
Build the transition matrix from Lumen paths ★ 8 min · everyone
Straight from the counting logic in Part 1. We take the sessionized Lumen paths (the journey table you built in B1), count every hop, and normalize each row into probabilities. Same code runs on our 4-path toy slice and on Lumen's millions of paths.
Conversion probability via the absorbing chain ★ 7 min · everyone
Now we solve for P(conversion | Start) the clean linear-algebra way. Split the matrix into transient states (Start and the channels) and absorbing states (Conv, Null). The fundamental matrix N = (I − Q)⁻¹ gives expected visits; N·R gives the probability of ending in each absorbing state.
The linear solve reconciling to the observed 0.75 base rate is your green light. On real Lumen data you would confirm the model's implied conversion probability lands within a hair of the actual conversion rate before trusting any channel score - if it drifts, your paths, your absorbing states, or your Start handling are off.
Removal effect per channel + the library one-liner ★ 8 min · everyone
The payoff. Wrap the solve in a function that can route a chosen channel to Null, loop over the channels, compute each removal effect, then normalize into spendable credit. Finally, the same result from ChannelAttribution once your data outgrows a from-scratch loop.
This week ◐ 40 min total
- Run the toy slice end to end and confirm you reproduce credit paid_social 50.9% / email 49.1%. If your normalization is off, you will see the raw 0.778 / 0.75 instead - which do not sum to 1.
- Scale to all 9 Lumen channels using the full sessionized path table from B1. Print the raw removal effects (watch them sum well above 1) and the normalized credit (sums to 1).
- Cross-check against ChannelAttribution with order=1 and confirm your from-scratch numbers match the library within rounding.
- Optional: bump the library to order=2 and note which channels gain or lose credit - then ask whether your path volume is really enough to trust the bigger state space.
Three questions before you go 🎯 ◐ 90 seconds
1 · The removal effect of a channel is defined as...
You remove the channel by sending all its transitions to Null, recompute conversion probability from Start, and measure how far it falls relative to the base. That fractional drop is the removal effect.
2 · Your channels' removal effects sum to 1.53, not 1. What do you do?
Shared channels mean removal effects overlap and double-count, so their raw sum runs above 1. That is expected. Divide each by the total to get a credit split that sums to 100%.
3 · What does a Markov chain capture that the heuristics cannot?
Heuristics score one path with a fixed rule. Markov learns the whole graph of channel-to-channel transitions across every journey, so it credits channels that feed the closers, not just the closers themselves.
What this session covers
This session owns Coverage Gap #1: the actual math of Markov attribution built from scratch in Python. No existing course walks the transition matrix, absorbing-chain solve and removal-effect normalization end to end - most hand you a library call and move on.