數據分析 | 任性娜娜,在線算命

大家好,今天也是被生活安排地明明白白的一天。

來龍去脈不細說了,總而言之就是根據賭約我要完成Kaggle里一個比賽。縱然對方十分親切地幫我挑選了一個最最基礎的入門級別的比賽,可我一個數據分析小學生,忽然去搞機器學習……那好像還是有點拔苗助長喲(⊙_⊙;)

好吧,買定離手愿賭服輸。那么,我就開始搜索如何從零開始入門機器學習了。
一個小時過后:我開始慌惹
兩個小時過后:我是誰我在哪我要干什么???

大概流程和框架捋了一下,娜某人就這么漫不經心地去決定別人的生死了!這本生死簿,就是正義!

泰坦尼克號幸存者預測 Titanic: Machine Learning from Disaster

先研究研究到底需要干什么吧。現在我手里有兩張表,一張叫train,一張叫test。第一張表(train)上記錄了泰坦尼克號部分(891位)客人的姓名性別家里幾口人人均幾畝地等等各種信息,包括他們最后有沒有生還(Survived);另一張表(test),和train一樣也記錄著一部分(418位)客人的各種信息,唯一的不同是Survived這一列為空值,也就是說不知道他們有沒有在災難中獲救。好的!懂了!我的任務就是通過手里的兩張表安排一下那418位乘客有沒有獲救。

891條數據有Survived值,418條數據Survived為空

表中各特征的含義如下:
PassengerId: 乘客編號
Survived: 是否生還(0 = 死亡,1 = 生存)
Pclass: 船票級別(1 = 高級,2 = 中級,3 = 低級)
Name: 姓名
Sex: 性別(male = 男性,female = 女性)
Age: 年齡
SibSp: 在 Titanic 上的兄弟姐妹以及配偶的人數
Parch: 在 Titanic 上的父母以及子女的人數
Ticket: 船票編號
Fare: 票價
Cabin: 所在的船艙
Embarked: 登船的港口 (C=Cherbourg, Q=Queenstown, S=Southampton)

接下來是《算命速成》,可能將浪費你人生中寶貴的15分鐘:
0 導入基本模塊以及加載數據
1 數據初探
2 數據預處理
3 特征工程
4 模型選擇
5 模型優化

0 導入基本模塊以及加載數據
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
% matplotlib inline

from pylab import mpl
mpl.rcParams['font.sans-serif'] = ['SimHei'] # 指定默認字體
mpl.rcParams['axes.unicode_minus'] # 負號正常顯示

import warnings
warnings.filterwarnings('ignore') 

import os
os.chdir(r'..\Desktop\泰坦尼克號幸存者模擬')
data = pd.read_csv('train.csv',header = 0)
1 數據初探

數據加載好了,看看我們能從數據里發現什么小秘密吧。
這里總結了一些數據初判的方法:

1.1 查看基本特征(空值、統計描述等)

(1) df.info() #查看基本信息
(2) df.isnull().sum() #查看每列空值數量,查看非空使用notnull()
(3) df.describe() #查看每列數據均值、標準差、分位數等統計值
(4) df['Pclass'].unique() #查看某列的幾種不重復的結果是什么

具體來看看:
1.2 查看基本數據分布(分布分析、對比分析等)

(1) df['Fare'].hist(bins=10) #查看某列直方圖,設置分段數量用bins=10
(2) df['Fare'].plot(kind='kde',style='k--',grid=True) #查看某列密度圖
(3) df['Fare'].boxplot(by='Pclass') #查看某列箱型圖,可按另一個特征分組查看
(4) 有一些特征變量是一些連續的數值,這時可以按照區間查看:
例如年齡Age,是這樣的一些數值:18,30,45……,單看這些年齡值不容易發現規律,這時用pd.cut()把它們劃分在不同區間里更容易看出規律。

 bins = 4 #分成4組
 group_names = ['Youth', 'YoungAdult', 'MiddleAged', 'Senior'] #每組標簽
 agecut = pd.cut(data['Age'],bins,labels=group_names,right = False) #分組區間
 agecut_count = agecut.value_counts(sort = False)#不按count數量多少排序

針對上面的步驟舉幾個栗子:

# (1)直方圖:查看票價Fare的分布,可發現大多為低價票
data.hist(column='Fare',
          bins = 20,        # bin箱子個數
          histtype = 'bar', # histtype 風格,bar,barstacked,step,stepfilled
          align = 'mid',    # align : {‘left’, ‘mid’, ‘right’}, optional(對齊方式)
          orientation = 'vertical',# orientation 水平還是垂直{‘horizontal’, ‘vertical’}
          alpha=0.6,
          color='forestgreen',
          normed =True)    # normed 標準化

# (2)密度圖:查看票價Fare的分布
data['Fare'].plot(kind='kde',style='--',grid = True)
直方圖和密度圖:查看Fare票價分布,發現多數為低價票
# (3)箱型圖:查看按船艙等級Pclass分類的票價Fare箱型圖,船票級別1=高級,2=中等,3=低等
data.boxplot(column='Fare',
             by = 'Pclass',
             sym = 'o',    # 異常點形狀
             vert = True,  # 是否垂直
             whis = 1.5,   # IQR,默認1.5,也可以設置區間比如[5,95],代表強制上下邊緣為數據95%和5%位置
             patch_artist = True,  # 上下四分位框內是否填充,True為填充
             meanline = False,showmeans=True,  # 是否有均值線及其形狀
             showbox = True,     # 是否顯示箱線
             showcaps = True,    # 是否顯示邊緣線
             showfliers = True,  # 是否顯示異常值
             notch = False,      # 中間箱體是否缺口
             ) 
