本文详述了Kaggle的Titanic幸存者预测这一分类问题,包括数据的探索性分析和可视化、数据预处理、特征工程、建模以及超参数的调优等。
一、初探数据
先观察训练集的前几行数据,如下:
#导入第三方库
importwarnings
warnings.filterwarnings('ignore')
importmatplotlib.pyplotasplt
importseabornassns
importpandasaspd
importnumpyasnp
importstatsmodels.formula.apiassmf
fromsklearn.ensembleimportRandomForestRegressor
fromstatsmodels.stats.outliers_influenceimportvariance_inflation_factor
fromsklearn.model_selectionimporttrain_test_split
fromsklearn.metricsimportroc_auc_score
#读取数据
data_train=pd.read_csv('E:/train.csv')
print(data_train)
每个特征的具体含义如下:
PassengerId => 乘客ID
Pclass => 乘客等级(1/2/3等舱位)
Name => 乘客姓名
Sex => 性别
Age => 年龄
SibSp => 堂兄弟/妹个数
Parch => 父母与小孩个数
Ticket => 船票信息
Fare => 票价
Cabin => 客舱
Embarked => 登船港口
#信息的总览
print(data_train.info())
从信息的总览可以看出,训练集中总共有891名乘客,Age(年龄)、Cabin(客舱)、Embarked(登船港口)等这几个特征存在缺失。
二、数据的探索性分析
每个乘客拥有那么多特征,那我们如何知道哪些特征更有用呢?这点就必须对数据有足够的认识,看看每个特征和最后的Survived之间有什么样关系呢?
2.0、目标变量Survived的分布
首先你得清楚目标变量Survived的分布,是否出现正负样本分布不均匀的情况,当其分布不均匀时,如何平衡,这些都必须考虑的问题。
# Survived的分布
data_train['Survived'].value_counts().plot.pie(autopct='%1.2f%%')
plt.show()
可见正负样本分布还算均匀,不需要平衡。
2.1、性别与是否存活的关系
sns.factorplot('Sex','Survived',data=data_train,size=3,aspect=1.5)
plt.title('Sex and Survived rate')
plt.show()
从上图可视化结果来看,女性的存活率远高于男性;性别无疑也要作为重要特征加入最后的模型之中。
2.2、乘客社会等级与是否存活的关系
sns.factorplot('Pclass','Survived',data=data_train,size=3,aspect=1.5)
plt.title('Pclass and Survived rate')
plt.show()
不同社会等级可能和财富/地位有关系,最后获救的概率可能会不一样;社会等级越高,存活率就越高。
2.3、配偶及兄弟姐妹的数量与是否存活的关系
sns.factorplot('SibSp','Survived',data=data_train,size=3,aspect=1.5)
plt.title('SibSp and Survived rate')
plt.show()
配偶及兄弟姐妹适中的乘客存活率越高。
2.4、父母与子女的数量与是否存活的关系
sns.factorplot('Parch','Survived',data=data_train,size=3,aspect=1.5)
plt.title('Parch and Survived rate')
plt.show()
父母与子女数量适中的,存活率更高。
2.5、年龄与是否存活的关系
facet=sns.FacetGrid(data_train,palette='Greens_d',hue='Survived',aspect=2)
#使用map函数映射kde,以Age作为X轴
facet.map(sns.kdeplot,'Age',shade=True)
#设置x轴的取值范围为到最大值
facet.set(xlim=(,data_train['Age'].max()))
facet.add_legend()
plt.show()
年龄对获救概率也有一定的影响,可以看出未成年人的存活率高于成年人。
2.6、船票费与是否存活的关系
facet=sns.FacetGrid(data_train,palette='Greens_d',hue='Survived',aspect=2)
#使用map函数映射kde,以Fare作为X轴
facet.map(sns.kdeplot,'Fare',shade=True)
#设置x轴的取值范围为到200
facet.set(xlim=(,200))
facet.add_legend()
plt.show()
可以看出船票费越高,存活率就越高。
2.7、登船港口与是否存活的关系
sns.factorplot('Embarked','Survived',data=data_train,size=3,aspect=2)
plt.title('Embarked and Survived rate')
plt.show()
可以看出在不同港口上船,存活率不同,C港口最高,Q次之,S最低。
三、数据预处理
数据的预处理指的是缺失值和异常值的处理,暂时只针对缺失值进行处理,通常的缺失值处理方式有如下几种。
如果缺值的样本比例极高,我们可能就直接删除了,作为特征加入的话,可能反倒带入noise,影响最后的结果。
如果缺失的样本适中,而该特征属于分类变量,可以把NaN作为一个新类别,加到类别特征中;或者通过中位数、众数、平均数等进行填充。
缺失的值个数并不是特别多,而该特征属于连续型变量,那我们也可以试着根据已有的值,通过模型预测,来填充缺失值。
目前的缺失特征有Age(年龄)、Cabin(客舱)、Embarked(登船港口);处理方式如下:
3.1、针对Cabin(客舱)的缺失,我们的处理方式是把NaN作为一个新类别,加到类别特征中。
3.2、针对Embarked(登船港口)的缺失,缺失比例较小,采用众数的方式进行填充。
3.3、针对Age(年龄)的缺失,我们采用的是scikit-learn中的RandomForest来预测缺失的年龄数据。
#统计缺失值比例
missing_rate=data_train.isnull().sum()/len(data_train)
# print(missing_rate)
# Cabin处理
data_train['Cabin']=data_train['Cabin'].apply(lambdax:ifxisnp.nanelse1)
# Embarked处理
data_train['Embarked']=data_train['Embarked'].fillna(data_train['Embarked'].mode()[])
# Age处理
defmissing_ages(df):
age_df=df[['Age','Fare','Parch','SibSp','Pclass']]
known_age=age_df[age_df.Age.notnull()]
unknown_age=age_df[age_df.Age.isnull()]
y=known_age.iloc[:,]
X=known_age.iloc[:,1:]
# fit到RandomForestRegressor之中
rfr=RandomForestRegressor(random_state=,n_estimators=500,n_jobs=-1)
rfr.fit(X,y)
#用得到的模型进行未知年龄结果预测
predictedAges=rfr.predict(unknown_age.iloc[:,1:])
#用得到的预测结果填补原缺失数据
df.loc[ (df.Age.isnull()),'Age']=predictedAges
returndf
data_train=missing_ages(data_train)
四、特征工程
有这么一句话在业界广泛流传:数据和特征决定了机器学习的上限,而模型和算法只是无限逼近这个上限而已。那么特征工程到底是什么呢?其本质是特征衍生和尺度变换,目的是最大限度的从原始特征中提取、衍生特征。下面将以示例的形式展开分析:
4.1、Sex和Age特征的提取
还记得副船长说『小孩和女士先走』,就这么一句话涵盖了多少信息,基于以上探索性分析,将年龄为16岁作为一个分界点,对其进行分类,小于16岁为‘child’,大于16岁保留性别。
# Sex和Age组合特征
defget_person(passenger):
age,sex=passenger
return'child'ifage
data_train['Person']=data_train[['Age','Sex']].apply(get_person,axis=1)
#删除Sex变量
data_train.drop(['Sex'],axis=1,inplace=True)
4.2、Parch和SibSp特征的提取
将Parch和SibSp特征组合为一个Famliy变量,示例如下:
# Parch和SibSp组合特征
data_train['Family']=data_train['Parch']+data_train['SibSp']
data_train['Family']=data_train['Family'].apply(lambdax:ifx==else1)
#删除Parch和SibSp
data_train.drop(['Parch','SibSp'],axis=1,inplace=True)
4.3、Fare特征的提取
基于数据的探索性分析,可以看出船票费大于20时,乘客的存活率普遍比20以下要高,故以20作为切分点。
# Fare特征的提取
data_train['Fare']=data_train['Fare'].apply(lambdax:ifx
4.4、Person、Pclass、Cabin、Embarked和Family特征的提取
针对这五个特征的处理方式,都是转换为哑变量。
# Person/Pclass/Cabin/Embarked/Family哑编码处理
col=['Person','Pclass','Cabin','Embarked','Family']
defdummy(df,col):
foriincol:
data_dummy=pd.get_dummies(df[i],prefix=i)
df=df.join(data_dummy)
df.drop(i,axis=1,inplace=True)
returndf
data_titanic=dummy(data_train,col)
#删除无关原始特征
data_titanic.drop(['Name','Ticket'],axis=1,inplace=True)
print(data_titanic)
4.5、尺度的变换
目前只针对年龄这个特征进行尺度的标准化处理。
#对Age进行标准化处理
defz_score(x):
return(x-x.mean())/x.std()
data_titanic['Age']=data_titanic['Age'].transform(z_score)
五、模型的构建
本文主要介绍基于逻辑回归的分类预测,该逻辑回归位于statsmodels模块中,下面将以示例具体介绍。
5.1、逻辑回归对共线性较为敏感,第一步首先是判断共线性问题,可以利用VIF检验,一般认为大于10的变量就存在共线性。
#多重共线性检验
Y=data_titanic['Survived']
X=data_titanic.drop(['PassengerId','Survived'],axis=1)
feature_selection=list(X.columns)
foriinrange(len(feature_selection)):
print('{}:{}'.format(feature_selection[i],variance_inflation_factor(X.values,i)))
一般筛选的规则是从下往上逐个剔除,直到所有特征的VIF值都小于10为止。最后结果如下所示:
#删除共线特征
X.drop(['Family_1','Embarked_S','Cabin_1','Pclass_3','Person_male'],axis=1,inplace=True)#
feature_selection=list(X.columns)
foriinrange(len(feature_selection)):
print('{}:{}'.format(feature_selection[i],variance_inflation_factor(X.values,i)))
5.2、逻辑回归通常对特征的显著性也同样敏感,第二步就需要进行特征的显著性检验;根据概率P值逐个剔除,直到所有的P值都小于0.1或者0.05。如下所示是我经过删除不显著变量之后的结果:
#删除不显著的变量
X.drop(['Embarked_Q','Family_0','Pclass_1','Fare'],axis=1,inplace=True)
#需要自行添加逻辑回归所需的intercept变量
X['intercept']=1.0
#划分训练集和测试集
X_train,X_test,y_train,y_test=train_test_split(X,Y,test_size=0.3,random_state=1)
#构建逻辑回归分类器
lr=smf.Logit(y_train,X_train).fit()
print(lr.summary())
5.3、训练模型以及模型的评估
介于比赛官方的要求,模型的评估采用的是AUC值作为此次比赛的评估标准,示例如下:
#训练误差
predicted=lr.predict(X_train)
print('AUC Score (Train): {:.5f}'.format(roc_auc_score(y_train,predicted)))
#测试误差
y_pred=lr.predict(X_test)
print('AUC Score (Test): {:.5f}'.format(roc_auc_score(y_test,y_pred)))
AUC Score (Train):0.87649
AUC Score (Test):0.80699
此次的分享到这差不多结束了,整个建模的流程大体就这么多,暂时没有涉及超参数的调节以及模型的融合,后期的文章会继续补充,感谢亲们的支持和关注。
学习与分享,关注小号
领取专属 10元无门槛券
私享最新 技术干货