一文搞懂泰坦尼克生存率分析
經歷了這麼多理論知識的學習,這次我們就來實際練一下手,看看真實的machine learning是什麼內容。
這就不得不介紹一下kaggle平台了,這是一個機器學習工作者的舞台。為什麼這麼重要呢?參考這篇文章:
Kaggle如何入門?然後你就可以來這裡下載泰坦尼克項目的原始數據了:
Titanic: Machine Learning from Disaster要做的其實很簡單,三步:登陸kaggle官網、下載測試和訓練數據、提交結果。是不是很簡單?哈哈
OK,閑話不說,看一下這個案例的具體內容吧,先上一個流程圖:
機器學習的步驟
如圖所示,數據分析分為下面幾步:
提出問題、理解數據、數據清洗(包括數據預處理和特徵工程)、構建模型、模型評估、方案實施。是不是感覺蠻熟悉的?在之前的數據分析基本流程里我們已經講過了,這裡又新增了特徵工程和模型評估這兩部分。
下面就一步步完成吧。
提出問題
這個在本次案例中很簡單:什麼樣的人在泰坦尼克號中更容易存活。當然在日常的工作學習中提出的問題又很多,你需要和業務部門進行充分的溝通後得到。
理解數據
首先我們導入剛剛下載好的原始數據:
#導入處理數據包import numpy as npimport pandas as pd#導入數據#訓練數據集train = pd.read_csv("./train.csv")#測試數據集test = pd.read_csv("./test.csv")#這裡要記住訓練數據集有891條數據,方便後面從中拆分出測試數據集用於提交Kaggle結果print (訓練數據集:,train.shape,測試數據集:,test.shape)
查一下訓練數據和測試數據各有多少行:
rowNum_train=train.shape[0]rowNum_test=test.shape[0]print(kaggle訓練數據集有多少行數據:,rowNum_train, ,kaggle測試數據集有多少行數據:,rowNum_test,)
kaggle訓練數據集有多少行數據: 891 ,kaggle測試數據集有多少行數據: 418
這裡一定要記住訓練數據和測試數據的行列數。因為,在數據清洗時,我們一般會將兩個資料庫合併,以減少工作量。之後的拆分則需要將合併的數據按照行列號分開。
合併的方法也很簡單,使用append方法:
#合併數據集,方便同時對兩個數據集進行清洗full = train.append( test , ignore_index = True )print (合併後的數據集:,full.shape)
合併之後,我們可以通過describe和info函數查看合併數據集的基本信息:
#獲取數據類型列的描述統計信息full.describe()describe只能查看數據類型的描述統計信息,對於其他類型的數據不顯示,比如字元串類型姓名(name),客艙號(Cabin)這很好理解,因為描述統計指標是計算數值,所以需要該列的數據類型是數據# 查看每一列的數據類型,和數據總數full.info()我們發現數據總共有1309行。其中數據類型列:年齡(Age)、船艙號(Cabin)裡面有缺失數據:1)年齡(Age)裡面數據總數是1046條,缺失了1309-1046=263,缺失率263/1309=20%2)船票價格(Fare)裡面數據總數是1308條,缺失了1條數據字元串列:1)登船港口(Embarked)裡面數據總數是1307,只缺失了2條數據,缺失比較少2)船艙號(Cabin)裡面數據總數是295,缺失了1309-295=1014,缺失率=1014/1309=77.5%,缺失比較大這為我們下一步數據清洗指明了方向,只有知道哪些數據缺失數據,我們才能有針對性的處理。
從information里我們可以看到,有一些數據缺失了,這就是我們下一步需要做的:數據清洗。
數據清洗
數據清洗分兩步:預處理和特徵工程。
這裡的數據預處理和之前學過的數據清洗一樣,包括a).選擇子集、b.)列明重命名、c).缺失數據處理、d).數據類型轉換、e).數據排序、f).異常值處理
當然這裡的數據都已經給的很合適了,我們直接從第三步、缺失數據處理開始。從info可以看出來,缺失的信息有:age、cabin、fare、embarked。還有一個survived是因為只有訓練數據有值才會缺失,我們不用管它。
先處理age和fare,使用平均值補全空值,使用的是fillna函數:
#年齡(Age)full[Age]=full[Age].fillna( full[Age].mean() )#船票價格(Fare)full[Fare] = full[Fare].fillna( full[Fare].mean() )print(處理後:)full.info()
OK,填好了。下一步是embarked,登船港口。缺失兩個。
分類變數Embarked,看下最常見的類別,用其填充full[Embarked].value_counts()
查看一下最常見的embarked類別:
看來S類別最常見。我們將缺失值填充為最頻繁出現的值:
full[Embarked] = full[Embarked].fillna( S )
最後一類缺失項是cabin,缺失了1309-295=1014項,嗯,相當多。這裡只能按照unknown處理了:
#缺失數據比較多,船艙號(Cabin)缺失值填充為U,表示未知(Uknow) full[Cabin] = full[Cabin].fillna( U )#查看最終缺失值處理情況,記住生成情況(Survived)這裡一列是我們的標籤,用來做機器學習預測的,不需要處理這一列full.info()
特徵工程
處理好缺失項,我們看一下特徵工程,嗯,一個新名詞。而且是機器學習中十分重要的一環。
什麼是特徵工程呢?簡單的說,特徵工程就是最大限度的從原始數據中提取特徵以供機器學習演算法和模型使用。它包括了特徵提取、特徵選擇和特徵降維。
先來看特徵提取。我們正常的數據分為三種:數值類型數據、數據序列數據、分類數據。數值類型數據是我們可以直接使用的數據,時間序列數據需要我們轉換成年月日再使用(見數據分析流程里的案例)。還有一種比較特殊,就是分類數據,比如男/女;少年/青年/中年/老年;或者這裡的頭等艙/二等艙/三等艙。這種類型的數據需要我們使用one-hot編碼。
什麼是one-hot編碼呢?
如圖,簡單的說,就是把不同的分類做出一個新的使用1/0值組成的表格。這種方法可以減少數據分析的調倉工作,以後會再講到。
現在可以看一下我們的特徵都有哪些類型:
1.數值類型:乘客編號(PassengerId),年齡(Age),船票價格(Fare),同代直系親屬人數(SibSp),不同代直系親屬人數(Parch)2.時間序列:無3.分類數據:1)有直接類別的乘客性別(Sex):男性male,女性female登船港口(Embarked):出發地點S=英國南安普頓Southampton,途徑地點1:C=法國 瑟堡市Cherbourg,出發地點2:Q=愛爾蘭 昆士敦Queenstown客艙等級(Pclass):1=1等艙,2=2等艙,3=3等艙2)字元串類型:可能從這裡面提取出特徵來,也歸到分類數據中乘客姓名(Name)客艙號(Cabin)船票編號(Ticket)
看來分類數據還是蠻多的。下面我們先進行特徵提取。
特徵提取
首先是性別:
將性別的值映射為數值男(male)對應數值1,女(female)對應數值0sex_mapDict={male:1, female:0}#map函數:對Series每個數據應用自定義的函數計算#map函數的解釋:https://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.map.htmlfull[Sex]=full[Sex].map(sex_mapDict)
插一句,map函數是一個很有用的函數,可以對series類型的數據進行批量處理,語法也很簡單。
然後是登船港口embarked,這裡使用get_dummies函數,dummy就是虛擬的意思。
#存放提取後的特徵embarkedDf = pd.DataFrame()使用get_dummies進行one-hot編碼,產生虛擬變數(dummy variables),列名前綴是EmbarkedembarkedDf = pd.get_dummies( full[Embarked] , prefix=Embarked )embarkedDf.head()
結果如下:
這就是one-hot編碼的結果。當然,還需要有一步將這個結果添加到源數據集中,這裡使用concat函數
#添加one-hot編碼產生的虛擬變數(dummy variables)到泰坦尼克號數據集full#pd.concat表示將兩個數據框鏈接,axis=1表示將embarkedDf放到在新列中full = pd.concat([full,embarkedDf],axis=1)因為已經使用登船港口(Embarked)進行了one-hot編碼產生了它的虛擬變數(dummy variables)所以這裡把登船港口(Embarked)刪掉full.drop(Embarked,axis=1,inplace=True)full.head()
接下來是客艙等級(pclass),方法同上
客艙等級(Pclass):1=1等艙,2=2等艙,3=3等艙#存放提取後的特徵pclassDf = pd.DataFrame()#使用get_dummies進行one-hot編碼,列名前綴是PclasspclassDf = pd.get_dummies( full[Pclass] , prefix=Pclass )#添加one-hot編碼產生的虛擬變數(dummy variables)到泰坦尼克號數據集fullfull = pd.concat([full,pclassDf],axis=1)#刪掉客艙等級(Pclass)這一列full.drop(Pclass,axis=1,inplace=True)full.head()
現在再看一下前幾行就變成這樣了:
當然,有一些數據不是可以直接分類的,比如這裡的姓名、客艙號、船票編號。我們也有辦法。
先來看姓名。姓名中有一些固定的部分,這裡就是頭銜:
查看姓名這一列長啥樣注意到在乘客名字(Name)中,有一個非常顯著的特點:乘客頭銜每個名字當中都包含了具體的稱謂或者說是頭銜,將這部分信息提取出來後可以作為非常有用一個新變數,可以幫助我們進行預測。例如:Braund, Mr. Owen HarrisHeikkinen, Miss. LainaOliva y Ocana, Dona. FerminaPeter, Master. Michael J#練習從字元串中提取頭銜,例如Mr#split用於字元串分割,返回一個列表#我們看到姓名中Braund, Mr. Owen Harris,逗號前面的是「名」,逗號後面是『頭銜. 姓』name1=Braund, Mr. Owen Harrissplit用於字元串按分隔符分割,返回一個列表。這裡按逗號分隔字元串也就是字元串Braund, Mr. Owen Harris被按分隔符,拆分成兩部分[Braund,Mr. Owen Harris]你可以把返回的列表列印出來瞧瞧,這裡獲取到列表中元素序號為1的元素,也就是獲取到頭銜所在的那部分,即Mr. Owen Harris這部分#Mr. Owen Harrisstr1=name1.split( , )[1] 繼續對字元串Mr. Owen Harris按分隔符.拆分,得到這樣一個列表[Mr, Owen Harris]這裡獲取到列表中元素序號為0的元素,也就是獲取到頭銜所在的那部分Mr#Mr.str2=str1.split( . )[0]#strip() 方法用於移除字元串頭尾指定的字元(默認為空格)str3=str2.strip()
我們當然不能一個一個去操作,可以編寫一個函數來完成:
定義函數:從姓名中獲取頭銜def getTitle(name): str1=name.split( , )[1] #Mr. Owen Harris str2=str1.split( . )[0]#Mr #strip() 方法用於移除字元串頭尾指定的字元(默認為空格) str3=str2.strip() return str3#存放提取後的特徵titleDf = pd.DataFrame()#map函數:對Series每個數據應用自定義的函數計算titleDf[Title] = full[Name].map(getTitle)titleDf.head()
有了title,就可以根據title編碼了。
定義以下幾種頭銜類別:Officer政府官員Royalty王室(皇室)Mr已婚男士Mrs已婚婦女Miss年輕未婚女子Master有技能的人/教師#姓名中頭銜字元串與定義頭銜類別的映射關係title_mapDict = { "Capt": "Officer", "Col": "Officer", "Major": "Officer", "Jonkheer": "Royalty", "Don": "Royalty", "Sir" : "Royalty", "Dr": "Officer", "Rev": "Officer", "the Countess":"Royalty", "Dona": "Royalty", "Mme": "Mrs", "Mlle": "Miss", "Ms": "Mrs", "Mr" : "Mr", "Mrs" : "Mrs", "Miss" : "Miss", "Master" : "Master", "Lady" : "Royalty" }#map函數:對Series每個數據應用自定義的函數計算titleDf[Title] = titleDf[Title].map(title_mapDict)#使用get_dummies進行one-hot編碼titleDf = pd.get_dummies(titleDf[Title])titleDf.head()
#添加one-hot編碼產生的虛擬變數(dummy variables)到泰坦尼克號數據集fullfull = pd.concat([full,titleDf],axis=1)#刪掉姓名這一列full.drop(Name,axis=1,inplace=True)
title提取完成。
同樣的道理,我們可以把船艙編碼和家庭情況進行特徵提取:
客艙號:
#存放客艙號信息cabinDf = pd.DataFrame()客場號的類別值是首字母,例如:C85 類別映射為首字母Cfull[ Cabin ] = full[ Cabin ].map( lambda c : c[0] )##使用get_dummies進行one-hot編碼,列名前綴是CabincabinDf = pd.get_dummies( full[Cabin] , prefix = Cabin )#添加one-hot編碼產生的虛擬變數(dummy variables)到泰坦尼克號數據集fullfull = pd.concat([full,cabinDf],axis=1)#刪掉客艙號這一列full.drop(Cabin,axis=1,inplace=True)full.head()
家庭情況:
#存放家庭信息familyDf = pd.DataFrame()家庭人數=同代直系親屬數(Parch)+不同代直系親屬數(SibSp)+乘客自己(因為乘客自己也是家庭成員的一個,所以這裡加1)familyDf[ FamilySize ] = full[ Parch ] + full[ SibSp ] + 1家庭類別:小家庭Family_Single:家庭人數=1中等家庭Family_Small: 2<=家庭人數<=4大家庭Family_Large: 家庭人數>=5#if 條件為真的時候返回if前面內容,否則返回0familyDf[ Family_Single ] = familyDf[ FamilySize ].map( lambda s : 1 if s == 1 else 0 )familyDf[ Family_Small ] = familyDf[ FamilySize ].map( lambda s : 1 if 2 <= s <= 4 else 0 )familyDf[ Family_Large ] = familyDf[ FamilySize ].map( lambda s : 1 if 5 <= s else 0 )#添加one-hot編碼產生的虛擬變數(dummy variables)到泰坦尼克號數據集fullfull = pd.concat([full,familyDf],axis=1)full.head()
現在看看特徵情況:
33列,比之前(12列)多了很多。不過,怎麼選擇特徵呢?這就要看「特徵選擇」這一步了。
特徵選擇
特徵選擇是一個很複雜的問題了,需要長時間積累,本案例使用最簡單的「相關係數法」判斷,有關特徵工程的詳細方法可以參考兩個鏈接:
使用sklearn做單機特徵工程 - jasonfreak - 博客園機器學習之特徵工程相關係數法?很熟悉吧?就是我們之前在線性擬合分析裡面學到的相關係數,這裡使用相關係數矩陣來選擇特徵。
#相關性矩陣corrDf = full.corr() corrDf
矩陣太多了,這裡只放一部分吧。我們的目的是找到和存活情況相關係數最大的那些特徵:
查看各個特徵與生存情況(Survived)的相關係數,ascending=False表示按降序排列corrDf[Survived].sort_values(ascending =False)
看來女士的確有優勢呢。
根據各個特徵與生成情況(Survived)的相關係數大小,我們選擇了這幾個特徵作為模型的輸入:
頭銜(前面所在的數據集titleDf)、客艙等級(pclassDf)、家庭大小(familyDf)、船票價格(Fare)、船艙號(cabinDf)、登船港口(embarkedDf)、性別(Sex)
#特徵選擇full_X = pd.concat( [titleDf,#頭銜 pclassDf,#客艙等級 familyDf,#家庭大小 full[Fare],#船票價格 cabinDf,#船艙號 embarkedDf,#登船港口 full[Sex]#性別 ] , axis=1 )
這樣,特徵選擇就做好了。
構建模型
構建模型之前也有做過,但是這次由於我們在數據清洗這一步把訓練數據和測試數據合併了,這裡需要在將兩部分拆分出來,這時候要特別注意不能拆分錯誤了。
1)坦尼克號測試數據集因為是我們最後要提交給Kaggle的,裡面沒有生存情況的值,所以不能用於評估模型。我們將Kaggle泰坦尼克號項目給我們的測試數據,叫做預測數據集(記為pred,也就是預測英文單詞predict的縮寫)。也就是我們使用機器學習模型來對其生存情況就那些預測。2)我們使用Kaggle泰坦尼克號項目給的訓練數據集,做為我們的原始數據集(記為source),從這個原始數據集中拆分出訓練數據集(記為train:用於模型訓練)和測試數據集(記為test:用於模型評估)。sourceRow是我們在最開始合併數據前知道的,原始數據集有總共有891條數據從特徵集合full_X中提取原始數據集提取前891行數據時,我們要減去1,因為行號是從0開始的。#原始數據集:特徵source_X = full_X.loc[0:sourceRow-1,:]#原始數據集:標籤source_y = full.loc[0:sourceRow-1,Survived] #預測數據集:特徵pred_X = full_X.loc[sourceRow:,:]
做好這一步之後我們可以檢查一下:
確保這裡原始數據集取的是前891行的數據,不然後面模型會有錯誤#原始數據集有多少行print(原始數據集有多少行:,source_X.shape[0])#預測數據集大小print(原始數據集有多少行:,pred_X.shape[0])
檢查無誤了,接下來就是按照之前邏輯回歸的演算法設計訓練數據並建立模型:
從原始數據集(source)中拆分出訓練數據集(用於模型訓練train),測試數據集(用於模型評估test)train_test_split是交叉驗證中常用的函數,功能是從樣本中隨機的按比例選取train data和test datatrain_data:所要劃分的樣本特徵集train_target:所要劃分的樣本結果test_size:樣本佔比,如果是整數的話就是樣本的數量from sklearn.cross_validation import train_test_split #建立模型用的訓練數據集和測試數據集train_X, test_X, train_y, test_y = train_test_split(source_X , source_y, train_size=.8)#輸出數據集大小print (原始數據集特徵:,source_X.shape, 訓練數據集特徵:,train_X.shape , 測試數據集特徵:,test_X.shape)print (原始數據集標籤:,source_y.shape, 訓練數據集標籤:,train_y.shape , 測試數據集標籤:,test_y.shape)
建立模型:
#第1步:導入演算法from sklearn.linear_model import LogisticRegression#第2步:創建模型:邏輯回歸(logisic regression)model = LogisticRegression()#第3步:訓練模型model.fit( train_X , train_y )
當然,模型不止一種,之後的學習還會進一步了解,這裡先Mark一下:
#隨機森林Random Forests Model#from sklearn.ensemble import RandomForestClassifier#model = RandomForestClassifier(n_estimators=100)#支持向量機Support Vector Machines#from sklearn.svm import SVC, LinearSVC#model = SVC()#Gradient Boosting Classifier#from sklearn.ensemble import GradientBoostingClassifier#model = GradientBoostingClassifier()#K-nearest neighbors#from sklearn.neighbors import KNeighborsClassifier#model = KNeighborsClassifier(n_neighbors = 3)# Gaussian Naive Bayes#from sklearn.naive_bayes import GaussianNB#model = GaussianNB()
模型評估
按照之前的邏輯回歸的方法,這裡使用正確率評估模型
# 分類問題,score得到的是模型的正確率model.score(test_X , test_y )
方案實施
別忘了,我們是要將結果上傳到kaggle上的。看一下kaggle上傳的要求,我們還需要把預測結果改成整型數據。
#使用機器學習模型,對預測數據集中的生存情況進行預測pred_Y = model.predict(pred_X)生成的預測值是浮點數(0.0,1.0)但是Kaggle要求提交的結果是整型(0,1)所以要對數據類型進行轉換pred_Y=pred_Y.astype(int)#乘客idpassenger_id = full.loc[sourceRow:,PassengerId]#數據框:乘客id,預測生存情況的值predDf = pd.DataFrame( { PassengerId: passenger_id , Survived: pred_Y } )predDf.shapepredDf.head()#保存結果predDf.to_csv( titanic_pred.csv , index = False )
然後,上傳kaggle:
你還可以在kaggle上看到自己的排名:
當然,這只是一個初步的模型,用這一套流程下來得分大概0.76分吧,當然你也可以不斷優化自己的模型,再將新的模型預測結果上傳,獲得更好的分數。
如果你還想了解一下大神是怎麼做這個項目的,可以看這個鏈接:
機器學習系列(3)_邏輯回歸應用之Kaggle泰坦尼克之災機器學習系列(3)_邏輯回歸應用之Kaggle泰坦尼克之災 - CSDN博客機器學習系列(3)_邏輯回歸應用之Kaggle泰坦尼克之災 - CSDN博客最後附一張小結吧:
推薦閱讀:
※周志華西瓜書習題2.5詳細解答
※雨沐田:數據分析有哪些步驟呢?
※使用開源軟體快速搭建數據分析平台
※kylin 同步原理及加入重試邏輯
※小白學習大數據掌握這幾個方法可輕鬆入門