|
趁着周末比较闲,做一个几乎所有人都会做的入坑项目——大名鼎鼎的Kaggle泰坦尼克。 数据地址我就不放了,Kaggle上自己翻吧。数据包括: - PassengerId => 乘客ID
- Survive => 乘客是否生还(仅在训练集中有,测试集中没有)
- Pclass => 乘客等级(1/2/3等舱位)
- Name => 乘客姓名
- Sex => 性别
- Age => 年龄
- SibSp => 堂兄弟/妹个数
- Parch => 父母与小孩个数
- Ticket => 船票信息
- Fare => 票价
- Cabin => 客舱
- Embarked => 登船港口
而我们需要根据以上乘客的个人信息,做出分类模型,对测试集中乘客的生还情况做出预测。因为生还只涉及到生/死两种情况,故此问题是个二分类问题,在这里我们使用逻辑回归进行分类。 1.数据认知首先来看一下数据 import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
data_train = pd.read_csv('train.csv')
data_train.info()
先从训练集开始看,结果如下: 
可以看到Cabin,Age和Embarked这三项在训练集中皆有缺失。 首先,让我们直观的认识一下各类信息与生还的相关情况。 1.1 舱位与生还的对比 fig = plt.figure()
fig.set(alpha=0.2)
no_survived = data_train.Pclass[data_train.Survived == 0].value_counts()
survived = data_train.Pclass[data_train.Survived == 1].value_counts()
df = pd.DataFrame({'Survived':survived,'Died':no_survived})
df.plot(kind='bar',stacked = True)
plt.title('Class vs Survive')
plt.xlabel('People')
plt.ylabel('Class')
plt.show()

我们可以看到,就生还率而言,一等舱明显最高,而三等舱显然最低。因此可以推测舱位的高低与生还情况成正相关。 这告诉了我们出门一定要多花钱买船/机票,做个有钱人。
1.2 性别与生还对比 fig = plt.figure()
fig.set(alpha = 0.2)
no_survived_g = data_train.Sex[data_train.Survived == 0].value_counts()
survived_g = data_train.Sex[data_train.Survived == 1].value_counts()
df_g = pd.DataFrame({'Survived':survived_g,'Died':no_survived_g})
df_g.plot(kind='bar',stacked = True)
plt.title('Gender vs Survive')
plt.xlabel('People')
plt.ylabel('Survive')
plt.show()

女性的生还状况要好于男性,看来在泰坦尼克号事故中,船员们还是普遍绅士的。 说明了如果是个女土豪,生还机会更好。 1.3 港口与生还情况 fig = plt.figure()
fig.set(alpha = 0.2)
no_survived_e = data_train.Embarked[data_train.Survived == 0].value_counts()
survived_e = data_train.Embarked[data_train.Survived == 1].value_counts()
df_e = pd.DataFrame({'Survived':survived_e,'Died':no_survived_e})
df_e.plot(kind='Bar',stacked = True)
plt.title('Embarked vs Survive')
plt.xlabel('People')
plt.ylabel('Survive')
plt.show()

至于就登陆港口而言,三个港口并看不出明显的差距,C港生还率略高于S港与Q港。 1.4 家庭成员与生还情况 family_s = data_train.groupby(['SibSp','Survived'])
df_s = pd.DataFrame(family_s.count()['PassengerId'])
df_s
family_p = data_train.groupby(['Parch','Survived'])
df_p = pd.DataFrame(family_p.count()['PassengerId'])
df_p
 
