Data Science Β· Chapter 39 of 43
Dashboards & BI Tools
DASHBOARDS give stakeholders live views of the numbers that matter. Popular tools: Power BI, Tableau, Looker, Metabase.
Good dashboards focus on decisions β not every possible chart.
Example 1 (python)
# 5 KPIs at top, 2 comparisons in middle, 1 detail table at bottom
A clean dashboard layout.
Example 2 (python)
# Use consistent colours: green = good, red = bad
Colour conventions matter.
Key points
- Focus on decisions, not everything.
- Consistent colours and units.
- Refresh frequency should match the decision.
- Explain the metric in a tooltip.
π‘ Note: The best dashboards are the ones people actually open every day β measure usage, not just clicks to build.
Understanding Dashboards & BI Tools
A dashboard is a compact interface for monitoring a situation and deciding what to inspect or change. Business intelligence (BI) tools such as Power BI, Tableau, Looker, and Metabase help teams assemble these interfaces from data, calculations, charts, and filters. A dashboard is not automatically live: its freshness depends on source updates, processing, and refresh settings. Begin with a decision, such as βWhich fulfillment region needs investigation?β, rather than a target number of charts.
The foundation is a metric definition. A key performance indicator (KPI) is a measure selected because it matters to a particular objective. For delivery performance, define on-time rate as on-time deliveries divided by eligible deliveries. Specify eligibility, reporting dates, time zone, and treatment of missing records. Also establish the data grain: what one row represents. One row per delivery supports different calculations from one row per region per day. Joining deliveries to multiple package records can duplicate counts unless that relationship is handled deliberately.
Filters change the population being measured. Selecting a region should normally update both the numerator and denominator of its on-time rate. Totals must follow the same definition: adding counts and then dividing is generally correct, while averaging regional percentages gives small and large regions equal influence. Centralizing these definitions in a shared data model reduces disagreement between pages. The pandas user guide covers grouping, missing data, and table visualization, useful foundations for preparing and checking dashboard inputs before configuring a BI interface.
Presentation should reveal context, not merely decorate numbers. Pair a KPI with its reporting period, denominator, and a relevant target or comparison. Use bars for category comparisons, lines for time trends, and a detail table for investigation. Label units explicitly and reinforce status colors with text. More filters offer flexibility but also create more states to validate. Frequent refreshes improve timeliness but can increase processing cost without improving a weekly decision. Show when data was refreshed and which period is complete. Finally, a dashboard can identify an unusual pattern, but it cannot establish its cause. Access controls, metric ownership, and reconciliation checks matter as much as visual polish when the interface informs operational choices.
Practical walkthrough
Build a prototype around this question: βDid either region miss our illustrative 90% on-time target during the selected period?β Assume the records contain complete, nonoverlapping daily counts of eligible deliveries. The example requires Python 3.11 or newer and an already installed pandas version; its operations are compatible with pandas 2.2 and 3.0. It creates data entirely in memory and prints a dashboard-ready summary, rather than launching a BI application.
First, declare the grain as one row per date and region. Include delivered and on_time counts, keeping the numerator alongside its denominator. Validate that keys are unique, values are present, and on-time counts cannot exceed delivered counts. These checks catch several structural errors, although they cannot prove that the source counts reflect reality.
Next, make the date selection explicit. Filter before aggregating so every summary describes the same period. Group the selected rows by region and sum both count columns. Calculate each rate from those sums; calculate the overall KPI from the selected population, not from the regional rates. Represent a zero denominator as unavailable rather than assigning a misleading zero percent.
Finally, map the printed results to a BI layout: an overall rate card with delivery volume, a regional bar chart with the target marked, and a supporting count table. Display the selected dates beside the title. A production version also needs refresh metadata and permission checks. Test whether selecting North makes the card and regional detail reconcile. Keep the target visibly labeled as a chosen operational threshold, not a statistically established boundary.
Worked example
import pandas as pd
rows = [
("2026-04-01", "North", 80, 72),
("2026-04-01", "South", 20, 14),
("2026-04-02", "North", 120, 108),
("2026-04-02", "South", 30, 21),
]
df = pd.DataFrame(rows, columns=["date", "region", "delivered", "on_time"])
counts = ["delivered", "on_time"]
assert not df.isna().any().any()
assert not df.duplicated(["date", "region"]).any()
assert (df[counts] >= 0).all().all()
assert (df["on_time"] <= df["delivered"]).all()
# ISO date strings sort chronologically for this fixed format.
start, end = "2026-04-01", "2026-04-02"
selected = df.loc[df["date"].between(start, end)]
summary = selected.groupby("region", sort=True)[counts].sum()
target = 0.90
def rate_text(on_time, delivered):
return "N/A" if delivered == 0 else f"{on_time / delivered:.1%}"
print(f"Period: {start} to {end} (inclusive)")
total = int(selected["delivered"].sum())
on_time = int(selected["on_time"].sum())
print(f"Overall: {rate_text(on_time, total)} on time ({on_time}/{total})")
print(f"Target: {target:.0%}")
for region, row in summary.iterrows():
delivered = int(row["delivered"])
on_time = int(row["on_time"])
status = "N/A" if delivered == 0 else (
"Meets target" if on_time / delivered >= target else "Below target"
)
print(f"{region}: {rate_text(on_time, delivered)} ({on_time}/{delivered}) | {status}")
Expected output
Period: 2026-04-01 to 2026-04-02 (inclusive)
Overall: 86.0% on time (215/250)
Target: 90%
North: 90.0% (180/200) | Meets target
South: 70.0% (35/50) | Below target
North contributes 180 on-time deliveries out of 200; South contributes 35 out of 50. Combining their counts gives 215 divided by 250, or 86.0%. Averaging 90% and 70% would instead produce 80%, which answers a different question by weighting regions equally. Sorted grouping fixes the display order, and explicit percentage formatting makes the output deterministic. North meets the inclusive target; South does not. This identifies where to investigate, not why delays occurred. The example validates a calculation layer only: it does not implement interactive filters, authentication, refresh scheduling, or charts. Real date handling should also address timestamps and time zones.
Common mistakes and how to avoid them
Treating missing delivery counts as confirmed zeros.
Distinguish absent reporting from measured zero activity. Flag incomplete periods, investigate missing inputs, and show unavailable rates when denominators are zero.
Using a filter on the numerator but not the denominator.
Apply the intended population selection before calculating both counts. Reconcile a single-region selection against its detail table and document any intentional filter exceptions.
Using red and green as the only status explanation.
Add labels such as βBelow target,β display the threshold, and use accessible contrast. A reader should understand the status without distinguishing colors.
Displaying a partial day beside completed days without warning.
Mark incomplete periods or compare matching elapsed windows. Show data coverage separately from refresh time; a recent refresh can still contain delayed source records.
Review questions
Why must the grain be defined before building charts?
It determines what can be counted and combined safely. A join that expands one delivery into several package rows can inflate delivery totals unless calculations preserve delivery-level uniqueness.
Why is the overall example rate 86% rather than 80%?
The delivery-level KPI weights every delivery equally: 215 of 250 were on time. The 80% average weights each region equally despite their different delivery volumes.
What should accompany an on-time KPI card?
Include its period, eligible delivery count, metric definition, and relevant comparison or target. Also expose freshness and coverage so users can judge whether the value is actionable.
Does South being below target justify a specific operational intervention?
Not by itself. Investigate source quality, shipment mix, and detailed delay patterns first. The dashboard highlights a discrepancy but does not establish which factor caused it.