Все материалы
Продуктовая аналитикаматериалстарт

Линейная регрессия простыми словами: уравнение, наклон и R²

Что такое регрессия и линейная регрессия: уравнение y = a + b·x, наклон, свободный член и R². Пример на пяти точках, расчёт в SQL, Python и Excel, остатки, прогноз и ловушки.

КейсПрактика2 октября 2026 г.16 мин

Линейная регрессия — это прямая ŷ = a + b·x, проведённая через облако точек: наклон b показывает, на сколько в среднем меняется y, когда x растёт на единицу. Пример: менеджер продукта смотрит на график за три месяца — DAU вырос с 32 до 530–590 человек, дневная выручка — с нуля до 600 с лишним долларов. «Если дорастим до тысячи активных, сколько будем зарабатывать в день?» Линейная регрессия отвечает одним запросом: 1 118 долларов. Ниже — откуда берётся это число, как читать уравнение и почему именно этот ответ руководителю нести нельзя.

Что такое регрессия простыми словами

Регрессия описывает, как в среднем меняется одна величина, когда меняется другая. Линейная регрессия делает это прямой: через облако точек проводят линию, которая лежит к ним ближе всего, и записывают её уравнением.

В уравнении y — зависимая величина, то, что объясняют: выручка, число заказов. x — независимая, или фактор: аудитория, бюджет, номер дня. Наклон b показывает, на сколько в среднем меняется y, когда x растёт на единицу; его же называют коэффициентом регрессии. Свободный член a — значение линии при x = 0.

Главные слова здесь — «в среднем». Линия не обещает, что каждая точка ляжет на неё: отдельные дни будут выше и ниже. Регрессионным анализом называют всю работу вокруг уравнения: подобрать линию, проверить, насколько она подходит, и посмотреть, где она ошибается. Вариант с одним x называют парной линейной регрессией.

Уравнение линейной регрессии
ŷ = a + b·x

ŷ — значение на линии, a — свободный член, b — наклон. Наблюдение отличается от линии на остаток: y = a + b·x + e.

Как найти уравнение регрессии: пример на пяти точках

Пять недель рекламной кампании. Бюджет по неделям — 10, 20, 30, 40 и 50 тысяч рублей, регистраций — 30, 50, 40, 70 и 60. Средний бюджет равен 30, среднее число регистраций — 50.

Для наклона у каждой недели считают отклонение бюджета и отклонение регистраций от своих средних, перемножают их и складывают: получается 800. Эту сумму делят на сумму квадратов отклонений бюджета — 1 000. Наклон равен 0,8. Линия всегда проходит через точку средних, отсюда свободный член: 50 − 0,8 · 30 = 26.

Уравнение: регистрации = 26 + 0,8 · бюджет. Неделя, в которую потратили на тысячу рублей больше, в среднем приносила на 0,8 регистрации больше. Для бюджета в 35 тысяч линия даёт 26 + 0,8 · 35 = 54 регистрации.

Этот способ называется методом наименьших квадратов: из всех возможных прямых выбирают ту, у которой сумма квадратов вертикальных промахов минимальна. У нашей линии она равна 360. Сдвиньте или поверните прямую — сумма вырастет.

Наклон и свободный член
b = Σ(x − x̄)(y − ȳ) / Σ(x − x̄)²; a = ȳ − b·x̄

x̄ и ȳ — средние. В примере b = 800 / 1 000 = 0,8 и a = 50 − 0,8 · 30 = 26.

Ручной расчёт: бюджет в тысячах рублей и регистрации за пять недель
Бюджет xРегистрации y(x − x̄)·(y − ȳ)(x − x̄)²Линия 26 + 0,8xОстаток
103040040034−4
20500100428
30400050−10
40702001005812
506020040066−6
среднее 30среднее 50сумма 800сумма 1 000сумма 0
pythonPython: та же линия через numpy.polyfit
import numpy as np

budget = np.array([10, 20, 30, 40, 50])   # тысяч рублей
signups = np.array([30, 50, 40, 70, 60])

