目录

  • 1 第一章 数据仓库概述
    • 1.1 授课安排
    • 1.2 数据仓库及其历史
      • 1.2.1 数据仓库的概念
        • 1.2.1.1 本节视频
      • 1.2.2 数据仓库特征
        • 1.2.2.1 本节视频
    • 1.3 数据仓库系统结构
      • 1.3.1 数据仓库系统的组成
        • 1.3.1.1 本节视频
      • 1.3.2 ETL
        • 1.3.2.1 本节视频
      • 1.3.3 数据仓库和数据集市的关系
      • 1.3.4 元数据及其管理
      • 1.3.5 数据集市和元数据管理视频
    • 1.4 数据仓库系统开发工具
    • 1.5 数据仓库与操作型数据库的关系
      • 1.5.1 本节视频内容
  • 2 第二章 数据仓库设计
    • 2.1 授课安排
    • 2.2 数据仓库设计概述
    • 2.3 数据仓库的规划和需求分析
    • 2.4 数据仓库的建模
    • 2.5 数据仓库的物理模型设计
    • 2.6 数据仓库的部署和维护
  • 3 第三章 OLAP技术
    • 3.1 授课安排
    • 3.2 OLAP概述
    • 3.3 OLAP的多维数据模型
    • 3.4 OLAP实现
  • 4 第四章 数据
    • 4.1 课程资料
  • 5 第五章 数据挖掘概述
    • 5.1 授课安排
    • 5.2 什么是数据挖掘?
    • 5.3 数据挖掘系统
    • 5.4 视频
    • 5.5 数据挖掘过程
  • 6 第六章 关联分析
    • 6.1 授课安排
    • 6.2 关联分析概念
    • 6.3 Apriori算法
    • 6.4 FP-growth树
    • 6.5 多层关联规则
    • 6.6 【扩充知识】机器学习——关联规则——支持度(support)、置信度(confidence)、提升度(Lift)
  • 7 第七章 序列模式挖掘
    • 7.1 序列模式挖掘概述
    • 7.2 AprioriAll算法
    • 7.3 AprioriSome算法
    • 7.4 FreeSpan算法
    • 7.5 PrefixSpan算法
  • 8 第八章 聚类分析
    • 8.1 聚类概述
  • 9 分类算法
    • 9.1 课件
  • 10 实验1 python基础
    • 10.1 讲解文本内容
    • 10.2 课程PDF
    • 10.3 实验代码
    • 10.4 实验报告封皮
  • 11 实验2-python
    • 11.1 讲解文本内容
    • 11.2 实验代码
    • 11.3 实验报告封面
  • 12 实验3--python
    • 12.1 讲解文本内容
    • 12.2 实验代码
    • 12.3 实验报告封面
  • 13 实验4--python
    • 13.1 讲解文本内容
    • 13.2 21.1实验代码
    • 13.3 实验内容2
    • 13.4 实验内容3
    • 13.5 实验报告封面
  • 14 实验5--python
    • 14.1 文本内容-NumPy模块
    • 14.2 第三方可视化数据分析图表
    • 14.3 数据
    • 14.4 思考题
    • 14.5 实验报告封面
  • 15 实验6--python
    • 15.1 实验 NumPy矩阵的基本操作
    • 15.2 实验 关联规则算法
    • 15.3 实验 商品零售购物篮分析
    • 15.4 实验报告封面
  • 16 实验7--python
    • 16.1 实验1 用关联规则分析方法推荐电影
    • 16.2 实验2 FP-growth算法
    • 16.3 实验3 教育平台的线上课程推荐策略
    • 16.4 实验报告封面
  • 17 实验8-python
    • 17.1 实验1 购物车分析
    • 17.2 实验2 基于关联规则的文本分析
  • 18 实验9--python
    • 18.1 实验1 聚类分析
    • 18.2 实验2 航空公司客户价值分析
    • 18.3 实验3 运输车辆安全驾驶行为分析
    • 18.4 实验报告封面
讲解文本内容

一、数据计算

Pandas提供了大量的数据计算函数,可以实现求和、求均值、求最大值、求最小值、求中位数、求众数、求方差、标准差等,从而使得数据统计变得简单、高效。

1.1  求和(sum函数)

在Python中通过调用DataFrame对象的sum函数实现行/列数据的求和运算,语法如下:

DataFrame.sum([axis,skipna,level,…])

参数说明:

■ axix:axis=1表示按行相加;axis=0表示按列相加,默认按列相加。

■ skipna:skipna=1,表示NaN值自动转换为0;skipna=0,表示NaN值不自动转换,默认NaN值自动转换为0。

说明:NaN表示非数值、非数字值。在进行数据处理、数据计算时,Pandas会为缺少的值自动分配NaN值。

■ level:表示索引层级。

■ 返回值:返回Series对象或DataFrame对象,一组含有行/列小计的数据。

快速示例01  计算“语文”“数学”“英语”的总成绩

首先,创建一组DataFrame类型的数据,包括“语文”“数学”和“英语”三科的成绩,如图 1所示。

图 1  DataFrame数据

程序代码如下: 

01 import pandas as pd

02 data = [[110,105,99],[105,88,115],[109,120,130]]

03 index = [1,2,3]

04 columns = ['语文','数学','英语']

05 df = pd.DataFrame(data=data, index=index, columns=columns)

下面使用sum函数计算三科的总成绩,代码如下:

df['总成绩']=df.sum(axis=1)

运行程序,结果如图 2所示。

图 2  sum函数计算三科的总成绩

1.2  求均值(mean函数)

在Python中,通过调用DataFrame对象的mean函数实现行/列数据平均值运算,语法如下:

DataFrame.mean([axis,skipna,level,…])

参数说明:

■ axix:axis=1,表示按行相加;axis=0,表示按列相加,默认按列相加。

■ skipna:skipna=1,表示NaN值自动转换为0;skipna=0,表示NaN值不自动转换,默认NaN值自动转换为0。

■ level:表示索引层级。

■ 返回值:返回Series对象或DataFrame对象,行/列的平均值数据。

快速示例02  计算“语文”“数学”“英语”各科的平均分

计算“语文”“数学”“英语”各科成绩的平均值,程序代码如下:

01 import pandas as pd

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

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

04 data = [[110,105,99],[105,88,115],[109,120,130],[112,115]]

05 index = [1,2,3,4]

06 columns = ['语文','数学','英语']

07 df = pd.DataFrame(data=data, index=index, columns=columns)

08 new=df.mean()

09 #增加一行数据(“语文”“数学”“英语”的平均值,忽略索引)

10 df=df.append(new,ignore_index=True)

11 print(df)

运行程序结果如图 3所示。

图 3  计算三科成绩的平均值

从运行结果得知:语文平均分为109,数学平均分为107,英语平均分为114.666667。

1.3  求最大值(max函数)

在Python中,通过调用DataFrame对象的max函数实现行/列数据最大值运算,语法如下:

DataFrame.max([axis,skipna,level,…])

参数说明:

■ axix:axis=1,表示按行相加;axis=0,表示按列相加,默认按列相加。

■ skipna:skipna=1,表示NaN值自动转换为0;skipna=0,表示NaN值不自动转换,默认NaN值自动转换为0。

■ level:表示索引层级。

■ 返回值:返回Series对象或DataFrame对象,行/列的最大值数据。

快速示例03  计算“语文”“数学”“英语”各科的最高分

计算“语文”“数学”“英语”各科成绩的最高分,程序代码如下:

01 import pandas as pd

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

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

04 data = [[110,105,99],[105,88,115],[109,120,130],[112,115]]

05 index = [1,2,3,4]

06 columns = ['语文','数学','英语']

07 df = pd.DataFrame(data=data, index=index, columns=columns)

08 new=df.max()

09 #增加一行数据(“语文”“数学”“英语”的最大值,忽略索引)

10 df=df.append(new,ignore_index=True)

11 print(df)

运行程序,结果如图 4所示。

图 4  计算三科成绩的最高分

从运行结果得知:语文最高分为112分,数学最高分为120分,英语最高分为130分。

1.4  求最小值(min函数)

在Python中,通过调用DataFrame对象的min函数实现行/列的数据最小值运算,语法如下:

DataFrame.min([axis,skipna,level,…])

参数说明:

■ axix:axis=1,表示按行相加;axis=0,表示按列相加,默认按列相加。

■ skipna:skipna=1,表示NaN值自动转换为0;skipna=0,表示NaN值不自动转换,默认NaN值自动转换为0。

■ level:表示索引层级。

■ 返回值:返回Series对象或DataFrame对象,行/列的最小值数据。

快速示例04  计算“语文”“数学”“英语”各科的最低分

计算“语文”“数学”“英语”各科成绩的最低分,程序代码如下:

01 import pandas as pd

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

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

04 data = [[110,105,99],[105,88,115],[109,120,130],[112,115]]

05 index = [1,2,3,4]

06 columns = ['语文','数学','英语']

07 df = pd.DataFrame(data=data, index=index, columns=columns)

08 new=df.min()

09 #增加一行数据(语文、数学和英语的最小值,忽略索引)

10 df=df.append(new,ignore_index=True)

11 print(df)

运行程序,结果如图 5所示。

图 5  计算三科成绩的最低分

从运行结果得知:语文最低分为105分,数学最低分为88分,英语最低分为99分。

1.5  求中位数(median函数)

中位数又称中值,是统计学的专有名词,是指按顺序排列的一组数据中位于中间位置的数,其不受异常值的影响。例如,年龄23、45、35、25、22、34、28这7个数,中位数就是按照大小排序后位于中间的数字,即28,而年龄23、45、35、25、22、34、28、27这8个数,中位数则是排序后中间两个数的平均值,即27.5。在Python中,直接调用DataFrame对象的median函数就可以轻松实现中位数的运算,语法如下:

DataFrame.median(axis=None,skipna=None,level=None,numeric_only=None,**kwargs)

参数说明:

■ axis:axis=1表示行,axis=0表示列,默认值为None(无)。

■ skipna:布尔型,表示计算结果是否排除了NaN/Null值,默认值为True。

■ level:表示索引层级,默认无。

■ numeric_only:仅数字,布尔型,默认无。

■ **kwargs:要传递给函数的附加关键字参数。

■ 返回值:返回Series对象或DataFrame对象。

快速示例05  计算学生各科成绩的中位数(1)

下面给出一组数据(3条记录),然后使用median函数计算“语文”“数学”和“英语”各科成绩的中位数,程序代码如下:

01 import pandas as pd

02 data = [[110,120,110],[130,130,130],[130,120,130]]

03 columns = ['语文','数学','英语']

04 df = pd.DataFrame(data=data,columns=columns)

05 print(df.median())

运行程序,输出结果如下:

语文    130.0

数学    120.0

英语    130.0

