Python数据分析个人笔记——Muse

前言

对于不同业务数据进行深层挖掘的前提,是对已有数据进行深刻了解和适当处理。大体量、噪声低的业务数据,是分析结果或建模预测结果具备良好可靠性的最大保障。若对相关业务没有太多的专业知识储备,干净且完备的数据其实便足矣刺激到数据分析工作者的神经。这将为业务的开展提供极大的助力。

因此,本博客结合Datawhale主持的《动手学数据分析》课程,从实用角度出发,将用得到的相关技术要点做个总结。若存在谬误,麻烦读者指出,笔者不胜感激!

一、数据载入

1、数据整体载入

应用Python语言进行数据处理与数值计算,均无法避开Numpy与Pandas库。两者是非常成熟且被广泛应用的数据处理库。相比于Pandas库,读者也可以进一步了解一下Polars库,对于大体量数据集会有更快的处理速度。

import numpy as np
import pandas as pd

数据载入有相对路径与绝对路径两种方式。相对路径即在当前py文件所在目录;绝对路径即硬盘的绝对存储位置。对于不同业务需求(如:.csv;.tsv;.xlsx文件格式),需要采取不同的读取方式。例如csv文件与tsv文件,两者的不同之处在于csv文件应用半角逗号“,”作为字段分隔符;tsv文件则应用制表符“\t”作为字段分隔符。本文示例以Kaggle泰坦尼克号乘客信息表为例。

  • pandas.read_csv():函数默认分隔符便为半角逗号。那么,应用该函数读取tsv文件,则需对该函数传入sep形参。
  • pandas.read_table():函数读取tsv、txt文件,因为该函数默认分隔符为制表符。从原理上说,read_csv()与read_table()函数是一围绕相同函数的不同封装,只是默认分隔符不同而已。
  • pandas.read_excel():函数专门读取xls、xlsx文件。
csvfile_path = r'D:\train.csv'
tsvfile_path = r'D:\train.tsv'
data_csv1 = pd.read_csv(csvfile_path)
data_tsv1 = pd.read_csv(tsvfile_path, sep = '\t')
data_csv2 = pd.read_table(csvfile_path, sep = ',')
data_tsv2 = pd.read_table(tsvfile_path)

 pandas.read_csv()函数还有很多有趣的形参对数据结构进行调整:

  • names:重定义载入数据列名的列表。
  • header:用作列名的数据行号,默认为0,即第一行数据;若没有列名,可设为None。
  • index_col:索引列的列名。

其中,“names”形参可以重新定义数据的表头。在实际业务中,该操作可能用于将英文表头替换为中文表头,从而更方便的理解该列数据的意义。除了上述方式外,也可以应用其他方法实现上述功能,如DataFrame.rename()函数。 

columns_names = ['乘客ID','是否幸存','乘客等级(1/2/3等舱位)',
                 '乘客姓名','性别','年龄','堂兄弟/妹个数',
                 '父母与小孩个数','船票信息','票价','客舱',
                 '登船港口']
data_csv1 = pd.read_csv(csvfile_path, names = columns_names)

data_csv2 = pd.read_csv(csvfile_path)
data_csv2.rename(columns = dict(zip(data_csv2.columns, columns_names)))

2、数据分割载入

对于大体量数据集,服务器有限的内存大小将限制一次读取的数据样本。为了解决该问题,可以应用上述读取数据函数的chunksize参数。

data_reader = pd.read_csv(csvfile_path, chunksize = 100)
#此时data_csv1的类型不再是DataFrame,而是一个可迭代对象
for i,chunk in enumerate(data_reader):
    #chunk类型为DataFrame
    print('number of chunks:',i)
    print(chunk.info())

二、数据认知与筛选

1、数据的初步认知

除了DataFrame数据类型,Series数据类型同样值得读者注意。Series数据类型是一个带有索引值的一维数组。那么我们可以很容易的联想到,DataFrame数据类型的每行与每列其实均由Series数据类型构建而来。两者均可以用数组、字典等方式嵌套构建,与之相关的学习资源众多,此处便不再赘述。