b, a = np.polyfit(budget, signups, 1)     # степень 1 — прямая
r = np.corrcoef(budget, signups)[0, 1]
sse = ((signups - (a + b * budget)) ** 2).sum()

print(round(b, 2), round(a, 2))           # 0.8 26.0
print(round(r, 2), round(r ** 2, 2))      # 0.8 0.64
print(round(sse, 1))                      # 360.0

Как посчитать линейную регрессию в SQL

Теперь рабочие данные. В учебной базе сервиса лежат события пользователей и оплаты в долларах. Соберём таблицу, где одна строка — один день: DAU, то есть число людей хотя бы с одним событием за день, и выручка — сумма оплат. Берём 90 дней с 1 июня по 29 августа 2026 года. 30 августа выгрузка обрывается в 13:00, и неполный день в расчёт не идёт.

В семи днях оплат не было. Это ноль выручки, а не пропуск, поэтому оплаты присоединяются через LEFT JOIN и COALESCE: иначе дни без денег выпадут из расчёта, и линия получится другой.

В PostgreSQL и DuckDB прямую считают агрегатные функции regr_slope, regr_intercept и regr_r2. Рядом полезны regr_count — сколько пар попало в расчёт — и corr. Порядок аргументов у всех один: сначала зависимая величина y, потом x. Функции, кроме regr_count, возвращают число с плавающей точкой, а round с двумя аргументами в PostgreSQL принимает только numeric, отсюда CAST. На одной-единственной точке наклон не определён: PostgreSQL вернёт NULL, DuckDB — NaN.

Результат: 90 дней, наклон 1,171, свободный член −52,12, R² = 0,773, коэффициент корреляции 0,879. Уравнение: выручка = −52,12 + 1,171 · DAU.

Диаграмма рассеяния: 90 дней, по горизонтали DAU от 32 до 586, по вертикали дневная выручка от 0 до 771 доллара, через облако точек проведена линия регрессии с наклоном 1,17.
Каждая точка — день. Линия проходит через середину облака, пунктир продолжает её до DAU = 0, где она опускается до −52. Полая точка — 30 августа: DAU посчитан за полдня, выручка — за весь день.
Линия регрессии дневной выручки по DAU: 90 дней, наклон 1,171, R² 0,773
WITH daily AS (
  SELECT d.day, d.dau, COALESCE(r.revenue, 0) AS revenue
  FROM (SELECT CAST(event_time AS date) AS day, count(DISTINCT user_id) AS dau
        FROM events GROUP BY 1) AS d
  LEFT JOIN (SELECT paid_at AS day, sum(amount) AS revenue
             FROM payments GROUP BY 1) AS r ON r.day = d.day
)
SELECT
  regr_count(revenue, dau) AS days,
  round(CAST(regr_slope(revenue, dau) AS numeric), 3) AS slope,
  round(CAST(regr_intercept(revenue, dau) AS numeric), 2) AS intercept,
  round(CAST(regr_r2(revenue, dau) AS numeric), 3) AS r2,
  round(CAST(corr(revenue, dau) AS numeric), 3) AS r
FROM daily
WHERE day < DATE '2026-08-30';

Как читать наклон, свободный член и R²

Наклон читают в единицах: на сколько единиц y приходится одна единица x. Здесь — день, в котором активных пользователей на одного больше, приносил в среднем на 1,17 доллара больше; сто дополнительных активных — около 117 долларов. Это сравнение разных дней между собой, а не обещание «приведём человека — получим 1,17».

Свободный член −52,12 формально означает выручку при нулевом DAU. Отрицательной выручки не бывает, а дней с DAU меньше 32 в данных нет. Свободный член ставит линию на нужную высоту, и толковать его можно, только если x = 0 лежит внутри наблюдений и имеет смысл.

R², коэффициент детерминации, — доля разброса y, которую описывает линия. 0,773 значит, что 77% различий в дневной выручке линия связывает с различиями в DAU, а 23% остаются необъяснёнными. При одном x это квадрат коэффициента корреляции: 0,879² ≈ 0,773.