快速示例06  计算学生各科成绩的中位数(2)

下面再给出一组数据(4条记录),同样使用median函数计算“语文”“数学”和“英语”各科成绩的中位数,程序代码如下:

01 import pandas as pd

02 data = [[110,120,110],[130,130,130],[130,120,130],[113,123,101]]

03 columns = ['语文','数学','英语']

04 df = pd.DataFrame(data=data,columns=columns)

05 print(df.median())

运行程序,输出结果如下:

语文    121.5

数学    121.5

英语    120.0

1.6  求众数(mode函数)

什么是众数?众数的众字有多的意思。顾名思义,众数就是一组数据中出现最多的数,它代表了数据的一般水平。

在Python中,通过调用DataFrame对象的mode函数可以实现众数运算,语法如下:

DataFrame.mode(axis=0,numeric_only=False,dropna=True)

参数说明:

■ axis:axis=1表示行;axis=0表示列,默认值为0。

■ numeric_only:仅数字,布尔型,默认为False。如果为True,则仅适用于数字列。

■ dropna:是否删除缺失值,布尔型,默认为True。

■ 返回值:返回Series对象或DataFrame对象。

首先来看一组原始数据,如图 6所示。

图 6  原始数据

快速示例07  计算学生各科成绩的众数

计算“语文”“数学”“英语”三科成绩的众数、每一行的众数和“数学”的众数,程序代码如下:

01 import pandas as pd

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

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

04 data = [[110,120,110],[130,130,130],[130,120,130]]

05 columns = ['语文','数学','英语']

06 df = pd.DataFrame(data=data,columns=columns)

07 print(df.mode())       #三科成绩的众数

08 print(df.mode(axis=1))    #每一行的众数

09 print(df['数学'].mode())   #“数学”的众数

三科成绩的众数:

   语文  数学  英语

0   130   120   130

每一行的众数:

0  110

1  130

2  130

“数学”的众数:

0    120

1.7  求方差(var函数)

方差主要用于衡量一组数据的离散程度,即各组数据与它们的平均数的差的平方,那么用这个结果来衡量这组数据的波动大小,并把它叫作这组数据的方差,方差越小越稳定。通过方差可以了解一个问题的波动性。下面简单介绍下方差的意义,相信通过一个简单的举例读者就会了解。

例如,某校两名同学的物理成绩都很优秀,而参加物理竞赛的名额只有一个,那么选谁去获得名次的机率更大呢?于是根据历史数据计算出了两名同学的平均成绩,但结果是实力相当,平均成绩都是107.6分,怎么办呢?这时让方差决定,看看谁的成绩更稳定。首先汇总物理成绩,如图 7所示。

图 7  物理成绩

通过方差对比两名同学物理成绩的波动,如图 8所示。

图 8  方差

接着来看下总体波动(方差和):小黑的数据是73.2;小白的数据是949.2,很明显“小黑”的物理成绩波动较小,发挥更稳定,所以应该选“小黑”参加物理竞赛。

以上举例就是方差的意义。在大数据时代,它能够帮助我们解决很多身边的问题、协助我们作出合理的决策。

在Python中,通过调用DataFrame对象的var函数可以实现方差运算,语法如下:

DataFrame.var(axis=None,skipna=None,level=None,ddof=1,numeric_only=None,**kwargs)

参数说明:

■ axis:axis=1表示行;axis=0表示列,默认值为None(无)。

■ skipna:布尔型,表示计算结果是否排除NaN/Null值,默认值为True。

■ level:表示索引层级,默认值为None(无)。

■ ddof:整型,默认为1。自由度,计算中使用的除数是N-ddof,其中N表示元素的数量。

■ numeric_only:仅数字,布尔型,默认无。

■ **kwargs:要传递给函数的附加关键字参数。

■ 返回值:返回Series对象或DataFrame对象。

快速示例08  通过方差判断谁的物理成绩更稳定

计算“小黑”和“小白”物理成绩的方差来判定谁的成绩更稳定,程序代码如下:

01 import pandas as pd

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

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

04 data = [[110,113,102,105,108],[118,98,119,85,118]]

05 index=['小黑','小白']

06 columns = ['物理1','物理2','物理3','物理4','物理5']

07 df = pd.DataFrame(data=data,index=index,columns=columns)

08 print(df.var(axis=1))

运行程序,输出结果如下:

小黑     18.3

小白    237.3

从运行结果得知:“小黑”的物理成绩波动较小,发挥更稳定。这里需要注意的是Pandas中计算的方差为无偏样本方差(即方差和/样本数-1),而NumPy中计算的方差就是样本方差本身(即方差和/样本数)。

1.8  标准差(数据标准化std函数)

标准差又称均方差,是方差的平方根,用来表示数据的离散程度。

在Python中,通过调用DataFrame对象的std函数求标准差,语法如下:

DataFrame.std(axis=None,skipna=None,level=None,ddof=1,numeric_only=None,**kwargs)

std函数的参数用法与var函数一样,这里不再赘述。

快速示例09  计算各科成绩的标准差

使用std函数计算标准差,程序代码如下:

01 import pandas as pd

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

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

04 data = [[110,120,110],[130,130,130],[130,120,130]]

05 columns = ['语文','数学','英语']

06 df = pd.DataFrame(data=data,columns=columns)

07 print(df.std())

运行程序,输出结果如下:

语文    11.547005

数学     5.773503

英语    11.547005

1.9  求分位数(quantile函数)

分位数也称分位点,它以概率为依据将数据分割为几个等份,常用的有中位数(即二分位数)、四分位数、百分位数等。分位数是数据分析中常用的一个统计量,经过抽样得到一个样本值。例如,经常会听老师说:“这次简单的考试竟然有20%的同学不及格!”,那么这句话就体现了分位数的应用。在Python中,通过调用DataFrame对象的quantile函数求分位数,语法如下:

DataFrame.std(q=0.5,axis=0,numeric_only=True, interpolation='linear')

参数说明:

■ q:浮点型或数组,默认为0.5(50%分位数),其值在0~1之间。

■ axis:axis=1表示行;axis=0表示列,默认值为0。

■ numeric_only:仅数字,布尔型,默认值为True。

■ interpolation:内插值,可选参数,用于指定要使用的插值方法,当期望的分位数位于两个数据点i和j之间时:

● 线性:i+(j-i)×分数,其中分数是指数被i和j包围的小数部分。

● 较低:i。

● 较高:j。

● 最近:i或j都以最近者为准。

● 中点:(i + j) / 2。

■ 返回值:返回Series对象或DataFrame对象。

快速示例10  通过分位数确定被淘汰的35%的学生

以学生成绩为例,数学成绩分别为120、89、98、78、65、102、112、56、79、45的10名同学,现根据分数淘汰35%的学生,该如何处理?首先使用quantile函数计算35%的分位数,然后将学生成绩与分位数比较,筛选小于或等于分位数的学生,程序代码如下:

01 import pandas as pd

02 #创建DataFrame数据(数学成绩)

03 data = [120,89,98,78,65,102,112,56,79,45]

04 columns = ['数学']

05 df = pd.DataFrame(data=data,columns=columns)

06 #计算35%的分位数

07 x=df['数学'].quantile(0.35)

08 #输出淘汰学生

09 print(df[df['数学']<=x])

运行程序,输出结果如下:

   数学

3    78

4    65

7    56

9    45

从运行结果得知:即将被淘汰的学生有4名,分数分别为78、65、56和45。

快速示例11  计算日期、时间和时间增量数据的分位数

如果参数numeric_only=False,将计算日期、时间和时间增量数据的分位数,程序代码如下:

01 import pandas as pd

02 df = pd.DataFrame({'A': [1, 2],

03  'B': [pd.Timestamp('2019'),

04 pd.Timestamp('2020')],

05  'C': [pd.Timedelta('1 days'),

06 pd.Timedelta('2 days')]})

07 print(df.quantile(0.5, numeric_only=False))

运行程序,输出结果如下:

A    1.5

B    2019-07-02 12:00:00

C     1 days 12:00:00

Name: 0.5, dtype: object

二、 数据格式化

当我们在进行数据处理时,尤其是在数据计算中应用求均值(mean函数)后,发现结果中的小数位数增加了许多。此时就需要对数据进行格式化,以增加数据的可读性。例如,保留小数点位数、百分号、千位分隔符等。首先来看一组数据,如图 9所示。

图 9  原始数据

2.1  设置小数位数

设置小数位数,主要使用DataFrame对象中的round函数,该函数可以实现四舍五入,而它的decimals参数则用于设置保留小数的位数,设置后的数据类型不会发生变化,依然是浮点型。语法如下:

DataFrame.round(decimals=0, *args, **kwargs)

■ decimals:每一列四舍五入的小数位数,整型、字典或Series对象。如果是整数,则将每一列四舍五入到相同的位置;否则,将dict和Series舍入到可变数目的位置。如果小数是类似于字典的,那么列名应该在键中;如果小数是级数,列名应该在索引中。没有包含在小数中的任何列都将保持原样,非输入列的小数元素将被忽略。

■ *args:附加的关键字参数。

■ **kwargs:附加的关键字参数。

■ 返回值:返回DataFrame对象。

快速示例12  四舍五入保留指定的小数位数

使用round函数四舍五入保留小数位数,程序代码如下:

01 import pandas as pd

02 import numpy as np

03 df = pd.DataFrame(np.random.random([5, 5]),

04 columns=['A1', 'A2', 'A3','A4','A5'])

05 print(df)

06 print(df.round(2))                 #保留小数点后两位

07 print(df.round({'A1': 1, 'A2': 2}))   #A1列保留小数点的后一位、A2列保留小数点的后两位

08 s1 = pd.Series([1, 0, 2], index=['A1', 'A2', 'A3'])  

09 print(df.round(s1))                #设置Series对象的小数位数

运行程序,输出结果如下:

     A1    A2    A3    A4    A5

0  0.79  0.87  0.16  0.36  0.96

1  0.94  0.59  0.94  0.16  0.74

2  0.78  0.36  0.62  0.17  0.66

3  0.44  0.98  0.54  0.36  0.17

4  0.19  0.02  0.05  0.65  0.53

    A1    A2        A3        A4        A5

0  0.8  0.87  0.157699  0.361039  0.963076

1  0.9  0.59  0.942715  0.160099  0.735882

2  0.8  0.36  0.620662  0.170067  0.657948

3  0.4  0.98  0.535800  0.361387  0.165886

4  0.2  0.02  0.047484  0.654962  0.526113

    A1   A2    A3        A4        A5

0  0.8  1.0  0.16  0.361039  0.963076

1  0.9  1.0  0.94  0.160099  0.735882

2  0.8  0.0  0.62  0.170067  0.657948

3  0.4  1.0  0.54  0.361387  0.165886

