Skill Tracer
Skills › Scientific Agent Skills › statsmodels › model_selection.md

model_selection.md

107 lines · skills/statsmodels/references/model_selection.md at 5f2bc03

Open on GitHub
1# Formula API and Model Selection
2
3The R-style formula API, then model selection and comparison: information criteria,
4nested-model tests, and cross-validation caveats for statistical models.
5

Formula API and Model Selection

The R-style formula API, then model selection and comparison: information criteria, nested-model tests, and cross-validation caveats for statistical models.

Raw lines 1 to 5
6## Formula API (R-style)
7
8Statsmodels supports R-style formulas for intuitive model specification:
9
10```python
11import statsmodels.formula.api as smf
13# OLS with formula
14results = smf.ols('y ~ x1 + x2 + x1:x2', data=df).fit()
16# Categorical variables (automatic dummy coding)
17results = smf.ols('y ~ x1 + C(category)', data=df).fit()
19# Interactions
20results = smf.ols('y ~ x1 * x2', data=df).fit() # x1 + x2 + x1:x2
22# Polynomial terms
23results = smf.ols('y ~ x + I(x**2)', data=df).fit()
25# Logit
26results = smf.logit('y ~ x1 + x2 + C(group)', data=df).fit()
28# Poisson
29results = smf.poisson('count ~ x1 + x2', data=df).fit()
31# ARIMA (not available via formula, use regular API)
32```

Formula API (R-style)

Statsmodels supports R-style formulas for intuitive model specification:

import statsmodels.formula.api as smf

# OLS with formula
results = smf.ols('y ~ x1 + x2 + x1:x2', data=df).fit()

# Categorical variables (automatic dummy coding)
results = smf.ols('y ~ x1 + C(category)', data=df).fit()

# Interactions
results = smf.ols('y ~ x1 * x2', data=df).fit()  # x1 + x2 + x1:x2

# Polynomial terms
results = smf.ols('y ~ x + I(x**2)', data=df).fit()

# Logit
results = smf.logit('y ~ x1 + x2 + C(group)', data=df).fit()

# Poisson
results = smf.poisson('count ~ x1 + x2', data=df).fit()

# ARIMA (not available via formula, use regular API)
Raw lines 6 to 33
34## Model Selection and Comparison
36Use identical response definitions and observations with comparable likelihoods. Formula missing-value handling can silently change the sample: select/record common rows first. Compare fixed effects in mixed models under ML, not REML. GLM likelihood BIC is `bic_llf`; deviance BIC is a different convention. Regular LR chi-square inference excludes boundary cases such as Poisson vs NB alpha=0, separation and unidentified parameters.
38Do not interpret a p-value after model/variable selection as though the model were prespecified.

Model Selection and Comparison

Use identical response definitions and observations with comparable likelihoods. Formula missing-value handling can silently change the sample: select/record common rows first. Compare fixed effects in mixed models under ML, not REML. GLM likelihood BIC is bic_llf; deviance BIC is a different convention. Regular LR chi-square inference excludes boundary cases such as Poisson vs NB alpha=0, separation and unidentified parameters.

Do not interpret a p-value after model/variable selection as though the model were prespecified.

Raw lines 34 to 39
40### Information Criteria
42```python
43# Compare models using AIC/BIC
44models = {
45 'Model 1': model1_results,
46 'Model 2': model2_results,
47 'Model 3': model3_results
50comparison = pd.DataFrame({
51 'AIC': {name: res.aic for name, res in models.items()},
52 'BIC': {name: res.bic for name, res in models.items()},
53 'Log-Likelihood': {name: res.llf for name, res in models.items()}
54})
56print(comparison.sort_values('AIC'))
57# Lower values rank comparable candidates; they do not establish absolute adequacy
58```

Information Criteria

# Compare models using AIC/BIC
models = {
    'Model 1': model1_results,
    'Model 2': model2_results,
    'Model 3': model3_results
}

