目录
一、研究意义
二、数据集介绍
样本属性及含义
三、数据预处理
(1)模型选择
(2)数据导入
(3)数据删除与清洗
(4)数据转化
(5)重点部分
(6)划分训练集与测试集
四、两种模型对上述数据集进行预测
决策树
朴素贝叶斯
五、结果分析
如果我们知道对收入高低起决定性的因素,或者哪些因素组合在一起有着能够增大收入 的可能性,那么这样可以帮助很多人少走弯路,朝着正确的方向努力,早日达到目标。 就像许多传授给我们人生智慧的书籍一样,我们的目标是探寻一种影响人收入的条件体系并在以后的日子里继续对这套体系做出完善,以求能找到富裕的秘诀。
Adult数据集(即“人口普查收入”数据集),由美国人口普查数据集库 抽取而来,其中共包含48842条记录,年收入大于50k美元的占比23.93%,年收入小于50k美元的占比76.07%,并且已经划分为训练数据32561条和测试数据16281条。 该数据集类变量为年收入是否超过50k美元,属性变量包括年龄、工种、学历、职业等 14类重要信息,其中有8类属于类别离散型变量,另外6类属于数值连续型变量。该数据集是一个分类数据集,用来预测年收入是否超过50k美元。
通过对“Adult”数据集的观察,发现在一些属性上的缺失值较多,并且14个样本属性中既有连续型变量也有离散型变量,又因为“Adult”数据集属于分类数据集,综合考虑下, 选取决策树算法与贝叶斯算法作为主要预测方式。
补充说明:决策树算法计算比较简单,解释性强,比较适合处理有缺失属性值的数据样本。贝叶斯算法源于古典数学理论,有着坚实的数学基础,分类效率稳定,同样算法比较简单,对缺失数据不太敏感。
#将下载的数据集adult.data和adult.test存在data文件夹中
train_data = 'data/adult.data'
test_data = 'data/adult.test'
#通过pandas包中read_csv方法,给每一列加上属性名
columns = ['Age','Workclass','fnlgwt','Education','EdNum','MaritalStatus',
'Occupation','Relationship','Race','Sex','CapitalGain',
'CapitalLoss','HoursPerWeek','Country','Income']
df_train_set = pd.read_csv(train_data, names=columns)
#因为第一行是无用数据,所以跳过
df_test_set = pd.read_csv(test_data, names=columns, skiprows=1)
# 因为fnlgwt属性记录的是人口普查员的ID,对预测结果无影响,故删除该列
df_train_set.drop('fnlgwt', axis=1, inplace=True)
df_test_set.drop('fnlgwt', axis=1, inplace=True)
#进行数据清洗,将数据集中‘?’字符替换为‘Unknown’
for i in df_train_set.columns:
df_train_set[i].replace('?', 'Unknown', inplace=True)
df_test_set[i].replace('?', 'Unknown', inplace=True)
#去掉非int64类型数据中的点和空格,以提高算法精度
for col in df_train_set.columns:
if df_train_set[col].dtype != 'int64':
df_train_set[col] = df_train_set[col].apply(lambda val: val.replace(" ", ""))
df_train_set[col] = df_train_set[col].apply(lambda val: val.replace(".", ""))
df_test_set[col] = df_test_set[col].apply(lambda val: val.replace(" ", ""))
df_test_set[col] = df_test_set[col].apply(lambda val: val.replace(".", ""))
#Education(受教育程度)和Ednum(受教育时间)特征相似,为减少干扰因素, 删除Education属性;除此之外,Country对年收入的影响也不大,故同样删除
df_train_set.drop(["Country", "Education"], axis=1, inplace=True)
df_test_set.drop(["Country", "Education"], axis=1, inplace=True)
#将数据的表头转换为列表形式并储存在colnames变量中,移除原有的Age和EdNum属性,同时添加新的AgeGroup和EduGroup属性
colnames = list(df_train_set.columns)
colnames.remove('Age')
colnames.remove('EdNum')
colnames = ['AgeGroup', 'EduGroup']+colnames
# 转化Age(年龄)和EdNum(受教育时间)列,将连续数值型转换为更高效的方式,
此处将年龄转换为10的整数倍,受教育时间转换为5的整数倍
# 这里利用了format方式快速创建字符型列表
labels = ["{0}-{1}".format(i, i+9) for i in range(0,100,10)]
# 调用pandas包中的pd方法将数据切分为离散的区间并打上标签
df_train_set['AgeGroup'] = pd.cut(df_train_set.Age, range(0,101,10), right = False, labels = labels)
df_test_set['AgeGroup'] = pd.cut(df_test_set.Age, range(0,101,10), right = False, labels = labels)
#下面的方法同上,转化EdNum属性为5的整数倍
labels = ["{0}-{1}".format(i,i+4) for i in range(0,20,5)]
df_train_set['EduGroup'] = pd.cut(df_train_set.EdNum, range(0,21,5), right = False, labels = labels)
df_test_set['EduGroup'] = pd.cut(df_test_set.EdNum, range(0,21,5), right = False, labels = labels)
# 这里只提取colnames中的列,并按照colnames排序
df_train_set = df_train_set[colnames]
df_test_set = df_test_set[colnames]
#将非数值型数据转换为数值型数据
#调用pandas包中的DataFrameMapper类对AgeGroup、AgeGroup、Workclass、Occupation等列进行标签编码,转化为连续的数值型变量,大大提高了代码的简洁性,一步到位
mapper = DataFrameMapper([('AgeGroup', LabelEncoder()),('EduGroup', LabelEncoder()),
('Workclass', LabelEncoder()),('MaritalStatus', LabelEncoder()),
('Occupation', LabelEncoder()),('Relationship', LabelEncoder()),
('Race', LabelEncoder()),('Sex', LabelEncoder()),
('Income', LabelEncoder())], df_out=True, default=None)
#和(4)中方法类似,将Income列转移到中间
cols = list(df_train_set.columns)
cols.remove('Income')
cols = cols[:-3]+['Income']+cols[-3:]
#调用fit_transform()方法拟合数据,并标准化
#替换表头,移除样本标记income
df_train = mapper.fit_transform(df_train_set.copy())
df_train.columns = cols
df_test = mapper.transform(df_test_set.copy())
df_test.columns = cols
cols.remove('Income')
x_train, y_train = df_train[cols].values, df_train['Income'].values
x_test, y_test = df_test[cols].values, df_test['Income'].values
至此数据预处理操作完毕
#引入相应包
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import LabelEncoder
from sklearn_pandas import DataFrameMapper
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
#数据导入
train_data = 'data/adult.data'
test_data = 'data/adult.test'
columns = ['Age','Workclass','fnlgwt','Education','EdNum','MaritalStatus',
'Occupation','Relationship','Race','Sex','CapitalGain',
'CapitalLoss','HoursPerWeek','Country','Income']
df_train_set = pd.read_csv(train_data, names=columns)
df_test_set = pd.read_csv(test_data, names=columns, skiprows=1)
#数据预处理
df_train_set.drop('fnlgwt', axis=1, inplace=True)
df_test_set.drop('fnlgwt', axis=1, inplace=True)
for i in df_train_set.columns:
df_train_set[i].replace('?', 'Unknown', inplace=True)
df_test_set[i].replace('?', 'Unknown', inplace=True)
for col in df_train_set.columns:
if df_train_set[col].dtype != 'int64':
df_train_set[col] = df_train_set[col].apply(lambda val: val.replace(" ", ""))
df_train_set[col] = df_train_set[col].apply(lambda val: val.replace(".", ""))
df_test_set[col] = df_test_set[col].apply(lambda val: val.replace(" ", ""))
df_test_set[col] = df_test_set[col].apply(lambda val: val.replace(".", ""))
df_train_set.drop(["Country", "Education"], axis=1, inplace=True)
df_test_set.drop(["Country", "Education"], axis=1, inplace=True)
colnames = list(df_train_set.columns)
colnames.remove('Age')
colnames.remove('EdNum')
colnames = ['AgeGroup', 'EduGroup']+colnames
labels = ["{0}-{1}".format(i, i+9) for i in range(0,100,10)]
df_train_set['AgeGroup'] = pd.cut(df_train_set.Age, range(0,101,10), right = False, labels = labels)
df_test_set['AgeGroup'] = pd.cut(df_test_set.Age, range(0,101,10), right = False, labels = labels)
labels = ["{0}-{1}".format(i,i+4) for i in range(0,20,5)]
df_train_set['EduGroup'] = pd.cut(df_train_set.EdNum, range(0,21,5), right = False, labels = labels)
df_test_set['EduGroup'] = pd.cut(df_test_set.EdNum, range(0,21,5), right = False, labels = labels)
df_train_set = df_train_set[colnames]
df_test_set = df_test_set[colnames]
#数据转换
mapper = DataFrameMapper([('AgeGroup', LabelEncoder()),('EduGroup', LabelEncoder()),
('Workclass', LabelEncoder()),('MaritalStatus', LabelEncoder()),
('Occupation', LabelEncoder()),('Relationship', LabelEncoder()),
('Race', LabelEncoder()),('Sex', LabelEncoder()),
('Income', LabelEncoder())], df_out=True, default=None)
cols = list(df_train_set.columns)
cols.remove('Income')
cols = cols[:-3]+['Income']+cols[-3:]
df_train = mapper.fit_transform(df_train_set.copy())
df_train.columns = cols
df_test = mapper.transform(df_test_set.copy())
df_test.columns = cols
cols.remove('Income')
# 训练数据与测试数据划分
x_train, y_train = df_train[cols].values, df_train['Income'].values
x_test, y_test = df_test[cols].values, df_test['Income'].values
# 模型初步训练与评分
treeClassifier = DecisionTreeClassifier()
treeClassifier.fit(x_train, y_train)
score = treeClassifier.score(x_test, y_test)
print('决策树网格搜索前评分:', score)
#绘制混淆矩阵
from sklearn.metrics import plot_confusion_matrix
np.set_printoptions(precision=4)
titles_options = [('不规范混淆矩阵',None),
('规范化混淆矩阵', 'true')]
class_names = [df_test_set['Income'][1],df_test_set['Income'][2]]
for title, normalize in titles_options:
disp = plot_confusion_matrix(treeClassifier, x_test, y_test,
display_labels = class_names,
cmap = plt.cm.Oranges,
normalize = normalize)
disp.ax_.set_title(title)
print(title)
print(disp.confusion_matrix)
# 参数优化
from sklearn.model_selection import GridSearchCV
paras = {'max_features':(None, 9, 6),
'max_depth':(None, 24, 16),
'min_samples_split':(2, 4, 8),
'min_samples_leaf':(16, 4, 12)
}
clf = GridSearchCV(treeClassifier, paras, cv = 5)
clf.fit(x_train, y_train)
clf.best_score_, clf.score(x_test, y_test), clf.best_params_
print('决策树网格搜索后最好评分:', clf.best_score_)
print('决策树网格搜索后评分:', clf.score(x_test, y_test))
print('最好参数:', clf.best_params_)
disp2 = plot_confusion_matrix(clf, x_test, y_test,
display_labels = class_names,
cmap = plt.cm.Blues,
normalize = 'true')
disp2.ax_.set_title('Confusion matrix after GridSearch')
print(title)
print(disp2.confusion_matrix)
参数调优前截图:
参数调优后截图:
#朴素贝叶斯算法的数据预处理过程与(1)中相同
#建立朴素贝叶斯分类模型
from sklearn.naive_bayes import GaussianNB
gaussianNB = GaussianNB()
gaussianNB.fit(x_train, y_train)
score = gaussianNB.score(x_test, y_test)
print('贝叶斯模型评分:', score)
#用交叉验证的方法来检验模型的准确性
from sklearn.model_selection import cross_val_score
num_validations=5
accuracy=cross_val_score(gaussianNB,x_test, y_test,
scoring='accuracy',cv=num_validations)
print('准确率:{:.2f}%'.format(accuracy.mean()*100))
precision=cross_val_score(gaussianNB,x_test, y_test,
scoring='precision_weighted',cv=num_validations)
print('精确度:{:.2f}%'.format(precision.mean()*100))
recall=cross_val_score(gaussianNB,x_test, y_test,
scoring='recall_weighted',cv=num_validations)
print('召回率:{:.2f}%'.format(recall.mean()*100))
f1=cross_val_score(gaussianNB,x_test, y_test,
scoring='f1_weighted',cv=num_validations)
print('F1 值:{:.2f}%'.format(f1.mean()*100))
朴素贝叶斯截图:
在判断年收入是否超过50K的问题中,两种算法均采用相同的数据处理方式,并通过scroce()方法对模型进行了评估。其中决策树算法附加使用了网格搜索的方法进行参数调优,对模型做出了更好的改进。朴素贝叶斯算法附加使用了交叉验证的方法来进一步验证模型的性能。综合上述实验结果可以观察得出:决策树模型在参数调优前后评分均高于贝叶斯模型,并且参数调优后模型精度得到了进一步的提高;而贝叶斯模型在经过交叉验证后,精度反而出现了下降。
究其原因,可能在于数据预处理时选择了对决策树算法更有利的清洗方式,并且决策树算法利用网格搜索进行了模型参数调优,而朴素贝叶斯算法没有重新再对数据进行更加细致的预处理,没有做到减少数据冗余的特征以及归并类似的特征取值,并且最重要的一点在于没有利用拉普拉斯平滑对数据进行处理,这些原因都可能导致决策树算法的性能优于朴素贝叶斯算法的性能。
通过对相关资料的查询,朴素贝叶斯算法与其他分类方法相比,具有最小的误差率。但是实际上并非总是如此,这是因为朴素贝叶斯模型假设属性之间相互独立,这个假设在实际应用中往往是不成立的,在属性个数比较多或者属性之间相关性较大时,分类效果不好,而在属性相关性较小时,朴素贝叶斯算法的性能最为良好,并且朴素贝叶斯算法对于输入数据的准备方式较为敏感,通常不适用于连续性数据,只能用于离散数据。而“Adult”数据集恰是属于属性个数和连续性数据量多,属性之间的相关性较大的数据集。因此在对判断年收入是否超过50K这一问题解决方案的选择中应更偏向决策树算法。