箱型圖:按不同船艙等級Pclass查看票價Fare分布,船艙等級越高票價越高
# (4)用pd.cut()查看年齡Age分組占比
bins = [0, 18, 40, 60, 80]
group_names = ['Youth', 'YoungAdult', 'MiddleAged', 'Senior']
agecut = pd.cut(data['Age'],bins)
agecut_count = agecut.value_counts(sort = False)
colors = ['lightcyan','paleturquoise','mediumturquoise','c']
plt.pie(agecut_count,labels = group_names,autopct='%i %%',colors=colors)
餅圖:查看年齡分組占比,大部分乘客處在18~40歲這個區間
# 除此之外還可以用熱圖初步查看幾個特征之間的相關性:
sns.heatmap(data[['Survived','SibSp','Parch','Age','Fare']].corr(),annot=True, fmt = '.2f', cmap = 'coolwarm')
熱圖:圖中發現似乎只有Fare與生存率密切相關,但這不意味著其他因素與生存無關
1.3 查看數據分組分布(groupby、crosstab、pivot_table、sns.pairplot)

只分析單一因素是不夠全面的,我們還需要查看不同因素聯立時的數據分布,下面的方法都可以用來進行分組查看:

(1) groupby:分組統計,可代替下面的兩種
temp = data[['Sex','Pclass','Survived']].groupby(['Pclass','Sex']).sum()
temp.plot(kind='bar')

(2)交叉表crosstab:可以按照指定的行和列統計分組頻數
temp = pd.crosstab([data['Pclass'], data['Sex']], data.Survived.astype(bool))
temp.plot(kind='bar', stacked=True, color=['red','blue'], grid=False)

(3)透視表pivot_table:產生類似數據透視表的效果
temp = pd.pivot_table(data, index='Pclass', columns='Sex',values = 'Survived')
temp.plot(kind='bar',stacked=True)

(4)查看多個變量兩兩之間的相互關系
sns.pairplot()

快來看看具體是怎么應用的:
1.3.1 按照船上家庭人數查看生存率:groupby

data['Family_Size'] = data['Parch'] + data['SibSp'] + 1
temp = data[['Family_Size','Survived']].groupby(['Family_Size']).mean()
temp.plot(kind='bar',color='lightcoral',alpha=0.9)
家庭人數增多時生存率呈現增加趨勢,但人數大于一定值(4個)后生存率會大幅減小

1.3.2 查看登船港口Embarked與生存率的關系:groupby

temp = data[['Embarked','Survived']].groupby(['Embarked']).mean()
temp.plot(kind='bar',color = 'crimson',alpha=0.7)

泰坦尼克號航行路線:Southampton--Cherbourg-Octeville--Queenstown
考慮到在Southampton港口和Cherbourg-Octeville港口登船的人將有機會在Queenstown港口之前下船,那么前面兩個港口登船的人果真生存率會更大嗎?


在Cherbourg-Octeville上船的人生還幾率最大,Queenstown次之,Southampton最小

1.3.3 查看船艙Cabin與生存率的關系,用船艙號首字母分組:groupby

data['Cabin_Initial'] = data['Cabin'].str[0]
temp = data[['Cabin_Initial','Survived']].groupby(['Cabin_Initial']).mean()
temp.plot(kind='bar',color = 'dodgerblue',alpha=0.7)
似乎并不能看出船艙號和獲救有什么直接關系,但直覺上船艙號決定了船艙位置,應該和逃生相關

1.3.4 查看按照Age分組區間幸存情況:crosstab

temp = pd.crosstab(agecut.values,data['Survived'])
temp.plot(kind='bar',color=['red','green'],alpha=0.7)
雖然18~40歲的乘客獲救人數最多,但獲救占比很小

1.3.5 按照船艙等級和性別查看幸存人數:crosstab

temp = pd.crosstab([data['Pclass'], data['Sex']], data['Survived'].astype(bool))
temp.plot(kind='bar', stacked=True, color=['red','green'],alpha=0.7)
基本上呈現出“Lady First”,同時船艙等級對能否獲救也有顯著影響

1.3.6 查看多個變量兩兩之間的相互關系

sns.pairplot(data[['Survived','Pclass','Age','Fare']], hue='Survived', palette = 'husl',diag_kind='kde',diag_kws=dict(shade=True),plot_kws=dict(s=10),markers=["o", "x"])
1.4 查看特征類別分布是否平衡

sns.countplot(x='Survived',data=data)

幸存和死亡人數相差不大,可以認為屬于類別平衡問題。

