Predict Bank Subscriptions with Python
Build and evaluate a responsible bank subscription classifier in Python.
Introduction
30 Second Summary
A bank can contact thousands of clients. Only some will subscribe to a term deposit.
In this project, you will use Python to predict whether a bank client will subscribe before a marketing call. You will compare a weak baseline with two logistic regression models to produce a responsible recommendation.
What You'll Build
Running bank_campaign.py gives you a campaign recommendation backed by reusable model evidence in outputs.
By the end of this project, you'll have:
- A reproducible Python analysis you can run from bank_campaign.py. The terminal shows each stage of the evidence behind your campaign recommendation.
- A portfolio-ready evidence folder containing a target chart, a model metrics chart, a confusion matrix, and a CSV comparison.
- A responsible pre-call recommendation you can defend by comparing three models. You will show why subscriber recall exposes the baseline. You will also show why duration cannot support a pre-call decision.
- Secret Mission: Test three probability thresholds to choose a precision-recall trade-off for the campaign.
Are there any prerequisites?
You need basic Python syntax plus an internet connection for the dataset download. The setup step covers Visual Studio Code, the project-local .venv, and every pinned package.
Before We Start
A useful prediction starts with a clear decision. Before any hands-on work, lock in what your model predicts and why the bank needs that prediction before a marketing call.
Set Up a Reproducible Workspace
A classification project depends on the same package versions every time it runs. A project-local virtual environment keeps this project isolated from other Python projects on your computer.
In this step, you will prepare Visual Studio Code for a reproducible run on Windows. Your finished workspace will use Python 3.11 or newer with pinned dependencies inside .venv.
In this step, get ready to:
- Verify your Python version and install the Python extension for Visual Studio Code.
- Create a project-local environment with the pinned dependencies.
- Run a starter script that confirms every required import works.
Verify Python and prepare Visual Studio Code
Visual Studio Code uses the Python interpreter selected for the current workspace. You will begin with a named project folder so every file stays together.
- Press the Windows key to open Windows search.
- Type Visual Studio Code, then press Enter.
- Select the Accounts menu at the bottom of the Activity Bar.
- Select Backup and Sync Settings....
- Select Sign in.
- Choose your existing Microsoft or GitHub account in the browser.
Your account now appears in the Accounts menu. Settings Sync can preserve your extensions across computers.
- Select File from the top menu bar.
- Select Open Folder....
- Select your Desktop in the folder picker.
- Select New Folder.
- Enter bank-campaign-classifier as the folder name.
- Select Select Folder.
- Select Yes, I trust the authors if the Workspace Trust dialog appears.
The Explorer sidebar now shows bank-campaign-classifier as your open project folder.
The pinned packages require Python 3.11 or newer. The integrated terminal lets you check which version Windows currently finds.
- Press Ctrl+` to open the integrated terminal.
- Check the installed Python version by running this command:
python --version
What Does This Check?
The python command asks the active Windows installation to report its version. This project needs version 3.11 or newer because the pinned data science packages require it.
✔️ I see version 3.11 or higher
Your Python version meets the project requirement. Keep this Visual Studio Code window open for the next check.
ⓧ I see an older version
Windows is finding a Python version below 3.11. Install the current Python 3.14 release before creating the environment.
The installer can take a few minutes while Windows downloads the package. A progress indicator means the installation is still moving.
- Install Python 3.14 by running this command:
winget install Python.Python.3.14
What Does This Command Do?
The Windows package manager downloads the registered Python 3.14 installer. Reopening Visual Studio Code lets its terminal detect the new installation.
- Close Visual Studio Code after the installation completes.
- Press the Windows key to reopen Windows search.
- Type Visual Studio Code, then press Enter.
- Press Ctrl+` to reopen the integrated terminal.
- Check the updated Python version by running this command:
python --version
What Should This Confirm?
The reopened terminal should report Python 3.11 or newer. That result confirms Windows can supply a compatible interpreter to this workspace.
Still Seeing the Older Version?
Close every Visual Studio Code window before reopening the project. Windows may keep the previous command path active in terminals that were already running.
If the older version remains, restart Windows before checking again.
Help me make Visual Studio Code detect my newer Python installation.
ⓧ Command not found
Windows cannot currently find a Python installation. Install Python 3.14 before creating the project environment.
The installer can take a few minutes while Windows downloads the package. A progress indicator means the installation is still moving.
- Install Python 3.14 by running this command:
winget install Python.Python.3.14
What Does This Command Do?
The Windows package manager downloads the registered Python 3.14 installer. Reopening Visual Studio Code allows a fresh terminal to find it.
- Close Visual Studio Code after the installation completes.
- Press the Windows key to reopen Windows search.
- Type Visual Studio Code, then press Enter.
- Press Ctrl+` to reopen the integrated terminal.
- Confirm Python is available by running this command:
python --version
What Should This Confirm?
The reopened terminal should report Python 3.11 or newer. That result confirms the installation is available to this workspace.
Python Still Not Available?
Restart Windows if the installation finished but the terminal still cannot find Python. This refreshes the command paths available to new applications.
Help me fix a missing Python command after installing Python on Windows.
The Python extension for Visual Studio Code connects the editor to your interpreter. It supplies the environment creation and file-running actions used in this project.
- Press Ctrl+Shift+X to open the Extensions view.
- Enter Python in the extension search box.
- Select the Python extension published by Microsoft.
✔️ I see the Manage button
The Microsoft Python extension is already installed. The Manage button confirms it is available in Visual Studio Code.
ⓧ I see the Install button
- Select Install on the Python extension page.
The button changes to Manage when installation finishes. That change confirms the extension is ready.
Extension Installation Stuck?
Confirm your internet connection before retrying the installation. The extension downloads from the Visual Studio Marketplace.
Help me troubleshoot the Microsoft Python extension installation in Visual Studio Code.
Create the dependency environment
A pinned requirements file records the exact package versions used by the analysis. Anyone rebuilding the workspace can install the same set instead of relying on whatever versions are newest.
- Select the Explorer icon in the Activity Bar.
- Select the New File icon beside bank-campaign-classifier.
- Enter requirements.txt as the file name.
- Add the four pinned dependencies by pasting this content:
matplotlib==3.11.2
pandas==3.0.5
scikit-learn==1.9.1
ucimlrepo==0.0.7
What Do These Dependencies Provide?
- The matplotlib==3.11.2 pin supplies the charts saved by the analysis.
- The pandas==3.0.5 pin supplies the tables used to inspect data and store results.
- The scikit-learn==1.9.1 pin supplies the classifier and evaluation tools.
- The ucimlrepo==0.0.7 pin supplies the dataset download interface.
- Press Ctrl+S to save requirements.txt.
- Confirm that requirements.txt appears beneath bank-campaign-classifier in the Explorer sidebar.
File Missing From the Explorer?
Check that the Explorer sidebar is showing bank-campaign-classifier. Creating the file outside that folder prevents the environment command from finding it.
Help me place requirements.txt inside my Visual Studio Code project folder.
✔️ Awesome, I've got everything!
Your dependency pins are saved in requirements.txt.
ⓧ I'd like to double check the full code
Compare your complete requirements.txt file with this reference:
matplotlib==3.11.2
pandas==3.0.5
scikit-learn==1.9.1
ucimlrepo==0.0.7
Why Match This File Exactly?
Each line fixes one dependency to the tested project version. Matching all four lines gives the workspace a repeatable package set.
Visual Studio Code can now create .venv from the interpreter you verified. It also reads requirements.txt during creation so the environment receives every dependency.
Dependency installation can take a few minutes while the packages download. Progress notifications or terminal activity mean the environment is still being prepared.
- Press Ctrl+Shift+P to open the Command Palette.
- Enter Python: Create Environment, then press Enter.
- Select venv as the environment manager.
- Choose a Python interpreter showing version 3.11 or newer.
- Accept .venv as the environment name.
- Select requirements.txt when Visual Studio Code asks which dependencies to install.
Visual Studio Code creates .venv inside bank-campaign-classifier. The package installation finishes inside that isolated environment.
- Press Ctrl+Shift+P to reopen the Command Palette.
- Enter Python: Select Interpreter, then press Enter.
- Select the interpreter whose path includes .venv.
You will see .venv as the selected interpreter in Visual Studio Code. This confirms future Python runs use the project-local packages.
Environment Creation Failed?
Confirm requirements.txt contains all four pins. A misspelled package name prevents the dependency installation from completing.
Confirm the chosen interpreter reports Python 3.11 or newer. An older interpreter cannot install every pinned package.
Help me fix my .venv or dependency installation in Visual Studio Code.
Run the import check
An import check is a small smoke test for the environment. A successful run proves the selected interpreter can load every library needed for the next stage.
- Select the New File icon beside bank-campaign-classifier in the Explorer sidebar.
- Enter bank_campaign.py as the file name.
- Add the environment check by pasting this code:
import matplotlib.pyplot as plt
import pandas as pd
from ucimlrepo import fetch_ucirepo
from sklearn.linear_model import LogisticRegression
print("Environment ready: pandas, matplotlib.pyplot, LogisticRegression, fetch_ucirepo")
What Does This Code Check?
- The first two imports confirm the charting and data-table packages are available.
- The fetch_ucirepo import confirms the future dataset loader is available.
- The LogisticRegression import confirms the future classification model is available.
- The final message only prints after Python completes every import above it.
- Press Ctrl+S to save bank_campaign.py.
Before you run the file, do you expect every import to succeed with .venv selected?
- Select Run Python File in Terminal for bank_campaign.py.
You will see Environment ready: pandas, matplotlib.pyplot, LogisticRegression, fetch_ucirepo in the terminal. That message proves the selected environment can import all four dependencies.
Import Check Not Reaching the Message?
Confirm the selected interpreter path includes .venv. A different interpreter may not contain the packages from this project.
Compare requirements.txt with the pinned reference above if the terminal reports a missing dependency.
Help me diagnose a failed import in bank_campaign.py.
✔️ Awesome, I've got everything!
Your selected .venv runs bank_campaign.py with every required import available.
ⓧ I'd like to double check the full code
Compare your complete bank_campaign.py file with this reference:
import matplotlib.pyplot as plt
import pandas as pd
from ucimlrepo import fetch_ucirepo
from sklearn.linear_model import LogisticRegression
print("Environment ready: pandas, matplotlib.pyplot, LogisticRegression, fetch_ucirepo")
Why Use This Exact Check?
This file imports one required interface from each installed package group. The final print statement gives you one visible signal that the environment is ready.
That setup hurdle is cleared. Your isolated workspace is ready to download the campaign data and reveal the subscription pattern.
Reveal the Subscription Pattern
The environment from the last step can now run your data science packages. The next question is what the model is trying to predict.
Before training a binary classifier, you need to see what its target represents. You also need to check whether the two outcomes occur equally often.
In this step, get ready to:
- Load UCI Bank Marketing dataset 222.
- Inspect the dataset summary in the terminal.
- Save the subscription distribution as a bar chart.
Load and encode the dataset
The UCI Machine Learning Repository provides the Bank Marketing dataset through fetch_ucirepo(id=222). Its y target records whether each client subscribed to a term deposit.
- Return to the bank_campaign.py tab from earlier.
- Replace its contents with the code below:
from pathlib import Path
import matplotlib.pyplot as plt
import pandas as pd
from ucimlrepo import fetch_ucirepo
from sklearn.linear_model import LogisticRegression
OUTPUT_DIR = Path("outputs")
def load_data():
dataset = fetch_ucirepo(id=222)
features = dataset.data.features
target = (dataset.data.targets["y"] == "yes").astype("int64")
return features, target
def main():
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
features, target = load_data()
print(f"Rows: {features.shape[0]:,}")
print(f"Features: {features.shape[1]}")
print("Columns:", ", ".join(features.columns))
if __name__ == "__main__":
main()
What Does This Code Do?
- The load_data() function retrieves dataset 222 through fetch_ucirepo().
- The features variable holds the columns the models can use.
- The target conversion maps yes to 1. Every other target value becomes 0.
- The main() function creates the outputs directory. It also prints the returned dataset structure.
- Save bank_campaign.py.
- Select Run Python File in Terminal from the editor toolbar.
You'll see a Rows: line plus a Features: line. The Columns: line lists the available feature names.
Your first data pass now proves that the dataset loads successfully.
Data Not Loading?
- Confirm that your computer has an active internet connection.
- Check that the project-local .venv remains selected as the interpreter.
- Compare the imports with the code block above.
Help me troubleshoot why bank_campaign.py cannot load the UCI dataset.
Read the target counts
A model needs both target labels represented in its evaluation data. Pandas can count each encoded outcome with value_counts().
- Find the existing Columns: print statement inside main().
- Replace that statement with these three lines:
print("Columns:", ", ".join(features.columns))
print("Target counts:")
print(target.value_counts())
What Do These Lines Reveal?
- The first line keeps the feature-name output from the previous run.
- The target.value_counts() call counts the rows assigned to each target label.
- The label 0 represents a client who did not subscribe.
- The label 1 represents a client who subscribed.
- Save bank_campaign.py.
- Select Run Python File in Terminal from the editor toolbar.
You'll see Target counts: followed by separate counts for 0 and 1.
Your target balance is now visible as terminal counts.
Target Counts Missing?
- Confirm that both target-count lines sit inside main().
- Check that each target-count line uses the same four-space indentation as the existing print statements.
- Compare the method name with value_counts().
Help me fix the missing target-count output.
Save the target distribution chart
A class-distribution chart makes differences between outcome frequencies visible. This is the quickest way to spot class imbalance before modeling.
- Select everything from def main(): through the final main() call in bank_campaign.py.
- Replace that selection with the code below:
def save_target_distribution(target):
target_counts = target.value_counts()
figure, axes = plt.subplots(figsize=(6, 4))
axes.bar(
["No", "Yes"],
[int(target_counts[0]), int(target_counts[1])],
color=["#4C78A8", "#F58518"],
)
axes.set_title("Bank campaign subscription outcomes")
axes.set_xlabel("Subscribed to a term deposit")
axes.set_ylabel("Clients")
figure.tight_layout()
figure.savefig(str(OUTPUT_DIR / "target_distribution.png"), dpi=150)
plt.close(figure)
def main():
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
features, target = load_data()
print(f"Rows: {features.shape[0]:,}")
print(f"Features: {features.shape[1]}")
print("Columns:", ", ".join(features.columns))
print("Target counts:")
print(target.value_counts())
save_target_distribution(target)
if __name__ == "__main__":
main()
How Does the Chart Work?
- The save_target_distribution() function counts the encoded target values.
- Matplotlib creates a figure with labeled No and Yes bars.
- The bar heights use the target counts returned by the dataset.
- The figure.savefig() call writes the chart to outputs/target_distribution.png.
- Save bank_campaign.py.
- Predict which subscription outcome has the taller bar.
- Select Run Python File in Terminal from the editor toolbar.
The terminal again shows the dataset summary. The outputs folder now contains target_distribution.png.
- Expand the outputs folder in the Explorer sidebar.
- Select target_distribution.png to open the saved chart.
You'll see two bars labeled No and Yes beneath the chart title. Their heights make the target balance visible.
Your first evidence file is ready: the target balance is now visible outside the terminal.
Chart File Missing?
- Confirm that save_target_distribution(target) appears inside main().
- Check that OUTPUT_DIR matches the constant near the top of the file.
- Look for a Python error in the terminal after the target counts.
Help me troubleshoot why outputs/target_distribution.png was not created.
✔️ Awesome, I've got everything!
Great. Double check that bank_campaign.py is saved. Confirm that outputs/target_distribution.png opens in Visual Studio Code.
ⓧ I'd like to double check the full code
Compare your bank_campaign.py file with the complete version below.
from pathlib import Path
import matplotlib.pyplot as plt
import pandas as pd
from ucimlrepo import fetch_ucirepo
from sklearn.linear_model import LogisticRegression
OUTPUT_DIR = Path("outputs")
def load_data():
dataset = fetch_ucirepo(id=222)
features = dataset.data.features
target = (dataset.data.targets["y"] == "yes").astype("int64")
return features, target
def save_target_distribution(target):
target_counts = target.value_counts()
figure, axes = plt.subplots(figsize=(6, 4))
axes.bar(
["No", "Yes"],
[int(target_counts[0]), int(target_counts[1])],
color=["#4C78A8", "#F58518"],
)
axes.set_title("Bank campaign subscription outcomes")
axes.set_xlabel("Subscribed to a term deposit")
axes.set_ylabel("Clients")
figure.tight_layout()
figure.savefig(str(OUTPUT_DIR / "target_distribution.png"), dpi=150)
plt.close(figure)
def main():
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
features, target = load_data()
print(f"Rows: {features.shape[0]:,}")
print(f"Features: {features.shape[1]}")
print("Columns:", ", ".join(features.columns))
print("Target counts:")
print(target.value_counts())
save_target_distribution(target)
if __name__ == "__main__":
main()
You can now see how the subscription outcomes are distributed. Next, you'll test a baseline that predicts no subscription for every client.
Build a No-Subscription Baseline
That first result is in place: your chart now makes the subscription imbalance visible. Accuracy can still look impressive when predictions provide little value to the campaign.
A baseline gives every later model a point of comparison. You will create a train-test split before measuring a deliberately simple benchmark.
In this step, get ready to:
- Reserve a stratified test set for evaluation.
- Add a reusable scorer for four model metrics.
- Run a constant no-subscription benchmark.
Prepare the benchmark tools
The benchmark needs a classifier plus several evaluation metrics. Two constants keep the data split repeatable across every model in the project.
- Return to bank_campaign.py from earlier.
- Find from sklearn.linear_model import LogisticRegression near the top of the file.
- Select that line through OUTPUT_DIR = Path("outputs").
- Replace the selected block with this benchmark setup:
from sklearn.dummy import DummyClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import (
accuracy_score,
precision_score,
recall_score,
roc_auc_score,
)
from sklearn.model_selection import train_test_split
OUTPUT_DIR = Path("outputs")
RANDOM_STATE = 42
TEST_SIZE = 0.2
What does this setup provide?
- The DummyClassifier import supplies a simple benchmark from scikit-learn.
- The metric imports measure accuracy, precision, recall, and ROC-AUC.
- RANDOM_STATE keeps the split reproducible.
- TEST_SIZE reserves 20 percent of the rows for testing.
- Save bank_campaign.py.
- Select Run Python File in Terminal from the editor toolbar.
You will see the row count, feature count, columns, and target counts complete without an import failure. This confirms the benchmark tools load inside your selected .venv.
Script stops after the import update?
- Confirm that the replacement starts with from sklearn.dummy import DummyClassifier.
- Confirm that OUTPUT_DIR still appears once before RANDOM_STATE.
If the imports still fail, help me check my benchmark imports.
Add the reusable scoring function
A single scoring function keeps every model comparison consistent. It calculates class predictions plus positive-class probabilities before returning the evidence needed later.
- Find the end of save_target_distribution() in bank_campaign.py.
- Add this function above def main():.
def score_model(name, model, test_features, test_target):
predictions = model.predict(test_features)
positive_probabilities = model.predict_proba(test_features)[:, 1]
metrics = {
"model": name,
"accuracy": accuracy_score(test_target, predictions),
"precision": precision_score(test_target, predictions, zero_division=0),
"recall": recall_score(test_target, predictions, zero_division=0),
"roc_auc": roc_auc_score(test_target, positive_probabilities),
}
print(
f"{name}: "
f"accuracy={metrics['accuracy']:.3f}, "
f"precision={metrics['precision']:.3f}, "
f"recall={metrics['recall']:.3f}, "
f"roc_auc={metrics['roc_auc']:.3f}"
)
return metrics, predictions
What does this function measure?
- predict() produces the final class assigned to each test row.
- predict_proba() supplies the positive-class probabilities required for ROC-AUC.
- metrics stores the model name plus all four evaluation values in one dictionary.
- The printed line formats every score to three decimal places for quick comparison.
- Save bank_campaign.py.
- Select Run Python File in Terminal from the editor toolbar.
You will see the existing dataset summary finish without a traceback. This confirms Python can parse the scoring function before the benchmark calls it.
Seeing a syntax problem in the scorer?
- Check that score_model() starts at the left edge of the file.
- Check that every line inside the function uses the indentation shown above.
- Check that each metric name keeps its matching quotation marks.
For help finding the mismatch, help me debug my score_model function.
Split the data and run the baseline
The test set acts as unseen evidence for every model. Stratification preserves the target balance while the constant classifier applies the same prediction rule to every row.
- Find save_target_distribution(target) inside main().
- Add this benchmark workflow directly below that line:
train_features, test_features, train_target, test_target = train_test_split(
features,
target,
test_size=TEST_SIZE,
random_state=RANDOM_STATE,
stratify=target,
)
baseline = DummyClassifier(strategy="constant", constant=0)
baseline.fit(train_features, train_target)
baseline_metrics, _ = score_model(
"No-subscription baseline", baseline, test_features, test_target
)
How does the benchmark work?
- train_test_split() creates matching training and test portions for the features plus target.
- stratify=target keeps the subscription balance represented in both portions.
- The constant classifier learns the training interface before assigning class 0 to each test row.
- score_model() evaluates the held-out predictions with the same metric set future models will use.
- Save bank_campaign.py.
Before you run the script, predict whether one repeated outcome can still earn high accuracy.
- Select Run Python File in Terminal from the editor toolbar.
You will see a No-subscription baseline metrics line where subscriber recall is 0.000. The baseline earns an accuracy value while finding none of the actual subscribers.
You have exposed the trap in a single run: accuracy alone cannot show whether a campaign model finds the people it needs to reach.
Baseline line missing?
- Confirm that the split starts below save_target_distribution(target) inside main().
- Confirm that the baseline block uses the same four split variable names shown above.
- Confirm that score_model() appears before main().
If the script still stops before the metrics line, help me debug the baseline run.
✔️ Awesome, I've got everything!
Great work. Save bank_campaign.py once more after confirming that subscriber recall is 0.000.
ⓧ I'd like to double check the full code
Compare your complete bank_campaign.py file with this version:
from pathlib import Path
import matplotlib.pyplot as plt
import pandas as pd
from ucimlrepo import fetch_ucirepo
from sklearn.dummy import DummyClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import (
accuracy_score,
precision_score,
recall_score,
roc_auc_score,
)
from sklearn.model_selection import train_test_split
OUTPUT_DIR = Path("outputs")
RANDOM_STATE = 42
TEST_SIZE = 0.2
def load_data():
dataset = fetch_ucirepo(id=222)
features = dataset.data.features
target = (dataset.data.targets["y"] == "yes").astype("int64")
return features, target
def save_target_distribution(target):
target_counts = target.value_counts()
figure, axes = plt.subplots(figsize=(6, 4))
axes.bar(
["No", "Yes"],
[int(target_counts[0]), int(target_counts[1])],
color=["#4C78A8", "#F58518"],
)
axes.set_title("Bank campaign subscription outcomes")
axes.set_xlabel("Subscribed to a term deposit")
axes.set_ylabel("Clients")
figure.tight_layout()
figure.savefig(str(OUTPUT_DIR / "target_distribution.png"), dpi=150)
plt.close(figure)
def score_model(name, model, test_features, test_target):
predictions = model.predict(test_features)
positive_probabilities = model.predict_proba(test_features)[:, 1]
metrics = {
"model": name,
"accuracy": accuracy_score(test_target, predictions),
"precision": precision_score(test_target, predictions, zero_division=0),
"recall": recall_score(test_target, predictions, zero_division=0),
"roc_auc": roc_auc_score(test_target, positive_probabilities),
}
print(
f"{name}: "
f"accuracy={metrics['accuracy']:.3f}, "
f"precision={metrics['precision']:.3f}, "
f"recall={metrics['recall']:.3f}, "
f"roc_auc={metrics['roc_auc']:.3f}"
)
return metrics, predictions
def main():
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
features, target = load_data()
print(f"Rows: {features.shape[0]:,}")
print(f"Features: {features.shape[1]}")
print("Columns:", ", ".join(features.columns))
print("Target counts:")
print(target.value_counts())
save_target_distribution(target)
train_features, test_features, train_target, test_target = train_test_split(
features,
target,
test_size=TEST_SIZE,
random_state=RANDOM_STATE,
stratify=target,
)
baseline = DummyClassifier(strategy="constant", constant=0)
baseline.fit(train_features, train_target)
baseline_metrics, _ = score_model(
"No-subscription baseline", baseline, test_features, test_target
)
if __name__ == "__main__":
main()
Your benchmark now exposes what accuracy can hide. Next, you will compare it with a model that learns from the campaign features.
Train an All-Feature Model
The baseline from the last step reported accuracy while missing every subscriber. A useful classifier must learn patterns from the bank's numeric and categorical columns.
You will train a logistic regression model with every available feature. After evaluating its metrics, you will check whether its inputs exist when the bank needs the prediction.
In this step, get ready to:
- Build a mixed-type preprocessing pipeline.
- Evaluate an all-feature logistic regression model.
- Check whether the model can support pre-call targeting.
Build the mixed-type pipeline
Numeric columns need feature scaling. Categorical columns need one-hot encoding.
A scikit-learn Pipeline keeps both transformations attached to the classifier. This gives training data and future test data the same preparation.
- In bank_campaign.py, select the import section from from pathlib import Path through from sklearn.model_selection import train_test_split.
- Replace the selected imports with this expanded import section:
from pathlib import Path
import matplotlib.pyplot as plt
import pandas as pd
from ucimlrepo import fetch_ucirepo
from sklearn.compose import ColumnTransformer
from sklearn.dummy import DummyClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import (
accuracy_score,
precision_score,
recall_score,
roc_auc_score,
)
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
Why These Imports?
- The ColumnTransformer sends different column groups through different preparation steps.
- The StandardScaler puts numeric columns onto comparable scales.
- The OneHotEncoder converts categories into values the classifier can process.
- The Pipeline runs preprocessing before logistic regression.
- Save bank_campaign.py.
- Run bank_campaign.py by selecting Run Python File in Terminal.
You'll see the target summary. You'll also see the No-subscription baseline metrics line.
Seeing an Import Problem?
Confirm that the selected interpreter belongs to the project's .venv. Use Python: Select Interpreter from the Command Palette if the terminal is using another environment.
Check that all three new import lines match the code block exactly.
Help me fix the preprocessing imports.
The reusable model builder detects each column type from the DataFrame it receives. It returns one fitted workflow shape that can later be reused with a different feature set.
- In bank_campaign.py, place the following function between save_target_distribution() and score_model():
def build_model(feature_frame):
numeric_columns = feature_frame.select_dtypes(include="number").columns
categorical_columns = feature_frame.select_dtypes(exclude="number").columns
preprocessing = ColumnTransformer(
transformers=[
("numeric", StandardScaler(), numeric_columns),
(
"categorical",
OneHotEncoder(handle_unknown="ignore"),
categorical_columns,
),
]
)
return Pipeline(
steps=[
("preprocessing", preprocessing),
(
"classifier",
LogisticRegression(max_iter=1000, class_weight="balanced"),
),
]
)
What Does This Function Do?
- The numeric_columns and categorical_columns variables hold the two column groups.
- The ColumnTransformer scales numeric values through StandardScaler.
- The categorical path uses OneHotEncoder(handle_unknown="ignore") so an unseen category can pass through the pipeline.
- The returned pipeline finishes with LogisticRegression(max_iter=1000, class_weight="balanced") to learn the subscription classes.
- Save bank_campaign.py.
- Run bank_campaign.py by selecting Run Python File in Terminal.
You'll see the baseline metrics complete again. This confirms that build_model() loads without disrupting the existing analysis.
Script Stops Before the Baseline?
Check that build_model() sits above score_model(). Confirm that every line inside the function uses the indentation shown in the code block.
Help me debug the model builder.
Fit the all-feature model
The model builder is ready to receive the training DataFrame. Passing train_features uses every column currently available in the dataset.
- In bank_campaign.py, find the baseline call at the end of main().
- Place the following model code directly below that baseline call:
all_feature_model = build_model(train_features)
all_feature_model.fit(train_features, train_target)
all_feature_metrics, _ = score_model(
"All features", all_feature_model, test_features, test_target
)
How Is the Model Evaluated?
The call to build_model(train_features) creates preprocessing rules from the training columns. The fit() call learns from the training rows.
The existing score_model() function evaluates untouched test rows. Its returned metrics can later join the baseline in a model comparison.
- Save bank_campaign.py.
Before you run it, which result do you expect to look more useful for finding subscribers?
- Run bank_campaign.py by selecting Run Python File in Terminal.
You'll see a new All features metrics line beneath the baseline. The learned model now gives you a tempting alternative to predicting no for everyone.
That result used every dataset column. Its usefulness now depends on whether each input is available before the bank places a call.
Missing the All-Feature Result?
Confirm that the new lines remain inside main() with four spaces of indentation. Check that all_feature_model has the same spelling in all three lines.
Help me debug the all-feature model.
Expose the prediction-time flaw
A targeting model must use features that exist before contact. The dataset's duration column records the last contact duration in seconds.
The bank cannot know that duration while choosing clients to call. The all-feature metrics therefore describe a model that cannot support the intended pre-call decision.
- In bank_campaign.py, place this warning directly below the All features scoring block:
print(
"Warning: 'duration' is recorded after the call, "
"so this model cannot choose who to call."
)
Why This Warning Matters
The model has learned from information collected after the decision point. Its metrics can still show how predictive that information is.
A deployable targeting model needs a feature set that matches prediction time. Feature availability determines whether a strong score can become a usable decision.
- Save bank_campaign.py.
Before you run the script again, do you expect the warning to support or reject the all-feature model for pre-call targeting?
- Run bank_campaign.py by selecting Run Python File in Terminal.
You'll see the All features metrics line. The next line reads Warning: 'duration' is recorded after the call, so this model cannot choose who to call.
Warning Not Printed?
Confirm that the print() block sits inside main(). Place it after the closing parenthesis of the score_model() call.
Help me restore the duration warning.
✔️ Awesome, I've got everything!
Great. Double check that bank_campaign.py is saved.
Your terminal should show both model results. It should also show the warning about duration.
ⓧ I'd like to double check the full code
Compare your bank_campaign.py file with this complete version:
from pathlib import Path
import matplotlib.pyplot as plt
import pandas as pd
from ucimlrepo import fetch_ucirepo
from sklearn.compose import ColumnTransformer
from sklearn.dummy import DummyClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import (
accuracy_score,
precision_score,
recall_score,
roc_auc_score,
)
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
OUTPUT_DIR = Path("outputs")
RANDOM_STATE = 42
TEST_SIZE = 0.2
def load_data():
dataset = fetch_ucirepo(id=222)
features = dataset.data.features
target = (dataset.data.targets["y"] == "yes").astype("int64")
return features, target
def save_target_distribution(target):
target_counts = target.value_counts()
figure, axes = plt.subplots(figsize=(6, 4))
axes.bar(
["No", "Yes"],
[int(target_counts[0]), int(target_counts[1])],
color=["#4C78A8", "#F58518"],
)
axes.set_title("Bank campaign subscription outcomes")
axes.set_xlabel("Subscribed to a term deposit")
axes.set_ylabel("Clients")
figure.tight_layout()
figure.savefig(str(OUTPUT_DIR / "target_distribution.png"), dpi=150)
plt.close(figure)
def build_model(feature_frame):
numeric_columns = feature_frame.select_dtypes(include="number").columns
categorical_columns = feature_frame.select_dtypes(exclude="number").columns
preprocessing = ColumnTransformer(
transformers=[
("numeric", StandardScaler(), numeric_columns),
(
"categorical",
OneHotEncoder(handle_unknown="ignore"),
categorical_columns,
),
]
)
return Pipeline(
steps=[
("preprocessing", preprocessing),
(
"classifier",
LogisticRegression(max_iter=1000, class_weight="balanced"),
),
]
)
def score_model(name, model, test_features, test_target):
predictions = model.predict(test_features)
positive_probabilities = model.predict_proba(test_features)[:, 1]
metrics = {
"model": name,
"accuracy": accuracy_score(test_target, predictions),
"precision": precision_score(test_target, predictions, zero_division=0),
"recall": recall_score(test_target, predictions, zero_division=0),
"roc_auc": roc_auc_score(test_target, positive_probabilities),
}
print(
f"{name}: "
f"accuracy={metrics['accuracy']:.3f}, "
f"precision={metrics['precision']:.3f}, "
f"recall={metrics['recall']:.3f}, "
f"roc_auc={metrics['roc_auc']:.3f}"
)
return metrics, predictions
def main():
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
features, target = load_data()
print(f"Rows: {features.shape[0]:,}")
print(f"Features: {features.shape[1]}")
print("Columns:", ", ".join(features.columns))
print("Target counts:")
print(target.value_counts())
save_target_distribution(target)
train_features, test_features, train_target, test_target = train_test_split(
features,
target,
test_size=TEST_SIZE,
random_state=RANDOM_STATE,
stratify=target,
)
baseline = DummyClassifier(strategy="constant", constant=0)
baseline.fit(train_features, train_target)
baseline_metrics, _ = score_model(
"No-subscription baseline", baseline, test_features, test_target
)
all_feature_model = build_model(train_features)
all_feature_model.fit(train_features, train_target)
all_feature_metrics, _ = score_model(
"All features", all_feature_model, test_features, test_target
)
print(
"Warning: 'duration' is recorded after the call, "
"so this model cannot choose who to call."
)
if __name__ == "__main__":
main()
You have trained a model that improves on the baseline while exposing a feature-availability flaw. Next up, you will remove the post-call feature and build a model the bank could use before contacting clients.
Build the Deployable Pre-Call Model
Your existing Visual Studio Code workspace has exposed a feature-availability problem. The duration value only exists after a marketing call ends.
You will remove that unavailable input before training a fresh logistic regression model. You will then save the evidence that supports a defensible pre-call targeting decision.
In this step, get ready to:
- Train a pre-call model without duration.
- Save the three-model comparison as reusable evidence.
- Document the recommendation in README.md.
Train the pre-call model
A deployable targeting model needs inputs that exist before the bank chooses whom to call. Removing duration aligns the training data with that decision point.
- In bank_campaign.py, place the following code below the warning about duration:
pre_call_train = train_features.drop(columns=["duration"])
pre_call_test = test_features.drop(columns=["duration"])
pre_call_model = build_model(pre_call_train)
pre_call_model.fit(pre_call_train, train_target)
pre_call_metrics, pre_call_predictions = score_model(
"Pre-call features", pre_call_model, pre_call_test, test_target
)
What does this code do?
- The pandas drop() method creates matching training and test frames without duration.
- The existing build_model() function rebuilds its preprocessing steps from the remaining columns.
- The score_model() call evaluates the pre-call model on the matching test frame.
- The returned predictions provide the decisions needed for the confusion matrix later in this step.
- Save bank_campaign.py.
Before you run the script, do you expect the new model to avoid the feature-availability warning?
- Select Run Python File in Terminal for bank_campaign.py.
You should see a new Pre-call features metrics line after the warning. That line confirms the model trains with information available before a call.
Pre-call model not running?
Check that both pre_call_train and pre_call_test drop the exact duration column.
Check that pre_call_model is fitted with pre_call_train before scoring.
Help me debug the pre-call model.
Save the comparison evidence
Terminal metrics are useful during development. A CSV table plus charts makes the comparison easier to inspect after the run.
- In bank_campaign.py, place the following function below score_model():
def save_metric_chart(results):
axes = results.plot(
x="model",
y=["accuracy", "precision", "recall", "roc_auc"],
kind="bar",
figsize=(10, 5),
rot=0,
title="Model metric comparison",
ylabel="Score",
)
axes.figure.tight_layout()
axes.figure.savefig(str(OUTPUT_DIR / "model_metrics.png"), dpi=150)
plt.close(axes.figure)
How does the metric chart work?
- The results table uses the model names for the horizontal axis.
- The four bars compare accuracy, precision, recall, and ROC-AUC for each model.
- The existing Matplotlib integration saves the chart to outputs/model_metrics.png.
- Closing the figure releases its memory after the image is saved.
- In main(), place the following code below the pre-call scoring block:
results = pd.DataFrame(
[baseline_metrics, all_feature_metrics, pre_call_metrics]
)
results.to_csv(OUTPUT_DIR / "model_comparison.csv", index=False)
save_metric_chart(results)
What evidence does this create?
- The DataFrame contains one row for each evaluated model.
- The model_comparison.csv file preserves the exact metric values for later analysis.
- The model_metrics.png chart turns those values into a side-by-side visual comparison.
- Save bank_campaign.py.
- Select Run Python File in Terminal.
- Expand the outputs folder in the VS Code file list.
You should see model_comparison.csv and model_metrics.png beside the target distribution chart.
Comparison files missing?
Make sure the results block sits inside main() after all three metric dictionaries have been created.
Check that save_metric_chart() appears above main().
Help me fix the missing comparison files.
A confusion matrix shows how the deployable model separates actual subscribers from non-subscribers. It exposes the kinds of decisions hidden inside a single score.
- Replace the existing sklearn.metrics import block with this expanded import group:
from sklearn.metrics import (
ConfusionMatrixDisplay,
accuracy_score,
precision_score,
recall_score,
roc_auc_score,
)
Why expand the metrics import?
The scikit-learn display class builds a labeled confusion matrix from the test targets and predictions. The existing scoring imports remain available to score_model().
- Save bank_campaign.py.
- Select Run Python File in Terminal.
The terminal should reach the Pre-call features line without an import error.
- Place the following function below save_metric_chart():
def save_confusion_matrix(test_target, predictions):
display = ConfusionMatrixDisplay.from_predictions(
test_target,
predictions,
display_labels=["No", "Yes"],
cmap="Blues",
colorbar=False,
)
display.figure_.tight_layout()
display.figure_.savefig(
str(OUTPUT_DIR / "deployable_confusion_matrix.png"), dpi=150
)
plt.close(display.figure_)
What does the confusion matrix show?
- The matrix compares each real outcome with the pre-call model's prediction.
- The No and Yes labels keep the chart connected to the campaign outcome.
- The chart is saved as outputs/deployable_confusion_matrix.png without a colorbar.
- In main(), place this call directly below save_metric_chart(results):
save_confusion_matrix(test_target, pre_call_predictions)
Why use the pre-call predictions?
The saved matrix must represent the model recommended for targeting. Passing pre_call_predictions keeps the visual evidence aligned with that model.
- Save bank_campaign.py.
- Select Run Python File in Terminal.
- Expand the outputs folder in the VS Code file list.
You should now see deployable_confusion_matrix.png with No and Yes labels.
Confusion matrix missing?
Check that the function receives test_target plus pre_call_predictions.
Confirm that ConfusionMatrixDisplay appears in the metrics import group.
Help me create the confusion matrix.
Document and verify the decision
A responsible recommendation connects model performance to the moment when predictions are used. The pre-call model earns that recommendation because its inputs exist before the marketing decision.
- In main(), place the following recommendation below the evidence-saving calls:
print("\nRecommendation:")
print(
"Use the pre-call model for targeting because it excludes "
"information that only exists after a call."
)
print(
f"On the held-out test set, it identifies "
f"{pre_call_metrics['recall']:.1%} of actual subscribers."
)
print(f"Saved project evidence to: {OUTPUT_DIR.resolve()}")
Why print this recommendation?
- The first line names the model that can support pre-call targeting.
- The recall sentence reports how many actual subscribers the held-out evaluation identifies.
- The final line prints the full location of the saved project evidence.
- Save bank_campaign.py.
- Select Run Python File in Terminal.
You should see the recommendation followed by the pre-call recall percentage. You should also see the resolved path to the outputs folder.
Recommendation not printed?
Make sure the recommendation remains indented inside main().
Check that the recall expression uses pre_call_metrics.
Help me fix the recommendation output.
✔️ Awesome, I've got everything!
Great. Double check that bank_campaign.py is saved before you create the project documentation.
ⓧ I'd like to double check the full code
Compare your completed bank_campaign.py with this full reference:
from pathlib import Path
import matplotlib.pyplot as plt
import pandas as pd
from ucimlrepo import fetch_ucirepo
from sklearn.compose import ColumnTransformer
from sklearn.dummy import DummyClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import (
ConfusionMatrixDisplay,
accuracy_score,
precision_score,
recall_score,
roc_auc_score,
)
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
OUTPUT_DIR = Path("outputs")
RANDOM_STATE = 42
TEST_SIZE = 0.2
def load_data():
dataset = fetch_ucirepo(id=222)
features = dataset.data.features
target = (dataset.data.targets["y"] == "yes").astype("int64")
return features, target
def save_target_distribution(target):
target_counts = target.value_counts()
figure, axes = plt.subplots(figsize=(6, 4))
axes.bar(
["No", "Yes"],
[int(target_counts[0]), int(target_counts[1])],
color=["#4C78A8", "#F58518"],
)
axes.set_title("Bank campaign subscription outcomes")
axes.set_xlabel("Subscribed to a term deposit")
axes.set_ylabel("Clients")
figure.tight_layout()
figure.savefig(str(OUTPUT_DIR / "target_distribution.png"), dpi=150)
plt.close(figure)
def build_model(feature_frame):
numeric_columns = feature_frame.select_dtypes(include="number").columns
categorical_columns = feature_frame.select_dtypes(exclude="number").columns
preprocessing = ColumnTransformer(
transformers=[
("numeric", StandardScaler(), numeric_columns),
(
"categorical",
OneHotEncoder(handle_unknown="ignore"),
categorical_columns,
),
]
)
return Pipeline(
steps=[
("preprocessing", preprocessing),
(
"classifier",
LogisticRegression(max_iter=1000, class_weight="balanced"),
),
]
)
def score_model(name, model, test_features, test_target):
predictions = model.predict(test_features)
positive_probabilities = model.predict_proba(test_features)[:, 1]
metrics = {
"model": name,
"accuracy": accuracy_score(test_target, predictions),
"precision": precision_score(test_target, predictions, zero_division=0),
"recall": recall_score(test_target, predictions, zero_division=0),
"roc_auc": roc_auc_score(test_target, positive_probabilities),
}
print(
f"{name}: "
f"accuracy={metrics['accuracy']:.3f}, "
f"precision={metrics['precision']:.3f}, "
f"recall={metrics['recall']:.3f}, "
f"roc_auc={metrics['roc_auc']:.3f}"
)
return metrics, predictions
def save_metric_chart(results):
axes = results.plot(
x="model",
y=["accuracy", "precision", "recall", "roc_auc"],
kind="bar",
figsize=(10, 5),
rot=0,
title="Model metric comparison",
ylabel="Score",
)
axes.figure.tight_layout()
axes.figure.savefig(str(OUTPUT_DIR / "model_metrics.png"), dpi=150)
plt.close(axes.figure)
def save_confusion_matrix(test_target, predictions):
display = ConfusionMatrixDisplay.from_predictions(
test_target,
predictions,
display_labels=["No", "Yes"],
cmap="Blues",
colorbar=False,
)
display.figure_.tight_layout()
display.figure_.savefig(
str(OUTPUT_DIR / "deployable_confusion_matrix.png"), dpi=150
)
plt.close(display.figure_)
def main():
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
features, target = load_data()
print(f"Rows: {features.shape[0]:,}")
print(f"Features: {features.shape[1]}")
print("Columns:", ", ".join(features.columns))
print("Target counts:")
print(target.value_counts())
save_target_distribution(target)
train_features, test_features, train_target, test_target = train_test_split(
features,
target,
test_size=TEST_SIZE,
random_state=RANDOM_STATE,
stratify=target,
)
baseline = DummyClassifier(strategy="constant", constant=0)
baseline.fit(train_features, train_target)
baseline_metrics, _ = score_model(
"No-subscription baseline", baseline, test_features, test_target
)
all_feature_model = build_model(train_features)
all_feature_model.fit(train_features, train_target)
all_feature_metrics, _ = score_model(
"All features", all_feature_model, test_features, test_target
)
print(
"Warning: 'duration' is recorded after the call, "
"so this model cannot choose who to call."
)
pre_call_train = train_features.drop(columns=["duration"])
pre_call_test = test_features.drop(columns=["duration"])
pre_call_model = build_model(pre_call_train)
pre_call_model.fit(pre_call_train, train_target)
pre_call_metrics, pre_call_predictions = score_model(
"Pre-call features", pre_call_model, pre_call_test, test_target
)
results = pd.DataFrame(
[baseline_metrics, all_feature_metrics, pre_call_metrics]
)
results.to_csv(OUTPUT_DIR / "model_comparison.csv", index=False)
save_metric_chart(results)
save_confusion_matrix(test_target, pre_call_predictions)
print("\nRecommendation:")
print(
"Use the pre-call model for targeting because it excludes "
"information that only exists after a call."
)
print(
f"On the held-out test set, it identifies "
f"{pre_call_metrics['recall']:.1%} of actual subscribers."
)
print(f"Saved project evidence to: {OUTPUT_DIR.resolve()}")
if __name__ == "__main__":
main()
The final file explains the three-model decision. It also records the dataset attribution plus the responsible-use boundary.
- Use the new-file control at the top of the VS Code file list.
- Type README.md into the filename field.
- Press Enter to create the file.
- Paste the following content into README.md:
# Bank Campaign Subscription Classifier
This project predicts whether a bank client will subscribe to a term deposit and compares three approaches:
1. A no-subscription baseline
2. A model using every available feature
3. A pre-call model that removes `duration`
The pre-call model is the defensible targeting model because call duration is only recorded after a call has taken place.
## Run the project
Open `bank_campaign.py` in Visual Studio Code and select Run Python File in Terminal.
The script creates:
- `outputs/target_distribution.png`
- `outputs/model_comparison.csv`
- `outputs/model_metrics.png`
- `outputs/deployable_confusion_matrix.png`
## Data source
Bank Marketing, UCI Machine Learning Repository:
https://archive.ics.uci.edu/dataset/222/bank+marketing
Citation: Moro, S., Rita, P., and Cortez, P. (2014). Bank Marketing [Dataset]. UCI Machine Learning Repository.
## Responsible use
This is an educational portfolio project, not a production decision system. The data contains demographic and financial attributes, so any real deployment would require fairness, privacy, consent, drift, and regulatory review.
What does the README capture?
- The opening section explains the baseline, all-feature model, and pre-call model.
- The run section lists every artifact produced by the script.
- The data section credits the UCI Machine Learning Repository.
- The responsible-use section sets limits around real-world deployment.
- Save README.md.
✔️ Awesome, I've got everything!
Your project now includes a saved explanation of the modeling decision plus its evidence files.
ⓧ I'd like to double check the full code
Compare your completed README.md with this full reference:
# Bank Campaign Subscription Classifier
This project predicts whether a bank client will subscribe to a term deposit and compares three approaches:
1. A no-subscription baseline
2. A model using every available feature
3. A pre-call model that removes `duration`
The pre-call model is the defensible targeting model because call duration is only recorded after a call has taken place.
## Run the project
Open `bank_campaign.py` in Visual Studio Code and select Run Python File in Terminal.
The script creates:
- `outputs/target_distribution.png`
- `outputs/model_comparison.csv`
- `outputs/model_metrics.png`
- `outputs/deployable_confusion_matrix.png`
## Data source
Bank Marketing, UCI Machine Learning Repository:
https://archive.ics.uci.edu/dataset/222/bank+marketing
Citation: Moro, S., Rita, P., and Cortez, P. (2014). Bank Marketing [Dataset]. UCI Machine Learning Repository.
## Responsible use
This is an educational portfolio project, not a production decision system. The data contains demographic and financial attributes, so any real deployment would require fairness, privacy, consent, drift, and regulatory review.
Before the final run, which model do you expect the script to recommend for a campaign decision made before any call begins?
- Return to bank_campaign.py.
- Select Run Python File in Terminal.
You should see all three model lines followed by the pre-call recommendation. The final line should point to the saved outputs folder.
- Open outputs/model_comparison.csv from the VS Code file list.
- Open outputs/model_metrics.png from the VS Code file list.
- Open outputs/deployable_confusion_matrix.png from the VS Code file list.
The CSV should contain rows for No-subscription baseline, All features, and Pre-call features.
The metric chart should compare accuracy, precision, recall, and ROC-AUC. The confusion matrix should display No and Yes outcomes for the deployable model.
Final evidence incomplete?
Confirm that all three save calls remain inside main() before the recommendation.
Rerun the active bank_campaign.py file after saving every edit.
Help me verify the final project artifacts.
That is the full decision workflow complete. Your project now compares model quality with feature availability before recommending a deployable option.
Secret mission
Tune the Decision Threshold
Test three probability thresholds on the deployable pre-call model. Compare precision with recall to choose a campaign cutoff you can defend.
Clean Up Your Resources
Clean Up Your Resources
Everything in this project runs locally, so keeping the workspace creates no ongoing charge. Choose whether to keep the files, pause your work, or delete the workspace.
Resources you used:
- Your local Visual Studio Code workspace contains .venv plus requirements.txt for the project environment.
- Your base project files are bank_campaign.py plus README.md.
- Your Secret Mission file is threshold_mission.py.
- Your charts and comparison tables are stored in outputs.
Keep everything running
No action needed. Choose this if you want to rerun the analysis or continue comparing decision thresholds.
- Keep the current workspace folder with .venv selected.
- Retain the files in outputs as evidence from your model evaluation.
- Continue using the workspace without any ongoing charge.
Pause - I'll come back to this later
Pausing closes the editor while leaving every project file in place. Local storage creates no ongoing charge.
- Close Visual Studio Code.
- Leave the current workspace folder on your machine.
- Return to the same workspace when you want to rerun bank_campaign.py or threshold_mission.py.
Delete - I don't want to use this again
Deleting your workspace is permanent. It affects only the local project files listed above.
- Close Visual Studio Code.
- In Windows, return to the location containing the folder currently open as your workspace.
- Delete the workspace folder.
- Return to the same location to confirm the workspace folder is no longer listed.
You should no longer see the workspace folder. This confirms that the virtual environment, scripts, documentation, and generated outputs have been removed.
Nice Work!
Nice Work!
You did it! Your reproducible Python classification project now predicts bank campaign subscriptions before a marketing call.
You've learned how to:
- Create a reproducible workspace with pinned packages and a project-local .venv.
- Load the Bank Marketing dataset from the UCI Machine Learning Repository. Make class imbalance visible in outputs/target_distribution.png.
- Run a responsible model comparison across a no-subscription baseline, an all-feature model, and a pre-call model. Compare accuracy, precision, recall, and ROC-AUC. Detect the timing flaw in duration. Save the defensible model evidence as charts, a confusion matrix, and a CSV table.
- Secret Mission: Compare decision thresholds of 0.3, 0.5, and 0.7. Use precision and recall to frame the campaign trade-off.
Ready to quiz yourself?