(一)机器学习基础 - 偏度、正态化以及 Box-Cox 变换
https://my.oschina.net/mathinside/blog/4942126
对于数据挖掘、机器学习中的很多算法,往往会假设变量服从正态分布。例如,在许多统计技术中,假定误差是正态分布的。这个假设使得能够构建置信区间并进行假设检验。因此,在数据预处理阶段会查看目标变量以及各个特征是否服从或接近正态分布,如果偏离就通过一定变换将该数据的分布正态化。
一般来说,数据的直方图如果单峰并近似正态但看上去又有些扭曲,可以考虑正态化。比如整体看上去还是一个山峰,但可能峰顶很尖或者整座山往左/往右倾斜了。这些现象如何用数字量化呢?偏度(skewness)和峰度(Kurtosis)就是两个常见的统计量,本篇主要处理前者。如下图所示,红色表示正态分布,黑色表示不同偏度,绿色和蓝色表示正负峰度。
.偏度的意义
偏度是一个重要的统计概念,我们至少可以从三个方面来衡量,
-
实际数据很少遵循正态分布。而偏度衡量了数据分布的不对称性,对于了解数据分布的形状来说至关重要。
-
偏度告诉我们离群值的方向。正偏度表示存在较大的极值,负偏度表示存在较小的极值。
-
偏度可以揭示大多数值集中在哪里,以及反应了均值、中位数以及众数间的大小关系。
例如,下图描绘了人均 GDP 的密度图,该图偏向右侧,平均值比中位数高出两倍多,换句话说,大多数国家的人均 GDP 较低。
如果使用此数据来预测例如预期寿命,则与预测人均 GDP 较高的国家的预期寿命相比,它可以更准确地预测那些人均 GDP 较低的国家的预期寿命。
2偏度
偏度,也称为偏态、偏态系数,是统计数据分布偏斜方向和程度的度量,是统计数据分布非对称程度的数量特征。
.定义
随机变量 的偏度 为三阶标准矩,定义为
其中 是三阶中心矩, 是标准差, 是期望。
.样本偏度
具有 个值的样本的样本偏度为,
其中 是样本平均值, 是三阶样本中心矩, 是二阶样本中心距,即样本方差。如果考虑无偏估计,则在上式中把除以 改为除以 。
根据数值可以将偏度分为两种,
-
负偏度或左偏度:左侧的尾部更长,数据左侧有较多的极值,分布的主体集中在右侧。
-
正偏度或右偏度:右侧的尾部更长,数据右侧有较多的极值,分布的主体集中在左侧。
.偏度与 3M
请注意,这里所谓的左偏和右偏的叫法,是根据尾部的方向来说的。对于左图,因为尾部在左侧,所以它是左偏(负偏);而右图的尾部是在右侧,所以它是右偏(正偏)。
上图分别为负偏度(左)和正偏度(右)的情况,注意平均值(mean)、中位数(median)和众数(mode)的位置。例如,对于右偏度,由于有较大的极值存在,所以拉高了平均值。
另外,如果分布对称,那么平均值 = 中位数,偏度为零。如果分布为单峰分布,那么平均值 = 中位数 = 众数。注意,偏度为零表示数值相对均匀地分布在平均值的两侧,但不一定意味着其为对称分布。
.皮尔逊偏度系数
为了简化计算,偏度还可以有其他定义方式,包括 Karl Pearson 建议的更简单的定义。
直观地看,偏度越大,众数与均值之间的距离就越大。从这个角度出发,我们可以定义如下皮尔逊(Pearson)第一偏度系数,
偏度众数均值标准差
可以用中位数和均值来近似计算众数,即
这就是说所谓的皮尔逊第二偏度系数,
3偏度分级
分布的偏度计算出来以后,我们要想办法将数据正态化。不同的偏度意味着数据分布与正态分布的不同偏离程度,因此需要使用不同的变换。可以将偏度分成三级,然后针对每个级别采用不同方法。
偏度 | |
---|---|
对称 | -0.5 到 0.5 |
中度正/负偏度 | -0.5 到 -1.0 以及 0.5 到 1.0 |
高度正/负偏度 | < -1.0 以及 > 1.0 |
将上面的图和表整合成如下一张图。
.样例
下面用三个图模拟一下对称、正偏和负偏三种情况,分别对应正态分布、指数分布和贝塔分布。
注意,紫色竖线表示平均值,蓝色竖线表示中位数。
4特征正态化
import pandas as pd
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
载入数据,发现有四个特征。
data = pd.read_csv('./data_to_transform.csv', encoding = "gbk")
data.shape
(10000,4)
# 把几个 object 改成 float64
for col in data.columns:
data[col] = pd.to_numeric(data[col], errors='coerce').astype('float64')
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10000 entries, 0 to 9999
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 中度正偏度 10000 non-null float64
1 高度正偏度 10000 non-null float64
2 中度负偏度 10000 non-null float64
3 高度负偏度 10000 non-null float64
dtypes: float64(4)
memory usage: 312.6 KB
计算偏度(和峰度),发现中度偏度和高度偏度各两个。
data.agg(['skew', 'kurtosis']).transpose()
skew | kurtosis | |
---|---|---|
中度正偏度 | 0.656308 | 0.584120 |
高度正偏度 | 1.271249 | 2.405999 |
中度负偏度 | -0.690244 | 0.790534 |
高度负偏度 | -1.201891 | 2.086863 |
分别绘制四个特征的直方图。
data.hist(grid=False, figsize=(10, 6), bins=30);
这里通过偏度和可视化的形式查看数据是否服从正态分布。当然也可以进行正态性的统计检验,例如 Shapiro-Wilks 等检验。
下面我们将开始转换上面四个非正态特征。首先,我们将先变换中等偏度的分布,然后再处理高偏度的数据。
.中度正偏 - 开方变换
对于中度正偏的特征,我们直接调用 np.sqrt
开根号伺候。
data.insert(len(data.columns), 'A_Sqrt', np.sqrt(data.iloc[:,0]))
# 查看变换前后的直方图
data[['中度正偏度', 'A_Sqrt']].hist(grid=False, figsize=(12, 5), bins=30);
# 查看变换以后的偏度值
data['A_Sqrt'].skew()
0.15247903034789928
.中度负偏
那么如何处理负(左)偏度数据呢?如果直接应用 np.sqrt
,那么现在,我们将收到 ValueError
。
我们用最大值减去各个值,然后再开平方根,就可以变换负(左)偏度的特征。
data.insert(len(data.columns), 'B_Sqrt',
np.sqrt(max(data.iloc[:, 2]+1) - data.iloc[:, 2]))
data.columns
Index(['中度正偏度', '高度正偏度', '中度负偏度', '高度负偏度', 'A_Sqrt', 'B_Sqrt'], dtype='object')
# 查看变换前后的直方图
data[['中度负偏度', 'B_Sqrt']].hist(grid=False, figsize=(12, 5), bins=30);
# 查看变换以后的偏度值。
data['B_Sqrt'].skew()
0.17976143295194769
.高度正偏
正偏度较大时,我们不再采用开方,而使用 log 对数变换。
data.insert(len(data.columns), 'C_log',
np.log(data['高度正偏度']))
# 查看变换前后的直方图
data[['高度正偏度', 'C_log']].hist(grid=False, figsize=(12, 5), bins=30);
# 查看变换以后的偏度值。
data['C_log'].skew()
0.3987799089368201
看着这个偏度值,似乎还可以再做一次。
.高度负偏
log 对数变换负偏度。
data.insert(len(data.columns), 'D_log',
np.log(max(data.iloc[:, 2] + 1) - data.iloc[:, 2]))
# 查看变换前后的直方图
data[['高度负偏度', 'D_log']].hist(grid=False, figsize=(12, 5), bins=30);
# 查看变换以后的偏度值。
data['D_log'].skew()
-0.3613244184552957
.Box-Cox 变换
除了 log 变换,还可以使用 Box-Cox 转换来对数据分布纠偏。从上面的开方变换和对数变换可以感觉到,不同偏度的数据应该使用不同的变换,那么我们能不能根据数据自动地选择变换函数呢?
我们用一个公式来统一上面两种函数,看公式,
这里当参数 时就对应开方,当参数 时就对应对数,这里将它单独列出来了。其实,上面第一个式子当参数 时的极限就是第二个式子(对数函数)。请看,当 时,
对于给定的数据,Box-Cox 变换的主要问题就是要估计出合适的参数值 。有了这个值就可以对数据作变换和反变换了。
调用 scipy 的 boxcox
至于参数值是如何从数据中估计的我们先不管它,接下来直接使用 SciPy 提供的 Box-Cox 来估计参数以及变换数据。
from scipy.stats import boxcox
# Box-Cox 变换
data.insert(len(data.columns), 'A_Boxcox', boxcox(data.iloc[:, 0])[0])
我们来验证一下,先取得 boxcox 返回的参数 ,然后代入 Box-Cox 变换函数。
lmd = boxcox(data.iloc[:, 1])[1]
(data.iloc[:, 1]**lmd-1)/lmd
0 0.812909
1 0.825921
2 0.826679
3 0.833058
4 0.835247
...
9995 1.457701
9996 1.459189
9997 1.468681
9998 1.475357
9999 1.480525
Name: 高度正偏度, Length: 10000, dtype: float64
# boxcox 返回的结果
boxcox(data.iloc[:, 1])[0]
array([0.81290868,
0.8259212,
0.82667884,
...,
1.46868052,
1.47535734,
1.48052514])
可以看到,结果是一致的。
# 查看各列的偏度
data.agg(['skew']).transpose()
skew | |
---|---|
中度正偏度 | 0.656308 |
高度正偏度 | 1.271249 |
中度负偏度 | -0.690244 |
高度负偏度 | -1.201891 |
A_Sqrt | 0.152479 |
B_Sqrt | 0.179761 |
C_log | 0.398780 |
D_log | -0.361324 |
A_Boxcox | 0.000155 |
我们从上表中可以看到,变换后的值的偏度值现在都在 0.5 以下。
# Box-Cox 变换
data.insert(len(data.columns), 'B_Boxcox', boxcox(data.iloc[:, 1])[0])
data[['中度正偏度', '高度正偏度', 'A_Sqrt', 'C_log', 'A_Boxcox', 'B_Boxcox']].hist(grid=False, figsize=(10, 15), bins=30);
可以看出来,在这个例子中, Box-Cox 变换比开根号和对数变换的纠偏效果更好。
接着,我们通过 QQ-plot 来检验一下原始数据、对数变换以及 Box-Cox 变换的正态性。
from scipy import stats
最后一个 Box-Cox 变换后的 QQ-plot 绘制如下,其他的类似。
stats.probplot(data['B_Boxcox'],dist='norm',plot=plt);
下面的图按原始数据、对数变换以及 Box-Cox 变换的顺序。
最后,如果你在训练一个机器学习的模型,那么 Box-Cox 的参数值应该从训练集里的数据估计而来,然后可以拿来对训练集之外的数据进行同样的变换。
# import modules
import numpy as np
from scipy import stats
from sklearn.model_selection import train_test_split
# plotting modules
import seaborn as sns
import matplotlib.pyplot as plt
# generate non-normal data
original_data = np.random.exponential(size = 1000)
# split into testing & training data
train,test = train_test_split(original_data, shuffle=False)
# transform training data & save lambda value
train_data,fitted_lambda = stats.boxcox(train)
# use lambda value to transform test data
test_data = stats.boxcox(test, fitted_lambda)
(二) 特征工程必要做的事
https://zhuanlan.zhihu.com/p/323209689
特征缩放
特征缩放的作用是消除特征不同尺度所造成的偏差。
- 标准化(某一特征)
- 归一化(某一特征)
偏度处理
偏度处理也是异常点处理,异常点会把数据的分布拉偏,这就好比二八法则,会把数据拉偏。
判断异常点来源
- 判断异常点的可靠性(错误或失误导致)
- 异常点和正常点是否来源于不同的生成机制
处理方式
- 异常点移除(总的数据量多的时候)
- 空间标识
- 先对同一样本的所有特征进行标准化,然后再单位化,这样处理可以把数据拉到高纬度空间的球面上。
- 去对数(某一特征)
- 求平方根(某一特征)
- 求倒数(某一特征)
缺失值处理
- 使用统计数据填充
- 使用算法填充
- K紧邻
- 线性回归
移除不重要的特征
- PCA(主成分分析)
- 移除不具有区分度的特征
- 该特征的某个特征的特征值出现的最高频率是次高频率的20倍以上(可删除)
(三) 机器学习特征工程经验总结一
https://blog.csdn.net/levy_cui/article/details/100557926
这块内容分为两篇文章,有一点长,但内容很实用,建议耐心的看一下。
机器学习特征工程经验总结一
机器学习特征工程经验总结二
--------------------------------------------
很多人其实非常好奇BAT里机器学习算法工程师平时工作内容是怎样?其实大部分人都是在跑数据,各种map-reduce,hive SQL,数据仓库搬砖,数据清洗、数据清洗、数据清洗,业务分析、分析case、找特征、找特征…而复杂的模型都是极少数的数据科学家在做。例如在阿里,算法工程师要挖掘业务场景,根据业务找出高效的特征,2周内可以完成一次特征迭代,一个月左右可以完成模型的小优化,来提升auc。因此特征很关键,腾讯、阿里他们得模型效果那么好多半归功于特征工程。
特征迭代的意思是把新造的、组合的、修正的特征加入模型中并且优化结果,这就是一次特征迭代。
工作中特征工程的流程一般是以下几点:
1. 数据采集
2. 数据清洗
3. 数据采样
4. 特征处理
5. 特征选择
而这篇总结核心主要在特征处理和特征选择上,特别是特征处理,除了对不同类型特征进行数据处理外,还有如何找到和构建组合特征。
数据采集
数据采集前需要明确采集哪些数据,一般的思路为:哪些数据对最后的结果预测有帮助?数据我们能够采集到吗?线上实时计算的时候获取是否快捷?
比如我要给用户做商品推荐,那我需要采集什么信息呢?
-店家:店铺的评分、店铺类别……
-商品:商品评分、购买人数、颜色、材质、领子形状……
-用户:历史信息(购买商品的最低价最高价)、消费能力、商品停留时间……
数据清洗
数据清洗在工作中也是很重要一步。数据清洗就是要去除脏数据。
那么如何判定脏数据呢?
简单属性判定:一个人身高3米+的人;一个人一个月买了10w的发卡。
组合或统计属性判定:号称在米国却ip一直都是大陆的新闻阅读用户?你要判定一个人是否会买篮球鞋,样本中女性用户85%?
补齐可对应的缺省值:不可信的样本丢掉,缺省值极多的字段考虑不用。
数据采样
采集、清洗过数据以后,正负样本是不均衡的,要进行数据采样。
既然总体中样本比例过低,很自然的思路就是从总体中重新抽样,提高建模样本中正样本的比例。
所谓正样本(positive samples)、负样本(negative samples),对于某一环境下的人脸识别应用来说,比如教室中学生的人脸识别,则教室的墙壁,窗户,身体,衣服等等便属于负样本的范畴。
过采样和欠采样是比较常用的方法,前者是增加正样本的数量,后者是减少负样本的数量。如果总体中正样本的绝对数量过少,可以将所有正样本全部纳入,再抽取部分负样本构建建模样本,这种思路其实就是过采样和欠采样的结合。
如果正样本量都很大,那么可以欠采样,也叫下采样
如果正样本数量不大就用采集更多的方法,比如
oversampling 过采样直接复制,还有smote算法,新生成更多的正样本。
#用index的方法去实现下采样和过采样
import numpy as np
import pandas as pd
#下采样/欠采样
def lower_sample_data(df, percent=1):
'''
percent:正样本与负样本的比例,比如0.6,正样本是6,
负样本是10
'''
data1 = df[df['Label'] == 1] # 将少数正样本
放在data1
data0 = df[df['Label'] == 0] # 将多数负样本
放在data0
index = np.random.randint(len(data0),
size=percent * len(data0))
# 随机给定下采样取出样本的序号
lower_data0 = data0.iloc[list(index)]
# 下采样
return(pd.concat([lower_data0, data1]))
#过采样
def over_sample_data(df, percent=1):
'''
percent:正样本与负样本的比例,比如0.6,正样本是6,
负样本是10
'''
data1 = df[df['Label'] == 1] # 将少数正样本
放在data1
data0 = df[df['Label'] == 0] # 将多数负样本
放在data0
lack_percent = percent - (len(data1)/len(data0))
index = np.random.randint(len(data1),
size=lack_percent * len(data1))
# 随机给定下采样取出样本的序号
over_data1 = data1.iloc[list(index)]
# 过采样
return(pd.concat([over_data1, data1,data0]))
---------------------
#用imblearn库去实现过采样,下采样和smote
#过采样
from sklearn.datasets import make_classification
from collections import Counter
X, y = make_classification(n_samples=5000,
n_features=2, n_informative=2,
n_redundant=0, n_repeated=0,
n_classes=3,n_clusters_per_class=1,
weights=[0.01, 0.05, 0.94],
class_sep=0.8, random_state=0)
Counter(y)
Out[10]: Counter({0: 64, 1: 262, 2: 4674})
from imblearn.over_sampling import RandomOverSampler
ros = RandomOverSampler(random_state=0)
X_resampled, y_resampled = ros.fit_sample(X, y)
sorted(Counter(y_resampled).items())
Out[13]:
[(0, 4674), (1, 4674), (2, 4674)]
#smote
from imblearn.over_sampling import SMOTE
X_resampled_smote, y_resampled_smote =
SMOTE().fit_sample(X, y)
sorted(Counter(y_resampled_smote).items())
Out[29]:
[(0, 4674), (1, 4674), (2, 4674)]
#下采样
和上面一样,用 imblearn.under_sampling的库
举一个数据不平衡的应用实战
本次分享的数据集来源于德国某电信行业的客户历史交易数据,该数据集一共包含条4,681记录,19个变量,其中因变量churn为二元变量,yes表示客户流失,no表示客户未流失;剩余的自变量包含客户的是否订购国际长途套餐、语音套餐、短信条数、话费、通话次数等。接下来就利用该数据集,探究非平衡数据转平衡后的效果。
# 导入第三方包
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn import model_selection
from sklearn import tree
from sklearn import metrics
from imblearn.over_sampling import SMOTE
# 读取数据
churn = pd.read_excel(r'C:\Users\Administrator\Desktop\Customer_Churn.xlsx')
# 中文乱码的处理
plt.rcParams['font.sans-serif']=['Microsoft YaHei']
# 为确保绘制的饼图为圆形,需执行如下代码
plt.axes(aspect = 'equal')
# 统计交易是否为欺诈的频数
counts = churn.churn.value_counts()
# 绘制饼图
plt.pie(x = counts,labels=pd.Series(counts.index).map({'yes':'流失','no':'未流失'}), autopct='%.2f%%')
# 显示图形
plt.show()
经过可视化发现流失用户仅占到8.3%,相比于未流失用户,还是存在比较大的差异的。可以认为两种类别的客户是失衡的,如果直接对这样的数据建模,可能会导致模型的结果不够准确。不妨先对该数据构建随机森林模型,看看是否存在偏倚的现象。
原始数据表中的state变量和Area_code变量表示用户所属的“州”和地区编码,直观上可能不是影响用户是否流失的重要原因,故将这两个变量从表中删除。除此,用户是否订购国际长途业务international_plan和语音业务voice_mail_plan,属于字符型的二元值,它们是不能直接代入模型的,故需要转换为0-1二元值。
# 数据清洗
# 删除state变量和area_code变量
churn.drop(labels=['state','area_code'], axis = 1, inplace = True)
# 将二元变量international_plan和voice_mail_plan转换为0-1哑变量
churn.international_plan = churn.international_plan.map({'no':0,'yes':1})
churn.voice_mail_plan = churn.voice_mail_plan.map({'no':0,'yes':1})
经过清洗后的干净数据,接下来对该数据集进行拆分,分别构建训练数据集和测试数据集,并利用训练数据集构建分类器,测试数据集检验分类器。
# 用于建模的所有自变量
predictors = churn.columns[:-1]
# 数据拆分为训练集和测试集
X_train,X_test,y_train,y_test =
model_selection.train_test_split(churn[predictors], churn.churn, random_state=12)
# 构建决策树
dt = tree.DecisionTreeClassifier(n_estimators = 300)
dt.fit(X_train,y_train)
# 模型在测试集上的预测
pred = dt.predict(X_test)
# 模型的预测准确率
print(metrics.accuracy_score(y_test, pred))
# 模型评估报告
print(metrics.classification_report(y_test, pred))
如上结果所示,决策树的预测准确率超过93%,其中预测为no的覆盖率recall为97%,但是预测为yes的覆盖率recall却为62%,两者相差甚远,说明分类器确实偏向了样本量多的类别(no)。
# 绘制ROC曲线
# 计算流失用户的概率值,用于生成ROC曲线的数据
y_score =
dt.predict_proba(X_test)[:,1]
fpr,tpr,threshold = metrics.roc_curve(y_test.map({'no':0,'yes':1}), y_score)
# 计算AUC的值
roc_auc = metrics.auc(fpr,tpr)
# 绘制面积图
plt.stackplot(fpr, tpr, color='steelblue', alpha = 0.5, edgecolor = 'black')
# 添加边际线
plt.plot(fpr, tpr, color='black', lw = 1)
# 添加对角线
plt.plot([0,1],[0,1], color = 'red', linestyle = '--')
# 添加文本信息
plt.text(0.5,0.3,'ROC curve (area = %0.3f)' % roc_auc)
# 添加x轴与y轴标签
plt.xlabel('1-Specificity') plt.ylabel('Sensitivity')
# 显示图形
plt.show()
如上图所示,ROC曲线下的面积为0.795,AUC的值小于0.8,故认为模型不太合理。(通常拿AUC与0.8比较,如果大于0.8,则认为模型合理)。接下来,利用SMOTE算法对数据进行处理。
# 对训练数据集作平衡处理
over_samples = SMOTE(random_state=1234)
over_samples_X,over_samples_y =
over_samples.fit_sample(X_train, y_train)
# 重抽样前的类别比例
print(y_train.value_counts()/len(y_train))
# 重抽样后的类别比例
print(pd.Series(over_samples_y).value_counts()/
len(over_samples_y))
如上结果所示,对于训练数据集本身,它的类别比例还是存在较大差异的,但经过SMOTE算法处理后,两个类别就可以达到1:1的平衡状态。下面就可以利用这个平衡数据,重新构建决策树分类器了。
# 基于平衡数据重新构建决策树模型
dt2 = ensemble.DecisionTreeClassifier(n_estimators = 300)
dt2.fit(over_samples_X,over_samples_y)
# 模型在测试集上的预测
pred2 =dt2.predict(np.array(X_test))
# 模型的预测准确率
print(metrics.accuracy_score(y_test, pred2))
# 模型评估报告
print(metrics.classification_report(y_test, pred2))
如上结果所示,利用平衡数据重新建模后,模型的准确率同样很高,为92.6%(相比于原始非平衡数据构建的模型,准确率仅下降1%),但是预测为yes的覆盖率提高了10%,达到72%,这就是平衡带来的好处。
# 计算流失用户的概率值,用于生成ROC曲线的数据
y_score = rf2.predict_proba(np.array(X_test))[:,1]
fpr,tpr,threshold = metrics.roc_curve(y_test.map({'no':0,'yes':1}), y_score)
# 计算AUC的值
roc_auc = metrics.auc(fpr,tpr)
# 绘制面积图
plt.stackplot(fpr, tpr, color='steelblue', alpha = 0.5, edgecolor = 'black')
# 添加边际线
plt.plot(fpr, tpr, color='black', lw = 1)
# 添加对角线
plt.plot([0,1],[0,1], color = 'red', linestyle = '--')
# 添加文本信息
plt.text(0.5,0.3,'ROC curve (area = %0.3f)' % roc_auc)
# 添加x轴与y轴标签
plt.xlabel('1-Specificity') plt.ylabel('Sensitivity')
# 显示图形 电动叉车
plt.show()
这篇是展示平时工作中如何做数据清理和预处理。
一般数据清理和预处理的流程是:
数据加载与粗略查看
处理丢失的数据
处理离群点
数据统计
特征值的合并、连接
数据转换、标准化、归一化
去除常变量
下面会拿热门的铁达尼号等数据做示范:
1.数据加载鱼粗略查看
在pandas读进来数据一个train后,train的格式为DataFrame,调用下面的几个方法就可以大致了解我们得到的数据是什么,有什么特征值,特征值的数据类型是什么,如果是数值那么最大最小值是什么等。
train.head(5) #显示前5行数据
train.tail(5) #显示后5行
train.columns #查看列名
train.info() #查看各字段的信息
train.shape #查看数据集行列分布,几行几列
train.describe() #查看数据的大体情况
2.处理丢失数据(缺失值)
2.1 找到丢失的位置
输出每个列丢失值也即值为NaN的数据和,并从多到少排序。
total = train.isnull().sum().sort_values(ascending=False)
print(total)
Cabin 687
Age 177
Embarked 2
Fare 0
Ticket 0
Parch 0
SibSp 0
Sex 0
Name 0
Pclass 0
Survived 0
PassengerId 0
也可以输出百分比:
percent =(train.isnull().sum()/train.isnull().count()).sort_values(ascending=False)
missing_data = pd.concat([total, percent], axis=1, keys=['Total', 'Percent'])
missing_data.head(20)
由此可以看到‘Cabin’的缺失数量最多,‘Embarked’最少。
2.2 缺失值处理
对缺失数据的处理我们有很多方法:
使用可用特征的均值/中位值/众数来填补缺失值(这是从列角度去处理缺失值);
使用特殊值来填补缺失值,如-1;
忽略有缺失值的样本(缺失的数目很多);
使用相似样本的均值添补缺失值(这时从行角度去处理缺失值);
使用另外的机器学习算法预测缺失值。
2.2.1 填补:
#使用出现次数最多的值填补
train['Embarked'] = train['Embarked'].fillna('S')
train.product_type[train.product_type.isnull()]=train.product_type.dropna().mode().values
#使用中位数填补
train['Fare'] = train['Fare'].fillna(train['Fare'].median())
使用平均数填补
train['Age'] = train['Age'].fillna(train['Age'].mean())
train['LotFrontage'].fillna(train['LotFrontage'].mean())
2.2.2 去除缺失值由于缺失数量太多
根据情况可以选择忽略这一特征的列和忽略出现缺失的那几行。
通常后者出现在缺失的行数比较少的情况下。
#去掉一列
train = train.drop(['Cabin'], axis = 1)
#去掉这个特征为空的行
#当然后面可以加上inplace=True表示直接就在内存中替换了
train_new = train.drop(train[train['Embarked'].isnull()].index)
2.3 处理离群点
2.3.1查找离群点
将数据可视化(python中的pyplot),观察异常值,引用HousePrice中的一个例子:
#bivariate analysis saleprice/grlivarea
var = 'GrLivArea'
data = pd.concat([df_train['SalePrice'], df_train[var]], axis=1)
data.plot.scatter(x=var, y='SalePrice', ylim=(0,800000));
可以发现,有四个点在非常偏离的位置,先讨论上面两个偏移位。
在下方无数点形成非常好的线性关系,那这偏移很大的两点是异常值吗?不是,虽然偏离位置很远,但是与下面无数点形成的线性关系还是拟合的。
再看看右下两个值,显然它们偏离得没有道理,删!
2.3.2 离群点处理
删除离群点
train.sort_values(by = 'GrLivArea', ascending = False)[:2]
train= train.drop(train[train['Id'] == 1299].index)
train= train.drop(train[train['Id'] == 524].index)
保留偏离值
当然并不是所有的偏离值都需要删除,具体需要在分析之后选择处理方式。这里将偏离值保留下来并不是原封不动保留,而需要做标准化或归一化处理,具体的处理方式会在下篇特征处理说明。
除此之外,还有一种方法去除离群点。
我们还可以采用了另外一种简单有效的方法:
在原始数据上训练 xgboost,用得到的 xgb 模型输出特征的重要性,取最重要的前 20 个特征,统计每个样本在这 20 个特征上的缺失值个数,将缺失值个数大于 10 的样本作为离群点。
2.4 数据统计
#统计某一列中各个元素值出现的次数
train['MSSubClass'].value_counts()
#列出数据的偏斜度
train['MSSubClass'].skew()
#列出数据的峰度
train['MSSubClass'].kurt()
#计算两个列的相关度
train['LotFrontage'].corr(train['LotArea'])
#观察两个列的值的二维图
x = 'GrLivArea';y = 'SalePrice'
data = pd.concat([train[y], train[x]], axis=1)
data.plot.scatter(x=x, y=y, ylim=(0,800000));
#这里800000为y的最大值
#计算所有特征值每两个之间的相关系数,并作图表示。
corrmat = train.corr()#得到相关系数
f,ax = plt.subplots(figsize = (12,9))
sns.heatmap(corrmat, vmax = .8, square = True)#热点图
#取出相关性最大的前十个,做出热点图表示
k = 10
#number of variables for heatmap
cols = corrmat.nlargest(k, 'SalePrice')['SalePrice'].index
cm = np.corrcoef(train[cols].values.T)
sns.set(font_scale=1.25)
hm = sns.heatmap(cm, cbar=True, annot=True, square=True, fmt='.2f', annot_kws={'size': 10}, yticklabels=cols.values,xticklabels=cols.values)
plt.show()
#就可以查看到相关性有多大
2.5 特征值合并和连接
在合并连接之前,我们需要了解pandas.groupby这个分组方法,因为很多时候我们是在从几个特征值里挖掘一些值来当作新的特征值,这样子我们这个分组的方法就显得尤为重要了,如按照同一个用户进行分组来计算这个用户的行为次数当作新的特征值等等
#按照用户分组---------------------一个特征值
train.groupby('userid',as_index=False)
#按照用户与目的地分组---------------两个特征值
train.groupby(['userid','end_loc'],as_index=False)
#用户、起点、目的地-----------------三个特征值
train.groupby(['userid','start_loc','end_loc'],as_index=False)
#跟MSSubClass进行分组,并求分组后的平均值
train[['MSSubClass', 'LotFrontage']].groupby(['MSSubClass'], as_index=False).mean()
#选取特定的属性的某个值然后进行分类
train[train['date']=='2017-1-2'].groupby(['userid'],as_index=False)
#获得分组后,统计分组中'end_loc'的数量返回为一列由‘userid’和‘user_count’组成的新的DataFrame
user_count = train.groupby('userid',as_index=False)['end_loc'].agg({'user_count':'count'})
#将获得的新的DataFrame合并到train,更多的merge参数请查阅文档
train= pd.merge(train,user_count,on=['userid'],how='left')
user_eloc_count = train.groupby(['userid','end_loc'],as_index=False)['userid'].agg({'user_eloc_count':'count'})
train= pd.merge(train,user_eloc_count,on=['userid','end_loc'],how='left')
还有许多方法构造新特征,后面会有篇写这个特征处理。
#讲训练数据与测试数据连接起来,以便一起进行数据清洗。
#这里需要注意的是,如果没有后面的ignore_index=True,
那么index的值在连接后的这个新数据中是不连续的,
如果要按照index删除一行数据,可能会发现多删一条。
merge_data=pd.concat([train,test],ignore_index=True)
2.6 数据转换,标准化和归一化
2.6.1 数据转换
数据类型转化(例如浮点数转为整数)
字符串数值化(序数编码,独热编码)
数值型数据离散化
字符大小写转换(例如_QQ和_qQ,都是同一个字符,一般统一转换成大小写)
空格符处理(例如“中国”和“中国 ”,将空格符去除)
城市名处理(例如“北京”和“北京市”,属于同一个城市,将市去掉)
#浮点型数值转换为整型
train['Age']=train['Age'].astype(int)
#字符串的替换--映射
#将字符串数值化
train['MSZoning']=train['MSZoning'].map({'RL':1,'RM':2,'RR':3,}).astype(int)
train['Embarked'] = train['Embarked'].map( {'S': 0, 'C': 1, 'Q': 2} ).astype(int)
#一般建议将map拿出来
title_mapping = {"Mr": 1, "Miss": 2, "Mrs": 3, "Master": 4, "Rare": 5}
train['Title'] = train['Title'].map(title_mapping)
train['Title'] = train['Title'].fillna(0)
#将字符串特征列中的内容分别提出来作为新的特征出现,表现为0、1。
train= pd.get_dummies(houseprice)
#将连续型特征值分块,每一块用数字标识
train.loc[ train['Fare'] <= 7.91, 'Fare'] = 0
train.loc[(train['Fare'] > 7.91) & (train['Fare'] <= 14.454), 'Fare'] = 1
train.loc[(train['Fare'] > 14.454) & (train['Fare'] <= 31), 'Fare'] = 2
train.loc[ train['Fare'] > 31, 'Fare'] = 3
train['Fare'] = train['Fare'].astype(int)
下面这个数值转换是将数值进行log计算,使分布的数值显常态
train['SalePrice'] = np.log(train['SalePrice'])
而有时这样的log不可行,就需要使用log(x+1)来 处理.
train["SalePrice"] = np.log1p(train["SalePrice"])
#将偏斜度大于0.75的数值列log转换,使之尽量符合正态分布。
numeric_feats = train[train.dtypes != 'object'].columns
skewed_feats =
train[numeric_feats].apply(lambda x: skew(x.dropna())) #compute skewness
skewed_feats = skewed_feats[skewed_feats > 0.75]
skewed_feats = skewed_feats.index
all_data[skewed_feats] = np.log1p(all_data[skewed_feats])
2.6.2 数据标准化和归一化(Standardization、Normalization)
标准化归一化会在后面说,实际使用时最主要的还是要了解什么时候需要标准化,什么时候用归一化,还需要清楚当前数据适合什么标准化方式等等。
在sklearn.preprocessing 介绍的标准化方式有:
preprocessing.scale()、preprocessing.StandardScaler(),使数据集呈现标准正态分布,即mean = 0,且标准差std = 1。
MinMaxScaler 、MaxAbsScaler,前者使数据集分布在[0,1],后者分布在[-1,1]。这种方式通常在(1) 特征的标准差较小 (2) 可以使稀疏数据集中的0值继续为0,这两种情况下使用。
preprocessing.QuantileTransformer(),将数据映射到[0,1]之间均匀分布,会破坏原数据之间的相关特性。
归一化方式:preprocessing.normalize(),将样本缩放成单位向量,(1)需要使用二次方程,比如点积或者其他核方法计算样本对之间的相似性(2)常用于文本分类和内容聚类的向量空间模型的基础。
2.7 去除常变量
常变量对于因变量来说都一样,没什么几乎大的变化,所以相关性不是很强。可以通过计算每个数值型特征的标准差,剔除部分变化很 小的特征。
比如下表列出的 几个个特征是标准差接近于 0 的,我们剔除了这些特征。
参考:
https://blog.csdn.net/weixin_42736194/article/details/83045400
https://blog.csdn.net/weixin_42736194/article/details/83046959
————————————————
版权声明:本文为CSDN博主「levy_cui」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/levy_cui/article/details/100557926