Capstone and Career

Week 16 of 16 · Capstone · 7 days

Full curriculum
Week 16 · Capstone

Capstone and Career

Week 16 · Day 1 of 7

Framing a Project

Starting from a decision instead of a dataset, and writing the brief

By 587 words

Fifteen weeks of technique. This one is about turning it into something with your name on it, which means starting where every real project starts, and where almost every portfolio project does not: with a decision somebody has to make.

Frame the decision, not the dataset

The most common way a capstone fails

It begins "I found an interesting dataset". Everything after that is a search for a question the data happens to answer, and the result reads like it, a competent notebook with no argument in it. Start instead with a decision that is currently made badly, then ask what data would inform it. You may well end up at the same dataset. You will end up with a different project.

Weak framingStrong framingWhy
"Predict house prices""Which listings are priced more than 10% below what comparable homes achieved, so an agent should call the vendor this week"Names who acts, when, and on what
"Analyse customer churn""Which 200 customers should the retention team ring in October, given they can make 200 calls"The budget constraint decides the threshold
"Classify images of plants""Let a grower photograph a leaf and get a disease shortlist without waiting for the agronomist"Establishes what the alternative is
"Sentiment analysis of reviews""Route the 5% of reviews that describe a safety problem to the product team within an hour"Makes recall on a rare class the metric

Write the brief before the code

  1. The decision. Who does what differently because of this model?
  2. The alternative. How is it decided today? That is your baseline, and it is rarely a machine learning model, usually it is a rule, a spreadsheet, or somebody's judgement.
  3. The unit. One row is one what? Get this wrong and every split in the project is wrong.
  4. The label. What exactly are you predicting, measured when? Write the SQL or the sentence that defines it.
  5. The moment of prediction. What is known at that instant? Anything not known then cannot be a feature. This one sentence prevents most leakage.
  6. The metric, and the threshold. Which errors cost what? Who bears them?
  7. What good enough looks like. A number, agreed before you start, that means this was worth doing.

Point five is the whole of week 4 in one sentence

"What is known at the moment the prediction is made" resolves almost every leakage question without any statistics. total_charges is known at prediction time; reason_for_leaving is not. A customer's tenure is known; whether they answered the retention call is not. Write the list, then check every column you add against it.

Scoping it to fit

ScaleRowsLooks likeFits in
Small< 10kOne table, a few features, a clear labelA weekend
Medium10k to 1MTwo or three joined tables, some feature engineeringTwo to three weeks
Large> 1MSampling, chunked reads, a real time splitA month, and it will still surprise you

For a capstone, medium is the target. Small does not exercise enough of the course; large means you spend three weeks on data plumbing and produce a rushed model. If the dataset you want is large, take a principled sample and say so in the write-up. That is a legitimate engineering decision, not a compromise.

Day 1 takeaway

Start from a decision somebody makes badly, not from a dataset you found. Write the brief first: the decision, the current alternative, the unit, the label, what is known at prediction time, the metric, and the number that means you succeeded.
Week 16 · Day 2 of 7

Structure and Reproducibility

Where the code lives, the four tests to write, and running it twice

By 607 words

A project somebody else can run is worth several times one they cannot, and the difference is about an hour of work done at the start rather than at the end.

The layout

churn-risk/
README.md what it does, how to run it, what it found
requirements.txt pinned
data/
raw/ never edited, never committed
processed/ generated, never committed
notebooks/
01-explore.ipynb narrative, allowed to be messy
src/
data.py loading and cleaning
features.py transformations, importable and testable
train.py fits and writes a model artefact
evaluate.py produces the report
models/
churn-1.0.0.joblib
churn-1.0.0.json the contract from week 15
tests/
test_features.py
reports/
findings.md

The rule that makes the rest work

