Overview
A company has a new diagnostic tool for diabetes, built on measurements anyone can take: glucose, blood pressure, BMI, age, a score for family history. The evidence is a trial of 768 women. The question is whether those eight numbers are enough to predict a diagnosis, and how much each one of them contributes.
Logistic regression answers with an intercept and eight coefficients, and gets 74% of the held-out women right. On its own that number is close to meaningless: 65% of the trial was negative, so a model that shrugged and said "no" to everybody would score 65%. The distance between shrugging and the model is the actual result, and it is smaller than 74% makes it sound.
The number I kept coming back to is recall: 0.57. Of the 28 women in the held-out set who really were diagnosed, the model catches 16 and sends 12 home. For a screening tool that is the expensive kind of mistake, and accuracy hides it completely. Lowering the decision threshold brings those women back and buys false alarms instead — there is no setting that fixes both, which is the part of the project worth keeping.
The assignment closes by building a small Keras network, on the grounds that logistic regression is the simplest neural network there is. Ten hidden units, fifty epochs, scaled inputs — and it lands slightly behind, at 0.71 accuracy and 0.56 F1. More machinery, no more signal in the data to find.
What I built
Reading the data before modelling it
- A pairplot of all nine variables, which mostly establishes what a linear model cannot do: the outcome is two flat bands, not a cloud with a slope through it.
- A histogram and a box plot per variable for the distributions and the outliers, and a correlation heatmap to put numbers on what the scatter plots only suggest — glucose is the single strong tie to the diagnosis at 0.47, blood pressure barely registers at 0.07.
The logistic model
- A 90/10 split seeded with my student number, then a logistic regression fitted on the 691 training women.
- The intercept read as something physical: −8.56 in log-odds is a probability of 0.019%, the model's belief about a woman whose every measurement is zero — a reference point, not a prediction.
- Each coefficient interpreted as an effect on the odds, including the awkward one: blood pressure comes out negative, which is not physiology but what happens to a variable that is nearly uncorrelated with the outcome and shares what little it has with BMI and age.
- Accuracy, precision, recall and F1 on the 77 held-out women, and the case for reading them side by side rather than stopping at the first.
A neural network, for comparison
- A Keras Sequential model — one ReLU layer of ten units, a sigmoid output, Adam at a 0.05 learning rate, binary cross-entropy, fifty epochs over standardised inputs.
- Scored on the same held-out women as the regression, so the comparison is like for like: 0.714 accuracy and 0.560 F1 against 0.740 and 0.615.
The block above runs the real fitted coefficients, so the threshold slider reproduces the notebook's metrics exactly at 0.50. It is a student project on a teaching dataset, and nothing more than that.
The model, running
Coefficients from project4.ipynb
The intercept and the eight coefficients below are the ones scikit-learn fitted on the trial data, copied over unchanged — so the threshold tab reproduces the notebook's numbers to the fourth decimal. Nothing is retrained here; the arithmetic is just being done in your browser instead of mine.
Predicted probability
22.5%
Log-odds -1.24 · under the 0.50 threshold
What is moving the risk
Log-odds, against the median woman of the trial.
Every slider is on the median of the trial, so nothing is pulling the risk either way. Move one and watch where it goes.
A student project on a 768-row teaching dataset, not a medical device. It predicts nothing about anybody and should not be used as if it did.
Inside the notebook
Read-only · outputs from project4.ipynb
Five cells from the assignment: looking at all eight measurements at once, putting numbers on what the scatter plots hint at, fitting the logistic regression, reading its intercept as a real probability, and letting a small neural network try to beat it.
data = pd.read_csv('diabetes.csv')
# Before anything else: look at all of it at once
sns.pairplot(data)# The pairplot shows everything and quantifies nothing. The heatmap does the opposite.
correlation_matrix = data.corr()
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt='.2f', cbar=True)
plt.show()np.random.seed(4914) # same split rule as the housing project
train_indices = np.random.choice(len(data), int(0.9 * len(data)), replace=False)
train_data = data.iloc[train_indices]
X = train_data.drop(columns=["Outcome"])
y = train_data["Outcome"]
log_model = LogisticRegression(max_iter=250)
log_model.fit(X, y)
print(f"Intercept (beta0): {log_model.intercept_[0]}")
print(f"Coefficients: {log_model.coef_[0]}")# What does an intercept of -8.56 actually mean?
probability = 1 / (1 + np.exp(-intercept))
print(probability * 100)# Logistic regression is the simplest neural network there is.
# So: build an actual one and see whether the extra machinery earns its place.
NN_model = Sequential([
Dense(10, activation='relu', input_dim=X.shape[1]),
Dense(1, activation='sigmoid')
])
NN_model.compile(optimizer=Adam(learning_rate=0.05), loss='binary_crossentropy',
metrics=['accuracy'])
NN_model.fit(scaler.fit_transform(X), y, epochs=50, batch_size=32, verbose=0)
NN_pred = (NN_model.predict(X_test_scaled) > 0.5).astype("int32")
print(pd.DataFrame(comparison))
