sqlpostgresqlregressionstatistics

REGR_R2 в PostgreSQL: как понять, можно ли доверять тренду

Чем REGR_R2 измеряет качество линейной регрессии в SQL и почему его нельзя читать в отрыве от REGR_SLOPE.

11 мин чтенияСправочникsql · postgresql · regression · statistics · analytics

Один наклон сам по себе ещё ничего не доказывает.

Запрос может честно посчитать, что выручка растёт. Функция REGR_SLOPE покажет положительный наклон, и на первый взгляд всё выглядит приятно: линия идёт вверх, бизнес развивается, можно радоваться.

Но есть подвох.

Иногда точки на графике разбросаны как попало: сегодня выручка высокая, завтра низкая, послезавтра снова скачок, потом провал. Если через такое облако точек провести прямую, она тоже может оказаться чуть наклонённой вверх. Формально рост есть. По смыслу — почти никакой закономерности нет.

Вот здесь и нужна функция REGR_R2.

Она отвечает на вопрос:

Насколько хорошо прямая линия вообще объясняет данные?

REGR_SLOPE говорит, куда направлен тренд.

REGR_R2 говорит, стоит ли этому тренду доверять.

Что такое REGR_R2

REGR_R2 возвращает коэффициент детерминации, его ещё называют R-squared.

Это число от 0 до 1.

Оно показывает, какую долю разброса зависимой переменной объясняет линейная модель.

Звучит академично, но смысл простой.

Представьте, что у нас есть точки на графике:

  • по горизонтали — время;
  • по вертикали — выручка.

Мы проводим через эти точки прямую линию тренда. REGR_R2 показывает, насколько эта линия удачно легла на данные.

Если точки почти лежат на прямой, значение будет близко к 1.

Если точки разбросаны хаотично, значение будет близко к 0.

Синтаксис

Синтаксис такой:

SELECT
  REGR_R2(y, x) AS r2
FROM points;

Порядок аргументов важен:

REGR_R2(y, x)

Сначала идёт зависимая переменная y.

Потом независимая переменная x.

Проще говоря:

  • y — что мы пытаемся объяснить или предсказать;
  • x — с помощью чего мы это объясняем.

Например:

REGR_R2(revenue, day_number)

Здесь мы спрашиваем:

Насколько хорошо день объясняет выручку?

То есть есть ли линейная связь между временем и выручкой.

Если перепутать аргументы местами, запрос может выполниться, но смысл получится другой. Поэтому привычка такая: сначала результат, потом фактор.

Как читать R-квадрат

Ориентиры такие:

Значение Как понимать
1.0 идеальная линейная связь, точки лежат на прямой
0.8 сильная линейная связь, тренд выглядит надёжно
0.4 часть разброса объясняется линией, но шума много
0.05 линия почти ничего не объясняет
0.0 линейной связи нет

Важно не превращать эти числа в магические правила.

В одних задачах 0.3 уже может быть полезным сигналом. В других даже 0.7 может быть мало. Но для учебных и аналитических отчётов полезная интуиция такая:

чем ближе REGR_R2 к 1, тем лучше линия описывает данные; чем ближе к 0, тем больше перед нами шум.

Простая интуиция: сравнение с ленивой моделью

Представьте самую ленивую модель на свете.

Она вообще не смотрит на x.

Она всегда говорит:

Я предсказываю среднее значение y.

Например, если средняя дневная выручка равна 10000, такая модель каждый день предсказывает 10000.

Линейная регрессия пытается быть умнее. Она строит прямую и говорит:

В начале периода ожидаю меньше, в конце больше.

И REGR_R2 показывает, насколько эта прямая лучше ленивого среднего.

Если лучше почти никак — R-squared будет около 0.

Если прямая объясняет большую часть разброса — значение будет ближе к 1.

Проверяем тренд выручки

Возьмём задачу из жизни.

Есть таблица orders. В ней лежат заказы, суммы и даты создания. Нужно понять, правда ли выручка растёт со временем.

