大綱
- 序
- 數據來源
- 工具介紹
- 數據分析
- 數據概覽
- 測定基準
- 分析特征重要性
- 數據展示(可視化)
- 特征工程
- 數據清洗
- 提取新特征
- 特征選取
- 數據導出
- 機器學習預測
- 選擇算法
- 訓練及評估
- 預測結果
- 人物畫像
- 總結
- 結合歷史核對結論
- 一些特別的數據
- 結語
- 參考
序
- 1912年,當時世界上體積最龐大、內部設施最豪華的客運輪船,有“永不沉沒”美譽的泰坦尼克號,在她的處女航中撞冰山沉入大西洋底3700米處,船上1500多人喪生。
- 1985年,美國和法國聯合搜索隊發現泰坦尼克號殘骸。
- 1997年,詹姆斯·卡梅隆執導美國電影《泰坦尼克號》,將整個驚心動魄的過程首次以電影的方式還原。
- 2012年,《泰坦尼克號》3D版在中國內地重映,再次喚起人們對艘富有傳奇色彩的巨輪的緬懷。
- 2017年5月,有科學家表示,細菌正在蠶食泰坦尼克號沉船殘骸。據最近的估計,到2030年,這艘船可能會迎來它的末日,徹底消失。
假設如果真有穿越這回事,突然哪一天你的靈魂穿越到了正在航行的泰坦尼克號上的某個乘客身上,你發現你身處某層客艙內,被你的同伴稱為Mr/Miss,而你只有現代的記憶。突然一聲巨響同時船身劇烈搖晃,人們驚慌不知所措,那么此時此刻的你,有多大機率可以活下來?
接下來,我會通過數據分析,告訴你有哪些特征會影響你的存活機率。
數據來源
著名的數據分析競賽網站Kaggle上,舉行了很多數據分析比賽,其中比較著名的就有 泰坦尼克號乘客生還預測 。
Kaggle提供的數據集中,共有1309名乘客數據,其中891是已知存活情況,剩下418則是需要進行分析預測的。
提供的數據特征如下:
PassengerId: 乘客編號
Survived :存活情況(存活:1 ; 死亡:0)
Pclass : 客艙等級
Name : 乘客姓名
Sex : 性別
Age : 年齡
SibSp : 同乘的兄弟姐妹/配偶數
Parch : 同乘的父母/小孩數
Ticket : 船票編號
Fare : 船票價格
Cabin :客艙號
Embarked : 登船港口
PassengerId
是數據唯一序號;Survived
是存活情況,為預測標記特征
;剩下的10個是原始特征數據。
下面我將嘗試分析所得原始數據,通過構建特征工程,建模預測乘客的生存情況。
工具介紹
以下數據分析過程中,我使用的是我自己用Ruby
語言開發的一套簡單的可通用的數據分析工具,集成了數據分析報告、數據可視化、特征工程構建、機器學習分類模型訓練、數據導出等。
先看下有哪些命令可以使用:
每個任務的用途和前置子任務用途描述如下:
每個任務都可以單獨執行,具體用法會在后面的分析過程中逐個演示。
數據分析
說到數據挖掘,是把散亂數據轉換成「有價值」信息的過程,數據是可以是數字或者文本內容甚至圖像,而信息是有語義的、人腦可理解的報告、圖表。
數據分析的結果,就是把數字轉化成人類可理解的信息。
數據概覽
先看一眼已有的數據概括。
第一步,配置好數據路徑:
第二步,執行數據分析任務 profile_data
輸出數據摘要信息:
rake app:classifiers:titanic:profile_data
得到基本的數據信息:
Rows: 1309, Cols: 12,Columns: ["PassengerId", "Survived", "Pclass", "Name", "Sex", "Age", "SibSp", "Parch", "Ticket", "Fare", "Cabin", "Embarked"]
(注:uniq_proportions
是數據唯一值數量的占比值,下面描述時直接x100,如 0.616
描述為 61.6%
,方便理解)
從輸出統計中可以得出以下信息:
-
Survived
中549條是0(死亡),342條是1(生還);占比(uniq_proportions)分別是61.6%和38.4,死亡率很高。有418條missing
(表示沒有值),是要預測的數據量。 -
Pclass
的unit_count
(唯一數量)是3,通過uniq_frequencies
看出分為1,2,3 個類別,對應頭等艙、二等艙和三等艙),其中3占了過半為54.2% - 有幾個特征有缺失數據(
missing
不為0的),Age
為263,Fare
為1,Cabin
為1014,Embarked
為2 -
Sex
乘客性別分布中男性占 64.4%,女性 35.6%(人多不一定是好事,后面分析中發現男性死亡很高) -
Age
乘客年齡分布中最小0.17(嬰兒),最大是80歲,平均為29.881 -
Ticket
總數是1309,而唯一數是929,說明有一票多人使用情況(這個信息在后面的特征提取中有用) -
Fare
最高是512.3292,而平均是33.295,貧富分化差距不小(同樣,這個信息在后面的特征提取中會有用) -
Embarked
有3組為"S"、"Q"、"C",其中"S"占69.9%,我猜這可能是啟航的港口,登船港口跟存活率有什么關系這是個疑問
測定基準-沒事跑個分
先跑個分看看,執行訓練任務train
:
rake app:classifiers:titanic:train summary=n
在什么數據都沒改動的情況下,使用所有特征數據來跑訓練任務(任務沒有配置時,默認使用樸素貝葉斯分類)和CV(Cross Validation,交叉驗證),得到平均的 F1 Score
是 0.75889
,意味這個是我們的模型的最低基準,最終構建出的模型分數要高于這個之上才是可靠的模型。
分析特征重要性
這么多特征,有哪些特征是跟存活率比較相關的?
可以使用這個數據分析任務,再做進一步分析:使用任務參數 label_column=Survived
來指明哪個是要預測的標記特征
,再執行一次數據分析任務:
rake app:classifiers:titanic:profile_data label_column=Survived
這次的輸出就有些差異,可以看到是針對 Survived
有值的數據來統計:
Profile 891 data which Survived is not nil
還多了特征分布占比統計:
Calcualte proportions of "Survived"
從而能知道各個特征值與標簽特征Survived
的對比,得出符合該特征時的“生存率”。
例如 Pclass 的統計:
+-------------+-------+------------+--------------------------------------------------------------+--------------------------------------------------------------+
| | count | uniq_count | Survived=0 | Survived=1 |
+-------------+-------+------------+--------------------------------------------------------------+--------------------------------------------------------------+
| Pclass | 891 | 3 | {3=>[372, "75.8%"], 1=>[80, "37.0%"], 2=>[97, "52.7%"]} | {3=>[119, "24.2%"], 1=>[136, "63.0%"], 2=>[87, "47.3%"]} |
Pclass=3(Survived=0)有372條,占75.8%,意思是三等艙的乘客中死亡率是75.8%;
Pclass=1(Survived=1)有136條,占63.0%,意味頭等艙的乘客中有63.0%是存活了下來。
以此類推,性別數據中:
+-------------+-------+------------+--------------------------------------------------------------+--------------------------------------------------------------+
| | count | uniq_count | Survived=0 | Survived=1 |
+-------------+-------+------------+--------------------------------------------------------------+--------------------------------------------------------------+
| Sex | 891 | 2 | {"male"=>[468, "81.1%"], "female"=>[81, "25.8%"]} | {"male"=>[109, "18.9%"], "female"=>[233, "74.2%"]} |
- 男性死亡率是81.1%,女性的死亡率是25.8%;
- 男性存活率是18.9%,女性的存活率是74.2%。
不用建模光是根據這2組數據就已經可以想象到,如果當時是女性,并入駐在頭等艙,存活下來的機率是很高的;反之,如果是男性,又是入駐在三等艙區的,差不多5個人中只能活1個。。。
這些分類結果比較少、存活率傾斜比較明顯的,顯然是比較重要的特征。那么其他特征值分布比較離散的,對最終的存活率影響是怎樣的呢?
在這里可以先使用 evaluate_features
(特征評估)任務來對每個特征進行一次初步評估,原理是單獨使用每個特征來自動訓練(默認使用了樸素貝葉斯做分類算法)并進行Cross Validation(交叉驗證),得出Accuracy(準確率)、Precision(精確率)、Recall(召回值),用來評估該特征對預測標記特征的重要程度。
執行特征評估任務,并使用選項label_column
指明Survived
為標記特征:
rake app:classifiers:titanic:evaluate_features label_column=Survived
留意提示:
Tips: if Average Accuracy > 0.8 means it's an important feature; if Precision is 0 means it's not reliable
從分析結果中可以看到:
-
Sex
的Accuracy
大于0.8,屬于比較重要的特征。 -
PassengerId
以及Name
的Precision
是0
,表示特征值太離散,不適合用來做預測的特征,而事實上這2個特征的值都是唯一不重復的(Name只有2條重復),確實不適合做為分類特征。 -
Age
、SibSp
、Parch
、Cabin
、和Embarked
的Precision
值都偏低(< 0.3),說明這個幾項相關性不足(有部分是受到缺失值得影響)。
數據展示(可視化)
人是視覺生物,天生對圖像信息比數字、文字信息更敏感,為了能更直觀的表達統計數據所體現的意義,可將數據以各種圖表的形式來可做視化展示,輔助對數據的理解。
我編寫的這套工具中集成了一些圖表的Web UI,可以直接使用數據分析任務的輸出報表來生成圖表,可以查看每一個特征的統計。
例如性別數量統計:
男多女少!那人多力量就大嗎?讓數據回答,加入存活狀態對比看看:
很明顯,不用看數字都看得明白:女性存活率更高!
乘客的年齡與人數的分布統計圖:
從圖中可看出在乘客中,人數最多的是24歲、有47人,整體呈現出中間層年齡段人數居多,人群以年輕、中年人為主。年輕力壯就會更有利嗎?未必!
在接下來的特征工程的后半段中會有更多的圖表展示,這里先不展開了。
特征工程
什么是特征工程?
數據和特征決定了機器學習的上限,而模型和算法只是逼近這個上限而已。而特征工程的目的是最大限度地從原始數據中提取特征以供算法和模型使用。顧名思義,特征工程其實就是一項工程活動,它是能夠將數據像藝術一般展現的技術。這樣說的原因是好的特征工程很好的混合了專業領域知識、直覺和基本的數學能力。
數據清洗
通過前面的數據分析知道有一些數據有缺失,需要先填充。
這里可以使用console=y
選項來以交互模式
來執行數據清洗任務clean_data
:
rake app:classifiers:titanic:clean_data console=y
填補 Fare
看下 Fare
數據的缺失情況:
只有一條缺失記錄,而這個乘客的Ticket只有他一個人在使用,無法通過查找相同票號的價格來填補。
船票的價格顯然是跟 Pclass
(客艙等級)及 Cabin
(客艙號)有關的,因此使用具有相同Pclass
和Cabin
的中位數來填補:
df.where(df['Fare'].not_eq(nil) & df['Pclass'].eq(3) & df['Cabin'].eq(nil) )['Fare'].median
=> 8.05
填補 Age
df['Age'].missing
=> 263
發現所有 Name
中有“Mr.” “Mrs.” 等頭銜字眼,因此提取出來可以作為一個輔助特征Title
,然后再使用具有相同Title
和Sex
的中位數來填補。
填補 Cabin
Cabin
缺失的比較多,但發現相同Cabin
的Ticket
也相同,因此可以反推具有相同Ticket
的乘客也會住在同一個Cabin
內,因此可以找出具有相同Ticket
但Cabin
不為空的乘客數據來填補:
df['Cabin'].missing
=> 1014
same_cabins = 0
df.where(df['Cabin'].eq(nil)).each_row_with_index do |row, idx|
same_cabin = df.where( df['Cabin'].not_eq(nil) & df['Ticket'].eq(row['Ticket']) )
if same_cabin.size != 0
same_cabins += 1
end
end
same_cabins
# => 16
發現這類數據也不多,只有16條。
其他的不確定性太多放棄填補。Cabin
數據雖然不完整,但還可以用來提取新的特征,下文有說明。
填補 Embarked
+-----+-------------+----------+--------+-------------------------------------------+--------+-----+-------+-------+--------+------+-------+----------+
| | PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked |
+-----+-------------+----------+--------+-------------------------------------------+--------+-----+-------+-------+--------+------+-------+----------+
| 62 | 62 | 1 | 1 | Icard, Miss. Amelie | female | 38 | 0 | 0 | 113572 | 80 | B28 | |
| 830 | 830 | 1 | 1 | Stone, Mrs. George Nelson (Martha Evelyn) | female | 62 | 0 | 0 | 113572 | 80 | B28 | |
+-----+-------------+----------+--------+-------------------------------------------+--------+-----+-------+-------+--------+------+-------+----------+
Embarked
的缺失只有2條,觀察數據后發現是2位女士,共用同一張Ticket
“113572”,住在同一個Cabin
“B28”,在同一個Pclass
1,因此推斷她們可能是認識的,因此也非常有可能是一起出行并在同一個港口登船,所以不用分別處理。另外我按常理推斷相同港口出售的票是相連的,可取Ticket
前4位數字相同的表示是相同港口,最后找出有相同Pclass
和相似Ticket
中 Embarked
的眾數:
df.filter_rows { |p| p['Ticket'].to_s.first(4) == '113572'.first(4) && p['Pclass'] == 1 } ['Embarked'].frequencies.max_index
=> #<Daru::Vector(1)>
C 7
得出是 “C”。
完成數據清洗后,查看現有的數據概況:
可以看到只有 Cabin
還有998條是缺失的了,至此數據清洗完畢,接下來做新特征提取。
提取新特征
問題:應該提取出哪些特征?
面對這樣的群體災難事件場景中,有什么因素會影響到個人生存機會呢?是個人的年齡、體力、社會身份、財富、擁有的權力?是因為你是女性、帶著孩子的母親?小孩和老人行動緩慢會很不利?人緣好朋友多會更有優勢?拖家帶口是累贅還是助力,還是獨善其身更有利?亦或是你當時所處的位置導致了最后的結局?
設想
結合已有的數據,我覺得有如下這些因素可以嘗試:
- Sex(性別):性別是人類個體之間最大的生理特征區別,影響到體力、社交地位,在群體中往往也受到優待(女士優先),而且前面的特征評估已說明這個特征很重要
- AgeLevel(年齡層):年齡影響到體力、社交地位,年紀小的、或者年紀大的在群體中往往也受到優待(尊老愛幼);這里可以分組為(child, young, midlife, aged),基于
Age
提取 - SocialTitle(社交頭銜):在群體活動中,不同身份顯然也會影響到群體決策 ;這里分組為 Mrs, Miss, Mr and Master,基于
Name
提取 - IncomeClass(收入水平): 收入決定了所買的船票類型,也就決定了客艙所在的位置,以及社交地位;分組為 no-income, lower-income, middle-income, upper-income), 可基于
Fare
和Pclass
提取 - SocialClass(社交地位):一個人可以沒有很多錢,但他從事的職業、貴族身份在群體活動中是有影響力的;分為 lower-class, middle-class, upper-class,基于
Title
,IncomeClass
提取 - FamilyRole(家庭角色):如果你是位父親,你所肩負的責任能讓你愿意作出巨大犧牲;一位母親在災難面前為了保護孩子往往迸發超出常人的生存欲望;可分組為grandparent, couple, father, mother, child;可基于
Sex
,AgeLevel
,SibSp
,Parch
- CabinArea(艙位所在區域):顯然這會影響到逃生的時機,基于
Cabin
- Mates(伙伴數量):這次航行中的同行人數(有多少人使用相同的
Ticket
) - FamilySize(同船的家庭成員數量): 家人在一起互助肯定比一群陌生人有更多的信任和生存機會;基于
SibSp
和Parch
- TravelAlone(是否孤身上路):
Mates
+FamilySize
,無論是朋友還是家人,多一個熟人在身邊也許就比別人多一份生存機率
實現
提取Sex
(性別)
使用原有數據,不需要改動。
提取 AgeLevel
(年齡層)
按如下年齡劃分:
age_level = lambda { |age, default="unknown"|
{
0..14.5 => 'child', # 少年0-14
15..35.5 => 'young', # 青年15-35
36..60.5 => 'midlife', # 中年36-60
61..90.5 => 'aged', # 老年61-90
}.select { |range| range === age }.values[0] || default
}
df['AgeLevel'] = df['Age'].map { |age| age_level.call(age) }
提取 SocialTitle
(社交頭銜)
# extract "Title" from "Name"
df['NameTitle'] = df['Name'].map { |name| name.match(/, ([^.]+)./)[1] }
# extract SocialTitle: what was his social title?
social_title = lambda { |title, sex|
title = title.sub("the ", "")
title = 'Mrs' if title == 'Dr' && sex == 'female' # a special case
{
%w{Mr Don Major Capt Jonkheer Rev Col Dr Sir}=> 'Mr',
%w{Miss Mlle Ms Lady Dona} => 'Miss',
%w{Mrs Countess Mme} => 'Mrs',
%w{Master} => 'Master',
}.select { |_| _.include? title }.values[0]
}
df['SocialTitle'] = df['NameTitle', 'Sex'].map(:row) { |row| social_title.call(row["NameTitle"], row["Sex"]) }
這樣把一些數量較少的NameTitle
都歸類起來,另外提取這個特征時,發現有一條特殊記錄,有一個Dr
是女性,因此做了特殊處理:
title = 'Mrs' if title == 'Dr' && sex == 'female' # a special case
提取 IncomeClass
(收入水平)
考慮到游船還是需要一定的經濟收入,我將上層階級
、中產階級
、下層階級
按照 1:2:7 的比例(下層等級的數量是上層的數量之和),獲取對應的唯一票價的分位數,作為分層的水平線:
uniq_fare = df['Fare'].uniq
income_classes = {"no-income": 0, "lower-income": uniq_fare.percentile(100-70), "middle-income": uniq_fare.percentile(100-20), "upper-income": uniq_fare.percentile(100-10) }.stringify_keys
# => {"no-income"=>0, "lower-income"=>10.5, "middle-income"=>57.75, "upper-income"=>82.2667}
得出的數據統計如下:
df['IncomeClass'].uniq_frequencies
# => {"middle-income"=>587, "lower-income"=>513, "upper-income"=>192, "no-income"=>17}
df['IncomeClass'].uniq_proportions
# => {"middle-income"=>0.448, "lower-income"=>0.392, "upper-income"=>0.147, "no-income"=>0.013}
參考資料:
提取 SocialClass
(社交地位)
社交地位中“名”與”利“起主要作用(別不服氣,現實社會的游戲規則就是這樣)。乘客數據中的頭銜信息很重要,因此可以把有貴族頭銜以及IncomeClass
是高級的都視作upper-class
;中產收入的歸為middle-class
;其他的就是lower-class
。
代碼簡單不展示了,只是有些英國的貴族頭銜信息我不熟悉需要查下參考資料。
參考資料:
提取 FamilyRole
(家庭角色)
這里判別規則比較復雜,看圖理解
提取 CabinArea
(艙位所在區域)
這里要先提取一個Deck
(甲板)特征,這個是根據Cabin
的首位字母得出的,可以分為A、B、C、D、E、F、G和T,未知的則標記為N/A
。
# extract "Deck" from "Cabin"
df['Deck'] = df['Cabin'].map { |cabin| cabin && cabin[0] || 'N/A' }
可以參考泰坦尼克號的剖面圖:
從圖上可以看出從字母越前越靠近輪船上層,意味逃生的路徑更短(這個和后面做新特征分析得到的統計信息是吻合的)。
這也是為什么Cabin
即使有缺失數據,也可以換個姿勢用來提取新特征。
CabinArea
則由Pclass
和Deck
組合而成,如 "P-1 D-C",意思是“頭等艙的C區”
# extract CabinArea: where was he? () based on Pclass, Deck
# e.g.: "P-1 D-C"
df['CabinArea'] = df['Pclass', 'Deck'].map(:row) { |x| "P-#{x['Pclass']} D-#{x['Deck']}" }
提取 Mates
(伙伴數量)
tickets = df['Ticket'].to_a.group_by(&:itself)
df['Mates'] = df['Ticket'].map { |ticket| tickets[ticket].size }
df['Mates'].uniq_proportions
# => {1=>0.545, 2=>0.202, 3=>0.112, 4=>0.049, 5=>0.027, 7=>0.027, 6=>0.018, 8=>0.012, 11=>0.008}
發現有近55%的乘客是獨自出行,而后面做新數據分析發現,這樣的人的生存率,不到3成。
提取 FamilySize
(同船的家庭成員數量)
很簡單,SibSp
+ Parch
# extract "FamilySize" from "SibSp" and "Parch"
df['FamilySize'] = df['SibSp'] + df['Parch']
df['FamilySize'].uniq_proportions
# {0=>0.604, 1=>0.18, 2=>0.121, 3=>0.033, 5=>0.019, 4=>0.017, 6=>0.012, 10=>0.008, 7=>0.006}
有6成是不帶家人,然而,后面的數據分析顯示,這個不是好主議。
提取 TravelAlone
(是否孤身上路)
是否有伙伴或親人,只有個人則標記為1,否則取0
df['TravelAlone'] = df['Mates', 'FamilySize'].map(:row) { |row| row.sum == 1 ? 1 : 0 }
df['TravelAlone'].uniq_frequencies
# => {1=>0.506, 0=>0.494}
各占一半。
把所有特征提取相關代碼添加到 extract_features
子任務中保存,至此,新的特征構建完畢!
至此我得到以下特征的數據:
- Sex(性別)
- AgeLevel(年齡層)
- SocialTitle(社交頭銜)
- IncomeClass(收入水平)
- SocialClass(社交地位)
- FamilyRole(家庭角色)
- CabinArea(艙位所在區域)
- Mates(伙伴數量)
- FamilySize(同船的家庭成員數量)
- TravelAlone(是否孤身上路)
這些新特征是否真的對預測存活有幫助,接下來讓看下新特征的分析和評估。
特征選取 - 評估及選取新特征
重新跑一次數據分析任務profile_data
,這次加上任務參數feature_columns
來指定要分析的特征數據: feature_columns=Sex,AgeLevel,SocialTitle,IncomeClass,SocialClass,FamilyRole,CabinArea,Mates,FamilySize,TravelAlone
執行的完整命令為:
rake app:classifiers:titanic:profile_data label_column=Survived feature_columns=Sex,AgeLevel,SocialTitle,IncomeClass,SocialClass,FamilyRole,CabinArea,Mates,FamilySize,TravelAlone
可以了解新增特征數據的整體概況,下面接著逐一分析每個新特征的數據統計。
新特征數據分析
要針對某個feature 分析,可以增加參數profile_feature=$feature_name
來執行數據分析任務profile_data
,例如:
rake app:classifiers:titanic:profile_data label_column=Survived feature_columns=Sex,AgeLevel,SocialTitle,IncomeClass,SocialClass,FamilyRole,CabinArea,Mates,FamilySize,TravelAlone profile_feature=Sex
得到:
Sex(性別):
結合存活狀態分布圖:
在這場事故中,女性擁有更高的生存機率:74.2%;男性僅有18.9%。
性別為什么在這樣的災難中其關鍵性作用?從事后幸存者的口中得知,這是因為一個男人的一個命令。我們帶著疑問繼續看下其他特征的統計分析。
AgeLevel(年齡層):
年齡層統計結果:
Profile feature "AgeLevel"
+----------+-------+-------------+----------------+----------------+
| AgeLevel | count | proportions | Survived=0 | Survived=1 |
+----------+-------+-------------+----------------+----------------+
| young | 591 | 0.663 | [379, "64.1%"] | [212, "35.9%"] |
| midlife | 196 | 0.22 | [118, "60.2%"] | [78, "39.8%"] |
| child | 82 | 0.092 | [35, "42.7%"] | [47, "57.3%"] |
| aged | 22 | 0.025 | [17, "77.3%"] | [5, "22.7%"] |
+----------+-------+-------------+----------------+----------------+
按生理階段分為4組:
-
child
:少年(0-14) -
young
:青年(15-35) -
midlife
:中年(36-60) -
aged
:老年(61-90)
生存率最高的是少年們,存活率是57.3%;老年人有最高的死亡率:77.3%;身強力壯的青年死亡率第二,為64.1%;更有生活經驗的中年存活率不到4成,為39.8%。
看圖更直觀:
從這些數據對比可以看出,年齡與存活率不是線性的關系,不是越低越好,也不是越高越差。思考下,年齡的分組,實際上也形成社會關系、家庭角色的分組差異,因此在這里統計表現可能只是表面結果,并不是深層原因。
SocialTitle(社交頭銜):
Profile feature "SocialTitle"
+-------------+-------+-------------+----------------+----------------+
| SocialTitle | count | proportions | Survived=0 | Survived=1 |
+-------------+-------+-------------+----------------+----------------+
| Mr | 537 | 0.603 | [451, "84.0%"] | [86, "16.0%"] |
| Miss | 186 | 0.209 | [55, "29.6%"] | [131, "70.4%"] |
| Mrs | 128 | 0.144 | [26, "20.3%"] | [102, "79.7%"] |
| Master | 40 | 0.045 | [17, "42.5%"] | [23, "57.5%"] |
+-------------+-------+-------------+----------------+----------------+
被稱呼為 Mrs
(太太)的存活率最高為79.7%,這個只是簡單的稱呼信息,別人尊稱你一聲“先生”、“小姐”或者“太太”,不會決定你的生死,但你可以據此快速了解到,當有人稱呼你為Mr
或Miss
時,你在這場事故中的存活概率。
IncomeClass(收入水平):
Profile feature "IncomeClass"
+---------------+-------+-------------+----------------+----------------+
| IncomeClass | count | proportions | Survived=0 | Survived=1 |
+---------------+-------+-------------+----------------+----------------+
| middle-income | 403 | 0.452 | [222, "55.1%"] | [181, "44.9%"] |
| lower-income | 348 | 0.391 | [273, "78.4%"] | [75, "21.6%"] |
| upper-income | 125 | 0.14 | [40, "32.0%"] | [85, "68.0%"] |
| no-income | 15 | 0.017 | [14, "93.3%"] | [1, "6.7%"] |
+---------------+-------+-------------+----------------+----------------+
存活率最低的是no-income
只有6.7%,最高的是upper-income
為68.0%,收入水平跟存活率有很明顯的線性關系:收入水平越高生存機率越高。這樣的結果很殘酷也很現實,更富有則擁有更多的生存優勢,從來如此。
有錢就一定能活下來嗎?
SocialClass(社交地位):
Profile feature "SocialClass"
+--------------+-------+-------------+----------------+----------------+
| SocialClass | count | proportions | Survived=0 | Survived=1 |
+--------------+-------+-------------+----------------+----------------+
| lower-class | 361 | 0.405 | [286, "79.2%"] | [75, "20.8%"] |
| middle-class | 357 | 0.401 | [201, "56.3%"] | [156, "43.7%"] |
| upper-class | 173 | 0.194 | [62, "35.8%"] | [111, "64.2%"] |
+--------------+-------+-------------+----------------+----------------+
存活率最低的是lower-class
為20.8%,最高的是upper-class
為64.2%,社交地位跟存活率有很明顯的線性關系:社交地位越高生存機率越高。這是說沒有利,也要有名嗎?數據不會撒謊,同樣很現實。
FamilyRole(家庭角色):
不看外因看內因,從家庭的角度看看:
Profile feature "FamilyRole"
+-------------+-------+-------------+----------------+----------------+
| FamilyRole | count | proportions | Survived=0 | Survived=1 |
+-------------+-------+-------------+----------------+----------------+
| single | 530 | 0.595 | [363, "68.5%"] | [167, "31.5%"] |
| mother | 87 | 0.098 | [27, "31.0%"] | [60, "69.0%"] |
| child | 82 | 0.092 | [35, "42.7%"] | [47, "57.3%"] |
| husband | 72 | 0.081 | [57, "79.2%"] | [15, "20.8%"] |
| wife | 49 | 0.055 | [8, "16.3%"] | [41, "83.7%"] |
| father | 49 | 0.055 | [42, "85.7%"] | [7, "14.3%"] |
| grandparent | 22 | 0.025 | [17, "77.3%"] | [5, "22.7%"] |
+-------------+-------+-------------+----------------+----------------+
存活率最低的是father
父親(14.3%),緊接是husband
丈夫(20.8%);最高的不是mother
母親(69.%),而是沒有孩子的wife
太太(83.7%)。
作為男性,明明是群體中最具有強壯的體魄,又有相對更豐富的生存經驗,怎么反而在這場事故中就成了生存機率最低的?這里再提出一個疑問。
CabinArea(艙位所在區域):
“天時不如地利”,艙位所在區域是否影響乘客到達甲板的時間、登上救生艇的機會?
Profile feature "CabinArea"
+-----------+-------+-------------+----------------+----------------+
| CabinArea | count | proportions | Survived=0 | Survived=1 |
+-----------+-------+-------------+----------------+----------------+
| P-3 D-N/A | 478 | 0.536 | [366, "76.6%"] | [112, "23.4%"] |
| P-2 D-N/A | 167 | 0.187 | [93, "55.7%"] | [74, "44.3%"] |
| P-1 D-C | 67 | 0.075 | [26, "38.8%"] | [41, "61.2%"] |
| P-1 D-B | 48 | 0.054 | [12, "25.0%"] | [36, "75.0%"] |
| P-1 D-N/A | 31 | 0.035 | [19, "61.3%"] | [12, "38.7%"] |
| P-1 D-D | 29 | 0.033 | [7, "24.1%"] | [22, "75.9%"] |
| P-1 D-E | 25 | 0.028 | [7, "28.0%"] | [18, "72.0%"] |
| P-1 D-A | 15 | 0.017 | [8, "53.3%"] | [7, "46.7%"] |
| P-2 D-F | 8 | 0.009 | [1, "12.5%"] | [7, "87.5%"] |
| P-3 D-F | 6 | 0.007 | [4, "66.7%"] | [2, "33.3%"] |
| P-2 D-E | 5 | 0.006 | [2, "40.0%"] | [3, "60.0%"] |
| P-2 D-D | 4 | 0.004 | [1, "25.0%"] | [3, "75.0%"] |
| P-3 D-G | 4 | 0.004 | [2, "50.0%"] | [2, "50.0%"] |
| P-3 D-E | 3 | 0.003 | [0, "0.0%"] | [3, "100.0%"] |
| P-1 D-T | 1 | 0.001 | [1, "100.0%"] | [0, "0.0%"] |
+-----------+-------+-------------+----------------+----------------+
- 存活率最低的是
P-1 D-T
,頭等艙T層甲板,只有1個人,存活率只有0%; - 存活率最高的是
P-3 D-E
,頭等艙E層甲板,有3個人,都活了下來,存活率100%; - 幸存人數最多的是
P-3 D-N/A
,三等艙其他區域,112人活了下來,而存活率只有23.4%; - 人數次之的是
P-2 D-N/A
,二等艙其他區域,存活率44.3%; - 第三的是
P-1 D-C
,頭等艙C層甲板,存活率61.2%;
觀察泰坦尼克號的艙室布局圖:
可以發現頭等艙分布在船體的各段部位,比二等、三等艙有更好的出入口、更接近甲板,因此應該擁有更有利的生存機會,跟得出的統計信息是吻合的。
你在哪里出發也就決定了你能走到哪里?輸在起跑線上,又有誰愿意呢。
Mates(伙伴數量):
中國古語云:“地利不如人和”,“在家靠父母,出門靠朋友”,看看出行的伙伴數量:
Profile feature "Mates"
+-------+-------+-------------+----------------+----------------+
| Mates | count | proportions | Survived=0 | Survived=1 |
+-------+-------+-------------+----------------+----------------+
| 1 | 481 | 0.54 | [351, "73.0%"] | [130, "27.0%"] |
| 2 | 181 | 0.203 | [88, "48.6%"] | [93, "51.4%"] |
| 3 | 101 | 0.113 | [35, "34.7%"] | [66, "65.3%"] |
| 4 | 44 | 0.049 | [12, "27.3%"] | [32, "72.7%"] |
| 7 | 24 | 0.027 | [19, "79.2%"] | [5, "20.8%"] |
| 5 | 21 | 0.024 | [14, "66.7%"] | [7, "33.3%"] |
| 6 | 19 | 0.021 | [15, "78.9%"] | [4, "21.1%"] |
| 8 | 13 | 0.015 | [8, "61.5%"] | [5, "38.5%"] |
| 11 | 7 | 0.008 | [7, "100.0%"] | [0, "0.0%"] |
+-------+-------+-------------+----------------+----------------+
- 1個獨自出行是最多的,但存活率只有 27.0%;
- 2個人結伴出行是數量排第二,存活率為 51.4%;
- 人數最多的有11個,有存活狀態的有7人,然后一個也沒活下來,存活率是0%;
- 存活率最高的是4個人一組的,有44個,存活率是72.7%;
- 除了0%以外存活率最低的是7個人一組的,有24人,存活率比單人還低,僅有20.8%;
從數據看,伙伴數量跟存活率并非是線性關系,而是呈現“凸函數”特征,2、3、4這樣的中間值都有過半的存活機率;1及6、7、11 兩端數值的生存率均不過3成。這是否是表明在出行時,3到4人是最佳的團隊人數,人數過少或過多都不利團隊生存。
FamilySize(同船的家庭成員數量):
家庭規模對生存的影響:
Profile feature "FamilySize"
+------------+-------+-------------+----------------+----------------+
| FamilySize | count | proportions | Survived=0 | Survived=1 |
+------------+-------+-------------+----------------+----------------+
| 0 | 537 | 0.603 | [374, "69.6%"] | [163, "30.4%"] |
| 1 | 161 | 0.181 | [72, "44.7%"] | [89, "55.3%"] |
| 2 | 102 | 0.114 | [43, "42.2%"] | [59, "57.8%"] |
| 3 | 29 | 0.033 | [8, "27.6%"] | [21, "72.4%"] |
| 5 | 22 | 0.025 | [19, "86.4%"] | [3, "13.6%"] |
| 4 | 15 | 0.017 | [12, "80.0%"] | [3, "20.0%"] |
| 6 | 12 | 0.013 | [8, "66.7%"] | [4, "33.3%"] |
| 10 | 7 | 0.008 | [7, "100.0%"] | [0, "0.0%"] |
| 7 | 6 | 0.007 | [6, "100.0%"] | [0, "0.0%"] |
+------------+-------+-------------+----------------+----------------+
- 沒有與親人同行的有537人,存活率30.4%;
- 存活率最低的去到0%,是成員數最大的7和10;
- 存活率最高是72.4%,是成員數為3;而人數到了4時生存率急劇下降到20.0%;
3人加上自己,也就是4,跟前面的Mates=4
時生存率最高的結果重疊了,這應該是團體票都是同一個票號的緣故。
TravelAlone(是否孤身上路):
Profile feature "TravelAlone"
+-------------+-------+-------------+----------------+----------------+
| TravelAlone | count | proportions | Survived=0 | Survived=1 |
+-------------+-------+-------------+----------------+----------------+
| 1 | 448 | 0.503 | [327, "73.0%"] | [121, "27.0%"] |
| 0 | 443 | 0.497 | [222, "50.1%"] | [221, "49.9%"] |
+-------------+-------+-------------+----------------+----------------+
當你一個人時,只有27.0%的機率存活;你有伙伴時,有49.9%的機會活下去。
所以出遠門時,還是結伴出行吧。
Mates
、FamilySize
、TravelAlone
這幾組數據表明,組隊是有必要的,但人數不宜過多,4個人在一起是最有優勢的組合。
數據導出
導出需要的特征數據進行存檔,執行數據導出任務export_features
:
rake app:classifiers:titanic:export_features label_column=Survived feature_columns=Sex,AgeLevel,SocialTitle,IncomeClass,SocialClass,FamilyRole,CabinArea,Mates,FamilySize,TravelAlone
Save to tmp/classifiers_output/titanic_survival_prediction-export.csv
導出數據在這里可以下載: titanic_survival_prediction-export.csv
機器學習預測
選擇算法
訓練數據有帶標注的特征、需要預測是否存活,顯然這是一個非常典型的二元分類問題,屬于監督學習分類問題;特征數據中有連續數值(continuous)和離散值(discrete),數據量也不大,這里我選擇了決策樹算法(Decisiontree)。另外,選擇這個算法,這也有種命運在做決策的意味。
訓練及評估
為了讓要預測特征比較語義,我加了一個新的特征 Survival
,只是簡單把1和0值映射為Survived
和No
:
# extract "Survival" so that can use it as the label_column
df['Survival'] = df['Survived'].map { |survived| survived && (survived == 1 ? 'Survived' : 'No') }
然后配置模型參數:
訓練及交叉驗證:
執行訓練任務train
會自動把有標記的數據集按比例拆分成訓練集和測試集,進行交叉驗證:
rake app:classifiers:titanic:train summary=n
模型評估結果的F1 Score
平均分是 0.81489,高于最開始的評分基準值,說明新增的特征整體上有更高的相關性。
要評估每個新特征的相關性,執行特征評估任務 evaluate_features
:
rake app:classifiers:titanic:evaluate_features
對比原始數據的評估結果,新特征的相關性更高些(CabinArea
由于有數據缺失,精確度不高)。
訓練結束后選擇 Persist trained data?(y/n):y
保存訓練結果,任務會自動保存模型圖像,可得到一棵決定存活與否的命運決策之樹:
預測結果
認為模型可信后,就可以對待預測數據進行預測,執行預測任務 predict
:
rake app:classifiers:titanic:predict summary=n
至此,完成了從數據分析到數據清洗、特征工程、機器學習建模以及最后的乘客生存狀態預測。
人物畫像
有了特征提取的規則,以及預測的模型,最后我們可以做下簡單的人物畫像,構建一個persona
任務,運行:
rake app:classifiers:titanic:persona
輸出如下:
... ...
"Klasen, Miss. Gertrud Emilia" was a child Miss.
She was 1 years old.
She was a child, with 2 families.
She was in a middle-income family since She spent $12.1833 to buy a 3rd Class ticket.
She embarked from Southampton port.
> Did She survive? Probably No.
"Parker, Mr. Clifford Richard" was a young Mr.
He was 28 years old.
He was single.
He was in a lower-income family since He spent $10.5 to buy a 2nd Class ticket.
He embarked from Southampton port.
> Did He survive? Probably No.
"Chaffee, Mrs. Herbert Fuller (Carrie Constance Toogood)" was a midlife Mrs.
She was 47 years old.
She was a wife, with Her husband.
She was in a upper-income family since She spent $61.175 to buy a 1st Class ticket.
She embarked from Southampton port.
Her cabin was on E Deck.
> Did She survive? Probably Survived.
... ...
那么,現在在船上的“你”,可以對照“自己”的特征,預測下自己有多少機會可以活下來了。
總結
結合歷史核對結論
數據分析的目的:通過數據,找出事件背后的原因、規律,用來改進、預防未來相似的事件。
在先前的分析數據時提出的疑問:
性別為什么在這樣的災難中其關鍵性作用?
為什么婦女兒童這類“弱者”反而更能生存?
根據泰坦尼克號唯一存活副船長查爾斯·萊特勒
,事后描述,面對沉船災難時,船長愛德華·約翰·史密斯(Edward J. Smith)
在最后的時刻下命令,命令先讓婦女和兒童上救生艇,許多乘客顯得十分平靜,一些人則拒絕與家人分開。
在生命面前,一切都是平等的。是什么抑制了生存的本能,讓人能作出如此犧牲讓步?顯然,是社會文明發展的結果。
是不是可以有個結論:災難發生時,如果跟高素質的人群聚集,弱者能得到優待?
再看另外一個問題:
有錢就一定能活下來嗎?
回憶錄中有描述:
亞斯特四世(當時世界第一首富)把懷著五個月身孕的妻子送上4號救生艇后,站在甲板上,帶著他的狗,點燃一根雪茄煙,對劃向遠處的小艇最后呼喊:"我愛你們!"
還有一個問題:
作為男性,明明是群體中最具有強壯的體魄,又有相對更豐富的生存經驗,怎么反而在這場事故中就成了生存機率最低的?
作為男人、作為孩子的父親、作為妻子的丈夫,肩頭上扛的一邊感情,另一邊是責任,面對災難是作出了何種抉擇,其實不用多說,看一段回憶錄:
一名叫那瓦特列的法國商人把兩個孩子送上了救生艇,委托幾名婦女代為照顧,自己卻拒絕上船。
兩個兒子得救后,世界各地的報紙紛紛登載兩個孩子的照片,直到他們的母親從照片上認出了他們。不幸的是,孩子們永遠失去了父親。
新婚燕爾的麗德帕絲同丈夫去美國度蜜月,她死死抱住丈夫不愿獨自逃生。
丈夫在萬般無奈中一拳將她打昏。麗德帕絲醒來時,她已在一條在海上漂浮的救生艇上了。
此后,她終生未再嫁,以此懷念亡夫。
一些特別數據
- 人數最多的家庭:
是 Sage一家,有11個人,大部分沒有存活。
- 年紀最小的乘客:
一個不足2個月的女嬰。
- 年紀最大的乘客:
是一位80高齡的老先生,當時幸存了下來。
- 唯一的女博士:
- 沒有家人的小孩:
不知為何沒有登記父母記錄,還是真的沒有父母陪同出行。
- 同名的人:
不確定是真的巧合同名,還是乘客登記信息有誤。
結語
泰坦尼克號事件發生距今已有105年,即使是年齡最小的幸存者也早已不在人世,這個事件留下給世人的教訓不應該只有對影視作品的唏噓與緬懷。
在冰冷沒有情感的數據上進行分析解讀,我們發現“物競天擇,適者生存”這樣的大自然生存法則,在泰坦尼克這樣的災難事件上完全失去了作用。
隨著科技的發展、更為先進的探測、預警工具的研發,人工智能駕駛技術的投入,以后這樣大型的意外事件可能會越來少發生,但一旦發生了,影響個體存活的因素,除了科技手段,還有群體的文明程度。很慶幸,我們生活在一個科技、文明都在高速發展的時代。
那么當科技發展的速度超過文明發展,由沒有感性只有理性的機器、人工智能來定最佳的生存選擇的策略時,又會是一種什么局面?