Функция linregress из SciPy добавляет стандартную ошибку и p-value. Стандартная ошибка наклона 0,068 показывает, насколько наклон гулял бы от выборки к выборке; 95%-й интервал — от 1,04 до 1,30. P-value 4,4·10⁻³⁰ — вероятность увидеть наклон не ближе к нулю, чем этот, если на самом деле связи нет. Оба числа отвечают только на вопрос, случаен ли наклон. Откуда он взялся, они не говорят: DAU и выручка росли весь квартал, а любые два растущих ряда дадут крошечный p-value. К этому вернёмся в разделе о причинности.

Коэффициент детерминации
R² = 1 − Σ(y − ŷ)² / Σ(y − ȳ)²; при одном x: R² = r²

В числителе — разброс вокруг линии, в знаменателе — разброс вокруг среднего. В примере с рекламой Σ(y − ȳ)² = 400 + 0 + 100 + 400 + 100 = 1 000, поэтому R² = 1 − 360 / 1 000 = 0,64 = 0,8². Разброс регистраций случайно совпал с разбросом бюджета, оттого наклон 0,8 равен r; в общем случае b = r · s_y / s_x.

pythonPython: linregress на тех же 90 днях — наклон, R², стандартная ошибка и p-value
import numpy as np
from scipy import stats

dau = np.array([
    32, 53, 76, 85, 104, 86, 76, 97, 105, 129, 141, 139, 132, 119, 146,
    154, 172, 180, 204, 185, 173, 198, 210, 233, 234, 249, 215, 203, 234, 240,
    285, 287, 297, 275, 257, 260, 283, 296, 292, 330, 286, 259, 323, 335, 374,
    418, 401, 354, 335, 383, 376, 389, 432, 385, 381, 381, 374, 427, 431, 454,
    480, 428, 406, 443, 419, 485, 499, 446, 457, 441, 444, 454, 494, 503, 498,
    491, 459, 505, 467, 531, 502, 519, 482, 495, 484, 529, 568, 556, 586, 530,
])
revenue = np.array([
    0, 0, 0, 0, 0, 48, 19, 77, 0, 58, 0, 105, 163, 145, 96,
    192, 212, 115, 172, 57, 258, 240, 221, 231, 212, 306, 174, 210, 297, 375,
    212, 307, 347, 182, 86, 424, 259, 193, 337, 365, 222, 298, 326, 395, 153,
    308, 268, 405, 509, 289, 403, 441, 481, 327, 346, 487, 394, 632, 471, 626,
    375, 480, 394, 345, 597, 500, 460, 499, 452, 770, 327, 510, 470, 518, 336,
    491, 460, 567, 316, 558, 442, 691, 771, 548, 529, 660, 519, 604, 646, 607,
])

fit = stats.linregress(dau, revenue)            # сначала x, потом y
residuals = revenue - (fit.intercept + fit.slope * dau)
n = len(dau)
half_width = stats.t.ppf(0.975, n - 2) * fit.stderr

print(round(fit.slope, 3), round(fit.intercept, 2))            # 1.171 -52.12
print(round(fit.rvalue, 4), round(fit.rvalue ** 2, 4))         # 0.8793 0.7731
print(round(fit.stderr, 3), f'{fit.pvalue:.1e}')               # 0.068 4.4e-30
print(round(fit.slope - half_width, 2), round(fit.slope + half_width, 2))  # 1.04 1.3
print(round(np.sqrt((residuals ** 2).sum() / (n - 2)), 1))     # 94.3

Что такое остатки и что по ним видно

Остаток — разница между фактом и линией: e = y − ŷ. В примере с рекламой остатки равны −4, 8, −10, 12 и −6. Их сумма всегда ноль: линия устроена так, что промахи вверх и вниз уравновешены.

На учебной базе самый большой промах пришёлся на 9 августа: выручка 770 долларов при 464 по линии, остаток +306. 22 августа — +259, 15 июля — минус 233: при DAU 374 пришло всего 153 доллара. Стандартное отклонение остатков — 94 доллара, это типичный промах линии.