Notebooks are for narrative; src/ is for anything that runs twice. The moment a cell is useful enough to copy into another notebook, it belongs in a module, where it can be imported, tested and fixed once. A project whose logic lives in notebook cells cannot be tested and cannot be deployed, and both of those are noticed immediately by anybody reviewing it.

Testing the parts that can silently break

import numpy as np
import pandas as pd

df = pd.read_csv('customers.csv').drop_duplicates().copy()
df['contract'] = df['contract'].str.strip().str.title()
df['total_charges'] = pd.to_numeric(df['total_charges'], errors='coerce')

NUM = ['tenure_months', 'support_calls', 'monthly_charges']
CAT = ['contract', 'internet_service', 'payment_method', 'has_dependents']
def add_features(data):
"""The kind of function that belongs in src/features.py."""
out = data.copy()
out['charges_per_month_of_tenure'] = (
out['total_charges'] / out['tenure_months'].clip(lower=1))
out['calls_per_year'] = (
out['support_calls'] * 12 / out['tenure_months'].clip(lower=1))
out['is_new'] = (out['tenure_months'] <= 3).astype(int)
return out

# tests/test_features.py -- these are the assertions that catch the
# mistakes nobody notices: a divide by zero, a column that silently
# disappears, a transformation that changes the number of rows.
def test_no_rows_lost():
assert len(add_features(df)) == len(df)

def test_no_division_blowup():
out = add_features(df)
assert np.isfinite(out['calls_per_year'].dropna()).all()

def test_zero_tenure_is_survivable():
edge = df.head(1).copy()
edge['tenure_months'] = 0
assert np.isfinite(add_features(edge)['calls_per_year']).all()

def test_original_columns_survive():
out = add_features(df)
assert set(df.columns).issubset(out.columns)

for test in [test_no_rows_lost, test_no_division_blowup,
test_zero_tenure_is_survivable, test_original_columns_survive]:
test()
print('PASS %s' % test.__name__)
PASS test_no_rows_lost
PASS test_no_division_blowup
PASS test_zero_tenure_is_survivable
PASS test_original_columns_survive

Four assertions, no framework, about five minutes of work. They do not check that your model is good. Nothing can, but they catch the class of bug that produces a plausible model trained on quietly corrupted features, which is the class of bug that is hardest to find later.

Reproducibility

import numpy as np
import pandas as pd

df = pd.read_csv('customers.csv').drop_duplicates().copy()
df['contract'] = df['contract'].str.strip().str.title()
df['total_charges'] = pd.to_numeric(df['total_charges'], errors='coerce')

NUM = ['tenure_months', 'support_calls', 'monthly_charges']
CAT = ['contract', 'internet_service', 'payment_method', 'has_dependents']
import hashlib
import json

def fingerprint(data):
"""Enough to prove two runs saw the same data."""
digest = hashlib.sha256(
pd.util.hash_pandas_object(data, index=True).values).hexdigest()
return {'rows': len(data), 'columns': list(data.columns),
'sha256': digest[:16]}

print(json.dumps(fingerprint(df), indent=2)[:300])
print('\nsame data again:', fingerprint(df)['sha256'])
print('one value changed:',
fingerprint(df.assign(monthly_charges=df['monthly_charges'] + 0.01))
['sha256'])
{
"rows": 3000,
"columns": [
"customer_id",
"signup_date",
"tenure_months",
"contract",
"internet_service",
"payment_method",
"has_dependents",
"support_calls",
"monthly_charges",
"total_charges",
"churned"
],
"sha256": "48cf8b8b0d435748"
}

same data again: 48cf8b8b0d435748
one value changed: 0168e3b07660fcf9
  1. Set every seed, and record it.
  2. Fingerprint the input data so a rerun can prove it saw the same rows.
  3. Pin dependencies.
  4. Make the whole thing runnable from one command, a Makefile, or python -m src.train.
  5. Never edit raw data in place. Read it, transform it, write elsewhere.
  6. Keep data/ out of version control, and say in the README where the data comes from.