Сначала соберём дневную выручку, а потом посчитаем наклон и R-squared.

WITH daily AS (
  SELECT
    date_trunc('day', created_at) AS day,
    SUM(amount) AS revenue
  FROM orders
  WHERE status = 'paid'
  GROUP BY 1
)
SELECT
  REGR_SLOPE(revenue, EXTRACT(EPOCH FROM day)) AS slope,
  REGR_R2(revenue, EXTRACT(EPOCH FROM day)) AS r2,
  COUNT(*) AS days
FROM daily;

Что здесь происходит:

  1. В daily мы собираем выручку по дням.
  2. REGR_SLOPE считает наклон линии.
  3. REGR_R2 показывает, насколько хорошо эта линия объясняет дневную выручку.
  4. COUNT(*) показывает, сколько дней попало в расчёт.

Почему в качестве x используется EXTRACT(EPOCH FROM day)?

Потому что регрессии нужны числа. Дата сама по себе — это дата, а не числовая ось. EXTRACT(EPOCH FROM day) превращает день в количество секунд, прошедших с начала Unix-эпохи.

Для тренда это удобно: время становится числом, и по нему можно строить линейную регрессию.

Как интерпретировать результат

Допустим, запрос вернул:

slope r2 days
0.02 0.04 60

Наклон положительный. Формально линия идёт вверх.

Но r2 = 0.04. Это значит, что линия объясняет примерно 4% разброса выручки. Остальное — шум, случайные скачки, акции, выходные, крупные клиенты, сезонность и всё что угодно.

Такой результат опасно продавать как «выручка уверенно растёт».

Правильнее сказать:

Наклон положительный, но качество линейного тренда слабое. По этим данным устойчивый рост не подтверждается.

А теперь другой результат:

slope r2 days
0.02 0.82 60

Здесь тоже положительный наклон, но теперь r2 = 0.82.

Это уже гораздо сильнее. Линия объясняет большую часть разброса дневной выручки. Такой тренд выглядит намного надёжнее.

Почему REGR_SLOPE нужно читать вместе с REGR_R2

REGR_SLOPE и REGR_R2 отвечают на разные вопросы.

REGR_SLOPE:

В какую сторону и насколько сильно наклонена линия?

REGR_R2:

Насколько хорошо эта линия описывает точки?

Без REGR_R2 можно легко обмануться.

Например:

slope r2 Что это значит
100 0.02 линия крутая, но данные шумные, доверия мало
5 0.95 рост слабый, зато очень устойчивый
-20 0.8 есть заметный и довольно надёжный спад
0 0.0 линейного тренда нет

Крутой наклон не всегда означает хороший тренд.

Пологий наклон не всегда означает бесполезный тренд.

Сначала смотрим на направление и величину через REGR_SLOPE, потом проверяем доверие через REGR_R2.

Пример с продажами по странам

Сила REGR_R2 особенно хорошо видна в группировках.

Например, мы хотим понять, в каких странах сумма заказов со временем растёт более устойчиво, а где всё похоже на шум.

SELECT
  u.country,
  REGR_SLOPE(o.amount, EXTRACT(EPOCH FROM o.created_at)) AS slope,
  REGR_R2(o.amount, EXTRACT(EPOCH FROM o.created_at)) AS r2,
  COUNT(*) AS orders_count
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.status = 'paid'
GROUP BY u.country
HAVING COUNT(*) >= 30
ORDER BY r2 DESC;

Здесь мы считаем отдельную линейную регрессию для каждой страны.

В результате можно увидеть, например:

country slope r2 orders_count
Germany 0.015 0.78 240
Spain 0.010 0.55 180
Italy 0.030 0.08 95

Что это значит?

У Германии тренд выглядит устойчиво: r2 высокий.

У Испании связь средняя: что-то есть, но шум тоже заметный.

У Италии наклон самый большой, но r2 очень низкий. Значит, красивая цифра slope может быть случайной. Там данные сильно скачут, и линия плохо объясняет происходящее.

