Kaggle实践1:“Titanic之灾”整理

来源:互联网 发布:淘宝网比价历史同期 编辑:程序博客网 时间:2024/04/20 17:18

整理自博客http://blog.csdn.net/han_xiaoyang/article/details/49797143

1. 数据概览与题目分析

首先导入数据集,并打印部分数据

# -*- coding:utf-8 -*-import pandas as pdimport numpy as npfrom pandas import Series,DataFramedata_train = pd.read_csv("train.csv")#print data_traindata_train.info()

得到如下输出

<class 'pandas.core.frame.DataFrame'>RangeIndex: 891 entries, 0 to 890Data columns (total 12 columns):PassengerId    891 non-null int64Survived       891 non-null int64Pclass         891 non-null int64Name           891 non-null objectSex            891 non-null objectAge            714 non-null float64SibSp          891 non-null int64Parch          891 non-null int64Ticket         891 non-null objectFare           891 non-null float64Cabin          204 non-null objectEmbarked       889 non-null objectdtypes: float64(2), int64(5), object(5)memory usage: 83.6+ KB

结论:
1. Cabin数据缺失过多,并且看起来没什么用,可以直接用yes no代替
2. Age数据缺失小部分,而且影响较大,缺失数据需要补全
3. 暂时可以认为无效的其他数据:PassengerId、Embarked

题目中所要预测的变量为Survived,显然这个题可以视作一个监督学习之下的分类问题,根据之前简单的整理,有以下几种基础模型可以选择:logistic Regression、Random Forest、Naive Bayesian等等

2. 数据间关联分析

首先,确定数据中变量与Survived的关联程度。
* 这里需要用到matplotlib.pyplot工具包
如,展示各个等级舱位中乘客获救的比例,观察其中差异

import matplotlib.pyplot as plt#看看各乘客等级的获救情况fig = plt.figure()fig.set(alpha=0.2)  # 设定图表颜色alpha参数Survived_0 = data_train.Pclass[data_train.Survived == 0].value_counts()Survived_1 = data_train.Pclass[data_train.Survived == 1].value_counts()df=pd.DataFrame({u'获救':Survived_1, u'未获救':Survived_0})df.plot(kind='bar', stacked=True)plt.title(u"各乘客等级的获救情况")plt.xlabel(u"乘客等级") plt.ylabel(u"人数") plt.show()

结果清晰地显示,越高等级的乘客获救的概率越高,观察这一特征的人数分布情况,各个等级的人数没有极端少或极端多的情况。

所以这一特征一定是影响结果的特征,其他类似特征分析以此类推

再展示一下年龄与客舱之间的关系,这一关系使用曲线图表示,纵轴是密度

import matplotlib.pyplot as plt#看看各乘客等级的获救情况#plt.subplot2grid((2,3),(0,0))             # 在一张大图里分列几个小图#plt.subplot2grid((2,3),(1,0), colspan=2)data_train.Age[data_train.Pclass == 1].plot(kind='kde')   data_train.Age[data_train.Pclass == 2].plot(kind='kde')data_train.Age[data_train.Pclass == 3].plot(kind='kde')plt.xlabel(u"年龄")# plots an axis lableplt.ylabel(u"密度") plt.title(u"各等级的乘客年龄分布")plt.legend((u'头等舱', u'2等舱',u'3等舱'),loc='best') # sets our legend for our graph.plt.show()

emmm,果然符合我们的一贯认知

最后看一个详细版的:各等级船舱分性别获救统计

