1. 程式人生 > >Kaggle_Titanic生存預測 -- 詳細流程吐血梳理

Kaggle_Titanic生存預測 -- 詳細流程吐血梳理

一直想在Kaggle上參加一次比賽,奈何被各種事情所拖累。為了熟悉一下比賽的流程和對資料建模有個較為直觀的認識,斷斷續續用一段時間做了Kaggle上的入門比賽:Titanic: Machine Learning from Disaster

總的來說收穫還算是挺大的吧。本來想的是隻簡單的做一下,在整個進行的過程中發現有很多好的Kernels以及資料分析的流程和方法,但是卻鮮有比較清晰直觀的流程和較為全面的分析方法。所以,本著自己強迫症的精神,同時也算對這次小比賽的一些方式方法以及繪圖分析技巧做一個較為系統的筆記,經過幾天快要吐血的整理下,本文新鮮出爐。

本文參考了若干kernels以及部落格知文,文章下方均有引用說明。

同時我在知乎上開設了關於機器學習深度學習的專欄收錄下面的內容,以方便大家在移動端的學習。歡迎關注我的知乎:大樹先生。一起學習一起進步呀!^_^

1. 資料總覽

Titanic 生存模型預測,其中包含了兩組資料:train.csvtest.csv,分別為訓練集合和測試集合。

import re
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

import warnings
warnings.filterwarnings('ignore'
) %matplotlib inline
觀察前幾行的源資料:
train_data = pd.read_csv('data/train.csv')
test_data = pd.read_csv('data/test.csv')

sns.set_style('whitegrid')
train_data.head()
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th… female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S

資料資訊總覽:

train_data.info()
print("-" * 40)
test_data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId    891 non-null int64
Survived       891 non-null int64
Pclass         891 non-null int64
Name           891 non-null object
Sex            891 non-null object
Age            714 non-null float64
SibSp          891 non-null int64
Parch          891 non-null int64
Ticket         891 non-null object
Fare           891 non-null float64
Cabin          204 non-null object
Embarked       889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.6+ KB
----------------------------------------
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 418 entries, 0 to 417
Data columns (total 11 columns):
PassengerId    418 non-null int64
Pclass         418 non-null int64
Name           418 non-null object
Sex            418 non-null object
Age            332 non-null float64
SibSp          418 non-null int64
Parch          418 non-null int64
Ticket         418 non-null object
Fare           417 non-null float64
Cabin          91 non-null object
Embarked       418 non-null object
dtypes: float64(2), int64(4), object(5)
memory usage: 36.0+ KB

從上面我們可以看出,Age、Cabin、Embarked、Fare幾個特徵存在缺失值。

繪製存活的比例:

train_data['Survived'].value_counts().plot.pie(autopct = '%1.2f%%')
<matplotlib.axes._subplots.AxesSubplot at 0x230c2508ef0>

這裡寫圖片描述

2. 缺失值處理的方法

對資料進行分析的時候要注意其中是否有缺失值。

一些機器學習演算法能夠處理缺失值,比如神經網路,一些則不能。對於缺失值,一般有以下幾種處理方法:

(1)如果資料集很多,但有很少的缺失值,可以刪掉帶缺失值的行;

(2)如果該屬性相對學習來說不是很重要,可以對缺失值賦均值或者眾數。比如在哪兒上船Embarked這一屬性(共有三個上船地點),缺失倆值,可以用眾數賦值

train_data.Embarked[train_data.Embarked.isnull()] = train_data.Embarked.dropna().mode().values
(3)對於標稱屬性,可以賦一個代表缺失的值,比如‘U0’。因為缺失本身也可能代表著一些隱含資訊。比如船艙號Cabin這一屬性,缺失可能代表並沒有船艙。
#replace missing value with U0
train_data['Cabin'] = train_data.Cabin.fillna('U0') # train_data.Cabin[train_data.Cabin.isnull()]='U0'
(4)使用迴歸 隨機森林等模型來預測缺失屬性的值。因為Age在該資料集裡是一個相當重要的特徵(先對Age進行分析即可得知),所以保證一定的缺失值填充準確率是非常重要的,對結果也會產生較大影響。一般情況下,會使用資料完整的條目作為模型的訓練集,以此來預測缺失值。對於當前的這個資料,可以使用隨機森林來預測也可以使用線性迴歸預測。這裡使用隨機森林預測模型,選取資料集中的數值屬性作為特徵(因為sklearn的模型只能處理數值屬性,所以這裡先僅選取數值特徵,但在實際的應用中需要將非數值特徵轉換為數值特徵)
from sklearn.ensemble import RandomForestRegressor

