Набор кода Python по статистическим тестам с комментариями. Взят для личного использования из собственных проектов и обучающих ресурсов.
Формат информации:
- <Ограничения теста>
<код теста> # комментарии в свободной форме
Flowchart Antoine Soetewey для определения необходимого теста. Схема для общего ознакомления с темой. Документ не следует ей на 100%. alt text
- Предполагает нормальность распределения
- Наблюдения в выборке должны быть независимы друг от друга
- Лучше не использовать на небольшие выборки
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)