4  0.2  0.0  0.05  0.654962  0.526113

当然,保留小数位数也可以用自定义函数,例如,将DataFrame对象中的各个浮点值保留两位小数,关键代码如下:

df.applymap(lambda x: '%.2f'%x)

注意:经过自定义函数处理后的数据将不再是浮点型而是对象型,如果后续计算则首先应该将数据类型进行转换。

2.2  设置百分比

在数据分析过程中,有时需要百分比数据。那么,利用自定义函数将数据进行格式处理,处理后的数据就可以从浮点型转换成带指定小数位数的百分比数据,主要使用apply函数与format函数。

快速示例13  将指定数据格式化为百分比数据

将A1列的数据格式化为百分比数据,程序代码如下:

01 import pandas as pd

02 import numpy as np

03 df = pd.DataFrame(np.random.random([5, 5]),

04 columns=['A1', 'A2', 'A3','A4','A5'])

05 df['百分比']=df['A1'].apply(lambda x: format(x,'.0%'))%')) #整列保留0位小数

06 df['百分比']=df['A1'].apply(lambda x: format(x,'.2%'))   #整列保留两位小数

07 df['百分比']=df['A1'].map(lambda x:'{:.0%}'.format(x)) #使用map函数整列保留0位小数

运行程序,输出结果如下:

         A1        A2        A3        A4        A5     百分比

0  0.379951  0.538359  0.378131  0.361101  0.835820  38%

1  0.073634  0.147796  0.573301  0.290091  0.472903   7%

2  0.752638  0.634261  0.607307  0.582695  0.001692  75%

3  0.371832  0.872433  0.620207  0.942345  0.866435  37%

4  0.869684  0.341358  0.370799  0.724845  0.257434  87%

         A1        A2        A3        A4        A5     百分比

0  0.379951  0.538359  0.378131  0.361101  0.835820  38.00%

1  0.073634  0.147796  0.573301  0.290091  0.472903   7.36%

2  0.752638  0.634261  0.607307  0.582695  0.001692  75.26%

3  0.371832  0.872433  0.620207  0.942345  0.866435  37.18%

4  0.869684  0.341358  0.370799  0.724845  0.257434  86.97%

         A1        A2        A3        A4        A5     百分比

0  0.379951  0.538359  0.378131  0.361101  0.835820  38%

1  0.073634  0.147796  0.573301  0.290091  0.472903   7%

2  0.752638  0.634261  0.607307  0.582695  0.001692  75%

3  0.371832  0.872433  0.620207  0.942345  0.866435  37%

4  0.869684  0.341358  0.370799  0.724845  0.257434  87%

2.3  设置千位分隔符

由于业务需要,有时需要将数据格式化为带千位分隔符的数据。那么,处理后的数据将不再是浮点型而是对象型。

快速示例14  将金额格式化为带千位分隔符的数据

将图书销售码洋格式化为带千位分隔符的数据,程序代码如下:

01 import pandas as pd

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

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

