项目介绍
基于kaggle提供的泰坦尼克之灾数据,使用python与sklearn机器学习模块,预测乘客的存活状况
kaggle竞赛之泰坦尼克之灾作为数据挖掘入门的第一个实战作品,第一次提交15000名开外,目前最好成绩1811,排名10%。
第一提交:
kaggle rank.png
目前最好成绩:
1811.png
数据处理大概流程
- 数据清洗(Data Cleaning)
- 探索性可视化(Exploratory Visualization)
- 特征工程(Feature Engineering)
- 基本建模&评估(Basic Modeling& Evaluation)
- 参数调整(Hyperparameters Tuning)
- 集成方法(EnsembleMethods)
本文章将4、6步骤合成一个
1、数据清洗(Data Cleaning)
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import warnings
warnings.filterwarnings('ignore') # 去掉警告
plt.rc('font', family='SimHei', size=13) #用于解决中文显示不了的问题
train_df = pd.read_csv('./train.csv')
test_df = pd.read_csv('./test.csv')
整体观看数据
image.png# 查看是否有缺失值、异常值与数据类型,为后面数据预处理做准备
train_df.info()
test_df.info()
image.png
训练集的age、Cabin、Embarked有空值
测试集的age、fare、Cabin有空值
单因子分析
train_df.describe()
image.png
- 训练集实际乘客人数(891)是总样本泰坦尼克号(2,224,这个数值kaggle官网获得)上的40%。
- Survived是一个具有0或1值的分类特征, 约38%的样本存活
- SibS中位数为0说明一半以上的乘客是没有堂兄弟/妹或配偶陪同的
- Parch的四分位数表明>75的乘客没有和父母或孩子一起出行
- Fare平均票值mean为32,四分位数的上边缘为31,说明75%左右的乘客票价不高
- Age >75的乘客年龄在38岁之内
- Pclass 大多数乘客乘坐的是二、三等舱
train_df.describe(include=['O']) #参数O代表只列出object类型的列
image.png
- Survived 是存活情况,为预测标记特征;剩下的10个是原始特征数据。
- PassengerId 是数据唯一序号,无意义;
- age数据为714条,总样本有891条,缺失接近20%,不考虑直接使用众数、中位数进行替代,使用随机森林拟合缺失的数据
- Ticket unique 为681,离散程度太高,考虑直接drop掉(后面发现这个同一个Ticket 是同一个船舱,不能drop)
- Cabin 缺失太严重,不具有参考意义,考虑直接drop掉
- Embarked 使用中位数填充
- Names 在整个数据集中是唯一的(count = unique = 891),由于Names可能含有社会地位等信息,需要做特征衍生处理
数据预处理
# 标注
Y_train = train_df["Survived"]
X_train = train_df.drop(["Cabin"], axis=1)
X_test = test_df.drop(["Cabin"], axis=1)
# 对缺失的Embarked Fare以众数来填补
X_train.loc[(X_train.Embarked.isnull()),"Embarked"] = X_train["Embarked"].mode().values
X_test.loc[(X_test.Fare.isnull()),"Fare"] = X_test["Fare"].mode().values
X_train.info()
X_test.info()
# 使用随机森林拟合age缺失的数据
#RandomForest在原始数据中做不同采样,建立多颗DecisionTree,再进行average等来降低过拟合现象,提高结果的机器学习算法
from sklearn.ensemble import RandomForestRegressor
def set_missing_ages(df):
# print(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即目标年龄
y = known_age[:, 0]
# X即特征属性值
X = known_age[:, 1:]
# fit到RandomForestRegressor之中 n_estimators 森林中决策树的个数 n_jobs = -1 使用所有核
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
X_train, rfr1 = set_missing_ages(X_train)
X_test, rfr2 = set_missing_ages(X_test)
2、探索性可视化
f, [ax1,ax2,ax3] = plt.subplots(1,3,figsize=(20,5))
# sns.countplot(x='Sex', hue='Survived', data=X_train, ax=ax1)
sns.barplot(x="Pclass",y="Survived",data=X_train, ax=ax1)
ax1.set_title("Pclass vs Survived")
sns.barplot(x="Sex",y="Survived",data=X_train, ax=ax2)
ax2.set_title('Sex vs Survived')
sns.violinplot("Sex","Age", hue="Survived", data=X_train,split=True, ax=ax3)
ax3.set_title('Sex and Age vs Survived')
ax3.set_yticks(range(0,110,10))
f, [ax1,ax2] = plt.subplots(1,2,figsize=(20,5))
sns.violinplot("Pclass","Age", hue="Survived", data=X_train,split=True, ax=ax1)
ax1.set_title('Pclass and Age vs Survived')
ax1.set_yticks(range(0,110,10))
sns.barplot(x="SibSp",y="Survived",data=X_train, ax=ax2)
ax2.set_title('SibSp vs Survived')
f, [ax1,ax2] = plt.subplots(1,2,figsize=(20,5))
sns.barplot(x="Parch",y="Survived",data=X_train, ax= ax1)
ax1.set_title('Parch vs Survived')
sns.boxplot(x="Pclass",y="Fare",data=X_train, ax= ax2)
ax2.set_title('Parch vs Survived')
plt.show()
plt.savefig('grid.jpg')![grid.jpg]
image.png
image.png
- 优先救女性与小孩
- 船舱等级越高 存活率越高
- 20-40区间的存活率最高
- 一等舱中年的存活率明显比2、3等仓的存活率高
- 有1或2个 堂兄弟/妹或配偶的存活率较高高
- 有1至3个父母或小孩个数的存活率较高
- 一等舱的票价都比较高 二等舱次之 三等舱最便宜
# 探索船舱等级的男女存活率
X_train[['Sex','Pclass','Survived']].groupby(['Pclass','Sex']).mean().plot.bar()
image.png
不难看出 基本符合女性优先的原则, 但是不同船舱等级的男女存活率是有区别的 1等仓的男性率几乎是2、3等仓的2倍
# 探索Embarked与存活的关系
sns.countplot('Embarked',hue='Survived',data=X_train)
plt.title('Embarked and Survived')
plt.savefig('探索Embarked与存活的关系.jpg')
plt.show()
image.png
s港口的存活数最多 样本数也是最多的
# kde分布
f,ax = plt.subplots(figsize=(10,5))
sns.kdeplot(X_train.loc[(X_train['Survived'] == 0),'Age'] , color='gray',shade=True,label='not survived')
sns.kdeplot(X_train.loc[(X_train['Survived'] == 1),'Age'] , color='g',shade=True, label='survived')
plt.title('Age特征分布 - Surviver V.S. Not Survivors', fontsize = 15)
plt.xlabel("Age", fontsize = 15)
plt.ylabel('Frequency', fontsize = 15)
plt.savefig('Age特征分布.jpg')
plt.show()
image.png
Survived与Not Survived特征分布的主要区别在 0 ~15左右。
小于15岁以下的乘客(也就是孩子)获救率相对于较高,而大于15岁的乘客分布无明显区别
Ticket_Count = dict(train_df['Ticket'].value_counts())
train_df['TicketGroup'] = train_df['Ticket'].apply(lambda x:Ticket_Count[x])
sns.barplot(x='TicketGroup', y='Survived', data=train_df)
plt.savefig('共票号的乘客幸存率.jpg')
plt.show()
image.png
与2至4人共票号的乘客幸存率较高
train_df['FamilySize']=train_df['SibSp']+train_df['Parch']+1
sns.barplot(x="FamilySize", y="Survived", data=train_df)
plt.savefig('家庭人数幸存率.jpg')
plt.show()
image.png
家庭人数为2到4的乘客幸存率较高
结论
- 基本上是女人和儿童优先,一等舱是社会上社会财富比较多的人所在的舱,
- 一等级的舱乘客年龄相对要比其他两个等级的舱乘客年龄都要大,符合实际,有钱人一般年龄偏大,掌握社会财富较多,低位也相对较高
- 孤身一人与人数较多的小团队的存活率较低,猜测若是有配偶小孩等,有人会选择让另外的人活下去;人数较多会导致生存机会出让困难
3、 特征工程
特征衍生
import re
label = X_train["Survived"]
X_train = X_train.drop("Survived",axis=1)
# 根据称呼来建立一个新特征
def get_title(name):
title_search = re.search('([A-Za-z]+)\.', name)
if title_search:
return title_search.group(1)
return ""
def get_titles(df):
titles = df["Name"].apply(get_title)
# 将称号转换成数值表示
title_mapping = {"Mr": 1, "Miss": 2, "Mrs": 3, "Master": 4, "Dr": 5, "Rev": 6, "Major": 7, "Col": 8, "Mlle": 9,
"Mme": 10, "Don": 11, "Lady": 12, "Countess": 13, "Jonkheer": 14, "Sir": 15, "Capt": 16, "Ms": 17,
"Dona": 18
}
for k, v in title_mapping.items():
titles[titles == k] = v
return titles
def Ticket_Label(s):
if (s >= 2) & (s <= 4):
return 2
elif ((s > 4) & (s <= 8)) | (s == 1):
return 1
elif (s > 8):
return 0
def Fam_label(s):
if (s >= 2) & (s <= 4):
return 2
elif ((s > 4) & (s <= 7)) | (s == 1):
return 1
elif (s > 7):
return 0
# 训练集
# 添加Title特征
titles = get_titles(X_train)
# print(pd.value_counts(titles))
X_train["Title"] = titles
X_train["FamilySize"] = X_train["SibSp"] + X_train["Parch"] + 1
X_train['FamilyLabel']=X_train['FamilySize'].apply(Fam_label)
X_train["NameLength"] = X_train["Name"].apply(lambda x: len(x))
Ticket_Count = dict(X_train['Ticket'].value_counts())
X_train['TicketGroup'] = X_train['Ticket'].apply(lambda x:Ticket_Count[x])
X_train['TicketGroup'] = X_train['TicketGroup'].apply(Ticket_Label)
# 测试集
# 添加Title特征
titles = get_titles(X_test)
# print(pd.value_counts(titles))
X_test["Title"] = titles
X_test["FamilySize"] = X_test["SibSp"] + X_test["Parch"] + 1
X_test['FamilyLabel']=X_test['FamilySize'].apply(Fam_label)
X_test["NameLength"] = X_test["Name"].apply(lambda x: len(x))
Ticket_Count = dict(X_test['Ticket'].value_counts())
X_test['TicketGroup'] = X_test['Ticket'].apply(lambda x:Ticket_Count[x])
X_test['TicketGroup'] = X_test['TicketGroup'].apply(Ticket_Label)
X_train = X_train.drop(["Name","Ticket"], axis=1)
X_test = X_test.drop(["Name","Ticket"], axis=1)
X_test.head()
image.png
特征变换
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.preprocessing import MinMaxScaler, StandardScaler
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
# Pclass Sex Embarked 离散化 ont hot编码 或 标签化
# Age SibSp Parch Fare 标准化 或归一化(若使用逻辑回归)梯度下降算法 参数之间差距太大会导致步长过长 会严重影响收敛速度 甚至不收敛
# age : Age --- False: MinMaxScaler True:StandardScaler
# ss : SibSp --- False: MinMaxScaler True:StandardScaler
# ph : Parch --- False: MinMaxScaler True:StandardScaler
# fare : Fare --- False: MinMaxScaler True:StandardScaler
# pc : Pclass --- False: LabelEncoder True:OneHotEncoder
# sex : Sex --- False: LabelEncoder True:OneHotEncoder
# ed : Embarked --- False: LabelEncoder True:OneHotEncoder
def data_preprocessing(df,age=False,ss=False,ph=False,fare=False,pc=True,sex=True,ed=True):
scaler_lst = [age,ss,ph,fare]
column_lst = ["Age","SibSp","Parch","Fare"]
for i in range(len(scaler_lst)):
if not scaler_lst[i]:
# fit_transform reshape(-1,1)是任意行数1列
df[column_lst[i]+"_scaled"] = StandardScaler().fit_transform(df[column_lst[i]].values.reshape(-1,1)).reshape(1, -1)[0]
else:
df[column_lst[i]+"_scaled"] = MinMaxScaler().fit_transform(df[column_lst[i]].values.reshape(-1,1)).reshape(1, -1)[0]
scaler_lst = [pc,sex,ed]
column_lst = ["Pclass","Sex","Embarked"]
for i in range(len(scaler_lst)):
if not scaler_lst[i]:
print("LabelEncoder")
df[column_lst[i]] = LabelEncoder().fit_transform(df[column_lst[i]])
# 标签化后进行归一化或者标准化处理
df[column_lst[i]] = MinMaxScaler().fit_transform(df[column_lst[i]].values.reshape(-1,1)).reshape(1, -1)[0]
else:
df = df.join(pd.get_dummies(df[column_lst[i]],prefix= column_lst[i]))
df = df.drop(column_lst[i], axis=1)
return df
X_train = data_preprocessing(X_train)
X_test = data_preprocessing(X_test)
selector = SelectKBest(f_classif, k=5) # 方差分析,计算方差分析(ANOVA)的F值 (组间均方 / 组内均方),选取前5个特征
selector.fit(X_train, label)
scores = -np.log10(selector.pvalues_)
xticks = X_train.columns.values
# 画图看各个特征的重要程度
plt.bar(range(len(xticks)), scores)
plt.xticks(range(len(xticks)), xticks, rotation='vertical')
plt.show()
plt.savefig('SelectKBest.jpg')
plt.show()
image.png
查看特征相关性
5、参数调整
略过
6、 集成方法
rf_est = RandomForestClassifier(n_estimators = 100, criterion = 'gini', max_features = 'sqrt',
max_depth = 3, min_samples_split = 4, min_samples_leaf = 2,
n_jobs = -1, random_state = 42, verbose = 1)
gbm_est = GradientBoostingClassifier(n_estimators=400, learning_rate=0.0008, loss='exponential',
min_samples_split=3, min_samples_leaf=2, max_features='sqrt',
max_depth=3, random_state=42, verbose=1)
et_est = ensemble.ExtraTreesClassifier(n_estimators=100, max_features='sqrt', max_depth=35, n_jobs=50,
criterion='entropy', random_state=42, verbose=1)
vote_est1 = [
('rf', rf_est),('gbm', gbm_est),('et', et_est)
]
vote_clf = ensemble.VotingClassifier(estimators = vote_est1,
voting = 'soft',
weights = [3,5,2],
n_jobs = 50)
vote_clf.fit(X_train,Y_train)
predict = vote_clf.predict(X_test)
集成随机森林、GBDT、极限树最后用VotingClassifier进行投票
submission = pd.DataFrame({'PassengerId':test_df['PassengerId'],
'Survived':predict})
submission.to_csv('submission_result.csv',index=False,sep=',')
导出csv文件上传到kaggle获得排名
个人心得体会
第一次完整的跑完一个流程才知道为什么有人说"只要特征足够好,模型随便跑"。由于经验不足,第一次提交的时候特征工程没做好,基础模型只使用随机森林的情况下,第一次提交排名15000+,评分0.75119(榜首为1)。
后面看了诸多大神的blog之后发现,虽然集成方法的模型各种各样,但是有一点基本都差不多,都用了很多精力在特征工程上面。
不过特征工程做起来也需要对业务场景熟练才行,像ticket特征,一开始不知道持有同票号的人是在同一个船舱的。还有name这个特征,一开始也没想到,名字其实是和性别是有关系的,而性别和存活率的相关性是相当高的。
在做这个泰坦尼克之灾的时候,将之前在数据挖掘中一些不太明白的点,慢慢的理解了。比如特征工程的重要性,特征衍生的重要性等。
最后有点感慨的是,数据挖掘确实比做IT好玩的多,好久都没体验过这种为了提高一点点努力而翻文档奋斗的感觉了,上一次有这种感觉还是做音视频编解码的时候。
希望最后能找到一份数据分析的工作 ╮(╯3╰)╭。
网友评论