Model formulas

statsmodels · Essentials · 10 min

Formulas handle intercepts, categories, transformations and interactions in one readable string.

statsmodels.formula.api (imported as smf) fits models from a formula and a DataFrame: smf.ols("price ~ area + rooms", data=df).fit(). The left of ~ is the response, the right lists predictors separated by +.

The formula engine (formulaic in current versions, patsy in older ones) builds the design matrix for you:

  • an intercept is included automatically; remove it with - 1 or + 0,
  • string or category columns are dummy-encoded automatically; force it for numeric codes with C(zip_code),
  • functions apply inline: np.log(income), center(x), standardize(x),
  • arithmetic needs I(...), because + and * have formula meanings: I(area ** 2), I(price / 1000),
  • a:b is the interaction only, a * b means a + b + a:b.

Model classes have lowercase formula versions: smf.ols, smf.logit, smf.glm, smf.poisson, smf.mixedlm, smf.quantreg. Rows with missing values in used columns are dropped by default; compare res.nobs with len(df) to notice.

The formula is stored with the model, so res.predict(new_df) applies the same transformations, including the same dummy columns, to new data. That makes formulas the most convenient way to work with statsmodels on DataFrames.

Example

import numpy as np
import pandas as pd
import statsmodels.formula.api as smf

rng = np.random.default_rng(1)
df = pd.DataFrame({"area": rng.uniform(40, 150, 120),
                   "city": rng.choice(["Oslo", "Rome", "Lima"], 120)})
bonus = df["city"].map({"Lima": 0, "Oslo": 60, "Rome": 30})
df["price"] = 50 + 2 * df["area"] + bonus + rng.normal(0, 10, 120)

res = smf.ols("price ~ area + C(city)", data=df).fit()
print(res.params.round(1).to_dict())
# {'Intercept': 43.4, 'C(city)[T.Oslo]': 61.6, 'C(city)[T.Rome]': 31.4, 'area': 2.0}

res2 = smf.ols("np.log(price) ~ area + I(area ** 2)", data=df).fit()
print(list(res2.params.index))   # ['Intercept', 'area', 'I(area ** 2)']

new = pd.DataFrame({"area": [100], "city": ["Rome"]})
print(res.predict(new).round(1).tolist())   # [279.0]

Remember

  • smf.ols("y ~ a + b", data=df) adds the intercept and encodes categories
  • Use C() for categorical codes and I() for arithmetic
  • predict(new_df) re-applies the formula's transformations

Watch out for

  • Writing y ~ x**2 instead of y ~ I(x**2), which does not square x.

Check yourself

  1. What does the formula `y ~ a * b` fit?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.statsmodels.org/stable/example_formulas.html