04 data = [['零基础学Python','1月',49768889],['零基础学Python','2月',11777775],['零基础学

     Python','3月',13799990]]

05 columns = ['图书','月份','码洋']

06 df = pd.DataFrame(data=data, columns=columns)

07 df['码洋']=df['码洋'].apply(lambda x:format(int(x),','))

08 print(df)

运行程序,输出结果如下:

             图书 月份        码洋

0  零基础学Python  1月  49,768,889

1  零基础学Python  2月  11,777,775

2  零基础学Python  3月  13,799,990

注意:设置千位分隔符后,对于程序来说,这些数据将不再是数值型,而是数字和逗号组成的字符串。如果由于程序需要再变成数值型就会很麻烦,因此设置千位分隔符要慎重。

三、数据分组统计

本节主要介绍数组分组统计函数groupby的各种应用。

3.1  分组统计groupby函数

对数据进行分组统计,主要使用DataFrame对象的groupby函数,其功能如下:

(1)根据给定的条件将数据拆分成组。

(2)每个组都可以独立应用函数(如求和函数(sum)、求平均值函数(mean)等)。

(3)将结果合并到一个数据结构中。

groupby函数用于将数据按照一列或多列进行分组,一般与计算函数结合使用,实现数据的分组统计,语法如下:

DataFrame.groupby(by=None,axis=0,level=None,as_index=True,sort=True,group_keys=True,

squeeze=False,observed=False)

参数说明:

■ by:映射、字典或Series对象、数组、标签或标签列表。如果by是一个函数,则对象索引的每个值调用它。如果传递了一个字典或Series对象,则使用该字典或Series对象值来确定组。如果传递了数组ndarray,则按原样使用这些值来确定组。

■ axis:axis=1表示行,axis=0表示列,默认值为0。

■ level:表示索引层级,默认无。

■ as_index:布尔型,默认为True,返回以组标签为索引的对象。

■ sort:对组进行排序,布尔型,默认为True。

■ group_keys:布尔型,默认为True,调用apply函数时,将分组的键添加到索引以标识片段。

■ squeeze:布尔型,默认为False,如果可能,减少返回类型的维度,否则返回一致类型。

■ observed:当以石斑鱼为分类时,才会使用该参数。如果参数值为True,则仅显示分类石斑鱼的观测值。如果为False,则显示分类石斑鱼的所有值。

■ 返回值:DataFrameGroupBy,返回包含有关组的信息的groupby对象。

3.1.1  按照一列分组统计

快速示例15  根据“一级分类”统计订单数据

按照图书“一级分类”对订单数据进行分组统计求和,程序代码如下:

01 import pandas as pd  #导入pandas模块

02 #设置数据显示的列数和宽度

03 pd.set_option('display.max_columns',500)

04 pd.set_option('display.width',1000)

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

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

07 df=pd.read_csv('JD.csv',encoding='gbk')

08 #抽取数据

09 df1=df[['一级分类','7天点击量','订单预定']]

10 print(df1.groupby('一级分类').sum()) #分组统计求和

运行程序,输出结果如图 10所示。

图 10  按照一列分组统计

3.1.2  按照多列分组统计

多列分组统计,以列表形式指定列。

快速示例16  根据“两级分类”统计订单数据

按照图书的“一级分类”和“二级分类”对订单数据进行分组统计求和,关键代码如下:

01 #抽取数据

02 df1=df[['一级分类','二级分类','7天点击量','订单预定']]

03 print(df1.groupby(['一级分类','二级分类']).sum())#分组统计求和

运行程序,输出结果如图 11所示。

图 11  按照多列分组统计

3.1.3  分组并按指定列进行数据计算

前面介绍的分组统计是按照所有列进行汇总计算的,那么如何按照指定列汇总计算呢?

快速示例17  统计各编程语言的7天点击量

统计各编程语言的7天点击量,首先按“二级分类”分组,然后抽取“7天点击量”列并对该列进行求和运算,关键代码如下:

print(df1.groupby('二级分类')['7天点击量'].sum())

运行程序,输出结果如图 12所示。

图 12  分组并按指定列进行数据计算

3.2  对分组数据进行迭代

通过for循环对分组统计数据进行迭代(遍历分组数据)。

快速示例18  迭代“一级分类”的订单数据

按照“一级分类”分组,并输出每一分类中的订单数据,关键代码如下:

01 #抽取数据

02 df1=df[['一级分类','7天点击量','订单预定']]

03 for name, group in df1.groupby('一级分类'):

04 print(name)

05 print(group)

运行程序,输出结果如图 13所示。

图 13  对分组数据进行迭代

上述代码中的name是groupby中“一级分类”的值;group是分组后的数据。如果groupby对多列进行分组,那么需要在for循环中指定多列。

快速示例19  迭代“两级分类”的订单数据

迭代“一级分类”和“二级分类”的订单数据,关键代码如下:

01 #抽取数据

02 df2=df[['一级分类','二级分类','7天点击量','订单预定']]

03 for (key1,key2),group in df2.groupby(['一级分类','二级分类']):

04 print(key1,key2)

05 print(group)

3.3  对分组的某列或多列使用聚合函数(agg函数)

Python也可以实现像SQL中的分组聚合运算操作,主要通过groupby函数与agg函数实现。

快速示例20  对分组统计结果使用聚合函数

按“一级分类”分组统计“7天点击量”和“订单预定”的平均值总和,关键代码如下:

print(df1.groupby('一级分类').agg(['mean','sum']))

运行程序,输出结果如图 14所示。

图 14  分组统计“7天点击量”和“订单预定”的平均值与总和

快速示例21  针对不同的列使用不同的聚合函数

在上述示例中,还可以针对不同的列使用不同的聚合函数。例如,按“一级分类”分组统计“7天点击量”的平均值和总和以及“订单预定”总和,关键代码如下:

print(df1.groupby('一级分类').agg({'7天点击量':['mean','sum'], '订单预定':['sum']}))

运行程序,输出结果如图 15所示。

图 15  分组统计“7天点击量”的平均值、总和以及“订单预定”总和

快速示例22  通过自定义函数实现分组统计

通过自定义函数也可以实现数据分组统计。例如,统计在1月份销售数据中,购买次数最多的产品,关键代码如下:

01 df=pd.read_excel('1月.xlsx')  #导入Excel文件

02 #x是“宝贝标题”对应的列

03 #value_counts函数用于Series对象中的每个值进行计数并且排序

04 max1 = lambda x: x.value_counts(dropna=False).index[0]

05 df1=df.agg({'宝贝标题': [max1],

06  '数量': ['sum', 'mean'],

07  '买家实际支付金额': ['sum', 'mean']})

08 print(df1)

运行程序,输出结果如图 16所示。

图 16  统计购买次数最多的产品

从运行结果得知:《零基础学Python》是用户购买次数最多的产品。

技巧:在输出结果中,lambda函数名称<lambda>被输出来,看上去不是很美观,那么如何去掉它?方法是使用__name__方法修改函数名称,关键代码如下:

max.__name__ = "购买次数最多"

运行程序,输出结果如图 17所示。

 

图 17  使用__name__方法修改函数名称

3.4  通过字典和Series对象进行分组统计

3.4.1  通过字典进行分组统计

首先创建字典建立的对应关系,然后将字典传递给groupby函数,从而实现数据分组统计。

快速示例23  通过字典分组统计“北上广”的销量

统计各地区的销量,业务要求:将“北京”“上海”和“广州”三个一线城市放在一起进行统计,那么首先创建一个字典将“上海出库销量”“北京出库销量”和“广州出库销量”都对应“北上广”,然后使用groupby方法进行分组统计,关键代码如下:

01 df=pd.read_csv('JD.csv',encoding='gbk')  #导入.csv文件

02 df=df.set_index(['商品名称'])

03 dict1={'北京出库销量':'北上广','上海出库销量':'北上广',

04  '广州出库销量':'北上广','成都出库销量':'成都',

05 '武汉出库销量':'武汉','西安出库销量':'西安'}

06 df1=df.groupby(dict1,axis=1).sum()

07 print(df)

运行程序,输出结果如图 18所示。

图 18  通过字典进行分组统计

3.4.2  通过Series对象进行分组统计

通过Series对象进行分组统计时,它与字典的方法类似。

快速示例24  通过Series对象分组统计“北上广”销量

首先,创建一个Series对象,关键代码如下:

01 data={'北京出库销量':'北上广','上海出库销量':'北上广',

02  '广州出库销量':'北上广','成都出库销量':'成都',

03 '武汉出库销量':'武汉','西安出库销量':'西安',}

04 s1=pd.Series(data)

05 print(s1)

运行程序,输出结果如图 19所示。

图 19  通过Series对象进行分组统计

然后,将Series对象传递给groupby函数实现数据分组统计,关键代码如下:

01 df1=df.groupby(s1,axis=1).sum()

02 print(df1)

运行程序,输出结果如图 20所示。

图 20  分组统计结果

四、数据移位

什么是数据移位?例如,分析数据时需要上一条数据怎么办?当然是移动至上一条,从而得到该条数据,这就是数据移位。在Pandas中,使用shift方法可以获得上一条数据,该方法将返回向下移位后的结果,从而得到上一条数据。例如,获取某个学生上一次的英语成绩,如图 21所示。

图 21  获取学生上一次的英语成绩

shift方法是一个非常有用的方法,用于数据位移与其他方法结合,能实现到很多难以想象的功能,语法格式如下:

DataFrame.shift(periods=1, freq=None, axis=0)

参数说明:

■ periods:表示移动的幅度,可以是正数;也可以是负数,默认值是1,1表示移动一次,注意这里移动的都是数据,而索引是不移动的,移动之后没有对应值的,就被赋值为NaN。

■ freq:可选参数,默认值为None,只适用于时间序列,如果这个参数存在,那么会按照参数值来移动时间索引,而数据值不会发生变化。

■ axis:axis=1表示列,axis=0表示行,默认值为0。

快速示例25  统计学生英语周测成绩的升降情况

使用shift方法统计学生每周英语测试成绩的升降情况,程序代码如下:

01 import pandas as pd

02 data = [110,105,99,120,115]

03 index=[1,2,3,4,5]

04 df = pd.DataFrame(data=data,index=index,columns=['英语'])

05 df['升降']=df['英语']-df['英语'].shift()

06 print(df)

运行程序,输出结果如图 22所示。

图 22  统计学生英语成绩的升降情况

从运行结果得知:第2次比第1次下降5分;第3次比第2次下降6分;第4次比第3次提升21分,第5次比第4次下降5分。

这里再扩展下,通过10次周测来看下学生整体英语成绩的升降情况,如图4.23、图 24所示。

图 23  10次周测英语成绩的升降情况

图 24  图表展示英语成绩的升降情况

说明:有关图表的知识,将在第5章介绍,这里读者先简单了解。

shift方法还有很多方面的应用。例如这样一个场景:分析股票数据,获取的股票数据中有股票的实时价格,也有每日的收盘价“close”,此时需要将实时价格和上一个工作日的收盘价进行对比,这时通过shift方法就可以轻松解决。shift方法还可以应用于时间序列,感兴趣的读者可以在学习完成后续章节再进行尝试和探索。

五、数据转换

数据转换一般包括一列数据转换为多列数据、行列转换、DataFrame转换为字典、列表和元组等等。

5.1  一列数据转换为多列数据

一列数据转换为列数据的情况在日常工作中经常会用到,从各种系统中导出的订单号、名称、地址很多都是复合组成的(即由多项内容组成),那么,这些列在查找、统计、合并时就没办法使用,需要将它们拆分开。例如,地址信息由“省”“市”“区”“街道”“门牌号”等信息组成,如果按省、市或区统计数据,就需要将地址信息中的“省”“市”和“区”拆分开,此时就应用到了一列数据转换为多列数据,通常使用以下方法:

■ split方法

Pandas的DataFrame对象中的str.split内置方法可以实现分割字符串,语法如下:

Series.str.split(pat=None, n=-1, expand=False)

参数说明:

■ pat:字符串、符号或正则表达式,表示字符串分割的依据,默认以空格分割字符串。

■ n:整型,分割次数,默认值是-1。0或-1都将返回所有拆分的字符串。

■ expand:布尔型,分割后的结果是否转换为DataFrame,默认值是False。

■ 返回值:系列、索引、DataFrame或多重索引。

首先来看一组淘宝销售订单数据(部分数据),如图 25所示。

图 25  淘宝销售订单数据(部分数据)

从图中数据得知:不仅“收货地址”是复合的,“宝贝标题”也是复合的,即由多种商品组成。

快速示例26  分割“收货地址”数据中的“省、市、区”

使用split方法先对“收货地址”进行分割,程序代码如下:

01import pandas as pd

02 #设置数据显示的列数和宽度

03 pd.set_option('display.max_columns',500)

04 pd.set_option('display.width',1000)

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

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

07 #导入Excel文件指定列数据(“买家会员名”和“收货地址”)

08 df = pd.read_excel('mrbooks.xls',usecols=['买家会员名','收货地址'])

09 #使用split方法分割"收货地址"

10 series=df['收货地址'].str.split(' ',expand=True)

11 df['省']=series[0]

12 df['市']=series[1]

13 df['区']=series[2]

14 print(df.head())  #显示前5条的数据

运行程序,输出结果如图 26所示。

图 26  分割后收货地址的数据

■ join方法与split方法结合

快速示例27  以逗号“,”分割多种商品数据

通过join方法与split方法结合,以逗号“,”分割“宝贝标题”,关键代码如下:

df = df.join(df['宝贝标题'].str.split(',', expand=True))

运行程序,输出结果如图 27所示。

图 27  分割后的“宝贝标题”

从运行结果得知:“宝贝标题”中含有多种商品的数据被拆分开,这样的操作便于日后对每一款产品的销量进行统计。

■ 将DataFrame中的tuple(元组)类型数据分割成多列

快速示例28  对元组数据进行分割

首先,创建一组包含元组的数据,程序代码如下:

01import pandas as pd

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

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

04df = pd.DataFrame({'a':[1,2,3,4,5], 'b':[(1,2), (3,4),(5,6),(7,8),(9,10)]})

05print(df)

然后,使用apply函数对元组进行分割,关键代码如下:

df[['b1', 'b2']] = df['b'].apply(pd.Series)

或者使用join方法结合apply函数,关键代码如下:

df= df.join(df['b'].apply(pd.Series))

运行程序,原始数据如图28所示;输出结果如图 29和图 30所示。

图 28  原始数据

图 29  apply函数分割元组

图 30  join方法结合apply函数分割元组

5.2  行列转换

在Pandas处理数据的过程中,有时需要对数据进行行列转换或重排,这时主要使用stack方法、unstack方法和pivot方法,下面介绍这三种方法的应用:

(1)stack方法

stack方法用于将原来的列索引转换成最内层的行索引,转换效果对比示意如图 31所示。

图 31  转换效果对比示意图

stack方法的语法如下:

DataFrame.stack(level=-1, dropna=True)

参数说明:

■ level:索引层级,定义为一个标签或索引或标签列表,默认值是-1。

■ dropna:布尔型,默认值是True,

■ 返回值:DataFrame对象或Series对象。

快速示例29  对英语成绩表进行行列转换

将学生英语成绩表进行行列转换,程序代码如下:

01import pandas as pd

02 #设置数据显示的列数和宽度

03pd.set_option('display.max_columns',500)

04pd.set_option('display.width',1000)

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

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

07df=pd.read_excel('grade.xls')   #导入Excel文件

08df = df.set_index(['班级','序号'])  #设置2级索引“班级”和“序号”

09df = df.stack()

10print(df)

(2)unstack方法

unstack方法与stack方法相反,它是stack方法的逆操作,即将最内层的行索引转换成列索引,转换效果的对比如图 32所示。

图 32  unstack方法转换数据示意图

unstack方法的语法如下:

DataFrame.unstack(level=-1, fill_value=None)

参数说明:

■ level:索引层级,定义为一个标签或索引或标签列表,默认值是-1。

■ fill_value:整型、字符串或字典,如果unstack方法产生丢失值,则用这个值替换NaN。

■ 返回值:DataFrame对象或Series对象。

快速示例30  使用unstack方法转换学生成绩表

同样转换学生成绩表,关键代码如下:

01 df=pd.read_excel('grade.xls',sheet_name='英语2')   #导入Excel文件

02 df = df.set_index(['班级','序号','Unnamed: 2'])      #设置多级索引

03 print(df.unstack())

在unstack方法中有一个参数可以指定转换第几层索引。例如,unstack(0)就是把第一层行索引转换为列索引,默认是将最内层索引转换为列索引。

(3)pivot方法

pivot方法针对列的值,即指定某列的值作为行索引,指定某列的值作为列索引,然后再指定哪些列作为索引对应的值。unstack方法针对索引进行操作;pivot方法针对值进行操作。但实际上,两者在功能方面往往可以互相实现。

pivot方法的语法如下:

DataFrame.pivot(index=None, columns=None, values=None)

参数说明:

■ index:字符串或对象,可选参数。列用于创建新DataFrame数据的索引。如果没有,则使用现有索引。

■ columns:字符串或对象,列来创建新DataFrame数据的列。

■ values:列用于填充新DataFrame数据的值,如果未指定,则将使用所有剩余的列,结果将具有分层索引列。

■ 返回值:DataFrame对象。

快速示例31  使用pivot方法转换学生成绩表

下面使用pivot方法转换学生成绩表,关键代码如下:

01 df=pd.read_excel('grade.xls',sheet_name='英语3')      #导入Excel文件

02 print(df.pivot(index='序号',columns='班级',values='得分'))

运行程序,输出结果如图 33所示。

图 33  使用pivot方法转换学生成绩表

5.3  DataFrame转换为字典

将DataFrame转换为字典主要使用DataFrame对象中的to_dict方法,以索引作为字典的键(key),以列作为字典的值(value)。例如,有一个DataFrame对象(索引为“类别”、列为“数量”),通过to_dict方法就会生成一个字典,示意如图4.34所示;如果DataFrame对象包含两列,那么to_dict方法就会生成一个两层的字典(dict),第一层是列名作为字典的键(key);第二层以索引列的值作为字典的键(key),以列值作为字典的值(value)。

图 34  将DataFrame对象转换为字典示意图

快速示例32  将Excel表的销售数据转换为字典

使用to_dict方法将按“宝贝标题”分组统计后的部分数据转换为字典,程序代码如下:

01 import pandas as pd

02 df = pd.read_excel('mrbooks.xls')

03 df1=df.groupby(["宝贝标题"])["宝贝总数量"].sum().head()

04 mydict=df1.to_dict()

05 for i,j in mydict.items():

06 print(i,':\t', j)

运行程序,输出结果如图 35所示。

图 35  将DataFrame对象转换为字典

5.4  DataFrame转换为列表

将DataFrame对象转换为列表时,主要使用DataFrame的tolist方法。

快速示例33  将电商数据转换为列表

将淘宝销售数据中的“买家会员名”转换为列表,程序代码如下:

01 import pandas as pd

02 df =pd.read_excel('mrbooks.xls')

03 df1=df[['买家会员名']].head()

04 list1=df1['买家会员名'].values.tolist()

05 for s in list1:

06 print(s)

运行程序,输出结果如图 36所示。

图 36  将DataFrame对象转成列表

5.5  DataFrame转换为元组

将DataFrame转换为元组时,首先通过循环语句按行读取DataFrame数据,然后使用元组函数tuple将其转换为元组。

快速示例34  将Excel数据转换为元组

将Excel表中的人物关系部分数据转换为元组,程序代码如下:

01import pandas as pd

02df = pd.read_excel('fl4.xls')

03df1=df[['label1','label2']].head()

04tuples = [tuple(x) for x in df1.values]

05for t in tuples:

06  print(t)

运行程序,输出结果如图 37所示。

图 37  将DataFrame对象转换为元组

5.6  Excel转换为HTML网页格式

在日常工作中,有时会涉及到财务数据的处理,而Excel的应用最为广泛,但是对于展示数据来说,Excel并不友好,如果想用其他格式的文件来向用户展示,那么使用HTML网页格式是不错的选择。首先使用read_excel方法导入Excel文件,然后使用to_html方法将DataFrame数据导出为HTML格式,这样便实现了Excel转换为HTML格式的功能。

快速示例35  将Excel订单数据转换为HTML格式

将淘宝部分订单数据转换为HTML格式,效果如图 38所示。

图 38  Excel转换为HTML网页格式

程序代码如下:

01import pandas as pd

02df=pd.read_excel('mrbooks.xls')

03df.to_html('mrbook.html',header = True,index = False)

六、数据合并

DataFrame数据合并主要使用Merge方法、Concat方法。

6.1  数据合并(使用Merge方法)

Pandas模块的Merge方法是按照两个DataFrame对象列名相同的列进行连接合并,两个DataFrame对象必须具有同名的列。Merge方法的语法如下:

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

参数说明:

■ right:合并对象,DataFrame对象或Series对象。

■ how:合并类型,参数值可以是left(左合并)、right(右合并)、outer(外部合并)或inner(内部合并),默认值为inner。各个值的说明如下:

● left:只使用来自左数据集的键,类似于SQL左外部联接,保留键的顺序。

● right:只使用来自右数据集的键,类似于SQL右外部联接,保留键的顺序。

● outer:使用来自两个数据集的键,类似于SQL外部联接,按字典顺序对键进行排序。

● inner:使用来自两个数据集的键的交集,类似于SQL内部连接,保持左键的顺序。

■ on:标签、列表或数组,默认None。DataFrame对象联接的列或索引级别名称;也可以是DataFrame对象长度的数组或数组列表。

■ left_on:标签、列表或数组,默认值为None。要连接的左数据集的列或索引级名称;也可以是左数据集长度的数组或数组列表。

■ right_on:标签、列表或数组,默认值为None。要连接的右数据集的列或索引级名称,也可以是右数据集长度的数组或数组列表。

■ left_index:布尔型,默认为False。使● left:只使用来自左数据集的键,类似于SQL左外部联接,保留键的顺序。

● right:只使用来自右数据集的键,类似于SQL右外部联接,保留键的顺序。

● outer:使用来自两个数据集的键,类似于SQL外部联接,按字典顺序对键进行排序。

● inner:使用来自两个数据集的键的交集,类似于SQL内部连接,保持左键的顺序。

■ on:标签、列表或数组,默认None。DataFrame对象联接的列或索引级别名称;也可以是DataFrame对象长度的数组或数组列表。

■ left_on:标签、列表或数组,默认值为None。要连接的左数据集的列或索引级名称;也可以是左数据集长度的数组或数组列表。

■ right_on:标签、列表或数组,默认值为None。要连接的右数据集的列或索引级名称,也可以是右数据集长度的数组或数组列表。

■ left_index:布尔型,默认为False。使用左数据集的索引作为连接键。如果是多重索引,则其他数据中的键数(索引或列数)必须匹配索引级别数。

■ right_index:布尔型,默认为False,使用右数据集的索引作为连接键。

■ sort:布尔型,默认为False,在合并结果中按字典顺序对连接键进行排序。如果为False,则连接键的顺序取决于连接类型how参数。

■ suffixes:元组类型,默认值为('_x','_y')。当左侧数据集和右侧数据集的列名相同时,数据合并后列名将带上“_x”和“_y”后缀。

■ copy:是否复制数据,默认值为True,如果为False,则不复制数据。

■ indicator:布尔型或字符串,默认值为False。如果值为True,则添加一个列以输出名为“_Merge”的DataFrame对象,其中包含每一行的信息。如果是字符串,将向输出的DataFrame对象中添加包含每一行信息的列,并将列命名为字符型的值。

■ validate:字符串,检查合并数据是否● left:只使用来自左数据集的键,类似于SQL左外部联接,保留键的顺序。

● right:只使用来自右数据集的键,类似于SQL右外部联接,保留键的顺序。

● outer:使用来自两个数据集的键,类似于SQL外部联接,按字典顺序对键进行排序。

● inner:使用来自两个数据集的键的交集,类似于SQL内部连接,保持左键的顺序。

■ on:标签、列表或数组,默认None。DataFrame对象联接的列或索引级别名称;也可以是DataFrame对象长度的数组或数组列表。

■ left_on:标签、列表或数组,默认值为None。要连接的左数据集的列或索引级名称;也可以是左数据集长度的数组或数组列表。

■ right_on:标签、列表或数组,默认值为None。要连接的右数据集的列或索引级名称,也可以是右数据集长度的数组或数组列表。

■ left_index:布尔型,默认为False。使用左数据集的索引作为连接键。如果是多重索引,则其他数据中的键数(索引或列数)必须匹配索引级别数。

■ right_index:布尔型,默认为False,使用右数据集的索引作为连接键。

■ sort:布尔型,默认为False,在合并结果中按字典顺序对连接键进行排序。如果为False,则连接键的顺序取决于连接类型how参数。

■ suffixes:元组类型,默认值为('_x','_y')。当左侧数据集和右侧数据集的列名相同时,数据合并后列名将带上“_x”和“_y”后缀。

■ copy:是否复制数据,默认值为True,如果为False,则不复制数据。

■ indicator:布尔型或字符串,默认值为False。如果值为True,则添加一个列以输出名为“_Merge”的DataFrame对象,其中包含每一行的信息。如果是字符串,将向输出的DataFrame对象中添加包含每一行信息的列,并将列命名为字符型的值。

■ validate:字符串,检查合并数据是否为指定类型。可选参数,其值说明如下:

● one_to_one或“1:1”:检查合并键在左右数据集中是否都是唯一的。

● one_to_many或“1:m”:检查合并键在左数据集中是否唯一。

● many_to_one或“m:1”:检查合并键在右数据集中是否唯一。

● many_to_many或“m:m”:允许,但不检查。

■ 返回值:DataFrame对象,两个合并对象的数据集。

6.1.1  常规合并

快速示例36  合并学生的成绩表

假设一个DataFrame对象包含了学生的“语文”“数学”和“英语”成绩,而另一个DataFrame对象则包含了学生的“体育”成绩,现在将它们合并,如图39所示。

图 39  数据合并效果对比示意图

程序代码如下:

01 import pandas as pd

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

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

04 df1 = pd.DataFrame({'编号':['mr001','mr002','mr003'],

05 '语文':[110,105,109],

06 '数学':[105,88,120],

07 '英语':[99,115,130]})

08 df2 = pd.DataFrame({'编号':['mr001','mr002','mr003'],

09  '体育':[34.5,39.7,38]})

10 df_merge=pd.merge(df1,df2,on='编号')

11 print(df_merge)

运行程序,输出结果如图 40所示。

 

图 40  合并学生的成绩表

快速示例37  通过索引列合并数据

如果通过索引列合并,则需要设置right_index参数和left_index参数值为True。例如,上述举例,通过索引列合并,关键代码如下:

01df_merge=pd.merge(df1,df2,right_index=True,left_index=True)

02print(df_merge)

运行程序,输出结果如图 41所示。

快速示例38  对合并数据去重

从图41所示的运行结果得知:数据中存在重复列(如编号),如果不想要重复列,可以设置按指定列和列索引合并数据,关键代码如下:

df_merge=pd.merge(df1,df2,on='编号',left_index=True,right_index=True)

还可以通过how参数解决这一问题。例如,设置该参数值为left,就是让df1保留所有的行列数据,df2则根据df1的行列进行补全,关键代码如下:

df_merge=pd.merge(df1,df2,on='编号',how='left')

运行程序,输出结果如图 42所示。

图 42  对合并数量去重

6.1.2  多对一的数据合并

多对一是指两个数据集(df1、df2)的共有列中的数据不是一对一的关系,例如,df1中的“编号”是唯一的,而df2中的“编号”中有重复的内容,类似这种就是多对一的关系,如图 43所示。

图 43  多对一数据合并的示意图

快速示例39  根据共有列进行合并数据

根据共有列中的数据进行合并,df2根据df1的行列进行补全,程序代码如下:

01 import pandas as pd

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

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

04 df1 = pd.DataFrame({'编号':['mr001','mr002','mr003'],

05 '学生姓名':['明日同学','高猿员','钱多多']})

06 df2 = pd.DataFrame({'编号':['mr001','mr001','mr003'],

07 '语文':[110,105,109],

08  '数学':[105,88,120],

09 '英语':[99,115,130],

10  '时间':['1月','2月','1月']})

11 df_merge=pd.merge(df1,df2,on='编号')

12 print(df_merge)

运行程序,输出结果如图 44所示。

 

图 44  根据共有列进行合并数据

6.1.3  多对多的数据合并

多对多是指两个数据集(df1、df2)的共有列中的数据不全是一对一的关系,都包含了重复数据,例如“编号”,如图 45所示。

图 45  多对多数据合并的示意图

快速示例40  合并数据并相互补全

根据共有列中的数据进行合并,df2、df1相互补全,程序代码如下:

01 import pandas as pd

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

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

04 df1 = pd.DataFrame({'编号':['mr001','mr002','mr003','mr001','mr001'],

05  '体育':[34.5,39.7,38,33,35]})

06 df2 = pd.DataFrame({'编号':['mr001','mr002','mr003','mr003','mr003'],

07  '语文':[110,105,109,110,108],

08 '数学':[105,88,120,123,119],

09 '英语':[99,115,130,109,128]})

10 df_merge=pd.merge(df1,df2)

11 print(df_merge)

运行程序,输出结果如图 46所示。

图 46  合并结果

6.2  数据合并(使用Concat方法)

Concat方法可以根据不同的方式将数据合并,语法如下:

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

参数说明:

■ objs:Series、DataFrame或Panel对象的序列或映射。如果传递一个字典,则排序的键将用作键参数。

■ axis:axis=1表示行,axis=0表示列,默认值为0。

■ join:值为inner(交集)或outer(联合),处理其他轴上的索引方式。默认值为outer。

■ ignore_index:布尔值,默认值为False。需要保留索引时,索引值为0,…,n-1;如果为True,则忽略索引。

■ keys:序列,默认值无。使用传递的键作为最外层构建层次索引。如果为多索引,应该使用元组。

■ levels:序列列表,默认值无。用于构建MultiIndex参数的特定级别(唯一值)。否则,它们将从键推断。

■ names:list列表,默认值为None,结果层次索引中的级别的名称。

■ verify_integrity:布尔值,默认值为False。检查新连接的轴是否包含重复项。

■ sort:布尔值,默认值为True(在1.0.0以后版本默认值为False,即不排序)。如果连接为外连接时(join='outer'),则对未对齐的非连接轴进行排序;如果连接为内连接时(join='inner'),该参数不起作用。

■ copy:是否复制数据,默认值为True;如果为False,则不复制数据。

下面介绍Concat方法中不同的合并方式,其中dfs代表合并后的DataFrame对象;df1、df2等代表单个的DataFrame对象,result代表合并后的结果(DataFrame对象)。

6.2.1  相同字段的表首尾相接

表结构相同的数据将直接合并,即表首尾相接,关键代码如下:

01dfs= [df1, df2, df3]

02result = pd.concat(dfs)

例如,表df1、df2和df3的结构相同,如图4.47所示,合并后的效果如图4.48所示。如果想要在合并数据时标记源数据来自哪张表,则需要在代码中加入参数keys,例如,表名分别为“1月”“2月”和“3月”,合并后的效果如图 49所示。

图 47  3个相同字段的表

图 48  首尾相接合并后的效果

图 49  合并后带标记(月份)的效果

关键代码如下:

result = pd.concat(dfs, keys=['1月', '2月', '3月'])

6.2.2  横向表合并(行对齐)

当合并的数据列名称不一致时,可以设置参数axis=1,Concat方法将按行对齐,然后将不同列名的两组数据进行合并,缺失的数据用NaN填充,df1和df4合并前后的效果如图 50和图 51所示。

图 50  横向表合并前效果

图 51  横向表合并后效果

关键代码如下:

result = pd.concat([df1, df4], axis=1)

6.2.3  交叉合并

想要交叉合并,需要在代码中加上join参数,如果值为“inner”,结果是两表的交集;如果值为“outer”,结果是两表的并集。例如两表交集,表df1和df4合并前后的效果如图 52和图 53所示。

图 52  交叉合并前效果

图 53  交叉合并后效果

关键代码如下:

result = pd.concat([df1, df4], axis=1, join='inner')

6.2.4  指定表对齐数据(行对齐)

如果指定参数join_axes,就可以指定根据哪个表来对齐数据。例如,根据df4对齐数据,就会保留表df4的数据,然后将表df1的数据与之合并,行数不变,合并前后的效果如图 54和图 55所示。

图 54  指定表对齐数据合并前效果

图 55  指定表对齐数据合并后效果

关键代码如下:

result = pd.concat([df1, df4], axis=1, join_axes=[df4.index])

七、数据导出

7.1  导出为.xlsx文件

导出数据为Excel文件,主要使用DataFrame对象的to_excel方法,语法如下:

DataFrame.to_excel(excel_writer,sheet_name='Sheet1',na_rep='',float_format=None,columns=None,header=True,index=True,index_label=None,startrow=0,startcol=0,engine=None,merge_cells=True,encoding=None,inf_rep='inf',verbose=True,freeze_panes=None)

参数说明:

■ excel_writer:字符串或ExcelWriter对象。

■ sheet_name:字符串,默认值为“Sheet1”,包含DataFrame数据的表的名称。

■ na_rep:字符串,默认值为' '。缺失数据的表示方式。

■ float_format:字符串,默认值为None,格式化浮点数的字符串。

■ columns:序列,可选参数,要编辑的列。

■ header:布尔型或字符串列表,默认值为Ture。列名称,如果给定字符串列表,则表示它是列名称的别名。

■ index:布尔型,默认值为Ture,行名(索引)。

■ index_label:字符串或序列,默认值为None。如果需要,可以使用索引列的列标签。如果没有给出,标题和索引为True,则使用索引名称。如果数据文件使用多索引,则需使用序列。

■ startrow:指定从哪一行开始写入数据。

■ startcol:指定从哪一列开始写入数据。

■ engine:字符串,默认值为None,指定要使用的写引擎,如openpyxl或xlsxwriter;也可以通过io.excel.xlsx.writer,io.excel.xls.writer和io.excel.xlsm.writer进行设置。

■ merge_cells:布尔型,默认值为Ture。

■ inf_rep:字符串,默认值为“正”,表示无穷大。

■ freeze_panes:整数的元组,长度为2,默认值为None,指定要冻结的行列。

■ encoding:指定Excel文件的编码方式,默认值为None。

快速示例41  将处理后的数据导出为Excel文件

将4.6.2小节数据合并后的结果导出为Excel文件,关键代码如下:

df_merge.to_excel('merge.xlsx')

运行程序,将数据导出为Excel文件,如图 56所示。

图 56  导出为Excel文件

上述举例,如果需要指定Sheet页名称,可以通过sheet_name参数指定,关键代码如下:

df1.to_excel('df1.xlsx',sheet_name='df1')

7.2  导出为.csv文件

导出数据为.csv文件,主要使用DataFrame对象中的to_csv方法,语法如下:

DataFrame.to_csv(path_or_buf=None,sep=',',na_rep='',float_format=None,columns=None,header=True,index=True,index_label=None,mode='w',encoding=None,compression='infer',quoting=None,quotechar='"',line_terminator=None,chunksize=None,date_format=None,doublequote=True,scapechar=None,decimal='.',errors='strict')

部分参数说明:

■ path_or_buf:要保存的路径及文件名。

■ sep:分隔符,默认值为“,”。

■ float_format:浮点数的输出格式,要用双引号括起来。

■ columns:指定要导出的列,用列名、列表表示,默认值为None。

■ header:是否输出列名,默认值为True。

■ index:是否输出索引,默认值为True。

■ index_label:索引列的列名,默认值为None。

■ encoding:编码方式,默认值为“utf-8”。

■ line_terminator:换行符,默认值为“\n”。

■ mode:Python的写入模式,默认值为“w”。

■ quoting:导出.csv文件是否用引号,默认值为0,表示不加引号;如果值为1,则每个字段都会加上引号,数值也会被当作字符串看待。

■ quotechar:引用字符,当quoting=1可以指定引号字符为双引号(“ ”)或单引号(‘ ’)。

■ chunksize:一次写入.csv文件的行数,当DataFrame对象数据特别大时需要分批写入。

■ date_format:日期输出格式。

快速示例42  将处理后的数据导出为CSV文件

下面介绍to_csv方法的常用功能,举例如下,df为DataFrame对象。

(1)相对位置,保存在程序所在路径下,代码如下:

df.to_csv('Result.csv')

(2)绝对位置,代码如下:

df.to_csv('d:\Result.csv')

(3)分隔符。使用问号“?”分隔符分隔需要保存的数据,代码如下:

df.to_csv('Result.csv',sep='?')

(4)替换空值,缺失值保存为NA,代码如下:

df.to_csv('Result1.csv',na_rep='NA') 

(5)格式化数据,保留两位小数,代码如下:

df.to_csv('Result1.csv',float_format='%.2f')

(6)保留某列数据,保存索引列和name列,代码如下:

df.to_csv('Result.csv',columns=['name'])

(7)是否保留列名,不保留列名,代码如下:

df.to_csv('Result.csv',header=True)

(8)是否保留行索引,不保留行索引,代码如下:

df.to_csv('Result1.csv',index=True)

7.3  导出到多个Sheet页中

导出到多个Sheet页中,应首先使用pd.ExcelWriter方法打开一个Excel文件,然后再使用to_excel方法导出指定的Sheet页中。

快速示例43  导出Excel表中的多个Sheet页的数据

导出Excel表中多个Sheet页的数据,关键代码如下:

01 df1.to_excel('df1.xlsx',sheet_name='df1')

02 work=pd.ExcelWriter('df2.xlsx') #打开一个Excel文件

03 df1.to_excel(work,sheet_name='df2')

04 df1['A'].to_excel(work,sheet_name='df3')

05 work.save()

八、日期数据处理

8.1  DataFrame的日期数据转换

在日常工作中,比较常见的事情就是日期的格式可以有很多种表达,比如同样是2020年2月14日,可以有很多种表达格式,如图 57所示。

那么,需要先将这些格式统一后才能进行后续的工作。在Pandas中提供了to_datetime方法可以帮助我们解决这一问题。

图 57  日期的多种格式转换

to_datetime方法可以用来批量进行日期数据转换,对于处理大数据非常的实用和方便,它可以将日期数据转换成所需的各种格式。例如,将2/14/20和14-2-2020转换为日期格式——2020-02-14。to_datetime方法的语法如下:

pandas.to_datetime(arg,errors='ignore',dayfirst=False,yearfirst=False,utc=None,box=True,format=None,exact=True,unit=None,infer_datetime_format=False,origin='unix',cache=False)

参数说明:

■ arg:字符串、日期时间、字符串数组。

■ errors:值为ignore、raise或coerce,默认值为ignore忽略错误。具体说明如下:

● ignore:无效的解析将返回原值。

● raise:无效的解析将引发异常。

● coerce:无效的解析将被设置为NaT,即无法转换为日期的数据转换为NaT。

■ dayfirst:第一个为天,布尔型,默认值为False。例如02/09/2020,如果值为True,解析日期的第一个为天,即2020-09-02;如果值为False,解析日期则与原日期一致,即为2020-02-09。

■ yearfirst:第一个为年,布尔型,默认值为False。例如14-Feb-20,如果值为True,解析日期的第一个为年,即为2014-02-20;如果值为False,解析日期则与原日期一致,即为2020-02-14。

■ utc:默认值为None,返回utc即协调世界时间。

■ box:布尔值,默认值为True,如果为True,返回DatetimeIndex;如果为False返回值的ndarray。

■ format:格式化显示时间的格式。字符串,默认值为None。

■ exact:布尔值,默认值为True。如果为True,则要求格式完全匹配;如果为False,则允许格式与目标字符串中的任何位置匹配。

■ unit:默认值为None,参数的单位(D、s、ms、ns))表示时间的单位。例如Unix时间戳,它是整数/浮点数。

■ infer_datetime_format:默认值为False。如果没有格式,则尝试根据第一个日期时间字符串推断格式。

■ origin:默认值为unix。定义参考日期。数值将被解析为单位数。

■ cache:默认值为False。如果为True,则使用唯一、转换日期的缓存应用日期时间进行转换。在解析重复日期字符串,特别是带有时区偏移的字符串时,可能会产生明显的加速。只有在至少有50个值时才使用缓存。越界值的存在将使缓存不可用,并可能减慢解析速度。

快速示例44  将各种日期字符串转换为指定的日期格式

将2020年2月14日的各种格式转换为日期格式,程序代码如下:

01 import pandas as pd

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

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

■ unit:默认值为None,参数的单位(D、s、ms、ns))表示时间的单位。例如Unix时间戳,它是整数/浮点数。

