Session 6 of 26 Part II: Financial Analysis & Value Drivers — Chapter 6 of 18
Part II: Financial Analysis & Value Drivers Chapter 6 of 18 Session 6 Power BI Excel Alt

Power BI Financial Dashboard

Transform raw financial data into an interactive, boardroom-ready dashboard that tells the story of a company's financial health at a glance.

Learning Objectives

📋
Three Tools, One Destination — Use What You Have

The deliverable of this session is an interactive financial dashboard — the tool is secondary. The primary path is Power BI Desktop (free, Windows). On macOS/Linux, use Google Looker Studio (free, browser — Section 6.9 maps every step). And if you have neither but do have Excel: the green Excel Alt boxes show how to build a genuinely interactive dashboard with PivotTables + PivotCharts + Slicers — clicking a slicer filters every chart, just like Power BI. All three produce the same analysis; only the click-paths differ.

Power BI Desktop (primary) Looker Studio (browser) Excel PivotCharts + Slicers

🔍 Opening Challenge — The CFO's Question

You are 12 slides into a static PowerPoint of financial charts when the CFO interrupts: "Interesting. But what if you exclude the two smallest companies — does the margin story still hold?" Your deck has no such slide. You say you'll "get back to them."
Your Task: With a partner, answer two questions: (1) How long would it take you to answer the CFO with your current tools? (2) What would a tool need to do to answer it in front of the CFO, in five seconds? List two capabilities. You have 2 minutes.

6.1 Why Power BI for Financial Dashboards?

In Chapters 3–5, you built Python pipelines that compute value drivers, ratios, and ROIC. These pipelines are powerful — but their output is DataFrames and static charts. When you present to a CFO, a board, or an investment committee, you need something more: an interactive, self-service dashboard where stakeholders can explore the data themselves, filter by year or segment, and drill into the numbers behind the headline.

Microsoft Power BI is the tool of choice for this task for several reasons:

CapabilityWhy It Matters for Valuation
Data connectivityImport from Excel, CSV, databases, and Python DataFrames. Your Python output feeds directly into Power BI.
DAX measuresWrite reusable financial calculations (YoY growth, margins, ratios) that respond dynamically to filters and slicers.
Interactive visualsKPI cards, line charts, waterfall charts, decomposition trees — purpose-built for financial storytelling.
Cross-filteringClick on a year, a segment, or a company, and every visual on the page updates instantly. This is impossible in static reports.
Cloud sharingPublish to Power BI Service and share a live link. Stakeholders interact with the dashboard in their browser — no software installation needed.
Free + Pro tiersPower BI Desktop is free. Microsoft 365 A1 (education) includes Power BI Pro for publishing. This course uses the free Desktop tier.
📝
Note: This chapter uses Power BI Desktop (free, Windows-only). If you are on macOS or Linux, you have two options: (a) use a Windows virtual machine, or (b) use Google Looker Studio (free, browser-based) as an alternative. The dashboard design principles are identical across platforms, though the specific steps differ. Looker Studio equivalents are noted throughout the chapter.
Your Turn — Answer the CFO ⏱ 2 min

Revisit your hook answers. Which of the six capabilities in the table above would have answered the CFO's "exclude the two smallest companies" question live? Identify at least two, and describe in one sentence each how they combine to produce the five-second answer.

Reveal

Slicers + cross-filtering: untick the two smallest companies in the Company slicer — every KPI card, trend, and comparison chart recomputes instantly, live, in the meeting. No new slides, no "I'll get back to you." That interactivity is the entire reason dashboards exist.

X Excel Alt — A Real Interactive Dashboard with PivotTables + Slicers Excel

Excel has had Power BI's core engine — PivotTables — for decades, and slicers make it interactive. The same CSV files this session generates power it:

  1. Import: open ratio_data.csv in Excel → select any cell in the data → Insert → PivotTable → New Worksheet.
  2. First visual (the ROIC comparison): drag Company to Rows, Value to Values (set to Average, not Sum!), filter Ratio to "ROIC (%)". Then Insert → PivotChart → Clustered Bar — the chart and table are now linked.
  3. The interactivity: click inside the PivotTable → PivotTable Analyze → Insert Slicer → tick Company and Year. Every slicer click now filters the PivotTable and its chart — the CFO question answered live, in Excel.
  4. Build the page: repeat for the margin trend (Rows = Year, filter Ratio = "Operating Margin (%)", Line PivotChart) and KPI summary (a small PivotTable filtered to the latest year). Arrange charts + slicers on one sheet following the Section 6.6 Z-pattern.
  5. One slicer, many charts: Ctrl-click to select multiple PivotTables → PivotTable Analyze → Report Connections (or right-click the slicer) → tick all PivotTables. Now ONE Company slicer drives every chart — true cross-filtering, exactly like Power BI.

Excel vs Power BI honestly: Excel wins on familiarity and zero installation; Power BI wins on DAX measures, decomposition trees, cloud sharing, and polish. For this course's deliverable, both are fully acceptable — the analytical content is judged, not the tool.

📋 Stable content — Reviewed: June 2026

6.2 Setting Up Power BI Desktop

6.2.1 Installation

  1. Download Power BI Desktop from powerbi.microsoft.com/en-us/desktop/ (free).
  2. Alternatively, install from the Microsoft Store (search "Power BI Desktop").
  3. Launch Power BI Desktop. You will see a blank canvas with three views on the left: Report (visuals), Data (tables), and Model (relationships).

