Titanic: kaggle入門實踐-top10%(附Python代碼)

Titanic是kaggle的一個入門級比賽,也是目前參賽隊伍最多的比賽,有7000多支隊伍。

問題背景:大家非常熟悉的【Jack and Rose】 的故事,豪華游輪沉沒,大家逃生,逃生的結果有生有死,那麼現在就需要我們根據已有的數據來預測這些乘客裡面哪些是生哪些是死

一、獲取數據

import pandas as pdndata_train = pd.read_csv(/Titanic/train.csv)ndata_test = pd.read_csv(/Titanic/test.csv)n

二、熟悉數據

data_train.head(5)n

data_test.head(5)n

查看訓練數據和測試數據的前5條,測試數據比訓練數據少Survived欄位,這也是我們最終的結果要得到並上傳的

  • PassengerId => 乘客ID
  • Survived => 是否存活(測試數據裡面需要我們預測的)
  • Pclass => 乘客等級(1/2/3等艙位)
  • Name => 乘客姓名
  • Sex => 性別
  • Age => 年齡
  • SibSp => 堂兄弟/妹個數
  • Parch => 父母與小孩個數
  • Ticket => 船票信息
  • Fare => 票價
  • Cabin => 客艙
  • Embarked => 登船港口

總共12列數據,其中Survive的一列在測試數據裡面是需要我們來預測的,其他11列數據則是乘客的特徵,供我們在後面的數據分析中使用(預測時不是所有的特徵都需要使用)

數據整體情況:

data_train.info()n

訓練數據總共是891條數據

Age只有714條,缺失177條

Cabin只有204條,缺失687條,缺失比較嚴重

Embarked缺失2條數據

數值類型數據分布:

data_train.describe()n

1、大概38%左右的人最後獲救

2、Pclass中2、3船艙的人要比1多

3、不到1%的乘客年齡在65-80(最大的是80)

4、大多數乘客沒有攜帶家屬

5、票價差異比較大,只有1%不到的乘客購買了512$的船票

離散類型數據分布:

data_train.describe(include=[np.object])n

1、Name屬性是唯一,沒有重名

2、性別就男和女(那時應該還沒有人妖吧)

3、Ticket不是唯一的,891名乘客只有681張船票,說明可能有的是和親人共用一張船票的

4、登錄港口是3個

三、初探各數據與Survived之間的關聯性

離散型特徵與Survived之間的關聯性

1、Pclass與Survived

data_train[[Pclass, Survived]].groupby([Pclass], as_index=False).mean().sort_values(by=Survived, ascending=False)n

Pclass的數值越小最後獲救的概率越大,並且很明顯,後期可做為一個建模特徵

2、Sex與Survived

data_train[[Sex, Survived]].groupby([Sex], as_index=False).mean().sort_values(by=Survived, ascending=False)n

Sex中女性的獲救概率遠遠高於男性,畢竟女士和小孩先走

3、SibSp與Survived

data_train[[SibSp, Survived]].groupby([SibSp], as_index=False).mean().sort_values(by=Survived, ascending=False)n

兄弟姐妹個數似乎與Survived之間沒有明顯的關聯

4、Parch與Survived

data_train[[Parch, Survived]].groupby([Parch], as_index=False).mean().sort_values(by=Survived, ascending=False)n

父母子女個數似乎與Survived之間也沒有明顯的關聯,但是後期可以考慮合併SibSp和Parch兩個特徵建立一個新的特徵FamilySize,畢竟we are family,親人之間是不可分割的

5、Embarked與Survived

data_train[[Embarked, Survived]].groupby([Embarked], as_index=False).mean().sort_values(by=Survived, ascending=False)n

Enbarked中C的獲救概率要高於Q和S

連續型特徵與Survived之間的關聯性

字不如表,表不如圖,接下來的數據探索過程都用圖來繪製

導入畫圖需要的包

import seaborn as snsnimport matplotlib as pltn

1、Age與Survived

grid = sns.FacetGrid(data_train, Survived)ngrid.map(ply.hist, Age, bins=20)nsns.set_style(darkgrid)#設置背景nplt.show()n

  • 嬰兒(Age<4)的存活概率較大
  • 年長者(Age=80)存活下來了
  • 大部分青年(15~25)沒有存活下來

2、Age/Pclass和Survived

grid = sns.FacetGrid(data_train, Survived, size=2.2, aspect=1.6)ngrid.map(plt.hist, Age, alpha=0.5, bins=20)nsns.set_style(darkgrid)nplt.show()n

  • Pclass=1中大部分乘客都存活下來了
  • Pclass=2和Pclass=3中嬰兒的存活概率較高
  • Pclass=3的乘客最多,但是大部分都沒有存活下下來