■ infer_datetime_format:默认值为False。如果没有格式,则尝试根据第一个日期时间字符串推断格式。

■ origin:默认值为unix。定义参考日期。数值将被解析为单位数。

■ cache:默认值为False。如果为True,则使用唯一、转换日期的缓存应用日期时间进行转换。在解析重复日期字符串,特别是带有时区偏移的字符串时,可能会产生明显的加速。只有在至少有50个值时才使用缓存。越界值的存在将使缓存不可用,并可能减慢解析速度。

快速示例44  将各种日期字符串转换为指定的日期格式

将2020年2月14日的各种格式转换为日期格式,程序代码如下:

01 import pandas as pd

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

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

04 df=pd.DataFrame({'原日期':['14-Feb-20', '02/14/2020', '2020.02.14','2020/02/14','20200214']})

05 df['转换后的日期']=pd.to_datetime(df['原日期'])

06 print(df)

运行程序,输出结果如图 58所示。

图 58  2020年2月14日的各种格式转换为日期格式

还可以实现从DataFrame对象中的多列,如年、月、日各列组合成一列日期。而键值是常用的日期缩略语。组合要求如下:

必选:year、month、day。

可选:hour、minute、second、millisecond(毫秒)、microsecond(微秒)、nanosecond(纳秒)。

