Conversion-Rate Bridge Methodology
From Session Lift to Estimated Conversions
SpotLift's core engine measures incremental session lift — the additional web traffic (GA4 direct/organic sessions) attributable to each TV spot. But sessions are an intermediate metric: marketers need to know how many conversions (purchases, signups, leads) the spot drove.
The conversion-rate bridge closes this gap by estimating an hourly conversion rate (CR) from the same GA4 telemetry and multiplying it by the attributed session lift:
where
How It Works
Step 1: Hourly Conversion Rate Aggregation
For each (day, hour) pair in the telemetry, SpotLift computes:
This requires the telemetry DataFrame to contain two channel sources:
| Channel Source | What It Contains |
|---|---|
ga4_direct_organic_sessions | Minute-level session counts (direct + organic traffic) |
ga4_conversions | Minute-level conversion / key-event counts (purchases, signups, etc.) |
If ga4_conversions is absent from the telemetry, the bridge silently skips — conversion_rate and key_event_response remain None. No error is raised, but no conversion estimate is produced.
Step 2: Session-Volume-Weighted CR per Spot
For a spot airing at timestamp
Why session-volume weighting? A flat average of hourly CRs would weight a 3 AM hour (few sessions, high noise) equally with an 8 PM hour (many sessions, stable CR). Session-volume weighting pulls the estimate toward the CR of hours that actually carry traffic, reducing noise from low-volume periods.
Why ±1 hour? TV response is immediate (within 5–15 minutes), but the conversion may happen slightly later (browsing → checkout takes 5–30 min). The ±1 hour window captures the CR of the surrounding traffic context without averaging across unrelated dayparts (e.g. daytime vs. prime time). The continuous window also seamlessly bridges across calendar-day boundaries at midnight (e.g. for spots airing at 23:45 or 00:15).
Step 3: Key Event Response
The estimated conversions for each spot:
This is a linear scaling — it assumes the incremental sessions driven by TV convert at the same rate as the organic sessions in the surrounding hour. This is a reasonable baseline assumption: TV-driven visitors are typically brand-aware (they saw the ad), so their conversion behavior is similar to direct/organic visitors who also arrive with brand intent.
Usage
Automatic (Default)
The bridge is enabled by default when ga4_conversions is present:
import spotlift as sl
airings = sl.load_airings("schaltplan.csv", tz="Europe/Berlin")
telemetry = sl.load_telemetry("ga4_export.parquet")
# apply_cr_model=True by default — bridge runs automatically
# if ga4_conversions is in the telemetry
results = sl.analyze_spots(
airings=airings,
web_telemetry=telemetry,
window_minutes=8,
)
# results now contain: conversion_rate, key_event_response
print(results[["spot_id", "incremental_lift", "conversion_rate", "key_event_response"]].head())Custom Conversion Source Label
If your conversions use a different channel_source label:
results = sl.analyze_spots(
airings=airings,
web_telemetry=telemetry,
conversions_source="ga4_purchases", # custom label
)Disabled
To skip the bridge entirely (session lift only, no conversion estimate):
results = sl.analyze_spots(
airings=airings,
web_telemetry=telemetry,
apply_cr_model=False,
)Standalone Functions
The bridge components are also available as standalone functions for custom workflows:
from spotlift.core.conversion import (
compute_hourly_conversion_rates,
estimate_conversion_rate_for_spot,
apply_conversion_model,
)
# Compute hourly CR table
hourly_cr = compute_hourly_conversion_rates(
telemetry,
sessions_source="ga4_direct_organic_sessions",
conversions_source="ga4_conversions",
)
# Estimate CR for a specific spot timestamp
cr = estimate_conversion_rate_for_spot(
spot_timestamp=airings.iloc[0]["timestamp"],
hourly_cr=hourly_cr,
window_hours=1,
)
# Or apply to an existing results DataFrame
annotated = apply_conversion_model(results, telemetry)Configurable Baseline Models
SpotLift defaults to a 21-day diurnal baseline (compute_diurnal_baseline) — a same-day-of-week median with weekday/weekend pooling and a 5-minute rolling median smoother. This works well for campaigns with ≥3 weeks of clean historical telemetry.
For campaigns with <7 days of history or markets without a clean diurnal pattern, use simple_mean_baseline:
from spotlift import simple_mean_baseline
results = sl.analyze_spots(
airings=airings,
web_telemetry=telemetry,
baseline_fn=simple_mean_baseline, # trailing 120-min rolling mean
)Custom Baseline Functions
Any callable conforming to the BaselineFn protocol can be passed:
from spotlift.core.baseline import BaselineFn
def my_baseline(
telemetry_series: pd.Series,
spot_timestamps: list[datetime],
**kwargs,
) -> tuple[pd.Series, dict[datetime, float]]:
# Your baseline logic here
# Must return (raw_baseline_series, alphas_dict)
...
results = sl.analyze_spots(
airings=airings,
web_telemetry=telemetry,
baseline_fn=my_baseline,
)The protocol requires:
- Input:
telemetry_series(minute-indexed pd.Series),spot_timestamps(list of datetime), plus**kwargs(SpotLift passeswindow_minutes,pre_window_minutes,baseline_days). - Output:
(raw_baseline_series, alphas_dict)— a Series of expected organic traffic per minute, and a dict of per-spot multiplicative calibration factors.
Limitations and Caveats
Linear assumption: The bridge assumes TV-driven sessions convert at the same rate as organic sessions in the same hour. If TV-driven visitors convert at a systematically different rate (e.g. higher brand intent), the estimate will be biased. For non-linear effects, use SpotLift Pro's EM deconvolution.
Continuous neighborhood across midnight: The CR is computed in a continuous ±1-hour window around the spot timestamp, bridging smoothly across midnight (e.g. 23:00 to 01:00). Delayed conversion paths that span multiple days (e.g. spot on Monday, conversion on Wednesday) are not captured by this immediate bridge (use SpotLift Pro's 36-hour distributed lag solver for delayed response).
Requires both channels: If
ga4_conversionsis missing from the telemetry, no estimate is produced. Ensure your GA4 export includes conversion events as a separatechannel_source.Hourly granularity: The CR is computed at hour granularity. Sub-hourly CR variation (e.g. a 15-minute checkout spike) is smoothed by aggregation.