在对已有数据集进行初步认识时,Pandas库提供了一个非常实用的函数——DataFrame.info()。该函数将返回每一列数据的基本情况,包括数据类型非空值个数以及占用内存大小。若想进一步探究数据的统计特征,可以采用DataFrame.describe()函数。其返回值包含各列特征样本的最小值最大值平均值方差样本数以及百分位数。但是笔者建议,需要先对数据进行细致清洗后再进行统计特征量化,这样得出的结果将更能表现数据的真实情况,避免空值与异常值影响统计特性。此处只是为了内容统一,将其与info函数一同介绍。

2、数据筛选

2.1、筛选与切片

DataFrame数据类型的筛选依靠的是索引,即对DataFrame数据类型的值设置的各种筛选条件,最终会形成一个布尔值Series类型。筛选条件分为直接筛选函数筛选两种。其中,直接筛选便是应用比较运算符以及逻辑运算符进行条件设定。值得注意的是,运用逻辑运算符需要将各个子条件括在括号中。相关例子如下:

#筛选年龄在10岁以下的乘客信息
data_csv1[data_csv1["Age"]<10].head(3)
#筛选年龄在10岁以上,50岁以下的乘客信息
data_csv1[(data_csv1["Age"]>10) & (data_csv1["Age"]<50)].head(3)
#筛选年龄在10岁以上,50岁以下的乘客信息的函数筛选形式
data_csv1[data_csv1["Age"].between(10,50)].head(3)

然而,在进行条件筛选过后可以发现,其行索引对应于原始数据表的行号。因此,可以采用DataFrame.reset_index(drop = True)函数重置行索引。drop形参为True,意味着覆盖原始行索引。

若需要对数据进行列或行的切片过滤,Pandas库也提供了相关函数。按笔者的习惯,将其分别称为索引切片标签切片

  • DataFrame.iloc:按照位置索引切片,如DataFrame.iloc[[0,1,2]],表示切片第0、1、2,共计三行的数据;DataFrame.iloc[[0,1], [0,1]],表示切片第0、1行,第0、1列;DataFrame.iloc[[True, True], [True,True]],同样表示切片第0、1行,第0、1列。
  • DataFrame.loc:按照标签名称索引切片,如DataFrame.loc[:, ['Age']],表示切片名称为“Age”的列;DataFrame.loc[DataFrame['Age'] < 10],表示过滤出所有“Age”小于10岁的乘客信息。

2.2、字符串匹配与正则表达式

而对于字符串的匹配查询及匹配提取,对应函数分别为Series.str.contain()以及Series.str.extract()。两个函数均需要读者具备正则表达式的相关基础知识,将正则表达匹配式传入函数。此处参考大佬deerchao的博客《正则表达式30分钟入门教程》[1] 进行要点总结,若想详细且系统的学习相关知识,请移步参考资源中deerchao的博客地址。

2.2.1、元字符与反义

简单来说,正则表达式的作用便是在复杂字符串中匹配目标子字符串。而匹配字符串的对应规则便被称为正则表达式。举个例子来说,我们想在复杂字符串中找到单词“hi”。那么,相应的正则表达式即为:“hi”。但是,在字符串中除了单词“hi”,同样存在包含“hi”的其他单词,如“hifi”、“hint”等等。那么,此时我们可以使用元字符(metacharacter)“\b”对单词起始与终止进行限定,即:r“\bhi\b”。值得注意的是,元字符“\b”并不匹配任何字符,而是仅仅匹配一个位置。(r前缀为Pyhton原始字符串前缀,即反斜杠不再被当作转义字符)

那么,还有哪些常用的元字符呢?请浏览下方表格。然而,在实际应用过程中,以下元字符包含的范围可能超出需求。因此,读者也可以自己用方括号进行自定义。如我们想匹配所有大写字母,即正则表达式为:“[A-Z]”;若想匹配所有大、小写字母,正则表达式即为:“[a-zA-Z]”。

表1 常用元字符说明表
元字符元字符具体说明
.匹配除换行符之外的任意字符
\w匹配字母、数字、汉字、下划线
\s匹配所有空白符
\d匹配数字
\b匹配单词的开始或结束位置
^匹配字符串的开始位置
$匹配字符串的结束位置
2.2.2、限定符