#choose training data to predict age
age_df = train_data[['Age','Survived','Fare', 'Parch', 'SibSp', 'Pclass']]
age_df_notnull = age_df.loc[(train_data['Age'].notnull())]
age_df_isnull = age_df.loc[(train_data['Age'].isnull())]
X = age_df_notnull.values[:,1:]
Y = age_df_notnull.values[:,0]
# use RandomForestRegression to train data
RFR = RandomForestRegressor(n_estimators=1000, n_jobs=-1)
RFR.fit(X,Y)
predictAges = RFR.predict(age_df_isnull.values[:,1:])
train_data.loc[train_data['Age'].isnull(), ['Age']]= predictAges
讓我們再來看一下缺失資料處理後的DataFram:
train_data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId    891 non-null int64
Survived       891 non-null int64
Pclass         891 non-null int64
Name           891 non-null object
Sex            891 non-null object
Age            891 non-null float64
SibSp          891 non-null int64
Parch          891 non-null int64
Ticket         891 non-null object
Fare           891 non-null float64
Cabin          891 non-null object
Embarked       891 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.6+ KB   

3. 分析資料關係

(1) 性別與是否生存的關係 Sex

train_data.groupby(['Sex','Survived'])['Survived'].count()
Sex Survived female 0 81 1 233 male 0 468 1 109 Name: Survived, dtype: int64
train_data[['Sex','Survived']].groupby(['Sex']).mean().plot.bar()
<matplotlib.axes._subplots.AxesSubplot at 0x230c251ab00>

這裡寫圖片描述

以上為不同性別的生存率,可見在泰坦尼克號事故中,還是體現了Lady First。

(2) 船艙等級和生存與否的關係 Pclass

train_data.groupby(['Pclass','Survived'])['Pclass'].count()
Pclass Survived 1 0 80 1 136 2 0 97 1 87 3 0 372 1 119 Name: Pclass, dtype: int64
train_data[['Pclass','Survived']].groupby(['Pclass']).mean().plot.bar()
<matplotlib.axes._subplots.AxesSubplot at 0x230c5e08b70>

這裡寫圖片描述

train_data[['Sex','Pclass','Survived']].groupby(['Pclass','Sex']).mean().plot.bar()
<matplotlib.axes._subplots.AxesSubplot at 0x230c5e2ad68>

這裡寫圖片描述

不同等級船艙的男女生存率:

train_data.groupby(['Sex', 'Pclass', 'Survived'])['Survived'].count()
Sex Pclass Survived female 1 0 3 1 91 2 0 6 1 70 3 0 72 1 72 male 1 0 77 1 45 2 0 91 1 17 3 0 300 1 47 Name: Survived, dtype: int64 從圖和表中可以看出,總體上泰坦尼克號逃生是婦女優先,但是對於不同等級的船艙還是有一定的區別。

(3) 年齡與存活與否的關係 Age

分別分析不同等級船艙和不同性別下的年齡分佈和生存的關係:

fig, ax = plt.subplots(1, 2, figsize = (18, 8))
sns.violinplot("Pclass", "Age", hue="Survived", data=train_data, split=True, ax=ax[0])
ax[0].set_title('Pclass and Age vs Survived')
ax[0].set_yticks(range(0, 110, 10))

sns.violinplot("Sex", "Age", hue="Survived", data=train_data, split=True, ax=ax[1])
ax[1].set_title('Sex and Age vs Survived')
ax[1].set_yticks(range(0, 110, 10))

plt.show()

這裡寫圖片描述

分析總體的年齡分佈:

plt.figure(figsize=(12,5))
plt.subplot(121)
train_data['Age'].hist(bins=70)
plt.xlabel('Age')
plt.ylabel('Num')

plt.subplot(122)
train_data.boxplot(column='Age', showfliers=False)
plt.show()