#各种舱级别情况下各性别的获救情况fig=plt.figure()fig.set(alpha=0.65) # 设置图像透明度plt.title(u"根据舱等级和性别的获救情况")ax1=fig.add_subplot(141)data_train.Survived[data_train.Sex == 'female'][data_train.Pclass != 3].value_counts().plot(kind='bar', label="female highclass", color='#FA2479')ax1.set_xticklabels([u"获救", u"未获救"], rotation=0)ax1.legend([u"女性/高级舱"], loc='best')ax2=fig.add_subplot(142, sharey=ax1)data_train.Survived[data_train.Sex == 'female'][data_train.Pclass == 3].value_counts().plot(kind='bar', label='female, low class', color='pink')ax2.set_xticklabels([u"未获救", u"获救"], rotation=0)plt.legend([u"女性/低级舱"], loc='best')ax3=fig.add_subplot(143, sharey=ax1)data_train.Survived[data_train.Sex == 'male'][data_train.Pclass != 3].value_counts().plot(kind='bar', label='male, high class',color='lightblue')ax3.set_xticklabels([u"未获救", u"获救"], rotation=0)plt.legend([u"男性/高级舱"], loc='best')ax4=fig.add_subplot(144, sharey=ax1)data_train.Survived[data_train.Sex == 'male'][data_train.Pclass == 3].value_counts().plot(kind='bar', label='male low class', color='steelblue')ax4.set_xticklabels([u"未获救", u"获救"], rotation=0)plt.legend([u"男性/低级舱"], loc='best')plt.show()

*在其中,我们把12都视为高级仓,使得高级仓和低级舱人数上较为一致

事实证明,无论是男性还是女性,在高等级机舱内获救几率都有大幅度提升

是否有兄弟姐妹和父母孩子有几人这两个特征,我们直接打印统计数据结果

g = data_train.groupby(['SibSp','Survived'])df = pd.DataFrame(g.count()['PassengerId'])print dfg = data_train.groupby(['Parch','Survived'])df = pd.DataFrame(g.count()['PassengerId'])print df

结果如下:

                PassengerIdSibSp Survived             0     0                 398      1                 2101     0                  97      1                 1122     0                  15      1                  133     0                  12      1                   44     0                  15      1                   35     0                   58     0                   7                PassengerIdParch Survived             0     0                 445      1                 2331     0                  53      1                  652     0                  40      1                  403     0                   2      1                   34     0                   45     0                   4      1                   16     0                   1

再举一个反例:登岸港口于是否获救的关联,柱状图显示,不同登岸港口人群中获救与未获救的比例相差不大,所以基本可以算无效特征

OK,数据间关联展示与分析先到这里

3. 数据预处理

这里说的数据预处理,其实就包括了feature engineering过程

先从最突出的数据属性,Cabin和Age开始,有丢失数据对下一步工作影响太大。

按Cabin有无数据,将这个属性处理成Yes和No两种类型

再说Age:

通常遇到缺值的情况,我们会有几种常见的处理方式

  • 如果缺值的样本占总数比例极高,我们可能就直接舍弃了,作为特征加入的话,可能反倒带入noise,影响最后的结果了

  • 如果缺值的样本适中,而该属性非连续值特征属性(比如说类目属性),那就把NaN作为一个新类别,加到类别特征中

  • 如果缺值的样本适中,而该属性为连续值特征属性,有时候我们会考虑给定一个step(比如这里的age,我们可以考虑每隔2/3岁为一个步长),然后把它离散化,之后把NaN作为一个type加到属性类目中。

  • 有些情况下,缺失的值个数并不是特别多,那我们也可以试着根据已有的值,拟合一下数据,补充上。

本例中,后两种处理方式应该都是可行的,我们先试试拟合补全吧(虽然说没有特别多的背景可供我们拟合,这不一定是一个多么好的选择)

我们这里用scikit-learn中的RandomForest来拟合一下缺失的年龄数据(注:RandomForest是一个用在原始数据中做不同采样,建立多颗DecisionTree,再进行average等等来降低过拟合现象,提高结果的机器学习算法,我们之后会介绍到)

