Как посчитать hazard rate в SQL
Содержание:
Зачем hazard rate
Survival говорит «сколько выжило». Hazard rate — «вероятность отвалиться прямо сейчас, при условии что дожил до t». Полезно, чтобы видеть когда наибольший риск: первые 7 дней trial, перед renewal, через месяц после downgrade. На отдельных периодах вырастающий hazard сигнализирует о структурной проблеме.
Формула
h(t_i) = d_i / n_id_i— события в точкеt_in_i— at-risk до началаt_i
В отличие от probability вообще, hazard условный — «при условии, что юзер дожил».
Hazard rate в SQL
WITH user_times AS (
SELECT
CASE
WHEN event_date IS NOT NULL THEN (event_date - start_date)::INT
ELSE (observed_until - start_date)::INT
END AS t,
event_date IS NOT NULL AS event_occurred
FROM subscriptions
WHERE start_date >= '2026-01-01'
),
risk_set AS (
SELECT
t,
COUNT(*) FILTER (WHERE event_occurred) AS events,
SUM(COUNT(*)) OVER (ORDER BY t DESC) AS at_risk
FROM user_times
GROUP BY t
)
SELECT
t,
at_risk,
events,
events::NUMERIC / NULLIF(at_risk, 0) AS hazard
FROM risk_set
ORDER BY t;Hazard 0.05 на t=14 означает «5% юзеров, доживших до 14 дней, отвалятся именно на 14-й».
Изменение во времени
Чтобы видеть тренд, агрегируйте по недельным интервалам:
WITH user_times AS (
SELECT
CASE
WHEN event_date IS NOT NULL THEN (event_date - start_date)::INT
ELSE (observed_until - start_date)::INT
END AS t,
event_date IS NOT NULL AS event_occurred
FROM subscriptions
),
weekly AS (
SELECT
(t / 7) AS week,
COUNT(*) FILTER (WHERE event_occurred) AS events,
COUNT(*) AS observed
FROM user_times
GROUP BY (t / 7)
),
cumulative AS (
SELECT
week,
events,
SUM(observed) OVER (ORDER BY week DESC) AS at_risk_week_start
FROM weekly
)
SELECT
week,
at_risk_week_start,
events,
events::NUMERIC / NULLIF(at_risk_week_start, 0) AS hazard_per_week
FROM cumulative
ORDER BY week;«Горб» на week=2-3 — типичный паттерн trial-юзеров. «Горб» на week=52 — annual subscription renewal.
Hazard ratio между группами
Hazard ratio = h_A(t) / h_B(t). В Cox model даёт «во сколько раз риск выше». Грубо в SQL:
WITH risk_by_group AS (
SELECT
plan,
(t / 7) AS week,
COUNT(*) FILTER (WHERE event_occurred)::NUMERIC
/ NULLIF(SUM(COUNT(*)) OVER (PARTITION BY plan ORDER BY (t / 7) DESC), 0) AS hazard_week
FROM user_times
GROUP BY plan, (t / 7)
)
SELECT
week,
MAX(CASE WHEN plan = 'free' THEN hazard_week END) AS hazard_free,
MAX(CASE WHEN plan = 'paid' THEN hazard_week END) AS hazard_paid,
MAX(CASE WHEN plan = 'free' THEN hazard_week END)
/ NULLIF(MAX(CASE WHEN plan = 'paid' THEN hazard_week END), 0) AS hazard_ratio
FROM risk_by_group
GROUP BY week
ORDER BY week;Hazard ratio > 1 → free отваливается быстрее. Стат-значимость через Cox / log-rank — вне SQL.
Частые ошибки
Ошибка 1. Считать events / total, не events / at_risk.
Это conversion rate, не hazard. Hazard смотрит только на ещё «живых» в точке t.
Ошибка 2. At-risk через FORWARD running sum.
Reverse cumulative: все юзеры с t >= t_current. Postgres SUM(...) OVER (ORDER BY t DESC) или эквивалент.
Ошибка 3. Hazard как survival. Survival = Π(1 - h). Hazard сам по себе — мгновенная вероятность, не накопленная.
Ошибка 4. Hazard на censored events.
Censored ≠ event. Не считайте их в d_i, только в n_i.
Ошибка 5. Сравнивать hazards без учёта confounders. Free и paid могут отличаться не только тарифом, но и source-каналом. Cox model для multiple covariates.
Связанные темы
- Как посчитать Kaplan-Meier в SQL
- Как посчитать survival rate в SQL
- Как посчитать churn в SQL
- Hazard ratio простыми словами
FAQ
Hazard vs churn rate?
Churn rate обычно period-based и не учитывает «доживание». Hazard — условная вероятность в точке t.
Hazard и risk — одно?
Нет. Risk — кумулятивная вероятность к моменту t. Hazard — мгновенная.
Какой hazard «нормален»?
Зависит от продукта. Для SaaS week-1 hazard 5-10% типично, потом падает.
Hazard 0 — это значит «бессмертный»?
Может быть censoring или маленький at_risk. Проверяйте at_risk рядом.
Hazard ratio > 2 — много?
В medical > 2 — серьёзный effect. В продукте контекст-зависимо.