1. 程式人生 > >kaggle練習項目—泰坦尼克乘客生還預測

kaggle練習項目—泰坦尼克乘客生還預測

sans 練習 missing 中文 ces 大寫 param show ont

一、問題復述

  泰坦尼克號是一艘英國皇家郵輪,在當時是全世界最大的海上船舶。1912年4月,該郵輪在首航中碰撞上冰山後沈沒。造成船上2224名人員中1514人罹難。

  現在根據乘客的船艙等級、性別、年齡等信息,對其是否獲救進行判定。我們一共有1309名乘客的信息,其中891名乘客信息作為訓練集,另外418名乘客信息作為測試集。

  先查看數據的總體情況:

# -*- coding: utf-8 -*-

import pandas as pd
import numpy as np
from pandas import Series,DataFrame
import matplotlib.pyplot as plt

plt.rcParams[‘font.sans-serif‘]=[‘SimHei‘]  #用來正常顯示中文標簽
plt.rcParams[‘axes.unicode_minus‘]=False    #用來正常顯示負號

pd.set_option(‘display.width‘, 2000, ‘display.max_rows‘, None,‘display.max_columns‘, None)  # 設置數據顯示
trd=pd.read_csv("../data/train.csv")                   # 讀取訓練數據
tsd=pd.read_csv("../data/test.csv")                    # 讀取測試數據
trd.info()                                       # 讀取訓練數據列信息
tsd.info()                                       # 讀取測試數據列信息
print(trd.describe())                       # 顯示訓練數據特征
print(tsd.describe())                       # 顯示測試數據特征

  

 可看到包含如下屬性:

  PassengerId(乘客編號),訓練集:1-891,測試集:892-1309;

  Survived(是否獲救),是用1表示,否用0表示,只訓練集中有該項屬性;

  Pclass(船艙等級),分為1、2、3級;

  Name(乘客姓名);

  Sex(乘客性別),female,male;

  Age(乘客年齡),訓練集:714名乘客有該項屬性,177名乘客缺失,測試集:332名乘客有該項屬性,86名乘客缺失;

  SibSp(兄弟姐妹\配偶個數);

  Parch (父母\子女個數);

  Ticket (船票信息),每名乘客均不同,由數字編號,字母等組成,十分雜亂;

  Fare(船票價格);

  Cabin(船艙編號) , 由單個大寫字母+數字組成,訓練集:204名乘客有該項屬性,687名乘客缺失;測試集:91名乘客有該項屬性,241名乘客缺失。

  Embarked(登船口),分別有C、S、Q三個登船口,訓練集中兩名乘客缺失該項信息。

二、數據初步分析

  每位乘客的信息中,優先考慮數據質量相對較高的數值屬性、標稱屬性等。對於PassengerId、Name、Ticket這3項暫時不做分析,另外8項屬性,首先獨立地分析每個屬性對乘客獲救與否的影響。

# -*- coding: utf-8 -*-

import pandas as pd
import numpy as np
from pandas import Series,DataFrame
import matplotlib.pyplot as plt

plt.rcParams[‘font.sans-serif‘]=[‘SimHei‘]  #用來正常顯示中文標簽
plt.rcParams[‘axes.unicode_minus‘]=False    #用來正常顯示負號

pd.set_option(‘display.width‘, 2000, ‘display.max_rows‘, None,‘display.max_columns‘, None)  # 設置數據顯示
trd=pd.read_csv("../data/train.csv")    # 讀取數據
trd.info()                              # 讀取列信息
# print(trd.describe())                 # 顯示特征值

# 兩個Series,將一個索引處有值另一個為NaN的地方填充為0
def func1(Series1,Series2):
    for i in Series1.index:
        if i not in Series2.index:
            Series2[i]=0
    for i in Series2.index:
        if i not in Series1.index:
            Series2[1] = 0
    return Series1,Series2

# begin -*- 6.2屬性與獲救結果的關聯統計 -*-
fig=plt.figure(figsize=(12,6))        # 定義圖並設置畫板尺寸
fig.set(alpha=0.2)                    # 設定圖表顏色alpha參數
# fig.tight_layout()                  # 調整整體空白
plt.subplots_adjust(left=0.08,right=0.94,wspace =0.36, hspace =0.5)       # 調整子圖間距

