Критерий Пирсона: проверка гипотез и анализ данных на Python
Для кого эта статья:
- Аналитики данных и специалисты по статистическому анализу
- Студенты и обучающиеся в области аналитики данных
Практикующие программисты на Python, интересующиеся статистическими методами
Критерий Пирсона (χ²) — это статистический инструмент, который помогает выявить связь между категориальными переменными и проверить, насколько наблюдаемые данные соответствуют ожидаемым. Несмотря на математическую мощь, многие аналитики избегают этого метода из-за кажущейся сложности. Однако с Python расчет критерия Пирсона превращается в элегантный процесс, требующий всего нескольких строк кода. 💻 В этой статье я разберу практические примеры реализации критерия χ² на Python — от базовых вычислений до визуализации и интерпретации результатов.
Хотите быстро освоить статистические методы анализа данных, включая критерий Пирсона? Программа Профессия аналитик данных от Skypro поможет вам овладеть всем спектром инструментов Python для статистического анализа. Вы научитесь не только применять готовые решения, но и понимать математическую логику за каждым методом. Уже через 9 месяцев вы сможете уверенно проводить сложный статистический анализ и принимать data-driven решения.
Теоретические основы критерия Пирсона и подготовка среды Python
Критерий χ² (хи-квадрат) Пирсона — один из фундаментальных методов в статистике, используемый для определения связи между категориальными переменными. В основе метода лежит сравнение фактически наблюдаемых частот с теоретически ожидаемыми значениями при условии справедливости нулевой гипотезы. Формула для расчета статистики χ² выглядит следующим образом:
χ² = ∑ (O – E)²/E
где O — наблюдаемые значения, E — ожидаемые значения.
Перед началом работы необходимо настроить среду Python для статистического анализа. Ключевые библиотеки, которые понадобятся для расчета критерия Пирсона:
- NumPy — для эффективных числовых вычислений
- SciPy — для статистических функций, включая реализацию критерия χ²
- Pandas — для структурирования и обработки данных
- Matplotlib и Seaborn — для визуализации результатов анализа
Установка необходимых библиотек выполняется одной командой:
pip install numpy scipy pandas matplotlib seaborn
После установки библиотек импортируем их в рабочее окружение:
import numpy as np
import scipy.stats as stats
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# Для красивого отображения графиков
plt.style.use('ggplot')
sns.set(style="whitegrid")
Андрей Соколов, ведущий специалист по анализу данных
Когда я только начинал работать с критерием Пирсона, часто возникала путаница между разными типами задач. Помню случай, когда клиенту требовалось определить, влияет ли сезонность на покупательское поведение. Я инстинктивно применил t-критерий, получил странные результаты, и только потом осознал, что имею дело с категориальными данными. Переключившись на критерий χ², я обнаружил сильную сезонную зависимость (p < 0.001). Этот опыт научил меня первым делом определять тип данных и задачи: для непрерывных данных подходят t-тесты и ANOVA, а для категориальных — именно критерий Пирсона. Теперь это первый шаг в моем аналитическом процессе.
Существует несколько типов задач, где критерий Пирсона применяется наиболее часто:
| Тип задачи | Описание | Пример применения |
|---|---|---|
| Проверка согласия распределений | Проверяет, соответствуют ли наблюдаемые данные ожидаемому распределению | Проверка нормальности распределения данных |
| Таблицы сопряженности | Определяет наличие связи между двумя категориальными переменными | Зависимость между полом и выбором продукта |
| Тест однородности | Проверяет, имеют ли несколько популяций одинаковое распределение | Сравнение возрастных групп по предпочтениям |

