DataWhale 9月组队学习-动手学数据分析 task2_学习记录

数据清洗及特征处理

通常原数据都是不干净的,可能存在异常值,缺失值以及其他问题。所以一般进行数据分析之前都需要先对数据进行清洗。

读个文件先

#加载所需的库
import numpy as np
import pandas as pd

#加载数据train.csv
df = pd.read_csv('train.csv')

缺失值观察与处理

缺失值,可能是人为失误或者机器失误造成部分数据值的空缺,如果不对这些空缺的地方进行操作,极大可能会影响我们后续分析或建模得到的结果。

首先是观察缺失值,观察缺失值的方法可以直接通过info()来查看,info()可以观察到行数、各列列名、每列非缺失值数、数据类型以及数据总量

df.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

可以看到总共891行,但是Age、Cabin和Embarked的非缺失值数都没有达到891,说明其中都含有缺失值,但是这样子看不够直观,而且不能快速的看出各列一共有多少给缺失值,通过isnull()sum()组合可以查看每列的缺失值个数。

df.isnull()

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

df.isnull().sum()

#运行结果

PassengerId      0
Survived         0
Pclass           0
Name             0
Sex              0
Age            177
SibSp            0
Parch            0
Ticket           0
Fare             0
Cabin          687
Embarked         2
dtype: int64

如果再组合一个sum()就可以直接得出缺失值的总和

df.isnull().sum().sum()

#运行结果

866

isnull()是元素级别的判断,把对应的所有元素的位置都列出来,元素为空或者为Nan就显示True,否则就是False。pandas中会将NaN和None都处理为np.nan

None

None为python自带的,为None Type类型,代表空类型,并不能参与运算

df[df['Age']==None]

#结果显示为空,未检测到有空值

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

np.nan

numpy中的isnan对应的是NaN值,代表“不是数字”,数值类型为float,数组中显示为nan,能参与运算,但结果显示为NaN。

df[df['Age']==np.nan]

#结果显示依旧为空,这里并不是真的不存在空值!

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

注意:这里卡了我好久,后来在论坛上找了下,找到了原因。计算机中本没有绝对相等的数据,所谓的相等只是精度允许的条件下相等!np.nan原意为not a number,所以,对于某值是否为nan进行判断,只能使用np.isnan(i),绝不能用i == np.nan来判断,因为nan具有不等于自身的属性!

参考资料:https://blog.csdn.net/craftsman2020/article/details/107739405

isnull()

这里顺带一提,numpy用isnan()检查是否存在NaN,pandas用isna()或者isnull()检查是否存在NaN。pandas它是构建在numpy之上的,在numpy中,即没有na也没有null,而只有NaN(“Not A Number”),因此,pandas也沿用NaN值,所以造成了isna和isnull两个名称不同但功能相同的情况。

在不同的应用场景中,对缺失值的处理都不一样,大致可分为:

  • 删除元组
  • 数据补齐
  • 不处理

删除元组就算将存在遗漏信息属性值的对象(元组,记录)删除,从而得到一个完备的信息表,以减少历史数据来换取信息的完备,但可能会对数据资源造成一定的浪费,若在数据集不大且缺失值较多的情况下,该方法可能并不适用。

数据补齐是最常用的缺失值处理方式,补齐的方法也比较多,例如:人工填写、特殊值填充、平均值填充、K最近距离邻法等等,使用这些方法需要对数据有一定的了解,寻找最合适的填充方式。

不处理缺失值我认为在某些人工神经网络中,尚不确定缺失值对模型训练结果有何影响,可以考虑不处理缺失值来进行观测。

参考资料:https://blog.csdn.net/zrjdds/article/details/50223091

删除元组

删除元组的操作通常都采用dropna(),此操作会将缺失值所在行一并删除

#原数据
df.head(5)

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

#删除缺失值
df.dropna().head(5)

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

通过观察乘客ID可以发现,部分乘客数据已经消失了。

数据补齐

在上述中,我们已经可以正确判断缺失值元组,如果再进行简单的数据填充就很方便了

直接对空缺的位置进行赋值,我这里用0去填充

df[df['Age'].isnull()] = 0
df.head(6)

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

