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(Xx)=2π σ1xe2σ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(Xx)=2π 1xe2t2dt
  • 误差函数 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)=π 20xet2dt

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 运行结果

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

参考目录:
https://zhuanlan.zhihu.com/p/292678346

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