Полезнее смотреть не на отдельные остатки, а на их рисунок вдоль оси x. Если прямая подходит, узора нет: в любой части диапазона средний остаток около нуля. В таблице так и есть — от −19 до +11 при разбросе в десятки долларов. Знаки «минус, плюс, плюс, минус» похожи на дугу, но каждое среднее меньше двух своих стандартных ошибок (−19,1 при ошибке 12), и квадратичная добавка к линии незначима.

Зато разброс остатков растёт: 47 долларов при DAU до 150 и 100–120 при DAU от 300. В верхней части диапазона промах прогноза вдвое с лишним больше, чем в нижней, и одно число «±94» этого не показывает. Тревожные рисунки выглядят иначе: дуга из минусов, плюсов и снова минусов говорит, что связь не прямая, а одинокая далёкая точка — что линию тянет выброс.

Остатки по диапазонам DAU, доллары в день
DAUДнейСредний остатокРазброс остатков
до 15015−19,146,9
150–2992610,879,5
300–449257,1120,2
450 и больше24−7,2101,2
Остатки линии по диапазонам DAU: средний остаток и разброс
WITH daily AS (
  SELECT d.day, d.dau, COALESCE(r.revenue, 0) AS revenue
  FROM (SELECT CAST(event_time AS date) AS day, count(DISTINCT user_id) AS dau
        FROM events GROUP BY 1) AS d
  LEFT JOIN (SELECT paid_at AS day, sum(amount) AS revenue
             FROM payments GROUP BY 1) AS r ON r.day = d.day
),
model AS (
  SELECT regr_slope(revenue, dau) AS b, regr_intercept(revenue, dau) AS a
  FROM daily
  WHERE day < DATE '2026-08-30'
),
residuals AS (
  SELECT
    CASE WHEN dau < 150 THEN '1. до 150'
         WHEN dau < 300 THEN '2. 150–299'
         WHEN dau < 450 THEN '3. 300–449'
         ELSE '4. 450 и больше' END AS dau_band,
    revenue - (a + b * dau) AS residual
  FROM daily CROSS JOIN model
  WHERE day < DATE '2026-08-30'
)
SELECT
  dau_band,
  count(*) AS days,
  round(CAST(avg(residual) AS numeric), 1) AS mean_residual,
  round(CAST(stddev_samp(residual) AS numeric), 1) AS sd_residual
FROM residuals
GROUP BY dau_band
ORDER BY dau_band;

Можно ли прогнозировать по уравнению регрессии

Прогноз — это подстановка x в уравнение. Для DAU 400 получается −52,12 + 1,171 · 400 = 416 долларов в день. Это интерполяция: 400 лежит внутри наблюдавшегося диапазона 32–586, вокруг десятки реальных дней. С поправкой на разброс остатков ответ звучит так: «около 416; три дня из четырёх укладываются в плюс-минус сотню, почти все — в плюс-минус 190».

Для DAU 1 000 та же формула даёт около 1 118 долларов (по округлённым коэффициентам выйдет 1 119). Это экстраполяция: выше 586 нет ни одного наблюдения. Линия проведена по тому, что было, и ничего не знает о том, сохранится ли форма связи за краем данных. В обратную сторону это видно сразу: при DAU 0 она обещает −52 доллара.

Проверить экстраполяцию можно на прошлом. Построим линию только по июню, когда DAU не поднимался выше 249. Она выглядит не хуже общей: наклон 1,529, R² = 0,735. Подставим в неё августовские дни со средним DAU 486,9: прогноз — 642,6 доллара в день, факт — 519,6. Линия, которой в июне не в чем было упрекнуть, завысила август на 24%.

Отсюда правило для отчёта. Внутри диапазона прогноз показывают вместе с разбросом. За его пределами — только как сценарий «если связь останется прежней», с оговоркой, что данных там нет.