3、Embarked/Sex/Fare和Survived

grid = sns.FacetGrid(data_train, row=Embarked, col=Survived, size=2.2, aspect=1.6)ngrid.map(sns.barplot, Sex, Fare, alpha=0.5, ci=None)nsns.set_style(darkgrid)nplt.show()n

  • 票價越高存活率越大
  • Embarked=C中男性的存活率比較高,但可能與Pclass的特徵相關

4、Cabin屬性裡面缺失值較多,那我們先來看看有Cabin和沒Cabin得乘客的獲救情況是什麼樣的

fig = plt.figure()nfig.set(alpha=0.2)nSurvived_cabin = data_train.Survived[pd.notnull(data_train.Cabin)].value_counts()nSurvived_nocabin = data_train.Survived[pd.isnull(data_train.Cabin)].value_counts()ndf=pd.DataFrame({u有:Survived_cabin, u無:Survived_nocabin}).transpose()ndf.plot(kind=bar, stacked=True)nplt.title(u"按Cabin有無看獲救情況")nplt.xlabel(u"Cabin有無") nplt.ylabel(u"人數")nplt.show()n

  • 有Cabin的似乎獲救的概率要高一點

四、特徵處理

數據整體和特徵了解之後就可以把我們需要的特徵轉化為想要的數據,以及對一些比較重要的數據進行缺失填補

1、Name屬性的處理

for dataset in combine:n dataset[Title] = dataset.Name.str.extract(([A-Za-z]+)., expand=False)n dataset[Title] = dataset[Title].replace([Lady, Countess, Col, Don, Dr, Major, Rev, Sir, Jonkheer, Dona], Rare)n dataset[Title] = dataset[Title].replace(Mlle, Miss)n dataset[Title] = dataset[Title].replace(Ms, Miss)n dataset[Title] = dataset[Title].replace(Mme, Mrs)n title_map = {Mr: 1, Miss: 2, Mrs: 3, Master: 4, Rare: 5}n dataset[Title] = dataset[Title].map(title_map)n dataset[Title] = dataset[Title].fillna(0)n

通過處理髮現Miss和Mrs以及Master的存活概率要高很多,是一個不錯的特徵

2、對SibSp和Parch屬性處理

for dataset in combine:n dataset[FamilySize] = dataset[SibSp] + dataset[Parch] + 1n dataset[FamilySizePlus] = 0n dataset.loc[dataset[FamilySize] == 1, FamilySizePlus] = 1n dataset.loc[dataset[FamilySize] == 2, FamilySizePlus] = 2n dataset.loc[dataset[FamilySize] == 3, FamilySizePlus] = 2n dataset.loc[dataset[FamilySize] == 4, FamilySizePlus] = 2n dataset.loc[dataset[FamilySize] == 5, FamilySizePlus] = 1n dataset.loc[dataset[FamilySize] == 6, FamilySizePlus] = 1n dataset.loc[dataset[FamilySize] == 7, FamilySizePlus] = 1n

上面對SibSp和Parch兩個特徵處理的時候發現兩個單獨的特徵對Survived並沒有明顯的影響,所以構建新的特徵FamilySize,但是發現並不是FamilySize越大存活率越高,是否有家庭對存活率的影響也不是很明顯,有的博客採取的做法是構建新的IsAlone特徵來處理,但是最終的效果不是很好,所以我選擇構建FamilySizePlus這個新的特徵,主要是將一些FamilySize進行分組處理

3、對Sex屬性的處理

for dataset in combine:n dataset[Sex] = dataset[Sex].map({female: 1, male: 0}).astype(int)n

轉化為數字,很easy

4、對Age屬性的處理

  • 填補缺失值

guess_ages = np.zeros((2, 3))nfor dataset in combine:n for i in range(0, 2):n for j in range(0, 3):n guess_df = dataset[(dataset[Sex] == i) & (dataset[Pclass] == j+1)][Age].dropna()n age_guess = guess_df.median()n guess_ages[i,j] = int(age_guess / 0.5 + 0.5) * 0.5n for i in range(0, 2):n for j in range(0, 3):n dataset.loc[(dataset.Age.isnull()) & (dataset.Sex == i) & (dataset.Pclass == j + 1), Age] = guess_ages[i, j]n dataset[Age] = dataset[Age].astype(int)n

  • 分組

for dataset in combine: n dataset.loc[ dataset[Age] <= 16, Age] = 0n dataset.loc[(dataset[Age] > 16) & (dataset[Age] <= 32), Age] = 1n dataset.loc[(dataset[Age] > 32) & (dataset[Age] <= 48), Age] = 2n dataset.loc[(dataset[Age] > 48) & (dataset[Age] <= 64), Age] = 3n dataset.loc[ dataset[Age] > 64, Age]n