快速示例45  将一组数据组合为日期数据

想要将一组数据组合为日期数据,关键代码如下:

01 df = pd.DataFrame({'year': [2018, 2019,2020],

02  'month': [1, 3,2],

03  'day': [4, 5,14],

04  'hour':[13,8,2],

05  'minute':[23,12,14],

06  'second':[2,4,0]})

07 df['组合后的日期']=pd.to_datetime(df)

08 print(df)

运行程序,输出结果如图 59所示。

图 59  将一组数据组合为日期数据的效果

8.2  dt对象的使用

dt对象是Series对象中用于获取日期属性的一个访问器对象,通过它可以获取日期中的年、月、日、星期数、季节等,还可以判断日期是否处在年底。语法如下:

Series.dt()

参数说明:

■ 返回值:返回与原始系列相同的索引系列。如果Series不包含类日期值,则引发错误。

dt对象提供了year、month、day、dayofweek、dayofyear、is_leap_year、quarter、weekday_name等属性和方法。例如,year可以获取“年”、month可以获取“月”、quarter可以直接获取每个日期分别是第几个季度,而weekday_name可以直接获取每个日期对应的星期几的名字。

快速示例46  获取日期中的年、月、日、星期数等

使用dt对象获取日期中的年、月、日、星期数、季节等。

