Skip to main content

statistical-modeling

Best-practice suggestions for building, interpreting, or reviewing statistical and inferential models in Python — OLS and logistic regression with statsmodels, robust standard errors, regression tables, confidence and prediction intervals, hypothesis tests, interrupted-time-series inference, and significance reporting. Use when estimating effects, interpreting coefficients, comparing explanatory specifications, or asking whether an effect is statistically and practically significant. Use ml-modeling for predictive CV/leaderboards, time-series-forecasting for ARIMA and forecasts, and geospatial-ds for PySAL/spreg. Pair implementation reviews with code-review.

Source facts

Repository
balintdecsi/skills
Last source activity
October 19, 2026 at 15:05
Detected SKILL.md language
English
Stars
0
Forks
1

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
7 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
statistical-modeling
description
Best-practice suggestions for building, interpreting, or reviewing statistical and inferential models in Python — OLS and logistic regression with statsmodels, robust standard errors, regression tables, confidence and prediction intervals, hypothesis tests, interrupted-time-series inference, and significance reporting. Use when estimating effects, interpreting coefficients, comparing explanatory specifications, or asking whether an effect is statistically and practically significant. Use ml-modeling for predictive CV/leaderboards, time-series-forecasting for ARIMA and forecasts, and geospatial-ds for PySAL/spreg. Pair implementation reviews with code-review.
# Statistical Modelling Best Practices These are **suggestions**, not absolute rules. The user is industry-experienced but wants to get sharper at the more academic side of modelling — confidence intervals, prediction intervals, significance tests, and clean regression-table reporting. The patterns here cover that gap. The patterns are inspired by Békés–Kézdi (*Data Analysis for Business, Economics, and Policy*) and CEU coursework: - [gabors-data-analysis/da_case_studies](https://github.com/gabors-data-analysis/da_case_studies) — chapter notebooks for OLS, splines, multiple regression, and prediction ([ch07](https://github.com/gabors-data-analysis/da_case_studies/tree/master/ch07-hotels-simple-reg)–[ch10](https://github.com/gabors-data-analysis/da_case_studies/tree/master/ch10-hotels-multiple-reg), [ch09-hotels-europe-stability](https://github.com/gabors-data-analysis/da_case_studies/tree/master/ch09-hotels-europe-stability), [ch13-used-cars-reg](https://github.com/gabors-data-analysis/da_case_studies/tree/master/ch13-used-cars-reg)). Heavy use of `statsmodels` + `stargazer`. - [da_data_repo on OSF](https://osf.io/3u5em/) — companion datasets for the textbook. - CEU Coding 2 — `intro_to_regression.ipynb` (`smf.ols`, robust SE, `stargazer` side-by-side tables, linear splines); private coursework. - [ch17-predicting-firm-exit](https://github.com/gabors-data-analysis/da_case_studies/tree/master/ch17-predicting-firm-exit) for the link to predictive evaluation under business loss. The inferential-versus-predictive distinction is explained in [reference/inference_vs_prediction.md](reference/inference_vs_prediction.md). ## Default Stack - `statsmodels` — `smf.ols`, `smf.logit`, `sm.OLS`. Use the formula API (`smf`) by default; falls back to `sm.OLS` when you need a design matrix. - `stargazer` — side-by-side regression tables (the package used across [da_case_studies](https://github.com/gabors-data-analysis/da_case_studies) and CEU Coding 2). - `scipy.stats` — t-tests, F-tests, Wilcoxon, etc. when you need raw tests. - `pandas`, `numpy`, and `matplotlib` for data preparation and diagnostics. ## Core Principles 1. **Always report standard errors and confidence intervals**, never just point estimates. 2. **Use robust standard errors by default** for cross-sectional data — `cov_type="HC3"` (or `HC0`/`HC1` to match older textbooks). Don't trust homoscedasticity unless you've checked. 3. **Distinguish confidence interval from prediction interval.** CI is uncertainty around the *expected value*; PI is uncertainty around an *individual observation* and is wider. 4. **Stargazer tables make comparisons honest.** When showing >1 specification, put them side by side with the same dependent variable. 5. **Test significance explicitly** — don't infer from "the coefficient is large". A coefficient is *significantly different from zero* only when the test says so. 6. **Distinguish statistical from practical significance.** A `p < 0.001` effect of $0.02 may be irrelevant. Always report effect size in the units stakeholders understand. 7. **Pre-specify the model.** If you ran 50 specifications and report the 3 with the smallest p-values, you have nothing. ## OLS — the workhorse ```python import statsmodels.formula.api as smf reg = smf.ols("price ~ distance + rating", data=hotels).fit(cov_type="HC3") print(reg.summary()) # Coefficients with 95% CI print(reg.params) print(reg.conf_int(alpha=0.05)) # Coefficient-level test print("p-values:", reg.pvalues) # Joint test of multiple coefficients (F-test) print(reg.f_test("distance = 0, rating = 0")) ``` Common specification choices: | Goal | Specification | |---|---| | Linear | `y ~ x` | | Log-level | `np.log(y) ~ x` (interpret coef as ~% change in y per unit x) | | Level-log | `y ~ np.log(x)` (interpret coef as change in y per 1% change in x ≈ coef/100) | | Log-log (elasticity) | `np.log(y) ~ np.log(x)` (coef = elasticity directly) | | Polynomial | `y ~ x + I(x**2) + I(x**3)` | | Linear spline | `y ~ lspline(x, [knot1, knot2])` (helper from [da_case_studies ch00](https://github.com/gabors-data-analysis/da_case_studies/tree/master/ch00-tech-prep)) | | Categorical | `y ~ C(category)` | | Interaction | `y ~ x * z` (includes `x`, `z`, `x:z`) | The `lspline` / `knot_ceil` helpers live in [snippets/spline_helpers.py](snippets/spline_helpers.py), adapted from [da_helper_functions.py](https://github.com/gabors-data-analysis/da_case_studies/blob/master/ch00-tech-prep/da_helper_functions.py). ## Logistic Regression (when the dependent is binary) ```python logit = smf.logit("default ~ income + age + C(region)", data=df).fit(cov_type="HC3") print(logit.summary()) # Odds ratios with 95% CI import numpy as np ors = np.exp(logit.params) ci = np.exp(logit.conf_int()) print(pd.concat([ors.rename("OR"), ci], axis=1)) ``` For pure prediction with logistic regression at scale, switch to `sklearn.linear_model.LogisticRegression` (`ml-modeling` skill). ## Side-by-side Tables with Stargazer The pattern from [ch13-used-cars-reg](https://github.com/gabors-data-analysis/da_case_studies/tree/master/ch13-used-cars-reg): ```python from stargazer.stargazer import Stargazer reg1 = smf.ols("price ~ distance", data=hotels).fit(cov_type="HC3") reg2 = smf.ols("price ~ distance + rating", data=hotels).fit(cov_type="HC3") reg3 = smf.ols("price ~ distance + rating + C(stars)", data=hotels).fit(cov_type="HC3") sg = Stargazer([reg1, reg2, reg3]) sg.rename_covariates({"distance": "Distance to centre", "rating": "User rating"}) sg.show_model_numbers(False) sg.title("Hotel price models") sg # in Jupyter, renders HTML; .render_latex() for LaTeX ``` A worked template lives in [snippets/stargazer_table.py](snippets/stargazer_table.py). ## Confidence vs Prediction Intervals This trip-up is the single most common one. From [ch09-hotels-europe-stability](https://github.com/gabors-data-analysis/da_case_studies/tree/master/ch09-hotels-europe-stability): - **Confidence interval (CI):** uncertainty around the *fitted value* (i.e. `E[Y | X = x]`). Narrow. - **Prediction interval (PI):** uncertainty around a *single new observation* — adds the residual variance on top. Wider. ```python pred = reg.get_prediction(new_X) summary = pred.summary_frame(alpha=0.05) # columns: mean, mean_se, mean_ci_lower, mean_ci_upper, ← CI on E[Y|X] # obs_ci_lower, obs_ci_upper ← PREDICTION interval on Y ``` When plotting fitted values, draw both intervals as differently shaded bands and label them. See [snippets/intervals_plot.py](snippets/intervals_plot.py). ## Hypothesis Tests You'll Actually Use | Question | Test | |---|---| | Is this single coefficient ≠ 0? | t-test (built into `summary()`) | | Are these k coefficients jointly 0? | F-test: `reg.f_test("a = 0, b = 0")` | | Is the bigger model actually better than the nested one? | Likelihood-ratio test or compare adj-R²/BIC; for nested OLS use `anova_lm(small, big)` | | Are two means / two groups different? | `scipy.stats.ttest_ind` (independent) or `ttest_rel` (paired) | | Same as above but non-normal | `scipy.stats.mannwhitneyu` or `wilcoxon` | | Are two proportions different? | `statsmodels.stats.proportion.proportions_ztest` or chi-square | | Is the residual heteroscedastic? | Breusch–Pagan or White: `het_breuschpagan`, `het_white` | | Is there autocorrelation? | Durbin–Watson (already in `summary()`) or `acorr_ljungbox` | A code catalogue with one-liners for each lives in [reference/tests_catalogue.md](reference/tests_catalogue.md). ## When You Compare *Models* (not coefficients) This is the bridge to `ml-modeling`: - **Nested OLS models (one is a subset of the other):** use F-test via `statsmodels.stats.anova.anova_lm(small, big)`. - **Non-nested:** compare on adj-R², AIC, BIC. The model with lower AIC/BIC wins; there's no formal test. - **Predictive ML models on the same CV folds:** use the **paired t-test on per-fold scores** pattern from the `ml-modeling` skill. ## Significance Reporting Etiquette When reporting an effect, give the reader four things in one sentence: > The coefficient on `experience` is **0.034 log-points** (95% CI: 0.022–0.046; p < 0.001), implying an additional year of experience is associated with roughly **3.4% higher wages**. That's: estimate, CI, p-value, plain-English interpretation. **Always include the unit and the practical magnitude.** A p-value alone is not a finding. ## Anti-Patterns to Flag - Reporting only the point estimate, no SE / CI. - Using default homoscedastic SE on cross-sectional data without checking residuals. - Reporting `R² = 0.87` without showing test/holdout performance — high R² on training data is uninformative. - Picking the "best" specification after looking at p-values across many runs (HARKing / p-hacking). - Calling a coefficient "significant" without saying at what α and with what test. - Confusing confidence interval with prediction interval in plots. - Comparing models by R² alone — penalise complexity (adj-R², BIC) or compare on a held-out set. - Treating logistic-regression coefficients as probabilities (they're log-odds). ## Data Preparation Utilities Standardise column names early (`df.columns = df.columns.str.replace('-', '_').str.replace(' ', '_').str.lower()`). For full column-locking helpers (`name_lock`, `type_lock`), see the `analytics-project-setup` skill. ## Code Snippets In [snippets/](snippets/): - `spline_helpers.py` — `knot_ceil` and `lspline` from [da_helper_functions.py](https://github.com/gabors-data-analysis/da_case_studies/blob/master/ch00-tech-prep/da_helper_functions.py) for use inside `smf.ols("y ~ lspline(x, [...])", ...)`. - `stargazer_table.py` — full stargazer table template with renamed covariates, custom rows, BIC/AIC footer. - `intervals_plot.py` — fitted line + confidence band + prediction band for OLS, properly labeled. - `regression_diagnostics.py` — residual plots, heteroscedasticity tests, influence diagnostics in one call. ## Data Sources Used in the Inspiration Courses Almost all worked examples across [da_case_studies](https://github.com/gabors-data-analysis/da_case_studies), [da_data_repo on OSF](https://osf.io/3u5em/), and CEU Coding 2 come from **Békés & Kézdi, *Data Analysis for Business, Economics, and Policy*** (Cambridge, 2021). The companion data is openly available and is the single best collection of cleanly-documented small/medium datasets for inferential modeling practice: | Resource | What | URL | |---|---|---| | Book site (datasets, code in R/Python/Stata, case studies) | Hub for everything | <https://gabors-data-analysis.com/> | | `da_data_repo` (clean datasets; no raw data) | Hosted on OSF — the GitHub mirror is gone | <https://osf.io/3u5em/> · parent project <https://osf.io/7epdj/> | | `da_case_studies` (chapter notebooks) | End-to-end workflows per chapter | <https://github.com/gabors-data-analysis/da_case_studies> | Specific datasets you'll keep returning to (all small, all interpretable): | Dataset | Typical use | Direct URL | |---|---|---| | `hotels-europe` (Vienna subset) | OLS on hotel price; log specs; splines; CIs vs PIs | <https://osf.io/r6uqb/> | | `cps-earnings` (CPS earnings) | Mincer-style log-wage regression, gender gap, robust SE | <https://osf.io/g8p9j/> | | `wms-management-survey` | Cross-country firm management practices regressions | <https://osf.io/uzpce/> | | `used-cars` | Hedonic price regression with feature engineering | <https://osf.io/7gvz9/> | | `share-health` | Logistic regression on health outcomes | <https://osf.io/yhdte/> | | `bisnode-firms` | Probability prediction (probit/logit), classification thresholds with business loss | <https://osf.io/3qyut/> | | `football-managers` | Difference-in-differences, event studies | <https://osf.io/r2psv/> | Other free, well-suited sources for inferential exercises: | Source | What | URL | |---|---|---| | Statsmodels built-ins | `sm.datasets.{anes96, fair, longley, macrodata, statecrime, ...}` for quick OLS/Logit demos | `import statsmodels.api as sm; sm.datasets.longley.load_pandas().data` | | Seaborn datasets | `tips`, `diamonds`, `mpg`, `penguins` — small enough to teach, big enough to find effects | `import seaborn as sns; sns.load_dataset("diamonds")` | | OpenML | Tabular datasets with metadata; great for replication exercises | <https://www.openml.org/> | | World Bank Open Data | Country-year panels for cross-country regressions | <https://data.worldbank.org/> |
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub