--- title: "Linear Models and Mixed-Effects Reference" task: "" lineage_type: import upstream_source: https://github.com/mims-harvard/ToolUniverse/blob/e2520a96/skills/tooluniverse-statistical-modeling/references/linear_models.md upstream_sha: e2520a96 imported_at: 2026-06-26 prompt_class: prompt upstream_changes: accepted author: upstream validated: false --- # Linear Models and Mixed-Effects Reference Complete guide to linear regression and mixed-effects models. ## Ordinary Least Squares (OLS) Regression ### Basic Linear Regression ```python import pandas as pd import statsmodels.api as sm import statsmodels.formula.api as smf # Method 1: Formula API (recommended) model = smf.ols('outcome ~ predictor1 + predictor2 + age', data=df).fit() # Method 2: Matrix API X = sm.add_constant(df[['predictor1', 'predictor2', 'age']]) y = df['outcome'] model = sm.OLS(y, X).fit() # Print summary print(model.summary()) ``` ### Interpreting Coefficients ```python # Extract coefficients coefs = model.params se = model.bse t_vals = model.tvalues p_vals = model.pvalues ci = model.conf_int() for var in coefs.index: print(f"\n{var}:") print(f" Coefficient: {coefs[var]:.4f}") print(f" Std Error: {se[var]:.4f}") print(f" t-value: {t_vals[var]:.4f}") print(f" p-value: {p_vals[var]:.6f}") print(f" 95% CI: ({ci.loc[var, 0]:.4f}, {ci.loc[var, 1]:.4f})") ``` ### Model Fit Statistics ```python # R-squared print(f"R-squared: {model.rsquared:.4f}") print(f"Adjusted R-squared: {model.rsquared_adj:.4f}") # F-statistic print(f"F-statistic: {model.fvalue:.4f}") print(f"F-test p-value: {model.f_pvalue:.6f}") # AIC/BIC print(f"AIC: {model.aic:.2f}") print(f"BIC: {model.bic:.2f}") # Root mean squared error rmse = np.sqrt(model.mse_resid) print(f"RMSE: {rmse:.4f}") ``` ## Diagnostics ### Residual Normality ```python from scipy import stats as scipy_stats # Shapiro-Wilk test residuals = model.resid sw_stat, sw_p = scipy_stats.shapiro(residuals) print(f"Shapiro-Wilk test:") print(f" Statistic: {sw_stat:.4f}") print(f" p-value: {sw_p:.6f}") if sw_p > 0.05: print(" ✅ Residuals appear normally distributed") else: print(" ⚠️ Residuals may not be normally distributed") ``` ### Homoscedasticity (Equal Variance) ```python from statsmodels.stats.diagnostic import het_breuschpagan # Breusch-Pagan test bp_stat, bp_p, _, _ = het_breuschpagan(residuals, model.model.exog) print(f"\nBreusch-Pagan test:") print(f" Statistic: {bp_stat:.4f}") print(f" p-value: {bp_p:.6f}") if bp_p > 0.05: print(" ✅ Homoscedasticity assumption met") else: print(" ⚠️ Heteroscedasticity detected") print(" Consider: robust standard errors, log transformation, or WLS") ``` ### Autocorrelation ```python from statsmodels.stats.stattools import durbin_watson # Durbin-Watson test dw = durbin_watson(residuals) print(f"\nDurbin-Watson statistic: {dw:.4f}") if 1.5 < dw < 2.5: print(" ✅ No autocorrelation detected") else: print(" ⚠️ Possible autocorrelation") ``` ### Multicollinearity (VIF) ```python from statsmodels.stats.outliers_influence import variance_inflation_factor X = model.model.exog vif_data = [] for i in range(X.shape[1]): var_name = model.model.exog_names[i] if var_name != 'Intercept': vif = variance_inflation_factor(X, i) vif_data.append({'variable': var_name, 'VIF': vif}) vif_df = pd.DataFrame(vif_data) print("\nVariance Inflation Factors:") print(vif_df) # Rule of thumb: VIF > 10 indicates multicollinearity if (vif_df['VIF'] > 10).any(): print("\n⚠️ High multicollinearity detected (VIF > 10)") print("Consider: removing correlated predictors or PCA") ``` ## Linear Mixed-Effects Models (LMM) ### When to Use LMM Use LMM when: - **Repeated measures** (same subject measured multiple times) - **Nested/clustered data** (patients within hospitals) - **Hierarchical structure** (students within schools) - **Need to model between-subject and within-subject variation** ### Random Intercept Model ```python import statsmodels.formula.api as smf # Random intercepts for subjects model = smf.mixedlm('outcome ~ treatment + time', data=df, groups=df['subject_id']) fit = model.fit(reml=True) print(fit.summary()) ``` ### Random Slope Model ```python # Random slopes for time (subjects have different time trends) model = smf.mixedlm('outcome ~ treatment + time', data=df, groups=df['subject_id'], re_formula='~time') fit = model.fit(reml=True) ``` ### Fixed Effects Interpretation ```python # Extract fixed effects fe_params = fit.fe_params fe_pvalues = fit.pvalues fe_ci = fit.conf_int() print("Fixed Effects:") for var in fe_params.index: coef = fe_params[var] p_val = fe_pvalues[var] ci_lower = fe_ci.loc[var, 0] ci_upper = fe_ci.loc[var, 1] print(f"\n{var}:") print(f" Coefficient: {coef:.4f}") print(f" 95% CI: ({ci_lower:.4f}, {ci_upper:.4f})") print(f" p-value: {p_val:.6f}") ``` ### Random Effects Variance ```python # Group (subject) variance cov_re = fit.cov_re if hasattr(cov_re, 'values'): group_var = cov_re.iloc[0, 0] else: group_var = float(cov_re) # Residual variance resid_var = fit.scale print(f"\nRandom Effects Variance:") print(f" Between-subject (Group): {group_var:.4f}") print(f" Within-subject (Residual): {resid_var:.4f}") ``` ### Intraclass Correlation Coefficient (ICC) ```python # ICC: proportion of variance due to grouping icc = group_var / (group_var + resid_var) print(f"\nICC: {icc:.4f}") print(f"Interpretation: {icc*100:.1f}% of variance is between subjects") if icc > 0.1: print(" ✅ Substantial clustering - LMM appropriate") else: print(" ⚠️ Low clustering - OLS may be sufficient") ``` ## Complete Example: Longitudinal Study ```python import pandas as pd import numpy as np import statsmodels.formula.api as smf # Simulated longitudinal data np.random.seed(42) n_subjects = 50 n_timepoints = 4 data = [] for subject_id in range(n_subjects): treatment = np.random.binomial(1, 0.5) baseline = np.random.normal(100, 10) for time in range(n_timepoints): # Treatment effect + time trend + random noise outcome = baseline + treatment * 5 + time * 2 + np.random.normal(0, 3) data.append({ 'subject_id': subject_id, 'treatment': treatment, 'time': time, 'outcome': outcome }) df = pd.DataFrame(data) print("=== Longitudinal Analysis ===\n") # 1. OLS (ignoring repeated measures - WRONG) print("1. OLS (incorrect for repeated measures):\n") ols_model = smf.ols('outcome ~ treatment + time', data=df).fit() print(f"Treatment effect: {ols_model.params['treatment']:.4f}") print(f"p-value: {ols_model.pvalues['treatment']:.6f}") print(f"R-squared: {ols_model.rsquared:.4f}\n") # 2. LMM with random intercepts (correct) print("2. LMM with random intercepts (correct):\n") lmm_model = smf.mixedlm('outcome ~ treatment + time', data=df, groups=df['subject_id']) lmm_fit = lmm_model.fit(reml=True) print(f"Treatment effect: {lmm_fit.fe_params['treatment']:.4f}") print(f"p-value: {lmm_fit.pvalues['treatment']:.6f}") # ICC group_var = float(lmm_fit.cov_re.iloc[0, 0]) resid_var = float(lmm_fit.scale) icc = group_var / (group_var + resid_var) print(f"ICC: {icc:.4f}\n") # 3. LMM with random slopes (allow different time trends) print("3. LMM with random slopes:\n") lmm_slopes = smf.mixedlm('outcome ~ treatment + time', data=df, groups=df['subject_id'], re_formula='~time') lmm_slopes_fit = lmm_slopes.fit(reml=True) print(f"Treatment effect: {lmm_slopes_fit.fe_params['treatment']:.4f}") print(f"p-value: {lmm_slopes_fit.pvalues['treatment']:.6f}\n") # Model comparison print("4. Model Comparison:") print(f"Random intercepts AIC: {lmm_fit.aic:.2f}") print(f"Random slopes AIC: {lmm_slopes_fit.aic:.2f}") if lmm_slopes_fit.aic < lmm_fit.aic: print("Random slopes model preferred (lower AIC)") else: print("Random intercepts model sufficient") ``` ## Weighted Least Squares (WLS) ### When to Use WLS Use WLS when: - Heteroscedasticity detected - Known different precision across observations - Want to downweight outliers ```python # Estimate weights from variance model # Common approach: inverse variance weighting residuals_sq = model.resid ** 2 weights = 1 / residuals_sq # Fit WLS wls_model = sm.WLS(y, X, weights=weights).fit() print(wls_model.summary()) ``` ## Robust Standard Errors ### Heteroscedasticity-Consistent Standard Errors ```python # Use robust standard errors (HC3) model_robust = model.get_robustcov_results(cov_type='HC3') print("Robust Standard Errors:") print(model_robust.summary()) ``` ## Generalized Estimating Equations (GEE) ### Alternative to LMM for Clustered Data ```python import statsmodels.api as sm from statsmodels.genmod.generalized_estimating_equations import GEE from statsmodels.genmod.families import Gaussian from statsmodels.genmod.cov_struct import Exchangeable # GEE for continuous outcome gee_model = GEE.from_formula('outcome ~ treatment + time', groups='subject_id', data=df, family=Gaussian(), cov_struct=Exchangeable()) gee_fit = gee_model.fit() print(gee_fit.summary()) ``` **GEE vs LMM**: - **GEE**: Population-averaged effects, requires weaker assumptions - **LMM**: Subject-specific effects, models random effects explicitly - Use GEE when interested in marginal effects, LMM when interested in individual trajectories ## Polynomial and Spline Models ### Polynomial Regression ```python # Add polynomial terms df['age_sq'] = df['age'] ** 2 df['age_cube'] = df['age'] ** 3 model = smf.ols('outcome ~ age + age_sq + age_cube', data=df).fit() ``` ### Natural Cubic Splines ```python from patsy import dmatrix # Create spline basis spline_basis = dmatrix("bs(age, df=4, degree=3)", df, return_type='dataframe') X_spline = pd.concat([spline_basis, df[['treatment']]], axis=1) y = df['outcome'] model_spline = sm.OLS(y, X_spline).fit() ``` ## Reporting Template ```python def report_linear_model(model): """Generate publication-ready linear model report.""" report = [] report.append("=== Linear Regression Results ===\n") report.append(f"N = {int(model.nobs)}") report.append(f"R² = {model.rsquared:.4f}") report.append(f"Adjusted R² = {model.rsquared_adj:.4f}") report.append(f"F({model.df_model:.0f}, {model.df_resid:.0f}) = {model.fvalue:.4f}, p = {model.f_pvalue:.6f}\n") report.append("Coefficients:\n") report.append("Variable | Coef | SE | t | p | 95% CI") report.append("---------|------|----|----|---|-------") for var in model.params.index: coef = model.params[var] se = model.bse[var] t_val = model.tvalues[var] p_val = model.pvalues[var] ci = model.conf_int().loc[var] sig = "*" if p_val < 0.05 else "" report.append(f"{var} | {coef:.4f} | {se:.4f} | {t_val:.4f} | {p_val:.6f}{sig} | ({ci[0]:.4f}, {ci[1]:.4f})") return "\n".join(report) print(report_linear_model(model)) ```