[机器学习入门]泰坦尼克生存预测
亿点题外话:
大家都熟悉『you jump i jump』的故事,豪华游艇倒了,大家都惊恐逃生,可是救生艇的数量有限,无法人人都有,副船长发话了『lady and kid first!』,所以是否获救其实并非随机,而是基于一些背景有rank先后的。
训练和测试数据是一些乘客的个人信息以及存活状况,要尝试根据它生成合适的模型并预测其他人的存活状况。
数据来源:
数据来源于 Kaggle,链接: TItanic 数据集
实践目标:
根据数据集中各个字段使用机器学习进行建模,来预测泰坦尼克沉船事件中的任一旅客生还的概率
初步分析:
首先观察给出的信息条目,检查数据的完整性,做出常识性推理
import pandas as pd from pandas import Series,DataFrame data_train =pd.read_csv('./titanic/train.csv') data_train.info()
得到以下信息:
<class 'pandas.core.frame.DataFrame'> RangeIndex: 891 entries, 0 to 890 Data columns (total 12 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 PassengerId 891 non-null int64 1 Survived 891 non-null int64 2 Pclass 891 non-null int64 3 Name 891 non-null object 4 Sex 891 non-null object 5 Age 714 non-null float64 6 SibSp 891 non-null int64 7 Parch 891 non-null int64 8 Ticket 891 non-null object 9 Fare 891 non-null float64 10 Cabin 204 non-null object 11 Embarked 889 non-null object dtypes: float64(2), int64(5), object(5) memory usage: 83.7+ KB
再使用 describe(),得到数值型数据的一些分布
print(data_train.describe(include = 'all'))
PassengerId | Survived | Pclass | Age | SibSp | Parch | Fare | |
---|---|---|---|---|---|---|---|
count | 891.000000 | 891.000000 | 891.000000 | 714.000000 | 891.000000 | 891.000000 | 891.000000 |
mean | 446.000000 | 0.383838 | 2.308642 | 29.699118 | 0.523008 | 0.381594 | 32.204208 |
std | 257.353842 | 0.486592 | 0.836071 | 14.526497 | 1.102743 | 0.806057 | 49.693429 |
min | 1.000000 | 0.000000 | 1.000000 | 0.420000 | 0.000000 | 0.000000 | 0.000000 |
25% | 223.500000 | 0.000000 | 2.000000 | NaN | 0.000000 | 0.000000 | 7.910400 |
50% | 446.000000 | 0.000000 | 3.000000 | NaN | 0.000000 | 0.000000 | 14.454200 |
75% | 668.500000 | 1.000000 | 3.000000 | NaN | 1.000000 | 0.000000 | 31.000000 |
max | 891.000000 | 1.000000 | 3.000000 | 80.000000 | 8.000000 | 6.000000 | 512.329200 |
从中,我们得到,整体的平均存活率为0.38,1等舱的人数少于2等舱少于3等舱,平均年龄为29.7,其余信息暂且也不知道是否有效这里先不罗列。
条目分析
- PassengerId => 乘客ID,无关变量
- Survived => 乘客是否存活
为本次建模的因变量,其余为自变量 - Pclass => 乘客等级(1/2/3等舱位)
- Name => 乘客姓名
按照常理,姓名应该是无关变量,但是本次建模中,姓名内包含了“Miss”、“Mrs”这类可以体现年龄的名称,以及“Master”这类体现身份地位的名称,故具有一定的参考意义。 - Sex => 性别
- Age => 年龄
存在数据缺失,暂定通过姓名信息补全 - SibSp => 堂兄弟/妹个数
- Parch => 父母子女个数
- Ticket => 船票信息,无关变量
- Fare => 票价
- Cabin => 客舱
由于缺失过多,暂定以二元变量参与计算 - Embarked => 登船港口
f,ax = plt.subplots(figsize=(16, 16)) sns.heatmap(data_train.corr(),annot=True,cmap = sns.diverging_palette(220, 10, as_cmap = True),linewidths=.9, fmt= '.3f',ax = ax) plt.title('Pearson Correlation of Features', y=1.05, size=15)
相关性检验:各个变量之间的皮尔逊系数没有强相关性,可以直接选用这些变量进行建模
逐条分析
Pclass:
Survived_0 = data_train.Pclass[data_train.Survived == 0].value_counts() Survived_1 = data_train.Pclass[data_train.Survived == 1].value_counts() df = pd.DataFrame({u'live': Survived_1, u'unlive': Survived_0}) df.plot(kind='bar', stacked=True) plt.show()
由常理推算,舱位可以体现经济状况,从而反映出是社会地位,那么在得救的时候就有了优先的权利,测了一下果然是酱紫,可见1/2等舱的存活率明显高于3等舱。那么这就成为了本次模型中一个很重要的特征。
Sex:
data_train = pd.read_csv('./titanic/train.csv') Survived_m = data_train.Sex[data_train.Survived == 0].value_counts() Survived_f = data_train.Sex[data_train.Survived == 1].value_counts() df = pd.DataFrame({u'live': Survived_m, u'unlive': Survived_f}) df.plot(kind='bar', stacked=True) plt.xticks(rotation=0) plt.show()
由此可见船长那一句“lady first”大家践行的还是很好的,不只是喊喊口号,所以Sex也成为一项重要的特征。
Age:
与前两项自变量不同,Age缺少了部分的数据。
通常遇到缺值的情况,我们会有几种常见的处理方式
- 如果缺值的样本占总数比例极高,我们可能就直接舍弃了,作为特征加入的话,可能反倒带入noise,影响最后的结果了
- 如果缺值的样本适中,而该属性非连续值特征属性(比如说类目属性),那就把NaN作为一个新类别,加到类别特征中
- 如果缺值的样本适中,而该属性为连续值特征属性,有时候我们会考虑给定一个step(比如这里的age,我们可以考虑每隔2/3岁为一个步长),然后把它离散化,之后把NaN作为一个type加到属性类目中。
- 有些情况下,缺失的值个数并不是特别多,那我们也可以试着根据已有的值,拟合一下数据,补充上。
- 本例中,后两种处理方式应该都是可行的,我们先试试拟合补全吧(虽然说没有特别多的背景可供我们拟合,这不一定是一个多么好的选择)
我们这里用scikit-learn中的RandomForest(参考CSDN韩小阳)来拟合一下缺失的年龄数据(注:RandomForest是一个用在原始数据中做不同采样,建立多颗DecisionTree,再进行average等等来降低过拟合现象,提高结果的机器学习算法)
from sklearn.ensemble import RandomForestRegressor ### 使用 RandomForestClassifier 填补缺失的年龄属性 def set_missing_ages(df): # 把已有的数值型特征取出来丢进Random Forest Regressor中 age_df = df[['Age','Fare', 'Parch', 'SibSp', 'Pclass']] # 乘客分成已知年龄和未知年龄两部分 known_age = age_df[age_df.Age.notnull()].values unknown_age = age_df[age_df.Age.isnull()].values y = known_age[:, 0] # y即目标年龄 X = known_age[:, 1:] # X即特征属性值 # fit到RandomForestRegressor之中 rfr = RandomForestRegressor(random_state=0, n_estimators=2000, n_jobs=-1) rfr.fit(X, y) # 用得到的模型进行未知年龄结果预测 predictedAges = rfr.predict(unknown_age[:, 1::]) # 用得到的预测结果填补原缺失数据 df.loc[ (df.Age.isnull()), 'Age' ] = predictedAges return df, rfr def set_Cabin_type(df): df.loc[ (df.Cabin.notnull()), 'Cabin' ] = 1 df.loc[ (df.Cabin.isnull()), 'Cabin' ] = 0 return df data_train, rfr = set_missing_ages(data_train) data_train = set_Cabin_type(data_train)
对 'SibSp'、'Parch'、'Sex'列生成新列
data_train['FamilySize'] = data_train ['SibSp'] + data_train['Parch'] + 1 data_train['Sex'][data_train['Sex'] == 'male'] = 1 data_train['Sex'][data_train['Sex'] == 'female'] = 0 data_train['IsAlone'] = 1 data_train['IsAlone'].loc[data_train['FamilySize'] > 1] = 0
- 用“FamilySize”代替 'SibSp'、'Parch',因为通过对 'SibSp'、'Parch'单独分析发现没有显著特征,于是尝试合并两项(体现家族人数,也许大家族优先?)
- 生成 IsAlone 由于familysize数据较为分散,为了更好预测集中数据特征,将乘客分为一人与多人
- 用0/1代替 male/female
“Age”和“Fare”的数据收敛处理:
from sklearn.preprocessing import LabelEncoder
data_train['FareBin'] = pd.qcut(data_train['Fare'], 4)
data_train['AgeBin'] = pd.cut(data_train['Age'].astype(int), 5)
label = LabelEncoder()
data_train['AgeBin'] = label.fit_transform(data_train['AgeBin'])
data_train['FareBin'] = label.fit_transform(data_train['FareBin'])