正态性检验:从Q-Q图到Shapiro-Wilk的全面解析数据是否呈现正态分布

机器学习关系型数据库云安全

picture.image

背景

正态分布是统计学中的一种连续概率分布,也称为高斯分布,其概率密度函数呈钟形曲线,正态分布有以下几个重要特点:

  • 均值( )为中心,数据在均值左右对称分布
  • 标准差( )决定了分布的宽度,较小的标准差使分布更窄,较大的标准差使分布更宽
  • 分布的总面积为1,即所有可能取值的概率之和为1

正态分布的概率密度函数可以表示为:

其中, 为均值, 是方差

数据呈正态分布作用

  • 统计假设前提:许多统计方法(如t检验、ANOVA)要求数据正态分布,否则结果可能失效
  • 模型性能:线性回归等模型在处理正态分布数据时表现更佳,解释性更强
  • 特征处理:正态分布的数据更适合标准化和正则化,提升模型的稳定性和泛化能力
  • 异常值检测:正态性检验有助于识别异常值,清理数据以减少干扰
  • 算法表现:K-Means等算法对正态分布数据效果更好,深度学习也通过正态化加速收敛
  • 树模型例外:决策树类算法对正态分布不敏感,但正态数据有助于更均衡的特征划分

正态性检验是数据分析和机器学习中的重要步骤,判断数据是否呈正态分布不仅可以帮助选择合适的算法和方法,还能提升模型的性能和可解释性,如果数据不符合正态分布,可以考虑通过数据变换(如对数变换、Box-Cox变换)来改善分布特征,参考文章——特征工程——数据转换

正态分布检验方法

正态分布检验用于判断给定的数据是否来自正态分布。常见的正态性检验方法包括:

图形法

  • Q-Q图:将样本的分位数与正态分布的理论分位数进行比较。如果样本点大致沿直线分布,说明数据接近正态分布
  • P-P图:将样本的累积分布函数(CDF)与理论分布的CDF进行比较
  • 直方图:将数据的直方图与正态分布的理论密度曲线进行对比,看是否呈现钟形

统计检验法

  • Shapiro-Wilk检验:一种广泛使用的正态性检验,假设数据来自正态分布,如果p值较小(通常小于0.05),则拒绝数据来自正态分布的假设
  • Kolmogorov-Smirnov检验:用于比较样本的经验分布与理论正态分布,适合较大样本,但对均值和方差的敏感性较弱
  • Anderson-Darling检验:对分布尾部有较高的灵敏度,是Shapiro-Wilk检验的加强版
  • Jarque-Bera检验:基于样本的偏度和峰度,检验数据是否符合正态分布。适用于大样本
  • D'Agostino's K-squared检验:基于偏度和峰度的综合检验方法,适合较大的样本

不同检验方法对数据的敏感度不同,选择时可以根据样本量和对正态性的严格要求程度来确定

代码实现

数据生成


          
import numpy as np
          
import pandas as pd
          
import matplotlib.pyplot as plt
          
plt.rcParams['font.family'] = 'Times New Roman'
          
plt.rcParams['axes.unicode_minus'] = False
          
np.random.seed(42)
          

          
# 生成一列正态分布数据,均值为0,标准差为1,数据量为1000
          
normal_data = np.random.normal(loc=0, scale=1, size=1000)
          
# 生成一列非正态分布数据(例如从均匀分布生成)
          
non_normal_data = np.random.uniform(low=-2, high=2, size=1000)
          

          
data = pd.DataFrame({
          
    'Normal_Distribution': normal_data,
          
    'Non_Normal_Distribution': non_normal_data
          
})
          
data
      

picture.image

生成两列数据,一列为正态分布,另一列为非正态分布,接下来根据这些数据进行数据正态分布检验实现

Q-Q图


          
import scipy.stats as stats
          
# 创建Q-Q图函数
          
def plot_qq(data, title):
          
    stats.probplot(data, dist="norm", plot=plt)
          
    plt.title(title)
          
    plt.grid(True)
          
# 绘制两列数据的Q-Q图
          
plt.figure(figsize=(12, 6),dpi=1200)
          
plt.subplot(1, 2, 1)
          
plot_qq(data['Normal_Distribution'], 'Q-Q Plot of Normal Distribution')
          
plt.subplot(1, 2, 2)
          
plot_qq(data['Non_Normal_Distribution'], 'Q-Q Plot of Non-Normal Distribution')
          
plt.tight_layout()
          
plt.savefig("Q-Q.pdf", format='pdf',bbox_inches='tight')
          
