Поговорим о том, как на практике реализовать тесты из предыдущего параграфа. Вычисление параметров тестов вручную — это сложно и трудно, поэтому для большинства из них есть уже написанные на Python реализации.
Но начнём мы с небольшой теории: рассмотрим чуть подробнее параметры и аргументы функций в Python.
Ключевые и позиционные параметры в функции
В параграфе 2.3 мы рассказали про параметры и аргументы функций и методов. Коротко вспомним, что это такое:
🔍 Аргумент — значение, передаваемое функции, или имя переменной, которая его содержит.
🔍 Параметр — принятый функцией в каком-то качестве аргумент.
Ниже — пример аргументов, переданных в метод read_csv
библиотеки pandas
:
>>> pd.read_csv('file.csv', ';', nrows=500)
У каждой функции есть свой единожды заданный порядок, в котором вводятся параметры. Если вы указываете аргументы в том же порядке, то интерпретатор Python понимает, какие параметры вы имеете в виду.
Обратите внимание на последний аргумент — 500
. Здесь мы напрямую указали, что он передаётся в параметр nrows
. Вообще, мы могли бы все аргументы явно связать с параметрами:
>>> pd.read_csv(filepath_or_buffer='file.csv', sep=';', nrows=500)
Тут filepath_or_buffer
, sep
и nrows
— это параметры, а аргументы — это file.csv
, ;
и 500
.
Как вы могли заметить, параметры могут быть ключевыми и позиционными.
🔍 Ключевой параметр — параметр, в котором связь с аргументом определяется по названию параметра.
🔍 Позиционный параметр — параметр, в котором связь с аргументом определяется по позиции параметра.
Параметр nrows
в обоих случаях ключевой, так как между ним и первыми тремя должен быть ещё один аргумент, который мы не используем.
Зачем это нужно? Представьте функцию, которая принимает 50 параметров. А вам для исследования нужны только 3-й и 17-й. Передавать в неё все 50 очень неудобно и трудоёмко, поэтому стоит использовать ключевые параметры.
>>> some_function(param_3='Some argument', param_17='Another argument')
Названия ключевых параметров можно узнать из документации к библиотеке. Вот пример описания для уже знакомого вам метода pd.read_csv
. Уточним также, что ключевые аргументы могут использоваться после позиционных, но позиционные после ключевых — не могут.
Всё, теперь мы готовы перейти к тестам!
Биномиальный тест
Многие тесты, в том числе биномиальный, можно провести с помощью пакета SciPy (Science Python). Это большая библиотека, в которой реализованы статистические методы и научные функции.
В этом параграфе мы будем использовать набор данных «Amazon Top 50 Bestselling Books 2009 - 2019». Он состоит из информации о пятидесяти самых популярных книгах на Amazon за каждый год с 2009-го по 2019-й.
Допустим, мы хотим узнать, какие книги чаще становятся популярными — художественные или нехудожественные. Мы можем сформулировать следующую нулевую гипотезу: «Вероятность встретить художественную и нехудожественную книгу в нашей подборке равна».
Проверим это с помощью теста. Не переживайте, если код кажется сложным, — мы пошагово разберём синтаксис ниже.
>>> import pandas as pd
>>> df = pd.read_csv('bestsellers.csv')
>>> from scipy.stats import binom_test
>>> bins = df['Genre'].value_counts().tolist()
>>> print(bins)
[310, 240]
>>> binom_test(bins)
0.0032232676763761965
Первые две строчки вам уже знакомы:
- импортируем библиотеку
pandas
под именемpd
; - считываем данные из файла
bestsellers.csv
, превращаем их в датафрейм и записываем в переменнуюdf
.
В дальнейших примерах мы этот код опустим — но держите в голове, что он нужен.
Далее мы загружаем из библиотеки scipy.stats
функцию binom_test
. Ключ stats
указывает на то, что внутри библиотеки scipy функции разбиты на модули и модуль stats
— это один из них.
Модуль в Python — отдельный файл с кодом, который можно повторно использовать в других программах. Библиотека может состоять из одного модуля, а может из нескольких.
Далее с помощью метода value_counts()
мы считаем количество художественных и нехудожественных книг в датафрейме и преобразуем эти числа в список с помощью метода tolist()
.
Мы выводим значение списка в консоль и видим, что в нём 310 художественных и 240 нехудожественных книг. Передаём список в функцию binom_test()
и получаем результат теста — 0.0032232676763761965 (напомним, что это p-value
).
Мы проверяем вероятность получить результат статистического теста такой же, как тот, что мы сейчас наблюдаем, в реальности, где нулевая гипотеза верна. Вероятность небольшая, поскольку она меньше 0.05
, — поэтому мы можем отвергнуть нулевую гипотезу «Вероятность встретить художественную и нехудожественную книгу в нашей подборке равна».
Проверка равенства средних значений в двух выборках (t-тест)
Попробуем узнать, как отличается рейтинг между художественными и нехудожественными текстами. Есть ли между ними значимые различия?
Для этого мы проведём t-тест. Наша нулевая гипотеза: между средними оценками художественных и нехудожественных книг нет значимого отличия.
У нас есть данные обо всех бестселлерах. Давайте сформируем из них две случайные выборки, средние в которых сравним.
Сначала нам нужно проверить нормальность распределения средних в выборке. Для этого мы можем использовать тест Шапиро — Уилка. Однако нам нужно много выборок, чтобы убедиться в том, что среднее значение в них действительно распределено нормально. Давайте выделим из наших данных 500 выборок и посчитаем для каждой среднее значение.
Для этого мы напишем цикл while
, который запустим 500 раз. Для этого будем считать с помощью переменной n
количество итераций цикла. Когда она станет равна 500 — остановимся.
Внутри цикла мы каждый раз будем случайным образом брать сто случайных нехудожественных книг и вычислять их среднюю оценку. Получившийся результат запишем в список.
>>> means_fic = []
>>> n = 1
>>> while n < 500:
>>> n += 1
>>> sample = df[df['Genre'] == "Non Fiction"].sample(100)['User Rating'].mean()
>>> means_fic.append(sample)
Для получившихся средних значений вычислим результат критерия Шапиро — Уилка для нехудожественных книг. Показатель p-value
незначим, поэтому мы не можем отклонить нулевую гипотезу и считаем, что средние в выборках распределены нормально.
>>> from scipy.stats import shapiro
shapiro(means_fic)
ShapiroResult(statistic=0.9967661499977112, pvalue=0.4215131402015686)
Аналогичный результат будет для художественных книг. Мы опустим его, чтобы не повторяться.
Далее нам нужно узнать, равны ли дисперсии по группам. И в зависимости от этого уточнить дополнительный параметр для t-теста. В Python для проверки критерия Левене мы будем использовать функцию levene
из пакета scipy
. Прежде чем проводить тест, снова выделим две случайные выборки, которые будем сравнивать между собой.
>>> from scipy.stats import levene
>>> user_rating_fic = df[df['Genre'] == "Fiction"].sample(100)['User Rating']
>>> user_rating_non = df[df['Genre'] == "Non Fiction"].sample(100)['User Rating']
>>> levene(user_rating_fic, user_rating_non)
LeveneResult(statistic=0.00167345627884177, pvalue=0.9674105395197554)
Для определения равенства дисперсий нам нужно сформировать две выборки. Возьмём значения оценок (User Rating
), сгруппированные по категории книжного жанра (Fiction
и Non Fiction
) и запишем их в разные переменные. А затем передадим в функцию levene
.
Тут может показаться, что на последней строчке мы вызываем функцию LeveneResult, но это не так — это именно результат исполнения функции levene
. Просто авторы библиотеки SciPy решили, что он будет выглядеть таким образом ¯_(ツ)_/¯
Вернёмся к интерпретации результата. Мы видим, что p-value большой, а значит, принимаем нулевую гипотезу о равенстве дисперсий. Нам нужно отразить это в t-тесте.
>>> from scipy.stats import ttest_ind
>>> ttest_ind(user_rating_fic, user_rating_non, equal_var=True)
Ttest_indResult(statistic=3.322999805682098, pvalue=0.0010607376016129276)
Учитываем, что дисперсии равны, с помощью параметра equal_var
. Так как p-value меньше 0.05, то мы не можем принять нулевую гипотезу о том, что средние оценки книг не различаются в зависимости от жанра. То есть жанр связан с оценкой книг.
Если бы дисперсии были не равны, следовало бы применить параметр equal_var = False
, учитывая критерий Уэлча.
>>> from statistics import mean
>>> print(mean(user_rating_fic), mean(user_rating_non))
4.692 4.589
Мы видим, что средняя оценка художественных книг оказывается значимо выше, чем нехудожественных. Это может быть связано с тем, что оценка художественных книг зависит от того, нравятся они или не нравятся, а нехудожественных — от того, воспринимаются ли они как полезные или нет.
ANOVA
Теперь мы можем проверить, отличаются ли средние оценки книг за три последних года? Поскольку групп наблюдения у нас больше двух, применим тест ANOVA. Наша нулевая гипотеза звучит так: «За последние три года книги в среднем оценивали одинаково».
>>> from scipy.stats import f_oneway
>>> ur_2019 = df[(df['Year'] == 2019)]['User Rating']
>>> ur_2018 = df[(df['Year'] == 2018)]['User Rating']
>>> ur_2017 = df[(df['Year'] == 2017)]['User Rating']
>>> f_oneway(ur_2019, ur_2018, ur_2017)
F_onewayResult(statistic=2.99211541687636, pvalue=0.05324686656835848)
Опираясь на порог p-value (0.053 > 0.05), мы не можем отбросить нулевую гипотезу. Значит, оценки в топе за год между собой не различаются.
В этом параграфе мы провели три базовых статистических теста с помощью Python.
Вы могли заметить общий принцип: мы формулируем гипотезы и подготавливаем данные, проводим сам тест и интерпретируем результаты.
Тесты, которые вы узнали, можно использовать и для других задач:
- вычислить, испортится ли погода на курорте, куда вы собираетесь в отпуск, проанализировав данные за предыдущие года (анализ временных рядов);
- разбить квартиры на рынке недвижимости на группы, изучив, как меняется стоимость в зависимости от разных параметров (кластеризация);
- узнать, что ещё вам нужно изучить, чтобы зарабатывать больше, — протестировав критерии, которые особенно сильно влияют на уровень зарплат в вашей индустрии (линейная регрессия).
В общем, мы дали вам мощный инструмент — применяйте его осторожно!
Мы подготовили несколько задач для самостоятельной работы — переходите к ним, чтобы закрепить знания.
А в следующем разделе поговорим о том, как корректно получать доступ к данным, и подробнее обсудим категориальные переменные.