大纲
- 序
- 数据来源
- 工具介绍
- 数据分析
- 数据概览
- 测定基准
- 分析特征重要性
- 数据展示(可视化)
- 特征工程
- 数据清洗
- 提取新特征
- 特征选取
- 数据导出
- 机器学习预测
- 选择算法
- 训练及评估
- 预测结果
- 人物画像
- 总结
- 结合历史核对结论
- 一些特别的数据
- 结语
- 参考
序
- 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年,即使是年龄最小的幸存者也早已不在人世,这个事件留下给世人的教训不应该只有对影视作品的唏嘘与缅怀。
在冰冷没有情感的数据上进行分析解读,我们发现“物竞天择,适者生存”这样的大自然生存法则,在泰坦尼克这样的灾难事件上完全失去了作用。
随着科技的发展、更为先进的探测、预警工具的研发,人工智能驾驶技术的投入,以后这样大型的意外事件可能会越来少发生,但一旦发生了,影响个体存活的因素,除了科技手段,还有群体的文明程度。很庆幸,我们生活在一个科技、文明都在高速发展的时代。
那么当科技发展的速度超过文明发展,由没有感性只有理性的机器、人工智能来定最佳的生存选择的策略时,又会是一种什么局面?