這裡寫圖片描述

不同年齡下的生存和非生存的分佈情況:

facet = sns.FacetGrid(train_data, hue="Survived",aspect=4)
facet.map(sns.kdeplot,'Age',shade= True)
facet.set(xlim=(0, train_data['Age'].max()))
facet.add_legend()
<seaborn.axisgrid.FacetGrid at 0x230c5e53cf8>

png

不同年齡下的平均生存率:

# average survived passengers by age
fig, axis1 = plt.subplots(1,1,figsize=(18,4))
train_data["Age_int"] = train_data["Age"].astype(int)
average_age = train_data[["Age_int", "Survived"]].groupby(['Age_int'],as_index=False).mean()
sns.barplot(x='Age_int', y='Survived', data=average_age)
<matplotlib.axes._subplots.AxesSubplot at 0x230c60135f8>

這裡寫圖片描述

train_data['Age'].describe()
count    891.000000
mean      29.668231
std       13.739002
min        0.420000
25%       21.000000
50%       28.000000
75%       37.000000
max       80.000000
Name: Age, dtype: float64

樣本有891,平均年齡約為30歲,標準差13.5歲,最小年齡為0.42,最大年齡80.

按照年齡,將乘客劃分為兒童、少年、成年和老年,分析四個群體的生還情況:

bins = [0, 12, 18, 65, 100]
train_data['Age_group'] = pd.cut(train_data['Age'], bins)
by_age = train_data.groupby('Age_group')['Survived'].mean()
by_age
Age_group
(0, 12]      0.506173
(12, 18]     0.466667
(18, 65]     0.364512
(65, 100]    0.125000
Name: Survived, dtype: float64
by_age.plot(kind = 'bar')
<matplotlib.axes._subplots.AxesSubplot at 0x230c6079e80>

這裡寫圖片描述

(4) 稱呼與存活與否的關係 Name

通過觀察名字資料,我們可以看出其中包括對乘客的稱呼,如:Mr、Miss、Mrs等,稱呼資訊包含了乘客的年齡、性別,同時也包含了如社會地位等的稱呼,如:Dr,、Lady、Major、Master等的稱呼。

train_data['Title'] = train_data['Name'].str.extract(' ([A-Za-z]+)\.', expand=False)

pd.crosstab(train_data['Title'], train_data['Sex'])
Sex female male
Title
Capt 0 1
Col 0 2
Countess 1 0
Don 0 1
Dr 1 6
Jonkheer 0 1
Lady 1 0
Major 0 2
Master 0 40
Miss 182 0
Mlle 2 0
Mme 1 0
Mr 0 517
Mrs 125 0
Ms 1 0
Rev 0 6
Sir 0 1

觀察不同稱呼與生存率的關係:

train_data[['Title','Survived']].groupby(['Title']).mean().plot.bar()
<matplotlib.axes._subplots.AxesSubplot at 0x230c61699b0>

這裡寫圖片描述

同時,對於名字,我們還可以觀察名字長度和生存率之間存在關係的可能:

fig, axis1 = plt.subplots(1,1,figsize=(18,4))
train_data['Name_length'] = train_data['Name'].apply(len)
name_length = train_data[['Name_length','Survived']].groupby(['Name_length'],as_index=False).mean()
sns.barplot(x='Name_length', y='Survived', data=name_length)
<matplotlib.axes._subplots.AxesSubplot at 0x230c61689b0>

這裡寫圖片描述

從上面的圖片可以看出,名字長度和生存與否確實也存在一定的相關性。

(5) 有無兄弟姐妹和存活與否的關係 SibSp

# 將資料分為有兄弟姐妹的和沒有兄弟姐妹的兩組:
sibsp_df = train_data[train_data['SibSp'] != 0]
no_sibsp_df = train_data[train_data['SibSp'] == 0]
plt.figure(figsize=(10,5))
plt.subplot(121)
sibsp_df['Survived'].value_counts().plot.pie(labels=['No Survived', 'Survived'], autopct = '%1.1f%%')
plt.xlabel('sibsp')

plt.subplot(122)
no_sibsp_df['Survived'].value_counts().plot.pie(labels=['No Survived', 'Survived'], autopct = '%1.1f%%')
plt.xlabel('no_sibsp')

