數據挖掘算法和實踐(五):泰坦尼克號titanic的乘客生存預測模型剖析

titanic乘客的生存預測是數據挖掘的入門級實例,根據船上乘客的多維特徵預測事故發生後乘客的生還機率,屬於監督學習中典型的分類問題。本文結合對數據挖掘流程的理解和經典案列,呈現數據挖掘過程。

該模型屬於監督學習,需要訓練集和數據集:

數據集地址:https://www.kaggle.com/omarelgabry/titanic/a-journey-through-titanic

該文章同步更新在公衆號:數據社,歡迎關注!

什麼是數據挖掘

數據分析/挖掘是以概率論、線性代數、統計學、信息論爲基礎,根據之前接觸到的數據挖掘流程,可定義爲:數據準備-->數據探索--> 數據預處理-->特徵工程-->模型建立-->模型評估,其中數據探索、數據預處理、特徵工程針對某一屬性同時進行。

構建模型的前幾個步驟佔數據挖掘工作量的80%以上,剩下20%優化模型,構建模型和模型評估幾行代碼可以搞定,若是數據質量無非保證,模型質量無從談起;

數據挖掘常用工具包

工具使用anaconda自帶的notebook,首先引入pandas的DataFrame對象,numpy包,matplotlib包,seaborn包;

import pandas as pd
from pandas import DataFrame
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier

數據挖掘流程

一、數據準備

pandas讀取並查看數據集,注意文件分隔符:

titanic_df=pd.read_csv('d:\\yzg\\ML_data\\titanic\\train.csv', dtype={"Age": np.float64}, )
test_df=pd.read_csv('d:\\yzg\\ML_data\\titanic\\test.csv', dtype={"Age": np.float64}, )
titanic_df.head()

可以看出測試數據集的passageId是唯一ID,其他屬性表徵了着乘客這一entry的特徵。

titanic_df.info()
print("----------------------------")

二、數據探索、數據預處理、特徵工程

數據探索的過程要求有很高的數據嗅覺,根據數據的分佈和相關性分析快速做好特徵工程和模型構建。數據導入後開始進行數據集探索,首先是查看數據的完整性,上圖可看出訓練集包含891行,12個屬性,其中cabin字段數據缺失率高,考慮丟棄,passengerID和name屬性無表徵含義,考慮丟棄。

test_df.info()
titanic_df = titanic_df.drop(['PassengerId','Name','Ticket'], axis=1)
test_df    = test_df.drop(['Name','Ticket'], axis=1)

1、數據探索

Embarked字段,字符型,表徵登錄的港口信息,思路是將其進行啞變量處理,該字段有2個null值,值域爲“S,Q,C”:

