【机器学习(6)】数据预处理:预处理、标准化和数据纠偏

Onida ·
更新时间:2024-09-21
· 921 次阅读

模型评价体系

回顾一下以前提及的模型评价的体系

类别 概念
模型(model) 规律和经验
学习(learning) 从数据中总结规律的过程
误差(error) 衡量模型准确性的指标
训练集(教材教辅) 训练模型的数据集
验证集(模拟考卷) 测试学习模型泛化能力的数据集
应用数据(高考) 模型实际应用场景的特征集
数据预处理与特征工程

概念:数据预处理与特征工程泛指对训练数据集进行特征增加、删除、变换的方法

目标:通过对训练数据的处理变换,提高模型训练表现和泛化能力

类别:
        特征变换:预处理、标准化、纠偏
        特征增加与删减:特征降维与变量扩展

1. 预处理 1.1缺失值:样本的部分特征信息缺失

处理方式:

1) 删除:缺失样本量 非常大,删除整个变量;如果缺失量较少,且 难以填充 则删除缺失样本

2) 填充:缺失量 小于10%,根据缺失变量的数据分布采取 均值(正态分布)中位数(偏态分布) 进行填充

3) 模拟或预测缺失样本:根据样本的数据分布,生成 随机值填充(插值);使用与缺失相比相关性非常高的特征,建立模型,预测缺失值

数据:房价数据(1000条共10个维度)

1.2 数据导入 import pandas as pd import numpy as np import matplotlib.pyplot as plt import os os.chdir(r"C:\Users\86177\Desktop") df = pd.read_excel('realestate_sample.xlsx') print(df.columns.to_list()) print(df.shape)

–> 输出的结果为:

['id', 'complete_year', 'average_price', 'area', 'daypop', 'nightpop', 'night20-39', 'sub_kde', 'bus_kde', 'kind_kde'] (1000, 10) 1.3 缺失值查看 print(df.isna().sum()) #print(df.isnull().sum()) #两种方法都可以

–> 输出的结果为:

id 0 complete_year 0 average_price 11 area 8 daypop 0 nightpop 12 night20-39 0 sub_kde 0 bus_kde 0 kind_kde 0 dtype: int6 1.4 逐一填充

注意,这里的缺失值不是很多,可以直接选择删除数据(df.dropna(inplace=True)),但是在数据量不大的情况下,还要进行预测,建议选择数据填充;删除的方式比较简单,不进行梳理了。下面选择填充的方式处理数据,梳理一下填充的过程:首先是要查看一下数据的分布情况,然后再决定采用填充的方式(三种方式进行数据的填充)

1)查看房价并进行数据填充

# 查看房价 df['average_price'].hist() plt.show()

–> 输出的结果为:(数据的分布大致是符合正态分布的,可以采用均值填充的方式)
在这里插入图片描述
2) 填充‘average_price’字段空值(核心:如何填充空值)

# 选择均值填充 price_mean = df['average_price'].mean() print(price_mean) # 使用均值进行缺失值填充 df.loc[df['average_price'].isna(),'average_price'] = price_mean print(df.isnull().sum())

–> 输出的结果为:(填充空值核心代码:df.loc[df['A'].isna(),'A'] = B

29629.326592517693 id 0 complete_year 0 average_price 0 area 8 daypop 0 nightpop 12 night20-39 0 sub_kde 0 bus_kde 0 kind_kde 0 dtype: int64

3)查看房间面积并进行数据填充

df['area'].hist() plt.show()

–> 输出的结果为:(房间面积数据是符合偏态分布的,可以按照中位数进行填充)
在这里插入图片描述
4) 填充‘area’字段空值

# 选择中位数填充 area_median = df['area'].median() print(area_median) # 使用均值进行缺失值填充 df.loc[df['area'].isna(),'area'] = area_median print(df.isnull().sum())

–> 输出的结果为:(最后就剩下一个字段未被处理)

79.8 id 0 complete_year 0 average_price 0 area 0 daypop 0 nightpop 12 night20-39 0 sub_kde 0 bus_kde 0 kind_kde 0 dtype: int64

5)查看夜间人口密度并进行数据填充

df['nightpop'].hist() plt.show()

–> 输出的结果为:(中间有部分数据是很突出的,这里可以换种方式处理)
在这里插入图片描述
6) 填充‘nightpop’字段空值