comparison = pd.DataFrame({
    'AIC': {name: res.aic for name, res in models.items()},
    'BIC': {name: res.bic for name, res in models.items()},
    'Log-Likelihood': {name: res.llf for name, res in models.items()}
})

print(comparison.sort_values('AIC'))
# Lower values rank comparable candidates; they do not establish absolute adequacy
Raw lines 40 to 59
60### Likelihood Ratio Test (Nested Models)
62```python
63# For nested models (one is subset of the other)
64from scipy import stats
66lr_stat = 2 * (full_model.llf - reduced_model.llf)
67df_diff = full_model.df_model - reduced_model.df_model
68if df_diff <= 0:
69 raise ValueError("Expected a larger full model")
70p_value = stats.chi2.sf(lr_stat, df_diff)
72print(f"LR statistic: {lr_stat:.4f}")
73print(f"p-value: {p_value:.4f}")
75if p_value < 0.05:
76 print("Reject the specified restrictions under regular LR assumptions")
77else:
78 print("No rejection of the specified restrictions; this is not equivalence")
79```

Likelihood Ratio Test (Nested Models)

# For nested models (one is subset of the other)
from scipy import stats

lr_stat = 2 * (full_model.llf - reduced_model.llf)
df_diff = full_model.df_model - reduced_model.df_model
if df_diff <= 0:
    raise ValueError("Expected a larger full model")
p_value = stats.chi2.sf(lr_stat, df_diff)

print(f"LR statistic: {lr_stat:.4f}")
print(f"p-value: {p_value:.4f}")

if p_value < 0.05:
    print("Reject the specified restrictions under regular LR assumptions")
else:
    print("No rejection of the specified restrictions; this is not equivalence")
Raw lines 60 to 80
81### Cross-Validation
83The example assumes independent observations and a numeric pandas design with an intercept. Use grouped or time-ordered splits for dependent data and fit preprocessing within each training fold. Scikit-learn LogisticRegression is regularized by default and is not an interchangeable statsmodels MLE wrapper.
85```python
86from sklearn.model_selection import KFold
87from sklearn.metrics import mean_squared_error
89kf = KFold(n_splits=5, shuffle=True, random_state=42)
90cv_scores = []
92for train_idx, val_idx in kf.split(X):
93 X_train, X_val = X.iloc[train_idx], X.iloc[val_idx]
94 y_train, y_val = y.iloc[train_idx], y.iloc[val_idx]
96 # Fit model
97 model = sm.OLS(y_train, X_train).fit()
99 # Predict
100 y_pred = model.predict(X_val)
102 # Score
103 rmse = np.sqrt(mean_squared_error(y_val, y_pred))
104 cv_scores.append(rmse)
106print(f"CV RMSE: {np.mean(cv_scores):.4f} ± {np.std(cv_scores):.4f}")
107```

Cross-Validation

The example assumes independent observations and a numeric pandas design with an intercept. Use grouped or time-ordered splits for dependent data and fit preprocessing within each training fold. Scikit-learn LogisticRegression is regularized by default and is not an interchangeable statsmodels MLE wrapper.

from sklearn.model_selection import KFold
from sklearn.metrics import mean_squared_error

kf = KFold(n_splits=5, shuffle=True, random_state=42)
cv_scores = []

for train_idx, val_idx in kf.split(X):
    X_train, X_val = X.iloc[train_idx], X.iloc[val_idx]
    y_train, y_val = y.iloc[train_idx], y.iloc[val_idx]

    # Fit model
    model = sm.OLS(y_train, X_train).fit()

    # Predict
    y_pred = model.predict(X_val)

    # Score
    rmse = np.sqrt(mean_squared_error(y_val, y_pred))
    cv_scores.append(rmse)

print(f"CV RMSE: {np.mean(cv_scores):.4f} ± {np.std(cv_scores):.4f}")
Raw lines 81 to 107