Алла Тамбовцева
scipy
для статистики¶Библиотека scipy
(сокращение от scientific Python) включает в себя разные модули, позволяющие выполнять научные расчеты, решать задачи оптимизации, генерировать выборки из (псевдо)случайных величин с заданными параметрами, реализовывать статистические тесты и создавать статистические модели.
Импортируем библиотеку pandas
и модуль stats
из библиотеки scipy
.
import scipy.stats as st
import pandas as pd
Загрузим базу данных (датафрейм) из csv-файла. Описание данных см.здесь.
df = pd.read_csv('https://raw.githubusercontent.com/allatambov/Py-programming-3/master/add/swiss.csv')
df.head()
Далее мы попробуем реализовать различные статистические тесты (применить статистические критерии) для сравнения средних значений или распределений в двух выборках.
Предположим, что нам необходимо сравнить средние значения уровня детской смертности в кантонах Швейцарии, где преобладает католическое население и где преобладает протестантское население. Сформируем две выборки на основе имеющихся данных: выберем соответствующие строки в таблице и возьмем столбец Infant.Mortality
.
df[df.Catholic > 50] # для иллюстрации
sample1 = df[df.Catholic > 50]["Infant.Mortality"] # выборка 1
sample1
sample2 = df[df.Catholic <= 50]["Infant.Mortality"] # выборка 2
sample2
Теперь приступим к формальной проверке гипотез.
T-test используется для сравнения средних значений двух генеральных совокупностей в предположении, что обе выборки взяты из нормального распределения. Существует две разновидности t-теста: t-тест для независимых выборок и t-тест для связных (парных) выборок. В связных выборках объекты связаны друг с другом. Пример связных выборок: значения уровня смертности в одних и тех же кантонах до и после какой-нибудь реформы)
Нулевая гипотеза: средние значения двух генеральных совокупностей, откуда взяты выборки, равны, то есть $H_0: a_1=a_2$.
Альтернативная гипотеза: средние значения двух генеральных совокупностей не равны, то есть: $H_1: a_1 \ne a_2$
В stats
в t-тесте в качестве альтернативной гипотезы используется двусторонняя альтернатива (средние не равны) и всегда выводится соответствующее p-value (two-tailed). То же будет характерно для всех последующих тестов. Так как наши выборки независимы, нам нужна функция ttest_ind()
, от independent.
st.ttest_ind(sample1, sample2)
Что возвращает эта функция? Наблюдаемое значение t-статистики и p-value. Результат понятный, только более лакончиный по сравнению с тем, что выводит R и другие статистические пакеты.
Выводы: так как p-value больше любого конвенционального уровня значимости (1%, 5%, 10%), на имеющихся данных на любом разумном уровне значимости нет оснований отвергнуть нулевую гипотезу. Средний уровень детской смертности в католических и протестантских районах можно считать одинаковым.
По умолчанию считается, что дисперсии генеральных овокупностей равны. Часто это бывает не так, и такое предположение без формальной проверки и без содержательных соображений может казаться нереалистичным. Если мы предполагаем, что дисперсии генеральных совокупностей не равны, то это можно учесть, добавив аргумент equal_var
:
st.ttest_ind(sample1, sample2, equal_var = False)
Принципиальных отличий в данном случае в результатах не наблюдается. А формально проверить гипотезу о равенстве дисперсий двух генеральных совокупностей (которые описываются двумя случайными величинами) можно с помощью F-критерия.
Нулевая гипотеза: дисперсии двух генеральных совокупностей равны, то есть $H_0: \sigma_1^2 = \sigma_2^2$
Альтернативная гипотеза: дисперсии двух генеральных совокупностей не равны, то есть $H_1: \sigma_1^2 \ne \sigma_2^2$.
Реализовать F-тест, который нам нужен именно для этого случая, в Python сразу не получится: встроенная функция f_oneway()
используется для однофакторного дисперсионного анализа (ANOVA), речь о котором пойдёт далее. Можно попробовать реализовать этот тест «вручную», рассчитав частное выборочных дисперсий и поработав с F-распределением, но давайте пойдём другим путём и воспользуемся другими критериями для сравнения дисперсий, которые явно встроены в Python.
Например, тест Левена:
st.levene(sample1, sample2)
На любом разумном уровне значимости нет оснований отвергнуть нулевую гипотезу о равенстве дисперсий.
Если мы не можем считать распределение генеральных совокупностей, откуда взяты выборки, нормальным, то следует использовать методы, основанные не на самих наблюдениях в выборках, а на их рангах. Для сравнения распределений (иногда речь идет о сравнении медиан) используются тесты Уилкоксона и Манна-Уитни. Начнем с теста Уилкоксона (не проверяем, является ли распределение данных нормальным, просто для примера используем те же выборки).
st.wilcoxon(sample1, sample2)
Неудача! Проблема в том, что реализация критерия Уилкоксона в stats
требует, чтобы выборки были одинакового размера. Но это ограничение можно обойти, просто выбрав другой критерий – критерий Манна-Уитни, который используется для аналогичных задач.
Нулевая гипотеза: выборки взяты из одного и того же распределения, то есть $H_0: F(x) = G(x)$
Альтернативная гипотеза: выборки взяты из разных распределений, то есть $H_1: F(x) \ne G(x)$.
st.mannwhitneyu(sample1, sample2)
Опять же, на имеющихся данных на любом уровне значимости нет оснований отвергнуть нулевую гипотезу. Выборки взяты из одного и того же распределения.
Если выборок больше, чем две, то использовать указанные выше критерии нельзя. В предположении, что все выборки взяты из нормального распределения, для сравнения средних значений более чем в двух группах используется однофакторный дисперсионный анализ (ANOVA, analysis of variance).
Нулевая гипотеза: средние значения по всем $k$ группам (во всех генеральных совокупностях) равны, то есть $H_0: a_1 = a_2 = \dots = a_k$
Альтернативная гипотеза: средние значения по всем группам (во всех генеральных совокупностях) не равны.
Чтобы не создавать искусственные группы на основе данных в swiss, загрузим таблицу с весами цыплят, которых кормили разным кормом :) Описание см. здесь.
dat = pd.read_csv('https://raw.githubusercontent.com/allatambov/Py-programming-3/master/add/chickwts.csv')
dat.head()
Задание: разбить датафрейм на группы с помощью groupby
по переменной feed
и сохранить значения weight
в словарь.
Решение:
wgt = {}
for name, d in dat.groupby('feed'):
wgt[name] = d.weight
wgt
Теперь ANOVA (f_oneway
от One-Way ANOVA):
st.f_oneway(wgt['casein'], wgt['horsebean'], wgt['linseed'], wgt['meatmeal'], wgt['soybean'], wgt['sunflower'])
Функция возвращает наблюдаемое значение F-статистики и p-value. В данном случае p-value близко к 0, поэтому гипотезу о равенстве средних генеральных совокупностей по группам можно отвергнуть на 1% уровне значимости. Средний вес цыплят, которых кормили разным кормом, отличается (ещё бы, horsebean или sunflower!).
Критерий Краскела-Уоллиса используется, когда нам необходимо сравнить распределения более, чем в двух группах в предположении, что выборки взяты не из нормального распределения (распределения неизвестны).
Нулевая гипотеза: выборки взяты из одного и того же распределения, то есть: $H_0: F(x) = G(x) = \dots = H(x)$
Альтернативная гипотеза: выборки взяты из разных распределений.
st.kruskal(wgt['casein'], wgt['horsebean'], wgt['linseed'], wgt['meatmeal'], wgt['soybean'], wgt['sunflower'])