類別不平衡(class-imbalance)是指在分類任務中,不同類別的訓練樣例數目相差很大的情況。(比如本例是一個二分類問題,每位乘客分為生存或者死亡兩種狀態,如果絕大部分乘客死亡而生存人數僅有幾個的話,就叫類別不平衡。)為什么要進行這樣的判斷呢?因為對于分類問題的訓練集,如果有999個都屬于A類,只有1個屬于B類,那么在預測時只需要永遠將新樣本預測為A類,這個分類器就能達到99.9%的精度,但這樣是沒有意義的。現實中,在銀行信用欺詐交易識別中,屬于欺詐交易的應該是很少部分,絕大部分交易是正常的,這就是一個正常的類別不平衡問題。如果出現類別不平衡的情況,已經超出我目前的知識范圍了,可以參考這里


2 數據預處理

經過第一步我們假裝已經了解手里的數據長什么樣了,下面就需要對其中一些特征進行預處理了。在此之前需要先把train表和test表用pd.concat()拼成一張表,這樣按照接下來的步驟處理完它兩還是相同的數據分布。

data_01 = pd.read_csv('train.csv',header = 0)
data_02 = pd.read_csv('test.csv',header = 0)
data_12 = pd.concat([data_01,data_02]) #合并兩個
data_12.reset_index(inplace=True)

下面總結了一些數據預處理的方法:

2.1 處理缺失值
缺失值情況

(1) 如果缺失值對于學習來說不是很重要,同時占比非常小:可以刪掉這條數據或者填補均值or眾值
(2) 如果缺失值的特征相對重要,而且不屬于數值型:可以考慮重新賦值
(3) 如果缺失值占比很大而且相對重要:可以建立模型(線性回歸、隨機森林等)預測缺失值

2.1.1 年齡Age(177個缺失值)
由于年齡是比較重要的特征,缺失值占比也比較大,考慮將Age完整的項作為訓練集,將Age缺失的項作為測試集,采用RandomForestRegressor()。因為想要利用其他特征量來預測Age,所以這里把Age的缺失值處理放到下面一步,先分析其他特征。

2.1.2 登船港口Embarked(2個缺失值)
缺失值很少,考慮用眾值填補:

train_data = pd.DataFrame()

mod = data_12['Embarked'].mode()[0]
data_12['Embarked'].fillna(mod,inplace=True)
train_data['Embarked'] = data_12['Embarked']

2.1.3 船艙Cabin(1014個缺失值)
對于所在的船艙的大量缺失值,考慮按特殊情況重新賦值為"Z0"(特殊情況是指,結合票價考慮,這些缺失值出現的原因可能是因為實際中這些人沒有船艙)

data_12['Cabin'].fillna('Z0',inplace=True)
train_data['Cabin'] = data_12['Cabin']

2.1.4 票價Fare(1個缺失值)
票價Fare的缺失值只有一個,考慮按他所在的船艙等級均價填補;另外發現有些人的票價為0,這些人共享同一個Ticket號碼,他們應該是團體票,因此還需要將團體票按總票價平均分配給這個團體中的每個人

# 缺失票價用所在船艙等級票價均值填補
train_data['Fare'] = data_12['Fare']
train_data['Pclass'] = data_12['Pclass']
train_data['Fare'] = train_data['Fare'].fillna(train_data.groupby('Pclass').transform('mean'))

# 將團體票的總價平均分配給組里每個人
train_data['Ticket'] = data_12['Ticket']
train_data['Group_Member'] = train_data['Fare'].groupby(by=train_data['Ticket']).transform('count')
# transform(func, args, *kwargs) 方法會把func應用到所有分組,然后把結果放置到原數組的index上

train_data['Fare'] = train_data['Fare'] / train_data['Group_Member']

那么我們就把登船港口、所在船艙和票價的缺失值填充好了!Age放在下一步處理!

2.2 處理異常值

剛剛提到的Fare票價為0的值其實就算異常值,在處理上應該具體問題具體分析,看它為什么異常,異常在哪里。用第一步中繪制箱型圖的方法可以查看異常值情況。箱型圖這里小小的解釋一下,箱型圖是用作顯示一組數據分散情況的統計圖。

箱形圖提供了一種只用5個點對數據集做簡單總結的方式。
這5個點包括中點、Q1、Q3、分部狀態的高位和低位
① 矩形盒兩端的邊線分別對應數據批的上下四分位數(Q3和Q1)。
② 矩形盒內部的線為中位線,代表了中位數(Xm)。
③ 在Q3+1.5IQR和Q1-1.5IQR處的線段為異常值截斷點,稱其為內限;在Q3+3IQR和Q1-3IQR處的線段稱其為外限。處于內限以外位置的點表示的數據都是異常值:其中在內限與外限之間的異常值為溫和的異常值(mild outliers);在外限以外的為極端的異常值(extreme outliers)。
④ 四分位距IQR=Q3-Q1。.
(以上內容整理自百度百科)

2.3 數據連續屬性離散化

連續屬性變換成分類屬性,即連續屬性離散化。第一步提到的年齡分組區間就是在做離散化。在數值的取值范圍內設定若干個離散劃分點,將取值范圍劃分為一些離散化的區間,最后用不同的符號或整數值代表每個子區間中的數據值。
下面看看票價Fare如何做這樣的分組處理:

# 數據連續屬性離散化:票價Fare
train_data['Fare_bin'] = pd.qcut(train_data['Fare'], 5)
#cut是根據數值本身來選擇箱子均勻間隔,qcut是根據這些值的頻率來選擇箱子的均勻間隔
train_data['Fare_bin'].head()

將得到這樣的分組效果:Categories (5, interval[float64]): [(-0.001, 7.229] < (7.229, 7.896] < (7.896, 10.5] < (10.5, 26.277] < (26.277, 128.082]]

每個人會根據票價被劃分在不同組:
僅截取部分乘客
2.4 數據標準化

數據的標準化是將數據按比例縮放,使之落入一個小的特定區間。常在比較和評價的指標處理中用到,去除數據的單位限制,將其轉化為無量綱的純數值,便于不同單位或量級的指標能夠進行比較和加權。
(1) MinMaxScaler(最小最大值標準化):將數據統一映射到[0,1]區間上
(2) Standardization標準化:將特征數據的分布調整成標準正太分布,也叫高斯分布,也就是使得數據的均值維0,方差為1。

下面以票價Fare為例做標準化處理:

# 數據標準化:票價Fare
from sklearn import preprocessing
train_data['Fare_scaled'] = preprocessing.StandardScaler().fit_transform(train_data['Fare'].values.reshape(-1,1))
#X.reshape(-1,1)是指我們不知道X的shape屬性是多少,但是想讓X變成只有一列。
#X.reshape(-1,1)由Numpy自動計算出有1309行,新的數組shape屬性為(1309, 1),與Fare列數據配套。
2.5 特征類型轉換

回憶一下我們的數據,有一些特征是這樣不同類別的取值:male男性 / female女性,這樣的變量需要轉化為數值型才能被模型識別。但如果只是簡單地把男性轉化為0女性轉化為1,就會為它們帶來數值上的意義。例如用數字1-12表示1-12月,那么就潛在表示了12月和1月差得很遠,其實它們離得很近。這時就需要引入下面的方法了:

(1) 引入虛擬變量dummy variable
當頻繁出現的幾個獨立變量時,可以用pandas.get_dummies()將定性變量轉換為虛擬變量。

(2) factorize因子化
當某個特征有非常多取值時,可以使用pandas.factorize()創建一些數字來表示類別變量,對每一個類別映射一個ID,這種映射最后只生成一個特征,不像dummy那樣生成多個特征。

說了這么多,到底怎么完成這種炫酷的操作呢:

# 對于船艙等級Pclass,引入dummy變量
Pclass_dummies_df = pd.get_dummies(train_data['Pclass']).rename(columns=lambda x: 'Pclass_' + str(x))
train_data = pd.concat([train_data, Pclass_dummies_df], axis=1)

Pclass這一特征就轉化完成了:
為船艙等級Pclass引入虛擬變量

其他幾個特征也同樣需要進行轉化:

# 票價Fare分組
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)


# 登船港口Embarked
Embarked_dummies_df = pd.get_dummies(train_data['Embarked']).rename(columns=lambda x: 'Embarked_' + str(x))
train_data = pd.concat([train_data, Embarked_dummies_df], axis=1)


# 性別Sex
train_data['Sex'] = data_12['Sex']
Sex_dummies_df = pd.get_dummies(train_data['Sex']).rename(columns=lambda x: 'Sex_' + str(x))
train_data = pd.concat([train_data, Sex_dummies_df], axis=1)

再來看船艙編號Cabin,這一特征下存在非常多的變量,所以這次采用factorize因子化的方法:

# 對于船艙Cabin:采用factorize因子化方法:
# 首先提取首字母Cabin_Initial
train_data['Cabin_Initial'] = data_12['Cabin'].str[0]
train_data['Cabin_Initial_factorize'] = pd.factorize(train_data['Cabin_Initial'])[0]

看下前幾行的效果:
factorize因子化

3 特征工程

特征工程即從各項參數中提取出可能影響到最終結果的特征,作為模型的預測依據。回顧上面一步,已經處理好了缺失值、異常值,也已經轉換了一些特征的變量類型。現在再來梳理一下手里的特征:

(1)登船港口Embarked
經過Step2預處理,登船港口引入虛擬變量,轉換為Embarked_C,Embarked_Q,Embarked_S;
(2)票價Fare
經過Step2預處理,將票價分為了5個等級;
(3)性別Sex
經過Step2預處理,性別Sex轉換為Sex_female,Sex_male;
(4)船艙等級Pclass
經過Step2預處理,船艙等級Pclass轉換為Pclass_1,Pclass_2,Pclass_3;
(5)船艙Cabin
經過Step2預處理,船艙Cabin轉換為Cabin_Initial_factorize;
(6)姓名Name
前面一直沒有細看這個特征,我們可以從名字中提取到稱呼(例如“Mr.”,“Capt”等),或許可以發現社會地位對生存率的影響。
(7)家庭成員Parch and SibSp
在Step1中發現,家庭成員的多少對獲救有一定影響,這里可以將兩項合并一下,研究家庭成員數量對生存率的影響。還可以根據人數對家庭進行分類:單個人、小型家庭、中型家庭、大型家庭。
(8)年齡Age
這一步將解決step2的遺留問題,用模型預測缺失年齡。
(9)船票號碼Ticket
觀察Ticket的值,前綴相同的號碼似乎是一起訂的,他們應該也是在同一個船倉的,所以這里把船票號碼前綴提取出來。(后面發現預測時這一特征并不是重要特征。)