Пошаговый расчет критерия Пирсона с использованием NumPy и SciPy
Рассмотрим процесс расчета критерия Пирсона на конкретном примере. Предположим, у нас есть данные о выборе программного языка среди разработчиков разного уровня опыта. Нам нужно проверить, существует ли зависимость между опытом и выбором языка. 🔍
Шаг 1: Создание таблицы наблюдаемых частот:
# Создаем данные: опыт (строки) × язык программирования (столбцы)
observed = np.array([
[30, 14, 6], # Junior (Python, Java, C++)
[45, 30, 25], # Middle (Python, Java, C++)
[25, 36, 49] # Senior (Python, Java, C++)
])
# Создаем датафрейм для лучшей визуализации
df_observed = pd.DataFrame(
observed,
index=['Junior', 'Middle', 'Senior'],
columns=['Python', 'Java', 'C++']
)
print("Наблюдаемые частоты:")
print(df_observed)
Шаг 2: Расчет ожидаемых частот в соответствии с нулевой гипотезой (отсутствие зависимости):
# Суммы по строкам и столбцам
row_totals = observed.sum(axis=1)
col_totals = observed.sum(axis=0)
total = observed.sum()
# Расчет ожидаемых частот
expected = np.outer(row_totals, col_totals) / total
# Создаем датафрейм ожидаемых частот
df_expected = pd.DataFrame(
expected,
index=['Junior', 'Middle', 'Senior'],
columns=['Python', 'Java', 'C++']
)
print("\nОжидаемые частоты:")
print(df_expected)
Шаг 3: Расчет статистики χ² вручную:
# Ручной расчет статистики хи-квадрат
chi2_stat = np.sum((observed – expected) ** 2 / expected)
# Степени свободы = (строки – 1) * (столбцы – 1)
dof = (observed.shape[0] – 1) * (observed.shape[1] – 1)
# p-значение
p_value = 1 – stats.chi2.cdf(chi2_stat, dof)
print(f"\nСтатистика χ²: {chi2_stat:.4f}")
print(f"Степени свободы: {dof}")
print(f"p-значение: {p_value:.6f}")
Шаг 4: Проверка результатов с помощью встроенной функции SciPy:
# Использование встроенной функции SciPy
chi2_result = stats.chi2_contingency(observed)
print("\nРезультаты с использованием stats.chi2_contingency:")
print(f"Статистика χ²: {chi2_result[0]:.4f}")
print(f"p-значение: {chi2_result[1]:.6f}")
print(f"Степени свободы: {chi2_result[2]}")
Давайте сравним ручной расчет с результатом встроенной функции SciPy:
# Сравнение результатов
print("\nРазница между ручным расчетом и функцией SciPy:")
print(f"Разница в статистике χ²: {abs(chi2_stat – chi2_result[0]):.10f}")
print(f"Разница в p-значении: {abs(p_value – chi2_result[1]):.10f}")
При правильном расчете разница будет минимальной, обусловленной только погрешностью вычислений. Это подтверждает корректность нашего пошагового метода. 👍
Преимущество использования SciPy заключается в простоте и надежности — библиотека автоматически обрабатывает все вычисления, включая расчет ожидаемых частот и оптимизированные алгоритмы для определения p-значения.
Применение критерия Пирсона для анализа категориальных данных
Критерий Пирсона особенно полезен для анализа категориальных данных, которые часто встречаются в маркетинговых исследованиях, социальных науках и медицине. Рассмотрим несколько типичных сценариев применения.
Мария Волкова, аналитик данных в ритейле
При работе с крупным онлайн-магазином я столкнулась с необходимостью определить, влияет ли способ доставки на вероятность возврата товаров. У нас были данные о тысячах заказов с разными способами доставки (курьер, пункт выдачи, почта) и информация о возвратах. Первоначальный анализ корреляций не дал четкой картины. Когда я применила критерий χ², результаты были поразительными — p-значение составило 0.0002, что однозначно указывало на наличие зависимости. Дальнейший анализ остатков показал, что заказы с курьерской доставкой возвращаются значительно реже. Это привело к стратегическому изменению в компании: клиентам с высокой стоимостью корзины стали предлагать скидку на курьерскую доставку, что снизило общий процент возвратов на 14% за квартал.
Сценарий 1: Анализ зависимости категориальных переменных
Проверим, существует ли связь между уровнем образования и предпочтениями в выборе операционной системы:
# Создаем данные: образование × ОС
education_os = np.array([
[150, 100, 50], # Среднее (Windows, macOS, Linux)
[120, 130, 50], # Бакалавр (Windows, macOS, Linux)
[80, 150, 120] # Магистр/PhD (Windows, macOS, Linux)
])
# Проведем тест хи-квадрат
chi2, p, dof, expected = stats.chi2_contingency(education_os)
print(f"Статистика χ²: {chi2:.4f}")
print(f"p-значение: {p:.6f}")
print(f"Степени свободы: {dof}")
# Интерпретация результата
alpha = 0.05
print("\nИнтерпретация:")
if p < alpha:
print(f"p-значение ({p:.6f}) < {alpha}: отклоняем нулевую гипотезу")
print("Существует зависимость между уровнем образования и предпочтениями ОС")
else:
print(f"p-значение ({p:.6f}) ≥ {alpha}: не отклоняем нулевую гипотезу")
print("Нет достаточных доказательств зависимости между образованием и предпочтениями ОС")
Сценарий 2: Анализ соответствия распределения теоретическому
Проверим, соответствует ли распределение оценок студентов ожидаемому распределению:
# Наблюдаемые частоты оценок
observed_grades = np.array([10, 25, 30, 25, 10]) # Оценки от 1 до 5
# Ожидаемое равномерное распределение
expected_grades = np.ones(5) * np.sum(observed_grades) / 5
# Проведем тест хи-квадрат
chi2_stat = np.sum((observed_grades – expected_grades) ** 2 / expected_grades)
p_value = 1 – stats.chi2.cdf(chi2_stat, len(observed_grades) – 1)
print(f"Наблюдаемые частоты: {observed_grades}")
print(f"Ожидаемые частоты: {expected_grades}")
print(f"Статистика χ²: {chi2_stat:.4f}")
print(f"p-значение: {p_value:.6f}")
# Интерпретация
if p_value < 0.05:
print("Распределение оценок не соответствует равномерному")
else:
print("Распределение оценок соответствует равномерному")
При работе с критерием Пирсона важно соблюдать несколько условий для получения корректных результатов:
| Условие | Требование | Решение при нарушении |
|---|---|---|
| Размер выборки | Достаточно большая выборка | Использовать точный тест Фишера для малых выборок |
| Ожидаемые частоты | Не менее 5 в 80% ячеек | Объединение категорий или использование альтернативных тестов |
| Независимость наблюдений | Каждое наблюдение должно быть независимым | Пересмотр дизайна исследования |
| Случайная выборка | Данные должны быть собраны случайно | Корректировка методологии сбора данных |
При соблюдении этих условий критерий Пирсона дает надежные результаты, помогая выявить статистически значимые закономерности в категориальных данных. 📊
Визуализация результатов проверки гипотез методом Пирсона
Визуализация результатов критерия Пирсона значительно упрощает интерпретацию и представление данных. Рассмотрим несколько эффективных способов визуализации с использованием matplotlib и seaborn. 📈
Пример 1: Визуализация наблюдаемых и ожидаемых частот
# Создаем данные
observed_data = np.array([
[30, 15, 5],
[25, 25, 10],
[20, 30, 40]
])
# Рассчитываем ожидаемые частоты
chi2, p, dof, expected = stats.chi2_contingency(observed_data)
# Создаем датафреймы для визуализации
df_observed = pd.DataFrame(observed_data,
index=['Группа A', 'Группа B', 'Группа C'],
columns=['Категория 1', 'Категория 2', 'Категория 3'])
df_expected = pd.DataFrame(expected,
index=['Группа A', 'Группа B', 'Группа C'],
columns=['Категория 1', 'Категория 2', 'Категория 3'])
# Настраиваем график
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# Визуализация наблюдаемых частот
sns.heatmap(df_observed, annot=True, cmap="YlGnBu", fmt=".1f", ax=axes[0])
axes[0].set_title('Наблюдаемые частоты')
# Визуализация ожидаемых частот
sns.heatmap(df_expected, annot=True, cmap="YlOrRd", fmt=".1f", ax=axes[1])
axes[1].set_title('Ожидаемые частоты')
plt.suptitle(f'Анализ частот (p-значение: {p:.6f})', fontsize=16)
plt.tight_layout()
plt.show()
Пример 2: Визуализация остатков (разницы между наблюдаемыми и ожидаемыми частотами)
# Расчет остатков (разница между наблюдаемыми и ожидаемыми частотами)
residuals = (observed_data – expected) / np.sqrt(expected)
df_residuals = pd.DataFrame(residuals,
index=['Группа A', 'Группа B', 'Группа C'],
columns=['Категория 1', 'Категория 2', 'Категория 3'])
# Визуализация остатков
plt.figure(figsize=(10, 8))
sns.heatmap(df_residuals, annot=True, cmap="coolwarm", center=0, fmt=".2f")
plt.title('Стандартизированные остатки')
plt.tight_layout()
plt.show()
Остатки — это важный инструмент для анализа конкретных ячеек в таблице сопряженности. Положительные остатки указывают, что наблюдаемое значение превышает ожидаемое (больше случаев, чем предполагалось), а отрицательные — что наблюдаемое значение меньше ожидаемого.
Интерпретация стандартизированных остатков:
- ±1.96 (примерно ±2) — значимость на уровне 0.05
- ±2.58 (примерно ±2.6) — значимость на уровне 0.01
- ±3.29 (примерно ±3.3) — значимость на уровне 0.001
Пример 3: Графическое представление p-значения на распределении χ²
# Создаем диапазон значений для распределения хи-квадрат
x = np.linspace(0, 30, 1000)
y = stats.chi2.pdf(x, dof) # PDF для распределения хи-квадрат с df степенями свободы
# Рисуем распределение и область p-значения
plt.figure(figsize=(10, 6))
plt.plot(x, y, 'b-', lw=2, label=f'χ² распределение (df={dof})')
# Заштрихованная область для критического значения
critical_value = stats.chi2.ppf(0.95, dof)
plt.fill_between(x, y, where=(x >= critical_value), color='red', alpha=0.3,
label=f'Критическая область (α=0.05)')
# Вертикальная линия для наблюдаемого значения χ²
plt.axvline(chi2, color='green', linestyle='--', lw=2,
label=f'Наблюдаемое χ²={chi2:.4f}')
# Заштрихованная область для p-значения
plt.fill_between(x, y, where=(x >= chi2), color='green', alpha=0.2,
label=f'p-значение={p:.4f}')
plt.xlabel('χ² значение')
plt.ylabel('Плотность вероятности')
plt.title('Распределение χ² и p-значение')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Визуализация распределения χ² с отмеченным наблюдаемым значением и p-значением помогает интуитивно понять результат теста. Если наблюдаемое значение χ² находится далеко в правом хвосте распределения (малое p-значение), это свидетельствует о статистически значимой связи между переменными.
Помимо этих примеров, полезно визуализировать сами данные с помощью мозаичных графиков или столбчатых диаграмм, чтобы наглядно продемонстрировать взаимосвязь категориальных переменных:
# Мозаичный график для визуализации взаимосвязи
from statsmodels.graphics.mosaicplot import mosaic
plt.figure(figsize=(10, 8))
props = {}
labelizer = lambda k: {('Группа A', 'Категория 1'): 'A1',
('Группа A', 'Категория 2'): 'A2',
('Группа A', 'Категория 3'): 'A3',
('Группа B', 'Категория 1'): 'B1',
('Группа B', 'Категория 2'): 'B2',
('Группа B', 'Категория 3'): 'B3',
('Группа C', 'Категория 1'): 'C1',
('Группа C', 'Категория 2'): 'C2',
('Группа C', 'Категория 3'): 'C3'}.get(k, '')
contingency_data = {(i, j): observed_data[i, j]
for i in range(3)
for j in range(3)}
mosaic(contingency_data, properties=props, labelizer=labelizer,
title=f'Мозаичный график (p={p:.4f})', axes_label=True)
plt.tight_layout()
plt.show()
Практические кейсы и интерпретация p-значений критерия χ²
Понимание p-значения критерия Пирсона имеет решающее значение для правильной интерпретации результатов. Рассмотрим несколько практических кейсов с разными p-значениями и их интерпретацию. 🕵️♂️
Кейс 1: Анализ эффективности маркетинговых кампаний
# Данные о конверсии для трех типов маркетинговых кампаний
marketing_data = np.array([
[120, 880], # Кампания A: [конверсия, нет конверсии]
[150, 850], # Кампания B: [конверсия, нет конверсии]
[200, 800] # Кампания C: [конверсия, нет конверсии]
])
# Проведение теста хи-квадрат
chi2, p, dof, expected = stats.chi2_contingency(marketing_data)
print(f"Кейс 1: Эффективность маркетинговых кампаний")
print(f"Статистика χ²: {chi2:.4f}")
print(f"p-значение: {p:.6f}")
# Интерпретация результата
if p < 0.05:
print("Существуют статистически значимые различия в эффективности кампаний")
# Анализ остатков для определения конкретных различий
residuals = (marketing_data – expected) / np.sqrt(expected)
for i, campaign in enumerate(['A', 'B', 'C']):
for j, outcome in enumerate(['Конверсия', 'Нет конверсии']):
print(f"Кампания {campaign}, {outcome}: остаток = {residuals[i, j]:.2f}")
if abs(residuals[i, j]) > 1.96:
direction = "выше" if residuals[i, j] > 0 else "ниже"
print(f" Значимо {direction} ожидаемого (p < 0.05)")
else:
print("Нет статистически значимых различий в эффективности кампаний")
Кейс 2: Анализ предпочтений продуктов в разных возрастных группах
# Данные о предпочтениях продуктов по возрастным группам
age_product_data = np.array([
[150, 100, 50], # 18-24: [продукт A, продукт B, продукт C]
[120, 150, 130], # 25-34: [продукт A, продукт B, продукт C]
[100, 120, 180], # 35-44: [продукт A, продукт B, продукт C]
[80, 100, 220] # 45+: [продукт A, продукт B, продукт C]
])
# Проведение теста
chi2, p, dof, expected = stats.chi2_contingency(age_product_data)
print(f"\nКейс 2: Предпочтения продуктов в разных возрастных группах")
print(f"Статистика χ²: {chi2:.4f}")
print(f"p-значение: {p:.10f}") # Очень малое p-значение
print(f"Степени свободы: {dof}")
# Интерпретация p-значения
if p < 0.001:
significance = "очень сильная"
elif p < 0.01:
significance = "сильная"
elif p < 0.05:
significance = "умеренная"
else:
significance = "отсутствует"
print(f"Статистическая значимость: {significance}")
Интерпретация p-значения критерия χ² следует классическим правилам статистической значимости:
- p < 0.001: очень сильное свидетельство против нулевой гипотезы
- p < 0.01: сильное свидетельство против нулевой гипотезы
- p < 0.05: умеренное свидетельство против нулевой гипотезы
- p ≥ 0.05: недостаточно доказательств для отклонения нулевой гипотезы
Важно понимать, что p-значение не указывает на силу связи между переменными, а лишь на вероятность наблюдения полученных данных (или более экстремальных) при условии верности нулевой гипотезы.
Для оценки силы связи можно использовать дополнительные метрики:
# Расчет коэффициента V Крамера для оценки силы связи
def cramers_v(confusion_matrix):
chi2 = stats.chi2_contingency(confusion_matrix)[0]
n = confusion_matrix.sum()
phi2 = chi2/n
r, k = confusion_matrix.shape
phi2corr = max(0, phi2 – ((k-1)*(r-1))/(n-1))
rcorr = r – ((r-1)**2)/(n-1)
kcorr = k – ((k-1)**2)/(n-1)
return np.sqrt(phi2corr / min((kcorr-1), (rcorr-1)))
# Рассчитываем V Крамера для обоих примеров
v_marketing = cramers_v(marketing_data)
v_age_product = cramers_v(age_product_data)
print("\nСила связи (V Крамера):")
print(f"Кейс 1 (маркетинг): {v_marketing:.4f}")
print(f"Кейс 2 (возраст-продукт): {v_age_product:.4f}")
# Интерпретация силы связи
def interpret_cramers_v(v):
if v < 0.1:
return "незначительная связь"
elif v < 0.3:
return "слабая связь"
elif v < 0.5:
return "умеренная связь"
elif v < 0.8:
return "сильная связь"
else:
return "очень сильная связь"
print(f"Интерпретация кейс 1: {interpret_cramers_v(v_marketing)}")
print(f"Интерпретация кейс 2: {interpret_cramers_v(v_age_product)}")
Для практического применения критерия Пирсона важно следовать определенному алгоритму:
- Формулировка гипотез: четко определите нулевую (H₀) и альтернативную (H₁) гипотезы
- Сбор данных: убедитесь, что выборка достаточно велика и репрезентативна
- Расчет статистики: вычислите значение χ² и соответствующее p-значение
- Интерпретация результатов: проанализируйте p-значение и, при необходимости, остатки
- Оценка силы связи: рассчитайте дополнительные коэффициенты (V Крамера, коэффициент сопряжённости)
При правильном применении и интерпретации критерий Пирсона становится мощным инструментом для выявления статистически значимых закономерностей в категориальных данных, позволяя принимать обоснованные решения, подкреплённые количественным анализом. 🔍
Критерий Пирсона — это не просто статистический тест, а универсальный инструмент для работы с категориальными данными. Его математическая простота в сочетании с мощной способностью выявлять взаимосвязи делает его незаменимым в арсенале каждого аналитика данных. Не бойтесь применять этот метод в своих исследованиях — несколько строк кода на Python могут привести к значимым открытиям в ваших данных, которые трудно заметить другими способами. Помните: статистическая значимость — это только начало анализа, а не его завершение. Настоящий инсайт приходит, когда вы соединяете статистические результаты с контекстом ваших данных и предметной областью.
Читайте также
- Scikit-learn: простая библиотека машинного обучения для Python
- Кластеризация данных в sklearn: методы, оценка и визуализация
- Топ-10 курсов по созданию сайтов на Python: обучение с гарантией
- Макросы Excel: как автоматизировать рутину и экономить время
- TensorFlow и PyTorch: какой фреймворк выбрать для проектов ML
- Машинное обучение в прогнозировании продаж: точность до 95%
- Искусство предобработки данных: от сырых чисел к качественным моделям
- PySpark для анализа Big Data: технологии распределенных вычислений
- Топ-10 книг для анализа данных на Python: руководство от эксперта
- Нейронные сети: как работает технология, меняющая мир технологий