#1 各船艙等級的獲救情況
ax1=fig.add_subplot(241)
ax1.set(title=u"各船艙等級乘客獲救情況",xlabel=u"船艙等級",ylabel=u"人數")
ax1.set_title(u"各船艙等級乘客獲救情況",fontdict={‘fontsize‘:10})                # 設置標題字體大小
ax1.axis([0,4,0,600])
S0_Pclass= trd.Pclass[trd.Survived == 0].value_counts()
S1_Pclass= trd.Pclass[trd.Survived == 1].value_counts()
plt.xticks(rotation=90)
dfp1=pd.DataFrame({u‘未獲救‘:S0_Pclass, u‘獲救‘:S1_Pclass}).plot(ax=ax1,kind=‘bar‘, stacked=True,rot=1)
for i in S0_Pclass.index:                                                                   # 添加列標簽
    plt.text(i-1.16,S0_Pclass[i]+S1_Pclass[i]+12,"{:.2f}".format(S1_Pclass[i]/(S0_Pclass[i]+S1_Pclass[i])))


#2 各船艙號乘客獲救情況
ax2=fig.add_subplot(242)
ax2.set(title="各船艙號乘客獲救情況",xlabel=u"船艙號",ylabel=u"人數")
ax2.set_title(u"各船艙號乘客獲救情況",fontdict={‘fontsize‘:10})                # 設置標題字體大小
ax2.axis([0,8,0,800])
trd2=trd.copy()
count=0
for i in trd2.Cabin.fillna("N").values:
    trd2.Cabin[count]=i[0]
    count+=1
S0_Cabin=trd2.Cabin[trd2.Survived==0].value_counts()
S1_Cabin=trd2.Cabin[trd2.Survived==1].value_counts()
dfp2=pd.DataFrame({"未獲救":S0_Cabin,"獲救":S1_Cabin}).plot(ax=ax2,kind="bar",stacked=True,rot=1)
S0_Cabin,S1_Cabin=func1(S0_Cabin,S1_Cabin)
S0_Cabin,S1_Cabin=S0_Cabin.sort_index(),S1_Cabin.sort_index()
count2=-0.5
for i in S0_Cabin.index:
    # print(i,S0_Cabin.index,S0_Cabin[i])
    # print(ax2.get_xticks())
    plt.text(count2,S0_Cabin[i]+S1_Cabin[i]+16,"{:.1f}".format(S1_Cabin[i]/(S0_Cabin[i]+S1_Cabin[i])))
    count2+=1

#3 各登船口的獲救情況
ax3=fig.add_subplot(243)
ax3.set(title=u"各登船口乘客獲救情況",xlabel=u"登船口",ylabel=u"人數")
ax3.set_title(u"各登船口乘客獲救情況",fontdict={‘fontsize‘:10})                # 設置標題字體大小
ax3.axis([0,3,0,800])
S0_Embarked= trd.Embarked[trd.Survived == 0].value_counts()
S1_Embarked= trd.Embarked[trd.Survived == 1].value_counts()
dfp2=pd.DataFrame({u‘未獲救‘:S0_Embarked, u‘獲救‘:S1_Embarked}).plot(ax=ax3,kind=‘bar‘, stacked=True,rot=1)
c=0
for i in S0_Embarked.index:                                                                   # 添加列標簽
    plt.text(c-0.2,S0_Embarked[i]+S1_Embarked[i]+20,"{:.2f}"             .format(S1_Embarked[i]/(S0_Embarked[i]+S1_Embarked[i])))
    c+=1

#4 各船票價格乘客的獲救情況
ax4=fig.add_subplot(244)
ax4.set(title="各船票價格乘客的獲救情況",xlabel=u"票價",ylabel=u"獲救率")
ax4.set_title(u"各船票價格乘客獲救情況",fontdict={‘fontsize‘:10})                # 設置標題字體大小
ax4.axis([0,300,0,1])
x=np.array(sorted(trd.Fare[trd.Fare.notnull()]))
y=[]
for i in x:
    y.append(trd.Fare[trd.Fare < i][trd.Survived == 1].count()/trd.Fare[trd.Fare < i].count())
y=np.array(y)
plt.plot(x,y,"--",linewidth=0.6)
    # ax4.set_xticks([])                                                   # 不顯示x軸刻度

