数据预处理一方面提高数据的质量,另一方面是要让数据更好的适应特定的挖掘工具。统计发现,在数据挖掘过程中,数据预处理工作量占到整个过程的60%
1 数据清洗
数据清洗主要是删除原始数据集中的无关数据,重复数据,平滑噪声数据,筛选掉和挖掘主题无关的数据,处理确实值,异常值等
数据来源:kaggle泰坦尼克号获救预测数据
test = pd.read_csv(r'C:\Users\Administrator\Desktop\test.csv')
train = pd.read_csv(r'C:\Users\Administrator\Desktop\train.csv')
data = train.append(test,ignore_index=True)
data.to_csv(r'C:\Users\Administrator\Desktop\data.csv')
#加载数据并合并测试集和训练集
1.1 重复值处理
1.1.1判断重复值
data['Name'].nunique()#查看某个标签去除重复值后实际个数
len(data['Name'])#查看某个标签数据长度
'Name'标签去重后个数为1307
'Name'标签实际长度1309
#针对数据行
data.duplicated()#标记出重复行
data.duplicated().any()#数据是否有相同行,true or False
返回Flase 数据无相同的行
1.1.2删除重复值
data.drop_duplicates()#删除数据相同的行,默认保留第一条
data.drop_duplicates(['Name'],keep='last')
#删除Name标签重复数据,keep='last'保留最后一条数据
1.2 缺失值处理
1.2.1判断缺失值
#直接判断是否是缺失值,返回一个Series
data.isnull()#缺失值返回True
data.notnull()#缺失值返回False
data.isnull().any()#判断每个标签是否有缺失值,返回一个Series
miss = pd.DataFrame({'数据类型':data.dtypes,
'是否缺失':data.isnull().any(),
'缺失量':data.isnull().sum()})
miss['缺失率'] = (miss['缺失量']/len(data)).map(lambda x:"%.2f%%"%(x*100))
miss = miss[miss['缺失量']>0]
miss
1.2.2缺失值常用处理方法
不处理
在个别情况下,比如应用决策树算法的时候,该算法本省允许数据缺失值直接进入分析挖掘,在这种情况行啊缺失值本身被可能是一个特定的属性类别
删除
- 直接删除有大量缺失值的变量,主要针对那些缺失值占比超过相当比例变量,比如缺失率超过20%以上。但需仔细考虑,这种大规模的缺失是否又另外的背景和意义
如Cabin(船舱号)变量大量缺失,可能造成缺失的原因,由于泰坦尼克号上有大量乘客居住在下等舱只有床位没有船舱,所有没有Cabin(船舱号)数据,后续挖掘建模分析,这些乘客存货概率极低,与存活与否有较高的负相关
del data['Cabin'] #直接删除某个变量
- 直接删除带有缺失值的数据元祖(行)或观察对象,该方法只适用于建模样本(训练集)里缺失比率很少,并且后期打分应用(测试集)中的数据缺失值比例很少的情况。不建议
data.dropna()#删除缺失值,默认参数inplace=False,不替换原数据
插补
- 均值/中位数/众数插补
根据属性值的类型,用该属性取值的均值,中位数,众数进行插补
定量数据(连续性变量)服从正太分布用均值插补,偏态分布用中位数或众数插补
定性数据(分类数据)用众数插补
- 使用固定值
将缺失的属性值用一个常量替换,如某地工厂普通外来务工人员的基本工资可以用当地上一年度工资标准进行填补
这里Cabin(船舱号)变量的大量缺失值可以理解为无船舱的床位,填补NA表示无
- 随机填充
样本数据个数N,观测数据个数Ni,缺失数据个数N0,N=Ni+N0,从Ni个已知观测数据中抽取No个数据,填充到缺失数据中,避免均值填充后数据过于集中的缺失,使填补后的分布接近真值分布,适用缺失率小于20%数据
ni = data[data.notsnull()] #已知观察数据
n0 = data.isnull().sum() #缺失个数
data[data.isnull()] = random.samle(ni,n0)
#从Ni个已知观测数据中抽取N0个数据,填充到缺失数据
- 回归方法
对带有缺失值的变量,根据已有的数据和与其相关的变量(因变量)的数据建立拟合模型来预测缺失的属性值
- 插值法
插值法是利用已知点建立合适的插值函数f(x),未知值由对应点xi求出的函数值f(xi)近视代替,其中拉格朗日插值法在Pyhton中容易实现(缺点:当插值节点增减时,插值多项式会随之变化,牛顿插值法改善了这一缺点)
#n为列向量,n为被插值的位置,k为取前后的数据个数
#由于数据量原因,以空值前后5个数据(共10个数据)为例做插值
def na_c(s,n,k=5):
y = s[list(range(n-k,n+1+k))] # 取数
y = y[y.notnull()] # 剔除空值
return(lagrange(y.index,list(y))(n))
#使用拉格朗日插值法对Age进行填补
for i in data.columns:
for j in range(len(data['Age'])):
if data[i].isnull()[j]: #判断是否为空值
data[i][j] = na_c(data[i],j)
缺失值插补的其他方法及应用详见
缺失数据的填充方法研究
对泰坦尼克号获救数据进行缺失值处理
#Cabin缺失率较大,根据对数据的理解,填充NA表示无船舱
data['Cabin'].fillna('NA',inplace = True)
#Embarked(登船口岸)为分类变量,缺失量较小,填充众数(出现频率最高的值)
data['Embarked'].fillna(data['Embarked'].mode()[0],inplace = True)
#Fare(票价)缺失一个数据,填充均值
data['Fare'].fillna(data['Fare'].mean(),inplace = True)
Age(年龄)连续性变量,缺失率20%,原始数据呈偏态分布,分别用中位数及随机填充,查看填充后分布情况
import random
fig,ax =plt.subplots(1,2,figsize=(20,5))
data['Age'].plot(kind='kde',grid=True,ax=ax[0],title='数据原始分布')
res = stats.probplot(data['Age'][data['Age'].notnull()],plot=plt)#正太概率图
d_m = data['Age'].fillna(data['Age'].median())#填充中位数
fig,ax =plt.subplots(1,2,figsize=(20,5))
d_m.plot(kind='kde',grid=True,ax=ax[0],title='缺失值填充中位数')
ni =data['Age'][data['Age'].notnull()]#未缺失观测样本
n0 = data['Age'].isnull().sum()#缺失数量
d_r = data['Age'].copy()
d_r[d_r.isnull()] = random.sample(ni.tolist(),n0)#随机填充
d_r.plot(kind='kde',grid=True,ax=ax[1],title='缺失值随机填充')
Age(年龄)字段缺失值随机填充后接近原始数据真实分布
#使用随机填充插补缺失值
data['Age'] = d_r
1.3 异常值处理
- 删除含有异常值的记录
- 视为缺失值,以缺失值的处理方法进行处理
- 不处理,直接在异常值的数据集上进行挖掘建模
在多数情况下,异常值的删除可以有效降低数据的波动,使得处理后的建模更加稳定,从而提高模型的稳定性,但是某些业务场景下,异常值的应用是另一个专门的业务方向,对于有价值的异常值的分析应用包括利用聚类分析技术识别异常值,利用稀有事件的预测模型搭建去监控,预测异常值出现的可能。
2.数据变换
主要是对数据进行规范化处理,将数据转换成适当的形式,以适用于挖掘人物及算法的需要。
2.1 生成衍生变量
通过对原始数据进行简单,适当的数学公式推导,产生更有商业意义的变量。一般常见的衍生变量如下
- 用户月均,年均消费金额和消费次数
- 用户在特定商品类目的消费金额占起全部消费金额的比例
- 家庭人均年收入
- 用户在线交易终止的次数占用户在线交易成功次数的比例
- 用户下单付费的次数占用户下单次数的比例
2.2改善变量分布的转换
样本中连续变量原始分布状态偏差较大,严重不对称。这种分布出现在自变量中会干扰模型的拟合。通过各种数学转换,使得自变量的分布呈现或者近视正太分布,模型的拟合常常会有明显的提升。常用数学变换如下
- 取对数(log)
np.log()#取对数
np.log1p()#np.log(1+s)
- 开平方根(Square Root)
np.sqrt()#开平方根,应用于Series
- 开平方(Square)
np.square()#平方
- 取倒数(Inverse)
np.invert()#取倒数
5.取指数(Exponential)
np.exp()#取指数
BOX-COX变换
线性回归模型满足线性性、独立性、方差齐性以及正态性的同时,又不丢失信息,此种变换称之为Box—Cox变换
from scipy.special import boxcox1p
boxcox1p(data,lam)
#重点lam 系数的取值 ,可用最大似然法取值
2.3分箱转换(连续变量离散化)
一些挖掘算法,特别是某些分类算法(如ID3决策树算法,Apriori算法等)要求数据是分类属性,需将连续变量转化为分类变量,即连续变量离散化
恰当的分箱可以降低变量的复杂性,简化数据,提升模型的预测效果,尤其自变量与因变量有比较明显的非线性关系,自变量的偏度很大时,分箱都有不错的效果
在数据挖掘实践中,经常会对改善变量分布的转换和分箱转化进行分别尝试,择其优者可用
常用离散化方法
- 等宽分箱
- 等频分箱
- 基于聚类分析的方法
from sklearn.cluster import KMeans
kmodel = KMeans(n_clusters = k,n_jobs=5)
#建立模型,n_jobs是并行数,一般等于CPU数较好
k = 4
fig,ax= plt.subplots(1,3,figsize=(20,5))
cat = pd.cut(data['Age'],k)
cat2 = pd.qcut(data['Age'],k)
kmodel.fit(data['Age'].reshape(len(data),1))
c = pd.DataFrame(kmodel.cluster_centers_).sort(0)
w = pd.rolling_mean(c,2).iloc[1:]
w = [0] + list(w[0]) + [data['Age'].max()]
cat3 = pd.cut(data['Age'], w)
cat.value_counts(sort = False).plot.bar(grid= True,ax=ax[0],title = '等宽分箱')
cat2.value_counts(sort = False).plot.bar(grid= True,ax=ax[1],title = '等频分箱')
cat3.value_counts(sort = False).plot.bar(grid= True,ax=ax[2],title = 'KMeans聚类分箱')
2.4数据标准化
对于参与聚类(k-means)的连续性变量,由于潜在的数量等级的差异(不同属性度量单位不统一),如果不加处理直接进行聚类,很容易造成聚类结果的失真,在聚类之前所采用的重要技术措施就是数据标准化
最常用标准化方式即Z-score标准化
Z-score 是一个分数与平均数的差再除以标准差的过程 z=(x-μ)/σ,其中x为某一具体分数,μ为平均数,σ为标准差
数学意义:一个给定分数距离平均数多少个标准差?
Max-Min标准化
将数据的最大最小值记录下来,并通过Max-Min作为基数(即Min=0,Max=1)进行数据的标准化处理 x = (x - Min) / (Max - Min) 使结果落在【0,1】区间范围内
容易受极值影响
2.5哑变量转换
对于参与回归模型的多分类变量,仅用一个回归系数来解释多分类变量之间的变化关系,及其对因变量的影响,就显得太不理想。
一般会将多分类变量转化为哑变量。来反映某个变量的不同属性。对于有n个分类属性的自变量,通常需要选取1个分类作为参照,产生n-1个哑变量(避免共线性)。
1. 对于无序多分类变量,引入模型时需要转化为哑变量
如血型分为A、B、O、AB四个类型,为无序多分类变量,通常情况下在录入数据的时候,为了使数据量化,我们常会将其赋值为1、2、3、4。从数字的角度来看,它们是具有从小到大一定的顺序关系的,而实际上,四种血型之间并没有这种大小关系存在,它们之间应该是相互平等独立的关系。如果按照1、2、3、4赋值并带入到回归模型中是不合理的,此时我们就需要将其转化为哑变量。
2. 对于有序多分类变量,引入模型时需要酌情考虑
如疾病的严重程度分为轻、中、重度,可认为是有序多分类变量,通常情况下我们也常会将其赋值为1、2、3(等距)或1、2、4(等比)等形式,通过由小到大的数字关系,来体现疾病严重程度之间一定的等级关系。而事实上由于疾病在临床上的复杂性,不同的严重程度之间并非是严格的等距或等比关系,因此再赋值为上述形式就显得不太合理,此时可以将其转化为哑变量进行量化。
3. 对于连续性变量,进行变量转化时可以考虑设定为哑变量
对于连续性变量,很多人认为可以直接将其带入到回归模型中即可,但有时我们还需要结合实际的临床意义,对连续性变量作适当的转换。例如年龄增加一岁,其效应是很微弱的,并没有太大的实际意义。此时,我们可以将年龄进行离散化,按照10岁一个年龄段进行划分,如0-10、11-20、21-30、31-40等等,将每一组赋值为1、2、3、4,此时构建模型的回归系数就可以解释为年龄每增加10岁时对因变量的影响。但有时候在年龄段较低和较高的人群中,某种疾病的死亡率较高,而在中青年人群中,死亡率却相对较低,年龄和死亡结局之间呈现一个U字型的关系,此时再将年龄段赋值为1、2、3、4就显得不太合理了。因此,当我们无法确定自变量和因变量之间的变化关系,将连续性自变量离散化时,可以考虑进行哑变量转换。
pd.get_dummies(data,prefix=None,prefix_sep="_",dummy_na=False,columns=None,drop_first=False)
#data 要处理的DataFrame
# prefix 列名的前缀,在多个列有相同的离散项时候使用
# prefix_sep 前缀和离散值的分隔符,默认为下划线,默认即可
# dummy_na 是否把NA值,作为一个离散值进行处理,默认为不处理
# columns 要处理的列名,如果不指定该列,那么默认处理所有列
# drop_first 是否从备选项中删除第一个,建模的时候为避免共线性使用
如何选择哑变量的参照组
对于有n个分类的自变量,需要产生n-1个哑变量,当所有n-1个哑变量取值都为0的时候,这就是该变量的第n类属性,即我们将这类属性作为参照。
例如上面提到的以职业因素为例,共分为学生、农民、工人、公务员、其他共5个分类,设定了4哑变量,其中职业因素中“其它”这个属性,每个哑变量的赋值均为0,此时我们就将“其它”这个属性作为参照,在最后进行模型解释时,所有类别哑变量的回归系数,均表示该哑变量与参照相比之后对因变量的影响。
原则上哑变量在模型中应同进同出,也就是说在一个模型中,如果同一个分类变量的不同哑变量,出现了有些哑变量有统计学显著性,有些无统计学显著性的情况下,为了保证所有哑变量代表含义的正确性,应当在模型中纳入所有的哑变量。