好久没更新了,由于疫情的原因,家里的工作一直比较忙,最近闲下来了,学校也还没开学,正好趁着这段时间,复习一下前段时间的知识,泰坦尼克号的案例在数据分析中是比较经典的,今天我们就借助这个案例看一下数据分析的流程是怎么样的。
一、数据集
首先我们先来看一下数据集,不要嘲笑我的中文路径(哈哈哈),当初整理文件的时候嫌麻烦,直接上中文了,不过这里还是提醒一下大家,路径尽量不要带中文,以免有些代码不支持。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
plt.style.use('fivethirtyeight') # 样式美化
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline
data = pd.read_csv('../../数据集汇总/泰坦尼克号数据集/titanic_train.csv')
data.head()
这里我们分别看一下每个列名代表的含义:
- PassengerId: 乘客id
- Survived:是否获救,0表示未获救,1表示获救
- Pclass:乘客等级(1/2/3等舱位)
- Name:乘客姓名
- Sex:性别
- Age:年龄
- SlbSp:堂兄弟/妹个数
- Parch:父母与小孩个数
- Ticket:船票信息
- Fare:票价
- Cabin:客舱
- Embarked:登船港口
数据集相对来说比较简单,想要的特征基本上都给出来,简单明了。
二、数据分析
数据集已经知道各个字段的含义,接下来我们要进行数据分析,看一下各个特征对结果的影响,统计绘图得出结论。
data.isnull().sum()
先来看一下,有没有缺失值,通过上图可以看出,Age(年龄)、Cabin(客舱)和Embarked(登船港口)这三列存在缺失值,其中Cabin(客舱)的缺失值相对较多,由于这一列本身价值不高,后期我们会舍去。
data.describe()
我们看一下具体的数据分布,通过
data.describe()
可以看到数据一共是891行,还可以看到一些列的最大最小值,比如年龄这一列,年龄最大的有80岁,最小的还不到一岁。
接下来,我们画图看一下,
fig,ax = plt.subplots(1,2,figsize=(18,8))
# data['Survived'].value_counts().plot.pie(explode=[0,0.1],autopct='%1.1f%%',shadow=True,ax=ax[0])
ax[0].pie(data['Survived'].value_counts(),explode=[0,0.1],autopct='%1.1f%%',shadow=True,labels=[0,1])
ax[0].set_title('Survived')
ax[0].set_ylabel('')
sns.countplot('Survived',data=data,ax=ax[1])
ax[1].set_title('Survived')
plt.show()
通过饼图可以看出来,获救的占比才38.4%,一共有891个人,获救的才300多一点,说明救援率还是比较低的。
接下来我们对各个特征进行单独分析,看一下各个特征对结果的影响情况。
- 1、性别特征分析
data.groupby(['Sex','Survived'])['Survived'].count()
根据上面的统计分析可以看出,女性被救出来的几率非常大,我们还可以画图看一下,
fig,ax = plt.subplots(1,2,figsize=(10,8))
data[['Sex','Survived']].groupby(['Sex']).mean().plot.bar(ax = ax[0])
ax[0].set_title('Survived vs Sex')
sns.countplot(x='Sex',hue='Survived',data=data,ax=ax[1])
ax[1].set_title('Sex:Survived vs Dead')
plt.show
通过左图可以看出,如果是女人,在所有的女人中有70%多的几率会被救,而男人只有20%不到(嗯...深刻的感受到了做男人的不易~),右图可以看出女性被救的人数比男性的两倍还要多。通过分析发现,性别是一个重要的特征。
- 2、船舱等级特征分析
pd.crosstab(data.Pclass,data.Survived,margins=True).style.background_gradient(cmap='summer_r')
通过表格我们可以看出一等舱一共216人,有136人被救,占比高于50%;二等舱有184人,87人被救,占比在50%左右;三等舱一共491人,只有119人被救,占比最低。虽然我们讲生命不分贵贱,但是现实还是比较残酷的,有钱人被救的几率还是远高于穷人。
我们画图看一下,
fig,ax=plt.subplots(1,2,figsize=(10,8))
data['Pclass'].value_counts().plot.bar(color=['green','blue','yellow'],ax=ax[0])
ax[0].set_title('Number Of Passengers By Pclass')
sns.countplot('Pclass',hue='Survived',data=data,ax=ax[1])
ax[1].set_title('Pclass:Survived vs Dead')
plt.show()
两图对比更加明显,一等舱被救的人数高于未被救的人数,二等舱差不多,三等舱被救的人远低于未被救的人数。
我们再来看一下性别和船舱等级与被救之间的关系,
pd.crosstab([data.Sex,data.Survived],data.Pclass,margins=True).style.background_gradient(cmap='summer_r')
通过上表可以看出,在一等舱里,女性被救的比例非常高,女性一共有94人,91人被救。
我们画图看一下男性与女性在各个船舱被救的比例,
sns.factorplot('Pclass','Survived',hue='Sex',data=data)
plt.show()
通过上图可以看明显看出,三个船舱中,女性被救的比例远高于男性。(心疼自己一秒钟...)
-
3、年龄特征分析
先来看一下年龄的数值特征,
print('最大年龄',data['Age'].max())
print('最小年龄',data['Age'].min())
print('平均年龄',data['Age'].mean())
# 输出
最大年龄 80.0
最小年龄 0.42
平均年龄 29.69911764705882
接下来画图分析一下,船舱等级和年龄、性别和年龄之间被救的关系,
fig,ax = plt.subplots(1,2,figsize=(10,8))
sns.violinplot('Pclass','Age',hue='Survived',data=data,split=True,ax=ax[0])
ax[0].set_title('Pclass and Age vs Survived')
ax[0].set_yticks(range(0,110,10))
sns.violinplot('Sex','Age',hue='Survived',data=data,split=True,ax=ax[1])
ax[1].set_title('Sex and Age vs Survived')
ax[1].set_yticks(range(0,110,10))
plt.show()
通过左图,可以分析出,一等舱被救的人中大部分集中在37、8岁左右,二等舱被救的人大部分集中在30岁,三等舱集中在20 ~ 30岁左右,可以看出一等舱被救的年龄比其他两个舱的要大,有可能是一等舱本身就是比较富有的人,一般都是拼搏到了一定的年龄才会这样(瞎猜的);右图的话,可以看出男性被救的年龄在30岁左右,女性被救的20~30岁左右,差距不是很大。
- 4、登船地点特征分析
pd.crosstab([data.Embarked,data.Pclass],[data.Sex,data.Survived],margins=True).style.background_gradient(cmap='summer_r')
通过图表可以看出,S登船地的人数最多,Q最少。
sns.factorplot('Embarked','Survived',data=data)
fig=plt.gcf()
fig.set_size_inches(5,3)
通过上图可以看出,C港的生存率最高,在0.55左右,而S港的生存率最低。(莫名的想起吃鸡~)
fig,ax=plt.subplots(2,2,figsize=(15,15))
sns.countplot('Embarked',data=data,ax=ax[0][0])
ax[0][0].set_title('No. Of Passengers Boarded')
sns.countplot('Embarked',hue='Sex',data=data,ax=ax[0][1])
ax[0][1].set_title('Male-Femal Split for Embarked')
sns.countplot('Embarked',hue='Survived',data=data,ax=ax[1][0])
ax[1][0].set_title('Embarked vs Survived')
sns.countplot('Embarked',hue='Pclass',data=data,ax=ax[1][1])
ax[1][1].set_title('Embarked vs Pclass')
plt.subplots_adjust(wspace=0.2,hspace=0.5)
plt.show()
左上,登船地点的人数统计,可以明显看出S港最多;右上,各个登船地点的男女人数统计;左下,各个登船地点获救与未获救的人数统计,结合上表,S港的获救人数最高,但是获救占比却是最低的;右下,各个登船地点的船舱等级人数统计,可以看出S港登船的人数最多,其中三等舱的人数最多,Q港几乎没有一等舱的人登船。
sns.factorplot('Pclass','Survived',hue='Sex',col='Embarked',data=data)
plt.show()
上图是各个登船地点,每个船舱等级被救的男女比例,总之完全符合女人和孩子第一的政策。
- 5、堂兄弟/妹特征分析
pd.crosstab(data.Survived,data.SibSp,margins=True).style.background_gradient(cmap='summer_r')
plt.figure(figsize=(8,8))
sns.countplot('SibSp',hue='Survived',data=data)
plt.show()
通过上图表可以看出,堂兄弟/妹个数越多,基数越少,被救的也就越少,所以我们就简单分析一下,保留这个特征。
- 6、父母与小孩个数特征分析
pd.crosstab(data.Survived,data.Parch,margins=True).style.background_gradient(cmap='summer_r')
plt.figure(figsize=(8,8))
sns.countplot('Parch',hue='Survived',data=data)
plt.show()
这个特征与堂兄弟/妹个数的特征相似,父母孩子个数越多,基数越少,被救的人数也越少。比如有父母或孩子4、5个的人很少,都没有被救,这个特征我们也保留。
以上就是我们对各个特征之间的理解,还有几个特征比如Ticket
(船票信息)、Cabin
(客舱)等我们没有进行分析,主要原因是船票信息对是否获救意义不大,而客舱Cabin
缺失值较多,这些信息在接下来的数据处理中我们直接删掉了。
三、数据清洗与预处理
1、缺失值填充
首先我们先处理一下缺失值,通过上面的data.isnull().sum()
我们知道有两个特征值存在缺失的情况(Cabin
不考虑)。
-
Age年龄问题
关于年龄填充,我们一般是选择年龄的平均值进行填充,但是这里我们发现,在Name
这一列,名字都比较长,中间会带有Mr,Miss,Mrs
等特征来标识每一个人,我们可以根据这个特点,求出每个标识中,年龄的平均值进行填充,这样填充的效果会更加实际。
# 先用正则表达式进行提取
data['initial'] = 0
for i in data:
data['initial']=data.Name.str.extract('([A-Za-z]+)\.')
data.head()
接下来我们看一下分出来的男女人数,
pd.crosstab(data.initial,data.Sex).T.style.background_gradient(cmap='summer_r')
我们将人数比较多的提取出来,剩下的用
others
代替,
data['initial']=data['initial'].replace(['Capt','Col','Countess','Don','Dr','Jonkheer','Lady','Major','Master','Mlle','Mme','Ms','Rev','Sir'],'others')
接下来,我们求出每个标识的平均年龄,
data.groupby('initial')['Age'].mean()
最后我们可以填充了,
data.loc[(data.Age.isnull())&(data.initial == 'Miss'),'Age']=22
data.loc[(data.Age.isnull())&(data.initial == 'Mr'),'Age']=32
data.loc[(data.Age.isnull())&(data.initial == 'Mrs'),'Age']=36
data.loc[(data.Age.isnull())&(data.initial == 'others'),'Age']=20
我们还可以画图展示一下,
fig,ax = plt.subplots(1,2,figsize=(10,8))
data[data['Survived']==0].Age.plot.hist(ax=ax[0],bins=20,edgecolor='black',color='red')
ax[0].set_title('Survived = 0')
data[data['Survived']==1].Age.plot.hist(ax=ax[1],bins=20,edgecolor='black',color='green')
ax[1].set_title('Survived = 1')
plt.show()
通过上图可以看到,被救最多人的的年龄分布在20~40之间,超过60岁的被救的比较少了(年龄最大的人(80岁)被救了)。
-
Embarked缺失值填充
Embarked
列的缺失值填充比较简单,通过上面的特征分析我们可以发现,S港的人数最多,所以我们就用众数填充,
data['Embarked'].fillna('S',inplace=True)
填充完毕后,我们再统计一下缺失值,
data.isnull().sum()
就只剩
Cabin
列,接下我们进行数据预处理。
2、数据预处理
首先我们先将没用的特征进行处理,
data=data.drop(['Name','Ticket','Cabin','initial'],axis=1)
data.head()
接下来我们将性别特征和登船港口数字化,因为接下来用到的算法没法处理文字,
#将性别数字化,male=0,female=1
data.loc[data['Sex']=='male','Sex']=0
data.loc[data['Sex']=='female','Sex']=1
# 登船港口数字化 S=0,C=1,Q=2
data.loc[data['Embarked']=='S','Embarked']=0
data.loc[data['Embarked']=='C','Embarked']=1
data.loc[data['Embarked']=='Q','Embarked']=2
data.head()
四、建立模型
这里我们使用多个机器学习算法进行预测,看一下效果如何。
1、线性回归预测
将训练集划分出特征和标签
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import KFold
linear = LinearRegression()
features = ['Pclass','Sex','Age','SibSp','Parch','Fare','Embarked']
kf=KFold(n_splits=3,shuffle=False,random_state=1)
source_x = data[features] # 训练数据集:特征
source_y = data['Survived'] #训练数据集:标签
source_x.shape
#输出
(891,7)
将数据集划分为训练集和测试集,
from sklearn.model_selection import train_test_split
train_x,test_x,train_y,test_y = train_test_split(source_x,source_y,train_size=0.8)
print('原始数据集特征:',source_x.shape,'训练集特征:',train_x.shape,'测试集特征',test_x.shape)
# 输出
原始数据集特征: (891, 7) 训练集特征: (712, 7) 测试集特征 (179, 7)
进行模型训练,
linear.fit(train_x,train_y)
linear.score(test_x,test_y)
准确率太低了,不知道哪里出了问题。
接下来,我们进行交叉验证再试一下,
# K折交叉验证
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import KFold
# from sklearn.model_selection import cross_val_score
model = LinearRegression()
kf = KFold(n_splits=3,shuffle=False,random_state=1)
# scores = cross_val_score(model,source_x,source_y,cv=kf)
# scores.mean()
scores=[]
for train,test in kf.split(source_x):
train_data = source_x.iloc[train,:]
train_target = source_y.iloc[train]
model.fit(train_data,train_target)
test_data = model.predict(source_x.iloc[test,:])
scores.append(test_data)
因为结果只有0和1,而我们预测的结果分布在[0,1]区间上,所以我们将预测的得分值超过0.5设为1,小于0.5的设为0,
import numpy as np
scores = np.concatenate(scores,axis=0)
scores[scores>.5]=1
scores[scores<.5]=0
accuracy = sum(scores==source_y)/len(source_y)
print(accuracy)
准确率提高了不少。
2、逻辑斯蒂回归预测
LR虽然是回归算法,但是也可以做分类的,这里我们也进行了交叉验证,
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LogisticRegression
model = LogisticRegression(random_state=1)
scores = cross_val_score(model,source_x,source_y,cv=3)
print(scores.mean())
准确率也有所提升。
3、随机森林预测
# 随机——随机对样本进行采样
# 随机——对特征进行随机抽取
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import KFold
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(random_state=1,
n_estimators=200,#200课树
min_samples_split=4,
min_samples_leaf=2)
kf = KFold(n_splits=3,shuffle=False,random_state=1)
scores = cross_val_score(model,source_x,source_y,cv=kf)
print(scores.mean())
随机森林还是比较强的,结果也有所提升。
4、集成算法
集成算法是将几个算法合并在一起进行预测的算法,在生产实际和各种比赛都被广泛应用;随机森林也是一种集成算法,它由多个决策树合并在一起进行决策,这里我们将随机森林与逻辑斯蒂回归算法进行合并,看一下效果如何。
features = ['Pclass','Sex','Age','SibSp','Parch','Fare','Embarked']
model = RandomForestClassifier(random_state=1,
n_estimators=200,#100课树
min_samples_split=8,
min_samples_leaf=4)
from sklearn.ensemble import GradientBoostingClassifier
import numpy as np
#定义两个算法集成在一起
algorithms = [
[GradientBoostingClassifier(random_state=1,n_estimators=200,max_depth=3),features],
[LogisticRegression(random_state=1,solver='liblinear'),features]
]
kf=KFold(n_splits=3,shuffle=False,random_state=1)
predictions=[]
for train,test in kf.split(source_x):
train_target = source_y.iloc[train]
full_test_predictions = []
for alg,predictors in algorithms:
alg.fit(source_x.iloc[train,:],train_target)
test_predictions = alg.predict_proba(source_x.iloc[test,:].astype(float))[:,1]
full_test_predictions.append(test_predictions)
test_predictions = (full_test_predictions[0]+full_test_predictions[1])/2
test_predictions[test_predictions <=.5] =0
test_predictions[test_predictions >.5] =1
predictions.append(test_predictions)
predictions = np.concatenate(predictions,axis=0)
accuracy = sum(predictions == source_y)/len(predictions)
print(accuracy)
可以看到效果也是不错,跟随机森林的准确率差不多。
五、总结
算法预测到这里就结束了,如果小伙伴有其他的想法可以自行测试,这里其实还是有很多可以改变的地方,比如,对于Name
列,一般名字比较长的属于大家族,比较富有,可以统计一下名字的长度作为一个特征;在提取Mr,Miss,Mrs等
标识时也可以使用one-hot编码
进行处理,这样获得特征就会增加很多;还可以将堂兄弟/妹和父母孩子个数做一个求和添加一个Family
的特征...这些都是可以的,感兴趣的同学可以尝试一下。
最后我们总结一下数据挖掘的一般流程:
1、数据读取
- 读取数据,并进行展示
- 统计数据各项指标
- 明确数据规模与要完成的任务
2、特征理解分析
- 单特征分析,逐个变量分析其对结果的影响
- 多变量统计分析,综合考虑多种情况影响
- 统计绘图得出结论
3、数据清洗与预处理
- 对缺失值进行填充
- 特征标准化/归一化
- 筛选有价值的特征
- 分析特征之间的相关性
4、建立模型
- 特征数据与标签准备
- 数据集切分
- 多种建模算法对比
- 集成策略等方案改进
好了,今天的学习到这里就结束了~
网友评论