#5 各性別的獲救情況
ax5=fig.add_subplot(245)
ax5.set(title=u"不同性別乘客獲救情況",xlabel=u"性別",ylabel=u"人數")
ax5.set_title(u"不同性別乘客獲救情況",fontdict={‘fontsize‘:10})                # 設置標題字體大小
ax5.axis([0,5,0,700])
S0_Sex=trd.Sex[trd.Survived==0].value_counts()
S1_Sex=trd.Sex[trd.Survived==1].value_counts()
dfp3=pd.DataFrame({u‘未獲救‘:S0_Sex, u‘獲救‘:S1_Sex}).plot(ax=ax5,kind=‘bar‘, stacked=True,rot=0)
c=1
for i in S0_Sex.index:                                                                   # 添加列標簽
    plt.text(c-0.15,S0_Sex[i]+S1_Sex[i]+16,"{:.2f}".format(S1_Sex[i]/(S0_Sex[i]+S1_Sex[i])))
    c-=1

#6 各年齡乘客的獲救情況
ax6=fig.add_subplot(246)
ax6.set(title="各年齡乘客獲救情況",xlabel=u"乘客年齡",ylabel=u"獲救率")
ax6.set_title(u"各年齡乘客獲救情況",fontdict={‘fontsize‘:10})                # 設置標題字體大小
x6=np.array(sorted(trd.Age[trd.Age.notnull()]))
# print(x6)
y6=[]
for i6 in x6:
    y6.append(trd.Age[trd.Age<i6][trd.Survived==1].count()/trd.Age[trd.Age<i6].count())
plt.plot(x6,y6,"--",linewidth=0.6)
    # ax6.set_xticks([])                                                   # 不顯示x軸刻度

#7 登船兄弟姐妹\配偶人數-乘客獲救情況
ax7=fig.add_subplot(247)
ax7.set(title=u"登船兄弟姐妹\配偶人數-乘客獲救情況",xlabel=u"登船兄弟姐妹\配偶人數",ylabel=u"人數")
ax7.set_title(u"登船兄弟姐妹\配偶人數-乘客獲救情況",fontdict={‘fontsize‘:10})                # 設置標題字體大小
ax7.axis([0,10,0,700])
S0_SibSp=trd.SibSp[trd.Survived==0].value_counts()
S1_SibSp=trd.SibSp[trd.Survived==1].value_counts()
dfp4=pd.DataFrame({"未獲救":S0_SibSp,"獲救":S1_SibSp}).plot(ax=ax7,kind="bar",stacked=True,rot=1)
S0_SibSp,S1_SibSp=func1(S0_SibSp,S1_SibSp)                      # 加起來
S0_SibSp=S0_SibSp.sort_index()                                  # 按照索引排序
S1_SibSp=S1_SibSp.sort_index()
c=0
for i in S0_SibSp.index:                                                                   # 添加列標簽
    plt.text(c-0.3,S0_SibSp[i]+S1_SibSp[i]+16,"{:.2f}".format(S1_SibSp[i]/(S0_SibSp[i]+S1_SibSp[i])))
    c+=1

#8 登船父母\子女人數-乘客獲救情況
ax8=fig.add_subplot(248)
ax8.set(title=u"登船父母\子女人數-乘客獲救情況",xlabel=u"登船父母\子女人數",ylabel=u"人數")
ax8.set_title(u"登船父母\子女人數-乘客獲救情況",fontdict={‘fontsize‘:10})                # 設置標題字體大小
ax8.axis([0,10,0,800])
S0_Parch=trd.Parch[trd.Survived==0].value_counts()
S1_Parch=trd.Parch[trd.Survived==1].value_counts()
dfp8=pd.DataFrame({"未獲救":S0_Parch,"獲救":S1_Parch}).plot(ax=ax8,kind="bar",stacked=True,rot=0.5)
S0_Parch,S1_Parch=func1(S0_Parch,S1_Parch)                      # 加起來
S0_Parch=S0_Parch.sort_index()                                  # 按照索引排序
S1_Parch=S1_Parch.sort_index()
c=0
for i in S0_Parch.index:                                                                   # 添加列標簽
    plt.text(c-0.3,S0_Parch[i]+S1_Parch[i]+16,"{:.2f}".format(S1_Parch[i]/(S0_Parch[i]+S1_Parch[i])))
    c+=1

