A self-contained PySpark workflow that demonstrates cross-screen panel measurement engineering on synthetic data: SQL / Python / Databricks / Spark.
The problem is industry-generic. Cross-screen ad measurement means joining disparate sources that never agreed on a schema: ad exposure files from a publisher, conversion data from a measurement partner, and panel weights from a household measurement panel. The join key is a household ID, and every source spells it slightly differently. This notebook runs that task end to end and keeps the failure modes visible instead of editing them out.
The workflow decomposes into the operations that define technical data analysis regardless of tool or buzzword:
| # | Operation | What it shows | Measurement task it maps to |
|---|---|---|---|
| 1 | Retrieve | Read three disparate CSVs into Spark | extracting data from source systems |
| 2 | Link (naive) | Quantify rows silently lost to key drift | the trap that separates real join fluency |
| 3 | Shape | Normalize keys, dedupe, handle nulls | the unglamorous 70-80% of the work |
| 4 | Link (correct) | Panel-as-spine join, no double-counting | custom matches and joins across disparate sources |
| 5 | Reduce | Weighted conversion rate by segment + window rank; saturation curve | conversion rate by audience segment |
| 6 | Infer | Weighted logistic regression, coefficients as drivers | drivers analysis via statistical modeling |
| 7 | Translate | Two-panel chart for a non-technical reader | making results accessible beyond the analyst |
The Config block at the top makes the whole thing a reusable template: re-point the data path, swap the campaign key, or extend the model formula, and the same operations rerun unchanged.
generate_sample_data.py builds three messy sources for 4,000 panel households,
seeded (SEED=42) so every run reproduces identically:
ad_exposure.csv- publisher-side impression log. Messy on purpose: thehousehold_idkey drifts in case and whitespace, ~5% null devices, ~3% double-logged rows.conversions.csv- measurement-partner purchase events.panel.csv- the canonical household universe with clean keys, audience segments (~4% missing), viewing profiles, and panel weights.
A real causal signal is baked in so the drivers analysis has something true to recover: conversion rises with exposure frequency but saturates past ~6 exposures; streaming-heavy viewers convert better than linear-heavy; high-HHI cordcutters are the strongest segment. The logistic regression recovers this ordering, which is how you know the pipeline works rather than just runs.
- Create a free account at community.cloud.databricks.com.
- Run
python generate_sample_data.pylocally once to produce the three CSVs (or upload them from a prior run). - Upload the CSVs to DBFS, e.g.
/FileStore/measurement/. - Import
measurement_drivers.ipynb, setDATA = "/FileStore/measurement"in the Config cell, attach to the free cluster, Run All.
Running it there confirms the workflow on Databricks itself, not just in a local Spark session.
pip install pyspark matplotlib pandas # PySpark needs Java 11+ installed
jupyter notebook measurement_drivers.ipynbKeep DATA = "data". The first cell auto-generates the sample data if it's
missing, so a fresh clone runs with no separate step.
naive inner join matched 2256 of 3232 exposure rows
-> 976 rows silently lost to key drift
exposure dedup: 3232 -> 3150 rows
master households: 4000 | converted: 572
rank segment households avg_freq weighted_cvr
1 Cordcutter_HHI_High 781 3.04 0.1915
...
6 Linear_Loyalist 739 3.03 0.1096
AUC: 0.676
Conversion drivers (by |coefficient|):
+0.6682 segment_Cordcutter_HHI_High
+0.3465 viewing_profile_Streaming-heavy
+0.1717 exposure_frequency
| File | Purpose |
|---|---|
measurement_drivers.ipynb |
The workflow. Self-documenting (markdown Why + code How per step). |
generate_sample_data.py |
Deterministic messy-data generator. |
data/ |
Generated CSVs + output chart (git-ignored; regenerable). |
requirements.txt |
pyspark, pandas, matplotlib. |
Built June 2026. Synthetic, deterministic data only; a fresh clone reproduces every figure above.