Model Proportions with Beta

Fit a GAM when the response is a proportion strictly between 0 and 1.

Use the Beta family when your response is a rate or proportion that is strictly between 0 and 1 (germination rates, survival fractions, market share, or test pass rates). A Gaussian model can predict values outside (0, 1) and assumes constant variance; Beta regression respects the natural bounds and allows variance to vary with the mean. The logit link means that each covariate has a multiplicative effect on the odds of the proportion.

Load data

Load the proportions dataset and inspect its columns.

import whittaker as wk

data = wk.load_dataset("proportions", as_frame=True)
data.columns.tolist()
['temperature', 'water', 'germination_rate']

The dataset contains germination_rate, temperature, and water.

data.head()
temperature water germination_rate
0 31.565096 14.971623 0.627402
1 31.662046 42.421705 0.834827
2 22.005744 39.074516 0.994336
3 14.431446 54.364739 0.991711
4 6.779713 20.583531 0.738985

The response germination_rate is already bounded in (0, 1). Verify this before fitting. The Beta family requires strictly interior values; zeros and ones require a zero-one-inflated extension.

Fit

Fit a Beta GAM with smooths over both covariates.

model = wk.GAM(
    "germination_rate ~ s(temperature) + s(water)",
    family=wk.Beta(),
).fit(data)

model.summary()
GAM fit summary
============================================================
Formula:    germination_rate ~ s(temperature) + s(water)
Family:     Beta(link='logit')
Inference:  GCV
Observations: 400
Coefficients: 19

Parametric coefficients:
  Term                       Estimate    Std.Err    t value    p-value
  ------------------------ ---------- ---------- ---------- ----------
  (Intercept)                  1.6150     0.0445     36.274    < 1e-16

Approximate significance of smooth terms:
  Term                        EDF Ref.df     Chi.sq    p-value
  ------------------------ ------ ------ ---------- ----------
  s(temperature)             3.95      4    565.110    < 1e-16
  s(water)                   2.84      3     66.788  2.079e-14

Total EDF:  7.79
Scale est:  0.084702
Deviance:   33.2210
Null dev:   96.1702
Dev. expl:  65.5%
GCV score:  0.086385
AIC:        -873.15
BIC:        -842.05

The EDF for each smooth indicates how non-linear the relationship is. An EDF near 1 suggests a roughly logistic (linear on the logit scale) relationship; higher EDF indicates more curvature.

Partial effects

wk.partial_effects(model)

Partial effects are shown on the logit (link) scale. Positive values increase the expected germination rate; negative values decrease it. The shape of each curve reveals whether the effect is monotone or peaks at an intermediate covariate value.

Diagnostics

wk.check(model)

For a Beta model, check that the residuals are roughly symmetric around zero and that there is no strong pattern in the residual-vs-fitted plot. Heteroscedasticity is less concerning here because the Beta family already allows non-constant variance.

Predict on the response scale

Predictions from a Beta GAM are automatically constrained to (0, 1): no transformation needed.

import pandas as pd

# Build new-data over temperature and water values
new_data = pd.DataFrame({
    "temperature": [15.0, 20.0, 25.0],
    "water": [30.0, 50.0, 70.0],
})

# Predict germination rates with confidence intervals
preds = model.predict(new_data, interval="confidence")

# Assemble results with prediction bounds
pd.DataFrame({
    "temperature": new_data["temperature"],
    "water": new_data["water"],
    "predicted_rate": preds.values,
    "lower": preds.lower,
    "upper": preds.upper,
})
temperature water predicted_rate lower upper
0 15.0 30.0 0.884682 0.862113 0.903969
1 20.0 50.0 0.947542 0.935010 0.957767
2 25.0 70.0 0.947592 0.934392 0.958255

All predicted values and interval bounds are on the (0, 1) scale, directly interpretable as expected germination rates.