plt.savefig(‘../result/數據初步分析.jpg‘)
plt.show()

  

  得到如下圖所示結果,對8個子圖逐一進行解釋和分析(子圖編號按照從左至右,先行後列排序)。技術分享圖片

  子圖1,船艙不同等級乘客獲救情況。共有3個等級,圖上標簽表示存活率。由圖可知,船艙等級為1、2、3的乘客獲救率分別為0.64、0.47、0.24。因此,船艙等級是一個較顯著的影響因素。

  子圖2,由於各乘客船艙號是大寫字母加數字,且大部分乘客缺失該項屬性,嘗試以船艙號首字母將其分類,並以N表示該項缺失。由子圖2可知,缺失該項屬性的乘客存活率為0.3,其它乘客存活率在0.5-0.8之間,且未缺失該項屬性的乘客每類樣本量均較小。因此在後續分析中,該項屬性以是否缺失作為分類標準。

  子圖3,從S、C、Q登船口登船的乘客獲救率分別為0.34、0.55、0.39。

  子圖4,票價-存活率的概率分布,即橫坐標為票價,縱坐標為低於該票價的乘客的存活率。可以看出,票價越高,獲救率越大。

  子圖5,按照乘客性別考查獲救率,可以看出女性乘客獲救率0.74,明顯高於男性0.19的獲救概率。是一個較顯著的影響因素。

  子圖6,年齡-存活率的概率分布,即橫坐標為年齡,縱坐標為小於該年齡的乘客的存活率。可以看出,年齡越小,獲救率越大。

  子圖7,按照同登船的兄弟姐妹\配偶個數考查,該屬性值為0、1、2的乘客獲救率分別為0.35、0.54、0.46,其它取值的乘客樣本量較小,且獲救率較低,可以歸為一類。

  子圖8,按照同登船的父母\子女個數考查,該屬性值為0、1、2的乘客獲救率分別為0.34、0.55、0.50,其它取值的乘客樣本量較小,且獲救率較低,可以歸為一類。

三、數據預處理

  通過以上分析,我們大致了解了各屬性對乘客獲救與否的影響,現對各屬性作如下預處理:

  船艙號:缺失該項屬性標記為0,未缺失標記為1

  登船口:缺失、C、S、Q分別標記為0、1、2、3

  船票價格: 規範化(按照比例映射到[0,1]區間內)

  性別:female標記為0,male標記為1

  年齡:利用隨機森林和其它屬性填補缺失數據,再對其規範化(按照比例映射到[0,1]區間內)

  登船兄弟姐妹\配偶人數:大於等於3個統一記為3,其余不變

  登船父母\子女人數:大於等於3個統一記為3,其余不變

  

# 數據數值化
def data_sd(trd):
    trd.loc[(trd.Cabin.notnull()), ‘Cabin‘] = 1
    trd.loc[(trd.Cabin.isnull()), ‘Cabin‘] = 0
    trd.loc[(trd[‘SibSp‘]>=3), ‘SibSp‘] = 3
    trd.loc[(trd[‘Parch‘]>=3),‘Parch‘] = 3
    trd.Sex[trd.Sex=="female"]=0
    trd.Sex[trd.Sex=="male"]=1
    trd.Embarked[trd.Embarked=="C"]=0
    trd.Embarked[trd.Embarked=="S"]=1
    trd.Embarked[trd.Embarked=="Q"]=2
    trd.Embarked[trd.Embarked.isnull()]=3
data_sd(trd)       # 訓練數據數值化
data_sd(tsd)       # 測試數據數值化

# 隨機森林填補缺失的年齡屬性
def set_missing_ages(df):
    df1= df[[‘Age‘, ‘Pclass‘, ‘Fare‘, "Embarked",‘Cabin‘,‘Parch‘, ‘SibSp‘]][df.Fare.notnull()]  # 提取特征較顯著的幾個屬性數據
    y = df1[df1.Age.notnull()].values[:, 0]    # 提取有年齡乘客的年齡數據
    x = df1[df1.Age.notnull()].values[:, 1:]   # 提取有年齡乘客的其它屬性數據
    rfr = RandomForestRegressor(random_state=0, n_estimators=2000, n_jobs=-1)  # 定義隨機森林
    rfr.fit(x, y)                              # 進行訓練
    predictedAges = rfr.predict(df1[df1.Age.isnull()].values[:, 1:])  # 進行預測。
    df.loc[(df.Age.isnull()), ‘Age‘] = predictedAges                  # 用得到的預測結果填補原缺失數據
    return df, rfr
