第5章〓Python数据表分析

在读者具备了pandas库和numpy库的基础知识以后,本章学习如何使用它们对数据表(即结构化数据)进行分析。5.1节介绍如何使用pandas库实现数据概览及预处理,5.2节在讲解数据排序后介绍pandas库和numpy库中常用的数据计算函数和方法,以便于进行数据的描述性统计分析,5.3节介绍如何实现数据的分组统计分析,5.4节以豆瓣读书排行榜的数据为例进行对应任务的实战分析。

5.1数据概览及预处理







5.1.1数据概览分析

数据概览是在数据分析之前对数据的规模、数据的类型及数据的质量等进行概览性的分析。使用pandas库中DataFrame对象的常用属性和方法可以进行数据概览。表5.1中列出DataFrame的常用属性,用于查看数据的基本信息以及数据的规模。


表5.1DataFrame的常用属性


属性
作用
类别



index
行名(索引) 

columns
列名
dtypes
数据的类型
values
数据值
数据的基本信息

shape
数据的形状

ndim
数据的维度
size
数据中元素的个数
Index.size
行数
columns.size
列数
数据的规模


下面以存储在cj.xlsx文件中的“成绩表”数据集为例进行数据的概览性分析,部分数据的示例如图5.1所示。





图5.1数据集示例


首先引入pandas库,读取cj.xlsx文件中的数据集,将其存储在DataFrame对象df中。



import pandas as pd

#读取数据

df=pd.read_excel("tdata/cj.xlsx")






注意: 这里的cj.xlsx文件存储在当前路径下的tdata文件夹中。

【例5.1】使用基础属性查看df数据集的基本信息。



print("索引:",df.index) 

print("列名:",df.columns) 

print("数据元素:",df.values[:5]) 

print("数据类型:\n",df.dtypes)






显示结果如下: 



索引: RangeIndex(start=0, stop=57, step=1) 

列名: Index(['学号', '姓名', '性别', '专业', '英语', '数学', 'Python', '选修',

'管理学'], dtype='object') 

数据元素: [[2020802045 '魏天' '男' '信息管理与信息系统' 67.12 90.80

93.0 95.0 106.0]

 [2020844001 '郭夏' '男' '国际贸易' 91.05 83.4 86.0 100.0 99.0]

 [2020844002 '王晓加' '男' nan 54.2 83.4 74.0 nan 90.0]

 [2020844003 '黄婷婷' '女' '国际贸易' 87.8 91.4 79.66 95.0 92.66]

 [2020844004 '赵小瑜' nan '国际贸易' 61.15 82.2 84.66 100.0 97.66]]

数据类型:

学号int64

姓名 object

性别 object

专业 object

英语float64

数学float64

Pythonfloat64

选修float64

管理学float64

dtype: object






【例5.2】使用基础属性查看df数据集的规模。



print("元素个数:",df.size) 

print("维度数:",df.ndim) 

print("形状:",df.shape) 

print("行数:",df.index.size) 

print("列数:",df.columns.size)






显示结果如下: 



元素个数: 513

维度数: 2

形状: (57, 9) 








行数: 57

列数: 9






使用DataFrame的一些方法可以查看数据、查看数据的总体信息、查看数据的缺失情况以及判断是否有重复的数据,如表5.2所示。


表5.2使用DataFrame进行概览分析的常用方法


方法
作用



head(n)
查看前n行数据,不指定n,默认为5行
tail(n)
查看后n行数据,不指定n,默认为5行
info()
查看数据的总体信息,包括数据类型、行/列名、行/列数、每列的数据类型、所占内存等
isnull()/isna()

notnull()/notna()
查看数据的缺失情况
duplicated()
判断是否有重复的数据


【例5.3】查看df数据集的样本数据。



#查看数据集的前两条样本

df.head(2)






结果如图5.2所示。





图5.2数据集的前两条样本


【例5.4】查看数据集df中各个特征的缺失情况。



print("df中每个特征的缺失情况:\n", df.isna() .sum())






显示结果如下: 



df中每个特征的缺失情况:

学号0

姓名0

性别3

专业3

英语0

数学0

Python0

选修4

管理学0

dtype: int64






isna方法判断每个样本值是否缺失,如果缺失,返回True。在进行概览性分析时直接使用isna方法不利于掌握数据集样本缺失的整体情况,因此通常在isna方法后要再使用sum方法汇总每列样本值缺失的总个数。

【例5.5】查看数据集df的完整信息摘要。



df.info()






显示结果如下: 



<class 'pandas.core.frame.DataFrame'>

RangeIndex: 57 entries, 0 to 56

Data columns (total 9 columns) :

学号57 non-null int64

姓名57 non-null object

性别54 non-null object

专业54 non-null object

英语57 non-null float64

数学57 non-null float64

Python57 non-null float64

选修53 non-null float64

管理学 57 non-null float64

dtypes: float64(5) , int64(1) , object(3) 

memory usage: 4.1+ KB






从输出结果可以看出,完整的数据集摘要信息包括数据集的数据类型,行/列索引信息,各个列的名称、非空值的数量以及该列的数据类型,所有列的数据类别的信息汇总以及DataFrame元素的内存使用情况。

5.1.2数据清洗

数据缺失会导致样本数据信息减少,增加数据分析的难度,也会导致数据分析的结果产生偏差; 数据重复会导致数据之间的差异变小,数据的分布发生较大变化; 数据集中有异常值会产生“伪回归”,因此需要在数据概览后对这些数据进行适当的处理。

数据清洗是通过预处理去除数据中的噪声,恢复数据的完整性和一致性。数据清洗通常包括缺失值处理、重复值处理以及异常值处理。

(1)  在处理缺失值时,可以采用删除缺失值法或者用其他值替换缺失值的方法。

(2)  在处理重复值时,一般采用删除的方法。

(3)  异常值的处理方法和缺失值相同,可以采用删除或替换的方式,只是对于如何判定异常值(也就是异常值检测),需要根据数据的具体情况采用不同的方法。

本节以5.1.1节中存储到DataFrame对象df中的数据集为例进行数据清洗的相关操作的介绍。



import pandas as pd

#解决数据输出时列名不对齐的问题

pd.set_option('display.unicode.east_asian_width', True) 

#读取数据

df=pd.read_excel("tdata/cj.xlsx")






1. 缺失值处理

在pandas中专门提供了删除缺失值的dropna方法和替换缺失值的fillna方法。

dropna方法的语法格式如下: 



DataFrame.dropna(axis=0,how="any",subset=None,inplace=False)






常见参数的说明如下。

(1) axis: 表示轴向,其中0为删除行、1为删除列,默认为0。

(2) how: 表示删除形式,其中"any"表示只要有缺失值就删除,"all"表示全部为缺失值时才删除,默认为"any"。

(3) subset: 表示进行去重的列/行,接收array,默认为None。

(4) inplace: 表示是否在原表上进行操作,接收boolean,默认为False。

dropna方法由DataFrame直接调用。

注意: 为了有效地保护原始数据,pandas提供了一种策略,即涉及删除、修改等操作时,默认都会在备份数据上进行,所以在涉及DataFrame数据修改和删除的方法中都有inplace参数,其作用是相同的。

【例5.6】数据集df的缺失值的删除处理示例。



#存在任一缺失值即删除

df1=df.dropna() 

print("删除前:",df.shape) 

print("删除后:",df1.shape) 

#所有列均为缺失值即删除

df1=df.dropna(how="all") 

print("删除前:",df.shape) 

print("删除后:",df1.shape) 

#指定列均为缺失值即删除

df1=df.dropna(how="all",subset=["专业","选修"]) 

print("删除前:",df.shape) 

print("删除后:",df1.shape)






显示结果如下: 



删除前: (57, 9) 

删除后: (48, 9) 



删除前: (57, 9) 

删除后: (57, 9) 



删除前: (57, 9) 

删除后: (56, 9)






fillna方法的语法格式如下: 



DataFrame.fillna(value, method=None,axis=1, inplace=False,limit=None)






常见参数的说明如下。

(1) value: 接收替换缺失值的值。

(2) method: 表示缺失值的填充方法,默认为None。其中,"backfill"或"bfill"表示用下一个非缺失值来填充; "pad"或"ffill"为使用上一个非缺失值来填充。

(3) axis: 表示轴向,其中0为行、1为列,默认为0。

(4) inplace: 表示是否在原表上进行操作,接收boolean,默认为False。

(5) limit: 表示填补缺失值个数的上限,接收int,默认为None。

fillna方法由DataFrame直接调用,其中在进行缺失值替换的时候会根据数据集的特点采取相应策略,如果想用数据集中缺失值之前或之后的值进行替换,则可以设置method填充方法。

【例5.7】数据集df的缺失值的填充处理示例。



#将缺失值NaN填充为0

df['选修'].fillna(0) 

#将缺失值NaN填充为后面的值

df['选修'].fillna(method="bfill") 

#将缺失值NaN填充为选修课的平均分

df['选修'].fillna(np.mean(df["选修"]))






2. 重复值处理

处理重复值可以使用drop_duplicates方法。

drop_duplicates方法的语法格式如下: 



