一、数据计算
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()

