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)
import statsmodels.formula.api as smf
Step 1 · Fit the model¶
Regress overall satisfaction on four candidate drivers. The coefficients say how many points of satisfaction each one-point improvement in a driver buys, holding the others fixed.
survey = load("Survey")
model = smf.ols("satisfaction ~ ease_of_use + support_quality + price_fairness + wait_time", survey).fit()
coefs = model.params.drop("Intercept").sort_values(ascending=False)
print(f"R-squared {model.rsquared:.3f} | mean satisfaction {survey.satisfaction.mean():.1f}")
print(coefs.round(2).to_string())
R-squared 0.843 | mean satisfaction 66.1 ease_of_use 3.82 support_quality 2.37 price_fairness 1.51 wait_time -0.90
Step 2 · Results figures¶
A coefficient plot ranks the drivers by impact, and a scatter shows the strongest one against satisfaction. Both carry titles that state the finding.
fig_coef, ax = plt.subplots(figsize=(7.4, 3.0))
cvals = coefs.sort_values()
ax.barh(cvals.index.str.replace("_"," "), cvals.values, color=[RD if v<0 else ROSE for v in cvals.values])
ax.axvline(0, color=INK, lw=1); ax.set_title("Ease of use is the biggest driver; long waits hurt")
ax.set_xlabel("points of satisfaction per 1-point change in driver"); ax.grid(axis="y", visible=False)
plt.tight_layout(); plt.show()
fig_sc, ax = plt.subplots(figsize=(6.6, 3.2))
ax.scatter(survey.ease_of_use, survey.satisfaction, s=14, alpha=0.4, color=ROSE)
b0, b1 = np.polyfit(survey.ease_of_use, survey.satisfaction, 1)
xs = np.array([survey.ease_of_use.min(), survey.ease_of_use.max()])
ax.plot(xs, b0*xs+b1, color=INK, lw=2)
ax.set_title("Satisfaction rises steadily with ease of use"); ax.set_xlabel("ease of use (1-10)"); ax.set_ylabel("satisfaction (0-100)")
plt.tight_layout(); plt.show()
Step 3 · The coefficient table for the report¶
The regression table with standard errors and p-values is the backbone of the written study. Display it cleanly here; the statistician reproduces it in the document and explains, in plain words, what each number means.
tbl = pd.DataFrame({"Driver":[i.replace("_"," ") for i in model.params.index],
"Coefficient":[f"{v:+.2f}" for v in model.params.values],
"Std. error":[f"{v:.2f}" for v in model.bse.values],
"p-value":[f"{v:.3f}" for v in model.pvalues.values]})
tbl
| Driver | Coefficient | Std. error | p-value | |
|---|---|---|---|---|
| 0 | Intercept | +28.03 | 1.33 | 0.000 |
| 1 | ease of use | +3.82 | 0.12 | 0.000 |
| 2 | support quality | +2.37 | 0.11 | 0.000 |
| 3 | price fairness | +1.51 | 0.11 | 0.000 |
| 4 | wait time | -0.90 | 0.11 | 0.000 |
From analysis to report¶
This notebook is the analysis: the regression, the coefficient table, and the two figures. The finished study is a separate, human-authored Word document, a statistician writing for a non-technical reader, who states the headline first, translates the coefficients into plain terms (each point of ease of use is worth about 3.8 points of satisfaction), and is honest that survey associations are not proven causes. Download it from the chapter to see the write-up these numbers deserve.