Линия по июню и её прогноз на август: 642,6 против факта 519,6
WITH daily AS (
  SELECT d.day, d.dau, COALESCE(r.revenue, 0) AS revenue
  FROM (SELECT CAST(event_time AS date) AS day, count(DISTINCT user_id) AS dau
        FROM events GROUP BY 1) AS d
  LEFT JOIN (SELECT paid_at AS day, sum(amount) AS revenue
             FROM payments GROUP BY 1) AS r ON r.day = d.day
),
june AS (
  SELECT regr_slope(revenue, dau) AS b, regr_intercept(revenue, dau) AS a,
    regr_r2(revenue, dau) AS r2, max(dau) AS max_dau
  FROM daily
  WHERE day < DATE '2026-07-01'
)
SELECT
  round(CAST(b AS numeric), 3) AS june_slope,
  round(CAST(r2 AS numeric), 3) AS june_r2,
  max_dau AS june_max_dau,
  round(avg(dau), 1) AS august_dau,
  round(CAST(avg(a + b * dau) AS numeric), 1) AS august_forecast,
  round(avg(revenue), 1) AS august_fact
FROM daily CROSS JOIN june
WHERE day >= DATE '2026-08-01' AND day < DATE '2026-08-30'
GROUP BY b, a, r2, max_dau;

Как один выброс и перепутанные оси меняют линию

Вернём в расчёт 30 августа. DAU за этот день — 223, потому что события есть только до 13:00, а выручка — 721 доллар: оплаты за день записаны целиком. С этой точкой наклон падает с 1,171 до 1,143, а R² — с 0,773 до 0,711. Одна строка из 91 забрала шесть процентных пунктов объяснённого разброса.

На коротком окне эффект сильнее. По 29 закрытым дням августа наклон равен 0,942 — и держится он слабо: R² = 0,12, 95%-й интервал от −0,06 до 1,94. Добавьте 30 августа — и наклон становится −0,002. Точка стоит далеко от остальных по оси x, 223 против 406–586, и работает как рычаг. Перед расчётом проверяйте, закрыт ли последний день, и смотрите на диаграмму рассеяния.

Вторая ловушка — порядок величин. Корреляция симметрична, регрессия — нет. Наклон выручки по DAU равен 1,171. Наклон DAU по выручке — 0,660, а не 1 / 1,171 = 0,854. Произведение двух наклонов равно R²: 1,171 · 0,660 = 0,773.

Причина в том, что метод наименьших квадратов уменьшает промахи только по вертикали. Поменяли оси — поменяли то, что считается промахом. В SQL эта ошибка выглядит как regr_slope(dau, revenue) вместо regr_slope(revenue, dau): запрос выполнится и вернёт правдоподобное число.

Один неполный день: наклон 1,171 → 1,143, в августе 0,942 → −0,002
WITH daily AS (
  SELECT d.day, d.dau, COALESCE(r.revenue, 0) AS revenue
  FROM (SELECT CAST(event_time AS date) AS day, count(DISTINCT user_id) AS dau
        FROM events GROUP BY 1) AS d
  LEFT JOIN (SELECT paid_at AS day, sum(amount) AS revenue
             FROM payments GROUP BY 1) AS r ON r.day = d.day
)
SELECT
  round(CAST(regr_slope(revenue, dau) FILTER (WHERE day < DATE '2026-08-30') AS numeric), 3) AS slope_90,
  round(CAST(regr_slope(revenue, dau) AS numeric), 3) AS slope_91,
  round(CAST(regr_r2(revenue, dau) FILTER (WHERE day < DATE '2026-08-30') AS numeric), 3) AS r2_90,
  round(CAST(regr_r2(revenue, dau) AS numeric), 3) AS r2_91,
  round(CAST(regr_slope(revenue, dau) FILTER (
    WHERE day >= DATE '2026-08-01' AND day < DATE '2026-08-30') AS numeric), 3) AS august_29,
  round(CAST(regr_slope(revenue, dau) FILTER (
    WHERE day >= DATE '2026-08-01') AS numeric), 3) AS august_30