(1)获取年、月、日,代码如下:

df['年'],df['月'],df['日']=df['日期'].dt.year,df['日期'].dt.month,df['日期'].dt.day

(2)从日期判断出所处的星期数,代码如下:

df['星期几']=df['日期'].dt.day_name()

(3)从日期判断所处的季度,代码如下:

df['季度']=df['日期'].dt.quarter

(4)从日期判断是否为年底的最后一天,代码如下:

df['是否年底']=df['日期'].dt.is_year_end

运行程序,输出结果如图 60所示。

图 60  获取日期中的年、月、日、星期数等

8.3  获取日期区间的数据

获取日期区间的数据的方法是直接在DataFrame对象中输入日期或日期区间,但前提必须设置日期为索引,举例如下:

■ 获取2018年的数据

df1['2018']

■ 获取2017年~2018年的数据

df1['2017':'2018']

■ 获取某月(如2018年7月)的数据

df1['2018-07']

■ 获取具体某天(如2018年5月6日)的数据

df1['2018-05-06':'2018-05-06']

快速示例47  获取指定日期区间的订单数据

获取2018年5月11日~6月10日之间的订单数据,效果如图 61所示。

图 61  2018年5月11日~6月10日之间的订单数据(省略部分数据)

程序代码如下:

01 import pandas as pd

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

03 pd.set_option('display.unicode.ambiguous_as_wide', True)

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

05 df = pd.read_excel('mingribooks.xls')

06 df1=df[['订单付款时间','买家会员名','联系手机','买家实际支付金额']]

07 df1=df1.sort_values(by=['订单付款时间'])

08 df1 = df1.set_index('订单付款时间') #将日期设置为索引

09 #获取某个区间数据

10 print(df1['2018-05-11':'2018-06-10'])

8.4  按不同时期统计并显示数据

8.4.1  按时期统计数据

按时期统计数据主要通过DataFrame对象中的resample方法结合数据计算函数实现。resample方法主要应用于时间序列的频率转换和重采样,它可以从日期中获取年、月、日、星期、季节等,结合数据计算函数就可以实现按年、月、日、星期或季度等不同时期来统计数据。举例如下:

(1)按年统计数据,代码如下:

df1=df1.resample('AS').sum()

(2)按季度统计数据,代码如下:

df2.resample('Q').sum()

(3)按月度统计数据,代码如下:

df1.resample('M').sum()

(4)按星期统计数据,代码如下:

df1.resample('W').sum()

(5)按天统计数据,代码如下:

df1.resample('D').sum()

技巧:按日期统计数据的过程中,可能会出现提示类型不正确的错误信息,如图 62所示。

图 62  错误提示

完整错误描述如下:

TypeError: Only valid with DatetimeIndex, TimedeltaIndex or PeriodIndex, but got an instance of 'Index'

出现上述错误,是由于resample函数要求索引必须为日期型引起的。

解决方法:将数据的索引转换为datetime类型,关键代码如下:

df1.index = pd.to_datetime(df1.index)

8.4.2  按时期显示数据

DataFrame对象中的period方法可以将时间戳转换为时期,从而实现按时期显示数据,前提是日期必须设置为索引。语法如下:

DataFrame.to_period(freq=None, axis=0, copy=True)

参数说明:

■ freq:字符串,周期索引的频率,默认值为None。

■ axis:行列索引,0为行索引,1为列索引,默认值为0。

■ copy:是否复制数据,默认值为True;如果为False,则不会复制数据。

■ 返回值:带周期索引的时间序列。

快速示例48  从日期中获取不同的时期

实现从日期中获取不同的时期,关键代码如下:

01 df1.to_period('A')  #按年

02 df1.to_period('Q')  #按季度

03 df1.to_period('M')  #按月

04 df1.to_period('W')  #按星期

8.4.3  按时期统计并显示数据

■ 按年统计并显示数据

df2.resample('AS').sum().to_period('A')

运行结果如图 63所示。

图 63  按年统计并显示数据

■ 按季度统计并显示数据

Q_df=df2.resample('Q').sum().to_period('Q')

运行结果如图 64所示。

图 64  按季度统计并显示数据

 ■ 按月度统计并显示数据

df2.resample('M').sum().to_period('M')

运行结果如图 65所示。

图 65  按月统计并显示数据

■ 按星期统计并显示数据(前5条数据)

df2.resample('W').sum().to_period('W').head()

运行结果如图 66所示。

图 66  按星期统计并显示数据

九、时间序列

9.1  重采样(Resample方法)

通过前面的学习,我们学会了如何生成不同频率的时间索引,如按小时、按天、按周、按月等,如果想对数据进行不同频率的转换,该怎么办呢?在Pandas中,对时间序列的频率的调整称之重新采样,即将时间序列从一个频率转换到另一个频率的处理过程。例如,将每一天一个频率,转换为每5天一个频率,如图 67所示。

图 67  时间频率的转换

重采样主要使用resample方法,该方法用于对常规时间序列重新采样和频率转换,包括降采样和升采样两种。首先来了解下resample方法,语法如下:

DataFrame.resample(rule,how=None,axis=0,fill_method=None,closed=None,label=None,convention='start',kind=None,loffset=None,limit=None,base=0,on=None,level=None)

参数说明:

■ rule:字符串,偏移量表示目标字符串或对象转换。

■ how:用于产生聚合值的函数名或数组函数。例如“mean”“ohlc”“np.max”等,默认值为是“mean”,其他常用的值为“first”“last”“median”“max”和“min”。

■ axis:整型,表示行列,0表示列,1表示行,默认值为0。

■ fill_method:升采样时所使用的填充方法,ffill方法(用前值填充)或bfill方法(用后值填充),默认值为None。

■ closed:当降采样时,时间区间的开和闭,和数学里区间的概念一样,其值为“right”或“left”,“right”表示左开右闭(即左边值不包括在内);“left”表示左闭右开(即右边值不包括在内),默认为“right”左开右闭。

■ label:当降采样时,如何设置聚合值的标签。例如,10:30-10:35会被标记成10:30还是10:35,默认值为None。

■ convention:当重采样时,将低频率转换到高频率所采用的约定,其值为“start”或“end”,默认值为“start”。

■ kind:聚合到时期(“period”)或时间戳(“timestamp”),默认聚合到时间序列的索引类型,默认值为None。

■ loffset:聚合标签的时间校正值,默认值为None。例如,“-1s”或“Second(-1)”用于将聚合标签调早1秒。

■ limit:向前或向后填充时,允许填充的最大时期数,默认值为None。

■ base:整型,默认值为0。对于均匀细分1天的频率,聚合间隔的“原点”。例如,对于“5min”频率,base的范围可以是0~4。

■ on:字符串,可选参数,默认值为None。对DataFrame对象使用列代替索引进行重新采样。列必须与日期时间类似。

■ level:字符串或整型,可选参数,默认值为None。用于多索引,重新采样的级别名称或级别编号,级别必须与日期时间类似。

■ 返回值:重新采样对象。