Зачем нужен HAVING COUNT(*) >= 30

В предыдущем запросе есть важная строка:

HAVING COUNT(*) >= 30

Это не украшение. Это защита от маленьких групп.

На двух точках прямая может пройти идеально. Поэтому для двух точек линейная связь может выглядеть подозрительно прекрасной.

Например, в стране было всего два заказа:

date amount
2026-01-01 100
2026-01-02 300

Через эти две точки легко провести прямую. Значение R-squared может получиться идеальным или почти идеальным, но такой вывод не стоит доверия.

Две точки — это не тренд. Это просто две точки.

Поэтому перед анализом сегментов полезно ставить минимальный порог:

HAVING COUNT(*) >= 30

Число 30 не является законом природы. В реальной аналитике порог зависит от задачи. Но сама идея обязательна: не сравнивайте качество тренда на крошечных выборках с нормальными сегментами.

REGR_R2 и CORR

Для простой линейной регрессии с одной независимой переменной есть полезная связь:

REGR_R2(y, x)

равен квадрату корреляции Пирсона:

POWER(CORR(y, x), 2)

Можно проверить это запросом:

SELECT
  REGR_R2(salary, manager_id) AS r2,
  POWER(CORR(salary, manager_id), 2) AS corr_squared
FROM employees
WHERE manager_id IS NOT NULL;

Оба значения должны совпасть с небольшой погрешностью из-за вычислений с числами с плавающей точкой.

Но пример с manager_id специально выглядит немного странно. manager_id — это идентификатор, а не настоящая числовая причина зарплаты. Формально посчитать можно, но смысл такого анализа сомнительный.

Это важный урок:

SQL посчитает формулу для любых чисел, но думать о смысле данных всё равно должен человек.

R-квадрат показывает только линейную связь

REGR_R2 оценивает именно линейную зависимость.

То есть он хорошо отвечает на вопрос:

Похожи ли данные на прямую линию?

Но связь между переменными не всегда прямая.

Например, зависимость может быть такой:

  • сначала рост быстрый, потом замедляется;
  • есть сезонные волны;
  • данные похожи на параболу;
  • после определённой точки начинается резкий скачок;
  • по будням одно поведение, по выходным другое.

В таких случаях R-squared может быть низким, хотя связь между переменными действительно есть. Просто она не линейная.

Очень простой пример: идеальная парабола. Между x и y есть железная связь, но прямая линия может описывать её плохо.

Поэтому низкий REGR_R2 означает не «связи нет вообще», а:

линейная модель плохо объясняет эти данные.

Это разные вещи.

Высокий R-квадрат не доказывает причинность

Ещё одна ловушка: высокий REGR_R2 не означает, что x вызывает y.

Он означает только одно:

прямая линия хорошо описывает связь между x и y.

Например, две метрики могут одновременно расти из-за третьей причины.

Продажи мороженого и продажи лимонада могут расти вместе. Но это не значит, что мороженое вызывает лимонад. Скорее всего, обе метрики растут из-за жары.

В бизнес-данных такое встречается постоянно:

  • выручка растёт вместе с количеством пользователей;
  • количество ошибок растёт вместе с трафиком;
  • нагрузка на поддержку растёт вместе с числом заказов;
  • расходы на рекламу растут вместе с продажами.

REGR_R2 помогает заметить линейную связь, но не объясняет причину этой связи.

NULL в REGR_R2

Как и другие агрегатные функции регрессии, REGR_R2 игнорирует строки, где x или y равны NULL.

Например:

SELECT
  REGR_R2(revenue, visitors) AS r2
FROM daily_metrics;

Если в строке нет revenue или нет visitors, эта пара не попадёт в расчёт.

Это логично: для точки на графике нужны обе координаты.

Но в отчётах из-за этого можно получить неожиданность. Вы думаете, что считаете по 100 дням, а реально в расчёт попало 63 дня.