Age缺失值較多,但是也是一個比較重要的特徵,可以採取不同的方式處理,有根據均值和標準差來隨機生成,也有根據隨機森林預測的,但是這樣的話容易引入雜訊。此處採取的做法是根據每個船艙的性別的中位數來填充,還有別的做法比如根據每個船艙不同的title的值來填充。

然後對Age的年齡分段,可以嘗試分四組或者分五組來分別看每種分法下的各組存活率,同時也可以處理為成年和未成年

5、Embarked屬性處理

  • 填補缺失值

freq_port = data_train.Embarked.dropna().mode()[0]nfor dataset in combine:n dataset[Embarked] = dataset[Embarked].fillna(freq_port)n

  • 特徵值轉換

for dataset in combine:n dataset[Embarked] = dataset[Embarked].map({S: 0, C: 1, Q: 2})n

缺失值很少,此處選擇用出現次數最多的港口來填充

6、Fare屬性處理

  • 填補缺失值

data_test[Fare].fillna(data_test[Fare].dropna().median(), inplace=True)n

  • 分組

for dataset in combine:n dataset.loc[ dataset[Fare] <= 7.91, Fare] = 0n dataset.loc[(dataset[Fare] > 7.91) & (dataset[Fare] <= 14.454), Fare] = 1n dataset.loc[(dataset[Fare] > 14.454) & (dataset[Fare] <= 31), Fare] = 2n dataset.loc[ dataset[Fare] > 31, Fare] = 3n dataset[Fare] = dataset[Fare].astype(int)n

測試集裡面有Fare缺失,用中位數來填充,然後進行分組處理

7、Cabin屬性處理

for dataset in combine:n dataset.loc[(dataset.Cabin.isnull()), Cabin] = 0n dataset.loc[(dataset.Cabin.notnull()), Cabin] = 1n

Cabin屬性簡單的做有無處理,在前面的可視化裡面發現其實Cabin的有無對於存活率的影響不是很大,所以在後面的建模裡面要考慮對這個特徵的處理

PS:個人覺得對於Cabin特徵可以考慮首字母的存活率,因為首字母可能對應船艙的位置,對乘客存活有一定的影響,後面有空了再對這一特徵進行處理

8、Ticket屬性處理

df = data_train[Ticket].value_counts()ndf = pd.DataFrame(df)ndf = df[df[Ticket] > 1]ndf_ticket = df.index.values #共享船票的票號ntickets = data_train.Ticket.values #所有的船票nresult = []nfor ticket in tickets:n if ticket in df_ticket:n ticket = 1n else:n ticket = 0 #遍歷所有船票,在共享船票裡面的為1,否則為0n result.append(ticket)nresults = pd.DataFrame(result)nresults.columns = [Ticket_Count]ndata_train = pd.concat([data_train, results], axis=1)n

ticket主要考慮有乘客共享船票的,如果是一個家庭的那麼很大的可能性是一家人都活了或者都遇難 ,但是在船票共享裡面並不是所有的家庭共享的,還有一種情況是跟朋友一起共享的,同時並不是共享了船票最後的存活率就一定高,可能有的共享船票的存活率反而比較低,這可能也跟Pclass和Cabin有關係,這一塊兒還可以再深入挖掘。

PS:這段代碼應該有更簡潔的代碼,如果有大神幫忙寫出更簡潔的代碼,感激不盡!

五、創建模型

1、數據準備

#把不需要的特徵丟掉ndata_train = data_train.drop([PassengerId, Name, SibSp, Parch, Ticket, FamilySize], axis=1)ndata_test_X = data_test.drop([PassengerId, Name, SibSp, Parch, Ticket, FamilySize], axis=1).copy()n#選取各數據對應的特徵nX_train = data_train[[Pclass, Sex, Age, Fare, Embarked, Cabin, Title, FamilySizePlus, Ticket_Count]]nY_train = data_train[Survived]nX_test = data_test_Xn

2、模型訓練比較

