K-S 检验法
文章目录
1、前言
K-S检验法(Kolmogorov-Smirnov test,柯尔莫哥罗夫-斯米尔诺夫检验)是一种非参数检验方法,用于检验一个样本是否来自特定的概率分布(one-sample K-S test),或者检验两个样本是否来自同一概率分布(two-sample K-S test)。
此外,还有一种校验法,夏皮洛-威尔克检验(Shapiro—Wilk test),简称S-W检验。
国标 GB/T 4882-2001《数据的统计处理和解释正态性检验》:SW检验适用于样本数8≤n≤50,小样本(n<8)对偏离正态分布的检验不太有效。
因此:
分析小于50行的小样本数据时,我们倾向于看 S-W 检验得到的正态性检验结果;
分析大于50行的大样本数据时,我们倾向于看 K-S 检验得到的正态性检验结果;
2、基本原理
只就检验一个样本是否来自正态分布的判别进行介绍,这也是工程中比较常用的。
2.1 正态分布相关概念
- 正态分布
X
X
X ~
N
(
μ
,
σ
2
)
N(\mu,\sigma^2)
N(μ,σ2),均值为
μ
\mu
μ,标准差为
σ
\sigma
σ。
而标准正态分布为 N ( 0 , 1 ) N(0,1) N(0,1),均值为 0 0 0,标准差为 1 1 1。
对 X X X 标准化,有 Z = X − μ σ Z = \frac{X-\mu}{\sigma} Z=σX−μ ~ N ( 0 , 1 ) N(0,1) N(0,1)。 - 分布函数
一般正态分布的分布函数 F ( x ) F(x) F(x) :
F ( x ) = P ( X ⩽ x ) = 1 2 π σ ∫ − ∞ x e − ( t − μ ) 2 2 σ 2 d t F(x)=P(X \leqslant x)=\frac{1}{\sqrt{2 \pi} \sigma} \int_{-\infty}^x e^{-\frac{(t-\mu)^2}{2 \sigma^2}} d t F(x)=P(X⩽x)=2πσ1∫−∞xe−2σ2(t−μ)2dt
标准正态分布的分布函数 Φ ( x ) \Phi(x) Φ(x) :
Φ ( x ) = P ( X ⩽ x ) = 1 2 π ∫ − ∞ x e − t 2 2 d t \Phi(x)=P(X \leqslant x)=\frac{1}{\sqrt{2 \pi}} \int_{-\infty}^x e^{-\frac{t^2}{2}} d t Φ(x)=P(X⩽x)=2π1∫−∞xe−2t2dt - 误差函数
e
r
f
erf
erf(高斯误差函数)
求解高斯分布的概率,已知门限( g a t e gate gate)变量的值,已知变量 X > g a t e X>gate X>gate的概率。
e r f ( x ) = 2 π ∫ 0 x e − t 2 d t erf(x)= \frac{2}{\sqrt{ \pi}} \int_{0}^x e^{-{t^2}} d t erf(x)=π2∫0xe−t2dt
2.2 运算过程
一组数据 x i {x_i} xi,个数有 n n n个。此时的判断临界值为 α \alpha α。
- 求取均值 μ \mu μ、标准差 σ \sigma σ
- 对该组数据排序,升序,从小到大,并重新赋予序号为 x i x_i xi
- 计算经验分布函数 F ( i ) = ( i + 1 ) / n F(i)=(i+1)/n F(i)=(i+1)/n
- 计算标准正态分布的累积分布函数 C D F ( i ) = 1 + e r f ( x i − μ 2 σ ) 2 CDF(i) = \frac{1+erf(\frac{x_i-\mu}{\sqrt2 \ \sigma})}{2} CDF(i)=21+erf(2 σxi−μ)
- 计算 f a b s ( i ) = ∣ F ( i ) − C D F ( i ) ∣ fabs(i)=|F(i)-CDF(i)| fabs(i)=∣F(i)−CDF(i)∣
- 计算 D = m a x { f a b s ( i ) } D= max\{fabs(i)\} D=max{fabs(i)}
- 判断 D < α D < \alpha D<α :满足,即为正态分布;反之,则不然。
注:单样本K-S检验统计量的临界值参照表

