Breast Cancer Classification¶
Imports¶
In [1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split, StratifiedKFold, cross_validate
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import (
confusion_matrix,
ConfusionMatrixDisplay,
accuracy_score,
precision_score,
recall_score,
f1_score,
roc_auc_score,
RocCurveDisplay
)
pd.set_option("display.max_columns", None)
Load Dataset¶
In [2]:
data = load_breast_cancer(as_frame=True)
X = data.data.copy()
# 1 = malignant, 0 = benign
y = (data.target == 0).astype(int)
print("Dataset shape:", X.shape)
print("\nClass counts:")
print(y.value_counts().rename(index={0: "Benign", 1: "Malignant"}))
Remove 105 Observations¶
In [3]:
AGE = 35
N_REMOVE = 3 * AGE
RANDOM_SEED = 42
rng = np.random.default_rng(RANDOM_SEED)
remove_indices = rng.choice(
X.index,
size=N_REMOVE,
replace=False
)
X_reduced = X.drop(index=remove_indices).reset_index(drop=True)
y_reduced = y.drop(index=remove_indices).reset_index(drop=True)
print("Removed:", N_REMOVE)
print("Remaining observations:", len(X_reduced))
Class Distribution¶
In [4]:
distribution = pd.DataFrame({
"Count": y_reduced.value_counts().sort_index(),
"Percentage": y_reduced.value_counts(normalize=True).sort_index() * 100
})
distribution.index = ["Benign", "Malignant"]
distribution
Out[4]:
Train-Test Split¶
In [5]:
X_train, X_test, y_train, y_test = train_test_split(
X_reduced,
y_reduced,
test_size=0.20,
random_state=RANDOM_SEED,
stratify=y_reduced
)
print("Training set:", len(X_train))
print("Test set:", len(X_test))
Train Models¶
In [6]:
logistic_model = Pipeline([
("scaler", StandardScaler()),
("classifier", LogisticRegression(
max_iter=5000,
random_state=RANDOM_SEED
))
])
tree_model = DecisionTreeClassifier(
max_depth=4,
random_state=RANDOM_SEED
)
rf_model = RandomForestClassifier(
n_estimators=300,
random_state=RANDOM_SEED,
n_jobs=-1
)
logistic_model.fit(X_train, y_train)
tree_model.fit(X_train, y_train)
rf_model.fit(X_train, y_train)
print("All models trained.")
Evaluation Function¶
In [7]:
def evaluate_model(name, model):
y_pred = model.predict(X_test)
y_prob = model.predict_proba(X_test)[:, 1]
cm = confusion_matrix(y_test, y_pred, labels=[0, 1])
tn, fp, fn, tp = cm.ravel()
result = {
"Model": name,
"Accuracy": accuracy_score(y_test, y_pred),
"Precision": precision_score(y_test, y_pred, zero_division=0),
"Sensitivity": recall_score(y_test, y_pred, zero_division=0),
"Specificity": tn / (tn + fp),
"F1 Score": f1_score(y_test, y_pred, zero_division=0),
"ROC-AUC": roc_auc_score(y_test, y_prob),
"TN": tn,
"FP": fp,
"FN": fn,
"TP": tp
}
print(name)
print(result)
ConfusionMatrixDisplay(
confusion_matrix=cm,
display_labels=["Benign", "Malignant"]
).plot(values_format="d")
plt.title(f"{name} Confusion Matrix")
plt.show()
return result
Logistic Regression¶
In [8]:
logistic_result = evaluate_model(
"Logistic Regression",
logistic_model
)
Decision Tree¶
In [9]:
tree_result = evaluate_model(
"Decision Tree",
tree_model
)
Random Forest¶
In [10]:
rf_result = evaluate_model(
"Random Forest",
rf_model
)
Model Comparison¶
In [11]:
results_df = pd.DataFrame([
logistic_result,
tree_result,
rf_result
])
results_df.round(4)
Out[11]:
ROC Curves¶
In [12]:
fig, ax = plt.subplots(figsize=(8, 6))
RocCurveDisplay.from_estimator(
logistic_model,
X_test,
y_test,
name="Logistic Regression",
ax=ax
)
RocCurveDisplay.from_estimator(
tree_model,
X_test,
y_test,
name="Decision Tree",
ax=ax
)
RocCurveDisplay.from_estimator(
rf_model,
X_test,
y_test,
name="Random Forest",
ax=ax
)
plt.title("ROC Curves")
plt.show()
5-Fold Cross-Validation¶
In [13]:
cv = StratifiedKFold(
n_splits=5,
shuffle=True,
random_state=RANDOM_SEED
)
scoring = {
"accuracy": "accuracy",
"precision": "precision",
"sensitivity": "recall",
"f1": "f1",
"roc_auc": "roc_auc"
}
models = {
"Logistic Regression": logistic_model,
"Decision Tree": tree_model,
"Random Forest": rf_model
}
cv_results = []
for name, model in models.items():
scores = cross_validate(
model,
X_reduced,
y_reduced,
cv=cv,
scoring=scoring,
n_jobs=-1
)
cv_results.append({
"Model": name,
"CV Accuracy": scores["test_accuracy"].mean(),
"CV Precision": scores["test_precision"].mean(),
"CV Sensitivity": scores["test_sensitivity"].mean(),
"CV F1": scores["test_f1"].mean(),
"CV ROC-AUC": scores["test_roc_auc"].mean()
})
cv_results_df = pd.DataFrame(cv_results)
cv_results_df.round(4)
Out[13]:
Final Summary¶
In [14]:
summary_df = results_df.merge(
cv_results_df,
on="Model"
)
summary_df.round(4)
Out[14]:
In [15]:
print("Fewest false negatives:")
print(results_df.loc[results_df["FN"].idxmin(), ["Model", "FN"]])
print("\nHighest sensitivity:")
print(results_df.loc[results_df["Sensitivity"].idxmax(), ["Model", "Sensitivity"]])
print("\nHighest ROC-AUC:")
print(results_df.loc[results_df["ROC-AUC"].idxmax(), ["Model", "ROC-AUC"]])
print("\nHighest CV sensitivity:")
print(cv_results_df.loc[
cv_results_df["CV Sensitivity"].idxmax(),
["Model", "CV Sensitivity"]
])