# Logistic Regressionnlogreg = LogisticRegression()nlogreg.fit(X_train, Y_train)nY_pred = logreg.predict(X_test)nacc_log = round(logreg.score(X_train, Y_train) * 100, 2)nn# Support Vector Machinesnsvc = SVC()nsvc.fit(X_train, Y_train)nY_pred = svc.predict(X_test)nacc_svc = round(svc.score(X_train, Y_train) * 100, 2)nn# k-Nearest Neighborsnknn = KNeighborsClassifier(n_neighbors = 3)nknn.fit(X_train, Y_train)nY_pred = knn.predict(X_test)nacc_knn = round(knn.score(X_train, Y_train) * 100, 2)nn# Gaussian Naive Bayesngaussian = GaussianNB()ngaussian.fit(X_train, Y_train)nY_pred = gaussian.predict(X_test)nacc_gaussian = round(gaussian.score(X_train, Y_train) * 100, 2)nn# Linear SVCnlinear_svc = LinearSVC()nlinear_svc.fit(X_train, Y_train)nY_pred = linear_svc.predict(X_test)nacc_linear_svc = round(linear_svc.score(X_train, Y_train) * 100, 2)nn# Stochastic Gradient Descentnsgd = SGDClassifier()nsgd.fit(X_train, Y_train)nY_pred = sgd.predict(X_test)nacc_sgd = round(sgd.score(X_train, Y_train) * 100, 2)nn# Decision Treendecision_tree = DecisionTreeClassifier()ndecision_tree.fit(X_train, Y_train)nY_pred = decision_tree.predict(X_test)nacc_decision_tree = round(decision_tree.score(X_train, Y_train) * 100, 2)nn# Random Forestnrandom_forest = RandomForestClassifier(n_estimators=100)nrandom_forest.fit(X_train, Y_train)nY_pred = random_forest.predict(X_test)nrandom_forest.score(X_train, Y_train)nacc_random_forest = round(random_forest.score(X_train, Y_train) * 100, 2)nnmodels = pd.DataFrame({n Model: [Support Vector Machines, KNN, Logistic Regression, n Random Forest, Naive Bayes, n Stochastic Gradient Decent, Linear SVC, n Decision Tree],n Score: [acc_svc, acc_knn, acc_log, n acc_random_forest, acc_gaussian, n acc_sgd, acc_linear_svc, acc_decision_tree]})nprint(models.sort_values(by=Score, ascending=False))n

測試不同的模型的得分情況,選擇得分最高的那一個(隨機森林)

PS:這段代碼是在pycharm裡面跑的,所以跟上面那些在jupyter跑的代碼和結果顯示有一點區別

3、各特徵的重要性

features = pd.DataFrame()nfeatures[Feature] = X_train.columnsnfeatures[importance] = random_forest.feature_importances_nprint(features)n

4、模型調參

forest = RandomForestClassifier(max_features=sqrt)nparameter_grid = {n max_depth : [4,5,6,7,8],n n_estimators: [100,200,250,500],n criterion: [gini,entropy]n }ncross_validation = StratifiedKFold(Y_train, n_folds=5)ngrid_search = GridSearchCV(forest,n param_grid=parameter_grid,n cv=cross_validation)ngrid_search.fit(X_train, Y_train)nprint(Best score: {}.format(grid_search.best_score_))nprint(Best parameters: {}.format(grid_search.best_params_))n

調參個人覺得也是很重要的,一個調參讓自己的得分提高了0.02,排名提高有1千多名,所以效果還是很不錯的。

n_estimators:弱學習器的最大迭代次數,或者說最大的弱學習器的個數,默認是100,一般來說n_estimators太小,容易欠擬合,太大容易過擬合

criterion:即CART樹做劃分時對特徵的評價標準

max_depth:決策樹最大深度

5、結果預測

random_forest = RandomForestClassifier(n_estimators=250, max_depth=5, criterion=gini)nrandom_forest.fit(X_train, Y_train)nY_pred = random_forest.predict(X_test)n

六、提交預測結果

submission = pd.DataFrame({n "PassengerId": data_test["PassengerId"],n "Survived": Y_predn })nsubmission.to_csv(submission.csv, index=False)n

最終得分:0.8061

排名:667(top10%)

總結:

整個過程收穫很多,需要學習的地方也很多

PS:我自己建了一個公眾號,主要記錄一些自己的數據學習筆記,一方面是鞏固,另一方面也是為了督促自己去學習。做為一名初學者還有很多的知識要學,如果你也跟我一樣是一名初學者歡迎你關注我的公眾號,可以一起討論一起學習

關注公眾號數據實踐筆記(掃描二維碼即可)回復Titanic給你數據和代碼(為了學習和交流而不是單純的跑一遍代碼得到一個分數)

aHR0cDovL3dlaXhpbi5xcS5jb20vci85VHZjeEo3RWQ4TUlyZHFuOTI3bg== (二維碼自動識別)


推薦閱讀:

Zillow Prize競賽系列--(一)競賽簡介
Kaggle求生:亞馬遜熱帶雨林篇
2016 CCF大數據與計算智能大賽的開源資料整理
數據挖掘系列篇(27):Kaggle 數據挖掘比賽經驗分享

TAG:Python | 「泰坦尼克号」沉没事故 | Kaggle |