1. 程式人生 > >Kaggle —— 泰坦尼克號Titanic

Kaggle —— 泰坦尼克號Titanic

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

觀察前幾行的源資料:

train_data = pd.read_csv('data/train.csv'
) test_data = pd.read_csv('data/test.csv') sns.set_style('whitegrid') train_data.head()
  • 1
  • 2
  • 3
  • 4
  • 5
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()
  • 1
  • 2
  • 3
<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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

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

繪製存活的比例:

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

這裡寫圖片描述

2. 缺失值處理的方法

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

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

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

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

train_data.Embarked[train_data.Embarked.isnull()] = train_data.Embarked.dropna().mode().values
  • 1

(3)對於標稱屬性,可以賦一個代表缺失的值,比如‘U0’。因為缺失本身也可能代表著一些隱含資訊。比如船艙號Cabin這一屬性,缺失可能代表並沒有船艙。

#replace missing value with U0
train_data['Cabin'] = train_data.Cabin.fillna('U0') # train_data.Cabin[train_data.Cabin.isnull()]='U0'
  • 1
  • 2

(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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

讓我們再來看一下缺失資料處理後的DataFram:

train_data.info()
  • 1
<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   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

3. 分析資料關係

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

train_data.groupby(['Sex','Survived'])['Survived'].count()
  • 1
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()
  • 1
<matplotlib.axes._subplots.AxesSubplot at 0x230c251ab00>
  • 1
  • 2

這裡寫圖片描述

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

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

train_data.groupby(['Pclass','Survived'])['Pclass'].count()
  • 1
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()
  • 1
<matplotlib.axes._subplots.AxesSubplot at 0x230c5e08b70>
  • 1
  • 2

這裡寫圖片描述

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

這裡寫圖片描述

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

train_data.groupby(['Sex', 'Pclass', 'Survived'])['Survived'].count()
  • 1
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()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

這裡寫圖片描述

分析總體的年齡分佈:

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()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

這裡寫圖片描述

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

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()
  • 1
  • 2
  • 3
  • 4
<seaborn.axisgrid.FacetGrid at 0x230c5e53cf8>
  • 1
  • 2

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)
  • 1
  • 2
  • 3
  • 4
  • 5
<matplotlib.axes._subplots.AxesSubplot at 0x230c60135f8>
  • 1
  • 2

這裡寫圖片描述

train_data['Age'].describe()
  • 1
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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

樣本有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
  • 1
  • 2
  • 3
  • 4
Age_group
(0, 12]      0.506173
(12, 18]     0.466667
(18, 65]     0.364512
(65, 100]    0.125000
Name: Survived, dtype: float64
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
by_age.plot(kind = 'bar')
  • 1
<matplotlib.axes._subplots.AxesSubplot at 0x230c6079e80>
  • 1
  • 2

這裡寫圖片描述

(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'])
  • 1
  • 2
  • 3
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()
  • 1
<matplotlib.axes._subplots.AxesSubplot at 0x230c61699b0>
  • 1
  • 2

這裡寫圖片描述

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

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)
  • 1
  • 2
  • 3
  • 4
<matplotlib.axes._subplots.AxesSubplot at 0x230c61689b0>
  • 1
  • 2

這裡寫圖片描述

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

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

# 將資料分為有兄弟姐妹的和沒有兄弟姐妹的兩組:
sibsp_df = train_data[train_data['SibSp'] != 0]
no_sibsp_df = train_data[train_data['SibSp'] == 0]
  • 1
  • 2
  • 3
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()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

這裡寫圖片描述

(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()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

這裡寫圖片描述

###(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')
  • 1
  • 2
  • 3
  • 4
  • 5
Text(0.5,1,'SibSp and Survived')
  • 1
  • 2

這裡寫圖片描述

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

這裡寫圖片描述

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

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

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

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

train_data.boxplot(column='Fare', by='Pclass', showfliers=False)
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5

這裡寫圖片描述

這裡寫圖片描述

train_data['Fare'].describe()
  • 1
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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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

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()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

這裡寫圖片描述

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

(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()
  • 1
  • 2
  • 3
  • 4
<matplotlib.axes._subplots.AxesSubplot at 0x230c7566080>
  • 1
  • 2

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()
  • 1
  • 2
  • 3
  • 4
  • 5
<matplotlib.axes._subplots.AxesSubplot at 0x230c5ebcd30>
  • 1
  • 2

這裡寫圖片描述

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

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

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

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

這裡寫圖片描述

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

這裡寫圖片描述

由上可以看出,在不同的港口上船,生還率不同,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)
  • 1
  • 2
  • 3
embark_dummies = train_data[['S', 'C', 'Q']]
embark_dummies.head()
  • 1
  • 2
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]
  • 1
  • 2
  • 3
  • 4
  • 5