plt.show()

這裡寫圖片描述

(6) 有無父母子女和存活與否的關係 Parch

和有無兄弟姐妹一樣,同樣分析可以得到:

parch_df = train_data[train_data['Parch'] != 0]
no_parch_df = train_data[train_data['Parch'] == 0]

plt.figure(figsize=(10,5))
plt.subplot(121)
parch_df['Survived'].value_counts().plot.pie(labels=['No Survived', 'Survived'], autopct = '%1.1f%%')
plt.xlabel('parch')

plt.subplot(122)
no_parch_df['Survived'].value_counts().plot.pie(labels=['No Survived', 'Survived'], autopct = '%1.1f%%')
plt.xlabel('no_parch')

plt.show()

這裡寫圖片描述

###(7) 親友的人數和存活與否的關係 SibSp & Parch
fig,ax=plt.subplots(1,2,figsize=(18,8))
train_data[['Parch','Survived']].groupby(['Parch']).mean().plot.bar(ax=ax[0])
ax[0].set_title('Parch and Survived')
train_data[['SibSp','Survived']].groupby(['SibSp']).mean().plot.bar(ax=ax[1])
ax[1].set_title('SibSp and Survived')
Text(0.5,1,'SibSp and Survived')

這裡寫圖片描述

train_data['Family_Size'] = train_data['Parch'] + train_data['SibSp'] + 1
train_data[['Family_Size','Survived']].groupby(['Family_Size']).mean().plot.bar()
<matplotlib.axes._subplots.AxesSubplot at 0x230c77155c0>

這裡寫圖片描述

從圖表中可以看出,若獨自一人,那麼其存活率比較低;但是如果親友太多的話,存活率也會很低。

(8) 票價分佈和存活與否的關係 Fare

首先繪製票價的分佈情況:

plt.figure(figsize=(10,5))
train_data['Fare'].hist(bins = 70)

train_data.boxplot(column='Fare', by='Pclass', showfliers=False)
plt.show()

這裡寫圖片描述

這裡寫圖片描述

train_data['Fare'].describe()
count    891.000000
mean      32.204208
std       49.693429
min        0.000000
25%        7.910400
50%       14.454200
75%       31.000000
max      512.329200
Name: Fare, dtype: float64

繪製生存與否與票價均值和方差的關係:

fare_not_survived = train_data['Fare'][train_data['Survived'] == 0]
fare_survived = train_data['Fare'][train_data['Survived'] == 1]

average_fare = pd.DataFrame([fare_not_survived.mean(), fare_survived.mean()])
std_fare = pd.DataFrame([fare_not_survived.std(), fare_survived.std()])
average_fare.plot(yerr=std_fare, kind='bar', legend=False)

plt.show()

這裡寫圖片描述

由上圖示可知,票價與是否生還有一定的相關性,生還者的平均票價要大於未生還者的平均票價。

(9) 船艙型別和存活與否的關係 Cabin

由於船艙的缺失值確實太多,有效值僅僅有204個,很難分析出不同的船艙和存活的關係,所以在做特徵工程的時候,可以直接將該組特徵丟棄。

當然,這裡我們也可以對其進行一下分析,對於缺失的資料都分為一類。

簡單地將資料分為是否有Cabin記錄作為特徵,與生存與否進行分析:

# Replace missing values with "U0"
train_data.loc[train_data.Cabin.isnull(), 'Cabin'] = 'U0'
train_data['Has_Cabin'] = train_data['Cabin'].apply(lambda x: 0 if x == 'U0' else 1)
train_data[['Has_Cabin','Survived']].groupby(['Has_Cabin']).mean().plot.bar()
<matplotlib.axes._subplots.AxesSubplot at 0x230c7566080>

png

對不同型別的船艙進行分析:

# create feature for the alphabetical part of the cabin number
train_data['CabinLetter'] = train_data['Cabin'].map(lambda x: re.compile("([a-zA-Z]+)").search(x).group())
# convert the distinct cabin letters with incremental integer values
train_data['CabinLetter'] = pd.factorize(train_data['CabinLetter'])[0]
train_data[['CabinLetter','Survived']].groupby(['CabinLetter']).mean().plot.bar()
<matplotlib.axes._subplots.AxesSubplot at 0x230c5ebcd30>

