背景
正态分布是统计学中的一种连续概率分布,也称为高斯分布,其概率密度函数呈钟形曲线,正态分布有以下几个重要特点:
- 均值( )为中心,数据在均值左右对称分布
- 标准差( )决定了分布的宽度,较小的标准差使分布更窄,较大的标准差使分布更宽
- 分布的总面积为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
生成两列数据,一列为正态分布,另一列为非正态分布,接下来根据这些数据进行数据正态分布检验实现
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()
左侧图是正态分布数据的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()
左侧是正态分布数据的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()
左侧是正态分布数据的直方图,其形状接近对称的钟形,并且与叠加的正态分布曲线高度吻合,说明数据符合正态分布,右侧是非正态分布数据的直方图,其形状较为平坦,未呈现正态分布的钟形曲线,说明数据不符合正态分布
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
正态分布数据:统计量为 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
正态分布数据:统计量为 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
正态分布数据:统计量为 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
正态分布数据:统计量为 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
正态分布数据:统计量为 2.576,p 值为 0.2759,p 值大于 0.05,无法拒绝数据符合正态分布的假设,非正态分布数据:统计量为 710.37,p 值极小,可以拒绝数据符合正态分布的假设
往期推荐
基于SHAP值的 BorutaShap 算法在特征选择中的应用与优化
复现SCI文章 SHAP 依赖图可视化以增强机器学习模型的可解释性
微信号|deep_ML
欢迎添加作者微信进入Python、ChatGPT群
进群请备注Python或AI进入相关群
无需科学上网、同步官网所有功能、使用无限制
如果你对类似于这样的文章感兴趣。
欢迎关注、点赞、转发~
个人观点,仅供参考