DataFrame.drop_duplicates(subset=None,keep="first",inplace=False)






常见参数的说明如下。

(1) subset: 表示进行去重的列,默认为None,表示全部列。

(2) keep: 表示重复时保留第几个数据,默认为"first"。其中,"first"为保留第一个,"last"为保留最后一个,False为均不保留重复值。

(3) inplace: 表示是否在原表上进行操作,接收boolean,默认为False。

调用drop_duplicates方法,如果直接使用无参数的方法,表示要去除全部的重复数据; 如果设置subset参数,则去除指定列的重复数据,keep参数表示去重时保留值的方法。

【例5.8】数据集df的重复值的处理示例。



#去除全部重复数据

df1=df.drop_duplicates() 

print("去重前:",df.shape) 

print("去重后:",df1.shape) 

#去除指定列中的重复数据

df1=df.drop_duplicates(["专业"]) 

print("去重前:",df.shape) 

print("去重后:",df1.shape) 

#去除指定列中的重复数据,设置keep参数

df1=df.drop_duplicates(["专业"],keep="last") 

print("去重前:",df.shape) 

print("去重后:",df1.shape) 

#去除指定若干列中的重复数据

df1=df.drop_duplicates(["学号","姓名"]) 

print("去重前:",df.shape) 

print("去重后:",df1.shape)






显示结果如下: 



去重前: (57, 9) 








去重后: (54, 9) 



去重前: (57, 9) 

去重后: (6, 9) 



去重前: (57, 9) 

去重后: (6, 9) 



去重前: (57, 9) 

去重后: (54, 9)






3. 异常值处理

在数据分析中,异常值是指超出或低于正常范围的值,比如成绩高于100分、身高超过3米、商品价格为负值等。一般可以简单地根据给定的数据范围进行判断,将不在范围内的数据视为异常值。另外,也可以使用统计学均方差检查数据是不是在标准差范围内,如图5.3所示,这个方法的局限性在于需要数据具有正态分布特征。后续在可视化分析中学习箱形图的绘制后,也可以使用箱形图识别异常值,如图5.4所示。





图5.3用统计学标准差检测异常值






图5.4用箱形图检测异常值


当然,针对不同的情形还有更多异常值的检测方法,这里不再介绍。

在异常值检测出来以后,最常见的处理方式是删除,也可以把它们当作缺失值进行替换填充处理。另外在一些场景中,比如针对信用卡使用记录的分析,还会专门把异常值当成特殊情况进行分析,研究其出现的原因。

5.1.3数据的抽取与合并

在进行数据分析时,并不是所有数据集中的数据都是必需的,此时可以进行数据抽取; 有时要分析的数据来源于不同的数据表,例如想对排行榜上的书籍信息进行分析,而所需要的数据在两张表中,这时就要进行数据合并。数据抽取和数据合并是进行结构化数据分析预处理中常见的操作,pandas库对这两项操作提供了灵活、便利的实现方法。

1. 数据抽取

对数据集进行抽取,既可以抽取所需要的列,也就是数据集的特征,也可以抽取所需要的行,也就是数据集的样本。

1)  DataFrame的索引

DataFrame数据结构有行和列两种索引,DataFrame的行、列索引都有两种表示方法: 一是隐式索引,系统默认赋值的索引编号,下标从0开始; 二是显式索引,是用户通过index和columns属性设置的行和列的名称。

隐式索引符合计算机用户的使用习惯,数据下标从0开始,但是显式索引更符合数据分析用户的习惯,因为带有标签的数据更有意义,比如34这个数据通过行、列显式索引可以看出是小张的语文成绩,而仅用隐式索引表示时,只能说34是第0行第0列的数据,如图5.5所示。





图5.5DataFrame的索引


2)  DataFrame的数据抽取方法

在DataFrame中隐式索引和显式索引表示方式是共存的,在抽取数据时,用iloc属性可以按照系统默认的隐式索引抽取数据,用loc属性可以根据用户定义的显式索引抽取数据。此外,抽取数据还可以用DataFrame的列名属性和索引下标的方法。

在实际应用中,因为行、列抽取的需求不同,大家在初学时可能会觉得pandas中对DataFrame的数据抽取相对复杂,但总结起来一共有列名属性、索引下标、iloc属性、loc属性4种方法,具体如表5.3所示。


表5.3数据抽取方法


方法
用法
作用
示例
说明



列名属性
.列名
抽取单列
df.学号
抽取学号列,返回Series类型
索引下标
[]
抽取单列或多列
df["学号"]

df[["学号"]]

df[["学号","姓名"]]
抽取学号列,返回Series类型

抽取学号列,返回DataFrame类型

抽取学号、姓名两列
iloc属性
.iloc[]
抽取任意列、任意行或指定行/列
df.iloc[:,[0]]

df.iloc[1:20,]

df.iloc[1:10,2:5]
抽取第0列

抽取第1到第19行

抽取第1到第9行,第2到第4列
loc属性
.loc[]
抽取任意列、任意行或指定行/列或根据条件进行抽取
df.loc[:,["学号"]]

df.loc[1:20,]

df.loc[1:10,["学号"]]

df.loc[df.英语>90,]
抽取“学号”列

抽取第1到第20行

抽取第1到第10行的学号列

抽取英语成绩超过90的所有行


3)  数据抽取示例

下面仍以cj.xlsx中的数据集为例来看使用这些方法抽取列、抽取行以及抽取行和列的示例。首先将数据集读取到Dataframe对象df中。



import pandas as pd

pd.set_option('display.unicode.east_asian_width', True) 

df=pd.read_excel("tdata/cj.xlsx")






(1) 抽取列的示例。



>>>df.学号






结果显示为: 



0 2020802045

1 2020844001

2 2020844002

…






使用DataFrame属性的方法可以抽取一列,其返回的数据是Series类型,这和使用[]索引下标引用单列的方法的作用相同,此时[]索引下标中的列名以字符串形式给出。



>>>df["学号"]






结果显示为: 



0 2020802045

1 2020844001

2 2020844002

…






在[]索引下标中也可以放置要抽取列名构成的列表,其中列名同样以字符串形式给出,用这种方式抽取的结果是DataFrame类型。

在列表中可以是单个列名: 



>>>df[["学号"]] 






结果显示为: 



学号


02020802045

12020844001

22020844002

 …






在列表中也可以是多个列名: 



>>>df[["学号","姓名","专业"]]






结果显示为: 



学号姓名专业


02020802045魏天信息管理与信息系统

12020844001郭夏国际贸易

22020844002王晓加NaN

 …






用iloc属性和loc属性进行数据抽取时,需要在属性后跟[ ]索引下标,[ ]里面是先行后列,用逗号“,”分隔。行和列的表示形式可以是单行、单列、切片(切片表示连续区域或列表)。

注意:  iloc属性和loc属性的区别如下。

 iloc属性中行列是隐式索引,使用整数的位置访问DataFrame元素。

 loc属性中行列是显式索引,使用标签的索引访问DataFrame元素。

 使用切片表示连续区域时,loc属性抽取到包括切片结束点本身数据,而iloc属性抽取到结束点-1的数据。

下面的代码同样可以分别实现对学号、姓名和专业列的数据抽取。



>>>df.iloc[:,[0,1,3]]

>>>df.loc[:,["学号","姓名","专业"]]






(2)  抽取行的示例。

抽取连续行数据可以使用iloc属性或loc属性。



>>>df.loc[1:5,]






结果如图5.6所示。



图5.6loc属性切片抽取结果



注意: 使用loc属性时,此处[ ]中的1和5被看作行标签,切片数据包括标签5所在行; 使用iloc属性时,1和5则被看作位置,切片数据不包括数字5所在的行。读者可以自行尝试df.iloc[1:5,],体会二者的区别。 

在抽取行时,除了可以使用切片抽取连续行,还可以抽取用列表表示的不连续的行。



>>>df.iloc[[1,2, 6,7],]






结果如图5.7所示。





图5.7iloc属性列表抽取结果


另外,如果将DataFrame类型数据看作序列,可以使用切片的方法来抽取指定的连续行。



>>>df[5:7]






结果如图5.8所示。





图5.8序列切片抽取结果


注意: 这种方法实际上较少使用,而且它不能抽取单行或不连续的行。

在根据用户自定义的条件对行数据进行抽取时只能使用loc属性,例如下面的代码抽取了英语列大于90的行数据。



>>>df.loc[df.英语>90,]






结果如图5.9所示。





图5.9loc属性条件抽取结果


(3)  抽取行和列的示例。

抽取指定行和列可以采用以下两种方法。

方法一: 用DataFrame数据框类型的索引下标法先抽取指定的列,再使用切片法抽取指定连续行的方法。

方法二: 用iloc或loc属性的方法在[ ]中同时指定要抽取的行、列索引。

其中iloc和loc属性的方法比较常用。

使用方法一进行抽取的示例如下。

这里先抽取指定的学号、姓名、专业列,再用切片法抽取前两行: 



 >>>df[["学号","姓名","专业"]][:2]#用切片法抽取连续行






结果显示为: 



学号姓名专业


02020802045魏天信息管理与信息系统

