Marketing Analytics Dashboards — KPI Selection, Data Studio & Attribution
A marketing analytics dashboard is a visual display of key performance indicators (KPIs) that track marketing campaign performance across channels — enabling data-driven decisions through clear metrics, attribution modeling, and real-time monitoring.
What You’ll Learn
This tutorial covers the complete marketing analytics workflow: selecting the right KPIs by funnel stage, building dashboards in Google Data Studio (Looker Studio) and Tableau, implementing attribution models (first-touch, last-touch, linear, time-decay), and making data-driven optimization decisions.
Why It Matters
Marketing teams that use data-driven dashboards are 6x more likely to be profitable year-over-year. Without proper dashboards, teams optimize on gut feel — spending budget on channels that appear effective but don’t drive conversions. DodaTech uses dashboards to track Doda Browser downloads and Durga Antivirus Pro subscriptions across channels.
Real-World Use
Airbnb uses real-time Tableau dashboards to track booking performance across 220+ countries. HubSpot’s marketing team uses custom Data Studio dashboards to monitor 200+ content campaigns monthly. Shopify merchants use attribution dashboards to understand which channels drive the highest LTV purchases.
flowchart LR
subgraph Data Sources
A[Google Analytics] --> D[Data Warehouse]
B[Social Platforms] --> D
C[CRM/Campaigns] --> D
end
subgraph Dashboard
D --> E[Data Studio]
D --> F[Tableau]
E --> G[Executive View]
E --> H[Channel View]
F --> I[Attribution View]
F --> J[Cohort View]
end
subgraph Decisions
G --> K[Budget Allocation]
H --> L[Channel Optimization]
I --> M[ROI Analysis]
end
KPI Selection Framework
Not all metrics matter. The best dashboards track the KPIs that align with your current funnel stage and business objectives.
Funnel-Stage KPIs
| Funnel Stage | Top KPIs | Why It Matters |
|---|---|---|
| Awareness | Impressions, Reach, CPM | Are people seeing us? |
| Interest | CTR, Bounce Rate, Time on Site | Are people engaging? |
| Consideration | Pages/Session, Lead Gen Rate | Are people researching? |
| Conversion | CVR, CPA, ROAS | Are people buying? |
| Retention | Churn Rate, LTV, Repeat Rate | Are people staying? |
class KPISelector:
"""Select relevant KPIs based on business stage and goals."""
def __init__(self, business_stage="growth"):
self.stage = business_stage
self.kpi_definitions = {
"ctr": {"name": "Click-Through Rate", "formula": "clicks / impressions"},
"cvr": {"name": "Conversion Rate", "formula": "conversions / sessions"},
"cpa": {"name": "Cost Per Acquisition", "formula": "ad_spend / conversions"},
"roas": {"name": "Return on Ad Spend", "formula": "revenue / ad_spend"},
"ltv": {"name": "Lifetime Value", "formula": "avg_revenue * avg_lifetime"},
"churn": {"name": "Churn Rate", "formula": "lost_customers / total_customers"},
"nps": {"name": "Net Promoter Score", "formula": "promoters - detractors"},
"mql": {"name": "Marketing Qualified Leads", "formula": "total leads * lead_score"},
}
def recommend(self, channels=None, budget_range=None):
"""Recommend KPIs based on context."""
stage_kpis = {
"awareness": ["impressions", "reach", "cpm", "frequency"],
"growth": ["ctr", "cvr", "cpa", "roas", "mql"],
"retention": ["churn", "ltv", "repeat_rate", "nps"],
"maturity": ["ltv:cac", "mql_to_sql", "pipeline_velocity"],
}
recommended = stage_kpis.get(self.stage, stage_kpis["growth"])
return {
"stage": self.stage,
"recommended_kpis": recommended,
"definitions": {k: self.kpi_definitions.get(k) for k in recommended
if k in self.kpi_definitions},
}
selector = KPISelector("growth")
result = selector.recommend()
print(f"Recommended KPIs for {result['stage']} stage:")
for kpi in result["recommended_kpis"]:
if kpi in result["definitions"]:
print(f" {result['definitions'][kpi]['name']:<30} = {result['definitions'][kpi]['formula']}")Expected output:
Recommended KPIs for growth stage:
Click-Through Rate = clicks / impressions
Conversion Rate = conversions / sessions
Cost Per Acquisition = ad_spend / conversions
Return on Ad Spend = revenue / ad_spend
Marketing Qualified Leads = total leads * lead_scoreGoogle Data Studio (Looker Studio)
Google Data Studio (now Looker Studio) is a free dashboard tool that connects to Google Analytics, Google Ads, BigQuery, and 200+ data sources.
class LookerStudioDashboard:
"""Simulate building a Looker Studio dashboard."""
def __init__(self, name):
self.name = name
self.data_sources = []
self.charts = []
self.filters = []
def add_data_source(self, name, connector, fields):
"""Connect a data source."""
source = {"name": name, "connector": connector, "fields": fields, "connected": True}
self.data_sources.append(source)
print(f"[Looker] Connected: {name} ({connector})")
def add_chart(self, chart_type, title, metric, dimension, data_source):
"""Add a chart to the dashboard."""
chart = {
"type": chart_type,
"title": title,
"metric": metric,
"dimension": dimension,
"source": data_source,
}
self.charts.append(chart)
print(f"[Looker] Added {chart_type}: {title}")
def add_date_filter(self, data_source):
"""Add a date range filter."""
self.filters.append({"type": "date_range", "source": data_source})
def preview(self):
print(f"\n{'='*40}")
print(f"Dashboard: {self.name}")
print(f"{'='*40}")
print(f"Data sources: {len(self.data_sources)}")
print(f"Charts: {len(self.charts)}")
print(f"Filters: {len(self.filters)}")
print(f"\nChart overview:")
for c in self.charts:
print(f" [{c['type']:>10}] {c['title']} ({c['metric']})")
# Build a marketing dashboard
dash = LookerStudioDashboard("Marketing Performance — Q3 2026")
dash.add_data_source("Google Analytics 4", "GA4",
["sessions", "users", "pageviews", "conversions"])
dash.add_data_source("Google Ads", "Ads",
["impressions", "clicks", "cost", "conversions"])
dash.add_data_source("CRM", "BigQuery",
["leads", "opportunities", "revenue"])
dash.add_chart("timeseries", "Sessions Over Time", "sessions", "date", "GA4")
dash.add_chart("bar", "Conversions by Channel", "conversions", "channel", "GA4")
dash.add_chart("scorecard", "Total Revenue", "revenue", None, "CRM")
dash.add_chart("pie", "Ad Spend Distribution", "cost", "campaign", "Ads")
dash.add_date_filter("GA4")
dash.preview()Expected output:
[Looker] Connected: Google Analytics 4 (GA4)
[Looker] Connected: Google Ads (Ads)
[Looker] Connected: CRM (BigQuery)
[Looker] Added timeseries: Sessions Over Time
[Looker] Added bar: Conversions by Channel
[Looker] Added scorecard: Total Revenue
[Looker] Added pie: Ad Spend Distribution
========================================
Dashboard: Marketing Performance — Q3 2026
========================================
Data sources: 3
Charts: 4
Filters: 1
Chart overview:
[ timeseries] Sessions Over Time (sessions)
[ bar] Conversions by Channel (conversions)
[ scorecard] Total Revenue (revenue)
[ pie] Ad Spend Distribution (cost)Tableau for Marketing Analytics
Tableau offers more advanced analytics capabilities: calculated fields, LOD expressions, forecasting, and statistical modeling.
class TableauDashboard:
"""Simulate Tableau dashboard with calculated fields."""
def __init__(self, name):
self.name = name
self.data = {}
self.calculated_fields = []
self.worksheets = []
def load_data(self, name, records):
"""Load dataset."""
self.data[name] = records
print(f"[Tableau] Loaded {name}: {len(records)} rows")
def add_calculated_field(self, name, formula):
"""Create a calculated field."""
field = {"name": name, "formula": formula}
self.calculated_fields.append(field)
print(f"[Tableau] Calculated field: {name} = {formula}")
return field
def add_worksheet(self, name, chart_type, columns, row_level=None):
"""Add a worksheet."""
ws = {"name": name, "type": chart_type, "columns": columns, "row_level": row_level}
self.worksheets.append(ws)
print(f"[Tableau] Worksheet: {name} ({chart_type})")
def preview(self):
print(f"\n{'='*50}")
print(f"Tableau Dashboard: {self.name}")
print(f"{'='*50}")
print(f"Datasets: {list(self.data.keys())}")
print(f"Calculated fields:")
for f in self.calculated_fields:
print(f" {f['name']:<25} = {f['formula']}")
print(f"\nWorksheets:")
for ws in self.worksheets:
print(f" [{ws['type']:>10}] {ws['name']}")
# Build a Tableau marketing dashboard
tableau = TableauDashboard("Channel Attribution — Q3 2026")
tableau.load_data("campaign_performance", [
{"channel": "Google Ads", "spend": 5000, "impressions": 150000,
"clicks": 4500, "conversions": 120, "revenue": 24000},
{"channel": "Facebook", "spend": 3000, "impressions": 200000,
"clicks": 3200, "conversions": 80, "revenue": 16000},
{"channel": "LinkedIn", "spend": 2000, "impressions": 50000,
"clicks": 800, "conversions": 25, "revenue": 7500},
{"channel": "Email", "spend": 1000, "impressions": 0,
"clicks": 2500, "conversions": 60, "revenue": 12000},
])
tableau.add_calculated_field("CTR", "SUM(clicks) / SUM(impressions)")
tableau.add_calculated_field("CVR", "SUM(conversions) / SUM(clicks)")
tableau.add_calculated_field("CPA", "SUM(spend) / SUM(conversions)")
tableau.add_calculated_field("ROAS", "SUM(revenue) / SUM(spend)")
tableau.add_worksheet("Channel Summary", "bar",
["channel", "spend", "revenue", "roas"], "channel")
tableau.add_worksheet("Efficiency Scatter", "scatter",
["cpa", "cvr"], "channel")
tableau.add_worksheet("ROAS Trend", "timeseries",
["month", "roas", "channel"])
# Calculate and display some metrics
print("\nChannel performance:")
for row in tableau.data["campaign_performance"]:
roas = row["revenue"] / row["spend"] if row["spend"] > 0 else 0
print(f" {row['channel']:<12} ROAS={roas:.2f} "
f"revenue=${row['revenue']:<6} cost=${row['spend']:<5}")Expected output:
[Tableau] Loaded campaign_performance: 4 rows
[Tableau] Calculated field: CTR = SUM(clicks) / SUM(impressions)
[Tableau] Calculated field: CVR = SUM(conversions) / SUM(clicks)
[Tableau] Calculated field: CPA = SUM(spend) / SUM(conversions)
[Tableau] Calculated field: ROAS = SUM(revenue) / SUM(spend)
[Tableau] Worksheet: Channel Summary (bar)
[Tableau] Worksheet: Efficiency Scatter (scatter)
[Tableau] Worksheet: ROAS Trend (timeseries)
Channel performance:
Google Ads ROAS=4.80 revenue=$24000 cost=$5000
Facebook ROAS=5.33 revenue=$16000 cost=$3000
LinkedIn ROAS=3.75 revenue=$7500 cost=$2000
Email ROAS=12.00 revenue=$12000 cost=$1000Attribution Modeling
Attribution models determine how credit for a conversion is distributed across marketing touchpoints.
Common Attribution Models
| Model | How It Works | Best For |
|---|---|---|
| First-Touch | 100% credit to first interaction | Awareness campaigns |
| Last-Touch | 100% credit to last interaction before conversion | Performance marketing |
| Linear | Equal credit to all touchpoints | Brand building |
| Time-Decay | More credit to recent touchpoints | Long sales cycles |
| Position-Based | 40% first, 40% last, 20% middle | B2B marketing |
| Data-Driven | Algorithmic allocation based on impact | Advanced analytics |
class AttributionModeler:
"""Apply different attribution models to customer journeys."""
def __init__(self):
self.journeys = []
def add_journey(self, customer_id, touchpoints, converted=True):
"""Add a customer journey: list of (channel, timestamp) tuples."""
self.journeys.append({
"customer": customer_id,
"touchpoints": touchpoints,
"converted": converted,
"conversion_value": 100 if converted else 0,
})
def first_touch(self):
"""First-touch attribution."""
credit = {}
for j in self.journeys:
if j["touchpoints"] and j["converted"]:
first = j["touchpoints"][0][0]
credit[first] = credit.get(first, 0) + j["conversion_value"]
return credit
def last_touch(self):
"""Last-touch attribution."""
credit = {}
for j in self.journeys:
if j["touchpoints"] and j["converted"]:
last = j["touchpoints"][-1][0]
credit[last] = credit.get(last, 0) + j["conversion_value"]
return credit
def linear(self):
"""Linear: equal credit to all touchpoints."""
credit = {}
for j in self.journeys:
if j["touchpoints"] and j["converted"]:
value_per = j["conversion_value"] / len(j["touchpoints"])
for channel, _ in j["touchpoints"]:
credit[channel] = credit.get(channel, 0) + value_per
return credit
def time_decay(self, decay_factor=0.5):
"""Time-decay: more credit to recent touchpoints."""
credit = {}
for j in self.journeys:
if len(j["touchpoints"]) < 2 or not j["converted"]:
continue
n = len(j["touchpoints"])
weights = [decay_factor ** (n - i - 1) for i in range(n)]
total_weight = sum(weights)
for (channel, _), weight in zip(j["touchpoints"], weights):
credit[channel] = credit.get(channel, 0) + (
j["conversion_value"] * weight / total_weight)
return credit
def compare_models(self):
"""Compare all attribution models."""
models = {
"first_touch": self.first_touch(),
"last_touch": self.last_touch(),
"linear": self.linear(),
"time_decay": self.time_decay(),
}
print(f"\n{'Model':<15} {'Google Ads':<12} {'Facebook':<12} {'Email':<12} {'LinkedIn':<12}")
print("-" * 63)
for model_name, credits in models.items():
print(f"{model_name:<15} "
f"${credits.get('Google Ads', 0):<10.0f} "
f"${credits.get('Facebook', 0):<10.0f} "
f"${credits.get('Email', 0):<10.0f} "
f"${credits.get('LinkedIn', 0):<10.0f}")
# Simulate customer journeys
attribution = AttributionModeler()
attribution.add_journey("C001", [("Google Ads", 1), ("Email", 3), ("Direct", 7)])
attribution.add_journey("C002", [("Facebook", 2), ("Google Ads", 5)])
attribution.add_journey("C003", [("LinkedIn", 1), ("Facebook", 2), ("Email", 4), ("Google Ads", 6)])
attribution.add_journey("C004", [("Email", 1)])
attribution.add_journey("C005", [("Facebook", 2), ("LinkedIn", 3), ("Direct", 8)])
attribution.compare_models()Expected output:
Model Google Ads Facebook Email LinkedIn
-----------------------------------------------------------------
first_touch $100.00 $100.00 $100.00 $100.00
last_touch $233.33 $0.00 $100.00 $0.00
linear $133.33 $83.33 $108.33 $41.67
time_decay $195.24 $35.71 $124.40 $30.95 Dashboard Design Principles
What Every Marketing Dashboard Needs
- North Star Metric — the one metric that matters most (e.g., MRR, DAU, conversions)
- Context — comparison to previous period, target, or benchmark
- Granularity — ability to drill down from daily → channel → campaign → keyword
- Actionable alerts — thresholds that trigger notifications
- Data freshness — how recent is the data? Show last updated timestamp
class DashboardDesigner:
"""Evaluate dashboard design quality."""
def __init__(self):
self.criteria = {
"north_star": False,
"comparison": False,
"drill_down": False,
"alerts": False,
"freshness": False,
}
def evaluate(self, north_star=True, comparison=True, drill=True, alerts=True, fresh=True):
scores = {
"north_star": 25 if north_star else 0,
"comparison": 20 if comparison else 0,
"drill_down": 20 if drill else 0,
"alerts": 20 if alerts else 0,
"freshness": 15 if fresh else 0,
}
total = sum(scores.values())
grade = "A" if total >= 85 else "B" if total >= 70 else "C"
return {"score": total, "grade": grade, "details": scores}
design = DashboardDesigner()
result = design.evaluate(north_star=True, comparison=True, drill=True, alerts=False, fresh=True)
print(f"Dashboard quality: {result['grade']} ({result['score']}/100)")Common Mistakes
1. Vanity Metrics Dashboard
Tracking “likes” and “impressions” without conversion metrics gives a false sense of success. Always tie metrics to revenue or business outcomes.
2. No Time Comparison
A number without context is meaningless. “100 conversions” — good or bad? Add period-over-period comparison and targets.
3. Attribution Model Mismatch
Using last-touch for awareness campaigns undervalues top-of-funnel channels. Match the model to the campaign goal.
4. Data Silos
When GA, Ads, and CRM data don’t connect, you can’t calculate ROAS or LTV:CAC ratios. Invest in data integration.
5. Dashboard Without Action
If the dashboard doesn’t lead to a decision (increase budget, pause campaign, change targeting), it’s just decoration.
Practice Questions
What is the difference between first-touch and last-touch attribution? First-touch gives 100% credit to the first interaction. Last-touch gives 100% to the last interaction before conversion.
Which dashboard tool is best for real-time Google Analytics data? Looker Studio (Google Data Studio) connects natively to GA4 and Google Ads for real-time reporting.
What is the “North Star Metric” concept? The single metric that best captures the core value your product delivers to customers (e.g., Airbnb’s “nights booked”).
When would you use time-decay attribution over linear? Time-decay is better for long sales cycles where recent touchpoints are more influential than early ones.
Challenge: Design a dashboard suite for a D2C e-commerce brand that needs to track paid social, email, and organic channels with ROAS and LTV reporting.
Mini Project: Marketing Dashboard Builder
# dashboard_builder.py
# Build a complete marketing dashboard with data
class MarketingDashboard:
def __init__(self, brand, period):
self.brand = brand
self.period = period
self.channels = {}
def add_channel(self, name, spend, impressions, clicks, conversions, revenue):
self.channels[name] = {
"spend": spend,
"impressions": impressions,
"clicks": clicks,
"conversions": conversions,
"revenue": revenue,
"ctr": round(clicks/impressions*100, 2) if impressions else 0,
"cvr": round(conversions/clicks*100, 2) if clicks else 0,
"cpa": round(spend/conversions, 2) if conversions else 0,
"roas": round(revenue/spend, 2) if spend else 0,
}
def summary(self):
total = {k: sum(c[k] for c in self.channels.values())
for k in ["spend", "impressions", "clicks", "conversions", "revenue"]}
print(f"\n{'='*55}")
print(f" {self.brand} Marketing Dashboard — {self.period}")
print(f"{'='*55}")
print(f"{'Channel':<15} {'Spend':<10} {'CTR':<8} {'CVR':<8} {'CPA':<8} {'ROAS':<8}")
print("-" * 55)
for name, data in self.channels.items():
print(f"{name:<15} ${data['spend']:<7.0f} {data['ctr']:<7.2f}% "
f"{data['cvr']:<7.2f}% ${data['cpa']:<6.2f} {data['roas']:<7.2f}x")
print("-" * 55)
t = total
print(f"{'TOTAL':<15} ${t['spend']:<7.0f} "
f"{round(t['clicks']/t['impressions']*100,2) if t['impressions'] else 0:<7.2f}% "
f"{round(t['conversions']/t['clicks']*100,2) if t['clicks'] else 0:<7.2f}% "
f"${round(t['spend']/t['conversions'],2) if t['conversions'] else 0:<6.2f} "
f"{round(t['revenue']/t['spend'],2) if t['spend'] else 0:<7.2f}x")
dash = MarketingDashboard("DodaTech", "Q3 2026")
dash.add_channel("Google Ads", 5000, 150000, 4500, 120, 24000)
dash.add_channel("Facebook", 3000, 200000, 3200, 80, 16000)
dash.add_channel("LinkedIn", 2000, 50000, 800, 25, 7500)
dash.add_channel("Email", 1000, 0, 2500, 60, 12000)
dash.summary()What’s Next
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro