Skip to content
Marketing Analytics Dashboards — KPI Selection, Data Studio & Attribution

Marketing Analytics Dashboards — KPI Selection, Data Studio & Attribution

DodaTech Updated Jun 20, 2026 10 min read

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 StageTop KPIsWhy It Matters
AwarenessImpressions, Reach, CPMAre people seeing us?
InterestCTR, Bounce Rate, Time on SiteAre people engaging?
ConsiderationPages/Session, Lead Gen RateAre people researching?
ConversionCVR, CPA, ROASAre people buying?
RetentionChurn Rate, LTV, Repeat RateAre 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_score

Google 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=$1000

Attribution Modeling

Attribution models determine how credit for a conversion is distributed across marketing touchpoints.

Common Attribution Models

ModelHow It WorksBest For
First-Touch100% credit to first interactionAwareness campaigns
Last-Touch100% credit to last interaction before conversionPerformance marketing
LinearEqual credit to all touchpointsBrand building
Time-DecayMore credit to recent touchpointsLong sales cycles
Position-Based40% first, 40% last, 20% middleB2B marketing
Data-DrivenAlgorithmic allocation based on impactAdvanced 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

  1. North Star Metric — the one metric that matters most (e.g., MRR, DAU, conversions)
  2. Context — comparison to previous period, target, or benchmark
  3. Granularity — ability to drill down from daily → channel → campaign → keyword
  4. Actionable alerts — thresholds that trigger notifications
  5. 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

  1. 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.

  2. 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.

  3. 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”).

  4. 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.

  5. 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