trd, rfr = set_missing_ages(trd)                   # 調用年齡填補函數
trd.Age=trd.Age.astype(np.int32)                   # 年齡數據換為整數
tsd, rfr = set_missing_ages(tsd)                   # 調用年齡填補函數
tsd.Age=tsd.Age.astype(np.int32)                   # 年齡數據換為整數


# 年齡數據規範化
import sklearn.preprocessing as prc
def data_asd(trd):
    mmsc= prc.MinMaxScaler(feature_range=(0, 1))    # 年齡數據規範區間(0,1)
    T=np.array([trd.Age]).transpose()               # 年齡數據加維、數組化、取轉置。才能順利進行規範化操作。
    trd_d=mmsc.fit_transform(T).transpose()[0]      # 數據規範化,轉置回來,取一維。
    trd["Age_mmsc"]=trd_d                           # 規範化的年齡數據拼接到原數據
data_asd(trd)
data_asd(tsd)


# 票價數據規範化
def data_fsd(trd):
    trd.Fare[trd.Fare.isnull()]=trd.Fare.mean()      # 空缺票價填充為平均值
    mmsc= prc.MinMaxScaler(feature_range=(0, 1))     # 票價數據規範區間(0,1)
    T=np.array([trd.Fare]).transpose()               # 票價數據加維、數組化、取轉置。才能順利進行規範化操作。
    trd_d=mmsc.fit_transform(T).transpose()[0]       # 數據規範化,轉置回來,取一維。
    trd["Fare_mmsc"]=trd_d                           # 規範化的票價數據拼接到原數據
data_fsd(trd)
data_fsd(tsd)

 

四、建模及結果輸出 

 對於8個屬性,一共可以有$c^1_8+c^2_8+...+c^8_8=255$種特征組合。對每種特征組合,我們用訓練集進行交叉驗證,並在指定標準差範圍內,選取出平均分最高的特征組合。

  采用k-鄰近算法、邏輯回歸、SVM、決策樹等方法進行建模,下面為k-鄰近算法代碼,其余方法代碼框架與其類似:

# k-鄰近算法
score=[]                  # 記錄評分的列表
temp0=[]                  # 記錄當前選取的特征組合的評分
temp1=0                   # 記錄當前選取組合的平均分數
temp2=0                   # 記錄當前選取組合的分數標準差
z=["Pclass","Sex","Embarked","Age_mmsc","Cabin","Fare_mmsc",‘SibSp‘,‘Parch‘]        # 用於生成特征組合的完整屬性列表
for j in range(1,9):
    for i in itertools.combinations(z, j):                     # 取包含j個屬性的特征組合
        i=list(i)

        # 交叉驗證庫,將訓練集進行切分交叉驗證取平均
        from sklearn import cross_validation
        from sklearn.model_selection import cross_val_score
        knc_kf=KNeighborsClassifier()                          # 定義一個k-鄰近分類器
        x =trd[i]                                           
        y =trd["Survived"]
        score=cross_val_score(knc_kf, x, y, cv=5)              # k為5的交叉驗證分數列表

        if (score.mean() > temp1 and score.std() < 0.016):     # 特征組合選取條件,在指定標準差範圍內,平均分最大
            temp0 = score
            temp1 = score.mean()
            temp2 = score.std()
            dict = {temp1: i}                      # 字典,key為平均分數,value為當前選取的特征組合

c =dict[temp1]                           # 最終選取的特征組合,用於建模

# K-鄰近算法建模
knc1 = KNeighborsClassifier()              # 定義一個K-鄰近分類器
x_trd = trd[c]
y_trd = trd["Survived"]
knc1.fit(x_trd, y_trd)                     # 訓練模型
x_tsd = tsd[c]
y_tsd = knc1.predict(x_tsd)                # 進行預測
result = pd.DataFrame({‘PassengerId‘: tsd[‘PassengerId‘].values, ‘Survived‘: y_tsd.astype(np.int32)})  # 預測結果改為要求的格式
result.to_csv("../result/result_knc.csv", index=False)      # 輸出結果

  

  在提交的結果中k-鄰近算法得分相對較高,為0.78947,相應特征組合為["Pclass","Sex","Embarked","Age_mmsc"]。

  嘗試模型融合,方法為在k-鄰近算法基礎上,用另外幾種算法結果進行優化,多次嘗試後,得分沒有得到提高,不再詳述。

   

kaggle練習項目—泰坦尼克乘客生還預測