plt.show()
      

picture.image

左侧图是正态分布数据的Q-Q图,点大致沿着一条直线分布,说明这列数据接近正态分布,右侧图是非正态分布数据的Q-Q图,点偏离直线较明显,说明这列数据不符合正态分布

P-P图


          
# 创建P-P图函数
          
def plot_pp(data, title):
          
    sorted_data = np.sort(data)
          
    cdf = stats.norm.cdf(sorted_data, np.mean(sorted_data), np.std(sorted_data))
          
    plt.plot(cdf, np.linspace(0, 1, len(data)), marker='o', linestyle='', markersize=3)
          
    plt.plot([0, 1], [0, 1], 'r--')
          
    plt.title(title)
          
    plt.xlabel('Theoretical CDF')
          
    plt.ylabel('Empirical CDF')
          
    plt.grid(True)
          

          
# 绘制两列数据的P-P图
          
plt.figure(figsize=(12, 6),dpi=1200)
          

          
plt.subplot(1, 2, 1)
          
plot_pp(data['Normal_Distribution'], 'P-P Plot of Normal Distribution')
          

          
plt.subplot(1, 2, 2)
          
plot_pp(data['Non_Normal_Distribution'], 'P-P Plot of Non-Normal Distribution')
          
plt.tight_layout()
          
plt.savefig("P-P.pdf", format='pdf',bbox_inches='tight')
          
plt.show()
      

picture.image

左侧是正态分布数据的P-P图,数据点与理论的CDF曲线大致符合,说明这列数据接近正态分布,右侧是非正态分布数据的P-P图,数据点明显偏离理论的CDF曲线,说明这列数据不符合正态分布

直方图


          
# 绘制直方图函数
          
def plot_hist(data, title):
          
    plt.hist(data, bins=30, density=True, alpha=0.6, color='g')
          
    # 绘制正态分布曲线
          
    mu, std = np.mean(data), np.std(data)
          
    xmin, xmax = plt.xlim()
          
    x = np.linspace(xmin, xmax, 100)
          
    p = stats.norm.pdf(x, mu, std)
          
    plt.plot(x, p, 'k', linewidth=2)
          
    plt.title(title)
          
    plt.grid(True)
          

          
# 绘制两列数据的直方图
          
plt.figure(figsize=(12, 6), dpi=1200)
          

          
plt.subplot(1, 2, 1)
          
plot_hist(data['Normal_Distribution'], 'Histogram of Normal Distribution')
          

          
plt.subplot(1, 2, 2)
          
plot_hist(data['Non_Normal_Distribution'], 'Histogram of Non-Normal Distribution')
          

          
plt.tight_layout()
          
plt.savefig("Histogram.pdf", format='pdf',bbox_inches='tight')
          
plt.show()
      

picture.image

左侧是正态分布数据的直方图,其形状接近对称的钟形,并且与叠加的正态分布曲线高度吻合,说明数据符合正态分布,右侧是非正态分布数据的直方图,其形状较为平坦,未呈现正态分布的钟形曲线,说明数据不符合正态分布

Shapiro-Wilk检验


          
# 进行Shapiro-Wilk检验
          
shapiro_normal = stats.shapiro(data['Normal_Distribution'])
          
shapiro_non_normal = stats.shapiro(data['Non_Normal_Distribution'])
          

          
shapiro_results = {
          
    'Normal_Distribution': {
          
        'Statistic': shapiro_normal.statistic,
          
        'p-value': shapiro_normal.pvalue
          
    },
          
    'Non_Normal_Distribution': {
          
        'Statistic': shapiro_non_normal.statistic,
          
        'p-value': shapiro_non_normal.pvalue
          
    }
          
}
          

          
shapiro_results
      

picture.image

正态分布数据:统计量为 0.9986,p 值为 0.6265,p 值大于 0.05,因此不能拒绝数据符合正态分布的假设,非正态分布数据:统计量为 0.9543,p 值非常小,p 值远小于 0.05,因此可以 拒绝数据符合正态分布的假设

Kolmogorov-Smirnov检验


          
# 进行Kolmogorov-Smirnov检验
          
ks_normal = stats.kstest(data['Normal_Distribution'], 'norm', args=(np.mean(data['Normal_Distribution']), np.std(data['Normal_Distribution'])))
          