from sklearn.ensemble import RandomForestRegressor### 使用 RandomForestClassifier 填补缺失的年龄属性def set_missing_ages(df):    # 把已有的数值型特征取出来丢进Random Forest Regressor中    age_df = df[['Age','Fare', 'Parch', 'SibSp','Pclass']]    # 乘客分成已知年龄和未知年龄两部分    known_age = age_df[age_df.Age.notnull()].as_matrix()    unknown_age = age_df[age_df.Age.isnull()].as_matrix()    # y即目标年龄    y = known_age[:, 0]    # X即特征属性值    X = known_age[:, 1:]    # fit到RandomForestRegressor之中    rfr = RandomForestRegressor(random_state=0, n_estimators=2000, n_jobs=-1)    rfr.fit(X, y)    # 用得到的模型进行未知年龄结果预测    predictedAges = rfr.predict(unknown_age[:, 1::])    # 用得到的预测结果填补原缺失数据    df.loc[ (df.Age.isnull()), 'Age' ] = predictedAges     return df, rfrdef set_Cabin_type(df):    df.loc[ (df.Cabin.notnull()), 'Cabin' ] = "Yes"    df.loc[ (df.Cabin.isnull()), 'Cabin' ] = "No"    return dfdata_train, rfr = set_missing_ages(data_train)data_train = set_Cabin_type(data_train)

因为逻辑回归建模时,需要输入的特征都是数值型特征,我们通常会先对类目型的特征因子化

以Cabin为例,原本一个属性维度,因为其取值可以是[‘yes’,’no’],而将其平展开为’Cabin_yes’,’Cabin_no’两个属性:
1. 原本Cabin取值为yes的,在此处的”Cabin_yes”下取值为1,在”Cabin_no”下取值为0
2. 原本Cabin取值为no的,在此处的”Cabin_yes”下取值为0,在”Cabin_no”下取值为1
* 这里0和1没有具体意义,只代表某种数据特征

我们使用pandas的”get_dummies”来完成这个工作,并拼接在原来的”data_train”之上,如下所示。

dummies_Cabin = pd.get_dummies(data_train['Cabin'], prefix= 'Cabin')dummies_Embarked = pd.get_dummies(data_train['Embarked'], prefix= 'Embarked')dummies_Sex = pd.get_dummies(data_train['Sex'], prefix= 'Sex')dummies_Pclass = pd.get_dummies(data_train['Pclass'], prefix= 'Pclass')df = pd.concat([data_train, dummies_Cabin, dummies_Embarked, dummies_Sex, dummies_Pclass], axis=1)df.drop(['Pclass', 'Name', 'Sex', 'Ticket', 'Cabin', 'Embarked'], axis=1, inplace=True)print df

我们还得做一些处理,仔细看看Age和Fare两个属性,乘客的数值幅度变化过大,如果了解逻辑回归与梯度下降的话,各属性值之间scale差距太大,将对收敛速度造成很大影响,甚至不收敛。所以我们先用scikit-learn里面的preprocessing模块对这两个特征做一个scaling,所谓scaling,其实就是将一些变化幅度较大的特征化到[-1,1]之内。

import sklearn.preprocessing as preprocessingscaler = preprocessing.StandardScaler()age_scale_param = scaler.fit(df['Age'])df['Age_scaled'] = scaler.fit_transform(df['Age'], age_scale_param)fare_scale_param = scaler.fit(df['Fare'])df['Fare_scaled'] = scaler.fit_transform(df['Fare'], fare_scale_param)print df

4. 建立logistic回归模型

from sklearn import linear_model#用正则取出我们要的属性值train_df = df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')train_np = train_df.as_matrix()# y即Survival结果y = train_np[:, 0]# X即特征属性值X = train_np[:, 1:]# fit到RandomForestRegressor之中clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6)clf.fit(X, y)#clf即所得模型data_test = pd.read_csv("/Users/Hanxiaoyang/Titanic_data/test.csv")data_test.loc[ (data_test.Fare.isnull()), 'Fare' ] = 0# 接着我们对test_data做和train_data中一致的特征变换# 首先用同样的RandomForestRegressor模型填上丢失的年龄tmp_df = data_test[['Age','Fare', 'Parch', 'SibSp', 'Pclass']]null_age = tmp_df[data_test.Age.isnull()].as_matrix()# 根据特征属性X预测年龄并补上X = null_age[:, 1:]predictedAges = rfr.predict(X)data_test.loc[ (data_test.Age.isnull()), 'Age' ] = predictedAgesdata_test = set_Cabin_type(data_test)dummies_Cabin = pd.get_dummies(data_test['Cabin'], prefix= 'Cabin')dummies_Embarked = pd.get_dummies(data_test['Embarked'], prefix= 'Embarked')dummies_Sex = pd.get_dummies(data_test['Sex'], prefix= 'Sex')dummies_Pclass = pd.get_dummies(data_test['Pclass'], prefix= 'Pclass')df_test = pd.concat([data_test, dummies_Cabin, dummies_Embarked, dummies_Sex, dummies_Pclass], axis=1)df_test.drop(['Pclass', 'Name', 'Sex', 'Ticket', 'Cabin', 'Embarked'], axis=1, inplace=True)df_test['Age_scaled'] = scaler.fit_transform(df_test['Age'], age_scale_param)df_test['Fare_scaled'] = scaler.fit_transform(df_test['Fare'], fare_scale_param)print df_test