這裡寫圖片描述

可見,不同的船艙生存率也有不同,但是差別不大。所以在處理中,我們可以直接將特徵刪除。

(10) 港口和存活與否的關係 Embarked

泰坦尼克號從英國的南安普頓港出發,途徑法國瑟堡和愛爾蘭昆士敦,那麼在昆士敦之前上船的人,有可能在瑟堡或昆士敦下船,這些人將不會遇到海難。

sns.countplot('Embarked', hue='Survived', data=train_data)
plt.title('Embarked and Survived')
Text(0.5,1,'Embarked and Survived')

這裡寫圖片描述

sns.factorplot('Embarked', 'Survived', data=train_data, size=3, aspect=2)
plt.title('Embarked and Survived rate')
plt.show()

這裡寫圖片描述

由上可以看出,在不同的港口上船,生還率不同,C最高,Q次之,S最低。

以上為所給出的資料特徵與生還與否的分析。

據瞭解,泰坦尼克號上共有2224名乘客。本訓練資料只給出了891名乘客的資訊,如果該資料集是從總共的2224人中隨機選出的,根據中心極限定理,該樣本的資料也足夠大,那麼我們的分析結果就具有代表性;但如果不是隨機選取,那麼我們的分析結果就可能不太靠譜了。

(11) 其他可能和存活與否有關係的特徵

對於資料集中沒有給出的特徵資訊,我們還可以聯想其他可能會對模型產生影響的特徵因素。如:乘客的國籍、乘客的身高、乘客的體重、乘客是否會游泳、乘客職業等等。

另外還有資料集中沒有分析的幾個特徵:Ticket(船票號)、Cabin(船艙號),這些因素的不同可能會影響乘客在船中的位置從而影響逃生的順序。但是船艙號資料缺失,船票號類別大,難以分析規律,所以在後期模型融合的時候,將這些因素交由模型來決定其重要性。

4. 變數轉換

變數轉換的目的是將資料轉換為適用於模型使用的資料,不同模型接受不同型別的資料,Scikit-learn要求資料都是數字型numeric,所以我們要將一些非數字型的原始資料轉換為數字型numeric。

所以下面對資料的轉換進行介紹,以在進行特徵工程的時候使用。

所有的資料可以分為兩類:

  • 1.定性(Quantitative)變數可以以某種方式排序,Age就是一個很好的列子。
  • 2.定量(Qualitative)變數描述了物體的某一(不能被數學表示的)方面,Embarked就是一個例子。

定性(Qualitative)轉換:

1. Dummy Variables

就是類別變數或者二元變數,當qualitative variable是一些頻繁出現的幾個獨立變數時,Dummy Variables比較適合使用。我們以Embarked為例,Embarked只包含三個值’S’,’C’,’Q’,我們可以使用下面的程式碼將其轉換為dummies:

embark_dummies  = pd.get_dummies(train_data['Embarked'])
train_data = train_data.join(embark_dummies)
train_data.drop(['Embarked'], axis=1,inplace=True)
embark_dummies = train_data[['S', 'C', 'Q']]
embark_dummies.head()
S C Q
0 1 0 0
1 0 1 0
2 1 0 0
3 1 0 0
4 1 0 0

2. Factorizing

dummy不好處理Cabin(船艙號)這種標稱屬性,因為他出現的變數比較多。所以Pandas有一個方法叫做factorize(),它可以建立一些數字,來表示類別變數,對每一個類別對映一個ID,這種對映最後只生成一個特徵,不像dummy那樣生成多個特徵。

# Replace missing values with "U0"
train_data['Cabin'][train_data.Cabin.isnull()] = 'U0'
# create feature for the alphabetical part of the cabin number
train_data['CabinLetter'] = train_data['Cabin'].map( lambda x : re.compile("([a-zA-Z]+)").search(x).group())
# convert the distinct cabin letters with incremental integer values
train_data['CabinLetter'] = pd.factorize(train_data['CabinLetter'])[0]
train_data['CabinLetter'].head()
0 0 1 1 2 0 3 1 4 0 Name: CabinLetter, dtype: int64