可以看到,独自一人的乘客生还情况明显劣于有家人相伴的乘客。 因此,要做一个和家人一起出门的女土豪会大大提高人在遇险时的生还几率。 2.数据清洗2.1 缺失数据
在认识到数据后,我们要对数据进行清洗。主要则是将缺失的数据补充,去除不需要的数据,对需要的数据进行整理,标准化等等。 引用一段看来的几种常见的处理方式: - 如果缺值的样本占总数比例极高,我们可能就直接舍弃了,作为特征加入的话,可能反倒带入noise,影响最后的结果了
- 如果缺值的样本适中,而该属性非连续值特征属性(比如说类目属性),那就把NaN作为一个新类别,加到类别特征中
- 如果缺值的样本适中,而该属性为连续值特征属性,有时候我们会考虑给定一个step(比如这里的age,我们可以考虑每隔2/3岁为一个步长),然后把它离散化,之后把NaN作为一个type加到属性类目中。
- 有些情况下,缺失的值个数并不是特别多,那我们也可以试着根据已有的值,拟合一下数据,补充上。
对于Cabin类,因为缺失很多,所以直接舍弃就好了。(其实之前我拿把个特征分为有Cabin和无Cabin做来的,后来发现效果 不好于是在最后整理的时候干脆舍弃了…)
对于Age类,这是一个连续特征值,因此我觉得拿回归森林做个拟合会比较好: from sklearn.ensemble import RandomForestRegressor
age_df = data_train[['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 = known_age[:,0]
X = known_age[:,1:]
rfr = RandomForestRegressor(random_state=0, n_estimators=2000, n_jobs=-1)
rfr.fit(X, y)
predictedAges = rfr.predict(unknown_age[:, 1::])
data_train.loc[ (data_train.Age.isnull()), 'Age' ] = predictedAges
对于Embarked,仅仅缺失两个样本,直接拿众数填好了: data_train.loc[(data_train.Embarked.isnull()),'Embarked']= 'S'
2.2 已有数据整合 处理完缺失数据,让我们来看看已有数据中有哪些需要整合的。 首先,堂兄妹个数和父母子女个数可以合并为同一项, 称为Family项,再将Family项离散化。具体我这里的处理方式是: 0人为无家庭成员,赋值为0。 1-3人为中型家庭,赋值为1。 4人以上为大型家庭,赋值为2。 data_train['Family']=data_train['SibSp']+data_train['Parch']
data_train.loc[(data_train.Family == 0),'Family']=0
data_train.loc[((data_train.Family > 0) & (data_train.Family < 4)) ,'Family']=1
data_train.loc[((data_train.Family >= 4)) ,'Family']=2
data_train.head()
其次,对于年龄同样进行离散化,分为五组: data_train.loc[(data_train.Age <= 16),'Age']=0
data_train.loc[((data_train.Age >= 16) & (data_train.Age < 32)) ,'Age']=1
data_train.loc[((data_train.Age >= 32) & (data_train.Age < 48)) ,'Age']=2
data_train.loc[((data_train.Age >= 48) & (data_train.Age < 64)) ,'Age']=3
data_train.loc[((data_train.Age >= 64)) ,'Age']=4
最后,由于票价的数量级与其他项明显不一样,将票价这一特征标准化,并删去不需要的特征: data_train['Fare'] = (data_train['Fare']-data_train['Fare'].mean())/data_train['Fare'].std()
data_train.drop(['SibSp','Parch','Name','Cabin','Ticket'],axis=1,inplace = True)
data_train.head()
现在我们的数据长这个样子: 
2.3 因子化 不难发现,除了Fare这一列,其他的特征都是离散的,因此可以将他们因子化。1.可以增加特征数量。2.便于计算 
嘛…大概就长这样 最后将训练集中的数据代入逻辑回归的模型 from sklearn import linear_model
train_df = df.filter(regex = 'Survived|Age_*|Fare|Embarked_.*|Sex_.*|Pclass_.*|Family_.')
train_np = train_df.as_matrix()
y = train_np[:,0]
X = train_np[:,1:]
clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6)
clf.fit(X, y)
clf
3.逻辑回归分析3.1 训练集分析 至此,训练集的逻辑回归模型已经做出来了,首先让我们看看模型的系数和我们所选取的特征之间的关系吧: pd.DataFrame({"columns":list(train_df.columns)[1:], "coef":list(clf.coef_.T)})
系数是正就是正相关,负是负相关,绝对值越大说明相关性越强。可以看出舱位,性别,家庭,以及年龄的相关性都比较好。记住!可见,16岁以下的家庭成员不超过4人睡一等舱的女小土豪在泰坦尼克号中存活的可能性最高。 再做一个Cross Validation来看一下我们的预测准确度: from sklearn import cross_validation
clf2 = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6)
all_data = df.filter(regex='Survived|Age_*.|Family.*|Fare|Embarked_.*|Sex_.*|Pclass_.*')
X = all_data.as_matrix()[:,1:]
y = all_data.as_matrix()[:,0]
print (cross_validation.cross_val_score(clf2, X, y, cv=5))
我们把测试集分成五份,来相互验证,结果: 
平均0.8+,感觉还可以啊。 3.2 测试集 既然已经在训练集上得到了一个还可以的结果,那就把他用在测试集上试一试吧。 首先观察测试集的数据: 
发现除了Cabin和Age缺失以外,Fare一列也缺失了一项。在此前说过,Cabin这个特征我们是不要的,所以可以忽略缺失值,Age则是用随机森林拟合补全。介于Fare只缺失一项,那就用他的平均值补全好了。剩下的处理方法和之前训练集 data_test = pd.read_csv('test.csv')
data_test.info()
data_test.loc[(data_test.Fare.isnull()),'Fare']=data_test['Fare'].mean()
from sklearn.ensemble import RandomForestRegressor
age_df = data_test[['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 = known_age[:,0]
X = known_age[:,1:]
rfr = RandomForestRegressor(random_state=0, n_estimators=2000, n_jobs=-1)
rfr.fit(X, y)
predictedAges = rfr.predict(unknown_age[:, 1::])
data_test.loc[ (data_test.Age.isnull()), 'Age' ] = predictedAges
data_test.loc[(data_test.Age <= 16),'Age']=0
data_test.loc[((data_test.Age >= 16) & (data_test.Age < 32)) ,'Age']=1
data_test.loc[((data_test.Age >= 32) & (data_test.Age < 48)) ,'Age']=2
data_test.loc[((data_test.Age >= 48) & (data_test.Age < 64)) ,'Age']=3
data_test.loc[((data_test.Age >= 64)) ,'Age']=4
data_test['Family']=data_test['SibSp']+data_test['Parch']
data_test.loc[(data_test.Family == 0),'Family']=0
data_test.loc[((data_test.Family > 0) & (data_test.Family < 4)) ,'Family']=1
data_test.loc[((data_test.Family >= 4)) ,'Family']=2
data_test['Fare'] = (data_test['Fare']-data_test['Fare'].mean())/data_test['Fare'].std()
data_test.drop(['SibSp','Parch','Name','Cabin','Ticket'],axis=1,inplace = True)
dummies_Pclass = pd.get_dummies(data_test['Pclass'],prefix='Pclass')
dummies_Age = pd.get_dummies(data_test['Age'],prefix='Age')
dummies_Sex = pd.get_dummies(data_test['Sex'],prefix='Sex')
dummies_Family = pd.get_dummies(data_test['Family'],prefix='Family')
dummies_Embarked = pd.get_dummies(data_test['Embarked'],prefix='Embarked')
df_test = pd.concat([data_test,dummies_Pclass,dummies_Sex,dummies_Family,dummies_Embarked,dummies_Age], axis=1)
df_test.drop(['Pclass','Sex','Family','Embarked','Age'],axis=1,inplace = True)
df_test.head()
训练集结果: 
将这个结果拟合到之前做好的模型中: test_df = df_test.filter(regex = 'Age_.*|Family_.*|Fare|Embarked_.*|Sex_.*|Pclass_.*')
predictions = clf.predict(test_df)
result = pd.DataFrame({'PassengerId':data_test['PassengerId'].as_matrix(), 'Survived':predictions.astype(np.int32)})
result.to_csv("Titanic Prediction.csv", index=False)
传到Kaggle上看一下评分,遗憾的只有0.77990...不到0.78。 4.模型融合来试着做一下模型融合,本来应该是个多数表决的过程...但由于现在只用了逻辑回归,所以只好用SciKit-Learn里的Bagging模块,他每次取训练集中的一部分作为子集进行预测,最终将所有子集结果进行融合。 from sklearn.ensemble import BaggingRegressor
train_df = df.filter(regex='Survived|Age_.*|Family_.*|Fare|Embarked_.*|Sex_.*|Pclass.*')
train_np = train_df.as_matrix()
y = train_np[:, 0]
X = train_np[:, 1:]
clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6)
bagging_clf = BaggingRegressor(clf, n_estimators=20, max_samples=0.8, max_features=1.0, bootstrap=True, bootstrap_features=False, n_jobs=-1)
bagging_clf.fit(X, y)
test = df_test.filter(regex='Age_.*|Family_.*|Fare|Embarked_.*|Sex_.*|Pclass.*')
predictions = bagging_clf.predict(test)
result = pd.DataFrame({'PassengerId':data_test['PassengerId'].as_matrix(), 'Survived':predictions.astype(np.int32)})
result.to_csv("Titanic_Prediction_bagging.csv", index=False)
取20个子集,最大为训练集的80%。就最后的结果而言,准确率为0.78468...提升了一丢丢。 
如果想继续提升预测的准确度的话,应该在特征上继续挖掘,完善特征工程。比如说:在上面的模型中,姓名这个特征是被舍弃的,但实际上姓名的前缀(如:Mr., Ms. Dr. etc.)都有可能对生还率造成影响,但影响可能不会很大,我就没有做。至此,有关泰坦尼克号的逻辑回归分析就可以结束了。如果有什么要总结的,那就是: 一定要做一个16岁以下的同行家庭成员不超过4人可以睡一等舱的女土豪!
|