import numpy as np, pandas as pd
import matplotlib as mpl, matplotlib.pyplot as plt
# A clean house style for report-ready figures: no chartjunk, strong titles, muted grid.
mpl.rcParams.update({"figure.dpi":110,"font.size":11,"axes.spines.top":False,"axes.spines.right":False,
"axes.grid":True,"grid.alpha":0.22,"axes.titleweight":"bold","axes.titlesize":12.5,
"axes.titlelocation":"left","axes.titlepad":10})
ROSE, INK, MUT, GR, RD = "#be123c", "#1a2138", "#64748b", "#16a34a", "#dc2626"
BASE = "https://raw.githubusercontent.com/johnfisher-ai/Statistics-Data-Science-AI-Visual-Book/main/data/"
fn = "communicating-insights--company-data.xlsx"
def load(sheet):
try: return pd.read_excel("../../data/" + fn, sheet_name=sheet)
except FileNotFoundError: return pd.read_excel(BASE + fn, sheet_name=sheet)
Step 1 · Load and analyze¶
Read the monthly sales sheet and compute the handful of numbers an executive actually needs: the headline total, the trend, and the regional split.
sales = load("SalesMonthly")
sales["quarter"] = "Q" + pd.PeriodIndex(sales.month, freq="M").quarter.astype(str)
total_rev = sales.revenue.sum(); total_orders = int(sales.orders.sum()); aov = total_rev/total_orders
byq = sales.groupby("quarter").revenue.sum()
growth = byq["Q4"]/byq["Q1"] - 1
byregion = sales.groupby("region").revenue.sum().sort_values(ascending=False)
print(f"total revenue ${total_rev:,.0f} | orders {total_orders:,} | AOV ${aov:.2f} | Q4 vs Q1 {growth:+.1%}")
print(byregion.round(0).to_string())
total revenue $2,154,938 | orders 22,576 | AOV $95.45 | Q4 vs Q1 +12.0% region North 535879.0 West 489393.0 East 422289.0 South 379930.0 Central 327447.0
Step 2 · Build the two visuals¶
One trend chart and one ranked bar. Each title states the point so the figures stand alone.
fig_trend, ax = plt.subplots(figsize=(7.6, 3.2))
m = sales.groupby("month").revenue.sum()/1000
ax.plot(m.index, m.values, color=ROSE, lw=2.4, marker="o", ms=3)
ax.set_title(f"Revenue grew {growth:+.0%} across 2024"); ax.set_ylabel("monthly revenue ($k)")
ax.tick_params(axis="x", rotation=45, labelsize=7); plt.tight_layout(); plt.show()
fig_reg, ax = plt.subplots(figsize=(7.6, 3.0))
r = byregion.sort_values()/1000
ax.barh(r.index, r.values, color=ROSE)
for y,v in enumerate(r.values): ax.text(v+4, y, f"${v:,.0f}k", va="center", fontsize=9)
ax.set_title("North leads; Central trails by a third"); ax.set_xlim(0, r.max()*1.16)
ax.grid(axis="y", visible=False); plt.tight_layout(); plt.show()
Step 3 · The summary table for the report¶
The two figures plus this compact KPI table are the evidence the report is built from. A clean table like this is what a statistician drops into the document alongside the charts.
kpi = pd.DataFrame({"Metric":["Total revenue","Orders","Average order value","Q4 vs Q1 growth","Top region"],
"Value":[f"${total_rev:,.0f}", f"{total_orders:,}", f"${aov:.2f}", f"{growth:+.0%}", "North"]})
kpi
| Metric | Value | |
|---|---|---|
| 0 | Total revenue | $2,154,938 |
| 1 | Orders | 22,576 |
| 2 | Average order value | $95.45 |
| 3 | Q4 vs Q1 growth | +12% |
| 4 | Top region | North |
From analysis to report¶
This notebook is the analysis: it loads the data, computes the numbers, and produces the two figures and the table. It deliberately stops there. The finished report is a separate, human-authored Word document, a statistician writing for a non-technical reader, who pastes these exact visuals in and explains what they mean in plain language, laid out inverted-pyramid style with the recommendation first. Download it from the chapter to see the difference between raw output and a written argument.