定量(Quantitative)轉換:

1. Scaling

Scaling可以將一個很大範圍的數值對映到一個很小的範圍(通常是-1 - 1,或則是0 - 1),很多情況下我們需要將數值做Scaling使其範圍大小一樣,否則大範圍數值特徵將會由更高的權重。比如:Age的範圍可能只是0-100,而income的範圍可能是0-10000000,在某些對陣列大小敏感的模型中會影響其結果。

下面對Age進行Scaling:

from sklearn import preprocessing

assert np.size(train_data['Age']) == 891
# StandardScaler will subtract the mean from each value then scale to the unit variance
scaler = preprocessing.StandardScaler()
train_data['Age_scaled'] = scaler.fit_transform(train_data['Age'].values.reshape(-1, 1))
train_data['Age_scaled'].head()
0   -0.558449
1    0.606773
2   -0.267144
3    0.388293
4    0.388293
Name: Age_scaled, dtype: float64

2. Binning

Binning通過觀察“鄰居”(即周圍的值)將連續資料離散化。儲存的值被分佈到一些“桶”或“箱“”中,就像直方圖的bin將資料劃分成幾塊一樣。下面的程式碼對Fare進行Binning。

# Divide all fares into quartiles
train_data['Fare_bin'] = pd.qcut(train_data['Fare'], 5)
train_data['Fare_bin'].head()
0      (-0.001, 7.854]
1    (39.688, 512.329]
2        (7.854, 10.5]
3    (39.688, 512.329]
4        (7.854, 10.5]
Name: Fare_bin, dtype: category
Categories (5, interval[float64]): [(-0.001, 7.854] < (7.854, 10.5] < (10.5, 21.679] < (21.679, 39.688] < (39.688, 512.329]]

在將資料Bining化後,要麼將資料factorize化,要麼dummies化。

# qcut() creates a new variable that identifies the quartile range, but we can't use the string
# so either factorize or create dummies from the result

# factorize
train_data['Fare_bin_id'] = pd.factorize(train_data['Fare_bin'])[0]

# dummies
fare_bin_dummies_df = pd.get_dummies(train_data['Fare_bin']).rename(columns=lambda x: 'Fare_' + str(x))
train_data = pd.concat([train_data, fare_bin_dummies_df], axis=1)

5. 特徵工程

在進行特徵工程的時候,我們不僅需要對訓練資料進行處理,還需要同時將測試資料同訓練資料一起處理,使得二者具有相同的資料型別和資料分佈。

train_df_org = pd.read_csv('data/train.csv')
test_df_org = pd.read_csv('data/test.csv')
test_df_org['Survived'] = 0
combined_train_test = train_df_org.append(test_df_org)
PassengerId = test_df_org['PassengerId']
對資料進行特徵工程,也就是從各項引數中提取出對輸出結果有或大或小的影響的特徵,將這些特徵作為訓練模型的依據。 一般來說,我們會先從含有缺失值的特徵開始。

(1) Embarked

因為“Embarked”項的缺失值不多,所以這裡我們以眾數來填充:

combined_train_test['Embarked'].fillna(combined_train_test['Embarked'].mode().iloc[0], inplace=True)
對於三種不同的港口,由上面介紹的數值轉換,我們知道可以有兩種特徵處理方式:dummy和facrorizing。因為只有三個港口,所以我們可以直接用dummy來處理:
# 為了後面的特徵分析,這裡我們將 Embarked 特徵進行facrorizing
combined_train_test['Embarked'] = pd.factorize(combined_train_test['Embarked'])[0]

# 使用 pd.get_dummies 獲取one-hot 編碼
emb_dummies_df = pd.get_dummies(combined_train_test['Embarked'], prefix=combined_train_test[['Embarked']].columns[0])
combined_train_test = pd.concat([combined_train_test, emb_dummies_df], axis=1)

(2) Sex

對Sex也進行one-hot編碼,也就是dummy處理:

# 為了後面的特徵分析,這裡我們也將 Sex 特徵進行facrorizing
combined_train_test['Sex'] = pd.factorize(combined_train_test['Sex'])[0]