Поэтому рядом полезно выводить количество строк:

SELECT
  REGR_R2(revenue, visitors) AS r2,
  COUNT(*) AS rows_total,
  COUNT(revenue) AS revenue_count,
  COUNT(visitors) AS visitors_count,
  REGR_COUNT(revenue, visitors) AS pairs_count
FROM daily_metrics;

REGR_COUNT показывает, сколько пар значений реально участвовало в регрессии.

Это особенно полезно, когда данные собраны из разных источников и в них много пропусков.

Пример: связь выручки и числа посетителей

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

  • day;
  • visitors;
  • revenue.

Хотим понять, насколько хорошо количество посетителей объясняет выручку.

SELECT
  REGR_SLOPE(revenue, visitors) AS slope,
  REGR_R2(revenue, visitors) AS r2,
  REGR_COUNT(revenue, visitors) AS pairs_count
FROM daily_metrics;

Если результат такой:

slope r2 pairs_count
12.5 0.88 90

это можно прочитать так:

Чем больше посетителей, тем выше выручка. Связь выглядит сильной и достаточно устойчивой.

Но если результат такой:

slope r2 pairs_count
12.5 0.10 90

наклон вроде тот же, но доверия намного меньше.

Возможно, на выручку сильно влияют не только посетители, но и скидки, источник трафика, день недели, сезонность, средний чек или крупные разовые покупки.

REGR_R2 в HAVING

Иногда удобно сразу отфильтровать группы, где линейная связь слабая.

Например, хотим показать только страны, где тренд по заказам выглядит достаточно уверенно:

SELECT
  u.country,
  REGR_SLOPE(o.amount, EXTRACT(EPOCH FROM o.created_at)) AS slope,
  REGR_R2(o.amount, EXTRACT(EPOCH FROM o.created_at)) AS r2,
  REGR_COUNT(o.amount, EXTRACT(EPOCH FROM o.created_at)) AS pairs_count
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.status = 'paid'
GROUP BY u.country
HAVING
  REGR_COUNT(o.amount, EXTRACT(EPOCH FROM o.created_at)) >= 30
  AND REGR_R2(o.amount, EXTRACT(EPOCH FROM o.created_at)) >= 0.6
ORDER BY r2 DESC;

Такой запрос оставит только сегменты, где:

  • достаточно данных;
  • линейная модель объясняет заметную часть разброса.

Но будьте аккуратны: фильтр по r2 — это не истина в последней инстанции. Он помогает убрать шумные сегменты, но окончательный вывод всё равно зависит от задачи.

Почему полезно смотреть на график

SQL хорошо считает числа. Но числа не всегда рассказывают всю историю.

Перед важным выводом полезно построить график точек:

  • x по горизонтали;
  • y по вертикали;
  • сверху линия тренда.

Почему это важно?

Потому что REGR_R2 может не показать некоторые особенности:

  • выбросы;
  • кластеры;
  • сезонность;
  • резкий перелом в середине периода;
  • нелинейную зависимость;
  • разные группы, смешанные в одну.

Например, общий тренд по всем пользователям может быть слабым. А если разделить пользователей по странам или тарифам, внутри каждой группы связь окажется сильной.

SQL даст вам первую проверку. График поможет понять, что именно происходит.

Производительность

REGR_R2 — агрегатная функция. Она считает значение по группе строк.

Если вы считаете один общий показатель по таблице, PostgreSQL проходит по подходящим строкам и агрегирует их.

Если используете GROUP BY, отдельный расчёт делается для каждой группы.

Например:

SELECT
  country,
  REGR_R2(revenue, visitors) AS r2
FROM daily_country_metrics
GROUP BY country;

Здесь регрессия считается отдельно для каждой страны.

На больших таблицах важны обычные вещи:

  • фильтруйте лишние строки в WHERE;
  • заранее агрегируйте сырые события до нужного уровня;
  • не считайте тренд по миллионам событий, если вам нужен тренд по дням;
  • проверяйте запрос через EXPLAIN ANALYZE.