这里不采用中位数的方式进行填充了,收集的数据中含有日间人口面密度,夜间人口密度以及夜间20-39岁人口密度,可以通过查看这三者直接的相关性来进行夜间人口密度数据的填充(前提是相关性比较大),下面就使用线性回归,然后使用r2作为评价指标,也就是高中学的相关性系数,其值越接近1,说明两者的相关性越大

首先查看一下三者的相关系数,因为是要进行nightpop数据的填充,选择相关性大的

# 查看相关性系数 print(df[['daypop','nightpop','night20-39']].corr())

–> 输出的结果为:(这里需要强调一下,相关性系数大于0.9基本上就可以认定变量共线性很高了,因此用谁预测都可以,虽然nightpop与night20-39变量之间的相关性较高,但实际情况中如果nightpop缺失,night20-39作为夜间的一部分,缺失的可能性也比较高,因此用daypop预测来填充缺失数据。)

daypop nightpop night20-39 daypop 1.000000 0.949165 0.938495 nightpop 0.949165 1.000000 0.983803 night20-39 0.938495 0.983803 1.000000

这里选取 daypop 变量数据进行 nightpop 变量缺失数据的预测填充

# 训练线性回归模型对夜间人口进行填补 from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error, r2_score train = df.copy().loc[~df['nightpop'].isna(),['daypop','nightpop']].reset_index(drop=True) x = train[['daypop']] y = train[['nightpop']] model = LinearRegression() model.fit(x,y) print(f'R squared is: {r2_score(y, x)}')

–> 输出的结果为:(划分数据之前,要将数据中的缺失值全部去除,r2为0.878,说明可以用来预测)

R squared is: 0.878354472726492

查看填充后的数据缺失情况

df.loc[df['nightpop'].isna(),['nightpop']] = model.predict(df.loc[df['nightpop'].isna(),['daypop']]) print(df.isnull().sum())

–> 输出的结果为:(至此所有的缺失值都已经填充完毕)

id 0 complete_year 0 average_price 0 area 0 daypop 0 nightpop 0 night20-39 0 sub_kde 0 bus_kde 0 kind_kde 0 dtype: int64

7) 特殊字段的处理

比如在收集数据中,会有一些数据显示的是未知/未知数据等,这些数据其实也是等同于缺失数据,因此也是需要单独拿出来进清洗的

# 特殊类型的缺失值 print(df.complete_year.value_counts().head())

–> 输出的结果为:(显然输出的第一个就是我们要处理的缺失数据)

未知 102 2006 83 1994 56 2005 54 1995 53 Name: complete_year, dtype: int64

对于这种特殊数据的处理,一般选择直接删除即可

df = df[df.complete_year!='未知'].reset_index(drop=True) print(df.head())

–> 输出的结果为:(注意,这一步为什么不放在第一步,而放在最后一步???如果放在第一步的话,直接就相当于删除了100多条数据,占了本身数据量的十分之一,那么对于其它字段的数据填充就会造成影响,而填充完毕后再删除特殊字段就不会有这种问题)

id complete_year average_price area daypop nightpop night20-39 sub_kde bus_kde kind_kde 0 107000909879 2008 33464.0 25.70 119.127998 150.060287 53.842050 5.241426e-11 0.279422 7.210007e-11 1 107000908575 1996 38766.0 26.57 119.127998 150.060287 53.842050 5.241426e-11 0.279422 7.210007e-11 2 107000846227 2005 33852.0 28.95 436.765809 376.523010 183.301881 1.523092e-24 0.231135 2.922984e-01 3 107000676489 1995 39868.0 30.10 247.545324 385.412857 142.819971 4.370519e-11 0.321443 2.401811e-01 4 107000676873 1995 42858.0 30.10 247.545324 385.412857 142.819971 4.370519e-11 0.321443 2.401811e-01 1.5 离群值处理

离群值:远离数据主要部分的样本(极大值或极小值)
处理方式:

删除:直接删除离群样本

填充样本:使用box-plot定义变量的数值上下界,以上界填充极大值,以下界填充最小值

1) 查看房价的离群情况

fig,axes = plt.subplots(1,2,figsize = (15,6)) df['average_price'].hist(ax = axes[0]) df[['average_price']].boxplot(ax = axes[1]) plt.show()