FROM daily;
sqlПерепутанные аргументы: наклон DAU по выручке 0,660 не равен 1 / 1,171
WITH daily AS (
  SELECT d.day, d.dau, COALESCE(r.revenue, 0) AS revenue
  FROM (SELECT CAST(event_time AS date) AS day, count(DISTINCT user_id) AS dau
        FROM events GROUP BY 1) AS d
  LEFT JOIN (SELECT paid_at AS day, sum(amount) AS revenue
             FROM payments GROUP BY 1) AS r ON r.day = d.day
)
SELECT
  round(CAST(regr_slope(revenue, dau) AS numeric), 3) AS revenue_on_dau,
  round(CAST(regr_slope(dau, revenue) AS numeric), 3) AS dau_on_revenue,
  round(CAST(1 / regr_slope(revenue, dau) AS numeric), 3) AS one_over_b,
  round(CAST(regr_slope(revenue, dau) * regr_slope(dau, revenue) AS numeric), 3) AS product
FROM daily
WHERE day < DATE '2026-08-30';

Что делать, если связь нелинейная или сегменты разные

Прямую можно провести через любые точки, и высокий R² не доказывает, что прямая — верная форма. Возьмём тренд: DAU по номеру дня. Наклон 5,55 — плюс пять с половиной активных пользователей в день, R² = 0,962. Выглядит как почти идеальная модель.

Остатки говорят другое. В первые десять дней линия завышает DAU в среднем на 22 человека, в середине квартала занижает на 18–19, в последние десять дней снова завышает на 25. Это дуга: рост замедляется, в июне DAU прибавлял по 6,7 в день, в августе — по 4,2. Прямая усредняет оба темпа, и прогноз на сентябрь по ней окажется выше, чем подсказывает август.

Вторая причина не верить одной линии — смешанные сегменты. Посчитаем ту же пару отдельно по каналам привлечения, где строка — день и канал. Платный поиск включили 1 июля, поэтому сравниваем на общем окне, 60 дней июля и августа: рефералы — 1,19, органика — 1,00, платный поиск — 0,75, партнёры — 0,54. Интервалы широкие и перекрываются (органика — от 0,33 до 1,67, платный поиск — от 0,45 до 1,05, партнёры — от −0,30 до 1,38), так что утверждать, что каналы различаются, по этим данным нельзя. Переносить общий наклон 1,17 на отдельный канал тоже нельзя: у каждого он свой и известен неточно.

Что с этим делать. При дуге — строить линию на коротком участке, где связь близка к прямой, или менять форму модели. При разных сегментах — считать линии отдельно, на одном окне, и показывать их рядом вместе с интервалами.

Средний остаток линии «DAU по номеру дня» по десятидневкам, пользователей

Выше нуля — факт больше линии, ниже — меньше. Остатки хорошей линии колебались бы вокруг нуля без рисунка; здесь они выстроились дугой при R² = 0,962.

Средний остатокНоль: линия угадала
Тренд DAU по номеру дня: средний остаток линии по десятидневкам
WITH daily AS (
  SELECT
    CAST(event_time AS date) - DATE '2026-06-01' + 1 AS day_no,
    count(DISTINCT user_id) AS dau
  FROM events
  WHERE event_time < TIMESTAMP '2026-08-30 00:00:00'
  GROUP BY 1
),
model AS (
  SELECT regr_slope(dau, day_no) AS b, regr_intercept(dau, day_no) AS a
  FROM daily
)
SELECT
  min(day_no) AS from_day,
  max(day_no) AS to_day,
  round(CAST(avg(dau - (a + b * day_no)) AS numeric), 1) AS mean_residual
