Условие
В регрессии revenue ~ age + tenure residuals растут с возрастом юзера. Гетероскедастичность? Что делать?
Решение
Что это
Гомоскедастичность: Var(ε|X) = σ² — постоянна.
Гетероскедастичность: дисперсия остатков зависит от X.
Типичные паттерны:
- Cone/fan shape в residual plot (разлёт растёт с fitted value).
- Размер дисперсии масштабируется с уровнем переменной (выручка у больших клиентов колеблется больше).
Последствия
- β оценки unbiased — но не efficient.
- SE неверны — обычно занижены → inflate significance.
- t-tests / CIs неточны.
Тесты
import statsmodels.api as sm
from statsmodels.stats.diagnostic import het_breuschpagan, het_white
X = sm.add_constant(df[['age', 'tenure']])
model = sm.OLS(df['revenue'], X).fit()
# Breusch-Pagan
lm, p, _, _ = het_breuschpagan(model.resid, X)
print(f'Breusch-Pagan p={p:.4f}') # < 0.05 → heteroscedasticity
# White's test (более общий, но менее мощный)
lm, p, _, _ = het_white(model.resid, X)Визуальная проверка
import matplotlib.pyplot as plt
plt.scatter(model.fittedvalues, model.resid)
plt.axhline(0)Cone shape — heteroscedasticity.
Лечение
1. Robust standard errors (самое простое):
model_robust = sm.OLS(y, X).fit(cov_type='HC3') # White's robust SEНе меняет β, но даёт корректные SE. Часто достаточно.
2. Log-transform Y:
df['log_revenue'] = np.log1p(df['revenue'])
model = sm.OLS(df['log_revenue'], X).fit()Часто стабилизирует variance.
3. Weighted Least Squares:
weights = 1 / df['age'] # если variance ~ age
model = sm.WLS(y, X, weights=weights).fit()4. Generalized Linear Models (GLM):
Для positive continuous Y — Gamma regression with log link.
import statsmodels.api as sm
model = sm.GLM(y, X, family=sm.families.Gamma(link=sm.families.links.log())).fit()HC0, HC1, HC2, HC3
Различные robust SE формулы:
- HC0 — оригинальный White.
- HC1 — корректировка для малых n.
- HC3 — для очень малых n или leverage точек, рекомендуется.
Кластеризация
Если данные группируются (юзеры одной компании), residuals скоррелированы внутри кластера. Cluster-robust SE:
model = sm.OLS(y, X).fit(cov_type='cluster', cov_kwds={'groups': df['company_id']})Подводные камни
- Breusch-Pagan на больших n находит малые отклонения — не всё значимое требует лечения.
- Robust SE решает inference, но не efficiency. Если данные сильно гетерос — WLS даёт лучшие оценки.
- Log(Y) меняет интерпретацию: β — это % изменение Y, не absolute.
- Cluster SE требует много кластеров (>20-30) для корректности.
- Heteroscedasticity часто следствие model misspecification (omitted variable, non-linearity), не «свойство данных».
Эталонный ответ
Breusch-Pagan p<0.05 → heteroscedasticity. Лечение: HC3 robust SE (самое простое), log(Y) transform, WLS с весами 1/variance, или GLM. β unbiased, но SE врут — robust SE достаточно для inference.