–> 输出的结果为:(通过左右图对比,箱型图更为明显的显示上下限)
在这里插入图片描述
2) 根据箱线图的上下限进行异常值的填充,封装处理方法

def boxplot_fill(col): # 计算iqr:数据四分之三分位值与四分之一分位值的差 iqr = col.quantile(0.75)-col.quantile(0.25) # 根据iqr计算异常值判断阈值 u_th = col.quantile(0.75) + 1.5*iqr # 上界 l_th = col.quantile(0.25) - 1.5*iqr # 下界 # 定义转换函数:如果数字大于上界则用上界值填充,小于下界则用下界值填充。 def box_trans(x): if x > u_th: return u_th elif x < l_th: return l_th else: return x return col.map(box_trans) # 填充效果查看 boxplot_fill(df['average_price']).hist() # 进行赋值 df['average_price'] = boxplot_fill(df['average_price']) plt.show()

–> 输出的结果为:(两端的数据就进行处理完毕了,注意上面的.map()方法,就是直接加载函数的意思,trans这里是transform的缩写不是train
在这里插入图片描述

1.6 保存数据

这里处理完标签数据之后,就可以进行数据的保存了

df.to_excel('realestate_sample_preprocessed.xlsx',index=False) 2. 数据标准化

1) 标准化目的:
         去除数据量纲的影响
         提高模型的解释性
         加快模型收敛速度

2) 标准化的方法:
         中心化:减去均值再除以标准差
         01标准化:减去最小值再除以最大值与最小值的差

2.1 sklearn 中标准化的方法 from sklearn.preprocessing import StandardScaler from sklearn.preprocessing import MinMaxScaler # 以中心化为例讲解sklearn中标准化的使用方法 scaler = StandardScaler() fig,axes = plt.subplots(1,2,figsize = (15,6)) df['daypop'].hist(ax=axes[0]) trans_data = df.copy()[['daypop']] scaler.fit(trans_data) trans_data['daypop'] = scaler.transform(trans_data) trans_data['daypop'].hist(ax=axes[1]) plt.show()

–> 输出的结果为:(原数据有0-3000变换到低区间范围)
在这里插入图片描述

2.2 过程封装Pipeline中,整合数据标准化与模型 from sklearn.linear_model import LinearRegression from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline # 构建模型工作流 pipe_lm = Pipeline([ ('sc',StandardScaler()), ('lm_regr',LinearRegression()) ]) print(pipe_lm)

–> 输出的结果为:(上述的代码会完成两项功能,首先是训练的数据输入进来之后会进行标准化处理,然后将标准化后的数据再输入到线性回归模型中进行训练)

Pipeline(memory=None, steps=[('sc', StandardScaler(copy=True, with_mean=True, with_std=True)), ('lm_regr', LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False))], verbose=False) 3. 数据纠偏

1) 数据分布:
         正态分布:数据呈现对称的钟形分布
         右偏态:样本大量集中在均值左边(均值偏到了右边)
         左偏态:样本大量集中在均值右边(均值偏到了左边)

2) 处理方法:
         右偏态:常用对数函数处理
         左偏态:常用指数函数处理

3) 通用变换方法:以降低数据的偏态系数为目标,使得数据分布更加接近正太分布的变换方法
         yeo-johnson 变换:可以处理包含正数、负数和零的变量
         box-cox变换:只能处理数值皆为正数的变量

sklearn 中纠偏的方法

使用pipeline进行纠偏过程的整合

from sklearn.preprocessing import PowerTransformer from sklearn.linear_model import LinearRegression from sklearn.pipeline import Pipeline # 构建模型工作流 pipe_lm = Pipeline([ ('sc',StandardScaler()), ('pow_trans',PowerTransformer(method='yeo-johnson')), ('lm_regr',LinearRegression()) ]) print(pipe_lm)

–> 输出的结果为:(method = ‘yeo-johnson’ or 'box-cox’,默认的是使用前者)

Pipeline(memory=None, steps=[('sc', StandardScaler(copy=True, with_mean=True, with_std=True)), ('pow_trans', PowerTransformer(copy=True, method='yeo-johnson', standardize=True)), ('lm_regr', LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False))], verbose=False)
作者:Be_melting



数据预处理 数据 学习 机器学习

需要 登录 后方可回复, 如果你还没有账号请 注册新账号