Как посчитать t-test в SQL
Содержание:
Зачем t-test
T-test проверяет, отличаются ли средние двух выборок. В A/B используют, когда метрика непрерывная — выручка на юзера, время на странице, число просмотров. Welch's t-test — версия для разных дисперсий и размеров групп (default в современных стат-пакетах).
Welch's формула
t = (μ_A − μ_B) / sqrt(σ_A²/n_A + σ_B²/n_B)df = (σ_A²/n_A + σ_B²/n_B)² / ((σ_A²/n_A)² / (n_A − 1) + (σ_B²/n_B)² / (n_B − 1))При df > 30 распределение t практически совпадает с нормальным — критическое значение для α=0.05 (двусторонний) ≈ 1.96.
T-test в SQL
WITH stats AS (
SELECT
variant,
COUNT(*) AS n,
AVG(revenue) AS mean,
VAR_SAMP(revenue) AS variance
FROM ab_users
WHERE experiment_id = 'pricing_v2'
GROUP BY variant
),
pair AS (
SELECT
MAX(CASE WHEN variant = 'A' THEN n END) AS n_a,
MAX(CASE WHEN variant = 'A' THEN mean END) AS mean_a,
MAX(CASE WHEN variant = 'A' THEN variance END) AS var_a,
MAX(CASE WHEN variant = 'B' THEN n END) AS n_b,
MAX(CASE WHEN variant = 'B' THEN mean END) AS mean_b,
MAX(CASE WHEN variant = 'B' THEN variance END) AS var_b
FROM stats
)
SELECT
mean_a,
mean_b,
mean_b - mean_a AS diff,
(mean_b - mean_a) / SQRT(var_a / n_a + var_b / n_b) AS t_statistic,
POWER(var_a / n_a + var_b / n_b, 2)
/ (POWER(var_a / n_a, 2) / NULLIF(n_a - 1, 0)
+ POWER(var_b / n_b, 2) / NULLIF(n_b - 1, 0)) AS welch_df,
CASE
WHEN ABS((mean_b - mean_a) / SQRT(var_a / n_a + var_b / n_b)) > 1.96
THEN 'significant at ~0.05'
ELSE 'NOT significant'
END AS verdict
FROM pair;A/B-тест по ARPU
То же, но revenue per user считают агрегацией ордеров:
WITH user_revenue AS (
SELECT
u.user_id,
u.variant,
COALESCE(SUM(o.amount), 0) AS revenue
FROM ab_users u
LEFT JOIN orders o
ON o.user_id = u.user_id
AND o.created_at >= u.bucket_at
WHERE u.experiment_id = 'paywall_v3'
GROUP BY u.user_id, u.variant
),
stats AS (
SELECT
variant,
COUNT(*) AS n,
AVG(revenue) AS mean,
VAR_SAMP(revenue) AS variance
FROM user_revenue
GROUP BY variant
)
SELECT * FROM stats;Дальше из stats тот же блок Welch-формулы, что в предыдущем примере.
Paired t-test
Когда сравниваете одного юзера в двух условиях (до/после):
WITH paired AS (
SELECT
user_id,
revenue_before,
revenue_after,
revenue_after - revenue_before AS diff
FROM user_before_after
WHERE revenue_before IS NOT NULL
AND revenue_after IS NOT NULL
)
SELECT
COUNT(*) AS n,
AVG(diff) AS mean_diff,
STDDEV_SAMP(diff) AS sd_diff,
AVG(diff) / NULLIF(STDDEV_SAMP(diff) / SQRT(COUNT(*)), 0) AS t_statistic
FROM paired;df = n − 1.
Частые ошибки
Ошибка 1. Population vs sample variance.
В SQL VAR_POP делит на N, VAR_SAMP — на N-1. Для t-test нужен VAR_SAMP (sample).
Ошибка 2. T-test на сильно скошенных данных. Revenue distributions обычно log-normal. На малых выборках t-test перекосит. Bootstrap или log-transform.
Ошибка 3. Считать без NULL-фильтра.
AVG игнорирует NULL, но COUNT(*) — нет. n окажется завышен → дисперсия среднего занижена.
Ошибка 4. Один юзер в двух группах.
SRM / cross-contamination. Перепроверьте ab_users на дубли user_id.
Ошибка 5. Сравнивать с z-test на маленьких выборках. До n ≈ 30 разница t и z существенна. После — пренебрежима.
Связанные темы
- Как посчитать chi-square test в SQL
- Как посчитать Mann-Whitney test в SQL
- Что такое t-test простыми словами
- T-test vs z-test
FAQ
Welch's vs Student's?
Welch's — для разных дисперсий и разных n. Default в scipy и большинстве A/B-калькуляторов. Student's — только если дисперсии равны (проверяют редко).
t-statistic, t-statistic с p-value?
t-stat сам по себе бесполезен. P-value через scipy.stats.t.sf(|t|, df) * 2 (двусторонний).
df < 30, что делать?
Используйте точное t-распределение. Не приближайте нормальным.
Skewed revenue?
Bootstrap CI или log-transform. На большой n (10k+) Welch остаётся валидным даже на skewed.
A/B по конверсии — t-test нужен?
Нет, для бинарных метрик — chi-square или two-proportion z-test.