sex_dummies_df = pd.get_dummies(combined_train_test['Sex'], prefix=combined_train_test[['Sex']].columns[0])
combined_train_test = pd.concat([combined_train_test, sex_dummies_df], axis=1)

(3) Name

首先先從名字中提取各種稱呼:

# what is each person's title? 
combined_train_test['Title'] = combined_train_test['Name'].map(lambda x: re.compile(", (.*?)\.").findall(x)[0])
將各式稱呼進行統一化處理:
title_Dict = {}
title_Dict.update(dict.fromkeys(['Capt', 'Col', 'Major', 'Dr', 'Rev'], 'Officer'))
title_Dict.update(dict.fromkeys(['Don', 'Sir', 'the Countess', 'Dona', 'Lady'], 'Royalty'))
title_Dict.update(dict.fromkeys(['Mme', 'Ms', 'Mrs'], 'Mrs'))
title_Dict.update(dict.fromkeys(['Mlle', 'Miss'], 'Miss'))
title_Dict.update(dict.fromkeys(['Mr'], 'Mr'))
title_Dict.update(dict.fromkeys(['Master','Jonkheer'], 'Master'))

combined_train_test['Title'] = combined_train_test['Title'].map(title_Dict)
使用dummy對不同的稱呼進行分列:
# 為了後面的特徵分析,這裡我們也將 Title 特徵進行facrorizing
combined_train_test['Title'] = pd.factorize(combined_train_test['Title'])[0]

title_dummies_df = pd.get_dummies(combined_train_test['Title'], prefix=combined_train_test[['Title']].columns[0])
combined_train_test = pd.concat([combined_train_test, title_dummies_df], axis=1)
增加名字長度的特徵:
combined_train_test['Name_length'] = combined_train_test['Name'].apply(len)

(4) Fare

由前面分析可以知道,Fare項在測試資料中缺少一個值,所以需要對該值進行填充。
我們按照一二三等艙各自的均價來填充:
下面transform將函式np.mean應用到各個group中。

combined_train_test['Fare'] = combined_train_test[['Fare']].fillna(combined_train_test.groupby('Pclass').transform(np.mean))
通過對Ticket資料的分析,我們可以看到部分票號資料有重複,同時結合親屬人數及名字的資料,和票價船艙等級對比,我們可以知道購買的票中有家庭票和團體票,所以我們需要將團體票的票價分配到每個人的頭上。
combined_train_test['Group_Ticket'] = combined_train_test['Fare'].groupby(by=combined_train_test['Ticket']).transform('count')
combined_train_test['Fare'] = combined_train_test['Fare'] / combined_train_test['Group_Ticket']
combined_train_test.drop(['Group_Ticket'], axis=1, inplace=True)
使用binning給票價分等級:
combined_train_test['Fare_bin'] = pd.qcut(combined_train_test['Fare'], 5)
對於5個等級的票價我們也可以繼續使用dummy為票價等級分列:
combined_train_test['Fare_bin_id'] = pd.factorize(combined_train_test['Fare_bin'])[0]

fare_bin_dummies_df = pd.get_dummies(combined_train_test['Fare_bin_id']).rename(columns=lambda x: 'Fare_' + str(x))
combined_train_test = pd.concat([combined_train_test, fare_bin_dummies_df], axis=1)
combined_train_test.drop(['Fare_bin'], axis=1, inplace=True)

(5) Pclass

Pclass這一項,其實已經可以不用繼續處理了,我們只需要將其轉換為dummy形式即可。

但是為了更好的分析問題,我們這裡假設對於不同等級的船艙,各船艙內部的票價也說明了各等級艙的位置,那麼也就很有可能與逃生的順序有關係。所以這裡分出每等艙裡的高價和低價位。

from sklearn.preprocessing import LabelEncoder

# 建立PClass Fare Category
def pclass_fare_category(df, pclass1_mean_fare, pclass2_mean_fare, pclass3_mean_fare):
    if df['Pclass'] == 1:
        if df['Fare'] <= pclass1_mean_fare:
            return 'Pclass1_Low'
        else:
            return 'Pclass1_High'
    elif df['Pclass'] == 2:
        if df['Fare'] <= pclass2_mean_fare:
            return 'Pclass2_Low'
        else:
            return 'Pclass2_High'
    elif df['Pclass'] == 3:
        if df['Fare'] <= pclass3_mean_fare:
            return 'Pclass3_Low'
        else:
            return 'Pclass3_High'

