正态分布 Python 3.12 实战:3种数据转换方法对比与68-95-99.7法则验证

发布时间:2026/7/5 12:07:20
正态分布 Python 3.12 实战:3种数据转换方法对比与68-95-99.7法则验证 正态分布 Python 3.12 实战3种数据转换方法对比与68-95-99.7法则验证在数据科学和机器学习领域正态分布的重要性不言而喻。许多经典算法如线性回归、逻辑回归等都假设数据服从或近似服从正态分布。然而现实中的数据往往并不完美这就需要我们掌握将非正态数据转换为正态分布的技术。本文将使用Python 3.12通过完整代码示例演示三种主流的数据转换方法并验证正态分布的经典68-95-99.7法则。1. 环境准备与数据生成在开始之前我们需要准备好Python环境和必要的库。Python 3.12带来了许多性能改进和新特性特别是在科学计算方面。以下是我们的环境配置# 导入所需库 import numpy as np import scipy.stats as stats import matplotlib.pyplot as plt import seaborn as sns from sklearn.preprocessing import PowerTransformer from sklearn.model_selection import train_test_split # 设置随机种子保证结果可复现 np.random.seed(42) # 生成非正态分布数据 - 这里使用指数分布 original_data np.random.exponential(scale2.0, size10000) # 可视化原始数据分布 plt.figure(figsize(12, 6)) sns.histplot(original_data, kdeTrue, bins50) plt.title(原始数据分布指数分布) plt.xlabel(数值) plt.ylabel(频数) plt.show()这段代码会生成一个右偏的指数分布数据集这是我们进行转换的理想起点。从可视化结果可以明显看出数据不服从正态分布。2. 数据转换方法对比2.1 对数变换Log Transformation对数变换是最简单也最常用的数据转换方法之一特别适用于右偏数据# 对数变换 log_transformed np.log1p(original_data) # 使用log1p避免对0取对数 # 可视化对数变换结果 plt.figure(figsize(12, 6)) sns.histplot(log_transformed, kdeTrue, bins50) plt.title(对数变换后的数据分布) plt.xlabel(数值) plt.ylabel(频数) plt.show() # 计算并打印偏度和峰度 log_skew stats.skew(log_transformed) log_kurtosis stats.kurtosis(log_transformed) print(f对数变换后数据 - 偏度: {log_skew:.4f}, 峰度: {log_kurtosis:.4f})对数变换通过压缩数据范围特别是较大的值使分布更对称。但它的局限性在于只能处理正值数据且要求数据没有零值因此我们使用log1p而不是log。2.2 Box-Cox变换Box-Cox变换是对数变换的推广它通过一个参数λ来优化变换效果# Box-Cox变换 - 要求数据必须为正数 positive_data original_data 1e-6 # 确保所有值为正 boxcox_transformed, lambda_boxcox stats.boxcox(positive_data) # 可视化Box-Cox变换结果 plt.figure(figsize(12, 6)) sns.histplot(boxcox_transformed, kdeTrue, bins50) plt.title(fBox-Cox变换后的数据分布 (λ{lambda_boxcox:.4f})) plt.xlabel(数值) plt.ylabel(频数) plt.show() # 计算并打印偏度和峰度 boxcox_skew stats.skew(boxcox_transformed) boxcox_kurtosis stats.kurtosis(boxcox_transformed) print(fBox-Cox变换后数据 - 偏度: {boxcox_skew:.4f}, 峰度: {boxcox_kurtosis:.4f})Box-Cox变换会自动寻找最优的λ值使变换后的数据尽可能接近正态分布。当λ0时Box-Cox变换就等同于对数变换。2.3 Yeo-Johnson变换Yeo-Johnson变换是Box-Cox变换的扩展可以处理包含零和负值的数据# Yeo-Johnson变换 pt PowerTransformer(methodyeo-johnson, standardizeFalse) yeojohnson_transformed pt.fit_transform(original_data.reshape(-1, 1)).flatten() # 可视化Yeo-Johnson变换结果 plt.figure(figsize(12, 6)) sns.histplot(yeojohnson_transformed, kdeTrue, bins50) plt.title(Yeo-Johnson变换后的数据分布) plt.xlabel(数值) plt.ylabel(频数) plt.show() # 计算并打印偏度和峰度 yeojohnson_skew stats.skew(yeojohnson_transformed) yeojohnson_kurtosis stats.kurtosis(yeojohnson_transformed) print(fYeo-Johnson变换后数据 - 偏度: {yeojohnson_skew:.4f}, 峰度: {yeojohnson_kurtosis:.4f})Yeo-Johnson变换比Box-Cox更灵活因为它不需要数据必须为正数。这使得它在实际应用中更为方便。3. 转换方法效果对比为了更直观地比较三种转换方法的效果我们可以将它们的结果放在一起# 创建对比图 fig, axes plt.subplots(2, 2, figsize(15, 12)) # 原始数据 sns.histplot(original_data, kdeTrue, bins50, axaxes[0, 0]) axes[0, 0].set_title(原始数据分布) # 对数变换 sns.histplot(log_transformed, kdeTrue, bins50, axaxes[0, 1]) axes[0, 1].set_title(对数变换后分布) # Box-Cox变换 sns.histplot(boxcox_transformed, kdeTrue, bins50, axaxes[1, 0]) axes[1, 0].set_title(Box-Cox变换后分布) # Yeo-Johnson变换 sns.histplot(yeojohnson_transformed, kdeTrue, bins50, axaxes[1, 1]) axes[1, 1].set_title(Yeo-Johnson变换后分布) plt.tight_layout() plt.show() # 创建统计量对比表格 methods [原始数据, 对数变换, Box-Cox变换, Yeo-Johnson变换] skewness [stats.skew(original_data), log_skew, boxcox_skew, yeojohnson_skew] kurtosis [stats.kurtosis(original_data), log_kurtosis, boxcox_kurtosis, yeojohnson_kurtosis] print(转换方法效果对比:) print(方法\t\t\t偏度\t\t峰度) for method, skew, kurt in zip(methods, skewness, kurtosis): print(f{method.ljust(15)}\t{skew:.4f}\t\t{kurt:.4f})从结果可以看出三种方法都能有效减少数据的偏度使分布更接近正态。Box-Cox和Yeo-Johnson变换通常能获得更好的效果特别是对于偏态严重的数据。4. 68-95-99.7法则验证正态分布的一个关键特性是68-95-99.7法则即约68%的数据落在均值±1个标准差范围内约95%的数据落在均值±2个标准差范围内约99.7%的数据落在均值±3个标准差范围内我们可以用转换后的数据验证这一法则def validate_empirical_rule(data, method_name): mean np.mean(data) std np.std(data) # 计算各区间内的数据比例 within_1std np.sum((data mean - std) (data mean std)) / len(data) within_2std np.sum((data mean - 2*std) (data mean 2*std)) / len(data) within_3std np.sum((data mean - 3*std) (data mean 3*std)) / len(data) print(f\n{method_name}验证68-95-99.7法则:) print(f均值: {mean:.4f}, 标准差: {std:.4f}) print(fμ±1σ范围内数据比例: {within_1std*100:.2f}% (理论68.27%)) print(fμ±2σ范围内数据比例: {within_2std*100:.2f}% (理论95.45%)) print(fμ±3σ范围内数据比例: {within_3std*100:.2f}% (理论99.73%)) # 对每种转换方法进行验证 validate_empirical_rule(log_transformed, 对数变换) validate_empirical_rule(boxcox_transformed, Box-Cox变换) validate_empirical_rule(yeojohnson_transformed, Yeo-Johnson变换)验证结果将显示转换后的数据在多大程度上符合正态分布的特性。理想情况下这些比例应该接近理论值。5. 实际应用建议在实际项目中应用这些转换方法时有几个关键注意事项数据分割时机务必在划分训练集和测试集之后再进行数据转换避免数据泄露。转换参数如λ值应该仅从训练数据中学习。# 正确的数据分割和转换流程 X_train, X_test train_test_split(original_data, test_size0.2, random_state42) # 在训练集上拟合转换器 pt PowerTransformer(methodyeo-johnson) pt.fit(X_train.reshape(-1, 1)) # 然后转换训练集和测试集 X_train_transformed pt.transform(X_train.reshape(-1, 1)).flatten() X_test_transformed pt.transform(X_test.reshape(-1, 1)).flatten()模型性能影响并非所有机器学习模型都要求输入数据服从正态分布。决策树类模型对数据分布不敏感而线性模型则可能受益于正态化转换。逆变换如果需要将预测结果转换回原始尺度记得保存转换参数以便进行逆变换# Box-Cox逆变换示例 from scipy.special import inv_boxcox # 假设我们有一些预测结果需要转换回原始尺度 predicted_transformed np.array([1.0, 1.5, 2.0]) # 示例数据 predicted_original inv_boxcox(predicted_transformed, lambda_boxcox)替代方案对于严重偏离正态分布的数据可以考虑使用分位数变换QuantileTransformer或直接选择对分布不敏感的模型。