机器学习笔记-3.3 数据清洗(特征工程)

本系列是笔者在贪心科技-机器学习教程的学习笔记, 补充有python相关的知识.初学机器学习, 还请多多指教.

本章节是3.2 逻辑回归的延续. 主要介绍面对未处理的数据做数据清洗的简单思路, 以及用到的一些python工具使用总结.

数据源:来自UCI机器学习库的葡萄牙银行电话营销数据集, 它是该银行收集的一批客户信息, 数据特征如下:

[‘age’, ‘job’, ‘marital’, ‘education’, ‘default’, ‘housing’, ‘loan’, ‘contact’, ‘month’, ‘day_of_week’, ‘duration’, ‘campaign’, ‘pdays’, ‘previous’, ‘poutcome’, ’emp_var_rate’, ‘cons_price_idx’, ‘cons_conf_idx’, ‘euribor3m’, ‘nr_employed’, ‘y’]

每个特征的定义这里不赘述, 后面会针对部分做解释, 完整请移步数据集的定义.本节我们将利用此数据集, 基于逻辑回归做预测, 预测的内容是客户是否会在银行做定期存款业务, 即数据集中的特征’y’, 0表示否定, 1为肯定.

开始, 第一步是导入数据集

# 引入依赖的包, 并对绘图工具plt 和 sns做一些配置
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn import preprocessing
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
plt.rc("font", size=14)
sns.set(style="white")
sns.set(style="whitegrid", color_codes=True)

data = pd.read_csv('banking.csv', header=0)
# 移除空数据, 可以通过参数指定移除规则, 本数据集无空值
data = data.dropna()

观察数据

面对手中的大量数据, 首先我们介绍几种观察数据的工具和方法, 了解数据才能处理数据.

pandas.DataFrame.loc[]

此API支持对df做筛选过滤排序等操作.

按单列查询

data.loc[data['y'] == 1]
# 筛选出y=1的数据

按多列查询

data.loc[(data['y'] == 1) & (data['job'] != 'retired')]
# 筛选y=1且job='retired'的数据

求和&最小值&最大值&etc.

data.loc[data['y'] == 1]['age'].sum()
# 对age列求和
data.loc[data['y'] == 1]['age'].min()
# 对age列求最大值
data.loc[data['y'] == 1]['age'].max()
# 对age列求最小值

pandas.DataFrame.groupby()

如方法名, 分组方法, 返回的对象是DataFrameGroupBy类型

dd = data.groupby('y')
# 按照y特征分类
dd = data.groupby(['y', 'job'])
# 按照y,job两个特征一起分类

通过groupby获取到DataFrameGroupBy对象之后, 它支持常用的数学操作包括min, max, mean, median, std(方差)等操作, 注意这些方法均会自动筛选支持的类型, 比如mean方法, 不支持字符串类型, 则会把这种类型的列移除.

pandas.crosstab()

crosstab可以绘制交叉表, 比如下面这个示例, 以job特征分类, 再对y特征进行求和

table = pd.crosstab(data.job, [data.y])
print(table)
# y                 0     1
# job
# admin.         9070  1352
# blue-collar    8616   638
# entrepreneur   1332   124
# housemaid       954   106
# management     2596   328
# retired        1286   434
# self-employed  1272   149
# services       3646   323
# student         600   275
# technician     6013   730
# unemployed      870   144
# unknown         293    37

# 做除法运算, 得到比例
table = table.div(table.sum(1).astype(float), axis=0)
# 绘图
table.plot(kind='bar', stacked=True)
plt.title('Stacked Bar Chart of Job title vs Purchase')
plt.xlabel('Job')
plt.ylabel('Proportion of Purchase')
plt.show()
交叉表的方式展示每个职业中办理业务(橙色)与未办理(蓝色)比例
从图中可以看出不同职业办理业务的比例还是相差很远, 是一个比较理想的预测因素

类似可以选出以下几个有效的预测特征:

岁数特征(这里每五岁作为一个梯度分类),
很明显青少年<=20岁和老人>=60岁购买办理业务的比例比较高
回访次数, 次数低的反而成交率高. 似乎成交的都在头几次, 反复回访并不会导致成交率上升.
是否已有信用卡, 有信用卡的都未办理, 也许是因为业务规则导致有信用卡的必然有定期存款业务?
通话时长, 按5分钟梯度划分. 比较明显的规律是通话时间长的成交率比较高.

One-Hot编码

# 以下几个特征为类别特征, 我们对其做ont-hot编码
cat_vars = ['job', 'marital', 'education', 'default', 'housing', 'loan', 'contact', 'month', 'day_of_week', 'poutcome']
for var in cat_vars:
    # one-hot编码, prefix指定列名前缀
    cat_list = pd.get_dummies(data[var], prefix=var)
    # 将one-hot编码的新列join到data中
    data = data.join(cat_list)
# 移除原类别特征, drop方法通过axis指定行(axis=0)指定列(axis=1)
data_final = data.drop(cat_vars, axis=1)

SMOTE过采样

然后我们再回来观察下label特征y,

print(data['y'].value_counts())
# 0    36548
# 1     4640
sns.countplot(x='y', data=data, palette='Set2')
plt.show()

可以看到存在大量未办理业务的数据. 因为垃圾进垃圾出原理, 如果我们的测试数据严重不平衡地倾向某个特征, 那么最终预测结果也多会严重倾向, 所以我们要对不平衡的数据做一些处理, 使得测试数据在预测维度y上相对平衡.

比如SOMTE(Synthetic Minority Oversampling Technique, 合成少数类过采样技术)就可以通过算法合成样本来创造少数类(本例中就是未办理业务的)样本. 它的简单使用如下:

from imblearn.over_sampling import SMOTE
os = SMOTE(random_state=0)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)
columns = X_train.columns

# 使用SMOTE创造少数类样本, 但是注意这里创造的只是训练集, 绝对不能修改测试集
os_data_X,os_data_y=os.fit_sample(X_train, y_train)
os_data_X = pd.DataFrame(data=os_data_X,columns=columns )
os_data_y= pd.DataFrame(data=os_data_y,columns=['y'])

# we can Check the numbers of our data
print("过采样以后的数据量: ",len(os_data_X))
print("未开户的用户数量: ",len(os_data_y[os_data_y['y']==0]))
print("开户的用户数量: ",len(os_data_y[os_data_y['y']==1]))
print("未开户的用户数量的百分比: ",len(os_data_y[os_data_y['y']==0])/len(os_data_X))
print("开户的用户数量的百分比: ",len(os_data_y[os_data_y['y']==1])/len(os_data_X))
# 过采样以后的数据量:  51134
# 未开户的用户数量:  25567
# 开户的用户数量:  25567
# 未开户的用户数量的百分比:  0.5
# 开户的用户数量的百分比:  0.5

最后使用LogisticRegression训练模型, 不过是参数代入, 很简单.

logreg = LogisticRegression().fit(os_data_X, os_data_y.values.reshape(-1))

print('在测试数据集上面的预测准确率: {:.2f}'.format(logreg.score(X_test, y_test)))
y_pred = logreg.predict(X_test)

# 打印模型质量报告
print(classification_report(y_test, y_pred))

#               precision    recall  f1-score   support
#
#            0       0.98      0.85      0.91      3674
#            1       0.41      0.87      0.56       445
#
#     accuracy                           0.85      4119
#    macro avg       0.70      0.86      0.74      4119
# weighted avg       0.92      0.85      0.87      4119

发表评论

电子邮件地址不会被公开。

7 + 2 =