如何判断使用Scipy拟合正态分布是否正确?含PCA降维后拟合场景
如何验证用Scipy拟合多元正态分布的操作是否正确?
先帮你确认下代码本身的正确性,再给你几个实用的验证方法,一步步来:
一、先检查你的拟合代码是否正确
你给出的代码片段方向是对的,补全后完整代码应该是这样:
import numpy as np from scipy.stats import multivariate_normal # z是PCA降维后的矩阵,shape为(样本数, 700) mean = np.mean(z, axis=0) # 按列计算每个主成分的均值,得到700维均值向量 cov = np.cov(z, rowvar=0) # rowvar=0表示每一行是一个样本,计算700x700的协方差矩阵 g = multivariate_normal(mean=mean, cov=cov) # 实例化多元正态分布对象
这里的关键细节:
np.mean(z, axis=0):完全正确,我们需要每个主成分(列)的均值,对应多元正态的均值向量。np.cov(z, rowvar=0):也没问题,rowvar=0明确告诉函数“行是样本,列是特征”,和你的数据结构匹配。- 额外提一句:因为你之前把原始数据归一化到均值为0,PCA作为线性变换,理论上主成分的均值应该非常接近0(比如绝对值<1e-10),如果你的
mean向量偏差很大,那得回头检查归一化或PCA的步骤有没有问题。
二、验证拟合是否合理的实用方法
1. 协方差矩阵的基础校验
PCA后的主成分应该是正交不相关的,所以协方差矩阵cov的非对角元素应该接近0,且矩阵本身是对称的。可以用两行代码快速验证:
# 检查协方差矩阵是否对称 print(np.allclose(cov, cov.T)) # 正常应该返回True # 检查非对角元素是否接近0(允许微小浮点误差) off_diag = cov[np.triu_indices_from(cov, k=1)] print(np.allclose(off_diag, 0, atol=1e-8)) # 正常应该返回True
如果这两个检查不通过,说明PCA的执行可能有问题,得先排查这一步。
2. 单个主成分的可视化校验
多元正态分布的每个边缘分布都是正态分布,所以你可以随机选几个主成分,画直方图+拟合的正态曲线,直观对比:
import matplotlib.pyplot as plt # 选第3个主成分为例(随便换索引就行) comp_idx = 3 data = z[:, comp_idx] # 绘制数据直方图(密度归一化) plt.hist(data, bins=30, density=True, alpha=0.6, color='#1f77b4', label='Data Histogram') # 生成拟合的正态分布PDF曲线 x_range = np.linspace(data.min(), data.max(), 1000) pdf = g.pdf(x_range[:, np.newaxis]) # 注意要把一维数组转成列向量 plt.plot(x_range, pdf, 'r-', linewidth=2, label='Fitted Normal PDF') plt.xlabel(f'Principal Component {comp_idx}') plt.ylabel('Density') plt.title('Data vs Fitted Normal Distribution') plt.legend() plt.show()
如果直方图的形状和红色曲线贴合得比较好,说明这个边缘分布符合正态性,间接支持多元正态的假设。
3. 统计检验:单个特征的正态性
用Shapiro-Wilk检验(小样本更灵敏)或Kolmogorov-Smirnov检验来量化每个主成分的正态性:
from scipy.stats import shapiro, kstest # 遍历所有主成分做Shapiro-Wilk检验 for idx in range(z.shape[1]): stat, p_val = shapiro(z[:, idx]) print(f"Component {idx}: Shapiro stat={stat:.4f}, p-value={p_val:.4f}")
- 若
p-value > 0.05,则不能拒绝“该主成分服从正态分布”的原假设; - 注意:如果你的样本量很大(比如超过5000),Shapiro-Wilk可能会因为微小的偏差就拒绝原假设,这时候要结合可视化结果一起判断,不要只看p值。
4. 统计检验:多元正态性
用Mardia检验直接验证整个数据集的多元正态性,这是专门针对多元分布的检验方法:
from scipy.stats import mardia # 计算偏度和峰度的检验统计量与p值 skew_stat, skew_p = mardia(z, axis=0, skewness=True) kurt_stat, kurt_p = mardia(z, axis=0, kurtosis=True) print(f"Mardia Skewness Test: stat={skew_stat:.4f}, p-value={skew_p:.4f}") print(f"Mardia Kurtosis Test: stat={kurt_stat:.4f}, p-value={kurt_p:.4f}")
如果两个p值都大于0.05,说明没有足够的证据拒绝“数据服从多元正态分布”的原假设,你的拟合是合理的。
5. 拟合优度检验:卡方检验
把数据分成若干区间,比较观测频数和拟合分布的期望频数,用卡方检验判断差异是否显著:
from scipy.stats import chisquare # 以第一个主成分为例 data = z[:, 0] n_bins = 10 # 计算观测频数和区间边界 obs_counts, bin_edges = np.histogram(data, bins=n_bins) # 计算每个区间的期望频数 bin_width = bin_edges[1] - bin_edges[0] bin_mids = (bin_edges[:-1] + bin_edges[1:]) / 2 exp_probs = multivariate_normal.pdf(bin_mids, mean=mean[0], cov=cov[0,0]) * bin_width exp_counts = exp_probs * len(data) # 执行卡方检验(注意:如果期望频数<5,需要合并区间) stat, p_val = chisquare(obs_counts, exp_counts) print(f"Chi-square Test: stat={stat:.4f}, p-value={p_val:.4f}")
p值大于0.05的话,说明观测数据和拟合的正态分布没有显著差异。
内容的提问来源于stack exchange,提问作者PyRsquared