3、程序
下面是使用 C++ 实现 K-S 检验法检验一组数据的分布类型的步骤:
3.1 均值和标准差求解函数
//均值
double average(double X[2000], int num)
{
double ave = 0;
double sum = 0;
for (int i = 0; i < num; i++) sum = sum + X[i];
ave = sum / num;
return ave;
}
//标准差
double calbzc(double X[2000] , int num, int free)
{
double sumvi2 = 0;
double bzc; //标准差
for (int i = 0; i < num; i++)
{
sumvi2 = sumvi2 + (X[i] - average(X,num)) * (X[i] - average(X, num));
}
bzc = sqrt(sumvi2 / (num - free));
return bzc;
}
标准差求解需要考虑数据自由度 (即程序中参数 f r e e free free )。
3.2 定义标准正态分布的累积分布函数
double cdfA(double x, double ave, double bzc)
{
return 0.5 * (1 + erf((x - ave) / bzc / sqrt(2)));
}
其中,erf (x) 为误差函数(error function),可使用cmath库中的erf函数计算,表示标准正态分布中,小于 x 的分布占比,例如erf(1.5)=0.966105
3.3 定义函数kstest,用于进行K-S检验
参数data是待检验的数据数组,n是数据数组的长度,cdf是累积分布函数
double kstest(double data[], int n)
{
// 均值
double ave=0;
ave = average(data, n);
cout << "均值:" << ave << endl;
// 标准差
double bzc = 0;
int free=1;
bzc = calbzc(data, n, free);
cout <<"标准差:" << bzc << endl;
// 排序数据数组 升序
sort(data, data + n);
double D = 0;
for (int i = 0; i < n; i++)
{
// 计算经验分布函数的值
double F = (double)(i + 1) / n;
// 计算CDF函数的值
double S = cdfA(data[i],ave,bzc);
// 计算D值
D = max(D, fabs(S - F)); //fabs浮点数绝对值
}
// 计算临界值
double alpha = 1.36 / sqrt(n);
cout <<"D:"<< D << endl;
// 判断是否拒绝原假设
if (D > alpha)
return 0; // 拒绝原假设
else
return 1; // 未拒绝原假设
}
上述程序中,fabs函数表示求取浮点数的绝对值。
3.4 编写主函数,读取对应路径文本数据并调用kstest函数进行检验。
int main()
{
double data[2000];
string txtname;
cout << "文件路径:" << endl;
cin >> txtname;
// 计数
int count = 0;
ifstream inFile(txtname); // 打开文本文件
double zhongzhuan;
while (inFile >> zhongzhuan)
{ // 逐个读取文件中的数字
count++; // 每读取一个数字,计数器加1
}
inFile.close(); // 关闭文件
ifstream ifs;
ifs.open(txtname, ios::in);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return 0;
}
for (int i = 0; i < count; i++) //
{
ifs >> data[i];
//cout << data[i] << "\t";
}
cout << endl;
// 进行K-S检验
double p = kstest(data, count);
if (p == 0)
cout << "数据不服从正态分布。" << endl;
else
cout << "数据服从正态分布。" << endl;
return 0;
}
3.5 完整
#include <iostream>
#include <cmath>
#include <algorithm>
#include <fstream>
using namespace std;
//均值
double average(double X[2000], int num)
{
double ave = 0;
double sum = 0;
for (int i = 0; i < num; i++) sum = sum + X[i];
ave = sum / num;
return ave;
}
//标准差
double calbzc(double X[2000] , int num, int free)
{
double sumvi2 = 0;
double bzc; //标准差
for (int i = 0; i < num; i++)
{
sumvi2 = sumvi2 + (X[i] - average(X,num)) * (X[i] - average(X, num));
}
bzc = sqrt(sumvi2 / (num - free));
return bzc;
}
//cdf函数
double cdfA(double x, double ave, double bzc)
{
return 0.5 * (1 + erf((x - ave) / bzc / sqrt(2)));
}
//kstest函数
double kstest(double data[], int n)
{
// 均值
double ave=0;
ave = average(data, n);
cout << "均值:" << ave << endl;
// 标准差
double bzc = 0;
int free=1;
bzc = calbzc(data, n, free);
cout <<"标准差:" << bzc << endl;
// 排序数据数组 升序
sort(data, data + n);
double D = 0;
for (int i = 0; i < n; i++)
{
// 计算经验分布函数的值
double F = (double)(i + 1) / n;
// 计算CDF函数的值
double S = cdfA(data[i],ave,bzc);
// 计算D值
D = max(D, fabs(S - F)); //fabs浮点数绝对值
}
// 计算临界值
double alpha = 1.36 / sqrt(n);
cout <<"D:"<< D << endl;
// 判断是否拒绝原假设
if (D > alpha)
return 0; // 拒绝原假设
else
return 1; // 未拒绝原假设
}
int main()
{
double data[2000];
string txtname;
cout << "文件路径:" << endl;
cin >> txtname;
// 计数
int count = 0;
ifstream inFile(txtname); // 打开文本文件
double zhongzhuan;
while (inFile >> zhongzhuan)
{ // 逐个读取文件中的数字
count++; // 每读取一个数字,计数器加1
}
inFile.close(); // 关闭文件
ifstream ifs;
ifs.open(txtname, ios::in);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return 0;
}
for (int i = 0; i < count; i++) //
{
ifs >> data[i];
//cout << data[i] << "\t";
}
cout << endl;
// 进行K-S检验
double p = kstest(data, count);
if (p == 0)
cout << "数据不服从正态分布。" << endl;
else
cout << "数据服从正态分布。" << endl;
return 0;
}
3.6 运行结果
- 生成一组符合正态分布的数据(此处可以参考我的另一篇博客)

- 读取生成的数据,判断是否为正态分布