这里可以观察到如果是像上述代码进行操作的话,不仅缺失值会变为0,缺失值所在行的所有值也一并变为了0。为了避免这种情况,我自己试着写了一个很丑的代码:

首先是观察

print(df['Age'][0])
# 运行结果
22.0

好的,这相当于一个二维数组。前面试过了判断缺失值,那我尝试将Age列的缺失值判断出来

df['Age'].isnull()
# 运行结果
0      False
1      False
2      False
3      False
4      False
       ...  
886    False
887    False
888     True
889    False
890    False
Name: Age, Length: 891, dtype: bool

挑选出缺失值

df['Age'][df['Age'].isnull()]
# 运行结果
5     NaN
17    NaN
19    NaN
26    NaN
28    NaN
       ..
859   NaN
863   NaN
868   NaN
878   NaN
888   NaN
Name: Age, Length: 177, dtype: float64

然后赋值

df['Age'][df['Age'].isnull()] = 0
df.head(6)

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

可以看到第六行的数据原本为NaN的’Age’,此时已经变成了0,其他数值都没有改变。

当然,我这种写法好像是有问题的,因为它弹出来了一个SettingWithCopyWarning:A value is trying to be set on a copy of a slice from a DataFrame

看它的意思可能是df['Age'][df['Age'].isnull()]df的一个切片,而切片在pandas里面是视图,只能读不能写,如果只是查看df['Age'][df['Age'].isnull()]是没问题的,但是如果要对它进行修改的话,就会发生问题(但是我不知道为什么我操作好像成功了,理论上应该是出不来结果的)。

解决方法

虽然我这边不小心运行成功了,但是还是得记一下解决方法,从网上找了好多相关的分享,发现最多的就是前面加多了copy()和采用loc提取列或者是把某一列新建成一个list然后再整个赋值回去。

fillna( )

除了我的错误代码,当然还有正规的方法

fillna(self, value=None, method=None, axis=None, inplace=False, limit=None, downcast=None, **kwargs)
df.fillna(0).head(6)   # 用0填充缺失数据

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

可以观察到所有的缺失值都被赋值了,而不仅仅只有特定某列的值

均值填充
df.fillna(df.mean()).head(6)     # 用每列特征的均值填充缺失数据

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

这个均值填充方式并不是所有情况下都适用,例如这里的年龄Age和客舱Cabin,在这个表里的年龄都是整数,但是经过操作之后就带有小数了,改变了原数据的格式。而客舱Cabin的值都是字符串,字符串自然没有办法进行均值运算,所以缺失值并没有得到正确的处理,依旧为缺失值。

中位数填充
df.fillna(df.median()).head(6)   # 用每列特征的中位数填充缺失数据

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

中位数填充方式可以很好的保证数值类型的数据的格式,但是对字符串类型依旧无效。

相邻特征填充
df.fillna(method='bfill').head(7)   # 用相邻后面(back)特征填充前面空值

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

这里是用后面的特征值来填充前面的缺失值,可以解决前几个方法无法填充字符串类型缺失值的问题,但这种做法对后面的模型训练造成什么样的影响目前还是不清楚的,需要学习者不断尝试不同的填充方法。

重复值观察与处理

df[df.duplicated()]  #查看重复值
df = df.drop_duplicates()  # 删除重复值

特征观察与处理

分箱(离散化)处理

经过上面的填充操作,可以发现很多时候因为不同类型的特征导致个别操作存在问题。经过我的(参考答案)缜密观察,可以把特征大概分为两大类:

  • 数值型特征:Survived ,Pclass, Age ,SibSp, Parch, Fare,其中Survived, Pclass为离散型数值特征,Age,SibSp, Parch, Fare为连续型数值特征
  • 文本型特征:Name, Sex, Cabin,Embarked, Ticket,其中Sex, Cabin, Embarked, Ticket为类别型文本特征

数值型特征一般可以直接用于模型的训练,但有时候为了模型的稳定性及鲁棒性会对连续变量进行离散化。文本型特征往往需要转换成数值型特征才能用于建模分析。就像对鸢尾花数据集进行模型训练的时候,会将各种花类型名称更改为指定的数值,比如0,1,2等等。

