泰坦尼克练习(上)

作者: 怀柔小龙虾 | 来源:发表于2019-03-15 15:06 被阅读4次

    这个项目是kaggle上的练手项目,实践方面是参考于csdn上一位大佬的总结,自己对其进行了实现和理解,主要是为了解整个项目操作的流程,并且,由于篇幅过长,所以会分为两部分记录

    数据导入

    import re
    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import seaborn as sns
    
    import warnings
    warnings.filterwarnings('ignore')
    
    • 注:这里加入
      import warnings
      warnings.filterwarnings('ignore')
      主要就是为了美观,如果不加的话,warning一堆堆的,不怎么整洁
    #导入数据
    train_data = pd.read_csv('C:/Users/Youngy/Desktop/Titanic_dataset/train.csv')
    test_data = pd.read_csv('C:/Users/Youngy/Desktop/Titanic_dataset/test.csv')
    #查看前几行的源数据
    sns.set_style('whitegrid')#设定图表的主题,其中whitegrid的主题比较简洁
    train_data.head()
    
    输出结果
    #查看所有的数据信息概况
    train_data.info()
    test_data.info()
    
    train的输出结果
    test的输出结果
    • 看过数据的概况后,可以发现,训练集中的Age,Cabin,Embarked,测试集中的Fare是有缺失值的
    #绘制存活的比例
    train_data['Survived'].value_counts().plot.pie(autopct = '%1.2f%%')
    
    存活比例

    缺失值处理

    • 如果数据集很大,缺失值只占极小的一部分,可以直接删掉带有缺失值的行
    • 如果该特征对模型的学习来说不是很重要,则可以对缺失值填充均值or众数;比如‘在哪儿登船’的这个特征共有三个登船的地点,总的数据是891,而该特征是889,缺失两个数值,就可以直接使用众数进行填充
    #众数
    train_data.Embarked.dropna().mode().values
    
    #将众数赋值到源数据中
    train_data.Embarked[train_data.Embarked.isnull()] = train_data.Embarked.dropna().mode().values
    
    • mode是众数,train_data.Embarked.dropna()是表示对删去缺失值后的数据,再加个mode则表示删去缺失值后,数据的众数
    • 对于标称属性,我们可以赋予一个代表缺失的值,比如‘U0’;因为缺失本身也可能代表着一些隐含信息,比如船舱号Cabin这个特征,缺失可能表示代表其没有船舱,也是有意义的
      • 注:标称属性(nominal attribute)意味着‘与名称相关’,它的值是一些符号或事物的名称。每个值代表某种类别,编码或状态,因此标称属性又被看作是分类的(categorical)
    train_data['Cabin'] = train_data.Cabin.fillna('U0')
    
    • 使用线性回归、随机森林等模型来预测缺失属性的值;比如:Age是一个非常重要的特征,我们需要保证缺失值填充的准确率,这是非常重要的
      • 一般来说,会采用数据较完整的条目作为模型的训练集,以此来预测缺失值
    #采用随机森林来预测
    from sklearn.ensemble import RandomForestRegressor
    
    age_df = train_data[['Age','Survived','Fare', 'Parch', 'SibSp', 'Pclass']]#提取数据
    age_df_notnull = age_df.loc[(train_data['Age'].notnull())]#训练集
    age_df_isnull = age_df.loc[(train_data['Age'].isnull())]#测试集(即需要进行缺失值填充的数据)
    
    X = age_df_notnull.values[:,1:]#训练集的输入X
    y = age_df_notnull.values[:,0]#训练集的输出标签y
    
    RFR = RandomForestRegressor(n_estimators=1000,n_jobs=-1)#调用随机森林,进行训练模型
    RFR.fit(X,y)
    
    predictAges = RFR.predict(age_df_isnull.values[:,1:])#预测
    train_data.loc[train_data['Age'].isnull(),['Age']] = predictAges#将预测的结果赋值到源数据中
    
    • 注意这种操作的顺序,提取notnull和isnull别搞错了就行
    • 对Series进行切片,一定要指定特征后加个values再进行,不然会报错
    #查看缺失值处理后的结果
    train_data.info()
    
    缺失值处理后的结果

    分析数据关系

    分析数据之间的关系,也就是分析特征与特征之间的关系,下面我们依次分析

    性别与是否生存的关系

    • 利用groupy().count()将sex和survived的数据统计出来
    train_data.groupby(['Sex','Survived'])['Survived'].count()
    
    统计结果
    train_data[['Sex','Survived']].groupby(['Sex']).mean().plot.bar()
    
    统计结果的柱状图
    train_data[['Sex','Survived']].groupby(['Sex']).mean()
             Survived
    Sex 
    female    0.742038
    male      0.188908
    
    • train_data[['Sex','Survived']].groupby(['Survived']).mean()并不可以统计,原因是sex中的数据都是非数字(经验证,利用replace改成数字后就可以统计了)
      除此之外,似乎pandas中的筛选数据的操作只能针对数字型的数据,eg:a[a.Survived > 0]才可以,a.Survived=0sex='male'都不可以

    基于以上的图表,可以发现这里性别之中,女性的生存率更高,所以有相当大的关系

    船舱等级和生存与否的关系

    train_data.groupby(['Pclass','Survived'])['Pclass'].count()
    
    统计结果
    train_data[['Pclass','Survived']].groupby(['Pclass']).mean().plot.bar()
    
    柱状图
    • 这里的mean到底计算的是什么呢?
      • 其实前面的count是统计总的量,而这里的mean是要计算全部非NA量的平均值,也就是Survived属性中的所有值都加在一起,然后除以这个属性的值个数,即mean = 136 / (80+136)
    train_data[['Sex','Pclass','Survived']].groupby(['Pclass','Sex']).mean().plot.bar()
    
    柱状图
    train_data.groupby(['Sex','Pclass','Survived'])['Survived'].count()
    
    统计结果

    由上面的图表可以看出,虽然每种船舱里都体现着女性优先,但最终,生存的情况和船舱等级有密不可分的联系

    年龄与存活与否的关系

    fig,axes = plt.subplots(1,2,figsize = (10,5))
    sns.violinplot(x = 'Pclass' , y = 'Age' , hue = 'Survived' , data = train_data , split = True , ax = axes[0])
    axes[0].set_title('Pclass and Age vs Survived')
    axes[0].set_yticks(range(0,110,10))
    
    sns.violinplot(x = 'Sex' , y = 'Age' , hue = 'Survived' , data = train_data , split = True , ax = axes[1])
    axes[1].set_title('Sex and Age vs Survived')
    axes[1].set_yticks(range(0,110,10))
    
    plt.show()
    
    琴图
    • 在写这里时出了个问题,就是会显示matplotlib里没有subplots属性,这里是因为前面在导入包的时候,导入的是import matplotlib as plt,其实只要导入import matplotlib.pyplot as plt就可以了
    # 我们先用直方图和箱线图看一下所有人的年龄分布
    plt.figure(figsize=(10,5))
    plt.subplot(121)
    train_data['Age'].hist(bins = 70)
    plt.xlabel('Age')
    plt.ylabel('Num')
    
    plt.subplot(122)
    train_data.boxplot(column='Age',showfliers=False)
    plt.show()
    
    直方图和箱线图
    • plt.subplot(121)表示一行两列的图像,最后一个1表示显示第一个位置
      bins是直方图中的分类粒度大小,值越大越细
      showfliers表示是否显示异常值,而默认是显示的,所以这里showfliers = False是让箱线图不显示异常值
    # 看一下不同年龄段的生存与否的分布情况
    facet= sns.FacetGrid(train_data,hue = 'Survived',aspect=3)#aspect是关于图像大小的参数
    facet.map(sns.kdeplot,'Age',shade=True)
    facet.set(xlim=(0,train_data['Age'].max()))
    facet.add_legend()
    
    • FacetGrid对象用于生成plot的网格布局,当网格创建完毕后,可以使用FacetGridmap方法来指定plot类型和需要绘制的属性
      • hue='Survived'是分类参数,也可以使用col='Survived'设定条件将数据分为多个子集,另外,aspect是设置图像大小的参数
      • sns.kdeplot是核密度曲线(Kernel Density Plot),其中shade=True是显示曲线的面积,可以方便观察
    # 再观察一下不同年龄下的平均生存率
    fig,axes1 = plt.subplots(1,1,figsize=(10,5))
    train_data['Age_int'] = train_data['Age'].astype(int)#源数据里的Age是float64,现在转换为int32,并且作为一个新的特征加入源数据
    
    #做一个数据的聚合,将平均值mean的数值作为Survived属性的新数据,然后将Age_int和Survived特征放到新的数据集average_age中
    average_age = train_data[['Age_int','Survived']].groupby(['Age_int'],as_index = False).mean()
    sns.barplot(x='Age_int',y='Survived',data=average_age)
    
    各个年龄的平均生存率
    • 上面是先转换数据,作为一个新特征加入源数据,然后筛选属性,做一个数据的聚合,将平均值mean的数值作为Survived属性的新数据,然后将Age_int和Survived特征放到新的数据集average_age中
      • 其中,as_index=False是保留原先的0~891的数字索引,而如果as_index=True(该参数默认为True),列Age_int会被默认为索引列,新的dataframe中不再包含这列数据
    # 观察年龄统计
    train_data['Age'].describe()
    
    输出
    • 样本有891,平均年龄约为30岁,标准差13.5岁,最小年龄为0.42,最大年龄80.
    • 根据年龄统计,我们可以将乘客划分为儿童,青少年,成年和老年,并进一步分析四个群体的生还情况
    bins = [0,12,18,65,100]
    train_data['Age_group']= pd.cut(train_data['Age'],bins)
    by_age = train_data.groupby('Age_group')['Survived'].mean()
    by_age
    
    输出结果
    • pd.cut是pandas自身的函数,其可以对连续型变量进行分类汇总
      • 其中,我们对(0,12)这样的区间段可以加上label
        eg:train_data['Age_group']= pd.cut(train_data['Age'],bins,label=['儿童','青少年','成年','老年'])
    by_age.plot(kind = 'bar')
    
    image.png

    乘客姓名与存活与否的关系

    train_data['Name'].head(5)
    
    输出
    • 通过观察名字数据,我们可以看出其中包括对乘客的称呼,如:Mr、Miss、Mrs等,称呼信息包含了乘客的年龄、性别,同时也包含了如社会地位等的称呼,如:Dr,、Lady、Major、Master等的称呼
    train_data['Title'] = test_data['Name'].str.extract(' ([A-Za-z]+)\.', expand=False)
    
    pd.crosstab(train_data['Title'],train_data['Sex'])
    
    输出
    • 这里是将姓名中的称呼用字符串str的str.extract()函数+正则表达式提取出来,然后利用交叉表crosstab将Title中的数据用sex特征进行统计
      • 注1:注意书写格式:要提取的部分正则表达式要用引号引起来。抽取多个数字或者字母的话要在后面加上'+'
      • 注2:加入之后的数据并不是数值格式的(属于字符串格式的),因此不能跟正常的数值一样进行运算,需要计算的时候要进行格式的转换
        • train_data['Title'].astype(float)转换为浮点型
        • train_data['Title'] = train_data['Title'].map(lambda x:float(x))也可以用map和匿名函数转换格式
      • 注3:.str的功能是可以使用切片器的,eg:train_data['Title'].str[:7]
    train_data[['Title','Survived']].groupby(['Title']).mean().plot.bar()[图片上传中...(image.png-cfdce2-1552632604968-0)]
    
    image.png
    fig,axis1 = plt.subplots(1,1,figsize = (10,5))
    train_data['Name_length'] = train_data['Name'].apply(len)
    name_length = train_data[['Name_length','Survived']].groupby(['Name_length'],as_index=False).mean()
    sns.barplot(x='Name_length',y='Survived',data=name_length)
    
    image.png
    在计算名字的长度时,我们用到了pandas.apply函数,这个函数是所有函数中自由度最高的函数
    • DataFrame.apply(func, axis=0, broadcast=False, raw=False, reduce=None, args=(), **kwds)
      • func是函数,相当于c里的函数指针,而这个函数需要自己去实现,因为函数的传入参数是根据axis来确定的,比如设置axis=1,则会把一行的数据作为Series的数据结构传入这个函数中,此时我们可以在这个函数中实现对Series的不同属性之间的数值计算,并返回一个结果,而apply函数则会自动遍历每一行的DataFrame的数据,最后将所有结果组合成一个Series数据结构返回;如果设置默认,即axis=1,则依然会一行一行的将数值参数传入函数中,比如x+9的简单函数,如果传入的数据中,一行有两个属性,则每个属性值都会+9,结果会返回一个所有数值都+9DateFrame
      • 如果我们想给自己实现的函数传递参数,就可以用的apply函数的*args**kwds参数,比如:func的传入参数是两个,而DataFrame只有一个数值,我们则可以用*args**kwds参数传入func

    有无兄弟姐妹和存活与否的关系

    # 我们先将数据分为有兄弟姐妹和没兄弟姐妹两组
    sibsp_df = train_data[train_data['SibSp']!=0]
    np_sibsp_df = train_data[train_data['SibSp']==0]
    
    #再对两组数据进行可视化
    plt.figure(figsize = (10,5))
    
    plt.subplot(121)
    sibsp_df['Survived'].value_counts().plot.pie(labels=['No Survived','Survived'],autopct= '%1.1f%%')
    plt.xlabel('sibsp')
    
    plt.subplot(122)
    sibsp_df['Survived'].value_counts().plot.pie(labels=['No Survived','Survived'],autopct= '%1.1f%%')
    plt.xlabel('no_sibsp')
    
    plt.show()
    
    image.png

    有无父母子女和存活与否的关系

    parch_df = train_data[train_data['Parch']!=0]
    no_parch_df = train_data[train_data['Parch']==0]
    
    plt.figure(figsize=(10,5))
    
    plt.subplot(121)
    parch_df['Survived'].value_counts().plot.pie(labels=['No Survived','Survived'],autopct= '%1.1f%%')
    plt.xlabel('parch')
    
    plt.subplot(122)
    no_parch_df['Survived'].value_counts().plot.pie(labels=['No Survived','Survived'],autopct= '%1.1f%%')
    plt.xlabel('no_parch')
    
    plt.show()
    
    image.png

    亲友的人数和存活与否的关系 SibSp & Parch

    # 先看一下有兄弟姐妹、有父母这两个属性与存活之间的数据统计
    fig,ax=plt.subplots(1,2,figsize=(10,5))
    train_data[['Parch','Survived']].groupby(['Parch']).mean().plot.bar(ax=ax[0])
    ax[0].set_title('Parch and Survived')
    train_data[['SibSp','Survived']].groupby(['SibSp']).mean().plot.bar(ax=ax[1])
    ax[1].set_title('SibSp and Survived')
    
    image.png
    # 再看一下SibSp & Parch在一起后与存活之间的关系
    train_data['Family_Size'] = train_data['Parch'] + train_data['SibSp'] + 1
    train_data[['Family_Size','Survived']].groupby(['Family_Size']).mean().plot.bar()
    
    image.png
    • 从图表中可以看出,若独自一人,那么其存活率比较低;但是如果亲友太多的话,存活率也会很低

    票价分布和存活与否的关系 Fare

    fig,axes = plt.subplots(1,2,figsize = (10,5))
    
    train_data['Fare'].hist(bins=70,ax = axes[0])
    train_data.boxplot(column='Fare',by='Pclass',showfliers=False,ax = axes[1])
    
    plt.show()
    
    image.png
    train_data['Fare'].describe()
    
    image.png
    #绘制生存与否与票价均值和方差的关系
    fare_not_survived = train_data['Fare'][train_data['Survived']==0]
    fare_survived = train_data['Fare'][train_data['Survived']==1]
    
    average_fare= pd.DataFrame([fare_not_survived.mean(),fare_survived.mean()])
    std_fare = pd.DataFrame([fare_not_survived.std(),fare_survived.std()])
    
    average_fare.plot(yerr=std_fare,kind='bar',legend=False)
    
    plt.show()
    
    image.png
    • 其中,参数yerr是y error的简称,可以画出y的偏差,所以我们在这里传入了yerr=std_fare

    船舱类型和存活与否的关系 Cabin

    • 由于船舱的缺失值确实太多,有效值仅仅有204个,很难分析出不同的船舱和存活的关系,所以在做特征工程的时候,可以直接将该组特征丢弃
    • 当然,这里我们也可以对其进行一下分析,将缺失的数据都分为一类。
      进一步,我们可以简单地将数据分为是否有Cabin记录来作为一个新的特征,让其与生存与否进行分析
    #加入一个新的特征‘Has_Cabin’
    train_data.loc[train_data.Cabin.isnull(),'Cabin'] = 'U0'
    train_data['Has_Cabin'] = train_data['Cabin'].apply(lambda x: 0 if x=='U0' else 1)
    
    train_data[['Has_Cabin','Survived']].groupby(['Has_Cabin']).mean().plot.bar()
    
    image.png
    #接着对不同类型的船舱进行分析
    train_data['CabinLetter'] = train_data['Cabin'].map(lambda x : re.compile('([a-zA-Z]+)').search(x).group())
    
    train_data['CabinLetter'] = pd.factorize(train_data['CabinLetter'])[0]
    train_data[['CabinLetter','Survived']].groupby(['CabinLetter']).mean().plot.bar()
    
    image.png

    可见,不同的船舱生存率也有不同,但是差别不大。所以在处理中,我们可以直接将特征删除

    港口和存活与否的关系 Embarked

    • 泰坦尼克号从英国的南安普顿港出发,途径法国瑟堡和爱尔兰昆士敦,那么在昆士敦之前上船的人,有可能在瑟堡或昆士敦下船,这些人将不会遇到海难
    sns.countplot('Embarked',hue = 'Survived',data = train_data)
    plt.title('Embarked and Survived')
    
    image.png
    sns.factorplot('Embarked','Survived',data=train_data,size=3,aspect=2)
    plt.title('Embarked and survived rate')
    
    image.png
    • 由上可以看出,在不同的港口上船,生还率不同,C最高,Q次之,S最低
    • 据了解,泰坦尼克号上共有2224名乘客。本训练数据只给出了891名乘客的信息,如果该数据集是从总共的2224人中随机选出的,根据中心极限定理,该样本的数据也足够大,那么我们的分析结果就具有代表性;但如果不是随机选取,那么我们的分析结果就可能不太靠谱了。

    其他可能和存活与否有关系的特征

    • 对于数据集中没有给出的特征信息,我们还可以联想其他可能会对模型产生影响的特征因素。如:乘客的国籍、乘客的身高、乘客的体重、乘客是否会游泳、乘客职业等等。
    • 另外还有数据集中没有分析的几个特征:Ticket(船票号)、Cabin(船舱号),这些因素的不同可能会影响乘客在船中的位置从而影响逃生的顺序。但是船舱号数据缺失,船票号类别大,难以分析规律,所以在后期模型融合的时候,将这些因素交由模型来决定其重要性

    变量转换

    • 变量转换的目的是将数据转换为适用于模型使用的数据,不同模型接受不同类型的数据,Scikit-learn要求数据都是数字型numeric,所以我们要将一些非数字型的原始数据转换为数字型numeric
    • 所有的数据可以分为两类:
      1. 定量(Quantitative)变量可以以某种方式排序,Age就是一个很好的列子。
      2. 定性(Qualitative)变量描述了物体的某一(不能被数学表示的)方面,Embarked就是一个例子。

    定性转换

    Dummy Variables

    • 定性变量的数据,例如性别、民族等,由于定性变量通常表示的是某种特征的有和无,所以量化方法可采用取值为0,1,2,3....等。这种变量称作虚拟变量(Dummy Variable),用D表示。它的作用是使定性数据能包括在统计模型中
    • 当qualitative variable(定性变量)是一些频繁出现的几个独立变量时,Dummy Variables比较适合使用。我们以Embarked为例,Embarked只包含三个值’S’,‘C’,‘Q’,我们可以使用下面的代码将其转换为dummies
    embark_dummies = pd.get_dummies(train_data['Embarked'])
    train_data = train_data.join(embark_dummies)
    train_data.drop(['Embarked'],axis=1,inplace=True)
    
    • get_dummies是pandas里的一个函数,可以用来对定性变量进行one-hot编码
      然后将编码后的数据连接到源数据中,同时又删去了‘Embarked’特征
    embark_dummies = train_data[['S','C','Q']]
    embark_dummies.head()
    
    image.png

    Factorizing

    • dummy不好处理Cabin(船舱号)这种标称属性,因为他出现的变量比较多。所以Pandas有一个方法叫做factorize(),它可以创建一些数字,来表示类别变量,对每一个类别映射一个ID,这种映射最后只生成一个特征,不像dummy那样生成多个特征
      • 疑问:这样的话会不会造成有序呢?就是会不会出现one-hot编码之前想去避免的情况呢
    train_data['Cabin'][train_data.Cabin.isnull()] = 'U0'
    train_data['CabinLetter'] = train_data['Cabin'].map(lambda x : re.compile("([a-zA-Z]+)").search(x).group())
    train_data['CabinLetter'] = pd.factorize(train_data['CabinLetter'])[0]
    
    train_data['CabinLetter'].head()
    
    image.png

    定量(Quantitative)转换

    scaling缩放

    • Scaling可以将一个很大范围的数值映射到一个很小的范围(通常是-1 - 1,或则是0 - 1),很多情况下我们需要将数值做Scaling使其范围大小一样,否则大范围数值特征将会由更高的权重。比如:Age的范围可能只是0-100,而income的范围可能是0-10000000,在某些对数组大小敏感的模型中会影响其结果
    #对Age进行缩放
    
    from sklearn import preprocessing
    
    assert np.size(train_data['Age']) == 891
    
    scaler = preprocessing.StandardScaler()
    train_data['Age_scaled'] = scaler.fit_transform(train_data['Age'].values.reshape(-1,1))
    
    train_data['Age_scaled'].head()
    
    image.png

    Binning

    • Binning通过观察“邻居”(即周围的值)将连续数据离散化。存储的值被分布到一些“桶”或“箱“”中,就像直方图的bin将数据划分成几块一样。下面的代码对Fare进行Binning
    train_data['Fare_bin'] = pd.qcut(train_data['Fare'],5)
    train_data['Fare_bin'].head()
    
    image.png
    pd.qcut是根据这些值的频率来选择箱子的均匀间隔,即每个箱子中含有的数的数量是相同的
    pd.cut将根据值本身来选择箱子均匀间隔,即每个箱子的间距都是相同的
    • 在将数据Bining化后,要么将数据factorize化,要么dummies化
    #factorize化
    train_data['Fare_bin_id'] = pd.factorize(train_data['Fare_bin'])[0]
    
    #dummies化
    fare_bin_dummies_df = pd.get_dummies(train_data['Fare_bin']).rename(columns=lambda x: 'Fare_' + str(x))
    train_data = pd.concat([train_data, fare_bin_dummies_df], axis=1)
    

    相关文章

      网友评论

        本文标题:泰坦尼克练习(上)

        本文链接:https://www.haomeiwen.com/subject/nmvlmqtx.html