06非参数检验
非参数检验
在总体分布未知或与总体分布无关的情况下进行统计推断的过程,称之为非参数检验
非参数检验的基本问题
非参数检验方法适用范围比较广,无论样本所在的总体分布形式如何,对于一些非精确测量的资料或登记资料数据均可适用。但要注意:对于符合用参数检验的数据,如用非参数检验,可能会丢失信息,导致检验效率下降。
检验目的不同
对总体分布形式的检验(拟合优度检验):检验样本所在总体是否服从某个已知的理论分布
对总体分布位置或形状的检验(位置检验):检验样本所在的总体的分布位置或形状是否相同,常对中位数进行检验
检验样本反映的总体数目不同
对单个样本的检验、对两个样本的检验、对多个样本的检验
秩是讲一个数列按照由小到大的顺序排列后,每个数值所获得的位置序号
import pandas as pd
Rank = pd.Series([8, 9, 12, 23, 3, 4, 5, 6, 0], name='rank')
Rank.index = Rank
Rank.index.name = 'value'
print(pd.DataFrame(Rank).rank().T)
# value 8 9 12 23 3 4 5 6 0
# rank 6.0 7.0 8.0 9.0 2.0 3.0 4.0 5.0 1.0
单样本非参数检验
单样本检验可对样本数据来自于何种位置和形状的总体或是否具有随机性进行检验。主要方法有Wilcoxon符号秩检验、K-S检验、游程检验等
中位数(均值)的检验
符号检验(sign test)是利用正、负号的数目,对某种假设做出判定的一种非参数统计方法。Wilcoxon符号秩检验也是符号检验法的一种。简单的符号检验法只是利用符号的正负来说明差异的存在,但是并没有考虑差异的大小。而Wilcoxon符号秩检验对此进行了改进,在该检验方法中,要求样本来自于连续且对称的总体。
Wilcoxon符号秩检验原假设
其基本思想是:假设总体的中位数为 \(M_0\),从总体中得到一个样本,样本的观测值为 \(x_1,x_2,..,x_n\),计算 \(D_i=x_i-M_0(i=1,2,...,n)\),然后按照 \(|D_i|\) 进行排序,每个 \(|D_i|\) 得到一个相应的秩。然后把 \(D_i\) 的符号加到相应的秩上,对带符号的秩的绝对值求和,记为 \(W^{-}\);对带正号的秩的绝对值求和,记为 \(W^{+}\)。如果 \(M=M_0\) 确实是中位数,\(W^{-}\) 和 \(W^{+}\) 应当基本上相等;而当\(W^{-}\) 和 \(W^{+}\) 相差很大时,则可以拒绝对总体中位数的原假设。通常情况下取 \(W=min(W^{-},W^{+})\) 作为Wilcoxon统计量进行检验。
由于Wilcoxon符号秩检验要求假定总体时连续且对称的,对总体中位数的检验等价于对总体均值的检验。此外Wilcoxon符号秩检验的基本思想还可用于两个样本的检验,检验两个样本在试验前后是否存在明显的变化。
示例
import pandas as pd
import statsmodels.api as sm
import scipy.stats as stats
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
# 纯净水标称净含量600ml,对其是否合格进行抽检,对其质量进行评价,\alpha=0.05
water = pd.read_csv('./data/water.csv')
print(water.head(5))
# Net
# 0 598.78
# 1 599.98
# 2 600.48
# 3 598.19
# 4 597.87
# 中位数
w_median = water['Net'].median()
print(w_median) # 598.86
# 由于未知总体分布,此阿勇非参数Wilcoxon符号检验。中位数<600
# H_0: M \geq 600; H_1: M < 600
# 自己编制大样本情况下进行wilcoxon符号检验
def wilcoxon_signed_rank_test(samp, mu0=0):
temp = pd.DataFrame(np.asarray(samp), columns=['origin_data'])
temp['D'] = temp['origin_data'] - mu0
temp['rank'] = abs(temp['D']).rank()
posW = sum(temp[temp['D'] > 0]['rank'])
negW = sum(temp[temp['D'] < 0]['rank'])
n = temp[temp['D'] != 0]['rank'].count()
z = (posW - n * (n + 1) / 4) / np.sqrt((n * (n + 1) * (2 * n + 1)) / 24)
p = (1 - stats.norm.cdf(abs(z))) * 2
return z, p
res = wilcoxon_signed_rank_test(water['Net'], mu0=600)
print(res) # (-1.9940749174328372, 0.04614386788589431)
# 计算出来的是双侧检验结果,备择假设中是<,则单侧p值=0.046/2 =0.023<0.05,故拒绝原假设,即不合格
# 其他方法
# statsmodels.stats.descriptivestats中sign_test提供了单样本wilcoxon符号检验
# scipy.stats.wilcoxon提供了两个成对样本的符号秩检验
分布检验
kolmogorov-Smirnov检验建成K-S检验,主要是用来检验样本数据所反映的总体是否服从某种理论分布族,即用样本数据的累积分布于某个特定的理论分布相比较,若二者间的差距很小,则推断样本来自于某特定分布族。
设总体累积分布位 \(F(x)\),卢纶分布族为 \(F_0(x)\),则K-S检验的问题转化为如下的假设和备择假设:
其中 \(F_0(x)\) 可以是需要进行检验的分布。
示例
import pandas as pd
import statsmodels.api as sm
import scipy.stats as stats
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
# 调查中收集到76各观测值样本数据,分析总体分布是否是正态分布,\alpha=0.05
ks = pd.read_csv('./data/ks.csv')
print(ks.head(5))
# observation
# 0 77
# 1 92
# 2 90
# 3 71
# 4 74
# 在估计均值和方差条件下进行正态检验
res = sm.stats.diagnostic.kstest_normal(ks['observation'])
print(res) # (0.11085609021293796, 0.021718830360242317)
res = sm.stats.diagnostic.lilliefors(ks['observation'])
print(res) # (0.11085609021293796, 0.021718830360242317)
# p-v=0.021<0.05,故拒绝原假设,不是正态分布
# 其他方法
# scipy可以对上百种分布进行检验
res = stats.kstest(ks['observation'], 'norm', args=(ks['observation'].mean(), ks['observation'].std()))
print(res) # KstestResult(statistic=0.11085609021293796, pvalue=0.2869341737555461)
res = stats.shapiro(ks['observation'])
# 针对正态分布的非参数检验法:shapiro-Wilk正态检验
print(res) # (0.9556435346603394, 0.009623649530112743)
res = stats.anderson(ks['observation'], dist='norm')
# anderson检验,一种修正的k-s己拿烟,此检验是将样本数据的经验累积分布函数与假设数据呈正态分布时期望的分布进行比较
# 可对正态分布、指数分布、逻辑分布和Gumbel I型极值分布等进行检验
# 返回三个值:统计量、检验分布的关键值、关键值对应的显著性水平%
# 如果统计量值比对应显著水平下的关键值要大,则可拒绝原假设
print(res) # AndersonResult(statistic=0.9221944197643097,
# critical_values=array([0.549, 0.626, 0.751, 0.876, 1.042]),
# significance_level=array([15. , 10. , 5. , 2.5, 1. ]))
# 统计量值在非0.01显著水平下都比关键值都大,故拒绝原假设,不符合正态分布。在0.01显著水平下不可拒绝原假设
游程检验
对变量的取值是否随机及性能检验的过程即游程检验(run test)。因此,游程检验的原假设为
\(H_0\):总体变量取值是随机的
游程
如有如下取值分别为0和1的数据
0011010101111000001
把其中相同的0(或相同的1)在一起的情况称为一个游程。因此上述序列中一共有 \(R=10\) 个游程
游程检验往往用于对只取两种取值的变量进行随机性检验,但是对于连续性的数值型变量可以使用数据是否大于中位数或均值(或者用户指定的数据分割点)的方式进行变通,使之能够进行游程检验(对于连续大于或小于的情况视为一个游程)
可以根据游程数目所近似服从的总体分布对之进行检验。
示例
import pandas as pd
import statsmodels.api as sm
import scipy.stats as stats
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
# 180名学生的公共课成绩,分析该数据是否具有随机性
runs = pd.read_csv('./data/runs.csv')
print(runs.head(5))
# economics statistics
# 0 66 72
# 1 77 60
# 2 70 65
# 3 99 45
# 4 53 78
# 游程检验
res = sm.stats.runstest_1samp(np.asarray(runs['economics']), cutoff='median')
print(res) # (-1.3394253515905177, 0.18043224087490017)
# 用于检验的Z统计量为-1.339,P_v=0.180,非常不显著,不能拒绝原假设。故可认为具有随机性
# 对statistics进行检验,是一个均值分隔点
res = sm.stats.runstest_1samp(np.asarray(runs['statistics']), cutoff='mean')
print(res) # (-1.93056976097277, 0.053536280411386646)
# 用于检验的Z统计量为-1.930,p_v=0.053,若取显著水平\alpha=0.05,则不能拒绝原假设,故可认为具有随机性
两个样本的非参数检验
对于来自两个独立总体的样本数据,同样可以利用非参数及拿烟的方法来检验它们之间的差异性。
独立样本中位数比较检验
当比较两个独立样本的均值差异时,可以使用Wilcoxon秩和检验。Wilcoxon秩和检验也被称为Mann-Whitney-Wilcoxon检验。两个独立样本的Wilcoxon秩和检验的原假设
\(H_0\):两个独立样本中位数相等
检查前置条件:两个总体的分布具有类似的形状。
基本原理:假定第1个样本的容量为 \(n_1\),第2个样本的容量为 \(n_2\),把这两个样本合并,得到的样本容量为 \(n_1+n_2\),把合并之后的样本数据从大到小进行排列可得每个观测值所对应的秩。然后分别把第1个样本和第2个样本的秩秩相加,得到第1个样本的秩和为 \(W_1\),第2个样本的秩和为 \(W_2\),如果 \(W_1\)和 \(W_2\) 差异比较大,则可以拒绝两个独立样本中位数相等的原假设。
示例
import pandas as pd
import statsmodels.api as sm
import scipy.stats as stats
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
# 为考察市场的地域差异性,对南方市场32家和北方市场40家连锁店进行调查,考察其某月销售额是否有差异
sales_district = pd.read_csv('./data/sales_district.csv')
print(sales_district.head(5))
print(sales_district['district'].unique()) # [1 2]
# district Sales
# 0 1 87.17
# 1 1 88.45
# 2 1 93.52
# 3 1 96.17
# 4 1 92.68
# 南(district=2),北(district=1)的销售差异,可比较其中位数是否相等
# H_0:M_1=M_2;H_1:M_1 \neq M_2
# Wilcoxon秩和检验
res = stats.ranksums(sales_district[sales_district['district'] == 1]['Sales'],
sales_district[sales_district['district'] == 2]['Sales'])
print(res) # RanksumsResult(statistic=-3.6377197716407874, pvalue=0.0002750624589981112)
# 双边检验,p_v=0.00027,可以拒绝原假设,认为南北方有显著差异
# 其他方法:Mann-Whitney秩和检验U统计量
res = stats.mannwhitneyu(sales_district[sales_district['district'] == 1]['Sales'],
sales_district[sales_district['district'] == 2]['Sales'],
alternative='two-sided')
print(res) # MannwhitneyuResult(statistic=319.0, pvalue=0.0002811747629731805)
# p_v=0.00028,可以拒绝原假设,与上述一致
独立样本的分布检验
两个独立样本分布的K-S检验主要检验样本所来自的总体分布是否相同。
其原假设和备择假设为:
其中 \(F_1(x),F_2(x)\) 分别表示两个总体的分布。
示例
import pandas as pd
import statsmodels.api as sm
import scipy.stats as stats
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
# 为考察两个城市网吧经营规模,对两城市电脑规模进行调查,检验分布是否相同
cafe_scale = pd.read_csv('./data/cafe_scale.csv')
print(cafe_scale.head(5))
# city Cafe_No Computers
# 0 1 1 200
# 1 1 2 50
# 2 1 3 160
# 3 1 4 50
# 4 1 5 80
# k-s检验
res = stats.ks_2samp(cafe_scale[cafe_scale['city'] == 1]['Computers'],
cafe_scale[cafe_scale['city'] == 2]['Computers'])
print(res) # Ks_2sampResult(statistic=0.1901098901098901, pvalue=0.41957474075667367)
# p_v=0.419,无法拒绝原假设,即无法否认两个城市网吧电脑分布分布相同的假设
# 样本经验分布图
cafe_scale[cafe_scale['city'] == 1]['Computers'].hist(bins=50, density=True, histtype='step', cumulative=True)
cafe_scale[cafe_scale['city'] == 2]['Computers'].hist(bins=50, density=True, histtype='step', cumulative=True)
plt.xlabel('Computers')
plt.ylabel('frequency')
plt.title('empirical distribution for Computers')
plt.show()
成对(匹配)样本中位数检验
类似于成对样本均值是否相等的参数检验,成对样本中位数的非参数检验同样也要对成对样本事先进行数据转换,即先求出构成成对样本配对的观测值的差,然后再利用单样本中位数的非参数检验方法进行检验,其基本原理一致
示例
import pandas as pd
import statsmodels.api as sm
import scipy.stats as stats
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
# 考察2015和2016固定样本的北京市居民生活的幸福程度。现假定对于两个年份幸福程度的分布情况未知,
# 对于幸福指数是否会得到提升的问题可以使用非参数检验的方法进行检验
# 来自于成对样本非参数检验的问题要求两个样本的样本量相同。
# H_0:M_{2015} \geq M_{2016};H_1:M_{2015}<M_{2016}
happiness = pd.read_csv('./data/happiness.csv')
print(happiness.head(5))
# Year2015 Year2016
# 0 69.48 77.44
# 1 82.51 67.49
# 2 82.12 64.56
# 3 70.32 70.14
# 4 75.29 74.72
# Wilcoxon符号秩检验法
res = stats.wilcoxon(happiness['Year2015'], happiness['Year2016'])
print(res) # WilcoxonResult(statistic=9660.5, pvalue=0.6346041550198774)
# p_v=0.634/2=0.317,故无法拒绝原假设,即认为幸福指数并没有得到显著提升
两样本的游程检验
两个样本数据的游程检验主要检验两个样本数据是否来自同一个分布,主要使用Wald-Wolfowitz游程检验法
示例
import pandas as pd
import statsmodels.api as sm
import scipy.stats as stats
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
# 检验两门课成绩是否来自同一总体
# H_0:两门课来自同一分布;H_1:两门课来自不同的分布
runs = pd.read_csv('./data/runs.csv')
print(runs.head(5))
# economics statistics
# 0 66 72
# 1 77 60
# 2 70 65
# 3 99 45
# 4 53 78
# 检测
res = sm.stats.runstest_2samp(np.asarray(runs['economics']).astype('float64'),
np.asarray(runs['statistics']).astype('float64'))
print(res) # (-9.288960762896162, 1.558020813536555e-20)
# p_v \sim 0,故拒绝原假设,认为两门课来自不同的分布
# 分组处理
# 1.函数不能直接处理整型数据,需要转为浮点型
# 2.可以使用参数groups对用指示变量对数据进行分组的数据进行分析
runs2 = pd.read_csv('./data/runs2.csv')
print(runs2.head(5))
# score group
# 0 66 0
# 1 77 0
# 2 72 1
# 3 60 1
# 4 65 1
res = sm.stats.runstest_2samp(np.asarray(runs2['score']).astype('float64'),
groups=np.asarray(runs2['group']))
print(res) # (-9.288960762896162, 1.558020813536555e-20)
多个样本的非参数检验
对于来自多个总体的样本呢数据,同样可利用非参数检验的方法检验它们之间的差异。
多个样本的分布检验
多样本Anderson-Darling检验是一种在单样本Anderson-Darling检验改进的方法,主要用于检验各样本数据是否来自于同一个总体,其原假设就是个样本数据是来自于同一个总体。
示例
import pandas as pd
import statsmodels.api as sm
import scipy.stats as stats
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
# 检验3个班级的某门课的考试成绩是否来自于同一个分布
ksampledis = pd.read_csv('./data/ksampledis.csv')
print(ksampledis.head(5))
# statistics_score class
# 0 91 2
# 1 95 3
# 2 75 1
# 3 52 3
# 4 57 2
# 多样本分布检测
# 数据预处理
G = ksampledis['class'].unique()
args = []
for i in list(G):
args.append(np.array(ksampledis[ksampledis['class'] == i]['statistics_score']))
# 检验
res = stats.anderson_ksamp(args)
print(res)
# Anderson_ksampResult(statistic=0.5632008353358472,
# critical_values=array([0.44925884, 1.3052767 , 1.9434184 , 2.57696569, 3.41634856,4.07210043, 5.56419101]),
# significance_level=0.2221673793881979)
# 检验结果给出的关键显著水平分别为25%、10%、5%、2.5%、1%,此外还给出了p值
# p_v=0.222,除了在0.25的显著水平下可以拒绝原假设,在其余的关键显著水平下均不能拒绝原假设。故可认为来自同一个总体。
独立样本位置的检验
此种非参数检验方法可以检验多个独立总体的位置参数(如中位数)是否一致,其原假设和备择假设如下:
\(H_0\):各总体位置参数相同;\(H_1\):各总体位置参数不全相同
基本原理与两独立样本的Wilcoxon检验思想类似。把来自于 \(n\) 个总体的样本放在一起进行排序,然后分别把来自不同总体的样本观测值的秩和 \(R_n\) 计算出来,如果 \(R_n\) 显著有差异,则可以认为各总体位置参数不同,反则反之。
通常情况下,多个独立样本位置秩和检验可以使用近似服从卡方分布的Kruskal-Wallis统计量进行检验,并且假定各个总体具有相似形状的连续分布。
示例
import pandas as pd
import statsmodels.api as sm
import scipy.stats as stats
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
# 考察某饮料在各个季节的销售情况是否有差异,对在各个销售网络销售中销售终端的销量进行调查
drinks = pd.read_csv('./data/drinks.csv')
print(drinks.head(5))
# season terminal_no Sales
# 0 1 1 98.96
# 1 2 1 88.97
# 2 3 1 88.55
# 3 4 1 84.34
# 4 1 2 94.43
# 数据预处理:添加标签,分组变量
drinks['season'] = drinks['season'].astype('category')
drinks['season'].cat.categories = ['spring', 'summer', 'autumn', 'winter']
drinks['season'].cat.set_categories = ['spring', 'summer', 'autumn', 'winter']
G = drinks['season'].unique()
args = []
for i in list(G):
args.append(drinks[drinks['season'] == i]['Sales'])
# Kruskal-Wallis H统计量的卡方检验
res = stats.kruskal(*args)
print(res) # KruskalResult(statistic=23.95945806717496, pvalue=2.5471577250553795e-05)
# p_v \approx 0,故拒绝原假设,即认为不是来自同一分布,不同季节销量有差异
# 其他方法
res = stats.median_test(*args)
print(res) # (26.0, 9.537406942615968e-06, 90.82, array([[10, 14, 8, 0], [ 6, 2, 8, 16]]))
# 结果分别为:统计量、p值、所有样本的中位数和形状为(2,n)的列联表(
# n表示参与检验的样本个数,第1行表示每个样本中大于中位数的数据个数,第2行每个样本中小于中位数的数据个数)
# 对各样本数据大于中位数的比例是否相同进行检验
from statsmodels.sandbox.stats.runs import median_test_ksample
res = median_test_ksample(drinks['Sales'], drinks['season'])
print(res)
# (Power_divergenceResult(statistic=26.0,
# pvalue=0.00022264244658763393),
# array([[ 8, 6, 2, 16], [ 8, 10, 14, 0]]),
# array([[8., 8., 8., 8.], [8., 8., 8., 8.]]))
# 图形描述
above_median = []
below_median = []
for i in list(G):
above_median.append((drinks[drinks['season'] == i]['Sales'] > drinks['Sales'].median()).sum())
below_median.append((drinks[drinks['season'] == i]['Sales'] < drinks['Sales'].median()).sum())
plt.bar(range(4), below_median, color='b', alpha=0.2, label='Not Above the Median', align='center')
plt.bar(range(4), above_median, color='pink', alpha=0.5, label='Above the Median', align='center', bottom=below_median)
plt.legend(loc='lower center', frameon=False, bbox_to_anchor=(0.5, -0.3))
plt.xticks(range(4), G)
plt.show()

浙公网安备 33010602011771号