kaggle練習項目—泰坦尼克乘客生還預測
一、問題復述
泰坦尼克號是一艘英國皇家郵輪,在當時是全世界最大的海上船舶。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練習項目—泰坦尼克乘客生還預測