Частая ошибка — считать регрессию прямо по отдельным заказам, когда на самом деле нужен тренд дневной выручки. Тогда сначала нужно собрать данные по дням, как в примере с daily, и только потом применять REGR_R2.

Переносимость

REGR_R2 есть в PostgreSQL, но не во всех популярных СУБД.

В MySQL стандартного аналога REGR_R2 нет. Там такой показатель обычно собирают вручную через корреляцию, дисперсии или промежуточные агрегаты.

В ClickHouse прямого REGR_R2 тоже может не быть в привычном PostgreSQL-виде, но похожий расчёт можно собрать через статистические агрегаты и формулы.

Если вы пишете именно под PostgreSQL, REGR_R2 удобен тем, что всё уже встроено и читается прямо в SQL.

Если запрос должен быть переносимым между разными базами, лучше заранее проверить поддержку регрессионных функций в нужной СУБД.

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

Первая ошибка — смотреть только на REGR_SLOPE.

SELECT
  REGR_SLOPE(revenue, visitors) AS slope
FROM daily_metrics;

Так вы видите направление, но не видите качество связи. Лучше сразу добавлять REGR_R2.

SELECT
  REGR_SLOPE(revenue, visitors) AS slope,
  REGR_R2(revenue, visitors) AS r2
FROM daily_metrics;

Вторая ошибка — забывать про размер выборки.

SELECT
  country,
  REGR_R2(amount, EXTRACT(EPOCH FROM created_at)) AS r2
FROM orders
GROUP BY country
ORDER BY r2 DESC;

Без проверки количества строк наверх могут попасть страны с двумя-тремя заказами. Формально r2 там может быть красивым, но доверять ему нельзя.

Лучше так:

SELECT
  country,
  REGR_R2(amount, EXTRACT(EPOCH FROM created_at)) AS r2,
  REGR_COUNT(amount, EXTRACT(EPOCH FROM created_at)) AS pairs_count
FROM orders
GROUP BY country
HAVING REGR_COUNT(amount, EXTRACT(EPOCH FROM created_at)) >= 30
ORDER BY r2 DESC;

Третья ошибка — считать высокий r2 доказательством причины.

R-squared говорит о качестве линейного описания, а не о том, что одно явление вызывает другое.

Четвёртая ошибка — делать вывод «связи нет», если r2 низкий.

Правильнее сказать:

Линейной связи почти нет.

Связь может быть нелинейной, сезонной или проявляться только внутри отдельных сегментов.

Главное

REGR_R2 в PostgreSQL показывает, насколько хорошо линейная регрессия объясняет данные.

Синтаксис:

SELECT
  REGR_R2(y, x) AS r2
FROM points;

Сначала пишем зависимую переменную y, потом независимую переменную x.

REGR_R2 возвращает число от 0 до 1:

  • ближе к 1 — точки хорошо ложатся на прямую;
  • ближе к 0 — прямая почти не объясняет разброс данных.

REGR_SLOPE и REGR_R2 лучше всегда считать вместе:

SELECT
  REGR_SLOPE(revenue, visitors) AS slope,
  REGR_R2(revenue, visitors) AS r2
FROM daily_metrics;

REGR_SLOPE показывает направление и величину изменения.

REGR_R2 показывает, насколько этому наклону можно доверять.

Высокий R-squared не доказывает причинность. Низкий R-squared не доказывает отсутствие любой связи. Он говорит только о том, насколько хорошо данные описываются прямой линией.

Практическое правило простое: если вы показываете тренд в отчёте, не ограничивайтесь наклоном. Добавляйте REGR_R2, количество наблюдений и, если решение важное, обязательно смотрите на график. Тогда вместо красивой, но случайной линии у вас появится нормальная аналитическая проверка.

Закрепи на практике

Решай задачи в SQL-тренажёре с мгновенной проверкой и подсказками.

Открыть тренажёр