←Back to the bookChapter 108 · Take the quiz & aim for 100%
▶ Open in Colab⬇ Download notebookView on GitHub
Chapter 108 · Solutions
What Is Machine Learning? · Solutions
Five challenges, each verified in code.

Chapter 108 · Practice Challenges, Solved¶

Five short exercises on the ML taxonomy, each worked in scikit-learn on the Chapter 108 customer table. Try them yourself first, then compare.

In [1]:
import numpy as np, pandas as pd, matplotlib.pyplot as plt
import seaborn as sns   # seaborn = high-level statistical plots (heatmaps, pairplots, count/bar plots)
from matplotlib.colors import ListedColormap
EM="#4338ca"; DEEP="#3730a3"; LIGHT="#c7d2fe"; INK="#1a2138"; GRID="#e6e9f2"; RED="#ef4444"; AMBER="#d97706"; GREEN="#059669"; BLUE="#2563eb"; PUR="#9333ea"; GREY="#94a3b8"; SLATE="#475569"; ORG="#4338ca"; CYAN="#0891b2"
plt.rcParams.update({"figure.facecolor":"white","axes.facecolor":"white","figure.dpi":110,"font.size":11,
   "axes.edgecolor":GRID,"axes.grid":True,"grid.color":GRID,"axes.axisbelow":True,"axes.spines.top":False,
   "axes.spines.right":False,"axes.titlesize":12,"axes.titleweight":"bold","legend.frameon":False})
sns.set_style("whitegrid")
BASE="https://raw.githubusercontent.com/johnfisher-ai/Statistics-Data-Science-AI-Visual-Book/main/data/"
In [2]:
import warnings; warnings.filterwarnings('ignore')
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.linear_model import LogisticRegression, LinearRegression
from sklearn.cluster import KMeans
from sklearn.metrics import accuracy_score, r2_score
import statsmodels.api as sm
try: df = pd.read_excel('../../data/what-is-machine-learning--customers.xlsx', sheet_name='Data')
except FileNotFoundError: df = pd.read_excel(BASE + 'what-is-machine-learning--customers.xlsx', sheet_name='Data')
feat = ['age','income_k','tenure_months','num_products','monthly_spend','support_calls']
print('loaded', df.shape)
loaded (600, 8)
CHALLENGE 1
Features vs label
Separate the input features from the target and print the shape of each.
In [3]:
X = df[feat]; y = df['churned']
print('features X:', X.shape, '->', feat)
print('label y   :', y.shape, '-> churned (', y.nunique(), 'classes:', sorted(y.unique()), ')')
features X: (600, 6) -> ['age', 'income_k', 'tenure_months', 'num_products', 'monthly_spend', 'support_calls']
label y   : (600,) -> churned ( 2 classes: [np.int64(0), np.int64(1)] )

Solution. X is the 600×6 feature matrix, y is the 600-length binary label. A present label means this is a supervised problem.

CHALLENGE 2
Train/test split and accuracy
Hold out 30%, train a logistic classifier, and report test accuracy.
In [4]:
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.30, random_state=1, stratify=y)
clf = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000)).fit(X_tr, y_tr)
print(f'test accuracy = {accuracy_score(y_te, clf.predict(X_te)):.1%}  (on {len(X_te)} unseen customers)')
test accuracy = 75.6%  (on 180 unseen customers)

Solution. Standardize then fit; accuracy is measured only on the held-out 30%, the honest estimate of performance on new customers.

CHALLENGE 3
Regression instead of classification
Predict a number, monthly spend, and report the test R-squared.
In [5]:
Xr = df[['age','income_k','num_products','tenure_months']]; yr = df['monthly_spend']
Xr_tr, Xr_te, yr_tr, yr_te = train_test_split(Xr, yr, test_size=0.30, random_state=1)
reg = LinearRegression().fit(Xr_tr, yr_tr)
print(f'test R2 = {reg.score(Xr_te, yr_te):.3f}')
test R2 = 0.795

Solution. Same supervised recipe, but a continuous target makes it regression; R-squared (not accuracy) is the natural score.

CHALLENGE 4
Cluster into segments
Run K-means with k=3 on the numeric features and print the mean income and spend per cluster.
In [6]:
Xu = StandardScaler().fit_transform(df[['income_k','monthly_spend','num_products','tenure_months']])
df['segment'] = KMeans(n_clusters=3, n_init=10, random_state=0).fit_predict(Xu)
print(df.groupby('segment')[['income_k','monthly_spend','num_products']].mean().round(1).to_string())
         income_k  monthly_spend  num_products
segment                                       
0            39.6           43.5           1.5
1           101.2           89.6           4.5
2            73.3           61.0           2.4

Solution. No label is used; the three clusters line up with low / middle / high income and spend, the budget, standard, and premium segments.

CHALLENGE 5
Explain vs predict
Use statsmodels to find the strongest churn predictor, then cross-validate for a prediction score.
In [7]:
Xc = sm.add_constant(StandardScaler().fit_transform(df[feat]))
logit = sm.Logit(df['churned'], Xc).fit(disp=0)
coefs = pd.Series(logit.params.values[1:], index=feat)
print('strongest predictor by |standardized coef|:', coefs.abs().idxmax(), f'({coefs[coefs.abs().idxmax()]:+.2f})')
cv = cross_val_score(make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000)), df[feat], df['churned'], cv=5)
print(f'5-fold accuracy = {cv.mean():.1%}')
strongest predictor by |standardized coef|: tenure_months (-1.18)
5-fold accuracy = 79.3%

Solution. Statistics reads the coefficients (short tenure has the largest standardized coefficient, with income and support calls also significant, all of them associations in observational data); ML reports a cross-validated accuracy, explanation versus generalization on one model.


Statistics, Data Science and AI: A Visual Handbook · © 2026 John Fisher
←Back to the bookChapter 108 · Take the quiz & aim for 100%