Linear Regression
When to use this skill
Use for continuous outcome prediction or explanation where interpretability matters. Triggers:
- "Predict revenue / session time / NPS"
- "What drives ?"
- "Linear regression for…"
- "OLS"
- "Explain the variation in…"
For binary outcomes use logistic-regression. For time-to-event use survival-analysis. For pure prediction with non-linear effects, fit linear first as baseline, then suggest gradient boosting.
Required inputs
| Input |
Why it matters |
| Continuous target |
What you're predicting (numeric, ideally not heavily skewed) |
| Feature set |
Predictors |
| Observation grain |
Per user / per session / per geography |
| Train/test strategy |
Temporal split for production, random for exploratory |
| Goal |
Pure prediction, coefficient interpretation, or both? |
Workflow
Audit the data (data-quality-audit skill).
Inspect the target distribution.
import matplotlib.pyplot as plt
df[target].hist(bins=50)
df[target].describe()
- Symmetric, finite variance → OK for OLS
- Heavily right-skewed (revenue, time-on-page) → consider
log(1 + target) transformation
- Heavy-tailed with extreme outliers → robust regression or winsorize
Check for outliers at p99 and p99.9. Decide:
- Cap at p99 (winsorize)
- Drop with documented justification
- Keep and use robust regression
Verify no leakage (same checks as logistic-regression).
Split temporally (or random for exploratory).
Fit baseline OLS with all features. Use statsmodels for coefficient inference, sklearn for production fitting.
Check assumptions (diagnostics matter more than for logistic):
- Linearity: residuals vs predicted should look random
- Homoscedasticity: residual variance constant across predicted values
- Normality of residuals: Q-Q plot
- No multicollinearity: VIF < 5 (warn at 5-10, fail at > 10)
- Independence: no autocorrelation (for time-series)
Evaluate on holdout:
- RMSE (in original units — interpretable to stakeholders)
- MAE (more robust to outliers)
- R² (variance explained — but easily inflated, don't worship)
- Out-of-time R² is the real test
Interpret coefficients.
- Standardized coefficients to compare features
- Report 95% CIs (statsmodels gives these)
- Watch for sign flips when adding/removing correlated features (multicollinearity sign)
Write the readout.
Output format
# Linear Regression: <outcome>
## Setup
- Outcome: <target> (unit: <e.g., dollars per user>)
- Distribution: mean <X>, median <Y>, p99 <Z> (transformation: <none | log(1+y) | winsorize@p99>)
- Observation unit: <user / session / geo>
- Sample: <N_train> train, <N_test> test (split: <temporal at YYYY-MM-DD | random>)
- Features: <N> features
## Performance (test set)
| Metric | Value | Notes |
|---|---|---|
| RMSE | 18.4 | Target std = 27.1 |
| MAE | 12.1 | |
| R² | 0.47 | |
| R² out-of-time | 0.42 | |
## Coefficients (top by |effect|, standardized)
| Feature | β | SE | t | 95% CI | β std | Interpretation |
|---|---|---|---|---|---|---|
| tenure_months | +1.84 | 0.12 | 15.3 | [+1.60, +2.08] | +0.41 | +1 month → +$1.84 revenue |
| has_team_plan | +24.10 | 1.42 | 16.9 | [+21.3, +26.9] | +0.38 | team plan → +$24.10 revenue |
| device_mobile_pct | -0.32 | 0.04 | -8.0 | [-0.40, -0.24] | -0.18 | 1pp more mobile → -$0.32 revenue |
| ... | | | | | | |
## Diagnostics
- Residuals vs predicted: <looks random | shows funnel pattern (heteroscedasticity) | shows curvature (non-linearity)>
- Q-Q plot: <residuals roughly normal | heavy tails>
- VIF: <max VIF = 3.1 (OK) | max VIF = 12.4 (multicollinearity warning)>
- Autocorrelation: <Durbin-Watson = 1.94 (OK)>
## Caveats
- <e.g., predictions outside the training range of feature X are extrapolation; not reliable>
- <e.g., model assumes effects are linear; non-linear effects detected for feature Y>
- <e.g., R² inflated by 0.07 vs out-of-time R²; mild overfitting>
## Next steps
- <e.g., target seasonal effects with interaction terms>
- <e.g., investigate residual cluster around segment Z>
- <e.g., if pure prediction matters, benchmark vs gradient boosting>
Validation checks
Edge cases & failure modes
- Heavy-tailed target (revenue): OLS gives undue weight to outliers. Use log-transform or quantile regression.
- Zero-inflated target: many zeros + continuous positives (e.g., revenue with many free users). Use two-part model: P(zero) via logistic, then OLS on positive values.
- Multicollinearity: coefficients become unstable. Drop one of correlated pair, or use ridge regression.
- Non-linear relationships: residuals show U-shape vs predicted. Add polynomial terms or use a tree-based model.
- Heteroscedasticity: residuals fan out at higher predicted values. Coefficient SEs are wrong. Use HC3 robust SEs (
cov_type='HC3' in statsmodels).
- Time-series autocorrelation: residuals from consecutive periods correlated. SEs are too small. Use Newey-West SEs.
Scripts
scripts/fit_linear.py — End-to-end OLS fit + diagnostics + report.
python scripts/fit_linear.py \
--input data.csv \
--target revenue_per_user \
--features feature_list.txt \
--transform log1p \
--split-by month --split-date 2026-03-01
Related skills
data-quality-audit — run before fitting
logistic-regression — binary outcomes
causal-inference — when you need effect estimation, not just association
stakeholder-readout — for packaging the model output
1---2name: linear-regression3description: Fits, evaluates, and interprets linear regression for continuous outcomes (revenue, session time, NPS scores) with residual diagnostics and assumption checks. Use when the user mentions linear regression, OLS, continuous outcome, "predict <numeric KPI>", coefficient interpretation, R-squared, or regression diagnostics.4---56# Linear Regression78## When to use this skill910Use for **continuous outcome prediction or explanation** where interpretability matters. Triggers:1112- "Predict revenue / session time / NPS"13- "What drives <continuous metric>?"14- "Linear regression for…"15- "OLS"16- "Explain the variation in…"1718For binary outcomes use `logistic-regression`. For time-to-event use `survival-analysis`. For pure prediction with non-linear effects, fit linear first as baseline, then suggest gradient boosting.1920## Required inputs2122| Input | Why it matters |23|---|---|24| Continuous target | What you're predicting (numeric, ideally not heavily skewed) |25| Feature set | Predictors |26| Observation grain | Per user / per session / per geography |27| Train/test strategy | Temporal split for production, random for exploratory |28| Goal | Pure prediction, coefficient interpretation, or both? |2930## Workflow31321. **Audit the data** (`data-quality-audit` skill).33342. **Inspect the target distribution.**35 ```python36 import matplotlib.pyplot as plt37 df[target].hist(bins=50)38 df[target].describe()39 ```40 - Symmetric, finite variance → OK for OLS41 - Heavily right-skewed (revenue, time-on-page) → consider `log(1 + target)` transformation42 - Heavy-tailed with extreme outliers → robust regression or winsorize43443. **Check for outliers** at p99 and p99.9. Decide:45 - Cap at p99 (winsorize)46 - Drop with documented justification47 - Keep and use robust regression48494. **Verify no leakage** (same checks as logistic-regression).50515. **Split temporally** (or random for exploratory).52536. **Fit baseline OLS** with all features. Use `statsmodels` for coefficient inference, `sklearn` for production fitting.54557. **Check assumptions** (diagnostics matter more than for logistic):56 - **Linearity**: residuals vs predicted should look random57 - **Homoscedasticity**: residual variance constant across predicted values58 - **Normality of residuals**: Q-Q plot59 - **No multicollinearity**: VIF < 5 (warn at 5-10, fail at > 10)60 - **Independence**: no autocorrelation (for time-series)61628. **Evaluate on holdout:**63 - **RMSE** (in original units — interpretable to stakeholders)64 - **MAE** (more robust to outliers)65 - **R²** (variance explained — but easily inflated, don't worship)66 - **Out-of-time R²** is the real test67689. **Interpret coefficients.**69 - Standardized coefficients to compare features70 - Report 95% CIs (statsmodels gives these)71 - Watch for sign flips when adding/removing correlated features (multicollinearity sign)727310. **Write the readout.**7475## Output format7677```markdown78# Linear Regression: <outcome>7980## Setup81- Outcome: <target> (unit: <e.g., dollars per user>)82- Distribution: mean <X>, median <Y>, p99 <Z> (transformation: <none | log(1+y) | winsorize@p99>)83- Observation unit: <user / session / geo>84- Sample: <N_train> train, <N_test> test (split: <temporal at YYYY-MM-DD | random>)85- Features: <N> features8687## Performance (test set)88| Metric | Value | Notes |89|---|---|---|90| RMSE | 18.4 | Target std = 27.1 |91| MAE | 12.1 | |92| R² | 0.47 | |93| R² out-of-time | 0.42 | |9495## Coefficients (top by |effect|, standardized)96| Feature | β | SE | t | 95% CI | β std | Interpretation |97|---|---|---|---|---|---|---|98| tenure_months | +1.84 | 0.12 | 15.3 | [+1.60, +2.08] | +0.41 | +1 month → +$1.84 revenue |99| has_team_plan | +24.10 | 1.42 | 16.9 | [+21.3, +26.9] | +0.38 | team plan → +$24.10 revenue |100| device_mobile_pct | -0.32 | 0.04 | -8.0 | [-0.40, -0.24] | -0.18 | 1pp more mobile → -$0.32 revenue |101| ... | | | | | | |102103## Diagnostics104- Residuals vs predicted: <looks random | shows funnel pattern (heteroscedasticity) | shows curvature (non-linearity)>105- Q-Q plot: <residuals roughly normal | heavy tails>106- VIF: <max VIF = 3.1 (OK) | max VIF = 12.4 (multicollinearity warning)>107- Autocorrelation: <Durbin-Watson = 1.94 (OK)>108109## Caveats110- <e.g., predictions outside the training range of feature X are extrapolation; not reliable>111- <e.g., model assumes effects are linear; non-linear effects detected for feature Y>112- <e.g., R² inflated by 0.07 vs out-of-time R²; mild overfitting>113114## Next steps115- <e.g., target seasonal effects with interaction terms>116- <e.g., investigate residual cluster around segment Z>117- <e.g., if pure prediction matters, benchmark vs gradient boosting>118```119120## Validation checks121122- [ ] Target distribution inspected; transformation decision documented123- [ ] Outliers checked and policy stated124- [ ] No leakage in features125- [ ] Residual diagnostics ran (linearity, homoscedasticity, normality)126- [ ] VIF checked for multicollinearity127- [ ] CIs reported alongside coefficients128- [ ] Out-of-time R² compared to in-sample R²129130## Edge cases & failure modes131132- **Heavy-tailed target (revenue)**: OLS gives undue weight to outliers. Use log-transform or quantile regression.133- **Zero-inflated target**: many zeros + continuous positives (e.g., revenue with many free users). Use two-part model: P(zero) via logistic, then OLS on positive values.134- **Multicollinearity**: coefficients become unstable. Drop one of correlated pair, or use ridge regression.135- **Non-linear relationships**: residuals show U-shape vs predicted. Add polynomial terms or use a tree-based model.136- **Heteroscedasticity**: residuals fan out at higher predicted values. Coefficient SEs are wrong. Use HC3 robust SEs (`cov_type='HC3'` in statsmodels).137- **Time-series autocorrelation**: residuals from consecutive periods correlated. SEs are too small. Use Newey-West SEs.138139## Scripts140141- `scripts/fit_linear.py` — End-to-end OLS fit + diagnostics + report.142143```bash144python scripts/fit_linear.py \145 --input data.csv \146 --target revenue_per_user \147 --features feature_list.txt \148 --transform log1p \149 --split-by month --split-date 2026-03-01150```151152## Related skills153154- `data-quality-audit` — run before fitting155- `logistic-regression` — binary outcomes156- `causal-inference` — when you need effect estimation, not just association157- `stakeholder-readout` — for packaging the model output