输出预测结果

test = df_test.filter(regex='Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')predictions = clf.predict(test)result = pd.DataFrame({'PassengerId':data_test['PassengerId'].as_matrix(), 'Survived':predictions.astype(np.int32)})result.to_csv("/Users/Hanxiaoyang/Titanic_data/logistic_regression_predictions.csv", index=False)

5. 模型优化

首先,Name和Ticket两个属性被我们完整舍弃了,是因为这俩属性,几乎每一条记录都是一个完全不同的值,我们并没有找到很直接的处理方式。

然后,年龄的拟合本身也未必是一件非常靠谱的事情,我们依据其余属性,其实并不能很好地拟合预测出未知的年龄。再一个,以我们的日常经验,小朋友和老人可能得到的照顾会多一些,这样看的话,年龄作为一个连续值,给一个固定的系数,应该和年龄是一个正相关或者负相关,似乎体现不出两头受照顾的实际情况,所以,说不定我们把年龄离散化,按区段分作类别属性会更合适一些。

先把得到的model系数和feature关联起来看看:

print pd.DataFrame({"columns":list(train_df.columns)[1:], "coef":list(clf.coef_.T)})

输出

                 coef      columns0   [-0.344228432669]        SibSp1   [-0.104931071044]        Parch2               [0.0]     Cabin_No3    [0.902140004873]    Cabin_Yes4               [0.0]   Embarked_C5               [0.0]   Embarked_Q6   [-0.417261727572]   Embarked_S7     [1.95657593799]   Sex_female8   [-0.677420127319]     Sex_male9    [0.341145077753]     Pclass_110              [0.0]     Pclass_211   [-1.19413974208]     Pclass_312   [-0.52378204527]   Age_scaled13   [0.084432447377]  Fare_scaled

首先,大家回去前两篇文章里瞄一眼公式就知道,这些系数为正的特征,和最后结果是一个正相关,反之为负相关。

我们先看看那些权重绝对值非常大的feature,在我们的模型上:

  1. Sex属性,如果是female会极大提高最后获救的概率,而male会很大程度拉低这个概率。
  2. Pclass属性,1等舱乘客最后获救的概率会上升,而乘客等级为3会极大地拉低这个概率。
  3. 有Cabin值会很大程度拉升最后获救概率(这里似乎能看到了一点端倪,事实上从最上面的有无Cabin记录的Survived分布图上看出,即使有Cabin记录的乘客也有一部分遇难了,估计这个属性上我们挖掘还不够)
  4. Age是一个负相关,意味着在我们的模型里,年龄越小,越有获救的优先权(还得回原数据看看这个是否合理)
  5. 有一个登船港口S会很大程度拉低获救的概率,另外俩港口压根就没啥作用(这个实际上非常奇怪,因为我们从之前的统计图上并没有看到S港口的获救率非常低,所以也许可以考虑把登船港口这个feature去掉试试)。
  6. 船票Fare有小幅度的正相关(并不意味着这个feature作用不大,有可能是我们细化的程度还不够,举个例子,说不定我们得对它离散化,再分至各个乘客等级上?)

待续