3.1 姓名Name提取Title
train_data['Title']=data_12['Name'].apply(lambda x: x.split(',')[1].split('.')[0].strip())
train_data['Title'].unique()

['Mr', 'Mrs', 'Miss', 'Master', 'Don', 'Rev', 'Dr', 'Mme', 'Ms','Major', 'Lady', 'Sir', 'Mlle', 'Col', 'Capt', 'the Countess','Jonkheer', 'Dona']
發現title種類很多,可以將相似的頭銜做合并為5類,并且引入虛擬變量。
(1) 有職務:Staff
Capt --上校
Col -- 上校
Major -- 少校
Dr -- 醫生/博士
Rev.= reverend -- 基督教的牧師

(2) 沒有頭銜的男性:Mr
Mr.= mister -- 先生

(3) 沒有頭銜的已婚女性:Mrs
Mrs.= mistress -- 太太/夫人
Mme.=Madame -- 女士
Ms -- 婚姻狀態不明的女性

(4) 沒有頭銜的未婚女性:Miss
Miss -- 未婚女性
Mlle -- 小姐

(5) 貴族:Noble
Lady -- 女士
Dona -- 夫人,女士
Master -- 主人
Don -- 先生
Sir -- 先生
Jonkheer -- 鄉紳
The Countless -- 女伯爵

train_data['Title'][train_data.Title.isin(['Capt', 'Col', 'Major', 'Dr', 'Rev'])] = 'Staff'
train_data['Title'][train_data.Title.isin(['Don', 'Sir', 'the Countess', 'Dona', 'Lady','Master','Jonkheer'])] = 'Noble'
train_data['Title'][train_data.Title.isin(['Mr'])] = 'Mr'
train_data['Title'][train_data.Title.isin(['Mme', 'Ms', 'Mrs'])] = 'Mrs'
train_data['Title'][train_data.Title.isin(['Mlle', 'Miss'])] = 'Miss'

# 稱謂Title:引入dummy變量
Title_dummies_df = pd.get_dummies(train_data['Title']).rename(columns=lambda x: 'Title_' + str(x))
train_data = pd.concat([train_data, Title_dummies_df], axis=1)
3.2 家庭成員Parch and SibSp

Parch和SibSp可以合并成家庭成員數量Family_Size,分類后引入虛擬變量。

train_data['Family_Size'] = data_12['Parch'] + data_12['SibSp'] + 1
def Family_Category(size):
    if size == 1:
        return 'Single'
    elif size < 3:
        return 'Small'
    elif size < 5:
        return 'Medium'
    else:
        return 'Large'
train_data['Family_Category'] = train_data['Family_Size'].map(Family_Category)
# Series的map方法可以接受一個函數或含有映射關系的字典型對象。 使用map()是一種實現元素級轉換以及其他數據清理工作的便捷方式。 

# 家庭類型Family_Category:引入dummy變量
Family_Category_dummies_df = pd.get_dummies(train_data['Family_Category']).rename(columns=lambda x: 'Family_Category_' + str(x))
train_data = pd.concat([train_data, Family_Category_dummies_df], axis=1)
3.3 年齡Age

前面提到過對于年齡Age的缺失值,將年齡完整的項作為訓練集,將年齡缺失的項作為測試集,采用RandomForestRegressor()預測。

from sklearn.ensemble import RandomForestRegressor

# 選擇訓練集數據
train_data['Age'] = data_12['Age']
age_df = train_data[['Age','Family_Size','Pclass','Sex_female','Sex_male','Fare']]
age_df_notnull = age_df[age_df['Age'].notnull()]
age_df_isnull = age_df[age_df['Age'].isnull()]
X = age_df_notnull.values[:,1:] # 除第一列Age外其他非空數據
Y = age_df_notnull.values[:,0]  # 第一列Age非空
# 隨機森林回歸
RFR = RandomForestRegressor(n_estimators=500, n_jobs=-1)
RFR.fit(X,Y) # 訓練
predictAges = RFR.predict(age_df_isnull.values[:,1:]) # 測試
train_data.loc[train_data['Age'].isnull(),['Age']]= predictAges

# 查看預測年齡Age前后數據分布:與之前數據分布基本相同
fig,ax=plt.subplots(1,2,figsize=(12,6))
data_12['Age'].hist(bins=20,ax=ax[0],color='green')
train_data['Age'].hist(bins=20,ax=ax[1],color='yellowgreen')
預測前后年齡分布相似

差點忘了年齡也應該做標準化處理:

from sklearn import preprocessing
scaler = preprocessing.StandardScaler()
train_data['Age_scaled'] = scaler.fit_transform(train_data['Age'].values.reshape(-1,1))
3.4 船票號碼Ticket

船票數字部分暫時沒看出什么規律,先嘗試把字母部分提取出來吧:

Ticket = []
for i in list(train_data['Ticket']):
    if not i.isdigit() :
        Ticket.append(i.replace('.','').replace('/','').strip().split(' ')[0]) 
    else:
        Ticket.append('X')
        