12020844001郭夏国际贸易






注意: 这种方法不适用于抽取单行或不连续的行,但可以在抽取列后通过自定义条件来抽取行,例如抽取学号、姓名、专业列,且数学成绩大于90分的样本记录。



>>>df[["学号","姓名","专业"]][df.数学>90]






结果显示为: 



学号姓名专业


02020802045魏天信息管理与信息系统

32020844003黄婷婷国际贸易

72020844008韩天国际贸易

…







在方法二中可以用iloc属性或loc属性同时指定行、列索引进行抽取。在抽取指定行和列的时候常用这两个属性,并且这两个属性在指定行时比较灵活,可以是单行、切片表示的连续行、列表表示的不连续行。其中iloc属性在指定行、列时均使用隐式索引,loc属性在抽取列时使用显式索引。

首先来看iloc属性进行行、列抽取的示例。



>>>df.iloc[3,2:5]#抽取单行






结果显示为: 



性别女

专业国际贸易

英语87.8

Name: 3, dtype: object






这里返回的是Series序列类型的数据。



>>>df.iloc[[3],2:5]#抽取单行






结果显示为: 



性别专业英语


3女国际贸易87.8






这里返回的是DataFrame数据框类型的数据。



>>>df.iloc[:3,2:5]#抽取连续行






结果显示为: 



性别专业英语


0男信息管理与信息系统67.116667

1男国际贸易91.050000

2男NaN54.200000






另外还可以指定不连续的行和不连续的列,例如: 



>>>df.iloc[[3,13],[0,2]]#抽取不连续的行






结果显示为: 



学号性别


3男女

13男男






用loc属性方法则可以显式指定要抽取的列索引,例如: 



>>>df.loc[:2,["学号","姓名","专业"]]






结果显示为: 



学号姓名专业


02020802045魏天信息管理与信息系统

12020844001郭夏国际贸易

22020844002王晓加NaN






用loc属性方法也可以自定义条件抽取指定的行,例如: 



>>>df.loc[df.数学>90,["学号","姓名","专业"]]






结果显示为: 



学号姓名专业


02020802045魏天信息管理与信息系统

32020844003黄婷婷国际贸易

72020844008韩天国际贸易

…







2. 数据合并

在进行数据分析时需要的数据可能来源于不同的表,为了便于分析,通常会对这些数据进行合并。数据合并涉及按列合并特征还是按行合并样本。表5.4~表5.7所示的是4个数据集,其中df1可以分别和df2、df3按列合并形成特征更丰富的数据集,这里df1和df2有重叠的隐式行索引,df1和df3有重叠的“学号”列。df1和df4可以按行合并,增加样本量。


表5.4数据集df1


序号
学号
姓名
专业


0
2020802045
魏天
信息管理与信息系统
1
2020844001
郭夏
国际贸易
2
2020844002
王晓加
NaN
3
2020844003
黄婷婷
国际贸易
4
2020844004
赵小瑜
国际贸易




表5.5数据集df2


序号
数学
选修


0
90.8
95.0
1
83.4
100.0
2
83.4
NaN
3
91.4
95.0
4
82.2
100.0




表5.6数据集df3


序号
学号
Python



0
2020802045
93.00
1
2020844001
86.00
2
2020844002
74.00
3
2020844003
79.66
4
2020844004
84.66




表5.7数据集df4


序号
学号
姓名
专业



20
2020844022
关帅
会计学
21
2020844023
刘嘉雯
会计学
22
2020844024
刘浩天
会计学
23
2020844025
刘宇
NaN
24
2020844026
胡童
会计学
25
2020844027
丁灿
会计学



上述4个DataFrame数据集由“成绩表”数据集抽取而成,具体生成程序的代码如下: 



import pandas as pd

pd.set_option('display.unicode.east_asian_width', True) 

df=pd.read_excel("tdata/cj.xlsx")#读取数据

df1=df[["学号","姓名","专业"]][:5]#生成df1

df2=df[["学号","Python"]][:5] #生成df2

df3=df[["数学","选修"]][:5] #生成df3

df4=df.loc[20:25,["学号","姓名","专业"]] #生成df4






在pandas库中主要有4个方法用于特征和样本,即列和行的合并。

 按列合并特征,可以采用DataFrame的join方法,或pandas中的merge和concat方法。

 按行合并样本,可以采用DataFrame的append方法,或pandas中的concat方法。

1) 合并列

合并列常用的方法是join和merge方法,其中join方法默认是按照DataFrame的行索引、merge方法默认是按照DataFrame中同名的列来实现列的合并功能。

下面是join方法的语法格式: 



DataFrame.join(other, on=None, how='inner', lsuffix=", rsuffix=", sort=False) 






主要参数的说明如下。

(1) other: 接收DataFrame、Series或者包含了多个DataFrame的list,表示参与连接的其他DataFrame,无默认值。

(2) on: 接收列名或者包含列名的list或tuple,表示用于连接的列名,默认为None。

(3) how: 接收特定string。inner代表内连接,outer代表外连接,left和right分别代表左连接和右连接,默认为inner。

(4) lsuffix: 接收string,表示用于追加到左侧重叠列名的末尾,无默认值。

(5) rsuffix: 接收string,表示用于追加到右侧重叠列名的末尾,无默认值。

(6) sort: 根据连接主键对合并后的数据进行排序,默认为True。

join方法在合并数据集的列时,默认是按照DataFrame的行索引来实现按列合并的功能,因此不允许待合并的数据集中有重叠(同名) 列,如果数据集中有同名列,在进行合并时需要指明lsuffix或rsuffix参数,即给同名列起一个别名。

【例5.9】使用join方法按列合并数据集。

按列合并df1和df3: 



df1.join(df3)






df1和df3默认以index为连接主键合并列,结果如图5.10所示。




图5.10df1和df3使用join方法合并结果


在默认情况下,如果待合并的数据集中有同名的列,用join方法合并会报错,例如: 



df1.join(df2)#有同名列,无法区分报错






报错信息为: 



C:\ProgramData\Anaconda3\lib\site-packages\pandas\core\reshape\merge.py in _items_overlap_with_suffix(left, right, suffixes)

2176 

2177 if not lsuffix and not rsuffix:

-> 2178 raise ValueError(f"columns overlap but no suffix specified: {to_rename}")

2179 

2180 def renamer(x, suffix):



ValueError: columns overlap but no suffix specified: Index(['学号'], dtype='object')






此时,为了合并成功,可以在join方法中使用lsuffix或rsuffix参数,例如: 



df1.join(df2,lsuffix="x")#给同名列起别名






结果如图5.11所示。





图5.11df1和df2使用join方法合并结果


从结果可以看出,df1作为待合并的左侧数据集,在其原“学号”列后增加了指定的字符“x”作为后缀,避免了在合并后的数据集中出现相同的“学号”列。

merge方法是按照两个DataFrame对象的同名列连接合并。和join方法不同,它默认要求待合并的两个DataFrame必须有相同的列,如果没有同名列,在合并时要设置left_index和right_index参数的值为True。下面是merge方法的语法格式: 



pandas.merge(left,right,how='inner',on=None,left_on=None,right_on=None,left_index=False, right_index=False, sort=False, suffixes=('_x', '_y'))






主要参数的说明如下。

(1) left: 接收DataFrame或Series,表示要添加的新数据,无默认值。

(2) right: 接收DataFrame或Series,表示要添加的新数据,无默认值。

(3) how: 接收inner、outer、left、right,表示数据的连接方式,默认为inner。

(4) on: 接收string或sequence,表示两个数据合并的主键(必须一致),默认为None。

(5) left_on: 接收string或sequence,表示left参数接收数据用于合并的主键,默认为None。

(6) right_on: 接收string或sequence,表示right参数接收数据用于合并的主键,默认为None。

(7) left_index: 接收boolean,表示是否将left参数接收数据的index作为连接主键,默认为False。

(8) right_index: 接收boolean,表示是否将right参数接收数据的index作为连接主键,默认为False。

(9) sort: 接收boolean,表示是否根据连接主键对合并后的数据进行排序,默认为False。

(10) suffixes: 接收tuple,表示用于追加到left和right参数接收数据重叠列名的尾缀默认为('_x', '_y')。

【例5.10】用merge方法按列合并数据集。

按列合并df1和df2: 



df1.merge(df2)






df1和df2按照同名列进行连接,自动删除同名列,结果如图5.12所示。



在默认情况下,如果待合并的数据集中没有同名的列,用merge方法合并会报错,例如: 



df1.merge(df3)






报错信息如下: 



…

-> 1035lidx=self.left_index, ridx=self.right_index)) 

1036 if not common_cols.is_unique:

1037 raise MergeError("Data columns not unique: {common!r}"

MergeError: No common columns to perform merge on. Merge options: left_on=None, right_on=None, left_index=False, right_index=False






如果要解决这个问题,可以设置left_index和right_index参数的值为True,例如: 



df1.merge(df3,left_index=True,right_index=True)






结果如图5.13所示。




图5.12df1和df2使用merge方法合并结果




图5.13df1和df3使用merge方法合并结果




2)  合并行

