T-тесты для сравнения средних
Выполните одновыборочный, независимый двухвыборочный и парный t-тесты с помощью scipy.stats и интерпретируйте доверительные интервалы
«T-тесты для сравнения средних» — бесплатный урок Pandas & NumPy Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Pandas & NumPy Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Pandas & NumPy Academy содержит 4 уроков всего.
Что такое t-тест
t-тест — это проверка гипотезы, которая определяет, является ли различие между mean групп статистически значимым или, скорее всего, объясняется случайностью. Она сопоставляет наблюдаемое различие с изменчивостью данных и возвращает t-статистику и p-значение. Если p ≤ 0.05 (принятый порог), мы отвергаем нулевую гипотезу о равенстве mean. Существует три распространённых типа: для одной выборки, для двух независимых выборок и парный t-тест — каждый предназначен для своего экспериментального плана.
t-тест для одной выборки
t-тест для одной выборки проверяет, отличается ли mean выборки статистически значимо от известного или предполагаемого mean генеральной совокупности. Например: «Значимо ли наше среднее время доставки отличается от отраслевого стандарта в 3 дня?» Используйте scipy.stats.ttest_1samp(data, popmean). Нулевая гипотеза: H₀: mean = popmean. Малое p-значение позволяет отвергнуть H₀ и заключить, что mean выборки отличается от целевого значения.
import numpy as np
from scipy import stats
np.random.seed(42)
# Delivery times in days (true mean is ~3.5, not 3)
delivery_times = np.random.normal(loc=3.5, scale=0.8, size=50)
# Test: is our mean significantly different from 3 days?
stat, p = stats.ttest_1samp(delivery_times, popmean=3.0)
print(f'Sample mean: {delivery_times.mean():.3f}')
print(f't-statistic: {stat:.3f}')
print(f'p-value: {p:.4f}')
print('Differs from 3?', 'Yes' if p < 0.05 else 'No')t-тест для двух независимых выборок
t-тест для двух независимых выборок сравнивает mean двух независимых групп. Например: «Приносит ли вариант A/B-теста более высокий средний доход, чем контрольная группа?» Используйте scipy.stats.ttest_ind(group_a, group_b). Параметр equal_var имеет значение: устанавливайте equal_var=False (t-тест Уэлча), если дисперсии двух групп могут различаться; это безопасный вариант по умолчанию для большинства реальных данных.
import numpy as np
from scipy import stats
np.random.seed(0)
# A/B test revenue per session
control = np.random.normal(loc=25.0, scale=8.0, size=80)
variant = np.random.normal(loc=28.0, scale=9.0, size=80)
# Welch's t-test (does not assume equal variances)
stat, p = stats.ttest_ind(control, variant, equal_var=False)
print(f'Control mean: {control.mean():.2f}')
print(f'Variant mean: {variant.mean():.2f}')
print(f't-statistic: {stat:.3f}')
print(f'p-value: {p:.4f}')
print('Significant difference?', 'Yes' if p < 0.05 else 'No')Односторонние и двусторонние проверки
По умолчанию t-тесты являются двусторонними: они проверяют, различаются ли mean в любом направлении (больше OR меньше). Если у Вас есть направленная гипотеза («вариант увеличивает доход»), используйте одностороннюю проверку с параметром alternative: 'greater' или 'less'. Односторонняя проверка обладает большей мощностью для конкретного направления, но не даёт свидетельств о другом направлении. Определите направление до изучения данных, чтобы избежать HARKing (формулирования гипотезы после того, как результаты стали известны).
import numpy as np
from scipy import stats
np.random.seed(1)
control = np.random.normal(25, 8, 100)
variant = np.random.normal(27, 8, 100)
# Two-tailed (default): does mean differ at all?
stat, p_two = stats.ttest_ind(control, variant, equal_var=False,
alternative='two-sided')
# One-tailed: is variant > control?
stat, p_one = stats.ttest_ind(control, variant, equal_var=False,
alternative='less')
print(f'Two-tailed p: {p_two:.4f}')
print(f'One-tailed p (variant greater): {p_one:.4f}')Парный t-тест
Парный t-тест используется, когда каждому наблюдению в группе A соответствует естественная пара в группе B — например, измерения до и после воздействия на одних и тех же испытуемых или данные одних и тех же магазинов за два разных месяца. Попарное сопоставление учитывает межсубъектную изменчивость, поэтому для этих данных проверка становится мощнее, чем t-тест для независимых выборок. Используйте scipy.stats.ttest_rel(before, after). Порядок элементов должен совпадать: before[i] и after[i] должны относиться к одному и тому же испытуемому.
import numpy as np
from scipy import stats
np.random.seed(5)
# Blood pressure before and after medication (paired)
before = np.random.normal(130, 10, 30)
after = before - np.random.normal(5, 3, 30) # Treatment reduces BP by ~5
stat, p = stats.ttest_rel(before, after)
print(f'Mean before: {before.mean():.2f}')
print(f'Mean after: {after.mean():.2f}')
print(f't-statistic: {stat:.3f}')
print(f'p-value: {p:.4f}')
print('Treatment effective?', 'Yes' if p < 0.05 else 'No')Интерпретация t-статистики
t-статистика показывает, на сколько стандартных ошибок наблюдаемое различие удалено от нуля. t-статистика, равная 2, означает, что наблюдаемое различие на 2 стандартные ошибки выше значения, ожидаемого при нулевой гипотезе. Для двусторонней проверки при α=0.05 и большой выборке критическое значение приблизительно равно ±1.96, поэтому |t| > 1.96 даёт p < 0.05. p-значение преобразует t-статистику в вероятность, благодаря чему её проще интерпретировать без обращения к таблицам t-распределения.
import numpy as np
from scipy import stats
# Demonstrate relationship between t-statistic and p-value
for t_val in [1.0, 1.96, 2.5, 3.0, 4.0]:
# degrees of freedom = large sample -> t ~ normal
p = 2 * stats.t.sf(abs(t_val), df=100) # two-tailed
print(f't = {t_val:4.2f} -> p = {p:.4f} '
f'({'significant' if p < 0.05 else 'not significant'})')Доверительные интервалы для разности средних
Одно p-значение не сообщает Вам о величине эффекта. Всегда вычисляйте доверительный интервал для разности mean. 95%-ный ДИ, включающий ноль, согласуется с p > 0.05 (различие незначимо); интервал, не включающий ноль, означает, что различие значимо. ДИ также показывает, имеет ли размер эффекта практическое значение: статистически значимое различие в доходе на $0.01 может быть несущественным, тогда как различие в $50 при p=0.06 всё ещё может иметь значение для бизнеса.
import numpy as np
from scipy import stats
np.random.seed(0)
control = np.random.normal(25, 8, 80)
variant = np.random.normal(28, 8, 80)
diff = variant.mean() - control.mean()
se = np.sqrt(control.var()/len(control) + variant.var()/len(variant))
df = len(control) + len(variant) - 2
t_crit = stats.t.ppf(0.975, df=df)
ci_low = diff - t_crit * se
ci_high = diff + t_crit * se
print(f'Mean difference: {diff:.2f}')
print(f'95% CI: [{ci_low:.2f}, {ci_high:.2f}]')
print('CI excludes zero:', ci_low > 0 or ci_high < 0)Размер эффекта: d Коэна
Статистическая значимость зависит от размера выборки: при достаточно больших выборках значимыми становятся даже тривиальные различия. d Коэна измеряет практическую значимость (размер эффекта): разность средних, делённую на объединённое стандартное отклонение. Ориентиры: d < 0,2 — пренебрежимо малый эффект, 0,2–0,5 — малый, 0,5–0,8 — средний, > 0,8 — большой. Всегда указывайте размер эффекта вместе с p-значениями: значимое p-значение при d = 0,05 означает, что различие действительно существует, но, вероятно, не имеет практического значения.
import numpy as np
from scipy import stats
np.random.seed(0)
g1 = np.random.normal(25, 8, 200)
g2 = np.random.normal(28, 8, 200)
stat, p = stats.ttest_ind(g1, g2)
# Cohen's d
pooled_std = np.sqrt((g1.var() + g2.var()) / 2)
cohens_d = (g2.mean() - g1.mean()) / pooled_std
print(f'p-value: {p:.4f}')
print(f'Cohen\'s d: {cohens_d:.3f}')
if cohens_d < 0.2: print('Effect: negligible')
elif cohens_d < 0.5: print('Effect: small')
elif cohens_d < 0.8: print('Effect: medium')
else: print('Effect: large')Допущения t-критерия
Независимый t-критерий предполагает: (1) независимость — наблюдения внутри каждой группы не зависят друг от друга; (2) нормальность — данные в каждой группе примерно распределены нормально (при n > 30 критерий устойчив благодаря CLT); (3) для t-критерия Стьюдента (equal_var=True) — равенство дисперсий. t-критерий Уэлча (equal_var=False) не требует третьего допущения и предпочтителен. Если при небольших выборках (< 30) нормальность серьёзно нарушена, используйте вместо него U-критерий Манна — Уитни.
import numpy as np
from scipy import stats
np.random.seed(0)
g1 = np.random.normal(10, 2, 30)
g2 = np.random.normal(11, 5, 30) # Very different variance!
# Levene test for equal variances
stat_l, p_l = stats.levene(g1, g2)
print(f'Levene test p: {p_l:.4f}')
if p_l < 0.05:
print('Unequal variances -> use Welch\'s (equal_var=False)')
stat, p = stats.ttest_ind(g1, g2, equal_var=False)
else:
print('Equal variances OK -> Student\'s t-test')
stat, p = stats.ttest_ind(g1, g2, equal_var=True)
print(f'Result: t={stat:.3f}, p={p:.4f}')t-критерии для рядов Pandas
На практике данные, которые нужно проверить, находятся в столбце DataFrame Pandas. Ряд Pandas можно напрямую передать функциям scipy.stats: они одинаково хорошо работают как с рядами, так и с массивами NumPy. Распространённый рабочий процесс выглядит так: отфильтровать DataFrame, чтобы получить ряд каждой группы, выполнить t-критерий и сохранить результаты в сводном DataFrame. Этот подход легко масштабируется для проверки множества пар столбцов или групп в цикле.
import pandas as pd
import numpy as np
from scipy import stats
np.random.seed(0)
df = pd.DataFrame({
'group': ['A']*60 + ['B']*60,
'revenue': np.concatenate([
np.random.normal(100, 20, 60),
np.random.normal(110, 22, 60)
])
})
grp_a = df.loc[df['group'] == 'A', 'revenue']
grp_b = df.loc[df['group'] == 'B', 'revenue']
stat, p = stats.ttest_ind(grp_a, grp_b, equal_var=False)
print(f'A mean: {grp_a.mean():.2f}, B mean: {grp_b.mean():.2f}')
print(f'p-value: {p:.4f}')Проблема множественных сравнений
Одновременное выполнение множества t-критериев повышает вероятность ложноположительного результата. Если выполнить 20 t-критериев при α=0,05, то случайно ожидается 1 ложноположительный результат. Это и есть проблема множественных сравнений. Применяйте поправку Бонферрони: разделите α на количество критериев (p_threshold = 0.05 / n_tests). Для большей мощности при меньшей консервативности используйте поправку Бенджамини — Хохберга для контроля ложного обнаружения (FDR), доступную в statsmodels. При A/B-тестировании с множеством метрик всегда вносите поправку на множественные сравнения.
import numpy as np
from scipy import stats
np.random.seed(0)
n_tests = 10
results = []
for i in range(n_tests):
g1 = np.random.normal(0, 1, 50)
g2 = np.random.normal(0.1, 1, 50) # Tiny true effect
_, p = stats.ttest_ind(g1, g2)
results.append(p)
print('p-values:', [round(p, 3) for p in results])
bonferroni_thresh = 0.05 / n_tests
print(f'Bonferroni threshold: {bonferroni_thresh}')
print('Significant after Bonferroni:', sum(p < bonferroni_thresh for p in results))Быстрая проверка
Проверьте, насколько хорошо Вы поняли концепции анализа данных из этого урока.
Итоги урока
В этом уроке Вы узнали, что ttest_1samp сравнивает среднее выборки с опорным значением, ttest_ind (критерий Уэлча с equal_var=False) сравнивает две независимые группы, а ttest_rel работает с парными измерениями. Всегда указывайте d Коэна вместе с p-значением, чтобы отличать статистическую значимость от практической. Далее Вы проверите взаимосвязи между категориальными переменными с помощью критерия хи-квадрат.
Изучай Python с ИИ-репетитором — бесплатно
Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.
- Курсы
- 30
- Уроки
- 120
Часто задаваемые вопросы
Урок «T-тесты для сравнения средних» бесплатный?
Да — полный текст урока «T-тесты для сравнения средних» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Pandas & NumPy Academy, подпишись на CoddyKit PRO. Курс Pandas & NumPy Academy содержит 4 уроков всего.
Чему я научусь в уроке «T-тесты для сравнения средних»?
Выполните одновыборочный, независимый двухвыборочный и парный t-тесты с помощью scipy.stats и интерпретируйте доверительные интервалы Ты практикуешь Pandas & NumPy Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Pandas & NumPy Academy?
Предыдущий опыт не требуется. Pandas & NumPy Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «T-тесты для сравнения средних»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Pandas & NumPy Academy?
Да. Каждый урок Pandas & NumPy Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Описательная статистика и проверка нормальности
- T-тесты для сравнения средних
- Критерий хи-квадрат для проверки независимости
- ANOVA и апостериорные тесты