6.2.2 The Power BI Workflow

Every Power BI project follows a four-step workflow. Understanding this workflow before you start will prevent hours of confusion:

  • Get Data: Connect to your data source (Excel, CSV, database, web) and load tables into Power BI's data model.
  • Transform (Power Query): Clean, reshape, and enrich the raw data — rename columns, change data types, create calculated columns, unpivot, merge tables. Every transformation is recorded and replayable.
  • Model: Define relationships between tables, create DAX measures, build a date table. This is where the "intelligence" of your dashboard lives.
  • Visualize (Report): Drag and drop visuals onto the canvas, bind them to your measures, format them, and arrange them into a coherent narrative.
  • 💡
    Pro Tip: Spend 70% of your time on steps 1–3 (data prep and modeling). A well-structured data model makes visualization trivial. A poorly structured model makes even a simple bar chart a struggle. The most common beginner mistake is rushing to the Report view before the data model is ready.

    🔗 Before You Move On — Quick Checks

    A classmate proudly shows you a beautiful dashboard — built before checking data types. The Year column loaded as Text. What will break?

    Reveal
    Almost everything time-based: the DateTable relationship won't form correctly, trend charts can't sort Year chronologically (2019, 2021, 2023 might sort as text), and time-intelligence DAX fails. Data types are checked in step 1 of the model build — never skip it.

    True or False: If your dashboard visuals look wrong, the fix is usually in the Report view formatting options.

    Reveal
    FALSE — wrong numbers almost always mean a data/model problem (wrong type, wrong relationship, wrong measure), not a formatting problem. Debug the model first, format last.

    6.3 Preparing Your Financial Data for Power BI

    Power BI works best with clean, structured, unpivoted data. The financial DataFrames we generated in Chapters 3–5 are wide-format (years as columns, metrics as rows). Power BI prefers long/tall format (one row per metric per year). Let us prepare the data correctly.

    6.3.1 The Target Schema

    Your Power BI data model should have this structure for a financial dashboard:

    Table NameColumnsDescription
    FinancialsYear, Company, Metric, ValueThe main fact table — one row per metric per company per year. Metrics include Revenue, COGS, EBIT, Net Income, Total Assets, etc.
    RatiosYear, Company, Ratio, ValuePre-computed ratios — Gross Margin, Operating Margin, ROIC, Current Ratio, D/E, etc.
    CompanyCompanyID, CompanyName, Sector, Ticker, MarketCapDimension table with company attributes for filtering.
    DateDate, Year, FiscalYearA date dimension table for time intelligence functions.

    6.3.2 Python Script: Generate Power BI-Ready Financial Data

    Run this Python script to fetch financial data for multiple Indian companies and export it in Power BI-friendly format. You must run this script and save the CSV files before you start the Power BI section.

    """
    Generate Power BI-ready financial datasets for Indian companies.
    Run this script once. It produces two CSV files:
      - financial_data.csv  →  Raw financial statement line items (long format)
      - ratio_data.csv      →  Pre-computed financial ratios (long format)
    
    Import both into Power BI Desktop as your data source.
    """
    
    import yfinance as yf
    import pandas as pd
    import numpy as np
    import time
    
    # ============================================================
    # CONFIGURATION: Choose your companies
    # ============================================================
    COMPANIES = {
        'TCS.NS':           {'Name': 'Tata Consultancy Services', 'Sector': 'IT Services'},
        'INFY.NS':          {'Name': 'Infosys', 'Sector': 'IT Services'},
        'WIPRO.NS':         {'Name': 'Wipro', 'Sector': 'IT Services'},
        'HCLTECH.NS':       {'Name': 'HCL Technologies', 'Sector': 'IT Services'},
        'TECHM.NS':         {'Name': 'Tech Mahindra', 'Sector': 'IT Services'},
    }
    
    # Alternatively, for a cross-sector comparison:
    # COMPANIES = {
    #     'TCS.NS':           {'Name': 'TCS', 'Sector': 'IT'},
    #     'ASIANPAINT.NS':    {'Name': 'Asian Paints', 'Sector': 'Consumer'},
    #     'RELIANCE.NS':      {'Name': 'Reliance Industries', 'Sector': 'Conglomerate'},
    #     'HDFCBANK.NS':      {'Name': 'HDFC Bank', 'Sector': 'Banking'},
    #     'SUNPHARMA.NS':     {'Name': 'Sun Pharma', 'Sector': 'Pharma'},
    # }
    
    # ============================================================
    # DATA EXTRACTION
    # ============================================================
    
    def safe_get(df, candidates):
        """Try multiple field names, return first match."""
        if isinstance(candidates, str):
            candidates = [candidates]
        for c in candidates:
            if c in df.index:
                return df.loc[c]
        return None
    
    financial_rows = []
    ratio_rows = []
    
    for ticker, meta in COMPANIES.items():
        print(f"Fetching {meta['Name']} ({ticker})...")
        try:
            t = yf.Ticker(ticker)
            info = t.info
            is_df = t.financials
            bs_df = t.balance_sheet
            cf_df = t.cashflow
    
            # --- Raw Financial Metrics (long format) ---
            mappings = {
                'Total Revenue': ['Total Revenue', 'Revenue'],
                'Cost of Revenue': ['Cost Of Goods Sold', 'Cost of Revenue'],
                'Gross Profit': ['Gross Profit'],
                'Operating Income': ['Operating Income', 'EBIT'],
                'EBITDA': ['EBITDA'],
                'Net Income': ['Net Income', 'Net Income Common Stockholders'],
                'Total Assets': ['Total Assets'],
                'Current Assets': ['Current Assets', 'Total Current Assets'],
                'Current Liabilities': ['Current Liabilities', 'Total Current Liabilities'],
                'Total Debt': ['Total Debt', 'Long Term Debt'],
                'Shareholders Equity': ['Total Equity Gross Minority Interest',
                                         'Stockholders Equity', 'Total Equity'],
                'Cash and Equivalents': ['Cash And Cash Equivalents',
                                          'Cash Cash Equivalents And Short Term Investments'],
                'Operating Cash Flow': ['Operating Cash Flow'],
                'Capital Expenditure': ['Capital Expenditure', 'Capital Expenditure Reported'],
                'Accounts Receivable': ['Accounts Receivable', 'Net Receivables'],
                'Inventory': ['Inventory', 'Inventories'],
            }
    
            for metric_name, field_candidates in mappings.items():
                val = safe_get(is_df, field_candidates)
                if val is None:
                    val = safe_get(bs_df, field_candidates)
                if val is None:
                    val = safe_get(cf_df, field_candidates)
                if val is not None:
                    for fiscal_year, value in val.items():
                        if pd.notna(value) and value != 0:
                            financial_rows.append({
                                'Company': meta['Name'],
                                'Ticker': ticker.replace('.NS', ''),
                                'Sector': meta['Sector'],
                                'Year': int(str(fiscal_year).split('-')[0]),
                                'Metric': metric_name,
                                'Value': round(value / 1e7, 2)  # Convert to Rs. Crore
                            })
    
            # --- Pre-computed Ratios (long format) ---
            rev  = safe_get(is_df, ['Total Revenue', 'Revenue'])
            gp   = safe_get(is_df, ['Gross Profit'])
            ebit = safe_get(is_df, ['Operating Income', 'EBIT'])
            ni   = safe_get(is_df, ['Net Income', 'Net Income Common Stockholders'])
            ebitda = safe_get(is_df, ['EBITDA'])
            ca   = safe_get(bs_df, ['Current Assets', 'Total Current Assets'])
            cl   = safe_get(bs_df, ['Current Liabilities', 'Total Current Liabilities'])
            debt = safe_get(bs_df, ['Total Debt', 'Long Term Debt'])
            eq   = safe_get(bs_df, ['Total Equity Gross Minority Interest',
                                     'Stockholders Equity', 'Total Equity'])
            cash = safe_get(bs_df, ['Cash And Cash Equivalents',
                                     'Cash Cash Equivalents And Short Term Investments'])
            ta   = safe_get(bs_df, ['Total Assets'])
            ocf  = safe_get(cf_df, ['Operating Cash Flow'])
            capex = safe_get(cf_df, ['Capital Expenditure', 'Capital Expenditure Reported'])
    
            for year_idx in is_df.columns:
                fy = int(str(year_idx).split('-')[0])
    
                try:
                    if rev is not None and rev.get(year_idx):
                        r = rev[year_idx]
                        # Margins
                        if gp is not None and gp.get(year_idx):
                            ratio_rows.append({'Company': meta['Name'], 'Ticker': ticker.replace('.NS',''),
                                               'Sector': meta['Sector'], 'Year': fy,
                                               'Ratio': 'Gross Margin (%)',
                                               'Value': round((gp[year_idx]/r)*100, 2)})
                        if ebit is not None and ebit.get(year_idx):
                            ratio_rows.append({'Company': meta['Name'], 'Ticker': ticker.replace('.NS',''),
                                               'Sector': meta['Sector'], 'Year': fy,
                                               'Ratio': 'Operating Margin (%)',
                                               'Value': round((ebit[year_idx]/r)*100, 2)})
                        if ni is not None and ni.get(year_idx):
                            ratio_rows.append({'Company': meta['Name'], 'Ticker': ticker.replace('.NS',''),
                                               'Sector': meta['Sector'], 'Year': fy,
                                               'Ratio': 'Net Margin (%)',
                                               'Value': round((ni[year_idx]/r)*100, 2)})
                        if ebitda is not None and ebitda.get(year_idx):
                            ratio_rows.append({'Company': meta['Name'], 'Ticker': ticker.replace('.NS',''),
                                               'Sector': meta['Sector'], 'Year': fy,
                                               'Ratio': 'EBITDA Margin (%)',
                                               'Value': round((ebitda[year_idx]/r)*100, 2)})
                        # Revenue Growth
                        prev_year = str(fy - 1)
                        for y2 in is_df.columns:
                            if str(y2).startswith(prev_year):
                                prev_rev = rev[y2]
                                if prev_rev and prev_rev != 0:
                                    growth = ((r - prev_rev) / prev_rev) * 100
                                    ratio_rows.append({'Company': meta['Name'], 'Ticker': ticker.replace('.NS',''),
                                                       'Sector': meta['Sector'], 'Year': fy,
                                                       'Ratio': 'Revenue Growth (%)',
                                                       'Value': round(growth, 2)})
                                break
    
                    # Ratios from balance sheet
                    if ca is not None and cl is not None and ca.get(year_idx) and cl.get(year_idx):
                        cr = (ca[year_idx] / cl[year_idx])
                        ratio_rows.append({'Company': meta['Name'], 'Ticker': ticker.replace('.NS',''),
                                           'Sector': meta['Sector'], 'Year': fy,
                                           'Ratio': 'Current Ratio', 'Value': round(cr, 2)})
    
                    if debt is not None and eq is not None and debt.get(year_idx) and eq.get(year_idx):
                        de = debt[year_idx] / eq[year_idx]
                        ratio_rows.append({'Company': meta['Name'], 'Ticker': ticker.replace('.NS',''),
                                           'Sector': meta['Sector'], 'Year': fy,
                                           'Ratio': 'Debt-to-Equity', 'Value': round(de, 2)})
    
                    # ROIC (preliminary)
                    if ebit is not None and eq is not None and debt is not None and cash is not None:
                        if all(x.get(year_idx) for x in [ebit, eq, debt, cash]):
                            nopat = ebit[year_idx] * 0.75
                            ic = eq[year_idx] + debt[year_idx] - cash[year_idx]
                            if ic > 0:
                                roic = (nopat / ic) * 100
                                ratio_rows.append({'Company': meta['Name'], 'Ticker': ticker.replace('.NS',''),
                                                   'Sector': meta['Sector'], 'Year': fy,
                                                   'Ratio': 'ROIC (%)', 'Value': round(roic, 2)})
    
                    # ROE
                    if ni is not None and eq is not None and ni.get(year_idx) and eq.get(year_idx):
                        roe = (ni[year_idx] / eq[year_idx]) * 100
                        ratio_rows.append({'Company': meta['Name'], 'Ticker': ticker.replace('.NS',''),
                                           'Sector': meta['Sector'], 'Year': fy,
                                           'Ratio': 'ROE (%)', 'Value': round(roe, 2)})
    
                    # Asset Turnover
                    if rev is not None and ta is not None and rev.get(year_idx) and ta.get(year_idx):
                        at = rev[year_idx] / ta[year_idx]
                        ratio_rows.append({'Company': meta['Name'], 'Ticker': ticker.replace('.NS',''),
                                           'Sector': meta['Sector'], 'Year': fy,
                                           'Ratio': 'Asset Turnover', 'Value': round(at, 2)})
    
                    # FCF Yield
                    mkt_cap = info.get('marketCap')
                    if ocf is not None and capex is not None and mkt_cap:
                        if ocf.get(year_idx) and capex.get(year_idx):
                            fcf = ocf[year_idx] + capex[year_idx]
                            fcf_yield = (fcf / mkt_cap) * 100
                            ratio_rows.append({'Company': meta['Name'], 'Ticker': ticker.replace('.NS',''),
                                               'Sector': meta['Sector'], 'Year': fy,
                                               'Ratio': 'FCF Yield (%)', 'Value': round(fcf_yield, 2)})
                except Exception:
                    pass  # Skip individual year/ratio failures
    
            print(f"  ✓ {meta['Name']} — {len([r for r in financial_rows if r['Company']==meta['Name']])} financial rows, "
                  f"{len([r for r in ratio_rows if r['Company']==meta['Name']])} ratio rows")
    
        except Exception as e:
            print(f"  ✗ {meta['Name']}: {e}")
        time.sleep(1.5)
    
    # Save to CSV
    df_fin = pd.DataFrame(financial_rows)
    df_rat = pd.DataFrame(ratio_rows)
    
    df_fin.to_csv('financial_data.csv', index=False)
    df_rat.to_csv('ratio_data.csv', index=False)
    
    print(f"\n=== DONE ===")
    print(f"financial_data.csv: {len(df_fin)} rows, {df_fin['Company'].nunique()} companies, "
          f"{df_fin['Year'].min()}-{df_fin['Year'].max()}")
    print(f"ratio_data.csv: {len(df_rat)} rows, {df_rat['Company'].nunique()} companies")
    print(f"\nUnique Metrics: {sorted(df_fin['Metric'].unique())}")
    print(f"Unique Ratios: {sorted(df_rat['Ratio'].unique())}")
    print(f"\nImport both CSV files into Power BI Desktop to begin the dashboard build.")
    
    🌎
    Your Turn: Run the script above. It will produce two CSV files. Before proceeding to the Power BI build, verify the output: open each CSV in Excel and confirm you have data for all your chosen companies across multiple years. The financial_data.csv should have columns: Company, Ticker, Sector, Year, Metric, Value. The ratio_data.csv should have: Company, Ticker, Sector, Year, Ratio, Value.

    6.4 Building the Data Model in Power BI

    With your CSV files ready, let us build the data model — the foundation of your entire dashboard.

    6.4.1 Import Data

  • Open Power BI Desktop. On the Home ribbon, click Get Data → Text/CSV. Select financial_data.csv and click Load. Repeat for ratio_data.csv.
  • Verify data types. Switch to Data view (left sidebar, second icon — the table). Select the financial_data table. Confirm: Year is Whole Number, Value is Decimal Number, and Company, Sector, Ticker, Metric are Text. If any column has the wrong type, click the column header, go to Column Tools ribbon, and change the Data Type.
  • Switch to Model view (third icon). You will see both tables. There is no relationship to create yet — we are using a single-table schema with the Ratio table as the primary analysis table. This is deliberately simple for a financial dashboard.
  • 📝
    Screenshot Placeholder 1: Power BI Model view showing the financial_data and ratio_data tables with their columns listed. [User: capture the Model view after importing both CSVs]

    6.4.2 Create the Date Table (Essential for Time Intelligence)

    DAX time intelligence functions (YEAR-OVER-YEAR growth, running totals, moving averages) require a proper date table. Create one now:

    -- 1. Go to Home → New Table (or Modeling → New Table)
    -- 2. Paste this DAX expression:
    
    DateTable =
    VAR MinYear = MIN(ratio_data[Year])
    VAR MaxYear = MAX(ratio_data[Year])
    RETURN
    ADDCOLUMNS(
        CALENDAR(DATE(MinYear, 4, 1), DATE(MaxYear, 3, 31)),
        "FiscalYear",
            IF(MONTH([Date]) >= 4,
               YEAR([Date]),
               YEAR([Date]) - 1
            ),
        "MonthNum", MONTH([Date]),
        "MonthName", FORMAT([Date], "MMM"),
        "Quarter", "Q" & FORMAT([Date], "Q"),
        "FiscalQuarter",
            "FY" & RIGHT(
                IF(MONTH([Date]) >= 4, YEAR([Date]), YEAR([Date]) - 1), 2
            ) & "-Q" & FORMAT(
                IF(MONTH([Date]) >= 4,
                   INT((MONTH([Date]) - 4) / 3) + 1,
                   INT((MONTH([Date]) + 8) / 3) + 1
                ), "0"
            )
    )
    
    -- 3. Mark as date table: Select DateTable → Table Tools → Mark as Date Table → choose [Date] column.

    This creates a date table aligned to the Indian fiscal year (April–March). If your data uses a January–December calendar year, simplify by removing the FiscalYear logic and using the plain Year column.

    6.4.3 Create Relationships

    In Model view, drag the Year column from ratio_data to the FiscalYear column in DateTable. This creates a one-to-many relationship (one DateTable row → many ratio_data rows for that year).

    6.5 Writing DAX Measures for Financial Analysis

    DAX (Data Analysis Expressions) is the formula language of Power BI. Measures are DAX formulas that compute values dynamically based on the current filter context. When a user clicks on "TCS" in a slicer, all measures automatically recompute for TCS.

    6.5.1 KPI Measures

    Right-click the ratio_data table in the Data pane and select New Measure. Create each of these:

    Measure NameDAX FormulaWhat It Shows
    Latest Revenue Growth VAR LatestYear = MAX(ratio_data[Year])
    RETURN CALCULATE(MAX(ratio_data[Value]), ratio_data[Ratio] = "Revenue Growth (%)", ratio_data[Year] = LatestYear)
    Most recent year's revenue growth rate
    Latest Operating Margin Same pattern, replace Ratio filter with "Operating Margin (%)" Most recent operating margin
    Latest ROIC Same pattern, replace Ratio filter with "ROIC (%)" Most recent ROIC
    Latest Net Margin Same pattern, replace Ratio filter with "Net Margin (%)" Most recent net margin
    Latest D/E Same pattern, replace Ratio filter with "Debt-to-Equity" Most recent debt-to-equity

    The pattern for each KPI measure is identical — only the ratio name changes. Here is the full DAX for one:

    Latest ROIC =
    VAR LatestYear = MAX(ratio_data[Year])
    RETURN
    CALCULATE(
        MAX(ratio_data[Value]),
        ratio_data[Ratio] = "ROIC (%)",
        ratio_data[Year] = LatestYear
    )

    6.5.2 Trend Measures

    For line charts that show a metric over time, create this simple aggregation measure:

    Operating Margin =
    CALCULATE(
        MAX(ratio_data[Value]),
        ratio_data[Ratio] = "Operating Margin (%)"
    )

    Create similar measures for Gross Margin, Net Margin, ROIC, ROE, Revenue Growth, and Current Ratio. You will need one trend measure per ratio you want to chart.

    6.5.3 Conditional Color Measure

    For KPI cards that turn green or red based on performance:

    ROIC Color =
    VAR roic = [Latest ROIC]
    RETURN
    SWITCH(
        TRUE(),
        roic >= 25, "#00C9A7",    -- Green: Excellent
        roic >= 15, "#6C8CFF",    -- Blue: Good
        roic >= 10, "#F0A040",    -- Orange: Adequate
        roic < 10,  "#E0556A",    -- Red: Poor
        "#A0A0B8"                 -- Grey: No data
    )
    🔍 DAX Lab — Predict, Then Debug ⏱ 5 min

    Part 1 — Predict (1 min, solo): Before typing anything, what will [Latest ROIC] return if the slicer selects two companies — TCS (ROIC 45%) and Infosys (ROIC 38%)? One number or two? Whose?

    Part 2 — Debug (pairs): A classmate's trend chart shows Operating Margin at 10x the expected value (e.g., 250 instead of 25). Their measure:

    Operating Margin =
    SUM(ratio_data[Value])

    Three questions: (a) Why is the number absurd? (b) Why did we use MAX with a RATIO = filter instead of SUM? (c) Write the corrected measure from memory.

    Reveal answers

    Part 1: MAX over the filtered rows — it returns the higher of the two (45%), silently. A KPI card assumes one context; for multi-select, you'd want AVERAGEX or "don't summarize" — know what your aggregation does when a filter returns several rows. Part 2: (a) SUM adds every ratio row in scope — margins, growth, D/E, everything — across all years: garbage. (b) The filter ratio_data[Ratio] = "Operating Margin (%)" isolates one metric; MAX then takes that (single) year's value. (c) The CALCULATE(MAX(...), Ratio = "Operating Margin (%)") pattern from 6.5.2 above.

    6.6 Building the Dashboard Visuals

    Now we assemble the visuals. Switch to Report view. The canvas is where your dashboard comes to life.

    6.6.1 Dashboard Layout: The Grid

    An executive dashboard follows a Z-pattern reading flow: top-left (most important) → top-right → bottom-left → bottom-right. Arrange your canvas as follows:

    PositionVisual TypeContent
    Top Row — Left4 KPI CardsLatest Revenue Growth %, Operating Margin %, ROIC %, Net Margin %
    Top Row — RightSlicer (dropdown)Company selector — allows filtering the entire dashboard to one company
    Middle Row — LeftLine ChartMargin trends over time (Gross, Operating, Net) — multi-series
    Middle Row — RightClustered Bar ChartROIC by company comparison
    Bottom Row — LeftLine ChartROIC and ROE trend over time
    Bottom Row — RightScatter ChartRevenue Growth vs ROIC (bubble size = last year revenue)

    6.6.2 Step-by-Step Visual Build

  • KPI Cards (Top-Left): From the Visualizations pane, select the Card visual (or Multi-row Card). Drag the [Latest Revenue Growth] measure into the Fields well. Format: set the title to "Revenue Growth %", data label font size to 24pt. Repeat for Operating Margin, ROIC, and Net Margin — arrange them in a 2×2 grid. Use the Format pane to set each card's background to a subtle gradient and apply the conditional color measure to the data label via Format → Data Label → Color → fx (conditional formatting).
  • Company Slicer (Top-Right): Select the Slicer visual. Drag ratio_data[Company] into the Field well. In Format → Slicer Settings → Style, select "Dropdown." Enable "Select All" and "Single Select" if desired. This slicer will filter every other visual on the page.
  • Margin Trends Line Chart (Middle-Left): Select Line Chart. Drag DateTable[FiscalYear] to X-axis. Drag [Operating Margin], [Gross Margin], and [Net Margin] to the Y-axis well. In Format: set Y-axis range to 0–50%, add a horizontal reference line at 15% (typical WACC proxy), and title it "Margin Trends."
  • ROIC Bar Chart (Middle-Right): Select Clustered Bar Chart. Drag ratio_data[Company] to Y-axis, [Latest ROIC] to X-axis. In Format → Data Colors, apply conditional formatting: green if ROIC > 15%, orange if 10–15%, red if <10%. Sort descending by ROIC. Add a reference line at 10% (WACC).
  • ROIC + ROE Trend (Bottom-Left): Select Line Chart. Drag DateTable[FiscalYear] to X-axis. Drag [ROIC] and [ROE] to Y-axis. Use distinct colors (blue for ROIC, orange for ROE). Add a horizontal reference line at 10%.
  • Growth vs ROIC Scatter (Bottom-Right): Select Scatter Chart. Drag [Revenue Growth] to X-axis, [ROIC] to Y-axis, ratio_data[Company] to Legend, and revenue to Size (you will need a separate measure for this). Add quadrant lines: vertical at 0% growth, horizontal at 10% ROIC.
  • 📝
    Screenshot Placeholder 2: The completed Report view canvas with all 6 visuals arranged in the Z-pattern layout, showing data for all companies. [User: capture the full Report view after building all visuals]

    6.6.3 Cross-Highlighting and Interactivity

    Power BI automatically enables cross-filtering and cross-highlighting between visuals. Verify these interactions:

    To customize interactions: select a visual → Format ribbon → Edit Interactions → choose which visuals it filters, highlights, or ignores.

    📝
    Screenshot Placeholder 3: The dashboard with "TCS" selected in the slicer — all visuals filtered to show only TCS data. [User: capture the dashboard after selecting a single company]
    Your Turn — The 60-Second Interactivity Audit ⏱ 2 min

    Run these five clicks on your dashboard. Any "no" means something is mis-connected:

    1. Slicer → one company: do all six visuals update?
    2. Click one bar in the ROIC chart: do the KPI cards stay at latest year while trends re-filter?
    3. Click a middle year on a trend line: does the rest of the page respond?
    4. Ctrl-click two companies: do averages/maximums still make sense (remember the DAX Lab)?
    5. Clear all filters (top-right eraser icon): is the page back to the full view?

    Fix any "no" before moving on — a dashboard where one chart ignores the slicer destroys the CFO-five-seconds promise of this whole session.

    👥 Pair Up — The Insight Hunt ⏱ 4 min

    With your dashboard (all companies visible), find and jot down:

    1. The single strongest value creator on your Growth-vs-ROIC scatter — and the click-sequence you used to prove it
    2. One company whose margins and ROIC disagree (healthy margin, weak ROIC — or the reverse) — what does the decomposition tell you?
    3. One year where something broke (a dip, a spike) across multiple charts at once

    Partner check: swap seats, reproduce each other's three findings by clicking — if you can't reproduce it, the insight (or the dashboard) isn't solid.

    6.7 Executive Dashboard Design Principles

    A technically correct dashboard can still fail if it does not communicate effectively. Here are the design principles that separate a professional executive dashboard from an amateur chart collection:

    6.7.1 The Five Principles

    PrincipleWhat It MeansHow to Apply in Power BI
    1. Hierarchy The most important information should occupy the most prominent visual real estate. KPI cards go top-left (first thing the eye sees). Supporting charts go in the middle. Detail tables go at the bottom or on a separate page.
    2. Consistency The same color should always mean the same thing. The same metric should always use the same format. Define a color palette: green = good, red = bad, blue = primary metric. Use the JSON theme file to enforce it globally (File → Options → Preview Features → Customize current theme).
    3. Narrative The dashboard should tell a story: Overview → Trend → Comparison → Detail. Arrange visuals in reading order. Add text boxes as section headers. Use the "Z-pattern" layout: KPI overview (top-left) → company filter (top-right) → trend (middle-left) → comparison (middle-right) → detail (bottom).
    4. Restraint Less is more. 6–8 well-chosen visuals communicate more than 20 cluttered ones. Limit to one page of key visuals. Move supplementary detail to tooltips or a second page. Remove gridlines, excessive borders, and distracting background images.
    5. Context Every number needs a benchmark. "15% ROIC" means nothing without knowing whether it is good or bad. Add reference lines (WACC at 10%), conditional formatting (green/red), and small text annotations explaining what to look for.

    6.7.2 Color Palette for Financial Dashboards

    PurposeColorHEX Code
    Primary metric (headline KPI)Royal Blue#6C8CFF
    Positive / Above benchmarkTeal Green#00C9A7
    Warning / MarginalAmber#F0A040
    Negative / Below benchmarkRed#E0556A
    Neutral / Background elementMuted Grey#A0A0B8
    Dark backgroundDark Navy#1C1C2E
    💡
    Pro Tip: Apply a dark theme to your Power BI dashboard. Dark backgrounds reduce eye strain in presentations, make colors pop, and look more professional in a boardroom setting. In Power BI: View → Themes → Dark. You can also import a custom JSON theme file. The HEX codes above work equally well on light backgrounds — the key is consistency.
    💪 Pair Critique — The Five-Point Design Audit ⏱ 6 min

    Swap screens with your partner. Audit their dashboard against the five principles — one line each:

    1. Hierarchy: What did your eye see FIRST? Is that the most important message?
    2. Consistency: Does green/red mean the same thing on every visual? Any metric formatted two ways?
    3. Narrative: Read the page aloud top-left to bottom-right — does it tell one coherent story, or three unrelated charts?
    4. Restraint: Count the visuals and the ink. What ONE element would you delete for more impact?
    5. Context: Pick any number on their page — can you tell within 3 seconds if it's good or bad? If not, what benchmark is missing?

    Deliver one compliment + one fix. Then swap back and apply the fix you received before the project submission. Excel track: run the identical audit on the PivotChart sheet layout.

    📋 Stable content — Reviewed: June 2026

    6.8 Publishing and Sharing Your Dashboard

    Once your dashboard is complete, you need to share it. Power BI offers multiple sharing paths depending on your audience:

    MethodBest ForHow
    Publish to Power BI ServiceSharing with faculty, classmates, or colleagues who have Power BI accountsHome → Publish → Select a workspace → Share the link. Requires Power BI Pro or Microsoft 365 A1+ license.
    Export to PDFIncluding the dashboard in a report or submitting as an assignmentFile → Export → Export to PDF. Captures the current filter state.
    Export to PowerPointPresenting in a live meetingFile → Export → Export to PowerPoint → Embed live data (if published) or static images.
    ScreenshotQuick sharing via WhatsApp, email, or embedding in documentsUse the Snipping Tool or Win+Shift+S. Capture the full dashboard at a readable resolution (1920×1080 recommended).
    Share .pbix fileCollaborative editing — others can open your file in Power BI DesktopSave the .pbix file and share via OneDrive, Teams, or USB. Recipients need Power BI Desktop installed.
    🎨 Match the Method — Pick the Right Channel ⏱ 3 min

    Which sharing method for each scenario? (Some have more than one defensible answer — justify.)

    1. Submitting this session's project to faculty, who will grade it without Power BI installed
    2. Live investment-committee meeting where the CFO may ask exactly one drill-down question
    3. A teammate must fix a broken DAX measure in your model while you are on leave
    4. Posting a one-image summary to the class WhatsApp group tonight

    Pair: Compare choices. For scenario 2, what do you LOSE by exporting to PowerPoint instead of presenting live?

    Check your answers

    1) Export to PDF (or screenshots) — readable anywhere, fixed filter state. 2) Publish to Service + share link, or present Desktop live — the drill-down answer requires interactivity. 3) Share the .pbix — collaborative editing needs the source file. 4) Screenshot (Win+Shift+S) at 1920×1080. Scenario 2 PowerPoint loss: the five-second CFO answer — static slides are exactly the trap this session opened with.

    📋 Stable content — Reviewed: June 2026

    6.9 Alternative: Google Looker Studio (Browser-Based, Free)

    If you cannot run Power BI Desktop (macOS/Linux users), Google Looker Studio (formerly Data Studio) is a capable, free alternative that runs entirely in the browser. The dashboard design principles are identical; only the tool mechanics differ.

    Power BI FeatureLooker Studio Equivalent
    Get Data (CSV import)Create Data Source → File Upload → CSV
    DAX MeasuresCalculated Fields (similar formula language, less powerful)
    SlicersFilter Control (dropdown, single-select, multi-select)
    KPI CardsScorecard visual
    Line ChartTime Series chart
    Scatter ChartScatter chart (same name)
    Cross-filteringAutomatic; configure via chart interactions
    Publish/ShareShare → Get report link (view-only or edit access)

    The CSV files generated by the Python script in Section 6.3 are directly importable into Looker Studio. The workflow is: Create Report → Add Data (File Upload) → drag charts onto the canvas → configure dimensions and metrics.

    Hands-On Project: Build and Present a Complete Financial Analysis Dashboard

    Your task is to build a complete, boardroom-ready financial analysis dashboard for 5 Indian companies using Power BI (or Looker Studio). You will source the data, build the model, design the visuals, and produce a final dashboard that tells a clear financial story. This dashboard will be part of your capstone project deliverables.

    Excel Option No Power BI or Looker access? Build the identical dashboard in Excel per Section 6.1's Excel Alt box: one sheet with 3–4 PivotCharts (ROIC comparison, margin trend, ROIC trend, KPI summary table), all connected to shared Company/Year slicers via Report Connections, arranged in the same Z-pattern and passing the same five-point design audit. The 5 screenshots and the 200-word insight summary are identical requirements regardless of tool.

    This is where I need your help. Follow the steps below in your Power BI environment and share the outputs. I will incorporate your actual screenshots and results into this chapter.

    Steps

    1. Choose your companies (5 companies, either within one sector or across sectors). For example:
      • Single-sector: TCS, Infosys, Wipro, HCL Tech, Tech Mahindra (IT Services)
      • Cross-sector: TCS, Asian Paints, Reliance, Sun Pharma, Titan
    2. Run the data generation script from Section 6.3.2. Update the COMPANIES dictionary with your chosen tickers. Verify the two CSV files are generated with correct data.
    3. Import into Power BI and build the data model (Section 6.4). Create the DateTable and relationships.
    4. Write DAX measures (Section 6.5) — at minimum: Latest Revenue Growth, Latest Operating Margin, Latest ROIC, Latest Net Margin, and the trend measures for line charts.
    5. Build the visuals (Section 6.6) — KPI cards, slicer, line charts, bar chart, and scatter plot. Apply conditional formatting.
    6. Apply design principles (Section 6.7) — dark theme, consistent colors, reference lines, proper labeling.
    7. Capture the following screenshots:
      • Screenshot A: The data generation script output in your terminal (showing the CSV row counts)
      • Screenshot B: Power BI Model view showing the tables and relationships
      • Screenshot C: The full Report view with all visuals and all companies visible
      • Screenshot D: The dashboard filtered to a single company (click on one company in the slicer)
      • Screenshot E: The most interesting insight you discovered — e.g., a surprising ROIC comparison, a margin trend that tells a story, or the Growth vs ROIC scatter plot showing a clear value creator
    8. Write a 200-word insight summary: Based on your dashboard, which company has the strongest financial profile? Which is the weakest? What surprised you?
    9. Live Defence (NEW): In pairs, present your dashboard for 3 minutes — but your partner plays the CFO and must ask at least one "what if…" filter question during the presentation (e.g., "exclude the smallest company," "show only the last 3 years"). You must answer it live with slicer clicks, not words. Then swap. If you couldn't answer live, note what your dashboard was missing — and fix it.
    🌎
    Collaboration Request: I need you (the user) to complete this exercise in your Power BI environment and share the 5 screenshots listed above. Once I have them, I will update this section with your actual results, annotated screenshots, and a walkthrough based on real data. This makes the chapter authentic rather than theoretical.

    Key Takeaways

    1

    A dashboard tells a story that raw data cannot. The same financial metrics that fill pages of tables become instantly comprehensible when arranged in a well-designed Power BI dashboard with interactivity.

    2

    70% of dashboard effort is data preparation. The Python pipeline you built in Chapters 3–5 generates the clean, structured data that makes Power BI import seamless. Never skip this step.

    3

    DAX measures are reusable across visuals. Write a measure once — like [Latest ROIC] — and use it in KPI cards, bar charts, tables, and conditional formatting rules. Changes propagate everywhere.

    4

    Follow the five design principles: Hierarchy, Consistency, Narrative, Restraint, and Context. A technically correct dashboard that violates these principles fails to communicate.

    5

    Interactive dashboards are the capstone deliverable for this course. Your ability to present financial analysis through Power BI is a direct job skill for investment banking, equity research, and corporate finance roles.

    Test Your Understanding

    1. In Power BI, what is the correct order of the four-step workflow for building a dashboard?

    2. Why is a "long format" (tall) data structure preferred over a "wide format" in Power BI?

    3. What does a DAX measure do that a regular calculated column does NOT?

    4. In the Z-pattern dashboard layout, where should the most important KPI metrics be placed?

    5. You want a KPI card to display the latest year's ROIC for whichever company is selected in the slicer. Which DAX pattern correctly achieves this?

    📝 Quick Reflection ⏱ 1 min

    Look at the dashboard you built today. Which single visual would you show FIRST in a boardroom, and which one would you cut if you had only 30 seconds? Write both down — this "elevator view" instinct is what you will refine into the executive capstone dashboard in Session 24.