Поговорим о том, как на практике реализовать тесты из предыдущего параграфа. Вычисление параметров тестов вручную — это сложно и трудно, поэтому для большинства из них есть уже написанные на 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.

Вы могли заметить общий принцип: мы формулируем гипотезы и подготавливаем данные, проводим сам тест и интерпретируем результаты.

Тесты, которые вы узнали, можно использовать и для других задач:

  • вычислить, испортится ли погода на курорте, куда вы собираетесь в отпуск, проанализировав данные за предыдущие года (анализ временных рядов);
  • разбить квартиры на рынке недвижимости на группы, изучив, как меняется стоимость в зависимости от разных параметров (кластеризация);
  • узнать, что ещё вам нужно изучить, чтобы зарабатывать больше, — протестировав критерии, которые особенно сильно влияют на уровень зарплат в вашей индустрии (линейная регрессия).

В общем, мы дали вам мощный инструмент — применяйте его осторожно!

Мы подготовили несколько задач для самостоятельной работы — переходите к ним, чтобы закрепить знания.

А в следующем разделе поговорим о том, как корректно получать доступ к данным, и подробнее обсудим категориальные переменные.

Отмечайте параграфы как прочитанные чтобы видеть свой прогресс обучения

Вступайте в сообщество хендбука

Здесь можно найти единомышленников, экспертов и просто интересных собеседников. А ещё — получить помощь или поделиться знаниями.
Вступить
Сообщить об ошибке
Предыдущий параграф3.2. Базовые статистические тесты
Следующий параграф4.1. Типология данных. Источники данных