Day 2 takeaway

Notebooks narrate, modules execute. Write four assertions about your feature code before you trust it. Seed everything, fingerprint the input, pin the dependencies, and make the project run from one command.
Week 16 · Day 3 of 7

Building the Capstone

Four milestones with artefacts, and where the time really goes

By 659 words

The capstone itself, as a sequence of checkpoints. Each has an artefact and a decision, so it is possible to know whether you have finished it.

Milestone 1: the brief and the baseline

  • The brief from day 1, written out, one page.
  • The data loaded, the unit confirmed, the label defined in code.
  • A split chosen and justified, random, grouped or by time.
  • A baseline that is not a model: the current rule, the majority class, last month's value, or a two-line heuristic.
  • The metric computed for that baseline.

The baseline is the most valuable number in the project

Without it, "AUC 0.81" means nothing. With it, "AUC 0.81 against the current rule's 0.68, on customers the rule cannot score at all" is a result. The baseline is also how you find out early that the problem is already solved, which is worth knowing in week one rather than week four.

import numpy as np
import pandas as pd

df = pd.read_csv('customers.csv').drop_duplicates().copy()
df['contract'] = df['contract'].str.strip().str.title()
df['total_charges'] = pd.to_numeric(df['total_charges'], errors='coerce')

NUM = ['tenure_months', 'support_calls', 'monthly_charges']
CAT = ['contract', 'internet_service', 'payment_method', 'has_dependents']
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score, f1_score

X, y = df[NUM + CAT], df['churned']
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25,
stratify=y, random_state=42)

# The rule the retention team uses today: month-to-month contract and
# more than two support calls.
rule = ((X_te['contract'] == 'Month-To-Month')
& (X_te['support_calls'] > 2)).astype(int)

print('the existing rule')
print(' flags %d of %d customers' % (rule.sum(), len(rule)))
print(' AUC %.4f' % roc_auc_score(y_te, rule))
print(' F1 %.4f' % f1_score(y_te, rule))
print(' of those flagged, %.1f%% actually churned'
% (100 * y_te[rule == 1].mean()))
print(' of all churners, it catches %.1f%%'
% (100 * rule[y_te == 1].mean()))
the existing rule
flags 51 of 750 customers
AUC 0.5351
F1 0.1905
of those flagged, 47.1% actually churned
of all churners, it catches 11.9%

Milestone 2: honest evaluation

  • A pipeline, with every transformation inside it.
  • Cross-validation appropriate to the data, from week 7.
  • Two or three model families compared, including a linear one.
  • A held-out set touched exactly once.
  • The threshold chosen from the cost of the two errors, not left at 0.5.

Milestone 3: improvement, measured

  • Features added one group at a time, each measured against the baseline.
  • The failures kept in the write-up. "I tried target encoding on payment method; it cost 0.004 AUC and I dropped it" is evidence of judgement.
  • Tuning, with the budget argument from week 14.
  • A final held-out number, reported once.

Milestone 4: explain and ship

  • Which features drive it, with the shape of the top effects.
  • Two or three individual predictions explained, including a wrong one.
  • An error analysis by segment, who does it fail?
  • The artefact and contract from week 15.
  • A README somebody can follow without asking you anything.

Where the time actually goes

