Набор кода Python по статистическим тестам с комментариями. Взят для личного использования из собственных проектов и обучающих ресурсов.
Формат информации:
- <Ограничения теста>
<код теста>
# комментарии в свободной форме
Flowchart Antoine Soetewey для определения необходимого теста. Схема для общего ознакомления с темой. Документ не следует ей на 100%.
- Предполагает нормальность распределения
- Наблюдения в выборке должны быть независимы друг от друга
- Лучше не использовать на небольшие выборки
from scipy import stats
results = stats.ttest_1samp(a = minnesota_ages, popmean = population_ages.mean())
std_bounds = stats.t.ppf(q=0.025, df=49)
# q - отрезки и в начале, и в конце. Для доверительного интервала в 95% надо 2,5% и там, и там, поэтому ставим 0.025, так как нам нужно узнать начало std
# tstatistic - отличие std выборки от std генеральной совокупности
# если выходит за рамки std_bounds, то значимо
# pvalue - вероятность случайности наблюдения при Г0
# если перешла низкий порог, то значимо
stats.shapiro(df)
- Минимум 50 наблюдений
from scipy import stats
dist = getattr(stats, 'norm')
param = dist.fit(df)
result = stats.kstest(df, 'norm', args=param)
stats.shapiro(df)
- Чувствителен к выбросам
- Наблюдения в выборке должны быть независимы друг от друга
from scipy.stats import wilcoxon
from scipy.stats import norm
wilcoxon(sample-np.median(general), zero_method="wilcox", correction=False)
# помимо pvalue, выдаст сумму рангов
# wilcox - сначала убирает нули перед назначением рангов значениям
# pratt - включит нули, но уберёт их ранг
# zsplit - включит нули и разделит ранги на положительные и отрицательные
z_value = norm.ppf(pvalue/2)
# нужно, если выборка слишком большая для критических значений Z-таблицы. Делим на 2 для двустороннего теста. Если > 1.96 для нормального распределения, то не принимаем Г0
- Наблюдения в выборке должны быть независимы друг от друга
- Слабее Уилкоксон-теста
- Различия между значениями выборки и гипотетическим значением должны быть симметрично распределены вокруг медианного различия нуля
- Лучше подходит к данным, которые можно разделить на две категории в зависимости от того, больше или меньше каждое наблюдение, чем гипотетическое значение
import statsmodels
from statsmodels.stats.descriptivestats import sign_test
hypothesised_median = 10
sign_test(df, hypothesised_median)
- Предполагает нормальность распределения
- Каждое наблюдение должно состоять в паре
- Лучше не использовать на небольшие выборки
results = stats.ttest_rel(a=before, b=after, axis=0, nan_policy="?")
std_bounds = stats.t.ppf(q=0.025, df=a+b-1)
scipy.stats.wilcoxon(x=before, y=after, axis=0, nan_policy="?", correction=False)
- Наблюдения в выборках должны быть независимы друг от друга
- Предполагает нормальность распределения
- Лучше не использовать на небольшие выборки
- Нужно понимать, есть ли равенство дисперсий
Левен-тест на равенство дисперсий для двух выборок
- Чувствителен к отклонениям от нормальности
- Хорошо работает с небольшими выборками
levene = stats.levene(sample1, sample2, center="mean")
var_sig = []
if levene[1] < 0.05:
var_sig.append(False)
else:
var_sig.append(True)
Флингер-Киллин-тест на равенство дисперсий для двух выборок
- Относительно устойчивв к отклонениям от нормальности
- Хорошо работает с небольшими выборками
flinger_killeen = stats.flinger(sample1, sample2, center="median")
var_sig = []
if flinger_killeen[1] < 0.05:
var_sig.append(False)
else:
var_sig.append(True)
Браун-Форсайт-тест на равенство дисперсий для двух выборок
- Относительно устойчивв к отклонениям от нормальности
brown_foresythe = stats.levene(sample1, sample2, center="median")
var_sig = []
if brown_foresythe[1] < 0.05:
var_sig.append(False)
else:
var_sig.append(True)
Стьюдент-Т-тест для двух независимых выборок
results = stats.ttest_ind(a = sample1, b = sample2, equal_var=var_sig)
std_bounds = stats.t.ppf(q=0.025, df=a+b-1)
# equal_var - есть ли гомогенность дисперсии двух выборок
# True - Стьюдента, False - Уэлча
# df для двух выборок = размер выборки один + размер выборки два -1. Df показывает в результатах levene
- Наблюдения в выборках должны быть независимы друг от друга
- Ожидается примерно одинаковое ненормальное распределение выборок
- Хуже работает с большими выборками
- Сложнее интерпретировать
- Если у выборок одна форма распределения - проверяется гипотеза различия средних значений. Если формы разные - гипотеза о стохастическом доминировании значений одной выборки над другой
results = stats.mannwhitneyu(sample1, sample2)
u = results[0]
mean = (len(sample1)*len(sample2))/2
std = np.sqrt((len(sample1)*len(sample2)*(len(sample1)+len(sample2)+1))/12)
z = (u-mean)/std
# для выборки < 20 критическое значение рассчитывается по U-таблице
- Наблюдения должны быть независимы друг от друга
- Переменные должны быть категориями
- Минимальная численность категорий - 5 для 80% наблюдений
- Суммы наблюдаемых и ожидаемых частот должны быть равны
observed = sample_crosstab
general_ratios = general_crosstab/len(general)
expected = general_ratios * len(sample)
crit = stats.chi2.ppf(q=0.95, df=4)
# chi - односторонний, поэтому 0.95
# df - количество категорий минус 1
stats.chisquare(f_obs=observed, f_exp=expected) # expected - распределение, которые мы должны ожидать от генеральной совокупности, если выборка репрезентативна
# если chi_statistic > chi_critical, то есть статистическая значимость
- Наблюдения должны быть независимы друг от друга ("независимы" означает отсутствие парности. Совокупности не должны, например, отражать состояние до и после)
- Переменные должны быть категориями
- Минимальная численность категорий - 5 для 80% наблюдений
observed = sample_crosstab # без totals
crit = stats.chi2.ppf(q=0.95, df=8) # измерения таблицы expected -1 на каждом измерении, помноженные друг на друга (таблица 3/5 = 8)
stats.chi2_contingency(observed=observed)
- Минимум 6 repeated samples
- Минимум 10 наблюдений в каждом
result = stats.friedmanchisquare(sample1, sample2, sample3)
- Наблюдения в выборках должны быть независимы друг от друга
- Минимум 3 группы
- Есть гомогенность дисперсий
- Генеральные совокупности имеют нормальное распределение
df = pd.read_csv("pokemon.csv")
types = ["Water", "Normal", "Grass", "Bug", "Psychic", "Fire"] # должны быть как в df
water = df.loc[df["Type 1"] == "Water"]["Total"]
normal = df.loc[df["Type 1"] == "Normal"]["Total"]
grass = df.loc[df["Type 1"] == "Grass"]["Total"]
bug = df.loc[df["Type 1"] == "Bug"]["Total"]
psych = df.loc[df["Type 1"] == "Psychic"]["Total"]
fire = df.loc[df["Type 1"] == "Fire"]["Total"]
results = stats.f_oneway(water, normal, grass, bug, psych, fire)
type_pairs = []
for typeA in range(4): # кол-во групп минус 1 (?)
for typeB in range(typeA+1,5): # кол-во групп плюс 1 (?)
type_pairs.append((types[typeA], types[typeB]))
for typeA, typeB in type_pairs:
print(typeA, typeB)
print(stats.ttest_ind(df.loc[df["Type 1"] == typeA]["Total"],
df.loc[df["Type 1"] == typeB]["Total"]))
# Bonferroni correction
# делим статистическую значимость на количество сравнений. В нашем случае 10
# 0.05 / 10 = 0.005
# значит, порог - pvalue меньше 0.005
# ИЛИ TUKEY TEST
from statsmodels.stats.multicomp import pairwise_tukeyhsd
excluded = [group for group in list(df["Type 1"].value_counts().index) if group not in types]
df = df[~df["Type 1"].isin(excluded)]
tukey = pairwise_tukeyhsd(endog=df["Total"],
groups=df["Type 1"],
alpha=0.05)
tukey.plot_simultaneous()
plt.vlines(x=df["Total"].mean(), ymin=-10, ymax=10, color="red")
plt.tight_layout()
plt.show()
print(tukey.summary())
- Предполагает нормальность распределения
- Наблюдения в выборках должны быть независимы друг от друга
- Есть гомогенность дисперсий
type_pairs = []
for typeA in range(4): # кол-во групп минус 1 (?)
for typeB in range(typeA + 1, 5): # кол-во групп плюс 1 (?)
type_pairs.append((types[typeA], types[typeB]))
for typeA, typeB in type_pairs:
print(typeA, typeB)
print(stats.levene(df.loc[df["Type 1"] == typeA]["Total"],
df.loc[df["Type 1"] == typeB]["Total"]))
stats.alexandergovern(water, normal, grass, bug, psych, fire)
- Наблюдения в выборках должны быть независимы друг от друга
- Непараметрическая версия ANOVA
stats.kruskal(water, normal, grass, bug, psych, fire)
- Минимум 3 группы
- Каждое наблюдение должно состоять в отношении с другим в иных группах
- Предполагает равенство дисперсий
from statsmodels.stats.anova import AnovaRM
df = pd.read_csv("rmAOV1way.csv")
anova = AnovaRM(df, depvar="rt", subject="Sub_id", within=["cond"])
# depvar = зависимый показатель, который отличается со временем
# subject = id субъектов эксперимента. Всё в одном df, поэтому эти id повторяются
# within = дополнительные переменные, на которые мы смотрим
result = anova.fit()
- Аналог односторонней ANOVA без предположения о равенстве дисперсий
import pingouin as pg
pg.welch_anova(dv="values", between="groups", data=df)