首先明确概念:用来训练模型的数据集越大,数据质量越好,数据模型的复杂度就越低
如果特征来自于不同的文件、数据库表、csv等,就要把不同地方的数据进行规整,统一存储在一个方便以后使用的地方
数据清洗
数据样本抽样
异常值、空值处理
数据样本抽样要注意:
抽样要具有代表性:样本各个特征的比例要尽量与整体的比例保持一致
样本比例要平衡以及样本不平衡时要如何处理
尽量考虑使用全部的数据
异常值、空值(NaN Not a Number)的处理
import pandas as pd
df = pd.DataFrame({'A':['a0', 'a1', 'a1', 'a2', 'a3', 'a4'], 'B':['b0','b1','b2','b2','b3', None],'C':[1,2,None,3,4,5],
'D':[0.1,10.2,11.4,8.9,9.1,12],'E':[10,19,32,25,8,None],'F':['f0','f1','g2','f3','f4','f5']})
df.isnull() # 看一哈总体情况
df.dropna() # 空的都被搞掉
df.dropna(subset=['A']) # A列的空值行被干掉
df.duplicated(['A']) # A列的重复值为True
df.duplicated(['A', 'B']) # 在A列和B列同为该列的重复值时才会显示True
df.drop_duplicated(['A']) # 去除重复值,有一个keep参数有'first','last',False全部重复的都干掉
df.fillna(0)
df.fillna(df['E'].mean()) # 把异常值用均值代替
# 用四分位数去除异常值
upper_q = df['D'].quantile(0.75)
lower_q = df['D'].quantile(0.25)
q_int = upprt_q - lower_q
k = 1.5
df[df['D'] > lower_q - k*q_int][df['D'] < upper_q + k*q_int]
df[[True if item.startswith('f') else False for item in list(df['F'].values)]] # 去除F列以g开头的字符串
连续型的数据可以考虑使用插值
# 三次样条插值 基于pandas
df['E'].interpolate(method='spline', order=3)
# 基于scipy
# 线性插值
from scipy.interpolate import interpld
LinearInsValue = interpld(x, y1, kind='linear')
# 拉格朗日插值
from scipy.interpolate import lagrange
LargeInsValue = lagrange(x, y1)
# 样条插值
from scipy.interpolate import spline
import numpy as np
SplineInsValue = spline(x, y, xnew=np.array([6, 7]))
标注 Label(特征预处理)
在数据清洗过后,想要在建模过程中发挥出数据特征的特点,有时还要记性特征预处理。
标注和特征:比如要预测某一时间会不会下雨,获得了以往了历史数据,直接对问题进行肯定和否定的就是标注,如‘会下雨’和‘不会下雨’,其他的比如‘湿度’,‘风的大小’,‘有没有太阳’等就是特征。
特征选择
去掉与标注不相关或者多余的特征进行降维(特征提取)
特征选择有3个切入思路
1.过滤思想:直接评价某个特征于标注特征的相关性,如果与标注的相关性很小就去掉,根据下面这张表进行分析方法选择
2.包裹思想,把原数据作为一个集合,不断的划分子集,对子集进行评估,评估最优的子集就继续划分子集,直到评价指标下降较大或者低于阈值。常见的有RFE算法
3.嵌入思想
把特征嵌入到一个简单的模型中,根据一个简单的模型来分析特征的重要性,最常见使用正则化的方式作特征选择,比如有n个特征,通过一个回归模型对标注进行回归,线性回归、逻辑回归都行,最后得到一些w系数,然后对这些w系数进行正则化,就是把它转化为一个0-1之间的数,此时这些w系数就反应了这些特征的分量和重要程度,如果有的系数比较小,就可以把这个特征去掉,嵌入思想实际上是有一定的风险的,比如模型选择不当,会导致重要的属性被丢弃,所以这里的模型最好是和后面要用来建模的模型有比较强的关联,比如都用线性模型,或者都用同样分布形式的(函数图形一致的非线性函数)
代码:
import numpy as np
import pandas as pd
import scipy.stats as ss
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style(style="whitegrid")
# df = pd.read_csv('./HR_comma_sep.csv')
df = pd.DataFrame({"A": ss.norm.rvs(size=10), "B": ss.norm.rvs(size=10), "C": ss.norm.rvs(size=10),
"D": np.random.randint(low=0, high=2, size=10)}) # D列表示标注
from sklearn.svm import SVR # SVR回归器
from sklearn.tree import DecisionTreeRegressor # 决策树回归器
X = df.loc[:, ['A', 'B', 'C']] # 特征
Y = df.loc[:, 'D'] # 标注
# 过滤思想常用类,包裹思想常用类,嵌入思想常用类
from sklearn.feature_selection import SelectKBest, RFE, SelectFromModel
# 过滤思想
skb = SelectKBest(k=2) # 三个特征,k=2表示筛选后保留两个
skb.fit(X, Y) # 拟合
# Out SelectKBest(k=2, score_func=<function f_classif at 0x000001F74284D400>)此处这个score_func是可以自己指定使用什么方法的
skb.transform(X) # 进行过滤得到经过过滤后的array
# 包裹思想
# 用复杂度比较小的线性回归器'linear',n_features_to_select最终要选取几个特征,step每步、每次迭代要去掉多少个特征
rfe = RFE(estimator=SVR(kernel='linear'), n_features_to_select=2, step=1)
rfe.fit_transform(X, Y)
# 嵌入思想
# threshold 表示重要性因子的数,低于多少要被去掉
sfm = SelectFromModel(estimator=DecisionTreeRegressor(), threshold=0.1)
sfm.fit_transform(X, Y)
特征变换
使得特征能发挥出它的特点
特征变换的方法:
1.对指化:对数据进行对数化或者指数化
指数化:把数据变为以e为底,那样在 x > 0 时,相同的dx可以放大dy,即对特征进行了放大
对数化:取对数,对特征进行缩小,更好比较(如对收入)
2.数据离散化:把连续数据变为离散数据(分为几段)
需要数据离散化的原因:①克服数据的缺陷,减少数据噪声的影响,对于这一类数据,如果进行离散化,直接分析离散值的分布属性,可以得到更令人信服的结论 ②某些算法需要使用的数据必须是离散的,比如朴素贝叶斯算法 ③数据的分线性映射需求:某些数据的分布(函数曲线)会有明显的拐点,那么连续数值在不同的区间内就可能代表不同的含义
数据离散化的方法:
分箱概念:先排序一下,深度就是数的个数,宽度就是数的区间
等宽分箱:
等深分箱:
自因变量优化:绘制自变量和因变量的柱状图,找拐点
代码
import numpy as np
import pandas as pd
# 等深分箱(按数据个数分成多少个等份)
lst = [6,8,10,15,16,24,25,40,67]
pd.qcut(lst, q=3) # q=3分成3个等份
Out[19]:
[(5.999, 13.333], (5.999, 13.333], (5.999, 13.333], (13.333, 24.333], (13.333, 24.333], (13.333, 24.333], (24.333, 67.0], (24.333, 67.0], (24.333, 67.0]]
Categories (3, interval[float64]): [(5.999, 13.333] < (13.333, 24.333] < (24.333, 67.0]]
# 可以发现替代等深分箱的值都是区间值
pd.qcut(lst, q=3, labels=['low', 'medium', 'high']) # 指定区间表明的label
Out[20]:
[low, low, low, medium, medium, medium, high, high, high]
Categories (3, object): [low < medium < high]
# 等宽分箱 (最大值 - 最小值)/等份数 * [1~等分数] + 最小值
pd.cut(lst, bins=3) # 指定分成3段 同样可以指定label
Out[21]:
[(5.939, 26.333], (5.939, 26.333], (5.939, 26.333], (5.939, 26.333], (5.939, 26.333], (5.939, 26.333], (5.939, 26.333], (26.333, 46.667], (46.667, 67.0]]
Categories (3, interval[float64]): [(5.939, 26.333] < (26.333, 46.667] < (46.667, 67.0]]
3.数据的归一化和标准化
归一化:把数据转换为0~1之间的范围
归一化好处:①可以观察单个数据相对于数据整体情况的比例 ② 如果遇到不同量纲的数据特征,要比较数据间的距离,直接用0-1之间的距离度量两个数据会好很多。
标准化:将数据转化为均值为0,标准差为1的尺度上。Z-score转换
进行Z-score转换后可以很好的体现一个数据与该特征下其他数据的相对大小的差距关系,就是比如一群人只有一个人是两米,别的都是一米5,就显得这个人贼高。
代码:
# 归一化,标准化
from sklearn.preprocessing import MinMaxScaler, StandardScaler
# 归一化
# fit_transform()传入的array必须满足于独立的行
# reshape(-1, columns) 表示你的行自己自动搞出来,我一定要指定每一行有多少列
MinMaxScaler().fit_transform(np.array([1, 4, 10, 15, 21]).reshape(-1, 1))
# 标准化
StandardScaler().fit_transform(np.array([1, 1, 1, 1, 0, 0, 0, 0]).reshape(-1, 1))
4.数值化:把非数值数据转化为数值数据的过程,一般,定类数据和定序数据要进行数值化,定距数据要进行归一化。
定序数据的数值化–标签化:如 low,medium,high 对应为 0,1,2,经过这样的数值化,可以进行四则运算
定序数据的数值化–独热编码:
代码
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
# 标签编码
LabelEncoder().fit_transform(np.array(['Down', 'Up', 'Up', 'Down','Medium']).reshape(-1, 1))
D:\Anaconda_file\lib\site-packages\sklearn\preprocessing\label.py:111: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel().
y = column_or_1d(y, warn=True)
Out[33]: array([0, 2, 2, 0, 1], dtype=int64) # label编码的结果是按首字母进行升序的0,1,2,3...
# 独热编码 要传入一个进行了标签编码的array
lb_encoder = LabelEncoder()
oht_encoder.transform(lb_encoder.transform(np.array(['Yellow', 'Blue', 'Green', 'Green', 'Red'])).reshape(-1, 1))
Out[37]:
<5x4 sparse matrix of type '<class 'numpy.float64'>'
with 5 stored elements in Compressed Sparse Row format>
oht_encoder.transform(lb_encoder.transform(np.array(['Yellow', 'Blue', 'Green', 'Green', 'Red'])).reshape(-1, 1)).toarray()
Out[38]:
array([[0., 0., 0., 1.],
[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.]])
5.正规化
正规化的本质是将一个向量的长度正规到单位1,如果距离尺度的衡量用L1就是L1正规化,如果距离尺度用L2衡量就是L2正规化(欧式距离)
正规化的用法:
1.直接用在特征的从处理上,可以将特征间的差距转换成考虑相对整体特征长度的一个相对值(很少用)
2.用在每个对象的各个特征的表示(特征矩阵的行),每个向量的分量都是一个特征,此时进行正规化,可以体现出一个对象特征之间影响的相对关系特点(常用)
3.用在模型的参数上,尤其是回归模型(常用)如:可以用L2正则化,让所有系数的平方和为1,可以表示出哪些特征对于标注的影响占比比较大,哪些特征对于标注的影响占比比较小。
代码
from sklearn.preprocessing import Normalizer
# 默认对行进行正规化,所以不用reshape了
Normalizer(norm='l1').fit_transform(np.array([[1, 1, 3, -1, 2]]))
Out[45]: array([[ 0.125, 0.125, 0.375, -0.125, 0.25 ]])
Normalizer(norm='l2').fit_transform(np.array([[1, 1, 3, -1, 2]]))
Out[44]: array([[ 0.25, 0.25, 0.75, -0.25, 0.5 ]])
特征降维Allocation
PCA变换、奇异值分解等线性降维,都是根据数据属性之间的相互关系,进行相关性因子的提取,进而进行降维的。但是PCA变换和奇异值分解,都没有考虑到标注,而是由特征与特征之间的相关性强弱来决定降维后的分布形态,是无监督的降维方法。
特征降维-LDA是使用到标注的降维方法,此处的LDA是 Linear Discriminant Allocation 线性判别式分析,自然语言处理中也有一个LDA 隐含狄利克雷分布 Latent Dirichlet Allocation 注意区分。
LDA的核心思想是:投影变换后同一标注内距离尽可能小,不同标准间距离尽可能大。
代码
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
X = np.array([[-1, -1], [-2, -1], [-3, -2], [1, 1], [2, 1], [3, 2]])
Y = np.array([1, 1, 1, 2, 2, 2]) # 标注
LinearDiscriminantAnalysis(n_components=1).fit_transform(X, Y)
# LDA降维以后可以当做一个判别器(分类器)来使用 fisher classifier fisher分类器
clf = LinearDiscriminantAnalysis(n_components=1).fit(X, Y)
print(clf.predict([[0.8, 1]]))
特征衍生
通常我们所采集到的数据的维度不会很大,直接采集到的特征不一定能完全体现数据的全部信息,要通过已有的数据组合来发现新的含义,常用的方法有:加减乘除、求导,求高阶导,人工归纳,
特征工程实战
import numpy as np
import pandas as pd
import scipy.stats as ss
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import MinMaxScaler, StandardScaler
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
from sklearn.preprocessing import Normalizer
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.decomposition import PCA
sns.set_style(style="whitegrid")
# sl:satisfaction_level---Flase:MinMaxScaler;Ture:StandardScaler
# le:last_evaluation---Flase:MinMaxScaler;Ture:StandardScaler
# npr:number_project---Flase:MinMaxScaler;Ture:StandardScaler
# amh:average_monthly_hours---Flase:MinMaxScaler;Ture:StandardScaler
# tsc:time_spend_company---Flase:MinMaxScaler;Ture:StandardScaler
# wa:Work_accident---Flase:MinMaxScaler;Ture:StandardScaler
# pl5:promotion_last_5years---Flase:MinMaxScaler;Ture:StandardScaler
# sales--False:LabelEncoding;True:OneHotEncoding
# slr:salary---False:LabelEncoding;True:OneHotEncoding
# lower_d降到几维
def hr_preprocessing(sl=False, le=False, npr=False, amh=False, tsc=False, wa=False, pl5=False, sales=False, slr=False,
lower_d=False,
ld_n=1):
df = pd.read_csv('./HR_comma_sep.csv')
# step1 清洗数据 应当先清洗数据在拿标注
df = df.drop('left', axis=1) # 默认以行进行删除
label = df['left'] # 分离标注和原数据
# step2 得到标注
# 抽样、除去异常值,抽样要尽量保持全量数据,数据量太小不抽样了
df = df.dropna(subset=['satisfaction_level', 'last_evaluation']) # 去除空值
# 获取此列 < 1 且不等于发现的那个异常值的数据
df = df[df['satisfaction_level'] <= 1][df['salary'] != 'nme']
# 特征选择
# 特征处理
# 处理 satisfaction_level 列,通过探索分析发现它是处于0-1的但最小值不是0,
# 如果硬是要处理,可以进行拉伸,强行拉伸到0-1之间, 也可以使用标准化,转化为均值为0,方差为1的格式
scaler_list = [sl, le, npr, amh, tsc, wa, pl5]
column_list = ['satisfaction_level', 'last_evaluation', 'number_project', 'time_spend_company', 'work_accident']
for i in range(len(scaler_list)):
if not scaler_list[i]:
df[column_list[i]] = \
MinMaxScaler().fit_transform(df[column_list[i]].values.reshape(-1, 1)).reshape(1, -1)[0]
else:
df[column_list[i]] = \
StandardScaler().fit_transform(df[column_list[i]].values.reshape(-1, 1)).reshape(1, -1)[0]
# 处理 average_monthly_hours 这个属性是一个整数并且覆盖的范围比较大,可以通过离散化的方式,把数据进行简化,分箱,
# 但是此处数据量太小,不分箱了,直接保留原数据,这样可能对模型来说不是最准确的
# 离散值的数据转化
# sales字段和salary字段都是离散值
map_salary = {'low': 0, 'medium': 1, 'high': 2}
scaler_list = [slr, sales]
for i in range(len(scaler_list)):
if not scaler_list[i]:
if column_list[i] == 'salary':
df[column_list[i]] = [map_salary.get(s) for s in df['salary'].values]
# 如果直接使用LabelEncoder,会按照首字母进行升序处理,那样就不是按照low,medium,high来0,1,2了
else:
df[column_list[i]] = LabelEncoder().fit_transform(df[column_list[i]])
df[column_list[i]] = MinMaxScaler().fit_transform(df[column_list[i]].values.reshape(-1, 1)).reshape(1, -1)[0]
else:
# 直接处理df的时候,pandas有其自身的OneHotEncoder方法叫get_dummies
df = pd.get_dummies(df, columns=[column_list[i]])
if lower_d:
# return LinearDiscriminantAnalysis(n_components=ld_n) LDA的n_components这个参数不能大于类的个数,此处类的个数为2
# PCA降维不受这个限制,所以使用PCA
return PCA(n_components=ld_n).fit_transform(df.values)
return df
if __name__ == '__main__':
df = hr_preprocessing(sl=True, le=True, npr=True)
print(df)