快速示例49  将一分钟的时间序列转换为3分钟

首先创建一个包含9个一分钟的时间序列,然后使用resample方法转换为3分钟的时间序列并对索引列进行求和计算,如图 68所示。

图 68  时间序列转换

程序代码如下:

01 import pandas as pd

02 index = pd.date_range('02/02/2020', periods=9, freq='T')

03 series = pd.Series(range(9), index=index)

04 print(series)

05 print(series.resample('3T').sum())

9.2  降采样处理

降采样是周期由高频率转向低频率。例如,将5min的股票交易数据转换为日交易,按天统计的销售数据转换为按周统计。

数据降采样会涉及到数据的聚合。例如,“天数据”变成“周数据”,那么就要对一周7天的数据进行聚合,聚合的方式主要包括求和、求均值等。例如,淘宝店铺的每天销售数据(部分数据),如图 69所示。

图 69  淘宝店铺的每天销售数据(部分数据)

快速示例50  按周统计销售数据

使用resample方法来做降采样处理,频率为“周”,也就是将上述的销售数据以每周单位(每7天)进行求和,程序代码如下:

01 import pandas as pd

02 df=pd.read_excel('time.xls')

03 df1 = df.set_index('订单付款时间') #设置“订单付款时间”为索引

04 print(df1.resample('W').sum().head())

运行程序,输出结果如图 70所示。

图 70  按周数据进行统计

在参数说明中,列出了closed参数的解释,如果把closed参数值设置为“left”,结果是怎么样的呢?如图 71所示。

图 71  使用left参数的效果

9.3  升采样处理

升采样是周期由低频率转向高频率。将数据从低频率转换到高频率时,就不需要聚合了,将其重采样到日频率,默认会引入缺失值。

例如,原来是按周统计的数据,现在变成按天统计。升采样会涉及到数据的填充,根据填充的方法不同,填充的数据也就不同。下面介绍三种填充方法:

■ 不填充。空值用NaN代替,使用asfreq方法。

■ 用前值填充。用前面的值填充空值,使用ffill方法或者pad方法。为了方便记忆,ffill方法可以使用它的第一个字母“f”代替,代表forward,向前的意思。

■ 用后值填充,使用bfill方法,可以使用字母“b”代替,代表back,向后的意思。

快速示例51  每6小时进行一次数据统计

下面创建一个时间序列,起始日期是2020-02-02,一共2天,每天对应的数值分别是1和2,通过升采样处理为每6小时统计一次数据,空值以不同的方式填充,程序代码如下:

01 import pandas as pd

02 import numpy as np

03 rng = pd.date_range('20200202', periods=2)

04 s1 = pd.Series(np.arange(1,3), index=rng)

05 s1_6h_asfreq = s1.resample('6H').asfreq()

06 print(s1_6h_asfreq)

07 s1_6h_pad = s1.resample('6H').pad()

08 print(s1_6h_pad)

09 s1_6h_ffill = s1.resample('6H').ffill()

10 print(s1_6h_ffill)

11 s1_6h_bfill = s1.resample('6H').bfill()

12 print(s1_6h_bfill)

运行程序,输出结果如图 72所示。

图 72  每6小时进行一次数据统计

9.4  时间序列数据汇总(ohlc函数)

在金融领域,经常会看到开盘(open)、收盘(close)、最高价(high)和最低价(low)数据等内容,而在Pandas中,经过重新采样的数据也可以实现这样的结果,通过调用ohlc函数得到数据汇总结果,即开始值(open)、结束值(close)、最高值(high)和最低值(low)。ohlc函数的语法如下:

resample.ohlc()

参数说明:

ohlc函数返回DataFrame对象,即每组数据的open(开)、high(高)、low(低)和close(关)值。

快速示例52  统计数据的open、high、low和close值

下面是一组5分钟的时间序列,通过ohlc函数获取该时间序列中每组时间的open、high、low和close,程序代码如下。

01 import pandas as pd

02 import numpy as np

03 rng = pd.date_range('2/2/2020',periods=12,freq='T')

04 s1 = pd.Series(np.arange(12),index=rng)

05 print(s1.resample('5min').ohlc())

运行程序,输出结果如图 73所示。

图 73  统计数据的open、high、low和close值

9.5  移动窗口数据计算(rolling函数)

通过重采样可以得到想要的任何频率的数据,但是这些数据也只是一个时点的数据。那么就存在这样一个问题:时点的数据波动较大,某一点的数据就不能很好地表现它本身的特性,于是就有了“移动窗口”的概念。简单得说,为了提升数据的可靠性,将某个点的取值扩大到包含这个点的一段区间,用区间来进行判断,这个区间就是窗口。

下面举例说明,如图 74所示,其中时间序列代表1号~15号每天的销量数据,接下来以3天为一个窗口,将该窗口从左至右依次移动,统计出3天的平均值作为这个点的值,比如3号的销量是1号、2号和3号的平均值。

图 74  移动窗口数据的示意图

通过上述示意图,相信读者已经理解了移动窗口,在Pandas中,可以通过rolling函数实现移动窗口数据的计算,语法如下:

DataFrame.rolling(window, min_periods=None, center=False, win_type=None, on=None,axis=0, closed=None)

参数说明:

■ window:时间窗口的大小,有两种形式(int或offset)。如果使用int,则数值表示计算统计量的观测值的数量,即向前几个数据。如果是offset类型,则表示时间窗口的大小。

■ min_periods:每个窗口最少包含的观测值数量,小于这个值的窗口结果为NA。值可以是int,默认值为None。在offset情况下,默认值为1。

■ center:把窗口的标签设置为居中。布尔型,默认值为False,居右。

■ win_type:窗口的类型。截取窗的各种函数。字符串类型,默认值为None。

■ on:可选参数。对于DataFrame对象,是指定要计算移动窗口的列,值为列名。

■ axis:整型、字符串、默认值为0,即对列进行计算。

■ closed:定义区间的开闭,支持int类型的窗口。对于offset类型,默认是左开右闭(默认值为right)。可以根据情况指定left。

■ 返回值:为特定操作而生成的窗口或移动窗口的子类。

快速示例53  创建淘宝的每日销量数据

首先创建一组淘宝每日销量数据,程序代码如下:

01 import pandas as pd

02 index=pd.date_range('20200201','20200215')

03 data=[3,6,7,4,2,1,3,8,9,10,12,15,13,22,14]

04 s1_data=pd.Series(data,index=index)

05 print(s1_data)

运行程序,输出结果如图 75所示。

图 75  原始数据

快速示例54  使用rolling函数计算三天的均值

下面使用rolling函数计算2020-02-01到2020-02-15中每三天的均值,窗口个数为3,关键代码如下:

s1_data.rolling(3).mean()

运行程序,看下rolling函数是如何计算的。如图 76所示,当窗口开始移动时,第一个时间点2020-02-01和第二个时间点2020-02-02的数值为空,这是因为窗口个数为3,它们前面有空的数据,所以均值为空;而到第三个时间点2020-02-03时,它前面的数据是2020-02-01到2020-02-03,所以三天的均值是5.333333,以此类推。

图 76  2020-02-01到2020-02-15移动窗口均值(1)

图 77  2020-02-01到2020-02-15移动窗口均值(2)

快速示例55  用当天的数据代表窗口数据

在计算第一个时间点2020-02-01的窗口数据时,虽然数据没有达到窗口长度3,但是至少有当天的数据,那么能否用当天的数据代表窗口数据呢?答案是肯定的,通过设置min_periods参数即可,它表示窗口中最少包含的观测值,小于这个值的窗口长度显示为空,等于或大于时都将有数值,关键代码如下:

s1_data.rolling(3,min_periods=1).mean() 

运行程序,对比效果如图4.77所示。

对于上述举例,我们再扩展下,通过图表观察原始数据与移动窗口数据的平稳性,如图 78所示,其中蓝色实线代表移动窗口数据,可以看出其走向更平稳,这也是学习移动窗口rolling函数的原因。

说明:红色虚线代表原始数据,蓝色实线代表移动窗口数据。

十、综合应用

案例1:Excel多表合并

在日常工作中,几乎我们每天都有大量的数据需要处理,桌面上总是布满密密麻麻的Excel表,这样看上去非常凌乱,其实完全可以将其中类别相同的Excel表合并到一起,这样不但不会丢失数据,而且还可以有效地分析数据。下面使用Concat方法将指定文件夹内的所有Excel表合并,程序代码如下:

01 import pandas as pd

02 import glob

03 filearray=[]

04 filelocation=glob.glob(r'./aa/*.xlsx')  \t#指定目录下的所有的Excel文件

05 #遍历指定目录

06 for filename in filelocation:

07 filearray.append(filename)

08 print(filename)

09 res=pd.read_excel(filearray[0])   #读取第一个Excel文件

10 #顺序读取Excel文件并进行合并

11 for i in range(1,len(filearray)):

12  A=pd.read_excel(filearray[i])

13 res=pd.concat([res,A],ignore_index=True,sort=False)

14 print(res.index)

15 #写入Excel文件,并保存

16 writer = pd.ExcelWriter('all.xlsx')

17 res.to_excel(writer,'sheet1')

18 writer.save()

案例2:分析股票行情数据

股票数据包括开盘价、收盘价、最高价、最低价、成交量等很多个指标,其中收盘价是当日行情的标准,也是下一个交易日开盘价的依据,可以预测未来证券的市场行情,所以投资者对行情分析时,一般会采用收盘价作为计算依据。

下面使用rolling函数计算某股票20天、50天和200天的收盘价均值并生成走势图(也称K线图),如图 79所示。

图 79  股票行情分析走势图

程序代码如下:

01 import pandas as pd

02 import numpy as np

03 import matplotlib.pyplot as plt

04 aa =r'000001.xlsx'

05 #设置数据显示的列数和宽度

06 pd.set_option('display.max_columns',500)

07 pd.set_option('display.width',1000)

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

09 pd.set_option('display.unicode.ambiguous_as_wide', True)

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

11 df = pd.DataFrame(pd.read_excel(aa))

12 df['date'] = pd.to_datetime(df['date'])  #将数据类型转换为日期类型

13 df = df.set_index('date')  #将date参数设置为index

14 df=df[['close']]

15 df['20天'] = np.round(df['close'].rolling(window = 20, center = False).mean(), 2)

16 df['50天'] = np.round(df['close'].rolling(window = 50, center = False).mean(), 2)

17 df['200天'] = np.round(df['close'].rolling(window = 200, center = False).mean(), 2)

18 plt.rcParams['font.sans-serif']=['SimHei']  #解决中文乱码

19 df.plot(secondary_y = ["收盘价", "20","50","200"], grid = True)

20 plt.legend(('收盘价','20天', '50天', '200天'), loc='upper right')

21 plt.show()