两个数据集按行合并,可以用DataFrame的append方法,下面是append方法的语法格式: 



DataFrame.append(self, other, ignore_index=False, verify_integrity=False)






主要参数的说明如下。

(1) other: 接收DataFrame或Series,表示要添加的新数据,无默认值。

(2) ignore_index: 接收boolean,如果输入True,会对新生成的DataFrame使用新的索引(自动产生)而忽略原来数据的索引。其默认为False。

(3) verify_integrity: 接收boolean,如果输入True,那么当ignore_index为False时会检查添加的数据索引是否冲突,如果冲突,则会添加失败。其默认为False。

【例5.11】用append方法合并行。

按行合并df1和df4: 



df1.append(df4)






df1和df4有相同列,合并结果如图5.14所示。



当待合并的两个数据集的列不相同时,使用append方法实现的是行、列并集的拼接,例如合并df1和df3: 



df1.append(df3)#列不相同,实现并集拼接






合并结果如图5.15所示。




图5.14df1和df4使用append方法合并结果





图5.15df1和df3使用append方法合并结果




3) 拼接合并数据

如果不考虑待合并的数据集是否有相同的行或列索引,也就是实现数据集的拼接,可以使用concat方法。concat方法的使用非常灵活,可以按行或按列合并。concat方法的语法格式如下: 



pandas.concat(objs, axis=0, join='outer', join_axes=None, ignore_index=False, keys=None, levels=None, names=None, verify_integrity=False, copy=True)






主要参数的说明如下。

(1) objs: 接收多个Series、DataFrame的组合,表示参与拼接的pandas对象的列表组合,无默认值。

(2) axis: 接收0或1,表示连接的轴向,默认为0,当axis为0时表示按行拼接,当axis为1时表示按列拼接。

(3) join: 接收inner或outer,表示其他轴向上的索引是按交集(inner)还是按并集(outer) 进行合并,其默认为outer。

(4) join_axes: 接收Index对象,表示用于其他n-1条轴的索引,不执行并集/交集运算。

(5) ignore_index: 接收boolean,表示是否不保留连接轴上的索引,产生一组新索引。其默认为False。

(6) keys: 接收sequence,表示与连接对象有关的值,用于形成连接轴向上的层次化索引。其默认为None。

(7) levels: 接收包含多个sequence的list,表示在指定keys参数后,指定用作层次化索引各级别上的索引。其默认为None。

(8) names: 接收list,表示在设置了keys和levels参数后用于创建分层级别的名称。其默认为None。

(9) verify_integrity: 接收boolean,表示是否检查结果对象新轴上的重复情况,如果发现则引发异常。其默认为False。

【例5.12】用concat方法实现数据集的拼接。

df1和df2按列拼接: 



pd.concat([df1,df2],axis=1)#按列拼接






拼接结果如图5.16所示。





图5.16df1和df2使用concat方法按列拼接结果


在concat方法中将要拼接的数据集以列表形式呈现,可以同时拼接两个以上的数据集,例如同时按行拼接df1、df2、df3数据集: 



pd.concat([df1,df2,df3],axis=0)#按行拼接






拼接以后部分结果如图5.17所示。





图5.17df1、df2和df3使用concat方法按行拼接结果








5.1.4数据的增、删、改

对数据进行预处理时经常会涉及数据的增、删、改操作。

1. 数据增加

数据增加就是在原始表中增加不存在的列或者行。

1) 增加列

增加列常用的方法有新建的列索引赋值方法和insert方法。

新建的列索引赋值方法是通过为DataFrame对象新建一个列索引,并对该列索引下的数据进行赋值操作,使用该方法可以实现在DataFrame的末尾新增一列。

【例5.13】在成绩表中增加“团员否”列,将其值设为True。



import pandas as pd

df=pd.read_excel("tdata/cj.xlsx")

df["团员否"]=True

df.head()






结果如图5.18所示。





图5.18增加“团员否”列后的结果


如果要在指定位置增加列,可以使用insert方法,该方法的语法格式如下:  



DataFrame.insert(loc,column,value,allow_duplicates=False) 






常见参数的说明如下。

(1) loc: 接收int,表示第几列,如果在第一列插入数据,则loc=0。

(2) column: 接收string,插入列的名称。

(3) value: 接收boolean、string、array、Series等类型数据,插入列的值。

(4) allow_duplicates:接收boolean,表示是否允许列名重复,若设为True,则允许新插入的列名和已存在的列名重复。

【例5.14】在成绩表的第3列增加“年龄”列,将其值设为18。



import pandas as pd

df=pd.read_excel("tdata/cj.xlsx") 

df.insert(2,"年龄",18) 

df.head()






结果如图5.19所示。




图5.19在指定位置增加“年龄”列后的结果


2) 增加行

用loc属性可以为DataFrame对象新建一个行索引,并对其赋值; 增加多行可以使用 append方法。

【例5.15】为成绩表新增一行数据。



import pandas as pd

df=pd.read_excel("tdata/cj.xlsx") 

df.loc[57]=["20200848045","王芳",10,"女","金融学",55,66,77,90]

df.tail()






结果如图5.20所示。





图5.20新增一行数据后的结果



【例5.16】为成绩表新增3行数据。



import pandas as pd

df=pd.read_excel("tdata/cj.xlsx")

#提取前3行数据的学号、姓名和专业列作为数据集df1

df1=df[["学号","姓名","专业"]][:3]

#将df1添加到df中并查看后5行数据

df.append(df1).tail()






结果如图5.21所示。





图5.21新增3行数据后的结果


注意: 使用append方法不会直接在原数据集df中增加新列,如果需要使用新增后的数据集,建议将其保存为新的DataFrame对象。

2. 数据修改

数据修改通过抽取DataFrame要修改的列或行直接赋新值。

【例5.17】修改成绩表的数据。



import pandas as pd

df=pd.read_excel("tdata/cj.xlsx") 

#修改年龄列

df["年龄"]=25

#修改第57行

df.loc[57]=["20200848043","刘云","女","金融学",75,86,87,96,95]

#修改前10行的年龄列为20

df["年龄"][:10]=20






另外,如果要修改DataFrame的列名,可以用给columns属性直接赋新列名的方法,也可以用rename方法修改指定列名。rename方法的语法格式如下: 



DataFrame.rename(index=None, columns=None)






主要参数的说明如下。

(1) index: 行标签参数,用于修改行名,需要传入一个字典或函数。

(2) columns: 列标签参数,用于修改列名,需要传入一个字典或函数。

【例5.18】修改成绩表的列名。



import pandas as pd

df=pd.read_excel("tdata/cj.xlsx") 

#修改columns属性

df.columns=['id', '姓名', 'age', '性别', '专业', '英语', '数学', 'Python', '选修', '团员否']

#使用rename方法修改指定列名

df1=df.rename(columns={"id":"我的学号"})






注意: 在修改列名时用columns属性赋值的方法需要给出全部列名,包括要修改的列名和未修改的列名; 用rename方法可以修改部分列名,也可以修改部分行名。

3. 数据删除

删除数据可以使用drop方法,其语法格式如下: 



DataFrame.drop (labels=None,axis=0,index=None,columns=None,inplace=False)






常见参数的说明如下。

(1) labels: 表示行标签或列标签。

(2) axis: 表示轴向,0为按行删除,1为按列删除,默认值为0。

(3) index: 表示删除行,默认值为None。

(4) columns: 表示删除列,默认值为None。

(5) inplace: 表示是否在原表上进行操作,接收boolean,默认值为False。

注意: 指定index是按行删除,指定columns是按列删除,通过同时指定labels和axis参数也可以删除行或列。

【例5.19】删除成绩表中的数据。



import pandas as pd

pd.set_option('display.unicode.east_asian_width', True) 

df=pd.read_excel("tdata/cj.xlsx")#读取数据

#增加“团员否”列

df["团员否"]=True

#删除第1、3、5行数据

df.drop(index=[1,3,5]) 

#删除“团员否”列

df.drop(columns=["团员否"]) 

#使用labels参数删除第1到第19行,axis参数默认为0

df.drop(labels=range(1,20))






注意: 上述数据删除都是在df的副本上进行的,并未删除df本身的数据。如果要在df上进行删除操作,需要指定inplace参数值为True,例如: 



#使用labels参数,设置axis参数为1,就地删除"选修"列

df.drop(labels="选修",axis=1,inplace=True)






5.1.5数据转换

处理数据有时会遇到数据转换的问题,例如转换指定列的数据类型、将整个DataFrame或Series序列转换成列表或字典等类型。

1. 转换指定列的数据类型

转换DataFrame中某列的数据类型可以用astype方法,其语法格式如下: 



DataFrame.astype(dtype, copy=True)






常见参数的说明如下。

(1) dtype: 用一个numpy.dtype或Python类型将整个pandas对象转换为相同类型; 或使用{col:dtype,...}将DataFrame的一个或多个列转换为dtype类型,其中col是列标签,而dtype是numpy.dtype或Python类型。

(2) copy:  为True时返回一个副本,为False时在原始数据集上修改。

【例5.20】修改成绩表中指定列的数据类型。



import pandas as pd

df=pd.read_excel("tdata/cj.xlsx")