在建模中,需要对连续变量离散化,特征离散化后,模型会更稳定,降低了模型过拟合的风险,分箱操作有等距分箱和等频分箱

等距分箱

cut将根据值本身来选择箱子均匀间隔,即每个箱子的间距都是相同的

#将连续变量Age划分为(0,5] (5,15] (15,30] (30,50] (50,80]五个年龄段,并分别用类别变量12345表示
df['AgeBand'] = pd.cut(df['Age'],[0,5,15,30,50,80],labels = [1,2,3,4,5])
df.head(3)

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

这里只考虑边界,每个等份里面的实例数量可能不等。

#将连续变量Age平均分箱成5个年龄段,并分别用类别变量12345表示
df['AgeBand'] = pd.cut(df['Age'], 5,labels = [1,2,3,4,5])
df.head()

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

等频分箱

qcut是根据这些值的频率来选择箱子的均匀间隔,即每个箱子中含有的数的数量是相同的

#将连续变量Age按10% 30% 50 70% 90%五个年龄段,并用分类变量12345表示
df['AgeBand'] = pd.qcut(df['Age'],[0,0.1,0.3,0.5,0.7,0.9],labels = [1,2,3,4,5])
df.head()

详细参考链接:https://blog.csdn.net/starter_____/article/details/79327997

文本变量转换
查看类别文本变量名及种类
  • value_counts( )

value_counts(normalize=False, sort=True, ascending=False, bins=None, dropna=True)

参数:

  1. normalize : boolean, default False。默认false,如为true,则以百分比的形式显示

  2. sort : boolean, default True。默认为true,会对结果进行排序

  3. ascending : boolean, default False。默认降序排序

  4. bins : integer, 格式(bins=1),意义不是执行计算,而是把它们分成半开放的数据集合,只适用于数字数据

  5. dropna : boolean, default True。默认删除na值

示例:

df['Sex'].value_counts()
# 输出结果
male      453
female    261
0           1
Name: Sex, dtype: int64
  • unique( )

对于一维数组或者列表,unique函数去除其中重复的元素,并按元素小到大返回一个新的无元素重复的元组或者列表。

df['Sex'].unique()
# 输出结果
array(['male', 'female', 0], dtype=object)
类别文本转换
  • replace( )
df['Sex_num'] = df['Sex'].replace(['male','female'],[1,2])
df.head()

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

  • map( )

这个方法具体是什么原理我还不是很清楚,和我在网上查阅的资料不太一样。网上描述的是map() 会根据提供的函数对指定序列做映射。课程的案例里边的用法和网上展示的不太一样,不知道能不能把下面map( )里的键映射为对应的值。

df['Sex_num'] = df['Sex'].map({'male': 1, 'female': 2})
df.head()

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

  • sklearn.preprocessing的LabelEncoder

LabelEncoder的作用是将n个类别编码为0~n-1之间的整数

from sklearn.preprocessing import LabelEncoder
for feat in ['Cabin', 'Ticket']:
    lbl = LabelEncoder()  
    label_dict = dict(zip(df[feat].unique(), range(df[feat].nunique())))
    df[feat + "_labelEncode"] = df[feat].map(label_dict)
    df[feat + "_labelEncode"] = lbl.fit_transform(df[feat].astype(str))

df.head()

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

参考链接:sklearn.preprocessing.LabelEncoder的使用 - ColdCode - 博客园 (cnblogs.com)

  • one hot编码

one hot编码是将类别变量转换为机器学习算法易于利用的一种形式的过程,又称为一位有效编码,主要是采用N位状态寄存器来对N个状态进行编码,每个状态都由他独立的寄存器位,并且在任意时候只有一位有效。我们的分类结果,得到的往往是隶属于某个类别的概率,这样在进行损失函数(例如交叉熵损失)或准确率计算时,变得非常方便。

# OneHotEncoder
for feat in ["Age", "Embarked"]:
    x = pd.get_dummies(df[feat], prefix=feat)
    df = pd.concat([df, x], axis=1) 
df.head()

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

参考链接:https://www.cnblogs.com/shuaishuaidefeizhu/p/11269257.html

提取特征

