- 本篇博文来自《Python数据分析从入门到精通》_明日科技编著
4.8 日期数据处理
4.8.1 DataFrame的日期数据转换
- 日常工作中,有一个非常麻烦的事情就是日期的格式可以有很多中表达,我们看到同样的2020年2月14日,可以有很多种格式,如图4.57所示。那么,我们需要先将这些格式统一后才能进行后续的工作。Pandas提供了to_datetime()方法可以帮助我们解决这一问题。
- 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,则返回DatatimeIndex;如果值为False,则返回ndarray。
- format:格式化显示时间的格式。字符串,默认值为None
- exact:布尔值,默认值为True,如果为True,则要求格式完全匹配;如果为False,则允许格式与目标字符串中的任何位置匹配
- unit:默认值为 None,参数的单位(D、s、ms、us、ns)表示时间的单位
- infer_datetime_format:默认值为False。如果没有格式,则尝试根据第一个日期时间字符串推断格式。
- origin:默认值为unix。定义参考日期。数值将被解析为单位数
- cache:默认值为False。如果值为True,则使用唯一、转换日期的缓存应用日期的时间转换。在解析重复日期字符串,特别是带有时区偏移的字符串时,可能会产生明显的加速。只有在至少有50个值时才使用缓存。越界值的存在将使缓存不可用,并可能减慢解析速度。
- 返回值:日期时间
将各种日期字符串转换为指定的日期格式
- 将2020年2月14日的各种格式转换为日期格式,程序代码如下:
import pandas as pd
df=pd.DataFrame({'原日期':['14-Feb-20', '02/14/2020', '2020.02.14', '2020/02/14','20200214']})
df['转换后的日期']=pd.to_datetime(df['原日期'])
print(df)
- 还可以实现从DataFrame对象中的多列,如年、月、日各列组合成一列日期。键值时常用的日期缩略语。组合要求:
- 必选:year、month、day
- 可选:hour、minute、second、milisecond(毫秒)、microsecond(微秒)、nanosecond(纳秒)。
将一组数据转换为日期数据
import pandas as pd
df = pd.DataFrame({'year': [2018, 2019,2020],
'month': [1, 3,2],
'day': [4, 5,14],
'hour':[13,8,2],
'minute':[23,12,14],
'second':[2,4,0]})
df['组合后的日期']=pd.to_datetime(df)
print(df)
4.8.2 dt对象的使用
- dt对象是Series对象中用于获取日期属性的一个访问器对象,通过它可以获取日期中的年、月、日、星期数、季节等,还可以判断日期是否处在年底。语法如下:
Series.dt()
- 返回值:返回与原始系列相同的索引系列。如果Series不包含日期值,则引发错误。
- dt对象提供了year、month、day、dayofweek、dayofyear、is_leap_year、quarter、weekday_name等属性和方法。例如,year可以获取"年"、quarter可以直接得到每个日期分别是第几个季度,weekday_name可以直接得到每个日期对应的是周几。
获取日期中的年、月、日、星期数等
import pandas as pd
df=pd.DataFrame({'原日期':['2019.1.05', '2019.2.15', '2019.3.25','2019.6.25','2019.9.15','2019.12.31']})
df['日期']=pd.to_datetime(df['原日期'])
df
df['年'],df['月'],df['日']=df['日期'].dt.year,df['日期'].dt.month,df['日期'].dt.day
df['星期几']=df['日期'].dt.day_name()
df['季度']=df['日期'].dt.quarter
df['是否年底']=df['日期'].dt.is_year_end
df
4.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']
获取指定日期区间的订单数据
- 获取2018年5月11日至6月10日的订单,结果如下图所示
import pandas as pd
df = pd.read_excel('mingribooks.xls')
df1=df[['订单付款时间','买家会员名','联系手机','买家实际支付金额']]
df1=df1.sort_values(by=['订单付款时间'])
df1
df1 = df1.set_index('订单付款时间') # 将日期设置为索引
df1
#获取某个区间数据
df2=df1['2018-05-11':'2018-06-10']
df2
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()
2.按时期显示数据
- DataFrame对象的to_period()方法可以将时间戳转换为时期,从而实现按日期显示数据,前提是日期必须设置为索引。语法如下:
DataFrame.to_period(freq=None,axis=0,copy=True)
- freq:字符串,周期索引的频率,默认值为None
- axis:行列索引,axis=0表示行索引,axis=1表示列索引。默认值为0,即表示行索引。
- copy:是否复制数据,默认值为True,如果值为False,则不复制数据。
- 返回值:带周期索引的时间序列
从日期中获取不同的时期
- 从日期中获取不同的时期,主要代码如下:
import pandas as pd
aa =r'TB2018.xls'
df = pd.DataFrame(pd.read_excel(aa))
df1=df[['订单付款时间','买家会员名','联系手机','买家实际支付金额']]
df1 = df1.set_index('订单付款时间') # 将date设置为index
df1
按月统计数据
#按月统计数据
#“MS”是每个月第一天为开始日期,“M”是每个月最后一天
df1.resample('M').sum()
按季统计数据
#按季统计数据
#“QS”是每个季度第一天为开始日期,“Q”是每个季度最后一天
df1.resample('QS').sum()
按年统计数据
#按年统计数据
#“AS”是每年第一天为开始日期,“A”是每年最后一天
df1.resample('AS').sum()
按年统计数据并显示数据
#按年统计数据并显示数据
#“AS”是每年第一天为开始日期,“A”是每年最后一天
df1.resample('AS').sum().to_period('A')
按季度统计并显示数据
# 按季度统计并显示数据
df1.resample('Q').sum().to_period('Q')
按月统计数据并显示数据
#按月统计数据
#“MS”是每个月第一天为开始日期,“M”是每个月最后一天
df1.resample('M').sum().to_period('M')
按星期统计并显示数据
df1.resample('w').sum().to_period('W').head()
4.9 时间序列
4.9.1 重采样(Resample()方法)
- 通过前面的学习,我们学会了如何生成不同频率的时间索引,按小时、按天、按周、按月等,如果想对数据做不同频率的转换,该怎么办?在Pandas中对时间序列的频率的调整称为重新采样,即将时间序列从一个频率转换到另一个频率的处理过程。例如,每天一个频率转换为每5天一个频率,如图4.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,level=None)
- rule:字符串,偏移量表示目标字符串或对象转换
- how:用于产生聚合值的函数名或数组函数。例如mean、ohlc和np.max等,默认值为mean,其他常用的值为first、last、median、max和min。
- axis:整型,表示行列,axis=0表示列,axis=1表示行。默认值为0,即表示列
- fill_method:升采样时所使用的填充方法,fill()方法(用前值填充)或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。用于多索引,重新采样的级别或级别编号,级别必须与日期时间类似
- 返回值:重新采样对象
一分钟的时间序列转化为3分钟的时间序列
- 首先创建一个包含9个一分钟的时间序列,然后使用resample()方法转换为3分钟的时间序列,并对索引进行求和计算,如图4.68所示。
- 程序代码如下:
import pandas as pd
index = pd.date_range('02/02/2020', periods=9, freq='T')
series = pd.Series(range(9), index=index)
series
series.resample('3T').sum()
4.9.2 降采样处理
- 降采样是周期由高频率转向低频率。例如,将5min股票交易数据转换为日交易,按天统计的销售数据转换按周统计。
- 数据降采样会涉及数据的聚合。例如,天数据变成周数据,那么就要对1周7天的数据进行聚合,聚合的方式主要包括求和、求均值等。
按周统计销售数据
import pandas as pd
df=pd.read_excel('time.xls')
df1 = df.set_index('订单付款时间') #设置“订单付款时间”为索引
df1
df1.resample('W').sum().head()
#%%
df1.resample('W',closed='left').sum()
4.9.3 升采样处理
- 升采样是周期由低频率转向高频率。将数据从低频率转换到高频率时,就不需要聚合了,将其重采样到日频率,默认会引入缺失值。
- 例如,原来是按周统计的数据,现在变成按天统计。升采样会涉及数据的填充,根据填充的方法不同,填充的数据也不同。下面介绍三种填充方法。
– 不填充。空值用NaN代替,使用asfreq()方法。
– 用前值填充。用前面的值填充空值,使用ffill()方法或者pad()方法。为了方便记忆,ffill()方法可以使用它的第一个字母"f"代替,代表forward,向前的意思。
– 用后值填充,使用bfill()方法,可以使用字母"b"代替,代表back,向后的意思。
每6小时统计一次数据
- 下面创建一个时间序列,起始日期是2020-02-02,一共两天,每天对应的数值分别是1和2,通过升采样处理为每6小时统计一次数据,空值以不同的方式填充,程序代码如下:
import pandas as pd
import numpy as np
rng = pd.date_range('20200202', periods=2)
s1 = pd.Series(np.arange(1,3), index=rng)
s1
s1_6h_asfreq = s1.resample('6H').asfreq()
print(s1_6h_asfreq)
s1_6h_pad = s1.resample('6H').pad()
print(s1_6h_pad)
s1_6h_ffill = s1.resample('6H').ffill()
print(s1_6h_ffill)
s1_6h_bfill = s1.resample('6H').bfill()
print(s1_6h_bfill)
4.9.4 时间序列数据汇总(ohlc()函数)
- 在金融领域,经常会看到开盘(open)、收盘(close)、最高价(high)和最低价(low)数据,而在Pandas中经过重新采样的数据也可以实现这样的结果,通过调用ohlc()函数得到数据汇总结果,即开始值(open)、结束值(close)、最高值(high)和最低值(low)。ohlc()函数的语法如下:
resample.ohlc()
- ohlc()函数返回DataFrame对象,每组数据的open(开)、high(高)、low(低)和close(关)值。
统计数据的open、high、low和close值。
- 下面每一组5分钟的时间序列,通过ohlc()函数获取该时间序列中每组时间的开始值、最高值、最低值和结束值,程序代码如下:
import pandas as pd
import numpy as np
rng = pd.date_range('2/2/2020',periods=12,freq='T')
s1 = pd.Series(np.arange(12),index=rng)
s1
s1.resample('5min').ohlc()
4.9.5 移动窗口数据计算(rolling()函数)
- 通过重采样可以得到想要的任何低频率的数据,但是这些数据也是一共时点的数据,那么就存在这样一个问题:时点的数据波动较大,某一点的数据就不能很好地表现它本身的特性,于是就有了"移动窗口"的概念,简单地说,为了提升数据的可靠性,将某个点的取值扩大到这个点的一段区间,用区间来进行判断,这个区间就是窗口。
- 下面举例说明,图4.74显示了移动窗口数据示意图,其中时间序列代表1号到15号每天的销量数据,接下来以3天为一个窗口,将该窗口从左至右依次移动,统计出3天的平均值作为这个点的值,如3号的销量是1号、2号和3号的平均值。
- 通过上述示意图相信您已经理解了移动窗口,在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:整型,axis=0表示列,axis=1表示行。默认值为0,即对列进行计算
- closed:定义区间的开闭,支持int类型的窗口。对于offset类型默认是左开右闭。可以根据情况指定left。
- 返回值:为特定操作而生成的窗口或移动窗口子类
创建淘宝每日销售数据
- 首先创建一组淘宝每日销售数据,程序代码如下:
import pandas as pd
index=pd.date_range('20200201','20200215')
data=[3,6,7,4,2,1,3,8,9,10,12,15,13,22,14]
s1_data=pd.Series(data,index=index)
s1_data
使用rolling()函数计算3天的均值
- 下面使用rolling()函数计算2020-02-01至2020-02-15中3天的均值,窗口个数为3,代码如下:
s1_data.rolling(3).mean()
- 运行程序,看下rolling()函数是如何计算的?当窗口开始移动时,第一个时间点2020-02-01和第二个时间点2020-02-02的数值为空,这是因为窗口个数为3,它们前面有空数据,所以均值为空;而到第三个时间点2020-02-03时,它前面的数据是2020-02-01至2020-02-03,所以3天的均值是5033333;以此类推。
用当天的数据代表窗口数据
- 在计算第一个时间点2020-02-01的窗口数据时,虽然数据不够窗口长度3,但是至少有当天的数据,那么能否用当天的数据代表窗口数据呢?答案是肯定的,通过设置min_periods参数即可,它表示窗口最少包含的观测值,小于这个值的窗口长度显示为空,等于或大于时都有值,主要代码如下:
s1_data.rolling(3,min_periods=1).mean()
import numpy as np
import pandas as pd
index=pd.date_range('20200201','20200215')
data=[3,6,7,4,2,1,3,8,9,10,12,15,13,22,14]
np.random.seed(2)
data=np.random.randint(20,size=len(index))
ser_data=pd.Series(data,index=index)
plt.figure(figsize=(15, 5))
ser_data.plot(style='r--')
ser_data.rolling(3).mean().plot(style='b')
4.10 综合应用
案例1:Excel多表合并
- 在日常工作中,几乎我们每天都有大量的数据需要处理,桌面上总是布满密密麻麻的Excel表,这样看上去非常凌乱,其实我们完全可以其中的类别相同的Excel表合并到一起,这样不但不会丢失数据,而且还可以非常有效地分析数据。下面使用concat()方法指定文件夹内的所有Excel表合并,程序代码如下:
import pandas as pd
import glob
filearray=[]
filelocation=glob.glob(r'./aa/*.xlsx') #指定目录下的所有Excel文件
#遍历指定目录
for filename in filelocation:
filearray.append(filename)
print(filename)
res=pd.read_excel(filearray[0]) #读取第一个Excel文件
#顺序读取Excel文件并进行合并
for i in range(1,len(filearray)):
A=pd.read_excel(filearray[i])
res=pd.concat([res,A],ignore_index=True,sort=False) #忽略索引,读取文件详见pandas统计分析中
print(res.index)
#写入Excel文件,并保存
writer = pd.ExcelWriter('all.xlsx')
res.to_excel(writer,'sheet1')
writer.save()
4.10.2 案列2:股票行情数据分析
- 股票数据包括开盘价、收盘价、最低价、成交量等多个指标。其中,收盘价是当日行情的标准,也是下一个交易日开盘价的依据,可以预测未来证券市场行情,因此当投资者对行情分析时,一般采用收盘价作为计价依据。
- 下面使用rolling()函数计算某股票20天、50天和200天的收盘价均值并生成走势图(也称K线图),程序代码如下:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
aa =r'000001.xlsx'
df = pd.DataFrame(pd.read_excel(aa))
df['date'] = pd.to_datetime(df['date']) #将数据类型转换为日期类型
df
df = df.set_index('date') # 将date设置为index
df=df[['close']] #提取收盘价数据
df
df['20天'] = np.round(df['close'].rolling(window = 20, center = False).mean(), 2)
df['50天'] = np.round(df['close'].rolling(window = 50, center = False).mean(), 2)
df['200天'] = np.round(df['close'].rolling(window = 200, center = False).mean(), 2)
plt.rcParams['font.sans-serif']=['SimHei'] #解决中文乱码
df.plot(secondary_y = ["收盘价", "20","50","200"], grid = True)
plt.legend(('收盘价','20天', '50天', '200天'), loc='upper right')