titanic_df.Embarked[titanic_df.Embarked.isnull()

titanic_df.groupby('Embarked').Survived.count()

titanic_df.groupby('Embarked').Survived.count()

Embarked的每個值對應的人數有統計量,發現基本上大部分取值都是'S'。因此將兩個空值用出現次數最多的'S'來填補 (如果是數值int類型,並且缺失率在可接受範圍內(<20%)可以用均值、中位數來填補)。開始進行數據可視化探索,seaborn是面向對象的數據可視化包;

titanic_df["Embarked"] = titanic_df["Embarked"].fillna("S")
sns.catplot('Embarked','Survived',data=titanic_df,height=3,aspect=3)

生成一個畫布包括3個圖(2個生還統計圖和1個生還概率圖)均值圖,titanic_df[['Embarked','Survived"]].groupby(['Embarked']相當於sql語句中的groupby函數,mean()函數對它計算均值後,生成了一個數據框DataFrame:

fig,(axis1,axis2,axis3) = plt.subplots(1,3,figsize=(15,5))
sns.countplot(x='Embarked', data=titanic_df, ax=axis1)
sns.countplot(x='Survived', hue="Embarked", data=titanic_df, order=[1,0], ax=axis2)
embark_perc = titanic_df[['Embarked','Survived']].groupby(["Embarked"],as_index=False).mean()
sns.barplot(x='Embarked', y='Survived', data=embark_perc,order=['S','C','Q'],a

2、數據處理

啞變量變黃:使用pd.get_dummies()方法得到Embarked這個變量的指標,類似於列轉行,將Embarked的三個值域變成S、C、Q三個特徵屬性(字段),樣本集和數據集作同樣處理,刪除S這一屬性:

embark_dummies_titanic  = pd.get_dummies(titanic_df['Embarked'])
embark_dummies_titanic.drop(['S'], axis=1, inplace=True)
embark_dummies_test  = pd.get_dummies(test_df['Embarked'])
embark_dummies_test.drop(['S'], axis=1, inplace=True)
titanic_df = titanic_df.join(embark_dummies_titanic)
test_df    = test_df.join(embark_dummies_test)

將原來的Emabrked這個特徵屬性刪除:

titanic_df.drop(['Embarked'], axis=1,inplace=True)
test_df.drop(['Embarked'], axis=1,inplace=True)

Fare字段表徵票價,整型,通過常規的統計分析和可視化分析進行數據探索(這裏未做歸一化處理),數值類型可以用describe方法查看統計特性:

test_df.Fare.describe()

# 發現test數據中有一個Fare變量是空值,用fillna()方法填充中值:
test_df["Fare"].fillna(test_df["Fare"].median(), inplace=True)
# 數據處理轉換,將float轉換成int類型:
titanic_df['Fare'] = titanic_df['Fare'].astype(int)
test_df['Fare']    = test_df['Fare'].astype(int)
# 分別得到Fare變量對應的倖存和沒有幸存的記錄,(這種引用很像R語言中的which()函數):
fare_not_survived = titanic_df["Fare"][titanic_df["Survived"] == 0]
fare_survived     = titanic_df["Fare"][titanic_df["Survived"] == 1]
# 轉換成數據框DataFrame,並作圖出來:
avgerage_fare = DataFrame([fare_not_survived.mean(), fare_survived.mean()])
std_fare      = DataFrame([fare_not_survived.std(), fare_survived.std()])
titanic_df['Fare'].plot(kind='hist', figsize=(10,3),bins=100, xlim=(0,50))

# 求均值和標準差
titanic_df['Fare'].plot(kind='hist', figsize=(10,3),bins=100, xlim=(0,50))
# 進行可視化探索
avgerage_fare.index.names = std_fare.index.names = ["Survived"]
avgerage_fare.plot(yerr=std_fare,kind='bar',legend=False)

age特徵屬性,面向對象畫2個圖,分別設置title:

fig, (axis1,axis2) = plt.subplots(1,2,figsize=(15,4))
axis1.set_title('Original Age values - Titanic')
axis2.set_title('New Age values - Titanic')

average_age_titanic   = titanic_df["Age"].mean()
std_age_titanic       = titanic_df["Age"].std()
count_nan_age_titanic = titanic_df["Age"].isnull().sum()

average_age_test   = test_df["Age"].mean()
std_age_test       = test_df["Age"].std()
count_nan_age_test = test_df["Age"].isnull().sum()

# 隨機生產年齡填充空值
# generate random numbers between (mean - std) & (mean + std)
rand_1 = np.random.randint(average_age_titanic - std_age_titanic, average_age_titanic + std_age_titanic, size = count_nan_age_titanic)
rand_2 = np.random.randint(average_age_test - std_age_test, average_age_test + std_age_test, size = count_nan_age_test)

titanic_df['Age'].dropna().astype(int).hist(bins=70, ax=axis1)
test_df['Age'].dropna().astype(int).hist(bins=70, ax=axis1)

titanic_df["Age"][np.isnan(titanic_df["Age"])] = rand_1
test_df["Age"][np.isnan(test_df["Age"])] = rand_2

titanic_df['Age'] = titanic_df['Age'].astype(int)
test_df['Age']    = test_df['Age'].astype(int)

titanic_df['Age'].hist(bins=70, ax=axis1)
test_df['Age'].hist(bins=70, ax=axis2)

繼續作圖,seaborn的FaceGrid()方法,需要查一下:

facet = sns.FacetGrid(titanic_df, hue="Survived",aspect=4)
facet.map(sns.kdeplot,'Age',shade= True)
facet.set(xlim=(0, titanic_df['Age'].max()))
facet.add_legend()

# 每個年齡的存活率:
fig, axis1 = plt.subplots(1,1,figsize=(18,4))
average_age = titanic_df[["Age", "Survived"]].groupby(['Age'],as_index=False).mean()
sns.barplot(x='Age', y='Survived', data=average_age)

Cabin特徵變量,表徵的是船艙號,無任何表徵意義被捨棄。

titanic_df.shap
 titanic_df.Cabin.count()
 # 發現總共891個記錄,只有204個記錄是非空的,而且它是一個字符型的,質量太差所以這個變量被刪除了。
titanic_df.drop("Cabin",axis=1,inplace=True)
test_df.drop("Cabin",axis=1,inplace=True)

整合/規約Parch和SibSp特徵屬性表徵的是乘客的家庭信息(sibsp 是sibling spouse的縮寫就是堂兄妹和配偶數量,parch是父母小孩的個數),這裏涉及到數據規約思想,將Parch和SibSp變量整合爲一個Famliy變量,作爲一個取值爲0和1的標籤變量。

titanic_df.Parch.describe()
titanic_df.Parch[titanic_df.Parch!=0].count()
titanic_df.SibSp[titanic_df.SibSp!=0].count()
# 這兩個屬性,只有極少數不是0值,故:
titanic_df['Family'] =  titanic_df["Parch"] + titanic_df["SibSp"]
titanic_df['Family'].loc[titanic_df['Family'] > 0] = 1
titanic_df['Family'].loc[titanic_df['Family'] == 0] = 0

test_df['Family'] =  test_df["Parch"] + test_df["SibSp"]
test_df['Family'].loc[test_df['Family'] > 0] = 1
test_df['Family'].loc[test_df['Family'] == 0] = 0

作圖,觀察和一家人出行和獨自一個人的差別:

titanic_df = titanic_df.drop(['SibSp','Parch'], axis=1)
test_df    = test_df.drop(['SibSp','Parch'], axis=1)

fig, (axis1,axis2) = plt.subplots(1,2,sharex=True,figsize=(10,5))
sns.countplot(x='Family', data=titanic_df, order=[1,0], ax=axis1)

family_perc = titanic_df[["Family", "Survived"]].groupby(['Family'],as_index=False).mean()

sns.barplot(x='Family', y='Survived', data=family_perc, order=[1,0], ax=axis2)
axis1.set_xticklabels(["With Family","Alone"], rotation=0)

Sex屬性轉換,這裏採用數據分箱方法,定義一個函數來判斷age是否超過16歲,小於16歲分類爲’child’,大於16歲保留性別,然後進行啞變量處理增加Child和Female兩個標籤變量;

def get_person(passenger):
    age,sex = passenger
    return 'child' if age < 16 else sex
# 分類
titanic_df['Person'] = titanic_df[['Age','Sex']].apply(get_person,axis=1)
test_df['Person']    = test_df[['Age','Sex']].apply(get_person,axis=1)
# 刪除sex
titanic_df.drop(['Sex'],axis=1,inplace=True)
test_df.drop(['Sex'],axis=1,inplace=True)

列轉行,把child','Female','Male'變成標籤屬性,刪除male這一屬性;

person_dummies_titanic  = pd.get_dummies(titanic_df['Person'])
person_dummies_titanic.columns = ['Child','Female','Male']
person_dummies_titanic.drop(['Male'], axis=1, inplace=True)

person_dummies_test  = pd.get_dummies(test_df['Person'])
person_dummies_test.columns = ['Child','Female','Male']
person_dummies_test.drop(['Male'], axis=1, inplace=True)

titanic_df = titanic_df.join(person_dummies_titanic)
test_df    = test_df.join(person_dummies_test)
fig, (axis1,axis2) = plt.subplots(1,2,figsize=(10,5))
sns.countplot(x='Person', data=titanic_df, ax=axis1)
person_perc = titanic_df[["Person", "Survived"]].groupby(['Person'],as_index=False).mean()
sns.barplot(x='Person', y='Survived', data=person_perc, ax=axis2, order=['male','female','child'])

# 刪除Person 屬性
titanic_df.drop(['Person'],axis=1,inplace=True)
test_df.drop(['Person'],axis=1,inplace=True)

Pclass屬性表徵船艙等級,字符型,採用啞變量變換成新得標籤變量;

sns.factorplot('Pclass','Survived',order=[1,2,3], data=titanic_df,size=5)

將Pclass的三個取值做成標籤變量,並刪除train和test中的class_3變量,因爲它的倖存率太低。、

pclass_dummies_titanic  = pd.get_dummies(titanic_df['Pclass'])
pclass_dummies_titanic.columns = ['Class_1','Class_2','Class_3']
pclass_dummies_titanic.drop(['Class_3'], axis=1, inplace=True)

pclass_dummies_test  = pd.get_dummies(test_df['Pclass'])
pclass_dummies_test.columns = ['Class_1','Class_2','Class_3']
pclass_dummies_test.drop(['Class_3'], axis=1, inplace=True)

titanic_df.drop(['Pclass'],axis=1,inplace=True)
test_df.drop(['Pclass'],axis=1,inplace=True)

titanic_df = titanic_df.join(pclass_dummies_titanic)
test_df    = test_df.join(pclass_dummies_test)

三、構建模型

至此,數據預處理和數據探索結束,開始選擇模型,既然是一個二分類問題,首先考慮邏輯迴歸和決策樹算法。(關於常用的數據挖掘算法,將在後續文章更新講解)

X_train = titanic_df.drop("Survived",axis=1)
Y_train = titanic_df["Survived"]
X_test  = test_df.drop("PassengerId",axis=1).copy()

應用sklearn中封裝的算法包,首先用邏輯迴歸去擬合Xtrain和Ytrain,然後用logreg.predict()函數去預測X_test的數據,最後用擬合的結果去給模型打分,邏輯迴歸模型的準確率是0.808。

logreg = LogisticRegression()
logreg.fit(X_train, Y_train)
Y_pred = logreg.predict(X_test)
# 模型打分
logreg.score(X_train, Y_train)

一般集成學習算法的效果好過單個算法的效率,採用隨機森林算法,隨機森林算法的準確率是0.968:

random_forest = RandomForestClassifier(n_estimators=100)
random_forest.fit(X_train, Y_train)
Y_pred = random_forest.predict(X_test)
# 模型打分 
random_forest.score(X_train, Y_train)

最後得出數據集的預測結果

submission = pd.DataFrame({
        "PassengerId": test_df["PassengerId"],
        "Survived": Y_pred
    })

submission.to_csv('titanic.csv', index=False)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章