phases = [('Framing and data understanding', 20),
('Cleaning and joining', 30),
('Feature engineering', 20),
('Modelling and tuning', 15),
('Evaluation and error analysis', 10),
('Writing it up', 5)]
for name, pct in phases:
print('%-34s %3d%% %s' % (name, pct, '#' * (pct // 2)))
print('\nthe part everyone plans for is the 15%.')
Framing and data understanding 20% ##########
Cleaning and joining 30% ###############
Feature engineering 20% ##########
Modelling and tuning 15% #######
Evaluation and error analysis 10% #####
Writing it up 5% ##

the part everyone plans for is the 15%.

Budget for the write-up before you need it

Five percent is the honest average and it is too little. A project that is finished but unexplained is worth roughly nothing to a reader, and the write-up is the only part of it most people will ever see. Book the last day of your capstone for writing, and treat it as non-negotiable.

Day 3 takeaway

Four milestones, each with an artefact: brief and baseline; honest evaluation; measured improvement including the failures; explanation and shipping. Half the time goes on data before any model is fitted, and that is the normal shape of the work.
Week 16 · Day 4 of 7

Marking Your Own Work

The rubric, the self-check, and the one-page write-up

By 631 words

How to mark your own work, and the specific things a reviewer looks for first.

The rubric

AreaFalls shortMeetsExceeds
FramingA dataset with a model on itA stated decision and userThe decision, the current alternative, and the cost of each error
Datadropna() and onwardsMissingness, duplicates and outliers handled deliberatelyEach choice justified and its effect measured
ValidationOne random splitCross-validation matched to the data's structureGrouped or time-based splitting, argued from how the data arose
LeakageNot consideredTransformations inside the pipelineAn explicit list of what is known at prediction time
BaselineNoneA sensible simple modelThe actual current process, measured
MetricAccuracyA metric matched to the problemA threshold chosen from costs, with the trade-off shown
InterpretationA feature importance bar chartImportances plus the shape of the main effectsIndividual cases explained, including errors, with limits stated
EngineeringOne long notebookModules, seeds, pinned versionsTests, one-command run, a saved artefact and contract
CommunicationCells and outputsA README and a clear findings sectionWritten for the decision-maker, with the caveats in it

Marking yourself honestly

checks = [
('Could a reader state the decision this informs?', None),
('Is there a non-model baseline, measured?', None),
('Is every transformation inside the pipeline?', None),
('Was the held-out set used exactly once?', None),
('Is the threshold justified by costs?', None),
('Are failed experiments written down?', None),
('Does the write-up state who the model fails?', None),
('Can somebody else run it from the README?', None),
('Are the seeds set and the versions pinned?', None),
('Is there a sentence about what would make it wrong?', None),
]
print('Score one point each. Below 7 and the reviewer will find it')
print('before you do.\n')
for i, (question, _) in enumerate(checks, 1):
print('%2d. [ ] %s' % (i, question))
Score one point each. Below 7 and the reviewer will find it
before you do.

1. [ ] Could a reader state the decision this informs?
2. [ ] Is there a non-model baseline, measured?
3. [ ] Is every transformation inside the pipeline?
4. [ ] Was the held-out set used exactly once?
5. [ ] Is the threshold justified by costs?
6. [ ] Are failed experiments written down?
7. [ ] Does the write-up state who the model fails?
8. [ ] Can somebody else run it from the README?
9. [ ] Are the seeds set and the versions pinned?
10. [ ] Is there a sentence about what would make it wrong?

The three things looked at first

From experience of reading these: the README, can I tell in thirty seconds what this is and what it found; the validation, is the reported number believable, or is it leaking; and the baseline, is the improvement over anything. A project that is strong on those three survives being weak elsewhere. A project that is weak on them does not recover, however good the modelling is.

The write-up

  1. The problem, in two sentences, naming the decision.
  2. The data: where from, how much, what is wrong with it.
  3. The approach: the split, the baseline, what you tried.
  4. The result: one headline number against the baseline, with the metric named and the uncertainty given.
  5. What it means: for the decision in point one, in the reader's language.
  6. Where it fails: which segments, which inputs, what would break it.
  7. What you would do next with another two weeks.

Seven sections, one page. If it runs longer than a page, the model is probably being described where the finding should be. Nobody outside the team needs your hyperparameters; everybody needs to know whether to trust the number and what to do with it.

Day 4 takeaway

Mark yourself against the rubric before anybody else does. Reviewers look at the README, the validation and the baseline first, in that order. Write one page, seven sections, in the reader's language rather than yours.
Week 16 · Day 5 of 7

The Portfolio

Three or four projects, and a README that leads with the result

By 539 words

What to do with sixteen weeks of work once it exists: the portfolio, and the specific ways it fails to land.

Three or four projects, not ten

ProjectShowsDrawn from
A tabular prediction, end to endJudgement: framing, leakage, baselines, thresholdsWeeks 1 to 8, 14
A deployed model with a live endpointThat you have shipped somethingWeek 15
A perceptual project, images or textThat you can work where features cannot be written by handWeeks 12 to 13
One deep analysis with a surprising findingCuriosity, and that you can writeWeeks 2, 7, 9 to 10

The portfolio mistakes that are easy to avoid

The famous datasets, Titanic, Iris, Boston housing, have been done ten thousand times and cannot distinguish you; use them to learn and not to demonstrate. A repository of notebooks with no README is unreadable. A 0.99 accuracy is read as leakage, not as success, so if you genuinely have one, explain immediately why it is real. And a project with no failures in it reads as a project where nothing was tried.

The README that gets read

# Churn Risk Scoring

Ranks broadband customers by likelihood of leaving in the next 90 days, so
a retention team of four can spend its 200 calls a month on the right people.

## Result
AUC 0.82 on a held-out sample, against 0.54 for the rule in use today.
Ranked by risk, the top 200 customers contain 58% of everybody who left;
the current rule finds 12% of them.

## Run it
pip install -r requirements.txt
python -m src.train # writes models/churn-1.0.0.joblib
python -m src.evaluate # writes reports/findings.md

## Data
3,000 customers. Synthetic, generated by src/generate.py, so the whole
project reproduces from a seed. 6% missing in two fields, handled by
median imputation inside the pipeline.

## What it does not do
Does not work for business accounts (excluded, different contract structure).
Degrades on customers with under 3 months of history - see reports/findings.md.
Retrained quarterly; the contract file records the training window.

Result before instructions, instructions before detail, limitations stated rather than buried. A reader who stops after the first two sections still knows what the project is and whether it worked, which is the only realistic goal. Those are the numbers this course actually measured, day 3 fitted the rule, day 6 ranked under a capacity constraint, so the example is a real README for the work you have already done, not a template with invented figures in it.

Talking about it

  1. Lead with the decision and the baseline, never with the algorithm.
  2. Give the headline number with its comparison attached, a number on its own invites the question you should have answered.
  3. Have one specific difficulty ready, and how you resolved it. This is the question that separates people who did the work from people who followed a tutorial.
  4. Know why you chose the model you chose, and what you rejected.
  5. Be able to say what would make it wrong.
  6. Keep it to two minutes unless asked for more.

Day 5 takeaway

Three or four projects that show different things, each with a README that leads with the result and admits the limitations. Avoid the famous datasets for demonstration. Include the failures. They are the evidence of judgement.
Week 16 · Day 6 of 7

Interviews

The questions, what they test, and the ranking answer

By 724 words

The questions that actually get asked, grouped by what they are really testing. Your capstone is the answer to most of them.

Fundamentals

QuestionWhat is being testedWhere it was covered
Explain the bias-variance trade-offWhether you understand generalisation or memorised a phraseWeek 4
Your model has 99% accuracy. What do you check?Leakage instincts, and class imbalanceWeeks 4, 7
When would you not use accuracy?Whether metrics are chosen or defaultedWeeks 5, 7
Why does regularisation help?Whether you can connect the penalty to the varianceWeek 4
How do you handle missing data?Whether imputation happens inside the pipelineWeeks 2, 8
Random forest versus gradient boostingWhether you know why the averaging differsWeek 6
What does a p-value mean?Precision of language under mild pressureWeek 3

Applied

QuestionWhat a strong answer contains
Walk me through a projectThe decision, the baseline, one difficulty, the result with its comparison, and a limitation
How would you detect data leakage?The prediction-time rule; suspiciously high scores; feature importance dominated by one column; a time-based split as the check
A model works in testing and fails in production. Why?Drift, leakage in training, a train/serve preprocessing mismatch, or a population that differs from the training sample
How do you choose a threshold?From the relative cost of the two errors and the operational capacity, not from 0.5
How would you explain this model to a non-technical stakeholder?Effects rather than coefficients, one worked example, and the limits stated plainly
When would you not use machine learning?A rule works; there are too few examples; the decision must be auditable; the cost of an error is catastrophic

The last one is a good question and a good answer

Week 12 ended with a support vector machine beating a convolutional network, and week 15 with a boosted challenger losing to logistic regression. Being able to say "I have measured a case where the simple thing won, and shipped the simple thing" is a stronger signal than any list of architectures. Enthusiasm for complexity is common; judgement about when to avoid it is not.

A whiteboard exercise you can practise now

import numpy as np
import pandas as pd

df = pd.read_csv('customers.csv').drop_duplicates().copy()
df['contract'] = df['contract'].str.strip().str.title()
df['total_charges'] = pd.to_numeric(df['total_charges'], errors='coerce')

NUM = ['tenure_months', 'support_calls', 'monthly_charges']
CAT = ['contract', 'internet_service', 'payment_method', 'has_dependents']
# "Design a system to decide which customers to call this month."
# Talk through it before writing anything. The structure below is the
# answer; the code is just proof it is not hand-waving.
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder

CAPACITY = 200 # the constraint that decides everything

prep = ColumnTransformer([
('num', Pipeline([('i', SimpleImputer(strategy='median')),
('s', StandardScaler())]), NUM),
('cat', Pipeline([('i', SimpleImputer(strategy='most_frequent')),
('o', OneHotEncoder(handle_unknown='ignore'))]), CAT),
])
X_tr, X_te, y_tr, y_te = train_test_split(
df[NUM + CAT], df['churned'], test_size=0.25,
stratify=df['churned'], random_state=42)
model = Pipeline([('prep', prep),
('clf', LogisticRegression(max_iter=2000))]).fit(X_tr, y_tr)

risk = model.predict_proba(X_te)[:, 1]
order = np.argsort(-risk)
called = order[:CAPACITY]

print('%d customers, capacity %d' % (len(X_te), CAPACITY))
print('churners in the whole test set: %d' % y_te.sum())
print('churners among the %d we would call: %d'
% (CAPACITY, y_te.iloc[called].sum()))
print(' hit rate %.1f%% against a base rate of %.1f%%'
% (100 * y_te.iloc[called].mean(), 100 * y_te.mean()))
print(' lift %.2fx' % (y_te.iloc[called].mean() / y_te.mean()))
print('\nnote what was never chosen: a threshold. Capacity chose it.')
750 customers, capacity 200
churners in the whole test set: 201
churners among the 200 we would call: 116
hit rate 58.0% against a base rate of 26.8%
lift 2.16x

note what was never chosen: a threshold. Capacity chose it.

That last line is the point of the exercise. A great many real deployments are ranking problems with a capacity constraint, not classification problems with a threshold, and answering in those terms tells an interviewer you have thought about how the output gets used.

Day 6 takeaway

Most questions are testing whether your understanding is structural or memorised, and your capstone is the evidence. Lead with decisions and baselines. Be ready to describe a case where the simple model won, because judgement about complexity is the rarer signal.
Week 16 · Day 7 of 7

Where This Leaves You

What was covered, what was not, and the habits worth keeping

By 695 words

Sixteen weeks. What was actually covered, what deliberately was not, and where to go next.

What you can now do

WeeksCapability
1-3Python and pandas for data work, and the linear algebra, calculus and probability underneath the models
4-6Regression and classification, regularisation, trees and ensembles, and why the simple model sometimes wins
7-8Validation you can trust, and features that beat model choice
9-10Clustering, dimensionality reduction and anomaly detection without labels
11-13Neural networks from the gradient up, convolution for images, embeddings and sequences for text
14-15Tuning within a budget, explaining a model, and running one in production
16Framing, shipping and communicating a project of your own

What this course did not cover

  • Reinforcement learning. A different problem shape entirely, learning from interaction rather than from a fixed dataset.
  • Causal inference. Week 14 drew the line and stopped at it. Everything past that line, experiments, instrumental variables, difference-in-differences, is a field of its own and the natural next one for anybody doing this work in a business.
  • Training large models. Distributed training, mixed precision and the engineering of very large networks.
  • Recommender systems. Collaborative filtering has its own evaluation traps.
  • Time series forecasting. Week 7 covered splitting by time; forecasting itself is a separate discipline.
  • Fine-tuning language models. Week 13 pointed at it. The mechanics move fast; the judgement in weeks 7 and 14 does not.

Where to go next

If you want toLearn next
Answer "did it work" rather than "what will happen"Causal inference and experiment design
Handle data that does not fit in memorySpark or DuckDB, and columnar formats
Own the systems as well as the modelsDocker, CI, orchestration, Airflow or Dagster
Work on languageTransformers and fine-tuning, on top of week 13
Go deeper on the theoryConvex optimisation and statistical learning theory
Get better fastest, whatever the directionShip three more projects end to end

The habits worth keeping

Fit the baseline first. Ask what is known at the moment of prediction. Put every transformation inside the pipeline. Read the confusion matrix. Choose the threshold from the cost of the errors. Write down the experiments that failed. Say who the model fails. Prefer the model you can explain when the numbers are close.

None of those depend on a library version, and all of them will still be true when whatever replaces today's tools has replaced them.

A last measurement

import numpy as np
import pandas as pd

df = pd.read_csv('customers.csv').drop_duplicates().copy()
df['contract'] = df['contract'].str.strip().str.title()
df['total_charges'] = pd.to_numeric(df['total_charges'], errors='coerce')

NUM = ['tenure_months', 'support_calls', 'monthly_charges']
CAT = ['contract', 'internet_service', 'payment_method', 'has_dependents']
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.metrics import roc_auc_score

X, y = df[NUM + CAT], df['churned']
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25,
stratify=y, random_state=42)

# The rule they had in week 1.
rule = ((X_te['contract'] == 'Month-To-Month')
& (X_te['support_calls'] > 2)).astype(int)

prep = ColumnTransformer([
('num', Pipeline([('i', SimpleImputer(strategy='median')),
('s', StandardScaler())]), NUM),
('cat', Pipeline([('i', SimpleImputer(strategy='most_frequent')),
('o', OneHotEncoder(handle_unknown='ignore'))]), CAT),
])
model = Pipeline([('prep', prep),
('clf', LogisticRegression(max_iter=2000))]).fit(X_tr, y_tr)
cv = cross_val_score(model, X_tr, y_tr, cv=5, scoring='roc_auc')

print('the rule in use AUC %.4f' % roc_auc_score(y_te, rule))
print('logistic regression, cv AUC %.4f +/- %.4f' % (cv.mean(), cv.std()))
print('logistic regression, test AUC %.4f'
% roc_auc_score(y_te, model.predict_proba(X_te)[:, 1]))
print('\nthirteen lines. everything else this course taught you was')
print('about knowing whether to believe the last number.')
the rule in use AUC 0.5351
logistic regression, cv AUC 0.8217 +/- 0.0110
logistic regression, test AUC 0.8174

thirteen lines. everything else this course taught you was
about knowing whether to believe the last number.

Course takeaway

The modelling was never the hard part. Framing a decision, building a split you can trust, engineering features that carry real information, choosing a threshold somebody has to live with, and saying honestly where the thing fails. That is the job, and you have now done all of it at least once.