#查看学号列的数据类型

print("学号列转换前:\n",df["学号"].dtypes)

#转换为object类型,并赋值给学号列

df["学号"]=df["学号"].astype(object)

print("学号列转换后:\n",df["学号"].dtypes)

#修改df中英语、数学列的数据类型为int类型

df=df.astype({"英语":int,"数学":int})

print("转换后df各列的数据类型:")

df.dtypes






结果显示为: 



学号列转换前:

int64

学号列转换后:

object

转换后df各列的数据类型:

学号object

姓名 object

性别 object

专业 object

英语int32

数学int32

Pythonfloat64

选修float64

管理学float64

dtype: object






2. 将DataFrame转换成其他数据类型

DataFrame可以整体转换成列表或字典。

(1) 转换成列表: 使用DataFrame的values属性的tolist方法。

(2) 转换成字典: 使用DataFrame的to_dict方法。

【例5.21】将DataFrame转换成列表和字典。



import pandas as pd

df=pd.read_excel("tdata/cj.xlsx")

#将df转换成列表

list=df.values.tolist() 

#查看转换后列表中第5到第9个元素

list[5:10]






显示结果如下: 



[[2020844005, '辛禧', '男', '国际贸易', 65.125, 88.6, 68.0, 80.0, 81.0],

 [2020844007, '王晨', '男', '国际贸易', 62.4, 80.0, 65.0, 90.0, 78.0],

 [2020844008, '韩天', '男', '国际贸易', 96.25, 91.0, 85.0, 97.0, 98.0],

 [2020844009, '刘玉', '女', '国际贸易', 89.05, 91.4, 80.32, 100.0, 93.32],

 [2020844010, '谢亚鹏', '男', '市场营销', 70.5, 85.2, 60.0, 90.0, 73.0]]






将df转换成字典类型。



df.to_dict()






显示结果如下: 



{'学号': {0: 2020802045,

1: 2020844001,

2: 2020844002,

...}






将df中的姓名和Python列转换为字典类型。



df[["姓名","Python"]].to_dict() 






显示结果如下: 



{'姓名': {0: '魏天',

1: '郭夏',

2: '王晓加',

...},

'Python': {0: 93.0,

1: 86.0,

...}}






5.2数据的描述性统计分析

数据的描述性统计分析是采用一些统计计算方法来概括事物的整体状况以及事物间的关联和类属关系。在对数据进行描述性统计分析之前按照某种依据对数据进行排序或排名,有助于从有序的角度把握数据的整体状况。

5.2.1数据排序和排名
1. 数据排序

DataFrame数据排序时主要采用的方法是sort_values方法,可以按照指定的行/列进行排序,语法格式如下: 



DataFrame.sort_values(by,axis=0,ascending=True, inplace=False, na_position='last') 






主要参数的说明如下。

(1) by: 接收list、string,要排序的依据,无默认值。

(2) axis: 接收int,表示轴向,其中0表示行、1表示列,默认为0,即按行进行操作。

(3) ascending: 接收boolean值或其列表,指定升序或降序,当指定多个排序时可以使用布尔值列表,默认为降序。

(4) inplace: 接收boolean,默认为False,如果设置为True表示就地排序。

(5) na_position: 空值NaN的位置,值为first表明空值显示在数据的开头,值为last表明空值显示在数据的最后。

【例5.22】对成绩表进行排序。



import pandas as pd

df=pd.read_excel("tdata/cj.xlsx")






按照选修课的成绩升序排序: 



df.sort_values(by="选修")






结果显示为: 



学号姓名性别专业英语数学Python选修

372020848007苏远 女 信息管理与信息系统90.2589.2 79.3268.0

402020848011张田田 女 信息管理与信息系统91.2089.6 96.3277.0

…






按照选修课的成绩降序排序: 



df.sort_values(by="选修",ascending=False) 






结果显示为: 



学号姓名性别专业英语数学Python选修

292020848001王春杨女会计学88.1089.884.00100.0

272020844029金耀男会计学79.4587.268.00100.0

…






按照选修课和Python的成绩升序排序: 



df.sort_values(by=["选修","Python"]) 






结果显示为: 



学号姓名性别专业英语数学Python选修

372020848007 苏远 女 信息管理与信息系统 90.25 89.279.3268.0

402020848011 张田田 女 信息管理与信息系统 91.20 89.696.3277.0

52020844005 辛禧男 国际贸易65.12588.668.0080.0

132020844014 刘欣语 男 市场营销48.71883.886.0080.0

…






按照选修升序排序,如该列中有空值,显示在开头: 



df.sort_values(by="选修",na_position="first") 






结果显示为: 



学号姓名性别专业英语数学Python选修

22020844002王晓加男 NaN54.2083.474.00 NaN

102020844011娄天楠男 市场营销58.8084.660.00 NaN

…






按行进行排序,则列之间应该是可比较的,这里提取df中的英语、数学、Python和选修4列,按行进行排序: 



dfs=df[["英语","数学","Python","选修"]]

dfs.sort_values(by=1,axis=1)






结果显示为: 



数学Python英语选修

090.893.0067.11666795.0

183.486.0091.050000100.0

283.4 74.0054.200000 NaN

…






2. 数据排名

DataFrame的rank方法可以实现按照DataFrame对象中的某些列进行排名,语法格式如下: 



DataFrame.rank(axis=0,method='average', numeric_only=None, ascending=True)






主要参数的说明。

(1) axis: 接收int,表示轴向,其中0表示行、1表示列,默认为0,即按行进行操作。

(2) method: 在相同值情况下采用的排名方法,默认是average(平均值),也可以是max(最大值)、min(最小值)、first(原始顺序)、dense(密集)。

(3) numeric_only: 接收boolean,若设为True,表示只对数字列进行排名。

(4) ascending: 接收boolean值或其列表,指定升序或降序,若指定多个排序可以使用布尔值列表,默认为降序。

【例5.23】对成绩表进行排名操作。



import pandas as pd

df=pd.read_excel("tdata/cj.xlsx")






按选修列进行排名: 



df[["选修"]].rank() 






结果显示为: 



选修

012.0

134.0

2NaN

312.0

434.0

…






按选修和Python列进行排名: 



df[["选修","Python"]].rank()






结果显示为: 



选修Python

012.052.5

134.037.0

2NaN13.5

312.021.0

434.032.0

…






rank排名方法在相同值的情况下默认按照平均值进行排名,通过设置method参数可以选择不同的排名方法: 



df["选修Average排名"]=df["选修"].rank() 

df["选修First排名"]=df["选修"].rank(method="first") 

df["选修Max排名"]=df["选修"].rank(method="max") 

df["选修Min排名"]=df["选修"].rank(method="min") 

df[["学号","选修Average排名","选修First排名","选修Max排名","选修Min排名"]]







结果显示为: 



学号选修Average排名 选修First排名选修Max排名选修Min排名

0202080204512.011.013.011.0

1202084400134.015.053.015.0

22020844002NaNNaNNaNNaN

3202084400312.012.013.011.0

4202084400434.016.053.015.0

…






3. 设置索引

设置索引能够快速地查询数据,实现数据的快速排序,也能在合并数据时实现数据的自动对齐。在pandas中提供了一系列方法设置和修改DataFrame数据对象的索引。

 reindex方法: 重设索引。

 set_index方法: 将某列设置为索引。

 reset_index方法: 重新设置连续索引。

1)  reindex方法

reindex方法通过修改索引操作完成对现有数据的重新排序。

【例5.24】按选修列排序后修改DataFrame索引为0~4或10~14。



import pandas as pd

df=pd.read_excel("tdata/cj.xlsx")

dfs=df.sort_values(by="选修") 

dfs.head()






按照选修列排序后结果如图5.22所示。





图5.22排序后的结果


修改行索引为0~4: 



dfs.reindex(range(0,5))






显示结果如图5.23所示。





图5.23修改行索引为0~4后的结果


修改索引为10~14: 



dfs.reindex(range(10,15))






显示结果如图5.24所示。





图5.24修改行索引为10~14后的结果



2)  set_index方法

set_index方法用于将DataFrame中的列转化为行索引,在转换之后,默认方法中的drop参数为True,即原有的列会被删除,可以通过设置drop参数为False保留原来的列。

【例5.25】将姓名列设置为DataFrame的索引。



dfs.set_index("姓名",drop=False)






部分结果如图5.25所示。





图5.25将姓名列设置为行索引后的结果


3)  reset_index方法

reset_index方法用于重新设置DataFrame索引。在对数据表进行清洗操作之后,例如去重或删除了空值所在的行,行索引本身是不会发生变化的,会出现不连续情形; 在对数据进行排序后,原始行索引也不会变化,会出现乱序情形,此时可以用reset_index方法重新设置索引。

【例5.26】在按选修列排序后重新设置DataFrame索引。



dfs=df.sort_values(by="选修") 

dfs.reset_index() .head()






结果如图5.26所示。





图5.26重新设置索引后的结果


5.2.2常见的数据计算方法

本节介绍描述性统计分析中常用的数据计算方法。描述性统计分析是采用一些统计计算方法来概括事物的整体状况以及事物间的关联和类属关系。