对于重复匹配的定义,正则表达式中将其统称为限定符,即限定匹配模式的重复次数。那么,以三种格式的电话号格式,(0xx)xxxxxxxx、0xx-xxxxxxxx,或0xxxxxxxxxx为例,其通用正则匹配式为:r“\(?0\d{2}[(-]?\d{8}”。

表2 常用限定符说明表
限定符限定符具体说明
*重复任意次数
+重复1次或多次
?重复0次或1次
{n}重复n次
{n,}重复n次及多次
{n,m}重复n至m次

对于某些不能简单定义的字符类别,正则表达式提供了反义概念,如匹配任意非数字的字符,即r“\D”。(在方括号内,元字符“^”表示反义,而非匹配字符串的开始位置)

表3 常用反义说明表
反义反义具体说明
\W匹配任意除了字母、数字、汉字、下划线的字符
\S匹配任意非空白符的字符
\D匹配任意非数字的字符
\B匹配不是单词开始或结束的位置
[^x]匹配除了x之外的任意字符
[^aeiou]匹配除了a、e、i、o、u之外的任意字符
2.2.3、分支

细心的读者其实能够发现,上述匹配模式将错误的包含如:0xx)xxxxxxxx或(0xx-xxxxxxxx格式。因此,可以采用分支方法对正确匹配模式进行定义,即运用逻辑运算符“|”将不同的匹配模式结合在一起,将满足其中之一的字符筛选出来。因此,如上正则表达式可以替换为:r“\(0\d{2}\)\d{8}|0\d{2}-\d{8}|0\d{10}”。

2.2.4、分组

通过限定符可以实现单个字符的重复,多个字符的重复则要依靠分组实现,即将重复字符用圆括号括起来,后面接被方括号包裹的重复次数。对于正确的IP地址的正则表达式可应用分组表示为:r"(25[0-5]\.|2[0-4]\d\.|[01]?\d\d?\.){3}(25[0-5]\.|2[0-4]\d\.|[01]?\d\d?"。

解读上述正则表达式,主要集中于r“(25[0-5]\.|2[0-4]\d\.|[01]?\d\d?\.)”这一部分。这说明IP地址可分为几种不同情况。由于IP地址最大值为255,当前两位为25时,第三位需为0-5之间的整数;当第一位为2,第二位为0-4时,第三位则可以为任意数字;当第一位为1时,第二三位可以为任意数字;第一二位存在前缀0的情况。

除此之外,分组也延伸出了多种语法,下面将分别进行阐述。

表4 常用分组语法说明表
分类语法语法具体说明
后向引用(exp)匹配exp,并捕获字符串至自动命名的组中

(?<name>exp)

or

(?'name'exp)

匹配exp,并捕获字符串至命名为“name”的组中
(?:exp)匹配exp,不捕获相应字符串,也不为此分组分配组号
零宽断言(?=exp)匹配exp前面的位置
(?<=exp)匹配exp后面的位置
(?!exp)匹配后面接的不是exp的位置
(?<!exp)匹配前面不是exp的位置
注释(?#comment)该分组不对正则表达式的处理产生任何影响,用于提供注释

(1)、后向引用:

在指定分组之后,分组捕获内容可以在后续表达式或其它程序中作进一步处理。默认情况下,每个分组会自动拥有一个组号。以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推,分组0对应整个正则表达式。若在后续表达式中需要匹配某个分组的子字符串,则可以用:r“\分组号”表示。举个例子来讲,当需要匹配重复出现的单词时,如“go go”,正则表达式可表示为:r“\b(\w+)\b\s+\1\b”。

除了采用自动分组,也可以对分组名称进行自定义,即采用语法:r"(?<name>\w+)"。对该分组匹配内容进行反向引用时,可以使用:r“\k<name>”。因此,上一个例子的正则表达式可以替换为:r“\b(?<word>\w+)\b\s+\k<word>\b”。

(2)、零宽断言与反向零宽断言

该语法用于匹配在某些内容(但并不包括这些内容)的位置之前或之后的子字符串,只有当断言为真时才会继续进行匹配。“零宽”指的是该语法定位的是位置,而不包括子字符串;“断言”指的是确定位置的子字符串断言条件。

例如,当我们想匹配以“ing”结尾单词的前一部分,其正则表达式为:r“\b\w+(?=ing\b)”;而当我们想想匹配以“re”开头单词的后一部分,其正则表达式为:r“(?<=\bre)\w+\b”。

反向零宽断言与零宽断言类似,只不过是用来匹配后面接的不是exp的位置,来确保子字符串不会出现。以匹配不包含连续字符串abc的单词为例,其正则表达式为:r“\b((?!abc)\w)+\b”。读者可能对此表达式存在疑虑,为什么反向零宽断言语法“(?!abc)”在元字符“\w”之前呢?这是因为要考虑“abc”连续字符串在单词开头的情况。若正则表达式为:r“\b(\w(?!abc))+\b”,则连续字符串“abc”在开头的单词仍然会被匹配到,因为元字符“\w”将匹配第一个单词a,使得反向零宽断言语法匹配的是连续字符串“bc”的位置。

2.2.5、贪婪与懒惰

当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符[1],即贪婪模式。以下面这个正则表达式为例:r“a.*b”。这会匹配最长的以a开始,以b结束的字符串。如:aabab,则匹配aabab。

但是有时我们的业务目的与之不同,需要匹配尽可能少的字符,即懒惰模式。此时,只需要在限定符后加上一个问号“?”。“.?”意味着匹配任意数量的重复,但是在匹配成功的前提下包含最少的重复。那么,上述例子的懒惰模式版本为:r“a.*?b”。那么字符串“aabab”,则分配匹配“aab”与“ab”。

表5 懒惰限定符说明表
懒惰限定符说明
*?重复任意次,但尽可能包含少的重复
+?重复1次或更多次,但尽可能包含少的重复
??重复0次或1次,但尽可能包含少的重复
{n,m}?重复n到m次,但尽可能包含少的重复
{n,}?重复n次以上,但尽可能包含少的重复

三、数据清洗及预处理

当我们拿到一个数据集时,很难确保数据的质量,即数据集中会不可避免的出现空值、重复值或异常值。因此,在对数据进行统计分析、相关性分析以及建模预测之前,要对其进行妥善的处理。 

1、空值处理

在第二章第一节中介绍的DataFrame.info()函数便能够展示各列特征的非空值行数。同时,也可以应用DataFrame.isnull().sum()函数返回各特征的空值行数。读者可能对isnull()函数有疑惑,该函数是对哪些统计哪些空值类型呢?其实,该函数统计的是numpy.nan以及None数据类型。

对于空值的处理有两种方式,其一可以应用筛选功能进行定位替换。值得注意的是,在应用筛选功能时,尽可能使用isnull()函数,而不是“== numpy.nan”。因为 numpy.nan == numpy.nan 将会返回False。“numpy.nan”的具体意义是“not a number”,即非数字类型。也就是说,该值是除了数字类型之外的任意类型,所以两个任意类型的值并不相等。

#将年龄列的空值替换为0
data_csv1[data_csv1['Age'].isnull()] = 0

 除此之外,也可以用Pandas库提供的DataFrame.fillna()以及DataFrame.dropna()函数处理:

  • DataFrame.fillna():按照指定方法和范围填充缺失值,较重要的几个形参分别为填充值value、填充轴axis、填充方法method。
  • DataFrame.dropna():删除存在空白值的行。

2、重复值处理

Pandas库中与重复值相关的函数为DataFrame.duplicated(),对于重复的行或值返回False;清楚重复行的函数为DataFrame.drop_duplicated()

3、数据删除

DataFrame.drop()函数提供了行、列方向的自由删除或隐藏功能。其主要形参包括标签名labels、参考轴axis以及替换选项inplace。labels可传入待删除的行、列名列表;axis = 1时表示列轴删除、axis = 0时表示行轴删除;inplace = True则在原数据结构中进行删除。

4、数据转换

在数据分析过程中接触到的数据主要有两大类:数值型数据、文本型数据。其中,数据型数据可以大致分为离散型数据以及连续型数据。数值型特征在不考虑量纲、数据分布及模型鲁棒性的前提下,一般可以直接用于数学分析与建模,而文本型特征则需要被转换成数值型特征用于进一步的分析。

4.1、连续数值离散化

 Pandas库提供了非常方便的分箱函数——Pandas.cut()、Pandas.qcut()对连续型数值进行离散化。前一个函数支持平均分段、自定义分段;后者则支持百分比分段方式。

#将连续变量Age平均分箱成5个年龄段,并分别用类别变量12345表示
data_csv1['AgeBand'] = pd.cut(data_csv1['Age'], 5,labels = [1,2,3,4,5])
#将连续变量Age划分为(0,5] (5,15] (15,30] (30,50] (50,80]五个年龄段
data_csv1['AgeBand'] = pd.cut(data_csv1['Age'],
                              [0,5,15,30,50,80],
                              labels = [1,2,3,4,5])
#将连续变量Age按10% 30% 50 70% 90%五个年龄段
data_csv1['AgeBand'] = pd.qcut(data_csv1['Age'],
                               [0,0.1,0.3,0.5,0.7,0.9],
                               labels = [1,2,3,4,5])

 4.2、文本特征转换

对于文本特征可以应用Series.value_counts()函数进行计数统计,返回该特征数据各不重复样本的行数。Series.unique()函数返回的则是该特征数据不重复样本。

因此,我们便可以应用Series.map()映射函数或Series.replace()替换函数对文本数据进行转换。熟悉sklearn库的读者可能会采用标签编码器独热编码器完成上述目的,条条大路通罗马,我们都可以随心选择相应工具。

#将“Sex”列的性别文本转化为数字
data_csv1['Sex_num'] = data_csv1['Sex'].map({'male': 1, 'female': 2})
data_csv1['Sex_num'] = data_csv1['Sex'].reolace(['male','female'],[1,2])

 4.3、数据重构

对于关系型数据库较为熟悉的读者应该非常了解,在具体业务当中,数据不仅仅局限于一个二维表当中,而是几张表的关联组合。因此,对于数据结构的重构是非常重要的。

Pandas.concat()函数能够提供对DataFrame、Series数据结构之间横向与纵向的联结。当axis = 1时,该函数返回数据结构列表左右拼接的结果;当axis = 0时,则返回上下拼接的结果。join参数默认为“out”,即取数据列表的并集。笔者建议,仅使用该函数进行并集联结。

类似于左右表取交集(SQL中的JOIN)的相关操作,Pandas库提供了Pandas.merge()函数。其主要参数如下:

  • left:左数据表;
  • right:右数据表;
  • how:关联方式,默认关联方式为 “inner”,除此之外还有“left”,“right”,“outer”,“cross”;
  • on:关联时指定的两表共有特征;
  • left_on:关联时用到左表中的特征,在两个表不共有关联特征时使用;
  • right_on:关联时用到右表中的特征,同上。

4.4、数据的分组与聚合

4.4.1、数据分组

Pandas的分组函数DataFrame/Series.groupby()函数与SQL中的GROUP BY语法仍然是融会贯通的。其中,分别有两个比较重要的参数:

  • by:指定根据哪个/哪些字段分组;
  • axis:设置按列分组还是按行分组,0表示按列分组,1表示按行分组,默认为0。
#对泰坦尼克号票价按性别分组
data_fare_gbsex  = data_csv1['Fare'].groupby(data_csv1['Sex'])
#对乘客信息按性别分组
data_gbsex = data_csv1.groupby('Sex')

groupby()分组得到的是一个DataFrameGroupBy可迭代对象,遍历出来的是一个个元组。其内部结构可以具象化为:[(分组1, 子DataFrame1), (分组2, 子DataFrame2), ...]。该对象的groups属性可以返回分组信息(DataFrameGroupBy.groups),返回结果是一个类似字典的对象,由分组名和此分组数据在原DataFrame中的行索引组成。因此,应用DataFrameGroupBy.groups.keys()函数可以提取出所有分组的分组名,分组对象的get_group()方法可以返回指定分组名的子DataFrame。

4.4.2、分组聚合

Pandas提供了类似SQL的聚合函数与窗口函数,能够快速、简洁地将多个函数的执行结果聚合到一起。

DataFrame.agg()聚合函数的应用非常方便,其重要的参数分别为func函数参数以及axis轴向参数。其中,func函数能够适配多种形式,apply()函数能够传入的运算方法都能够与其适配。[2] axis的设置则与上述axis参数一致。

#聚合泰坦尼克号旅客年龄的最大值以及票价平均值
data_agg = data_csv1.agg({'Age':'max', 'Fare':'mean'}]
#先根据客舱等级与年龄分组,再对票价进行聚合平均
data_gbsex_agg = data_csv1.groupby(['Pclass','Age']).agg({'Fare':'mean'})

 持续更新中......


参考资源

[1] 正则表达式30分钟入门教程 (deerchao.cn)

[2] Pandas知识点-详解聚合函数agg - 知乎 (zhihu.com)

  • 19
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值