在Name特征里,除了乘客的姓名外还有称呼,例如Miss、Mrs等,这些可以判断出乘客的婚姻情况,也可以作为用于模型训练的新特征。

df['Title'] = df.Name.str.extract('([A-Za-z]+)\.', expand=False)
df.head()

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

数据重构

先读取个文件

# 载入data文件中的:result.csv
text = pd.read_csv('result.csv')
text.head()

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

数据聚合与运算

这一部分首先得先来看看什么是GroupBy机制

GroupBy

  • 作用:进行数据的分组以及分组后地组内运算

  • 语法:df[](指输出数据的结果属性名称).groupby([df[属性],df[属性]).mean()

说实话还不太会表述GroupBy,我感觉我是大概懂它是做什么的,下面看示例代码:

df = text['Fare'].groupby(text['Sex'])
df
# 运行结果
<pandas.core.groupby.generic.SeriesGroupBy object at 0x000001EA18771340>

可以看到,此时数据不再是DataFrame数据,而是一种DataFrameGroupBy对象。顾名思义由DataFrame转换而来的分组对象为DataFrameGroupBy,由Series转换的分组对象就是SeriesGroupBy。

接下来我们来查看泰坦尼克号男性与女性的平均票价

means = df.mean()
means
# 运行结果
Sex
female    44.479818
male      25.523893
Name: Fare, dtype: float64

输出的结果是男性和女性的平均票价,也就是说通过GroupBy( )操作将Fare这一列数据按照Sex进行分类了,就好像是把男性的票价归为一类,女性票价归为一类,我只是加了个平均运算,最终的结果就输出出来了,如果要计算男性与女性的票价和,只需要把mean( )改为sum( )就好了。

sums = df.sum()
sums

# 运行结果
Sex
female    13966.6628
male      14727.2865
Name: Fare, dtype: float64

看吧看吧,就是这个样子,那接下来统计就方便很多了

例如说统计泰坦尼克号中男女的存活人数

survived_sex = text['Survived'].groupby(text['Sex']).sum()
survived_sex

# 运行结果
Sex
female    233
male      109
Name: Survived, dtype: int64

通过这个统计结果,我们可以更进一步算出男性和女性的存活率,对于初步的数据观测是有一定的必要的,这些能反映出一些问题。

再比如不同等级的客舱存活的人数

survived_pclass = text['Survived'].groupby(text['Pclass']).sum()
survived_pclass

# 运行结果
Pclass
1    136
2     87
3    119
Name: Survived, dtype: int64

通过不同等级的客舱存活的人数,可以猜测可能事发最严重的可能是二等客舱,因为2等客舱存活人数相对来说较少,当然也需要观察不同等级的客舱入住人数,才可以进一步做判断,总之这些初步的观测不是没有意义的,虽然后面还有建模,但是如果能从这些数据表面获得一些重要事实的话,或许我们可以在模型训练的时候,往这些我们认为比较重要的特征上增加权重,以此提高模型预测的准确性。

数据可视化

导入包

import matplotlib.pyplot as plt

导入文件

text = pd.read_csv(r'result.csv')
text.head()

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

我认为可视化是数据分析里必不可缺的一环,因为很多时候良好的可视化可以让我们更直观的读取到数据信息,如果要展示给外人看那就更应该运用可视化了,可视化的重要性应该不用再说太多了。比如给出泰坦尼克号数据集中男女中生存人数,虽然数字的大小一眼就能看出来,但是数值大概相差多少在人脑里并不能第一时间得知,没什么概念,还需要再心算一下(没错就是我,我最懒了,不想心算)

sex = text.groupby('Sex')['Survived'].sum()
sex.plot.bar()
plt.title('survived_count')
plt.show()

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

看吧看吧,直观感觉就是女性存活人数要比男性存活人数多一半左右

再来看看生存与死亡在男性女性中的比例

text.groupby(['Sex','Survived'])['Survived'].count().unstack().plot(kind='bar',stacked='True')
plt.title('survived_count')
plt.ylabel('count')

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

可以非常直观的看到,男性总人数远多与女性,死亡率也是远高于女性,我的想法可能是当时大部分男性展现出了一定的风度,感动.

unstack( )

顺带一提的是代码里新加了unstack( )函数,与之相关的是stack( )函数

  • unstack( ):将数据的行索引转换为列索引
  • stack( ):将数据的列索引转换为行索引

本废物六级还未过,首先就吃了没文化的亏,stack的意思是堆叠,unstack就是“不要堆叠”,从图表上理解的话,就是Survived这个特征并没有在x轴上另起一条,而是以堆叠的方式呆在了Sex特征上(我也不知道我在胡说八道什么)。这里面的列索引我们可以简单理解为列名,初次见面还是有点难理解,让我们先看看不加untack( )的结果是什么:

text.groupby(['Sex','Survived'])['Survived'].count().plot(kind='bar',stacked='True')
plt.title('survived_count')
plt.ylabel('count')

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

我们发现原来的柱状图被拆分了开来,且x的值从[female、male]变为了[(female, 0), (female, 1), (male, 0), (male, 1)]。接下来继续上图,毕竟这一节可是可视化,用可视化来学习可视化理所应当吧哈哈哈哈,接下来从数据层面上来观察stack和unstack的差别。

原数据

text.groupby(['Sex','Survived'])['Survived'].count()
# 运行结果
Sex     Survived
female  0            81
        1           233
male    0           468
        1           109
Name: Survived, dtype: int64

原数据就是stack( )的状态,可以看到Sex和Survived同为行索引,并且初步感觉Sex的“等级”要高于Survived,接下来观察unstack( )状态下的数据。

原数据 + unstack( )

text.groupby(['Sex','Survived'])['Survived'].count().unstack()

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

很容易发现原本作为行索引的Survived特征,现在已经变成了列索引,整体变为了一个二维的表格。我觉得现在应该可以大致理解unstack( )的原理了。

参考链接:(4条消息) pandas中DataFrame的stack()、unstack()和pivot()方法的对比_S_o_l_o_n的博客-CSDN博客

随机演示

不同仓位等级的人员幸存情况

import seaborn as sns
sns.countplot(x="Pclass", hue="Survived", data=text)

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

不同年龄的人员幸村情况

facet = sns.FacetGrid(text, hue="Survived",aspect=3)
facet.map(sns.kdeplot,'Age',shade= True)
facet.set(xlim=(0, text['Age'].max()))
facet.add_legend()

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

不同舱位等级的人员分部情况

text.Age[text.Pclass == 1].plot(kind='kde')
text.Age[text.Pclass == 2].plot(kind='kde')
text.Age[text.Pclass == 3].plot(kind='kde')
plt.xlabel("age")
plt.legend((1,2,3),loc="best")

DataWhale 9月组队学习-动手学数据分析 task2_学习记录

可视化的库里边有太多的函数可以用,这些都需要我们花时间进一步去发掘,良好的可视化不仅看起来炫酷,而且看起来还很炫酷。就像是有些潜在的关系就可以通过可视化非常直观的体现出来,如果没有用可视化进行操作的话,那么它就没有被可视化进行操作(哈哈哈哈哈哈哈哈哈废话文学带师了我)

好了不开玩笑了,这篇学习记录之后还是会添加内容的,添加的内容取决于我学的东西,所以有时候可能这里插入一点内容,那里又插入一点内容,不过应该也没人看,这个纯粹就是我用于证明我学习过程东西,虽然写的很烂,但是我学的也不怎么样,主要是知识面还不够广,遇到新问题不能用以前的学过类似的东西来类比,然后是语文水平较低,很多东西不知道应该如何正确的表达,好多次都只想写个“自己意会”敷衍过去了(有一说一,除了平时多研究编程的东西外,我觉得我也要多地接触文学作品,现在真的像个莽夫)。

接下来应该会继续补充可视化的内容的,虽然说别的数据清洗、特征提取这些也只是鸡毛蒜皮,但是可视化这边连鸡毛蒜皮还没达到,所以都先拉到同一水平,而且我个人认为可视化在学习的过程中也能起到一定的帮助。YoungCat加油!

上一篇:C语言-结构体


下一篇:爬虫入门——爬取QQ音乐某一歌手前五首歌曲信息及相关评论