从分析的角度而言,数据可以分为数值型数据和类别型数据两种。

 数值型数据: 比如表示成绩、年龄的数据,对其的描述统计主要包括了计算数值型数据的最小值、均值、中位数、最大值、方差、标准差等。

 类别型数据: 比如表示性别、年级的数据,对其的描述统计主要是分布情况,可以使用频数统计。

1. 数值型数据的描述统计

进行数值型数据的描述性统计分析既可以采用表5.8所示numpy库中的数值计算方法,也可以采用表5.9所示的pandas提供的数值计算方法。


表5.8numpy库中常用的数值计算方法


方法
说明
方法
说明



np.min()
计算最小值
np.max() 
计算最大值
np.mean()
计算均值
np.ptp()
计算极差
np.median()
计算中位数
np.std()
计算标准差
np.var()
计算方法
np.cov()
计算协方差




表5.9pandas库中常用的数值计算方法


方法
说明
方法
说明



min()
计算最小值
max()
计算最大值
mean()
计算均值
quantile() 
计算四分位数
median()
计算中位数
std()
计算标准差
var()
计算方差
cov()
计算协方差
count()
计算非空值数量
mode()
计算众数
skew()
计算样本偏度
kurt()
计算样本峰度


【例5.27】对成绩表中的数值列进行计算。



import pandas as pd

df=pd.read_excel("tdata/cj.xlsx")

dfs=df[["英语","数学","Python","选修"]]






计算各列的均值: 



print("按列求均值\n",dfs.mean())






结果显示为: 



按列求均值

英语80.440877

数学87.845614

Python81.625965

选修96.679245

dtype: float64






计算各行的均值,显示前5条记录: 



print("按行求均值\n",dfs.mean(axis=1) [:5])






结果显示为: 



按行求均值

086.479167

190.112500

270.533333








388.465000

482.002500

dtype: float64






计算各列的中位数: 



print("中位数:\n",dfs.median())






结果显示为: 



中位数:

英语83.75

数学 87.40

Python 83.00

选修100.00

dtype: float64






计算各列的四分位数: 



print("四分位数:\n",dfs.quantile(0.75))






结果显示为: 



四分位数:

英语89.55

数学 91.40

Python 89.00

选修100.00

Name: 0.75, dtype: float64






计算各列及单独某数据列的众数: 



print("四科成绩的众数\n",dfs.mode()) 

print("数学成绩的众数\n",dfs["数学"].mode())






结果显示为: 



四科成绩的众数

英语数学Python选修

083.7587.483.0 100.0

1NaN NaN85.0NaN

2NaN NaN86.0NaN

3NaN NaN89.0NaN

4NaN NaN92.0NaN

数学成绩的众数

 087.4

dtype: float64






计算各列的方差: 



print("方差:\n",dfs.var())






结果显示为: 



方差:

英语211.639194

数学 17.961454

Python 90.198842

选修 48.491292

dtype: float64






计算各列的标准差: 



print("标准差:\n",dfs.std())





 
结果显示为: 



标准差:

英语14.547824

数学 4.238096

Python 9.497307

选修 6.963569

dtype: float64






计算各列的偏度和峰度: 



print("样本偏度:\n",dfs.skew())#计算各列的偏度

print("样本峰度:\n",dfs.kurt())#计算各列的峰度






结果显示为: 



样本偏度:

英语-2.200623

数学 -0.393805

Python -0.599544

选修 -2.454543

dtype: float64

样本峰度:

英语6.655603

数学0.103636

Python-0.371648

选修6.041652

dtype: float64






2. 类别型数据的描述统计

在pandas中,表示类别的特征可以用object字符串类型,比如性别是“男”还是“女”,专业是“市场营销”还是“金融”等,此时描述类别型数据的分布情况可以使用pandas库中的value_counts方法。

【例5.28】统计各个专业的人数。



import pandas as pd

df=pd.read_excel("tdata/cj.xlsx")

df["专业"].value_counts()






结果显示为: 



信息管理与信息系统14

会计学14

市场营销10

金融学 9

国际贸易 7

Name: 专业, dtype: int64






另外,pandas专门提供了一种特殊的类别类型——category类型,以便于进行统计分类。可以用astype方法将object类型的数据转换为category类型,例如: 



df["专业"].astype("category") 






3. describe方法

pandas还提供了对数据进行描述性统计分析的describe方法,既适用于Series对象,也适用于DataFrame对象。

describe方法是以列为单位进行统计分析,如果列是数值类型,进行的统计运算包括count(求列元素的个数)、mean(求列数据均值)、std(求标准差)、min(求列数据的最小值)、max(求列数据的最大值),以及求前25%的数据均值、前50%的数据均值、前75%的数据均值; 如果列是object类型或category类型,进行的统计运算包括count(求列数据的元素个数)、unique(求列数据中元素的种类)、top(求列元素中出现频率最高的元素)、freq(求列元素中出现频率最高的元素的个数)。

【例5.29】对成绩表中的所有数值列进行描述性统计分析。



import pandas as pd

df=pd.read_excel("tdata/cj.xlsx")

df.describe()






显示结果如图5.27所示。





图5.27成绩表中数值列的描述性统计结果


DataFrame的describe方法默认只对数值型列进行统计分析,如果想对object类型或category类型进行统计分析,可以设置describe方法中的include参数,或者使用Series列调用describe方法。

【例5.30】对成绩表中所有的列进行描述性统计分析。



import pandas as pd








df=pd.read_excel("tdata/cj.xlsx")

df.describe(include="all")






显示结果如图5.28所示。





图5.28成绩表中所有列的描述性统计结果


设置include参数为“all”,可以对DataFrame的全部列数据进行分析,也可以通过设置include参数为number、category或np.object指定对DataFrame中的数值类别列、类别类型列或字符串类型列的数据进行分析。

【例5.31】对成绩表中的object类型列数据进行描述性分析。



import numpy as np

import pandas as pd

df=pd.read_excel("tdata/cj.xlsx")

df.describe(include=np.object)






结果显示为: 



姓名性别

count5754

unique54 2

top 陈小恬男

freq332






对指定列的描述性统计分析可以先抽取列,再调用Series的describe方法。

【例5.32】对成绩表中的专业列进行描述性统计分析。



import pandas as pd

df=pd.read_excel("tdata/cj.xlsx")

df["专业"].describe()






结果显示为: 



count 54

unique 5

top 信息管理与信息系统

freq14

Name: 专业, dtype: object






5.3分组统计

数据的分组统计也叫分组聚合,是根据某个或某几个字段先对数据集进行分组,然后对各组应用一个函数进行聚合计算,是数据分析中的常见操作。pandas提供了一个灵活、高效的groupby方法进行分组,配合agg或者apply方法,能够实现分组统计。图5.29显示了分组统计操作的实现原理,具体包括拆分、应用和合并3个步骤。



图5.29分组统计操作的实现原理


① 拆分: 根据分组原则对某个或某几个字段进行拆分,将相同属性值的字段分成一组,例如按照3个属性值A、B、C拆分成3组。

② 应用: 对拆分后的各组应用函数进行相应的转换操作,例如应用sum函数进行各组内的求和操作。

③  合并: 将结果合并成一个数据结构,例如汇总各组求和后的结果。









5.3.1数据分组

按照一列或多列对数据进行分组(即拆分功能)是由groupby方法实现的,该方法的语法格式如下: 




DataFrame.groupby(by, axis=0, level=None, as_index=True, sort=True, group_keys=True, squeeze=False, **kwargs)







主要参数的说明如下。

(1) by: 接收list、string、mapping或generator,用于确定进行分组的依据,无默认值。

(2) axis: 接收int,表示操作的轴向,默认为0,对列进行操作。

(3) level: 接收int或者索引名,代表标签所在的级别,默认为None。

(4) as_index: 接收boolean,表示聚合后的聚合标签是否以DataFrame索引形式输出,默认为True。

(5) sort: 接收boolean,表示是否对分组依据的分组标签进行排序,默认为True。

(6) group_keys: 接收boolean,表示是否显示分组标签的名称,默认为True。

(7) squeeze: 接收boolean,表示是否在允许的情况下对返回的数据进行降维,默认为False。

by表示分组依据,是必填参数,常用的是给出列名表示的单个字符串或字符串列表。

分组后的结果是groupby对象,该对象存在于内存中,无法直接查看,如果输出,显示的是groupby对象的内存地址。

【例5.33】对成绩表进行分组操作,并查看分组对象。



import pandas as pd

df=pd.read_excel("tdata/cj.xlsx")

#按照性别列进行分组

g1=df.groupby("性别")

print(g1) 

#按照性别和专业列进行分组

g2=df.groupby(["性别","专业"]) 

print(g2)






结果显示为: 



<pandas.core.groupby.groupby.DataFrameGroupBy object at 0x118cf65c0>

<pandas.core.groupby.groupby.DataFrameGroupBy object at 0x1091fe748>






groupby对象可以调用描述性统计方法,以便于返回各组的统计值。

【例5.34】对分组对象进行求和运算。



import pandas as pd

df=pd.read_excel("tdata/cj.xlsx")

