# Model Proportions with Beta

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.


``` python
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`.


``` python
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.


``` python
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


``` python
wk.partial_effects(model)
```


<style>
  #altair-viz-ef19c60dd64f4508a99ad6a7b25cbb02.vega-embed {
    width: 100%;
    display: flex;
  }

  #altair-viz-ef19c60dd64f4508a99ad6a7b25cbb02.vega-embed details,
  #altair-viz-ef19c60dd64f4508a99ad6a7b25cbb02.vega-embed details summary {
    position: relative;
  }
</style>


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


``` python
wk.check(model)
```


<style>
  #altair-viz-0759b891a44b45618bb2aa19fd46863b.vega-embed {
    width: 100%;
    display: flex;
  }

  #altair-viz-0759b891a44b45618bb2aa19fd46863b.vega-embed details,
  #altair-viz-0759b891a44b45618bb2aa19fd46863b.vega-embed details summary {
    position: relative;
  }
</style>


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.


``` python
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.
