数据预处理作为机器学习中的关键一环,这里介绍如何改变特征的分布形状——Transformation变换
分析诊断 可通过下述常见的可视化工具来判断、分析数据的分布特征:
Histogram直方图 :统计、展示各数据区间范围内的样本频数。较为简单、直观
KDE核密度图 :KDE核密度估计是一种非参的概率密度函数估计方法。通过平滑的概率密度曲线,可以直观的看出数据的分布特征
Q-Q(Quantile-Quantile)图 :Q-Q图将样本数据分布与指定理论分布进行比较,来判断样本数据是否服从指定分布。具体地,Q-Q图中的散点是以理论分布的百分位数为x坐标、以样本数据的百分位数为y坐标。同时,图中还有一条参考线。如果上述散点都紧密地落在参考线附近,则说明该样本数据的分布 与 指定分布吻合。该图的典型应用场景:将理论分布指定为正态分布,用于判断样本数据是否符合正态分布
这里介绍用于衡量数据分布的形状、形态指标:Skewness 偏态系数、Kurtosis 峰度系数
Skewness 偏态系数 :用于衡量数据分布的偏移方向、程度
偏态系数 > 0 :即所谓的正偏态 。数据分布向右侧延伸得更长,故又被称为右偏态 。由于大部分数据集中在左侧,但右侧存在少量的极大值。故导致 均值 > 中位数 。典型的例子:个人收入
偏态系数 < 0 :即所谓的负偏态 。数据分布向左侧延伸得更长,故又被称为左偏态 。由于大部分数据集中在右侧,但左侧存在少量的极小值。故导致 均值 < 中位数 。典型的例子:考试成绩
偏态系数 = 0 : 此时数据分布左右两侧是对称的,故导致 均值 = 中位数 。典型的例子:身高数据的偏态系数近似为0
Kurtosis 峰度系数 :其衡量的是相对于正态分布 而言,数据样本中的极端值(即尾部,特别大的值和特别小的值)的数量是多还是少。如果多,则说明是重尾;反之,则说明是轻尾
峰度系数 > 0 :即所谓的重尾 。相比于正态分布 而言,样本中存在更多的极端值 。典型的例子:自然灾害(例如:地震、洪水等)的强度。现实世界一般每天发生弱震的次数很多,极端剧烈强震虽然的次数很少。但事实上发生的极端强震概率还是比正态分布模型 预测的概率要高很多
峰度系数 < 0 :即所谓的轻尾 。相比于正态分布 而言,样本中存在更少的极端值 。典型的例子:健康人群的体温数据。由于生理系统的调节机制,健康人群的体温基本都维持在37度附近
峰度系数 = 0 :样本中极端值的频率与正态分布一致 。典型的例子:身高数据由于近似符合正态分布,故峰度系数近似为0
下面将展示正态分布、右偏态分布、左偏态分布的数据在Histogram直方图、KDE核密度图、Q-Q(Quantile-Quantile)图的表现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 import pandas as pdimport numpy as npimport seaborn as snsimport numpy as npimport matplotlib.pyplot as pltimport scipy.stats as statsdef plot (data: pd.Series, data_name: str ): """ 对指定数据绘制: Histogram直方图、KDE核密度图、Q-Q图 """ skewness = data.skew() kurtosis = data.kurt() fig, (ax1, ax2, ax3) = plt.subplots(1 , 3 ) fig.suptitle(f"{data_name} \n(Skewness: {skewness:.2 f} | Kurtosis: {kurtosis:.2 f} )" , fontsize=16 ) sns.histplot(data, ax=ax1) ax1.set_title("Histogram" ) ax1.set_xlabel("Value" ) ax1.set_ylabel("Freq" ) sns.kdeplot(data, ax=ax2, fill=True , color='orange' ) ax2.set_title("KDE" ) ax2.set_xlabel("Value" ) ax2.set_ylabel("Density" ) stats.probplot(data, dist="norm" , plot=ax3) ax3.set_title("Normal Q-Q" ) ax3.get_lines()[0 ].set_markerfacecolor('r' ) ax3.get_lines()[0 ].set_markeredgecolor('r' ) ax3.get_lines()[1 ].set_color('k' ) plt.tight_layout() plt.show() iris_dataset = sns.load_dataset('iris' ) sepal_width_data = iris_dataset['sepal_width' ] titanic_dataset = sns.load_dataset('titanic' ) fare_data = titanic_dataset['fare' ] rng = np.random.default_rng(seed=42 ) data = rng.beta(a=9967 , b=3 , size=40000 ) left_skewed_data = pd.Series(data) plot(sepal_width_data, "Sepal Width of Iris Data" ) plot(fare_data, "Fare of Titanic Data" ) plot(left_skewed_data, "Left Skewed Data" )
从下图不难看出,无论是那种分布形状在Histogram直方图、KDE核密度图中的表现都是比较明显、直观的;而在Q-Q图(理论分布为正态分布时)中,对于正态分布而言,散点基本可以很好贴合在参考线上;对于右偏分布而言,可以看到散点的形状像是一个笑脸的嘴形 ,右侧散点逐渐向参考线的上方翘起;对于左偏分布而言,可以看到散点的形状像是一个哭脸的嘴形 ,右侧散点逐渐向参考线的下方落下去
调整方法 对数变换,顾名思义就是直接对数据取对数。根据对数函数的特性,不难知道,其适用于处理右偏态数据。一般底数取e自然对数,公式如下。这里x加1的目的在于使其可以处理数据为0的场景。这样其适用条件就从 要求数据均为正数 变为 要求数据非负 即可。毕竟现实世界中,数据为0很常见
Box-Cox 变换 Box-Cox 变换由统计学家George Box、David Cox于1964年提出的。其是一种广义的幂变换方法,通过λ参数来控制变换的强度。SciPy、Sklearn中均提供了该变换方式,其能够自动确定一个最佳的λ值,实现将数据变换为更接近正态分布的效果。变换公式如下所示。特别地,当λ为0时,该变换就等价于对数变换(取极限可得)。但该变换最大的限制在于,其要求所有数据都必须是正数
Yeo-Johnson 变换 Yeo-Johnson变换是对Box-Cox变换的一个扩展,由In-Kwon Yeo、Richard A. Johnson于2000年提出。其不仅可以处理正数、零,还可以处理负数。故Sklearn中的PowerTransformer类默认使用的就是 Yeo-Johnson 变换
实践 下面来展示如何通过Sklearn调整右偏态的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 import numpy as npimport matplotlib.pyplot as pltfrom sklearn.preprocessing import FunctionTransformerimport seaborn as snsimport scipy.stats as statsfrom sklearn.preprocessing import PowerTransformerdef plot (data: np.ndarray, data_name: str ): """ 对指定数据绘制: Histogram直方图、KDE核密度图、Q-Q图 """ skewness = stats.skew(data)[0 ] kurtosis = stats.kurtosis(data)[0 ] fig, (ax1, ax2, ax3) = plt.subplots(1 , 3 ) fig.suptitle(f"{data_name} \n(Skewness: {skewness:.2 f} | Kurtosis: {kurtosis:.2 f} )" , fontsize=16 ) sns.histplot(data, ax=ax1, legend=False ) ax1.set_title("Histogram" ) ax1.set_xlabel("Value" ) ax1.set_ylabel("Freq" ) sns.kdeplot(data, ax=ax2, fill=True , facecolor='orange' , alpha=0.6 , legend=False ) ax2.set_title("KDE" ) ax2.set_xlabel("Value" ) ax2.set_ylabel("Density" ) stats.probplot(data.ravel(), dist="norm" , plot=ax3) ax3.set_title("Normal Q-Q" ) ax3.get_lines()[0 ].set_markerfacecolor('r' ) ax3.get_lines()[0 ].set_markeredgecolor('r' ) ax3.get_lines()[1 ].set_color('k' ) plt.tight_layout() plt.show() titanic_dataset = sns.load_dataset('titanic' ) fare_data_series = titanic_dataset['fare' ] fare_data = fare_data_series.to_numpy().reshape(-1 ,1 ) print (f"fare_data: 类型: {type (fare_data)} , 形状: {fare_data.shape} " )plot(fare_data, "Fare Data: Origin" ) print ("-" *45 , "对数变换" , "-" *45 )log_transformer = FunctionTransformer(func=np.log1p) fare_data_by_log = log_transformer.fit_transform(fare_data) plot(fare_data_by_log, "Fare Data: Log Transformation" ) print ("-" *45 , "Box-Cox变换" , "-" *45 )fare_plus_one = fare_data + 1 boxcox_transformer = PowerTransformer(method='box-cox' , standardize=False ) fare_data_by_boxcox = boxcox_transformer.fit_transform(fare_plus_one) print (f"Box-Cox变换 λ参数: {boxcox_transformer.lambdas_} " )plot(fare_data_by_boxcox, "Fare Data: Box-Cox Transformation" ) print ("-" *45 , "Yeo-Johnson变换" , "-" *45 )yeo_johnson_transformer = PowerTransformer(method='yeo-johnson' , standardize=False ) fare_data_by_yeo_johnson = yeo_johnson_transformer.fit_transform(fare_data) print (f"Yeo-Johnson变换 λ参数: {yeo_johnson_transformer.lambdas_} " )plot(fare_data_by_yeo_johnson, "Fare Data: Yeo-Johnson Transformation" )
输出结果如下
1 2 3 4 5 6 fare_data: 类型: <class 'numpy .ndarray '> , 形状: (891 , 1 ) --------------------------------------------- 对数变换 --------------------------------------------- --------------------------------------------- Box-Cox变换 --------------------------------------------- Box-Cox变换 λ参数: [-0.09778702 ] --------------------------------------------- Yeo-Johnson变换 --------------------------------------------- Yeo-Johnson变换 λ参数: [-0.09778703 ]
效果如下所示
参考文献
机器学习 周志华著
机器学习公式详解 谢文睿、秦州著
图解机器学习和深度学习入门 山口达辉、松田洋之著