FROM daily CROSS JOIN model
GROUP BY CAST(floor((day_no - 1) / 10.0) AS integer)
ORDER BY from_day;
sqlНаклон выручки по DAU по каналам на общем окне, июль–август: от 1,19 до 0,54
WITH dau AS (
  SELECT CAST(e.event_time AS date) AS day, u.channel, count(DISTINCT e.user_id) AS dau
  FROM events AS e
  JOIN users AS u ON u.user_id = e.user_id
  WHERE e.event_time < TIMESTAMP '2026-08-30 00:00:00'
  GROUP BY 1, 2
),
revenue AS (
  SELECT p.paid_at AS day, u.channel, sum(p.amount) AS revenue
  FROM payments AS p
  JOIN users AS u ON u.user_id = p.user_id
  GROUP BY 1, 2
),
daily AS (
  SELECT d.day, d.channel, d.dau, COALESCE(r.revenue, 0) AS revenue
  FROM dau AS d
  LEFT JOIN revenue AS r ON r.day = d.day AND r.channel = d.channel
)
SELECT
  channel,
  regr_count(revenue, dau) AS days,
  round(CAST(regr_slope(revenue, dau) AS numeric), 2) AS slope,
  round(CAST(regr_r2(revenue, dau) AS numeric), 2) AS r2
FROM daily
WHERE day >= DATE '2026-07-01'
GROUP BY channel
ORDER BY slope DESC;

Доказывает ли регрессия причинность

Нет. Наклон 1,17 описывает, чем дни с большим DAU отличались от дней с маленьким. Он не говорит, что будет, если DAU поднять. В учебной базе оплата — первая покупка или продление — почти не связана с заходом в продукт в тот же день: из 1 222 оплат только 201 (16%) пришлась на день, когда плательщик что-то делал в сервисе, — столько же, сколько в случайный день. И аудитория, и выручка растут по одной причине — растёт база пользователей.

Проверить это можно, убрав общий тренд. Вычтем из DAU и из выручки их линии по номеру дня и построим регрессию по остаткам: так сравниваются дни, которые различаются по DAU при одном и том же положении в календаре. Наклон падает с 1,17 до 0,36, и его 95%-й интервал, от −0,31 до 1,02, включает ноль. Один номер дня без DAU объясняет выручку не хуже: R² = 0,785 против 0,773.

Приросты день к дню говорят то же, но слабее. По 89 парам наклон равен 0,198, r = 0,041, R² = 0,002, p-value 0,70. Приросты выручки шумят так сильно, что интервал наклона — от −0,83 до 1,23: в одиночку этот тест ничего не решает. Вместе оба расчёта сходятся: своей связи DAU с выручкой, помимо общего роста, данные не показывают.

Поэтому фраза «каждый активный пользователь приносит 1,17 доллара в день» — неверное чтение уравнения. Причину показывает эксперимент или хотя бы сравнение, в котором третья величина не может двигать обе стороны сразу. Подробно — в разборе корреляции и причинности.

Регрессия без общего тренда: наклон по остаткам 0,357 вместо 1,171
WITH daily AS (
  SELECT d.day - DATE '2026-06-01' + 1 AS day_no, d.dau, COALESCE(r.revenue, 0) AS revenue
  FROM (SELECT CAST(event_time AS date) AS day, count(DISTINCT user_id) AS dau
        FROM events GROUP BY 1) AS d
  LEFT JOIN (SELECT paid_at AS day, sum(amount) AS revenue
             FROM payments GROUP BY 1) AS r ON r.day = d.day
  WHERE d.day < DATE '2026-08-30'
),
trend AS (
  SELECT
    regr_slope(revenue, day_no) AS rev_b, regr_intercept(revenue, day_no) AS rev_a,
    regr_slope(dau, day_no) AS dau_b, regr_intercept(dau, day_no) AS dau_a,
    regr_r2(revenue, day_no) AS r2_day_only
  FROM daily
),
detrended AS (
  SELECT
    revenue - (rev_a + rev_b * day_no) AS revenue_res,
    dau - (dau_a + dau_b * day_no) AS dau_res,
    r2_day_only
  FROM daily CROSS JOIN trend
)
SELECT
  round(CAST(regr_slope(revenue_res, dau_res) AS numeric), 3) AS slope_without_trend,
  round(CAST(r2_day_only AS numeric), 3) AS r2_day_only