g1=df.groupby("性别")

g2=df.groupby(["性别","专业"])






对按性别分组后的对象g1做求和运算: 



g1.sum()







显示结果如图5.30所示。



对按性别和专业分组后的对象g2做求和运算: 



g2.sum()






显示结果如图5.31所示。




图5.30按性别分组求和后的结果






图5.31按性别和专业分组求和后的结果




5.3.2分组聚合

agg方法和apply方法是两个常见的聚合方法,它们既可以作用于整个DataFrame对象,也可以作用于groupby分组对象。当作用于groupby分组对象时具有分组聚合的功能。

1. agg方法

agg方法是aggregate方法的简写形式,支持对每个分组应用某个函数,不同的分组可以应用不同的函数,该方法的语法格式如下: 



DataFrame.agg(func, axis=0, *args, **kwargs) 






主要参数的说明如下。

(1) func: 接收list、dict、function,表示应用于每行/每列的函数,无默认值。

(2) axis: 接收0或1,代表操作的轴向,默认为0,对列进行操作。

agg方法中接收的分组聚合函数可以是以下之一: 

 Python内置的数学函数; 

 numpy库中提供的数学运算函数; 

 用户自定义函数。

下面以成绩表的性别分组对象g1为例了解agg方法的具体用法。

【例5.35】使用agg方法实现分组聚合运算。



import pandas as pd

df=pd.read_excel("tdata/cj.xlsx")

g1=df.groupby("性别")






对分组对象进行聚合运算,将聚合函数名以字符串形式作为agg方法的参数: 



g1.agg("sum") #求男、女分组各科成绩的总和








图5.32男、女分组各科成绩的求和结果



显示结果如图5.32所示。

对分组对象进行多个聚合运算,将多个聚合函数名以列表元素的形式作为agg方法的参数,如果使用的是内置的函数,则函数名以字符串形式给出,例如: 



g1.agg(["sum","mean","max"])#求男、女分组各科成绩的总和、均值和最大值






如果使用的是numpy库中的数据运算函数,则函数名直接给出,例如: 



g1.agg([np.sum,np.mean,np.max])






上述显示结果均如图5.33所示。





图5.33男、女分组各科成绩求总和、均值和最大值的结果


对分组对象的不同列进行不同运算,例如对某个列求均值,对另一列求最大值,可以使用字典的方法,将两个列名分别作为key,把聚合函数分别作为value,例如: 



funcDict={"英语":np.mean ,"数学":max}

g1.agg(funcDict)






显示结果如图5.34所示。

如果对分组对象的某个列求多个统计值,对另一列求一个统计值,此时只需要将字典对应key的value变为列表,列表元素为多个目标的聚合函数即可,例如: 



funcDict={"英语":[np.mean,max],"数学":max}

g1.agg(funcDict)






结果如图5.35所示。





图5.34男、女分组对英语求均值、
对数学求最大值的结果





图5.35男、女分组对英语求均值和最大值、
对数学求最大值的结果




agg方法可传入自定义的函数,例如: 



def doubleSum(data) :#求和的两倍

return data.sum() *2

g1.agg(doubleSum)






结果如图5.36所示。





图5.36男、女分组各字段两倍求和的结果


2. apply方法

apply方法支持对每个分组应用某个函数,该方法的语法格式如下: 



DataFrame.apply(func, axis=0, broadcast=False, raw=False, reduce=None, **kwds)






主要参数的说明如下。

(1) func: 接收functions,表示应用于每行/列的函数,无默认值。

(2) axis: 接收0或1,代表操作的轴向,默认为0,对列进行操作。

(3) broadcast: 接收boolean,表示是否进行广播,默认为False。

(4) raw: 接收boolean,表示是否直接将ndarray对象传递给函数,默认为False。

(5) reduce: 接收boolean或者None,表示返回值的格式,默认为None。

apply方法将当前分组后的数据一起传入,可以返回多维数据。下面仍以成绩表的性别分组对象g1为例了解apply方法的具体用法。

【例5.36】使用apply方法实现分组聚合运算。



import pandas as pd

df=pd.read_excel("tdata/cj.xlsx")

g1.apply(sum)






结果如图5.37所示。





图5.37使用apply方法进行sum聚合后的结果


从上述结果可以看出,apply将sum聚合函数作用在分组后的每一列,对于数值型的数据,计算每个元素的累计和; 对于字符串类型的数据,则将每个元素进行连接。

如果只想显示数值类型列,例如英语、数学、Python列的聚合结果,可以针对g1对象做抽取,也可以在应用apply方法进行聚合以后做抽取,例如: 



g1[["英语","数学","Python"]].apply(sum)#先抽取再聚合

g1.apply(sum) [["英语","数学","Python"]]#先聚合再抽取






上述结果显示如图5.38所示。





图5.38抽取数值型数据用apply方法聚合后的结果


另外,apply方法同样可以使用自定义函数进行聚合运算,其用法和agg方法相同。


5.4实战: 豆瓣读书Top250的数据表分析

本节以从互联网上爬取并存储在CSV文件中的豆瓣读书排行榜Top250的数据为例进行数据表的分析实战,图5.39所示为数据集的示例。





图5.39豆瓣读书排行榜Top250数据集



针对豆瓣读书排行榜的数据,大家会有一些分析需求,例如想了解排行榜上图书的平均评分、想查看最受关注的图书信息或者想了解各个出版社各有多少上榜图书等。由于数据是从互联网上爬取而来的,数据往往含有噪声,也存在不一致的情况,一般不能直接用来进行数据分析,在分析前通常需要对数据进行预处理。







5.4.1数据预处理

对爬取下来的豆瓣读书排行榜的数据集进行预处理操作,包括数据清洗、合并、拆分等。目前该数据存在出版信息列包括多特征信息、评价人数含有冗余字符等问题。

【预处理思路】: 拆分出版信息列,将拆分后的列合并到原始数据集中,删除评价人数列中的冗余字符,转换具有数据特征的列,例如评价人数列的数据类型,具体的处理步骤如下。

(1) 读取数据集。

(2) 数据概览性分析。

(3) 拆分列。

(4) 合并列。

(5) 删除冗余列。

(6) 删除冗余字符。

(7) 数据类型转换。

1.  读取数据集

首先使用pandas库读取数据集,将其保存在DataFrame的对象df中,具体代码如下: 



import pandas as pd

df=pd.read_excel("豆瓣.xlsx")#读取数据






2. 数据概览性分析

在进行数据预处理前,使用DataFrame的info和head方法对数据集df进行概览性分析,明确需要对数据集进行哪些预处理操作。



df.info() 

df.head()










图5.40数据集的概览信息


上述代码的显示结果如图5.40和图5.41所示。

通过概览性分析的显示结果可以看出,该数据集共有250个样本、8列特征,其中Unnamed列为自动保存下来的数据集的行索引列,为int64类型,评分列为float64类型,其余列为object类型,除备注列以外,其他列不存在缺失值。通过观察数据,发现需要针对该数据集进行拆分列、合并列、删除列元素的冗余字符、转换数据类型以及删除冗余列等操作。



图5.41数据集的前5条数据



3. 拆分列


出版信息列包括有关图书的多种信息,一般包括作者、译作者、出版社、出版时间和定价,这些信息之间多用“/”分隔,但具体图书的信息并不一致,大概能够覆盖3种情形,即包括5种信息的、4种信息的和3种信息的,因此需要对这些不同的情形进行分别处理。从通用性出发,将出版信息拆分为5列,如果有的图书信息缺失,则将其内容设置为空。其具体实现代码如下: 



items=[]#存放每本图书的列表

for str in df["出版信息"]:

item=[]#存放一本图书信息的列表

infos=str.split("/")#以"/"为分隔符进行拆分

if len(infos) ==5: #具备5种信息

item.append(infos[0]) 

item.append(infos[1]) 

item.append(infos[2]) 

item.append(infos[3]) 

item.append(infos[4]) 

elif(len(infos) ==4) : #具备4种信息,缺失译作者

item.append(infos[0]) 

item.append("") 

item.append(infos[1]) 

item.append(infos[2]) 

item.append(infos[3]) 

else:#具备3种信息,缺失作者、译作者

item.append("") 

item.append("") 

item.append(infos[0]) 

item.append(infos[1]) 

item.append(infos[2]) 

items.append(item)






提取列表的前5条信息: 



items[:5]






可以看到拆分后的结果为: 



[['[清] 曹雪芹 著 ', '', ' 人民文学出版社 ', ' 1996-12 ', ' 59.70元'],

['余华 ', '', ' 作家出版社 ', ' 2012-8-1 ', ' 20.00元'],

['[哥伦比亚] 加西亚·马尔克斯 ', ' 范晔 ', ' 南海出版公司 ', ' 2011-6 ', ' 39.50元'],

['[英] 乔治·奥威尔 ', ' 刘绍铭 ', ' 北京十月文艺出版社 ', ' 2010-4-1 ', ' 28.00'],

['[美国] 玛格丽特·米切尔 ', ' 李美华 ', ' 译林出版社 ', ' 2000-9 ', ' 40.00元']]






4. 合并列

