Kaggle數(shù)據(jù)科學(xué)的入門項目:Titanic
寫在前面
基本步驟
- 數(shù)據(jù)準備和探索
- 數(shù)據(jù)清洗
- 特征選擇
- 模型訓(xùn)練
- 模型驗證
- 提交結(jié)果
1. 數(shù)據(jù)準備和探索
1.1 明確數(shù)據(jù)來源和要研究的問題
Titanic是世界十大災(zāi)難之一,一部經(jīng)典的電影讓沉船的慘烈和凄美的愛情故事都留在了大眾心中。同時蹋绽,災(zāi)難的傷亡數(shù)據(jù)一直成為了數(shù)據(jù)科學(xué)領(lǐng)域的研究熱門。
這次的研究的目的是試圖根據(jù)Titanic乘客的數(shù)據(jù)嘱丢,如性別、年齡祠饺、艙位越驻、客艙編號等,來預(yù)測乘客最終是獲救生還,還是不幸遇難伐谈。
顯然烂完,這是一個標準的二分類問題,預(yù)測類別為:生還和遇難诵棵。
先從Titanic Data下載數(shù)據(jù)集抠蚣,一共有3個文件:
文件 | 內(nèi)容 |
---|---|
train.csv |
訓(xùn)練數(shù)據(jù)集蛹含,包含特征信息和存活與否的標簽溢十,用來建模 |
test.csv |
測試數(shù)據(jù)集屯阀,只包含特征信息滔驾,用來檢測模型的準確度 |
gender_submission.csv |
提交文檔模板,假設(shè)所有女乘客都生還 |
1.2 數(shù)據(jù)探索
先簡單查看一下數(shù)據(jù)穗慕,讀取訓(xùn)練數(shù)據(jù)后放典,用head()
函數(shù)來查看前5行數(shù)據(jù):
#matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd
import warnings
warnings.filterwarnings('ignore')
train = pd.read_csv('train.csv', index_col=0)
test = pd.read_csv('test.csv', index_col=0)
train.head()
通過對數(shù)據(jù)的觀察辽幌,可以總結(jié)出如下數(shù)據(jù)字典:
變量名 | 變量解釋 | 數(shù)據(jù)解釋 |
---|---|---|
PassengerId | 乘客編號 | 唯一編號 |
Survived | 乘客是否生還 | 0=未生還忠蝗,1=生還 |
Pclass | 乘客所在艙位 | 1=一等艙现横,2=二等艙,3=三等艙 |
Name | 乘客姓名 | |
Sex | 乘客性別 | male阁最,female |
SibSp | 乘客的兄弟姐妹和配偶數(shù)量 | |
Parch | 乘客的父母和子女數(shù)量 | |
Ticket | 船票編號 | |
Fare | 票價 | |
Cabin | 乘客所在船艙號 | |
Embarked | 乘客登船港口 | C = Cherbourg, Q = Queenstown, S = Southampton |
由此可見戒祠,除了確定乘客的唯一編號PassengerId外,一共有10個可以研究的特征變量速种。
用train.info()
來查看數(shù)據(jù)的基本情況姜盈,運行結(jié)果如下:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 PassengerId 891 non-null int64
1 Survived 891 non-null int64
2 Pclass 891 non-null int64
3 Name 891 non-null object
4 Sex 891 non-null object
5 Age 714 non-null float64
6 SibSp 891 non-null int64
7 Parch 891 non-null int64
8 Ticket 891 non-null object
9 Fare 891 non-null float64
10 Cabin 204 non-null object
11 Embarked 889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB
可以看到各個數(shù)據(jù)字段的缺失情況和數(shù)據(jù)類型。
同時也不忘查看一下測試數(shù)據(jù)配阵,test.info()
運行結(jié)果如下:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 418 entries, 0 to 417
Data columns (total 11 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 PassengerId 418 non-null int64
1 Pclass 418 non-null int64
2 Name 418 non-null object
3 Sex 418 non-null object
4 Age 332 non-null float64
5 SibSp 418 non-null int64
6 Parch 418 non-null int64
7 Ticket 418 non-null object
8 Fare 417 non-null float64
9 Cabin 91 non-null object
10 Embarked 418 non-null object
dtypes: float64(2), int64(4), object(5)
memory usage: 36.0+ KB
2. 數(shù)據(jù)清洗
通過初步探索發(fā)現(xiàn)馏颂,在Age
、Cabin
棋傍、Fare
和Embarked
特征上都有數(shù)據(jù)缺失救拉,我們需要處理缺失值才能進行下一步的分析。
處理缺失數(shù)據(jù)一般有兩種方法:濾除缺失數(shù)據(jù)和填充缺失數(shù)據(jù)舍沙。Titanic數(shù)據(jù)集特征只有10個近上,顯然不能舍棄數(shù)據(jù)缺失的特征,所以需要填充缺失數(shù)據(jù)拂铡。
填充數(shù)據(jù)的時,一般會根據(jù)實際情況葱绒,將數(shù)據(jù)補0感帅,或以該特征數(shù)據(jù)的均值或中位數(shù)來填充,或?qū)⑷笔?shù)據(jù)劃為新的類型來處理地淀。
2.1 補齊Embarked字段
Embarked為乘客登船港口失球,首先觀察一下字段數(shù)據(jù)情況。
#查看Embarked字段數(shù)據(jù)
train['Embarked'].value_counts()
結(jié)果如下:
S 644
C 168
Q 77
Name: Embarked, dtype: int64
由于港口數(shù)據(jù)類型是字符型,而且數(shù)據(jù)量比較少实苞,我們可以用眾數(shù)來填補空缺數(shù)據(jù)豺撑,即港口數(shù)最多的'S'
train['Embarked'].fillna('S', inplace=True)
test['Embarked'].fillna('S', inplace=True)
2.1 補齊Age和Fare字段
Age和Fare字段簡單地采用均值來填補
train['Age'].fillna(train['Age'].mean(), inplace=True)
test['Age'].fillna(test['Age'].mean(), inplace=True)
train['Fare'].fillna(train['Fare'].mean(), inplace=True)
test['Fare'].fillna(test['Fare'].mean(), inplace=True)
3. 特征選擇
選取數(shù)據(jù)中有價值的特征,提取train和test中的特征向量
features = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']
train_features = train[features]
train_labels = train['Survived']
test_features = test[features]
由于數(shù)據(jù)中Embarked和Sex字段都是字符型數(shù)據(jù)黔牵,這樣不利于分析聪轿,可以用sklearn中的特征提取,將符號化數(shù)據(jù)抽取成不同的特征向量:
from sklearn.feature_extraction import DictVectorizer
dvec = DictVectorizer(sparse=False)
train_features = dvec.fit_transform(train_features.to_dict(orient='record'))
print(dvec.feature_names_)
查看feature_names_
可以看出猾浦,原來的Embarked
和Sex
兩列均根據(jù)變量取值拆成了若干列:
['Age', 'Embarked=C', 'Embarked=Q', 'Embarked=S', 'Fare', 'Parch', 'Pclass', 'Sex=female', 'Sex=male', 'SibSp']
4. 模型訓(xùn)練
選擇sklearn中的決策樹模型陆错,使用ID3算法構(gòu)造決策樹:
from sklearn.tree import DecisionTreeClassifier
clf = DecisionTreeClassifier(criterion='entropy')
clf.fit(train_features, train_labels)
決策樹根據(jù)train數(shù)據(jù)fit過,可以用來預(yù)測test數(shù)據(jù)了:
test_features = dvec.transform(test_features.to_dict(orient='record'))
pred_labels = clf.predict(test_features)
5. 模型驗證
因為沒有測試數(shù)據(jù)的正確結(jié)果金赦,計算決策樹在訓(xùn)練數(shù)據(jù)上的準確率:
acc = round(clf.score(train_features, train_labels), 6)
print(u'accuracy = %.4lf' % acc)
運行結(jié)果:
accuracy = 0.9820
6. 提交結(jié)果
最終提交的結(jié)果要符合kaggle示范文件的格式音瓷,也就是包含兩列PassengerID
和Survived
數(shù)據(jù)的csv文件。
passengers = test['PassengerId']
survived= pd.Series(data=pred_labels, name='Survived')
result = pd.concat([passengers, survived], axis=1)
result.to_csv('submission.csv', index=False)
保存好文件后夹抗,在kaggle比賽頁面提交绳慎,提交成功后會計算出此次的分數(shù):
最終準確率是0.746╮(╯▽╰)╭
數(shù)據(jù)分析版hello world到此結(jié)束,如何提高準確率提升名次漠烧,把文中偷懶和簡化的部分做得更細致偷线,大概就可以提升不少了吧,日后再刷~~
---------------------------------------更新的分割線----------------------------------------
很顯然之前簡單用決策樹模型得出的準確率74.6%是遠遠不夠的沽甥,不足的地方有:
- 數(shù)據(jù)探索不夠深入声邦,沒有挖出數(shù)據(jù)更深層的隱含信息
- 數(shù)據(jù)清洗不到位,對數(shù)據(jù)的宏觀把握不足摆舟,對數(shù)據(jù)的認識和感知還不夠亥曹,所以進而數(shù)據(jù)的清洗也沒有找到更合適的方法
- 數(shù)據(jù)模型單一且簡單,應(yīng)該選擇更合適的模型
- 模型驗證方法很顯然不對恨诱,與實際準確率相去甚遠
接下來就來逐個點優(yōu)化吧~~~
7. 數(shù)據(jù)再探索
7.1 數(shù)據(jù)基本分布
先從宏觀上了解一下數(shù)據(jù)媳瞪,看看幾個主要特征上的分布情況,然后再進一步分析的切入點:
fig = plt.figure(figsize=(17,10), dpi=200)
#查看獲救人數(shù)和未獲救人數(shù)分別有多少
plt.subplot2grid((2,3),(0,0))
train_data.Survived.value_counts().plot(kind='bar',color=['lightblue','pink'])
plt.ylabel(u'人數(shù)')
plt.title(u'獲救情況(1為獲救)')
# 查看各艙位乘客人數(shù)
plt.subplot2grid((2,3),(0,1))
train_data.Pclass.value_counts().plot(kind='bar',color=['lightblue','pink','palegreen'])
plt.ylabel(u'人數(shù)')
plt.title(u'乘客等級分布')
# 查看獲獲救和未獲救乘客的年齡分布
plt.subplot2grid((2,3),(0,2))
plt.scatter(train_data.Survived, train_data.Age, color='skyblue')
plt.ylabel(u'年齡')
plt.grid(b=True, which='major', axis='y')
plt.title(u'按年齡看獲救分布(1為獲救)')
# 查看各艙位等級乘客的年齡分布
plt.subplot2grid((2,3),(1,0), colspan=2)
train_data.Age[train_data.Pclass==1].plot(kind='kde')
train_data.Age[train_data.Pclass==2].plot(kind='kde')
train_data.Age[train_data.Pclass==3].plot(kind='kde')
plt.xlabel(u'年齡')
plt.ylabel(u'密度')
plt.title(u'各等級的乘客年齡分布')
plt.legend((u'頭等艙',u'2等艙',u'3等艙'), loc='best')
# 查看各登船港口的獲救人數(shù)
plt.subplot2grid((2,3),(1,2))
train_data.Embarked.value_counts().plot(kind='bar',color=['lightblue','pink','palegreen'])
plt.title(u'各登船口岸上岸人數(shù)')
plt.ylabel(u'人數(shù)')
plt.show()
圖表果然清晰多了照宝,從圖中不難看出:
- 泰坦尼克號沉船是個大災(zāi)難蛇受,獲救的人只是一小部分,為逝者默哀一分鐘
- 3等艙的乘客人數(shù)遠遠大于其他兩個船艙的乘客
- 獲救和未獲救的人年齡分布都很廣
- 各艙位乘客的年齡分布厕鹃,3等艙的乘客集中分布在20左右兢仰,頭等艙乘客40歲左右最多,很符合社會財富分布
- S港口的登陸乘客最多剂碴,遠多于C把将、Q港口
大概了解了數(shù)據(jù)分布后可以大膽假設(shè)一下: - 乘客的財富地位也許會影響到最終是否被獲救,所以與財富地位相關(guān)的因素如:艙位等級忆矛、姓名Title等都可能是相關(guān)特征
- 乘客的登船港口和地點相關(guān)察蹲,各地區(qū)之間居民收入和身份地位也許也不同
接下來就進行相關(guān)性分析,探討一下到底哪些因素會影響到是否獲救。
7.2 各特征與是否獲救的關(guān)聯(lián)統(tǒng)計
7.2.1 性別與獲救情況
# 按性別查看獲救情況
survived_M = train_data.Survived[train_data.Sex=='male'].value_counts()
survived_F = train_data.Survived[train_data.Sex=='female'].value_counts()
df = pd.DataFrame({u'男性':survived_M, u'女性':survived_F})
df.plot(kind='bar', stacked=True, color=['lightblue','pink'])
plt.title(u'按性別查看獲救情況')
plt.xlabel(u'性別')
plt.ylabel(u'人數(shù)')
plt.show()
果然洽议,lady first不僅僅是一句口號宗收,獲救的乘客中還是女性居多,犧牲的乘客中男性占了絕大多數(shù)亚兄。
7.2.2 客艙等級與獲救情況
# 按客艙等級查看獲救情況
Survived_0 = train_data.Pclass[train_data.Survived == 0].value_counts()
Survived_1 = train_data.Pclass[train_data.Survived == 1].value_counts()
df = pd.DataFrame({u'獲救':Survived_1,u'未獲救':Survived_0})
df.plot(kind='bar', stacked=True, color=['#92ff92','#ff9292'])
plt.title(u'各乘客等級的獲救情況')
plt.ylabel(u'人數(shù)')
plt.xlabel(u'乘客等級')
plt.show()
1等艙和2等艙的獲救比例遠遠大于3等艙混稽,乘客的錢不是白花的呀,萬惡的資本主義~~
7.2.3 登船港口與獲救情況
# 查看各港口的獲救情況
survived_0 = train_data.Embarked[train_data.Survived == 0].value_counts()
survived_1 = train_data.Embarked[train_data.Survived == 1].value_counts()
df = pd.DataFrame({u'獲救':survived_0,u'未獲救':survived_1})
df.plot(kind='bar', stacked=True, color=['#92ff92','#ff9292'])
plt.title(u'各港口乘客的獲救情況')
plt.ylabel(u'人數(shù)')
plt.xlabel(u'港口')
plt.show()
S港口的乘客人數(shù)眾多儿捧,獲救率似乎也低一些荚坞,C港口獲救率比稍微高一些,港口這個特征暫且留著吧
7.2.4 船艙號與獲救情況
有個特征Cabin
缺失嚴重菲盾,補足數(shù)據(jù)也不太方便颓影,我們可以考慮或許有沒有Cabin
字段會和生還率相關(guān)呢?
# 根據(jù)有沒有Cabin屬性創(chuàng)建一個新的CabinBool屬性
train_data['CabinBool'] = (train_data['Cabin'].notnull().astype('int'))
cabin_1 = train_data['CabinBool'][train_data['Survived']==1].value_counts(normalize=True)
cabin_0 = train_data['CabinBool'][train_data['Survived']==0].value_counts(normalize=True)
df = pd.DataFrame({'獲救':[cabin_1[0], cabin_0[0]],'未獲救':[cabin_1[1], cabin_0[1]]})
df.plot.bar(stacked=True, color=['#92ff92','#ff9292'])
plt.show()
很顯然诡挂,有Cabin記錄的乘客的獲救率遠遠高于沒有Cabin記錄的乘客,可以考慮將Cabin轉(zhuǎn)化成二元屬性用于模型訓(xùn)練临谱。
還有一些屬性
Parch
璃俗、SibSp
簡單的查看了一下,參考價值不大悉默,暫且不用了吧城豁。分析到這里就可以進行下一步的預(yù)處理了。
8. 數(shù)據(jù)預(yù)處理
為了方便起見抄课,我們將原始的訓(xùn)練數(shù)據(jù)和測試數(shù)據(jù)合并后一并處理唱星。
y_train = train_data.pop('Survived')
data_all = pd.concat((train_data, test_data), axis=0)
8.1 提取姓名Title
先查看一下Name
屬性的格式
print(data_all['Name'].head(20))
可以看出來,
Name
屬性的格式是名
+Title
+姓
跟磨,名和姓對我們都不重要间聊,只要把Title提取出來就行了。
title = pd.DataFrame()
title['Title'] = data_all['Name'].map(lambda name:name.split(',')[1].split('.')[0].strip())
print(title['Title'].value_counts())
可以大致把Title分為
Normal
抵拘、Middle
和Royal
三類哎榴。
# 先將Title歸類
title_dict = pd.DataFrame()
title_dict['Normal'] = ['Mr', 'Miss', 'Mrs', 'Ms', 'Mme','Mlle']
title_dict['Middle']=['Capt', 'Dr', 'Rev', 'Col', 'Master', 'Major']
title_dict['Royal']=['the Countess', 'Sir', 'Lady', 'Don','Jonkheer', 'Dona']
#構(gòu)造title_map
title_map = {}
for index,row in title_dict.iteritems():
for title_ in row:
title_map[title_] = index
print(title_map)
捋請了Title的分類后,就可以對原始數(shù)據(jù)進行改造了:
title['Title'] = title.Title.map(title_map)
# 將title進行one-hot encoding
title = pd.get_dummies(title.Title)
#拼接到data上
data_all = pd.concat((data_all, title), axis=1)
data_all.pop('Name')
print(data_all.head())
這時數(shù)據(jù)變成了13列僵蛛,丟棄了Name
尚蝌,將title拆為了Normal
、Middle
heRoyal
三列墩瞳。
8.2 處理Cabin屬性
根據(jù)是否有Cabin
將原字符型屬性轉(zhuǎn)為binary屬性:
data_all['Cabin'] = (data_all.Cabin.notnull().astype('int'))
8.3 處理Pclass屬性
根據(jù)上面的分析驼壶,Pclass和是否獲救的相關(guān)性很大,Pclass也是字符型屬性喉酌,所以也很適合用one-hot encoding處理:
data_all.Pclass = data_all.Pclass.astype(str)
data_all.Pclass = data_all.Pclass.map(lambda pclass:'pclass_'+pclass)
pclass = pd.get_dummies(data_all.Pclass)
data_all = pd.concat((data_all, pclass), axis=1)
data_all.pop('Pclass')
Pclass
被拆成了pclass_1
、pclass_2
和pclass_3
3列。
8.4 處理其他屬性
先填充缺失值泪电,用均值填充Age
:
data_all.Age.fillna(data_all.Age.mean(),inplace=True)
填充Embarked
屬性般妙,這是個字符型屬性,用出現(xiàn)概率最高的S
來填充相速,再用one-hot encoding處理:
data_all.Embarked.fillna('S', inplace=True)
embarked = pd.get_dummies(data_all.Embarked)
data_all = pd.concat((data_all, embarked), axis=1)
data_all.pop('Embarked')
test數(shù)據(jù)中有一個Fare
缺失碟渺,也用均值來填充:
data_all.Fare.fillna(data_all.Fare.mean(),inplace=True)
將Sex
屬性拆成二元屬性:
sex = pd.get_dummies(data_all.Sex)
data_all = pd.concat((data_all, sex),axis=1)
data_all.pop('Sex')
還有一個比較迷的Ticket
屬性,現(xiàn)在特征數(shù)量已經(jīng)很多了突诬,就利用奧卡姆剃刀把它剃掉吧~~
經(jīng)過這一系列的處理后苫拍,現(xiàn)在的數(shù)據(jù)變成了這樣:
由原來的10個特征變成了17個特征,并且全都是數(shù)值型的旺隙。
最后绒极,別忘了把訓(xùn)練數(shù)據(jù)和測試數(shù)據(jù)拆開:
train_d = data_all.loc[train_data.index]
test_d = data_all.loc[test_data.index]
print(train_d.shape, test_d.shape)
拆開后shape
分別是(891, 16)和(418, 16),符合原始數(shù)據(jù)的大小蔬捷。
9. 模型訓(xùn)練
這次準備使用sklearn里的邏輯回歸模型垄提,同樣也是一個很適合分類問題的模型。
先用sklearn試試最簡單的baseline模型效果咋樣:
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
lr_clf = LogisticRegression(C=1.0, penalty='none', tol=1e-6)
lr_clf.fit(train_features, y_train)
lr_pred = lr_clf.predict(test_features)
corss_score = np.mean(cross_val_score(lr_clf, train_features, y_train, cv=10))
print(u'cross score = %.4lf' % corss_score)
cross validation 的準確率是 0.7834周拐,似乎也沒比決策樹優(yōu)秀多少铡俐。
下面就要開始進一輪的模型優(yōu)化了~~
9.1 模型系數(shù)關(guān)聯(lián)分析
查看一下目前這個LR模型中各特征的關(guān)聯(lián)系數(shù):
pd.DataFrame({"columns":list(train_d.columns)[0:], "coef":list(lr_clf.coef_.T)})
這個關(guān)聯(lián)系數(shù)表示的是每個特征的在邏輯回歸模型中的模型參數(shù),邏輯回歸模型會通過sigmod函數(shù)將這個參數(shù)映射到0~1之間妥粟。
coef大于0時审丘,說明特征和結(jié)果是正相關(guān),小于0時是負相關(guān)勾给。
從上面的關(guān)聯(lián)系數(shù)表可以看出:
- female和是否獲救正相關(guān)滩报,male則是負相關(guān),很符合數(shù)據(jù)給人的印象
- Age有一點點負相關(guān)锦秒,說明年齡越小越容易獲救
- Cabin這個字段竟然很有助于獲救露泊,說明有登記船艙信息的都是比較容易獲救的
- 登船港口S、Q旅择、C竟然呈現(xiàn)出截然不同的表現(xiàn)惭笑,很出乎意料,不知道是不是有點弄錯了o(╥﹏╥)o
- Parch的關(guān)聯(lián)性竟然這么高生真,還有SibSp也不錯沉噩,可以考慮多挖掘挖掘
9.2 模型優(yōu)化
根據(jù)上表,找出幾個可以嘗試的模型調(diào)優(yōu)的幾個點:
- 增加Child屬性柱蟀,年齡小于12的添加Child屬性
- Pclass似乎沒有利用起來川蒙,吧Pclass和港口組合成新的特征
- 增加一個Family屬性,把Parch和SibSp還有自己加起來长已,看看家庭人數(shù)的影響
經(jīng)過一系列處理后畜眨,目前最好成績是0.78947
各種特征排列組合尋求最優(yōu)解是一個需要耐心和細心的活兒昼牛,可能也需要一點點的靈感和新的視角
暫時告一段落啦,接下來會用模型融合來試一試~~