ks_non_normal = stats.kstest(data['Non_Normal_Distribution'], 'norm', args=(np.mean(data['Non_Normal_Distribution']), np.std(data['Non_Normal_Distribution'])))
          

          
ks_results = {
          
    'Normal_Distribution': {
          
        'Statistic': ks_normal.statistic,
          
        'p-value': ks_normal.pvalue
          
    },
          
    'Non_Normal_Distribution': {
          
        'Statistic': ks_non_normal.statistic,
          
        'p-value': ks_non_normal.pvalue
          
    }
          
}
          
ks_results
      

picture.image

正态分布数据:统计量为 0.0215,p 值为 0.7370,p 值大于 0.05,无法拒绝数据符合正态分布的假设,非正态分布数据:统计量为 0.0678,p 值为 0.00019,p 值远小于 0.05,因此可以拒绝数据符合正态分布的假设

Anderson-Darling检验


          
# 进行Anderson-Darling检验
          
ad_normal = stats.anderson(data['Normal_Distribution'], dist='norm')
          
ad_non_normal = stats.anderson(data['Non_Normal_Distribution'], dist='norm')
          

          
ad_results = {
          
    'Normal_Distribution': {
          
        'Statistic': ad_normal.statistic,
          
        'Critical_Values': ad_normal.critical_values,
          
        'Significance_Level': ad_normal.significance_level
          
    },
          
    'Non_Normal_Distribution': {
          
        'Statistic': ad_non_normal.statistic,
          
        'Critical_Values': ad_non_normal.critical_values,
          
        'Significance_Level': ad_non_normal.significance_level
          
    }
          
}
          
ad_results
      

picture.image

正态分布数据:统计量为 0.347,低于所有的临界值(0.574, 0.653, 0.784, 0.914, 1.088),因此我们不能拒绝数据符合正态分布的假设,非正态分布数据:统计量为 11.326,远高于所有的临界值,因此可以拒绝数据符合正态分布的假设

Jarque-Bera检验


          
# 进行Jarque-Bera检验
          
jb_normal = stats.jarque_bera(data['Normal_Distribution'])
          
jb_non_normal = stats.jarque_bera(data['Non_Normal_Distribution'])
          

          
jb_results = {
          
    'Normal_Distribution': {
          
        'Statistic': jb_normal.statistic,
          
        'p-value': jb_normal.pvalue
          
    },
          
    'Non_Normal_Distribution': {
          
        'Statistic': jb_non_normal.statistic,
          
        'p-value': jb_non_normal.pvalue
          
    }
          
}
          
jb_results
      

picture.image

正态分布数据:统计量为 2.456,p 值为 0.2928,p 值大于 0.05,无法拒绝数据符合正态分布的假设,非正态分布数据:统计量为 59.919,p 值非常小,可以拒绝数据符合正态分布的假设

D'Agostino's K-squared检验


          
# 进行D'Agostino's K-squared检验
          
dagostino_normal = stats.normaltest(data['Normal_Distribution'])
          
dagostino_non_normal = stats.normaltest(data['Non_Normal_Distribution'])
          

          
dagostino_results = {
          
    'Normal_Distribution': {
          
        'Statistic': dagostino_normal.statistic,
          
        'p-value': dagostino_normal.pvalue
          
    },
          
    'Non_Normal_Distribution': {
          
        'Statistic': dagostino_non_normal.statistic,
          
        'p-value': dagostino_non_normal.pvalue
          
    }
          
}
          

          
dagostino_results
      

picture.image

正态分布数据:统计量为 2.576,p 值为 0.2759,p 值大于 0.05,无法拒绝数据符合正态分布的假设,非正态分布数据:统计量为 710.37,p 值极小,可以拒绝数据符合正态分布的假设

往期推荐

SCI图表复现:整合数据分布与相关系数的高级可视化策略

复现顶刊Streamlit部署预测模型APP

树模型系列:如何通过XGBoost提取特征贡献度

SHAP进阶解析:机器学习、深度学习模型解释保姆级教程

特征选择:Lasso和Boruta算法的结合应用

从基础到进阶:优化SHAP力图,让样本解读更直观

SCI图表复现:优化SHAP特征贡献图展示更多模型细节

多模型中的特征贡献度比较与可视化图解

SCI图表复现:特征相关性气泡热图展示

基于SHAP值的 BorutaShap 算法在特征选择中的应用与优化

复现SCI文章 SHAP 依赖图可视化以增强机器学习模型的可解释性

picture.image

picture.image

picture.image

微信号|deep_ML

欢迎添加作者微信进入Python、ChatGPT群

进群请备注Python或AI进入相关群

无需科学上网、同步官网所有功能、使用无限制

如果你对类似于这样的文章感兴趣。

欢迎关注、点赞、转发~

个人观点,仅供参考

0
0
0
0
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论