FROM detrended
GROUP BY r2_day_only;
sqlРегрессия приростов день к дню: наклон 0,198, R² 0,002
WITH daily AS (
  SELECT d.day, d.dau, COALESCE(r.revenue, 0) AS revenue
  FROM (SELECT CAST(event_time AS date) AS day, count(DISTINCT user_id) AS dau
        FROM events GROUP BY 1) AS d
  LEFT JOIN (SELECT paid_at AS day, sum(amount) AS revenue
             FROM payments GROUP BY 1) AS r ON r.day = d.day
),
steps AS (
  SELECT
    dau - lag(dau) OVER (ORDER BY day) AS dau_change,
    revenue - lag(revenue) OVER (ORDER BY day) AS revenue_change
  FROM daily
  WHERE day < DATE '2026-08-30'
)
SELECT
  regr_count(revenue_change, dau_change) AS pairs,
  round(CAST(regr_slope(revenue_change, dau_change) AS numeric), 3) AS slope,
  round(CAST(corr(revenue_change, dau_change) AS numeric), 3) AS r,
  round(CAST(regr_r2(revenue_change, dau_change) AS numeric), 3) AS r2
FROM steps;

Что ещё называют регрессией: к среднему, множественная, логистическая

Регрессия к среднему — это не модель, а явление: после необычно высокого или низкого значения следующее чаще оказывается ближе к среднему. Рекордный по выручке день редко повторяется назавтра, и дело не в том, что команда что-то сломала. Название пришло от Фрэнсиса Гальтона. В статье 1886 года «Regression towards Mediocrity in Hereditary Stature» он описал рост родителей и детей: у очень высоких родителей дети в среднем ниже их, у очень низких — выше. От этой работы слово «регрессия» перешло к уравнениям, которыми такую связь описывают.

Множественная регрессия — то же уравнение с несколькими x: выручку объясняют сразу DAU, каналом и днём недели. Каждый коэффициент читается как «при прочих равных». Она нужна, когда факторы действуют одновременно и разделить их вклад одной линией нельзя — например, когда на выручку сразу влияют DAU и день недели.

Логистическая регрессия нужна, когда y — это «да» или «нет»: оплатил, ушёл, кликнул. Она предсказывает вероятность от 0 до 1. Прямая для такой задачи не годится: при больших и малых x она выдаёт «вероятности» ниже нуля и выше единицы.

Частые вопросы

Чем регрессия отличается от линейной регрессии? Регрессия — общее название уравнений, которые показывают, как в среднем меняется одна величина при изменении другой. Линейная — случай, когда связь описывают прямой y = a + b·x.

Чем регрессия отличается от корреляции? Корреляция — одно число от −1 до 1 о том, насколько тесно точки лежат вокруг прямой; она не меняется, если поменять величины местами. Регрессия даёт саму прямую в единицах измерения, и для неё важно, что считать y. В нашем примере r = 0,879 в обе стороны, а наклоны — 1,171 и 0,660.

Что показывает коэффициент регрессии? На сколько единиц в среднем меняется y, когда x увеличивается на одну единицу. Знак показывает направление, а величина зависит от единиц: в долларах на пользователя и в центах на пользователя это разные числа при одной и той же линии.

Какой R² считается хорошим? Общего порога нет: он зависит от того, насколько шумна сама величина и какая точность нужна для решения. К тому же R² не говорит, верна ли форма: тренд DAU с R² = 0,962 систематически ошибается на краях, а линия выручки с 0,773 — нет. Сначала смотрите на остатки, потом на R².

Как построить регрессию в Excel? Наклон считает функция НАКЛОН(известные_значения_y; известные_значения_x), свободный член — ОТРЕЗОК с теми же аргументами, R² — КВПИРСОН. Проверьте себя на пяти точках из примера: должны получиться 0,8, 26 и 0,64. На точечной диаграмме ту же прямую рисует линия тренда: вкладка «Конструктор диаграмм» (в старых версиях — «Конструктор»), «Добавить элемент диаграммы», «Линия тренда», «Линейная». Уравнение и R² появятся, если в параметрах линии отметить «показывать уравнение на диаграмме» и «поместить на диаграмму величину достоверности аппроксимации (R^2)».

Что читать дальше

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

Продолжить чтение
Вся библиотека