import whittaker as wk
data = wk.load_dataset("credit", as_frame=True)
# column names
data.columns.tolist()['income', 'debt_ratio', 'age', 'default']
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 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).
| 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 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.
Plotting the partial effects shows how each predictor bends the log-odds of default.
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.
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
probsarray([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.
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.