train_data['Ticket'] = Ticket

# 船票Ticket字母部分:引入dummy變量
Ticket_dummies_df = pd.get_dummies(train_data['Ticket']).rename(columns=lambda x: 'Ticket_' + str(x))
train_data = pd.concat([train_data, Ticket_dummies_df], axis=1)

特征工程其實是非常非常重要的一步,但能力太菜時間緊迫只能分析出這么多了……


4 模型選擇

可以用于分類的模型很多很多,我也看得眼花繚亂,可以參考這里


看了Kaggle論壇里好多大神的分享,暫時選了幾種python會調用的(原理目前還不理解)先看一下準確率。

Where is a beginner to start? I recommend starting with Trees, Bagging, Random Forests, and Boosting. They are basically different implementations of a decision tree, which is the easiest concept to learn and understand. (轉自Kaggle論壇

計算準確率前需要用到train_test_split(),它的作用是給定數據集X和類別標簽Y,將數據集按一定比例隨機切分為訓練集和測試集。挑選了下面四種模型進行準確率的計算:

(1) 邏輯回歸 Logistic Regression
我們熟悉的線性回歸模型通常可以處理因變量是連續變量的問題,如果因變量是分類變量,線性回歸模型就不再適用了,可用邏輯回歸模型解決。

(2) 支持向量機 SVM
SVM是Support Vector Machines支持向量機的縮寫,可以用來做分類和回歸。SVC是SVM的一種Type,是用來的做分類的。概念有點難理解,基本概念以及參數說明可以參考這里這里

(3) 隨機森林 Random Forest
隨機森林是集成學習的一個子類,它依靠決策樹的投票選擇來決定最后的分類結果。

隨機森林的工作原理是生成多個子模型,各自獨立地學習和作出預測。這些預測最后結合成單預測,因此優于任何一個單分類的做出預測。 一個形象的比喻:森林中召開會議,討論某個動物到底是老鼠還是松鼠,每棵樹都要獨立地發表自己對這個問題的看法,也就是每棵樹都要投票。該動物到底是老鼠還是松鼠,要依據投票情況來確定,獲得票數最多的類別就是森林的分類結果。森林中的每棵樹都是獨立的,99.9%不相關的樹做出的預測結果涵蓋所有的情況,這些預測結果將會彼此抵消。少數優秀的樹的預測結果將會超脫于蕓蕓“噪音”,做出一個好的預測。將若干個弱分類器的分類結果進行投票選擇,從而組成一個強分類器,這就是隨機森林bagging的思想。(隨機森林通俗易懂的解釋

(4) 梯度提升Gradient Boosting
Boosting是一種知錯就改的思想。通過一系列的迭代來優化分類結果,每迭代一次引入一個弱分類器,來克服現在已經存在的弱分類器組合的缺點。Gradient Boosting 在迭代的時候選擇梯度下降的方向來保證最后的結果最好。

損失函數用來描述模型的“靠譜”程度,假設模型沒有過擬合,損失函數越大,模型的錯誤率越高。讓損失函數持續下降,就能使得模型不斷改性提升性能,其最好的方法就是使損失函數沿著梯度方向下降。(參考這里

#備份一下,以備不時之需
data_backup = train_data.copy()
# 刪掉不需要的特征
train_data.drop(['Embarked', 'Cabin', 'Fare', 'Pclass', 'Ticket', 'Group_Mumber','Cabin_Initial', 
                'Fare_bin', 'Family_Size', 'Family_Category', 'Age','Sex','Title'],axis=1,inplace=True)
# 分開訓練集和測試集
Titanic_train_data = train_data[:891]
Titanic_test_data = train_data[891:]
train_data_X = Titanic_train_data
train_data_Y = data_01['Survived']
test_data_X = Titanic_test_data

# 各種模型預測準確率
from sklearn.model_selection import train_test_split  
seed = 4
train_X,test_X,train_Y,test_Y=train_test_split(train_data_X,train_data_Y,test_size=0.4,random_state=seed)  

# Logistic Regression
from sklearn.linear_model import LogisticRegression  
LR=LogisticRegression(C=1.0,tol=1e-6,random_state=seed)  
LR.fit(train_X,train_Y)  
print(LR.score(test_X,test_Y))  

# SVC
from sklearn.svm import SVC  
SVC=SVC(C=2, kernel='rbf', decision_function_shape='ovo',random_state=seed)  
SVC.fit(train_X,train_Y)  
print(SVC.score(test_X,test_Y))  

# Random Forest
from sklearn.ensemble import RandomForestClassifier  
RF=RandomForestClassifier(n_estimators=500,max_depth=5,random_state=seed)  
RF.fit(train_X,train_Y)  
print(RF.score(test_X,test_Y))  

# GradientBoosting
from sklearn.ensemble import GradientBoostingClassifier  
GB=GradientBoostingClassifier(n_estimators=600,max_depth=5,random_state=seed)  
GB.fit(train_X,train_Y)  
print(GB.score(test_X,test_Y))  

Logistic Regression、SVC、Random Forest、GradientBoosting模型的得分依次為:
四種模型準確率

試著變換隨機數種子,前三種模型得分依然比GradientBoosting模型高。可以考慮用前面三種任意一種來做最終預測,或者通過votingClassifer綜合幾種模型。

5 模型優化

雖說特征工程非常非常重要,但模型優化也是必不可少的。準備從下面兩個方面入手:

(1) 篩選重要特征
(2) 參數調整

5.1 篩選重要特征

這一步目標是找到與應變量高度相關的特征變量。

# 用兩個模型來篩選重要特征
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import GradientBoostingClassifier

def get_top_n_features(train_data_X, train_data_Y, top_n_features):
    seed = 4
    
    # RandomForest
    RF_estimator = RandomForestClassifier(random_state=seed)
    RF_param_grid = {'n_estimators': [500,1000], 'min_samples_split': [2, 3], 'max_depth': [10,20], 'min_samples_leaf':[2,3]}
    RF_grid = model_selection.GridSearchCV(RF_estimator, RF_param_grid, cv=10, verbose=1)
    RF_grid.fit(train_data_X, train_data_Y)
    feature_imp_sorted_RF = pd.DataFrame({'feature': list(train_data_X),
                                          'importance': RF_grid.best_estimator_.feature_importances_}).sort_values('importance', ascending=False)
    features_top_n_RF = feature_imp_sorted_RF.head(top_n_features)['feature']
    print('Sample 10 Features from RF_Classifier')
    print(str(features_top_n_RF[:10]))

    # GradientBoosting
    GB_estimator =GradientBoostingClassifier(random_state=seed)
    GB_param_grid = {'n_estimators': [500,1000], 'learning_rate': [0.001, 0.01, 0.1], 'max_depth': [10,20], 'min_samples_leaf':[2,3]}
    GB_grid = model_selection.GridSearchCV(GB_estimator, GB_param_grid, cv=10, verbose=1)
    GB_grid.fit(train_data_X, train_data_Y)
    feature_imp_sorted_GB = pd.DataFrame({'feature': list(train_data_X),
                                           'importance': GB_grid.best_estimator_.feature_importances_}).sort_values('importance', ascending=False)
    features_top_n_GB = feature_imp_sorted_GB.head(top_n_features)['feature']
    print('Sample 10 Feature from GB_Classifier:')
    print(str(features_top_n_GB[:10]))

    # concat
    features_top_n = pd.concat([features_top_n_RF, features_top_n_GB], 
                               ignore_index=True).drop_duplicates()
    features_importance = pd.concat([feature_imp_sorted_RF, feature_imp_sorted_GB],ignore_index=True)

    return features_top_n , features_importance

N = 20
feature_top_n, feature_importance = get_top_n_features(train_data_X, train_data_Y, N)
train_data_X = pd.DataFrame(train_data_X[feature_top_n])
test_data_X = pd.DataFrame(test_data_X[feature_top_n])
RandomForest
GradientBoosting

兩個分類模型的重要特征不盡相同,但是可以發現票價、年齡、性別、社會地位及家庭大小是比他們在船上的位置更為重要的因素。因此可以將Ticket相關的特征刪掉。

5.2 參數調整

參數調整采用交叉驗證,可以用scikit-learn中的網格搜索,即GridSearchCV類。這是一種窮舉的搜索方式:在所有候選的參數選擇中,通過循環遍歷,嘗試每一種可能性,表現最好的參數就是最終的結果。

為什么叫網格搜索?以有兩個參數的模型為例,參數a有3種可能,參數b有4種可能,把所有可能性列出來,可以表示成一個3*4的表格,其中每個cell就是一個網格,循環過程就像是在每個網格里遍歷、搜索,所以叫grid search(轉自這里

from sklearn import model_selection
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import GradientBoostingClassifier

seed = 4
# SVC
SVC_estimator = SVC(random_state=seed)
SVC_param_grid = {'C': [0.1,1,2,5], 'gamma': [0.2,0.1,0.04,0.01]}
SVC_grid = model_selection.GridSearchCV(SVC_estimator, SVC_param_grid, cv=10, verbose=1)
SVC_grid.fit(train_data_X, train_data_Y)
print('Best SVC Params:',str(SVC_grid.best_params_))
print('Best SVC Score:',str(SVC_grid.best_score_))

    
# RandomForest
RF_estimator = RandomForestClassifier(random_state=seed)
RF_param_grid = {'n_estimators': [500,1000], 'min_samples_split': [2, 3], 'max_depth': [10,20], 'min_samples_leaf':[2,3]}
RF_grid = model_selection.GridSearchCV(RF_estimator, RF_param_grid, cv=10, verbose=1)
RF_grid.fit(train_data_X, train_data_Y)
print('Best RF Params:',str(RF_grid.best_params_))
print('Best RF Score:',str(RF_grid.best_score_))


# Logistic Regression
LR_estimator =LogisticRegression(tol=1e-6, random_state=seed)
LR_param_grid = [{'penalty':['l1','l2'],'C': [0.01,0.1,1,5,10],'solver':['liblinear']},
                 {'penalty':['l2'],'C': [0.01,0.1,1,5,10],'solver':['lbfgs']}]
LR_grid = model_selection.GridSearchCV(LR_estimator, LR_param_grid, cv=10, verbose=1)
LR_grid.fit(train_data_X, train_data_Y)
print('Best LR Params:',str(LR_grid.best_params_))
print('Best LR Score:',str(LR_grid.best_score_))

# GradientBoosting
GB_estimator =GradientBoostingClassifier(random_state=seed)
GB_param_grid = {'n_estimators': [500,1000], 'learning_rate': [0.001, 0.01, 0.1], 'max_depth': [10,20], 'min_samples_leaf':[2,3]}
GB_grid = model_selection.GridSearchCV(GB_estimator, GB_param_grid, cv=10, verbose=1)
GB_grid.fit(train_data_X, train_data_Y)
print('Best GB Params:' + str(GB_grid.best_params_))
print('Best GB Score:' + str(GB_grid.best_score_))

可以得到幾種模型最優的參數設置:
Best SVC Params: {'C': 1, 'gamma': 0.2}
Best SVC Score: 0.843995510662

Best RF Params: {'max_depth': 10, 'min_samples_split': 2, 'n_estimators': 1000, 'min_samples_leaf': 2}
Best RF Score: 0.83950617284

Best LR Params: {'C': 5, 'solver': 'liblinear', 'penalty': 'l1'}
Best LR Score: 0.829405162738

Best GB Params:{'max_depth': 10, 'n_estimators': 1000, 'min_samples_leaf': 3, 'learning_rate': 0.001}
Best GB Score:0.818181818182

最后就可以用votingClassifer建立最終預測模型了(參考了Kaggle論壇):

# 將幾種模型的結果通過投票的方式進行聚合
from sklearn.ensemble import VotingClassifier
SVC_estimator = SVC(C=1, gamma = 0.2, kernel='rbf', decision_function_shape='ovo',random_state=seed, probability=True)  
RF_estimator = RandomForestClassifier(n_estimators=1000,max_depth=10, min_samples_split=2,
                                      max_features='sqrt',criterion = 'gini',
                                      min_samples_leaf=2,random_state =seed)
LR_estimator =LogisticRegression(tol=1e-6, C=5, solver='liblinear', penalty='l1', random_state=seed)
GB_estimator = GradientBoostingClassifier(n_estimators=1000, learning_rate=0.001, max_depth=10,
                                          min_samples_split=2, min_samples_leaf=3,random_state =seed)


vote_soft = VotingClassifier(estimators = [('SVC', SVC_estimator),('RF', RF_estimator),('LR', LR_estimator),('GB', GB_estimator)],
                             voting = 'soft', weights = [1.5,0.8,0.8,0.5],n_jobs =-1)
vote_soft.fit(train_data_X,train_data_Y)
predictions = vote_soft.predict(test_data_X).astype(np.int32)

最后別忘了輸出結果:

# 結果輸出
result = pd.DataFrame({'PassengerId':data_02['PassengerId'].as_matrix(),'Survived':predictions})
result.to_csv('submission_result_01.csv',index=False,sep=',')

準確率:0.79425

回顧這三天:

  • 參數調整過程太痛苦了,我的筆記本算的好慢...
  • 因為是帶著問題學習,所以很不系統,有很多很多很多沒考慮到的地方,比如過擬合欠擬合,比如提取到的特征不夠好,比如最終模型預測權重不知道怎么合理設置…
  • 一直以來都很喜歡邊用邊學,再學以致用的過程,是痛并享受著的三天。最終并沒有成為算命大師,準確率有點低~如果能讓屏幕前的你對數據分析產生一點點點點興趣的話就覺得很開心了!
  • 可能你也會對數據分析,從入門到放棄感興趣。

參考:
[1] http://www.cnblogs.com/fantasy01/p/4581803.html?utm_source=tuicool
[2] http://www.cnblogs.com/pinard/p/6126077.htm
[3] http://mars.run/2015/11/Machine%20learning%20kaggle%20titanic-0.8/
[4] https://blog.csdn.net/u012897374/article/details/74999940
[5] https://blog.csdn.net/app_12062011/article/details/50385522
[6] https://blog.csdn.net/app_12062011/article/details/50536369
[7] lhttps://zhuanlan.zhihu.com/p/30538352
[8] https://www.kaggle.com/yassineghouzam/titanic-top-4-with-ensemble-modeling
[9] https://www.kaggle.com/helgejo/an-interactive-data-science-tutorial

(-'?_?'-)謝謝您閱讀,請勿轉載。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容

  • 探索數據集-泰坦尼克號數據 一、讀取數據 import pandas as pdimport numpy as n...
    楊小彤閱讀 840評論 0 1
  • 對kaggle不做過多介紹 都知道這是一個數據挖掘的圣地,泰坦尼克號事件也不多做介紹,馬上進入正題 ...
    披風海膽放閱讀 1,215評論 1 4
  • pyspark.sql模塊 模塊上下文 Spark SQL和DataFrames的重要類: pyspark.sql...
    mpro閱讀 9,479評論 0 13
  • 1、加載文件,查看:(兩個數據集,train作為學習集進行數據建模,通過test測試集查看建模的情況。) trai...
    12_21閱讀 1,052評論 0 0
  • 最近擠出時間,用python在kaggle上試了幾個project,有點體會,記錄下。 Step1: Explor...
    JxKing閱讀 39,642評論 8 140