将拆分出来的信息合并到原始数据集中,先将二维列表items转换成带有列标题的DataFrame数据结构: 



infoT=["作者","译作者","出版社","出版时间","定价"]

dfinfo=pd.DataFrame(items,columns=infoT)






查看dfinfo的前5条数据: 



dfinfo.head()






结果如图5.42所示。





图5.42拆分后数据的结果


因为拆分后的dfinfo和df具有相同的行索引,所以可以使用join方法将dfinfo合并到df中,并查看合并结果: 



df=df.join(dfinfo) 

df.head(2)






查看前两条记录,合并后的结果如图5.43所示。





图5.43合并列后的结果


5. 删除冗余列

在合并列后,数据集中原始的出版信息列成为冗余列,另外读取数据集时自动生成的Unnamed:0列也是冗余列,都可以使用drop方法删除。



df.drop(labels="出版信息",axis=1,inplace=True)

df.drop(labels="Unnamed: 0",axis=1,inplace=True)






注意: 在drop方法中设置了inplace参数为True,表明在df数据集中直接删除。

6. 删除冗余字符

为便于后续的数据分析,需要修改一些列,包括去掉“评价人数”列中的“人评价”3个字、去掉“定价”列中的非数字和小数点的其他字符。

(1)  “评价人数”列中所有的数据元素都统一成以“人评价”结尾,可以直接使用字符串的replace方法将冗余字符串替换为空。

(2) “定价”列中有些数据元素的后面带有“元”字符,有些没有,还有些数据元素的前面带有“CNY”字符,为便于统一操作,使用字符串的extract方法书写正则表达式提取数字和小数点字符。

其具体代码如下: 



df["评价人数"]=df["评价人数"].str.replace("人评价"," ") 

df["定价"]=df["定价"].str.extract(r"(\d+.\d+) ") 

df.head(2)






查看前两条记录,结果如图5.44所示。





图5.44删除冗余字符后的结果


7. 数据类型转换

在完成上述处理操作后,再次调用df的info方法对数据进行概览性分析。



df.info() 






显示结果如图5.45所示。





图5.45处理后的数据集概览


可以看出评价人数列和定价列均为object类型,根据分析需求,应将这两列的数据类型转换为数值类型,这里转换为float64类型。



df["评价人数"]=df["评价人数"].astype("float64") 

df["定价"]=df["定价"].astype("float64")






至此完成了数据集的清洗和转换工作,为方便今后分析使用,可以将预处理后的数据保存下来,例如将其保存为douban250.xlsx文件。



df.to_excel("douban250.xlsx")






【实战案例代码5.1】豆瓣读书数据集的预处理。



import pandas as pd

#读取数据

df=pd.read_excel("豆瓣.xlsx") 

#概览性分析

df.info()

df.head() 

#拆分列

items=[]

for str in df["出版信息"]:

item=[]

infos=str.split("/")

if len(infos)==5:

item.append(infos[0])

item.append(infos[1])

item.append(infos[2])

item.append(infos[3])

item.append(infos[4])

elif(len(infos)==4):

item.append(infos[0])

item.append("")

item.append(infos[1])

item.append(infos[2])

item.append(infos[3])

else:

item.append("")

item.append("")

item.append(infos[0])








item.append(infos[1])

item.append(infos[2])

items.append(item)

items[:5]

#合并列

infoT=["作者","译作者","出版社","出版时间","定价"]

dfinfo=pd.DataFrame(items,columns=infoT)

df=df.join(dfinfo)

df.head()

#删除冗余列

df.drop(labels="出版信息",axis=1,inplace=True)

df.drop(labels="Unnamed: 0",axis=1,inplace=True)

#删除冗余字符

df["评价人数"]=df["评价人数"].str.replace("人评价"," ")

df["定价"]=df["定价"].str.extract(r"(\d+.\d+)")

df.head()

#数据类型转换

df.info()

df["评价人数"]=df["评价人数"].astype("float64")

df["定价"]=df["定价"].astype("float64")

#存储预处理后的数据

df.to_excel("douban250.xlsx")












5.4.2数据分析

数据预处理后,如果用户想更深入地掌握排行榜上榜图书的信息,可以进行相关分析。

【分析思路】数据分析往往要围绕用户需求。针对豆瓣读书排行榜的数据,读者可能会关心评分高的图书的信息,例如用户评分最高的图书; 出版社可能会关心同行的相关信息,例如各个出版社上榜图书的平均单价等。此处模拟一些用户需求进行相关分析,具体步骤如下: 

(1) 抽取数据。

(2) 根据评分进行图书信息分析。

(3) 根据出版社进行分组分析。

1. 抽取数据

考虑满足用户需求并不需要数据集的全部列,在进行数据分析之前首先进行特征抽取,将抽取后的数据保存在数据集dfs中。



df=pd.read_excel("douban250.xlsx")

dfs=df[["书名","评分","评价人数","作者","译作者","出版社","出版时间","定价"]]






2. 根据评分进行图书信息分析

针对dfs数据集,围绕评分列进行分析处理,匹配读者的需求,例如进行以下分析: 

(1) 查看排行榜上榜图书的用户平均评分; 

(2) 查看所有高于平均分的图书的信息; 

(3) 查看用户评分最高的图书的信息; 

(4) 按照评分和评价人数降序查看排行榜上的数据。

查看排行榜图书的平均评分即为计算评分列的平均值。



print("排行榜平均评分",dfs["评分"].mean())






结果显示为: 



排行榜平均评分 8.918799999999996






查看所有高于平均分的图书的信息即为筛选出大于平均分评分的所有行。



dfs.loc[dfs["评分"]>dfs["评分"].mean() ,]






结果如图5.46所示。





图5.46高于平均分的图书的信息


查看用户评分最高的图书信息也是根据评分列对行进行筛选。



dfs.loc[dfs["评分"]==dfs["评分"].max() ,]






结果如图5.47所示。





图5.47用户评分最高的图书信息


按照评分和评价人数降序查看排行榜上的图书数据,同时为了让排序后的数据索引有序,使用reset_index方法重新设置索引。



dfs.sort_values(by=["评分","评价人数"],ascending=False) .reset_index() 






结果如图5.48所示。





图5.48排序后的图书信息


3. 根据出版社进行分组分析

针对dfs数据集,按出版社进行分组后进行分析处理,匹配出版社的需求,例如进行以下分析: 

(1) 查看各个出版社上榜图书的情况; 

(2) 了解各个出版社出版图书的平均单价; 

(3) 总体查看各个出版社上榜图书的数量和平均单价。

查看各个出版社各有多少上榜图书,可以在分组后使用count方法统计每组中图书的数量,这里显示上榜图书数量最多的前十个出版社的信息。



dfs.groupby("出版社") ["书名"].count() .sort_values(ascending=False) [:10]






结果如图5.49所示。

了解各出版社上榜图书的平均单价,先按照出版社分组,再抽取定价列,对分组后的定价求均值,为便于查看,此处对分组聚合后的结果进行降序排序。



dfs.groupby("出版社") ["定价"].mean() .sort_values(ascending=False) 






结果如图5.50所示。




图5.49上榜图书数量最多的前十个出版社的信息






图5.50各出版社上榜图书的平均单价




查看各个出版社上榜图书的数量和平均单价,采用分组后对书名和定价做聚合运算的方法来实现。



dfs.groupby("出版社") .agg({"书名":"count","定价":"mean"}) .sort_values(by="书名",ascending=False)






显示结果如图5.51所示。




图5.51查看各个出版社上榜图书的数量和平均单价



【实战案例代码5.2】豆瓣读书排行榜的数据分析。



import pandas as pd

df=pd.read_excel("douban250.xlsx")

#抽取列

dfs=df[["书名","评分","评价人数","作者","译作者","出版社","出版时间","定价"]]

#了解排行榜图书的平均评分

print("排行榜平均评分",df["评分"].mean())

#查看所有高于平均分的图书的信息

df.loc[df["评分"]>df["评分"].mean(),]

#查看最受关注的图书的信息

df.loc[df["评分"]==df["评分"].max(),]

#根据评分和评价人数对排行榜的数据进行重新排序

df.sort_values(by=["评分","评价人数"],ascending=False).reset_index()

#查看各个出版社各有多少上榜图书

df.groupby("出版社")["书名"].count().sort_values(ascending=False)[:10]

#查看各个出版社所出版图书的平均单价

df.groupby("出版社")["定价"].mean().sort_values(ascending=False)

#查看各个出版社的图书的数量和平均单价

df.groupby("出版社").agg({"书名":"count","定价":"mean"}).sort_values(by="书名",ascending=False)






本章小结

本章首先介绍了如何使用pandas库进行数据概览及预处理,包括数据概览分析的属性和方法、数据清洗的方法、抽取与合并的方法、数据的增/删/改和数据类型转换的方法; 然后介绍了数据描述性统计分析的方法,包括如何进行数据的排序和排名、常用的数据计算方法; 在介绍完数据的分组统计(包括数据分组和分组聚合运算)后,以豆瓣读书Top250的数据集为例进行了数据表分析,包括数据预处理和模拟用户需求进行的相关数据分析。

习题5