用KNN和GBDT判断美国某区域居民年收入
读取数据
import numpy as np
import pandas as pd
#读取数据
data=pd.read_excel("F:\income.xlsx")
#预览数据前五行
data.head()
对某区域居民的人口普查
#查看各变量类型
data.info()
int为数值型,object为离散型
workclass为工作类型,fnlwgt为序号,relationship为家庭成员关系,race为种族,capital-gain为资本收益,native-country为国籍,因变量为需要预测居民的年收入是否超过5万美元
数据预处理
重复观测,缺失值,异常值,对字符型数据数值转换
#查看缺失值并合计个数
data.isnull().sum(axis = 0)
有三个离散型变量有缺失且数目比较多,用众数替换
#使用各个离散型变量的众数来替换缺失值
data.fillna(value = {'workclass':data.workclass.mode()[0],
'occupation':data.occupation.mode()[0],
'native-country':data['native-country'].mode()[0]}, inplace = True)
#再次查看缺失值并合计个数
data.isnull().sum(axis = 0)
探索性分析
#数值型变量统计描述
data.describe()
#离散型变量统计描述
data.describe(include=["object"])
以受教育水平为例,一共有16种不同的水平,3万多居民中,高中毕业的最多,有10501名
针对数值型变量,描述分布,以年龄为例,观测分布,确定峰度,偏度
#分布形状
#绘制不同收入水平下的年龄核密度图
import seaborn as sns
import matplotlib.pyplot as plt
#设置绘图风格
plt.style.use("ggplot")
# 中文乱码和坐标轴负号的处理
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
# 取出收入<=50k
Age1 = data.age[data.income == ' <=50K']#<=之前要空一格
# 取出收入>50k
Age2 = data.age[data.income == " >50K"]
# 绘制收入<=50k的年龄的核密度图
sns.distplot(Age1, hist = False, kde_kws = {'color':'red', 'linestyle':'-'},
norm_hist = True, label = '<=50K')
# 绘制收入>50k的年龄的核密度图
sns.distplot(Age2, hist = False, kde_kws = {'color':'black', 'linestyle':'--'},
norm_hist = True, label = '>50K')
plt.title('不同收入水平下的年龄核密度图')
# 显示图例
plt.legend()
# 显示图形
plt.show()
在不同的收入水平下,年龄的核密度分布图表明,对于年收入超过5万美元的居民,几乎呈现正态分布,而收入低于5万美元的居民,年龄呈现右偏分布,即年龄偏大的居民人数比年龄偏下的人数多
同样,针对离散型变量,以对比居民的收入水平高低在种族之间的差异为例,可以发现这些离散型变量是否影响收入水平
data.groupby(by = ['race','income']).aggregate(np.size)
data.groupby(by = ['race','income']).aggregate(np.size).loc[:,'age']
# 构造不同收入水平下各种族人数的数据
race = pd.DataFrame(data.groupby(by = ['race','income']).aggregate(np.size).loc[:,'age'])
race
# 重设行索引
race = race.reset_index()
race
# 变量重命名
race.rename(columns={'age':'counts'}, inplace=True)
# 排序
race.sort_values(by = ['race','counts'], ascending=False, inplace=True)
race
# 设置图框比例,并绘图
plt.figure(figsize=(9,5))
sns.barplot(x="race", y="counts", hue = 'income', data=race)
plt.show()
在相同的种族下,居民年收入高低的人数差异,在白种人中,年收入低于5万和高于5万的居民比例大致为3;1
数据建模
对离散变量重编码,这样的字符型变量不可直接建模,需要重编码
# 离散变量的重编码
for feature in data.columns:
if data[feature].dtype == 'object':
data[feature] = pd.Categorical(data[feature]).codes
data.head()
所有的字符型变量都变成了整数型变量
又教育水平和教育时长产生信息冗余,且序号fnlwgt无意义,故删除变量
# 删除变量
data.drop(['education','fnlwgt'], axis = 1, inplace = True)
data.head()
拆分数据集
# 导入第三方包
from sklearn import model_selection
predictors = data.columns[:-1]
# 将数据集拆分为训练集和测试集,且测试集的比例为25%
X_train, X_test, y_train, y_test = model_selection.train_test_split(data[predictors], data.income,
test_size = 0.25, random_state = 1234)
print('训练数据集共有%d条观测' %X_train.shape[0])
print('测试数据集共有%d条观测' %X_test.shape[0])
sklearn模块提供了网格搜索法完成模型的参数选择
KNN模型
# K近邻模型的网格搜索法
# 导入k近邻模型的类
from sklearn.neighbors import KNeighborsClassifier
# 导入网格搜索法的函数
from sklearn.grid_search import GridSearchCV
# 选择不同的参数
k_options = list(range(1,12))
parameters = {'n_neighbors':k_options}
# 搜索不同的K值
grid_kn = GridSearchCV(estimator = KNeighborsClassifier(), param_grid = parameters, cv=10, scoring='accuracy', verbose=0, n_jobs=2)
grid_kn.fit(X_train, y_train)
print(grid_kn)
# 结果输出
grid_kn.grid_scores_, grid_kn.best_params_, grid_kn.best_score_
estimator接受一个指定的模型,param_grid指定模型需要搜索的参数列表对象,cv为x重交叉验证,scoring指定模型评估的度量值
第一部分为11种K值下的平均准确率(因为是10重交叉验证),第二部分选择了最佳的K值,第三部分是当为最佳K值时,模型的最佳平均准确率
评估模型,对于预测的连续变量为MSE和RMSE;对于预测的分类变量为混淆矩阵中的准确率,ROC曲线下的面积AUC,K-S值
# 导入模型评估模块
from sklearn import metrics
# 预测测试集
grid_kn_pred = grid_kn.predict(X_test)
print(pd.crosstab(grid_kn_pred, y_test))
# 模型得分
print('模型在训练集上的准确率%f' %grid_kn.score(X_train,y_train))
print('模型在测试集上的准确率%f' %grid_kn.score(X_test,y_test))
# 绘制ROC曲线
fpr, tpr, _ = metrics.roc_curve(y_test, grid_kn.predict_proba(X_test)[:,1])
plt.plot(fpr, tpr, linestyle = 'solid', color = 'red')
plt.stackplot(fpr, tpr, color = 'steelblue')
plt.plot([0,1],[0,1], linestyle = 'dashed', color = 'black')
plt.text(0.6,0.4,'AUC=%.3f' % metrics.auc(fpr,tpr), fontdict = dict(size = 18))
plt.show()
大于0.8,模型合理
GBDT模型
# 导入网格搜索法的函数
from sklearn.grid_search import GridSearchCV
# 导入GBDT模型的类
from sklearn.ensemble import GradientBoostingClassifier
from sklearn import ensemble
# 运用网格搜索法选择梯度提升树的合理参数组合
learning_rate = [0.01,0.05,0.1] #模型的学习速率
n_estimators = [100,300,500] #生成的基础决策树的个数
max_depth = [3,5,7,9] #每个基础决策树的最大深度
params = {'learning_rate':learning_rate,'n_estimators':n_estimators,'max_depth':max_depth}
gbdt_grid = GridSearchCV(estimator = ensemble.GradientBoostingClassifier(),
param_grid= params, scoring = 'roc_auc', cv = 10, n_jobs = 4)
gbdt_grid.fit(X_train,y_train)
# 返回参数的最佳组合和对应AUC值
gbdt_grid.best_params_, gbdt_grid.best_score_
最佳模型学习率为0.05,生成的基础决策树个数为300棵,并且每颗基础决策树的最大深度为5,这样的组合可以使GBDT模型的平均准确率达到93.02%
# 导入模型评估模块
from sklearn import metrics
# 预测测试集
gbdt_grid_pred = gbdt_grid.predict(X_test)
print(pd.crosstab(gbdt_grid_pred, y_test))
# 模型得分
print('模型在训练集上的准确率%f' %gbdt_grid.score(X_train,y_train))
print('模型在测试集上的准确率%f' %gbdt_grid.score(X_test,y_test))
# 绘制ROC曲线
fpr, tpr, _ = metrics.roc_curve(y_test, gbdt_grid.predict_proba(X_test)[:,1])
plt.plot(fpr, tpr, linestyle = 'solid', color = 'red')
plt.stackplot(fpr, tpr, color = 'steelblue')
plt.plot([0,1],[0,1], linestyle = 'dashed', color = 'black')
plt.text(0.6,0.4,'AUC=%.3f' % metrics.auc(fpr,tpr), fontdict = dict(size = 18))
plt.show()
模型在训练集上的准确率达到94%,在测试集上的准确率也达到92%,AUC的值高达0.921,远远超过0.8。
集成算法GBDT的表现比KNN要优秀,因为这是基于多棵决策树进行投票的优点。
总之,无论是KNN还是GBDT,都可以通过网格搜索法找到各自的最佳模型参数,而且这些最佳参数的组合一般都会使得模型比较优秀和健壮、
在纵向比较默认参数的模型和网格搜索后的最佳参数模型,后者一般比较好;在横向比较单一模型和集成模型,后者也一般表现更优秀。