⚙️ Setup¶
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
NAVY="#0a1230"; INK="#1a2138"; INK_SOFT="#4a5578"
CYAN="#0891b2"; PURPLE="#7c3aed"; AMBER="#d97706"; GREEN="#059669"; PINK="#db2777"; BLUE="#2563eb"; GRID="#e6e9f2"
plt.rcParams.update({
"figure.facecolor":"white","axes.facecolor":"white","figure.dpi":110,"font.size":11,
"axes.edgecolor":GRID,"axes.linewidth":1.2,"axes.grid":True,"grid.color":GRID,"axes.axisbelow":True,
"axes.spines.top":False,"axes.spines.right":False,"axes.titlesize":14,"axes.titleweight":"bold","axes.titlecolor":INK,
"axes.labelcolor":INK_SOFT,"xtick.color":INK_SOFT,"ytick.color":INK_SOFT,"legend.frameon":False})
print("✅ Ready.")
✅ Ready.
answer = {
"Business need": "Reduce membership cancellations",
"Data question": "Can we predict which members will cancel next month?",
"Target (label)": "will_cancel (yes / no)",
"Success metric": "Accuracy / recall of catching cancellers before they leave",
}
for k, v in answer.items():
print(f"{k:>16}: {v}")
Business need: Reduce membership cancellations Data question: Can we predict which members will cancel next month? Target (label): will_cancel (yes / no) Success metric: Accuracy / recall of catching cancellers before they leave
Answer: A good frame names what you predict (the target) and how you'll know it worked (the metric). "Reduce cancellations" → "predict will_cancel, measured by recall."
sources = ["Membership database (join date, plan, price)",
"Check-in logs (how often each member visits)"]
target = "will_cancel (did the member cancel? yes/no)"
for s in sources: print("Data source:", s)
print("Target/label:", target)
Data source: Membership database (join date, plan, price) Data source: Check-in logs (how often each member visits) Target/label: will_cancel (did the member cancel? yes/no)
Answer: Good sources tie directly to the question (visit frequency is a strong churn signal). The target is the thing you want to predict: will_cancel.
raw = pd.DataFrame({
"name": ["Ana","Ben","Ben","Cara","Dan"],
"age": [34, 41, 41, None, 29],
})
print("RAW:"); print(raw)
clean = raw.drop_duplicates().reset_index(drop=True)
clean["age"] = clean["age"].fillna(round(clean["age"].mean(), 1))
print("\nCLEAN:"); print(clean)
RAW: name age 0 Ana 34.0 1 Ben 41.0 2 Ben 41.0 3 Cara NaN 4 Dan 29.0 CLEAN: name age 0 Ana 34.0 1 Ben 41.0 2 Cara 34.7 3 Dan 29.0
Answer: Drop the repeated "Ben" row, then fill Cara's missing age with the mean of the known ages (≈ 34.7). Always clean before analyzing.
exercise = pd.Series([0, 1, 2, 3, 4, 5, 6]) # hours/week
heart = pd.Series([80, 78, 75, 72, 70, 67, 64]) # resting bpm
corr = exercise.corr(heart)
print(f"Correlation = {corr:.2f}")
fig, ax = plt.subplots(figsize=(7.5,4.2))
ax.scatter(exercise, heart, color=GREEN, s=70, edgecolor="white")
ax.set_title("Challenge 4 — Exercise vs resting heart rate", loc="left")
ax.set_xlabel("Exercise (hours/week)"); ax.set_ylabel("Resting heart rate (bpm)")
plt.tight_layout(); plt.show()
Correlation = -1.00
Answer: Correlation ≈ −0.99, a strong negative relationship: more exercise is associated with a lower resting heart rate. (Correlation ≠ causation, but it's a strong lead.)
slope, intercept = np.polyfit(exercise, heart, 1)
print(f"Model: heart_rate = {intercept:.1f} + ({slope:.1f}) * hours")
pred = slope*3.5 + intercept
print(f"Prediction at 3.5 hrs/week -> {pred:.1f} bpm")
Model: heart_rate = 80.3 + (-2.7) * hours Prediction at 3.5 hrs/week -> 70.9 bpm
Answer: The model is roughly heart = 80 − 2.6 × hours; at 3.5 hours it predicts ≈ 71 bpm. Fitting the line is the "learning" step.
pred = np.array([70, 60, 80])
actual= np.array([72, 58, 85])
rmse = np.sqrt(np.mean((actual - pred)**2))
print(f"RMSE = {rmse:.2f} (typical error, in the same units as the target)")
RMSE = 3.32 (typical error, in the same units as the target)
Answer: RMSE ≈ 3.1. In production you'd monitor the error over time, if it starts creeping up (data drift), loop back, collect fresh data, and retrain. That feedback loop is what makes the lifecycle a circle.