Pclass1_mean_fare = combined_train_test['Fare'].groupby(by=combined_train_test['Pclass']).mean().get([1]).values[0]
Pclass2_mean_fare = combined_train_test['Fare'].groupby(by=combined_train_test['Pclass']).mean().get([2]).values[0]
Pclass3_mean_fare = combined_train_test['Fare'].groupby(by=combined_train_test['Pclass']).mean().get([3]).values[0]

# 建立Pclass_Fare Category
combined_train_test['Pclass_Fare_Category'] = combined_train_test.apply(pclass_fare_category, args=(
    Pclass1_mean_fare, Pclass2_mean_fare, Pclass3_mean_fare), axis=1)
pclass_level = LabelEncoder()

# 給每一項新增標籤
pclass_level.fit(np.array(
    ['Pclass1_Low', 'Pclass1_High', 'Pclass2_Low', 'Pclass2_High', 'Pclass3_Low', 'Pclass3_High']))

# 轉換成數值
combined_train_test['Pclass_Fare_Category'] = pclass_level.transform(combined_train_test['Pclass_Fare_Category'])

# dummy 轉換
pclass_dummies_df = pd.get_dummies(combined_train_test['Pclass_Fare_Category']).rename(columns=lambda x: 'Pclass_' + str(x))
combined_train_test = pd.concat([combined_train_test, pclass_dummies_df], axis=1)
同時,我們將 Pclass 特徵factorize化:
combined_train_test['Pclass'] = pd.factorize(combined_train_test['Pclass'])[0]

(6) Parch and SibSp

由前面的分析,我們可以知道,親友的數量沒有或者太多會影響到Survived。所以將二者合併為FamliySize這一組合項,同時也保留這兩項。

def family_size_category(family_size):
    if family_size <= 1:
        return 'Single'
    elif family_size <= 4:
        return 'Small_Family'
    else:
        return 'Large_Family'

combined_train_test['Family_Size'] = combined_train_test['Parch'] + combined_train_test['SibSp'] + 1
combined_train_test['Family_Size_Category'] = combined_train_test['Family_Size'].map(family_size_category)

le_family = LabelEncoder()
le_family.fit(np.array(['Single', 'Small_Family', 'Large_Family']))
combined_train_test['Family_Size_Category'] = le_family.transform(combined_train_test['Family_Size_Category'])

family_size_dummies_df = pd.get_dummies(combined_train_test['Family_Size_Category'],
                                        prefix=combined_train_test[['Family_Size_Category']].columns[0])
combined_train_test = pd.concat([combined_train_test, family_size_dummies_df], axis=1)

(7) Age

因為Age項的缺失值較多,所以不能直接填充age的眾數或者平均數。

常見的有兩種對年齡的填充方式:一種是根據Title中的稱呼,如Mr,Master、Miss等稱呼不同類別的人的平均年齡來填充;一種是綜合幾項如Sex、Title、Pclass等其他沒有缺失值的項,使用機器學習演算法來預測Age。

這裡我們使用後者來處理。以Age為目標值,將Age完整的項作為訓練集,將Age缺失的項作為測試集。

missing_age_df = pd.DataFrame(combined_train_test[
    ['Age', 'Embarked', 'Sex', 'Title', 'Name_length', 'Family_Size', 'Family_Size_Category','Fare', 'Fare_bin_id', 'Pclass']])

missing_age_train = missing_age_df[missing_age_df['Age'].notnull()]
missing_age_test = missing_age_df[missing_age_df['Age'].isnull()]
missing_age_test.head()
Age Embarked Sex Title Name_length Family_Size Family_Size_Category Fare Fare_bin_id Pclass
5 NaN 2 0 0 16 1 1 8.4583 2 0
17 NaN 0 0 0 28 1 1 13.0000 3 2
19 NaN 1 1 1 23 1 1 7.2250 4 0
26 NaN 1 0 0 23 1 1 7.2250 4 0
28 NaN 2 1 2 29 1 1 7.8792 0 0

建立Age的預測