摘要
1912年4月15日,在首次航行期间,泰坦尼克号撞上冰山后沉没,2224名乘客和机组人员中有1502人遇难。这场轰动的悲剧震惊国际社会,在这次海难中导致死亡率高的原因之一是没有足够的救生艇给乘客和机组人员,幸存者非常的少。
此次分析主要是采用python语言,数据是在kaggle网站上进行下载,kaggle网站是著名的数据分析竞赛网站,它举行了很多数据分析比赛。网站给出两个文件,一个用于训练,一个用于测试提交,由于进行测试的文件没有标记,所以采用第一个文件进行下面的分析,让数据更具说服力以及准确性。本次论文分析主要先对下载的数据进行分析,对数据进行预处理,如对缺失值的处理和对数据进行清洗等,然后进行机器学习得出结论。
关键词:python;数据分析;机器学习;
1.引言
数据分析是指用适当的统计分析方法对收集来的大量数据进行分析,将它们加以汇总和理解并消化,以求最大化地开发数据的功能,发挥数据的作用。数据分析是为了提取有用信息和形成结论而对数据加以详细研究和概括总结的过程。而机器学习是专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能,它是人工智能的核心,是使计算机具有智能的根本途径。
数据分析与机器学习的组合在如今大数据时代非常的热门,比如苹果公司研发的siri和谷歌人工智能围棋程序AlphaGo,而大数据+机器学习正是AlphoGo取胜的关键。本次课程设计,就是通过对泰坦尼克号的现有数据进行数据的处理和分析,再通过机器学习来进行结论的验证。论文分为数据分析、数据处理、特征工程、机器学习、总结和展望这么几个部分。
2.数据分析
2.1数据初探
首先是对数据进行导入和定义分析,数据来自于著名的数据分析竞赛网站Kaggle:
import pandas as pd
data = pd.read_csv("data/titan_train.csv")
数据特征定义具体如下:
- PassengerId:乘客编号
- Survived: 是否存活(1-存活,0-未存活)
- PClass:船舱等级(1,2,3等舱位)
- Name: 乘客姓名(包括Mr,Mrs,Miss,Master等)
- Sex:性别
- Age: 年龄
- SibSp: 同船的堂兄弟或姐妹个数
- Parch: 直系亲属(父母或者孩子)数量
- Ticket: 船票编号信息
- Fare: 票价
- Cabin: 船舱编号
- Embarked: 登船港口(S,C,Q)
data.info()
以上结果表明:
A. 一共891位乘客;
B. 一共12个特征(12个数据列);
C. 年龄,船舱编号,登船港口有缺失值,登船港口的缺失值较大;
D. 数据有数值类型,也有字符串类型。
data.describe()
data.loc[data["Age"]==0.42,:]
data.loc[data["Age"]==80, :]
从上述结果可看出:
A. 年龄有缺失值,还有一些非数值类的无法描述;
B.乘客编号的统计值没有意义;
C. 有38.4%的乘客获救;
D.平均年龄29.7,最小的乘客0.42岁,最大的乘客80岁,他们两个都获救了,可见年龄是一个重要的特征;
E. 2,3等船舱乘客居多,50%以上的乘客是3等舱;
F. 票价均值为32,但最贵的票竟高达512。
2.2 数据初分析
每个乘客有12个特征,哪些特征是对分析和预测更有用,首先画图来看看乘客的各个特征与存活结果之间的关系:
%matplotlib inline
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
fig = plt.figure(figsize=(12,8))
fig.set(alpha=0.2)
# 设置子图模式(2行3列的网格模式),第一幅图获救情况的柱状图
plt.subplot2grid((2,3),(0,0))
data["Survived"].value_counts().plot(kind="bar")
plt.title("获救情况(1为获救)")
plt.ylabel("人数")
#绘制船舱等级分布柱状图
plt.subplot2grid((2,3),(0,1))
data["Pclass"].value_counts().plot(kind="bar")
plt.title("乘客船舱等级分布")
plt.ylabel("人数")
#绘制获救和年龄之间的关系的散点图
plt.subplot2grid((2,3),(0,2))
plt.scatter(data["Survived"],data["Age"])
plt.title("获救乘客的年龄分布(1为获救)")
plt.ylabel("人数")plt.grid(b=True,which="major",axis="y")
# 绘制各船舱等级乘客的年龄分布
plt.subplot2grid((2,3),(1,0),colspan=2)
data.loc[data["Pclass"]==1,"Age"].plot(kind="kde")
data.loc[data["Pclass"]==2,"Age"].plot(kind="kde")
data.loc[data["Pclass"]==3,"Age"].plot(kind="kde")
plt.title("各船舱等级的年龄分布曲线")
plt.xlabel("年龄")
plt.ylabel("密度")
plt.legend(["头等舱","2等舱","3等舱"], loc="best")
#绘制各港口登船人数的分布
plt.subplot2grid((2,3),(1,2))
data["Embarked"].value_counts().plot(kind="bar")
plt.title("各港口登船人数的分布")
以上图形分析结果表明:
A. 大多数乘客未获救;
B. 3等舱乘客超过一半,2等舱最少;
C. 60岁以上获救概率较低;
D. 2,3等舱主要是20-30岁的乘客,头等舱主要是40岁以上;
E. S港口登船人数最多,S:英国南安普敦,C:法国瑟堡-奥克特维尔,Q:爱尔兰-昆士敦(Queenstown) ,绝大多数人从始港口出发。
#计算各等级获救和遇难的人数
s_0 = data.loc[data["Survived"]==0,"Pclass"].value_counts()
s_1 = data.loc[data["Survived"]==1,"Pclass"].value_counts()
# 绘制层叠柱状图
df = pd.DataFrame({"获救":s_1,"未获救":s_0})
df.plot(kind="bar",stacked=True)
plt.title("各船舱等级的获救情况")
plt.ylabel("人数")
从柱状图中可以清晰的看出:头等舱获救比例非常高,3等舱获救比例非常低。
#计算男女性别获救和遇难的人数
s_0 = data.loc[data["Survived"]==0,"Sex"].value_counts()
s_1 = data.loc[data["Survived"]==1,"Sex"].value_counts()
# 绘制层叠柱状图
df = pd.DataFrame({"获救":s_1,"未获救":s_0})
df.plot(kind="bar",stacked=True)
plt.title("乘客性别与获救的关系")
plt.ylabel("人数")
显然,女性获救比例远远高于男性。
plt.title("根据舱位等级和性别分析获救情况")
plt.xticks([])
plt.yticks([])
# 女性1,2等舱
ax1 = fig.add_subplot(221)
data.Survived[data.Sex=="female"][data.Pclass!=3].value_counts().plot(kind="bar",color="red")
ax1.set_xticklabels(["获救","未获救"],rotation=0)
ax1.legend(["女性/高级舱"],loc="best")
# 女性3等舱
ax2 = fig.add_subplot(222)
data.Survived[data.Sex=="female"][data.Pclass==3].value_counts().plot(kind="bar",color="yellow")
ax2.set_xticklabels(["获救","未获救"],rotation=0)
ax2.legend(["女性/低级舱"],loc="best")
# 男性1,2等舱
ax3 = fig.add_subplot(223)
data.Survived[data.Sex=="male"][data.Pclass!=3].value_counts().plot(kind="bar",color="green")
ax3.set_xticklabels(["未获救","获救"],rotation=0)
ax3.legend(["男性/高级舱"],loc="best")
# 男性3等舱
ax4 = fig.add_subplot(224)
data.Survived[data.Sex=="male"][data.Pclass==3].value_counts().plot(kind="bar",color="blue")
ax4.set_xticklabels(["未获救","获救"],rotation=0)
ax4.legend(["男性/低级舱"],loc="best")
上图可以得出以下结论:
A. 高级舱(1,2等舱的女性几乎全部获救);
B. 低级舱女性获救一半左右;
C. 男性高级舱获救比例30%不到;
D. 男性低级舱获救比例不到20%;
E. 可见性别也是评判是否能获救的重要指标。
#计算各港口等船的乘客获救和遇难的人数
s_0 = data.Embarked[data.Survived==0].value_counts()
s_1 = data.Embarked[data.Survived==1].value_counts()
# 绘制层叠柱状图
df = pd.DataFrame({"获救":s_1,"未获救":s_0})
df.plot(kind="bar",stacked=True)
plt.title("乘客登船港口与获救的关系")plt.ylabel("人数")
登录港口似乎和获救没有直接的关系,但C港(法国瑟堡-奥克特维尔,是泰坦尼克号的始发港口)登录的乘客获救概率较高一些。
#堂兄弟姐妹的数量对获救有没有影响?
g = data.groupby(["SibSp","Survived"])
df = pd.DataFrame(g.count()["PassengerId"])
df
#父母孩子的数量对获救的影响
g = data.groupby(["Parch","Survived"])
df = pd.DataFrame(g.count()["PassengerId"])
df
父母孩子数量超过3个,以及兄弟姐妹超过4个的几乎全部遇难。
#计算有无船舱编号获救和遇难的人数
s_c = data.Survived[pd.notnull(data.Cabin)].value_counts()
s_nc = data.Survived[pd.isnull(data.Cabin)].value_counts()
#绘制层叠柱状图
df = pd.DataFrame({"有编号":s_c,"无编号":s_nc}).transpose()
df.plot(kind="bar",stacked=True)
plt.title("有无船舱编号与获救的关系")
plt.ylabel("人数")
船舱编号缺失值太多,但还是可以看出有编号的乘客获救率更高。
data.pivot_table(values='Survived',index=['Pclass','Sex'],aggfunc=np.mean)
plt.figure(figsize=(10,5))
sns.pointplot(data=data,x='Pclass',y='Survived',hue='Sex',ci=None)
plt.show()
从上折线图可看出:
A. 在各个船舱中,女性的存活率明显都大于男性;
B. 一二等船舱中女性存活率接近,且远大于三等舱;
C. 一等舱的男性存活率远大于二三等舱,二三等舱男性存活率接近。
data['AgeGroup'] = pd.cut(data['Age'],5)
data.AgeGroup.value_counts(sort=False)
data.pivot_table(values='Survived',index='AgeGroup',columns='Sex',aggfunc=np.mean)
plt.figure(figsize= (10 ,5))
sns.pointplot(data=data,x="AgeGroup",y="Survived",hue="Sex",ci=None,
markers=["^", "o"], linestyles=["-", "--"])
plt.xticks(rotation=60)
plt.show()
从上折线图可看出青少年及女性的生还率更高(想起电影里,船长说的:让小孩跟妇女先走),男性生还的基本上都是青少年。
3.数据处理
根据以上对数据及特征进行分析后,现在需要对数据进行预处理,对有缺失值的特征进行处理,通过对数据进行统计可看出,其中有缺失值的特征有年龄、船舱编号和登船港口。
首先是对年龄缺失值的处理,因为年龄跨度较大,大到80岁,小到四个月,因此在这对年龄的缺失值进行采用取年龄平均值的方式进行填充处理;
#求年龄的均值
import numpy as np
aver_age = round(np.mean(data.Age),1)
aver_age
#填充年龄缺失值
data.loc[pd.isnull(data.Age),"Age"] = aver_age
接着是对于船舱编号的处理,因为从上面的分析可看出船舱是否有编号对存活率也有一定的影响,但由于船舱编号不一长短也不一样,不利于后面的机器学习,因此统一将有编号的设为yes,无编号的设为no。
data.loc[pd.notnull(data.Cabin),"Cabin"] = "Yes"
data.loc[pd.isnull(data.Cabin), "Cabin"] = "No"
data.Cabin.value_counts()
最后是对登船港口缺失值的处理,从上面的统计可看出,登船港口缺失值较少,因此可直接用登船港口人数最多的“S”港进行填充。
data.loc[pd.isnull(data.Embarked),"Embarked"] = "S"
最后统计一下可看出,所有特征项已无缺失值,除了前面数据分析时加入的AgeGroup,在后面的特征工程会将其删除。
4.特征工程
特征工程指的是把原始数据转变为模型的训练数据的过程,它的目的就是获取更好的训练数据特征,使得机器学习模型逼近这个上限。经过上面的数据分析和数据处理后,我们已经完成对数据的缺失值的处理和特征的分析,下一步是对文本字段进行处理,在这使用的是One-Hot:独热编码,涉及到Embarked, Cabin, Sex和Pclass,不需要的字段包括PassengerId,,Name和Ticket。
首先对字段进行One-Hot编码:
# Sex字段的One-Hot编码
d_sex = pd.get_dummies(data.Sex,prefix="Sex")
# Cabin字段的One-Hot编码
d_cabin = pd.get_dummies(data.Cabin,prefix="Cabin")
# Embarked字段的One-Hot编码
d_embarked = pd.get_dummies(data.Embarked,prefix="Embarked")
# Pclass字段的One-Hot编码
d_pclass = pd.get_dummies(data.Pclass,prefix="Pclass")
下面使用计算得到的One-Hot编码字段替代数据中原有字段,拼接数据框,将新生成的字段导入数据集中:
#拼接数据框,将新生成的字段加入到数据集中
df = pd.concat([data,d_sex,d_cabin,d_embarked,d_pclass],axis=1)
df.head()
下一步是删除数据集中被替代的列(Sex,Cabin,Embarked,Pclass)以及不需要的列(PassengerId,Name,Ticket):
df.drop(["Sex","Cabin","Embarked","Pclass","PassengerId","Name","Ticket"],axis=1,inplace=True)
df.head()
删除前面数据分析时不小心加入的AgeGroup:
df.drop(["AgeGroup",],axis=1,inplace=True)
df.head()
接着,观察到Age和Fare这两个特征的取值量级较大,需要进行标准化。常用的标准化手段包括正态分布标准化,最大最小值标准化,在此采用了后者:
#最大最小值标准化(让取值在0-1之间)
#(x-min)/(max-min)
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
Age_scale = scaler.fit_transform(df.Age.values.reshape(-1,1))
Fare_scale = scaler.fit_transform(df.Fare.values.reshape(-1,1))
df["Age_scale"] = Age_scale
df["Fare_scale"] = Fare_scale
df.drop(["Age","Fare"],axis=1,inplace=True)
df.head()
最后再将数据进行保存,至此我们特征工程的工作就完成了,主要是删除与目标(是否获救)明显没有关系的特征,然后是对字符特征和无序的分类特征进行One-Hot编码。
5. 机器学习
通过上面的数据分析、数据处理和特征工程的建模后,接下来是进行机器学习,主要为数据集的准备、划分训练集和测试集以及机器学习分类算法,最主要的是对算法的选择。
首先准备数据:
#准备数据,X代表数据,y代表目标(是否获救)
X = df.iloc[:,1:].values
y = df.iloc[:,0].values
下面是对数据集进行划分:
#划分训练集和测试集
from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test = train_test_split(X, y, test_size=0.3, random_state=33)
print(X_train.shape, X_test.shape,y_train.shape,y_test.shape)
(623, 14) (268, 14) (623,) (268,)
接下来是机器学习算法的选择,算法有很多,在这先选择KNN算法、逻辑回归算法、决策树算法、支持向量机算法和随机森林算法进行比对,选择最佳的算法。
KNN算法:
# KNN分类算法
from sklearn.neighbors import KNeighborsClassifier
#建模
knn = KNeighborsClassifier(n_neighbors=7, p=1)
#学习
knn.fit(X_train, y_train)
#评估
print("模型参数:", knn)
print("模型测试准确度:", knn.score(X_test,y_test))
print("模型训练准确度:", knn.score(X_train, y_train))
逻辑回归算法:
#逻辑回归算法
from sklearn.linear_model import LogisticRegression
#建模
lr = LogisticRegression(penalty='l1')
#学习
lr.fit(X_train, y_train)
#评估
print("模型参数:", lr)
print("模型测试准确度:", lr.score(X_test,y_test))
print("模型训练准确度:", lr.score(X_train, y_train))
决策树算法:
#决策树算法
from sklearn.tree import DecisionTreeClassifier
#建模
dtc = DecisionTreeClassifier(splitter="random")
#学习
dtc.fit(X_train,y_train)
#评估
print("模型参数:", dtc)
print("模型测试准确度:", dtc.score(X_test,y_test))
print("模型训练准确度:", dtc.score(X_train, y_train))
支持向量机算法:
#支持向量机算法
from sklearn.svm import SVC
#建模
svc = SVC(gamma=0.1,C=15)
#学习
svc.fit(X_train,y_train)
#评估
print("模型参数:", dtc)
print("模型测试准确度:", svc.score(X_test,y_test))
print("模型训练准确度:", svc.score(X_train, y_train))
随机森林算法:
#随机森林算法
from sklearn.ensemble import RandomForestClassifier
#建模
rfc = RandomForestClassifier( n_estimators=33)
#学习
rfc.fit(X_train,y_train)
#评估
print("模型参数:", rfc)
print("模型测试准确度:", rfc.score(X_test,y_test))
print("模型训练准确度:", rfc.score(X_train, y_train))
从上面机器学习算法模型得分中,可以看出决策树算法和随机森林算法的测试集得分跟训练集得分相差太大,可能是学习过度,产生了过拟合想象。在剩下的KNN算法、逻辑回归算法和支持向量机算法中,训练集得分和测试集得分基本接近,可见拟合较好,下面进行交叉验证。
KNN算法:
# KNN
import numpy as np
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score
knn = KNeighborsClassifier()
scores = cross_val_score(knn, X, y, cv=5)
print(scores)
print("交叉验证平均分:",np.mean(scores))
逻辑回归算法:
#逻辑回归
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
lr = LogisticRegression(penalty="11")
scores = cross_val_score(knn, X, y, cv=10)
print(scores)
print("交叉验证平均分:",np.mean(scores))
支持向量机算法:
#支持向量机
import numpy as np
from sklearn.svm import SVC
from sklearn.model_selection import cross_val_score
svc = SVC(gamma=0.08, C=15, kernel="poly", degree=2)
scores = cross_val_score(svc, X, y, cv=10)
print(scores)
print("交叉验证平均分:",np.mean(scores))
综上交叉验证得分,可见支持向量机的算法是更好的,不会过拟合也不会学习不足。
选定了最佳算法后,采用网格搜索的方式确定来最优参数,确定最优的参数建模后采用最优参数建模并用k折验证得到评估的平均分。
网格搜索:
from sklearn.model_selection import GridSearchCV
from sklearn.svm import SVC
svc = SVC()
params = {
"C":[1,5,10,15,20,30,50,70,100],
"gamma":[0.0001, 0.001, 0.005, 0.01, 0.05, 0.1],
"kernel":["rbf", "linear","poly","sigmoid"],
"degree":[2,3,4,5]
}
grid = GridSearchCV(svc, params, scoring="accuracy")
grid.fit(X, y)
print(grid.best_score_)
print(grid.best_params_)
k折验证:
import numpy as np
from sklearn.svm import SVC
from sklearn.model_selection import cross_val_score
svc = SVC(gamma=0.1, C=70, kernel="rbf", degree=2)
scores = cross_val_score(svc, X, y, cv=10)
print(scores)
print("交叉验证平均分:",np.mean(scores))
不知道为什么使用网格搜索的最佳参数模型反而验证分数降低了。
6.总结和展望
通过这个课程设计,加深了对python语言的学习,python不仅仅是一门编程语言,它的功能非常的强大,不仅可以编程,还可以进行数据分析、数据可视化操作、机器学习等。
在这个课程设计中,我选择的是对泰坦尼克号沉船旅客存活率进行分析,内容较多,运用的知识比较广泛,主要是在数据分析这一部分的内容会比较多,后面也因为时间原因,没有对多个特征进行综合分析,比如存活率是否与年龄性别综合相关呢?是否与头衔跟性别相关呢?也有因为个人能力问题只能分析到这个程度。在数据可视化这一部分,也因为时间问题没有深入去分析哪些图形可视化能更直观的表达分析的结果,图形画的较为单一,基本都为柱状图。在机器学习这一部分也因为没有深入学习,所以在进行网格搜索调参的时候出现了验证分数比自己随机选择的参数分数还低的情况,这个部分我一直没找到原因,这也是一个课程设计最大的一个不足的地方。
完成这个课程设计论文,我觉得不仅是对python语言更加的熟练,也增加了自己数据分析和数据可视化方面的能力,还有对机器学习的初步涉略。即使这个课程结束了,我还是会继续深入学习python,深入学习机器学习。