Built a multi-touch attribution model across 2,847 customer journeys — revealed Instagram was being underpaid by 3.75× under last-click, and projected a 2.3× ROAS improvement through data-driven budget reallocation.
The D2C brand was spending across 4 channels but using last-click attribution by default — the same broken model most brands inherit from Google Analytics. The result: Google Ads was getting 71% of the credit and 60% of the budget, while Instagram — the channel that discovered 65% of customers — was getting 8% credit and 15% of the budget.
Three Python outputs from the analysis notebook — click to expand. The journey analysis chart is the most important: it shows first-touch vs last-touch distribution across channels, proving the gap between where customers come from and where they convert.
| Attribution Model | ROAS | Monthly Revenue |
|---|---|---|
| Last Click current | 1.8x | $36,000 |
| First Click | 2.4x | $48,000 |
| Linear | 3.2x | $64,000 |
| U-Shaped recommended | 4.1x | $82,000 |
Each model distributes credit differently across the customer journey. The key was finding which model matched the actual behavior shown in the journey data.
| Model | How Credit is Distributed | Problem |
|---|---|---|
| Last Click | 100% to final touchpoint | Ignores discovery entirely — systematically overfunds Google |
| First Click | 100% to first touchpoint | Ignores the close — would overfund Instagram instead |
| Linear | Equal split across all touches | Overvalues minor mid-funnel touches — blurs signal |
| U-Shaped (Position-Based) | 40% first touch, 40% last touch, 20% middle | ✓ Matches this brand's discovery → nurture → close journey |
def u_shaped_attribution(journey):
"""40% first, 40% last, 20% distributed across middle touches"""
n = len(journey)
if n == 1:
return {journey[0]: 1.0}
if n == 2:
return {journey[0]: 0.5, journey[1]: 0.5}
credits = {ch: 0.0 for ch in journey}
credits[journey[0]] += 0.40 # first touch
credits[journey[-1]] += 0.40 # last touch
middle_credit = 0.20 / (n - 2)
for ch in journey[1:-1]:
credits[ch] += middle_credit
return credits
# Apply to all 2,847 journeys
df['u_shaped_credits'] = df['touchpoint_sequence'].apply(u_shaped_attribution)
attribution_df = pd.DataFrame(df['u_shaped_credits'].tolist()).fillna(0).sum()
Under last-click, Google was receiving $12k/month (60%) while Instagram received $3k (15%). Aligning budget to U-shaped attribution credit flips that ratio — same $20k total, completely different ROI.
| Channel | Last-Click Budget | Proposed Budget | Change | Why |
|---|---|---|---|---|
| $3,000 (15%) | $6,000 (30%) | +100% ↑ | Discovers 65% of customers — was starved | |
| $4,000 (20%) | $5,000 (25%) | +25% ↑ | Keeps 52% of prospects engaged | |
| $1,000 (5%) | $2,000 (10%) | +100% ↑ | Highest ROI channel — was nearly defunded | |
| Google Ads | $12,000 (60%) | $7,000 (35%) | -42% ↓ | Still the closer — but was massively overfunded |
Analysis without action is just a report. Here's exactly what a brand should do the morning after getting this finding.
I build attribution frameworks that show you where your marketing money actually works — not just where your last sale happened to click.
Let's Talk →