Model Binary Outcomes with Binomial

Fit a logistic GAM for 0/1 responses.

Use the Binomial family when the response is a binary outcome encoded as 0 or 1 (e.g., loan default, disease presence, conversion events, etc.). The logit link models log-odds on the linear predictor scale, but predict() returns probabilities in [0, 1] by default, so you rarely need to transform the output yourself. Smooth terms let the log-odds curve non-linearly with each predictor instead of forcing a straight line through the probability space.

Load data

Load the built-in credit dataset as a DataFrame and inspect its columns.

import whittaker as wk

data = wk.load_dataset("credit", as_frame=True)

# column names
data.columns.tolist()
['income', 'debt_ratio', 'age', 'default']

The credit dataset has four columns: income (annual income in thousands), debt_ratio (total debt divided by income), age (applicant age in years), and default (1 if the applicant defaulted, 0 otherwise).

data.head()
income debt_ratio age default
0 33.703867 0.536422 59.125160 1.0
1 57.889681 0.178947 61.798004 0.0
2 148.203914 0.212943 37.445462 1.0
3 113.145926 0.690079 49.161208 1.0
4 35.060583 0.245785 26.822899 1.0

Each row represents one credit applicant. The response must be a 0/1 float column. Whittaker checks for this and raises an error if you pass integer labels or strings.

Fit

Fit a logistic GAM with smooth terms for income and debt ratio.

model = wk.GAM(
    "default ~ s(income) + s(debt_ratio)",
    family=wk.Binomial(),
).fit(data)

model.summary()
GAM fit summary
============================================================
Formula:    default ~ s(income) + s(debt_ratio)
Family:     Binomial(link='logit')
Observations: 1000
Coefficients: 19

Parametric coefficients:
  Term                       Estimate    Std.Err    z value    p-value
  ------------------------ ---------- ---------- ---------- ----------
  (Intercept)                 -0.3591     0.0725     -4.954  7.258e-07

Approximate significance of smooth terms:
  Term                        EDF Ref.df     Chi.sq    p-value
  ------------------------ ------ ------ ---------- ----------
  s(income)                  2.90      3    113.502    < 1e-16
  s(debt_ratio)              1.75      2    100.420    < 1e-16

Total EDF:  5.65
Deviance:   1144.6940
Null dev:   1364.9020
Dev. expl:  16.1%
GCV score:  1.157741
Scale est:  1.000000
AIC:        1156.00
BIC:        1183.73

The EDF column in the summary tells you how many degrees of freedom each smooth consumed. Large EDF values (close to the basis dimension k) are a signal to run the k-index diagnostic to check whether k is large enough to capture the true shape.

Partial effects

Plotting the partial effects shows how each predictor bends the log-odds of default.

wk.partial_effects(model)

Partial effects are plotted on the log-odds scale. A rising curve for s(debt_ratio) means that higher debt ratios are associated with higher log-odds of default (holding income constant). Points near zero on the y-axis indicate that the predictor is not shifting the log-odds much at that value.

Predict probabilities

Pass a dictionary of new covariate values to get back predicted default probabilities. All values are in [0, 1] (no sigmoid transformation is needed).

# Build new-data points at varying income levels
new_data = {
    "income": [50.0, 100.0, 150.0],
    "debt_ratio": [0.3, 0.3, 0.3],
}

# Predict default probabilities
probs = model.predict(new_data).values

probs
array([0.53882074, 0.31042173, 0.15767976])

Holding debt_ratio fixed at 0.3, the predicted probability of default falls as income rises. This is the direction you would expect if income is a protective factor in the model.

Proportion responses

If your response is a proportion rather than a strict 0/1 binary (for example, the fraction of items that passed inspection in each batch) use family=wk.Beta() instead. The Beta family is defined on the open interval (0, 1) and handles the boundary-avoiding behavior of proportions more faithfully than the Binomial.