Как посчитать adjusted R² в SQL

Закрепи формулу r2 adjusted в Карьернике
Запомнить надолго — 5 коротких сессий с задачами на эту тему. Бесплатно
Тренировать r2 adjusted в Telegram

Зачем adjusted R2

R² растёт каждый раз, когда вы добавляете предиктор — даже если он мусорный. Это inflates ощущение «модель улучшилась». Adjusted R² корректирует на количество фичей: модель с большим количеством бесполезных переменных имеет более низкий adjusted R², даже если R² растёт.

Формула

adj_R² = 1 − (1 − R²) × (n − 1) / (n − p − 1)
  • n — число наблюдений
  • p — число предикторов (не считая intercept)

Если p = 0, adj_R² = R². Если p растёт без полезных эффектов, adj_R² падает.

Adjusted R2 в SQL

WITH stats AS (
    SELECT
        REGR_R2(y, x) AS r2,
        COUNT(*) AS n,
        1 AS p  -- bivariate: только 1 predictor
    FROM training_data
)
SELECT
    r2,
    n,
    p,
    1 - (1 - r2) * (n - 1)::NUMERIC / NULLIF(n - p - 1, 0) AS adjusted_r2
FROM stats;

Для multivariate REGR_R2 не подходит — нужна Python. Но если есть готовый R² для multi-X модели:

WITH config AS (
    SELECT 0.78 AS r2_multi, 500 AS n, 5 AS p
)
SELECT
    r2_multi,
    1 - (1 - r2_multi) * (n - 1)::NUMERIC / NULLIF(n - p - 1, 0) AS adjusted_r2
FROM config;

Сравнение моделей

Удобно держать таблицу с метриками всех протестированных моделей:

SELECT
    model_name,
    r2,
    n_predictors,
    n_observations,
    1 - (1 - r2) * (n_observations - 1)::NUMERIC
        / NULLIF(n_observations - n_predictors - 1, 0) AS adjusted_r2
FROM model_comparison
ORDER BY adjusted_r2 DESC;

Модель с adj_R² 0.65 и 3 фичами лучше, чем модель с adj_R² 0.62 и 10 фичами — даже если её raw R² ниже.

Закрепи формулу r2 adjusted в Карьернике
Запомнить надолго — 5 коротких сессий с задачами на эту тему. Бесплатно
Тренировать r2 adjusted в Telegram

R2 vs adjusted vs penalty

WITH versions AS (
    SELECT
        'simple' AS name, 0.50 AS r2, 100 AS n, 1 AS p
    UNION ALL SELECT 'medium', 0.62, 100, 4
    UNION ALL SELECT 'complex_useful', 0.75, 100, 8
    UNION ALL SELECT 'overfit', 0.85, 100, 30
)
SELECT
    name,
    r2,
    p,
    1 - (1 - r2) * (n - 1)::NUMERIC / NULLIF(n - p - 1, 0) AS adj_r2,
    r2 - (1 - (1 - r2) * (n - 1)::NUMERIC / NULLIF(n - p - 1, 0)) AS penalty
FROM versions
ORDER BY adj_r2 DESC;

«Overfit» model с R² 0.85 и 30 предикторами имеет penalty 0.07 — adj_R² ≈ 0.78. Это всё ещё выше других, но если убрать 10 «случайных» предикторов и R² падает на 0.05 — penalty снизится больше.

Частые ошибки

Ошибка 1. n − p − 1 = 0. При p = n − 1 знаменатель = 0. Это «насыщенная» модель — все степени свободы исчерпаны. NULLIF лечит.

Ошибка 2. p включает intercept. В формуле p — число предикторов БЕЗ intercept. Если у вас 5 фичей + bias, p = 5, не 6.

Ошибка 3. Сравнивать adj_R² с разными samples. Adj_R² валиден для one dataset с разными моделями. Между samples — не очень.

Ошибка 4. Adj_R² < 0. Возможно, если модель хуже базовой (predicting mean). Сигнал, что что-то очень плохо.

Ошибка 5. Только adj_R² без cross-validation. Adj_R² на train не гарантирует generalization. Test R² + adj_R² оба.

Связанные темы

FAQ

Adj R² < R²?

Да, всегда. Поправка вычитает penalty за предикторы.

Какой adj R² хороший?

Не отличается от R²: 0.5+ заметный сигнал, 0.7+ сильный.

Adjusted R² и BIC?

BIC жёстче штрафует за p. Используют для model selection.

Adjusted R² для logistic?

Pseudo-R² (McFadden, Cox-Snell) — другая концепция.

Можно ли adj R² для feature selection?

Можно. Stepwise — выбирают модель с максимальным adj_R².