第十章|时序数据|joyful pandas

第十章 时序数据

```python import numpy ad np import pandas as pd ```

一、时序中的基本对象

时间序列的概念在日常生活中很常见,对于时序事件而言,可以从多个时间对象的角度来描述

  1. Date times,pandas中利用Timestamp 时间戳,一系列时间戳可以组成DatetimeIndex,放到Series,变成了datetime64[ns],如果有涉及时区则为datetime64[ns,tz],tz:timezone
  2. time delta 时间差,两个timestamp做差可以得到时间差,pandas中利用Timedelta来表示,一系列的时间差组成了TimedeltaIndex,放到Series变成了timedelta64[ns]
  3. Time spans时间段,pands中利用Period来,一系列组成PeriodIndex,放到Series,Series类型变成Peroid
  4. DateOffsets日期偏置。

在这里插入图片描述

二、时间戳

1. Timestamp的构造与属性

#单个时间戳的生成利用pd.Timestamp实现
ts=pd.Timestamp('2020/1/1')
ts=pd.Timestamp('2020-1-1 08:10:30')
ts.year
ts.month
ts.day
ts.hour
ts.minute
ts.second

pandas,时间戳最小精度为纳秒ns,使用了64位存储
可以表示的时间范围大约可以如下计算:
T i m e   R a n g e = 2 64 1 0 9 × 60 × 60 × 24 × 365 ≈ 585 ( Y e a r s ) \rm Time\,Range = \frac{2^{64}}{10^9\times 60\times 60\times 24\times 365} \approx 585 (Years) TimeRange=109×60×60×24×365264585(Years)

通过`pd.Timestamp.max`和`pd.Timestamp.min`可以获取时间戳表示的范围
#pd.Timestamp.max
#pd.Timestamp.min
#pd.Timestamp.max.year-pd.Timestamp.min.year

2. Datetime序列的生成

  1. 一组时间戳可以组成时间序列
    可以用to_datetimedate_range生成
    to_datetime可以把一列时间戳格式的对象转换成datetime64[ns]类型的时间序列
pd.to_datetime(['2020-1-1','2020-1-3','2020-1-6'])
df=pd.read_csv('../data/learn_pandas.csv')
s=pd.to_datetime(df.Test_Date)
s.head()
  1. 少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
temp=pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
#DatetimeIndex

传入的是列表,而非pandas内部的Series
返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化

pd.Series(temp).head()
#datetime64[ns]
  1. 把表的多列时间属性拼接转为时间序列的to_datetime操作,此时的列名必须和以下给定的时间关键词列名一致
df_data_cols=pd.DataFrame({'year':[2020,2020],
'month':[1,1],'day':[1,2],'hour':[10,20],'minute':[30,50],'second':[20,40]})
pd.to_datetime(df_date_cols)
  1. date_range是一种生成连续间隔时间的一种方法
    参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数
pd.date_range('2020-1-1','2020-1-21',freq='10D')
pd.date_range('2020-1-1','2020-2-28',freq='10D')
pd.date_range('2020-1-1','2020-2-28',periods=6)
# 由于结束日期无法取到,freq不为10天

freq参数与Data

【练一练】

Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。

def intervaldate(start, end):
	begin=pd.Timestamp(start).value
	over=pd.Timestamp(end).value
	randdatev=random.randint(begin, end)
	return pd.Timestamp(randdatev)
	
	
	
【END】

最后,要介绍一种改变序列采样频率的方法asfreq,它能够根据给定的freq对序列进行类似于reindex的操作

s=pd.Series(np.random.rand(5),index=pd.to_datetime(['2020-1-%d'%i for i in range(1,10,2)]))
s.head()
s.asfreq('D').head()
s.asfreq('12H').head()

s,min,h,d

【NOTE】

datetime64[ns]本质上可以理解为一个大整数,max,min,mean取得最大时间戳,最小时间戳和平均时间戳
在这里插入图片描述

3. dt对象

category,string序列上定义了cat,str完成分类数据 和文本数据的操作,时序类型的序列上定义了dt对象完成许多时间序列的相关操作

  1. 取出时间相关的属性
  2. 判断时间戳是否满足条件
  3. 取整操作

第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。

第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:

第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处

s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
s.dt.date
s.dt.time
s.dt.day
s.dt.daysinmonth
s.dt.dayofweek
s.dt.month_name()#返回英文名
s.dt.day_name()
#第二类操作
s.dt.is_year_start
# 还可选 is_quarter/month_start
s.dt.is_year_end
# 还可选 is_quarter/month_end

#取整
s = pd.Series(pd.date_range('2020-1-1 20:35:00', '2020-1-1 22:35:00', freq='45min'))
s.dt.round('1H')
s.dt.ceil('1H')
s.dt.floor('1H')

4. 时间戳的切片与索引

第一类方法是利用dt对象和布尔条件联合使用
另一种方式是利用切片,后者常用于连续时间戳


s=pd.Series(np.random.randint(2,size=366),index=pd.date_range('2020-01-01','2020-12-31'))
#2020有366天
idx=pd.Series(s.index).dt
s.head()
#Example1:每月的第一天或者最后一天
s[(idx.is_month_start|idx.is_month_end).values]
#Example2:双休日
s[idx.dayofweek.isin([5,6]).values].head()
dayofweek,数组下标从0(星期一)开始

#Example3:取出单日值
s['2020-01-01']
s['20200101']
#自动转换标准格式
#Example4:取出七月
s['2020-07'].head()
#Example5:取出5月初至7月15日
s['2020-05':'2020-7-15'].head()
s['2020-05':'2020-7-15'].tail()

三、时间差

1. Timedelta的生成

时间差可以理解为两个时间戳的差,这里也可以通过pd.Timedelta来构造:

生成时间差序列的主要方式是pd.to_timedelta,其类型为timedelta64[ns]


直接生成

#两个timestamp相减
pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
#构造参数设置
pd.Timedelta(days=1, minutes=25) # 需要注意加s
#字符串构造生成
pd.Timedelta('1 days 25 minutes')
# dataframe转换
s=pd.to_timedelta(df.Time_Record)

# 时间差序列
pd.timedelta_range('0s', '1000s', freq='6min')
pd.timedelta_range('0s', '1000s', periods=3)

Timedelta序列定义了dt对象
属性包括days, seconds, mircroseconds, nanoseconds
seconds不是指单纯的秒,而是对天数取余后剩余的秒数:

s.dt.seconds.head()
#天数取余得到秒数
s.dt.total_seconds().head()
#直接对应秒数
pd.to_timedelta(df.Time_Record).dt.round('min').head()
#对分进行取整

2. Timedelta的运算

时间差支持的常用运算有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:

td1 = pd.Timedelta(days=1)
td2 = pd.Timedelta(days=3)
ts = pd.Timestamp('20200101')
td1 * 2
td1 = pd.timedelta_range(start='1 days', periods=5)
td2 = pd.timedelta_range(start='12 hours', freq='2H', periods=5)
ts = pd.date_range('20200101', '20200105')

四、日期偏置

1. Offset对象

当使用`+`时获取离其最近的下一个日期,当使用`-`时获取离其最近的上一个日期:
pd.Timestamp('20200831') + pd.offset.WeekOfMonth(week=0,weekday=0)
#查找求2020年9月第一个周一的日期
pd.Timestamp('20200907') + pd.offsets.BDay(30)
#求2020年9月7日后的第30个工作日是哪一天
pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
pd.Timestamp('20200907') - pd.offsets.BDay(30)
pd.Timestamp('20200907') + pd.offsets.MonthEnd()

特殊的Offset对象CDay,其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤
前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串
只保留字符串中出现的星期

my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
dr = pd.date_range('20200108', '20200111')
dr.to_series().dt.dayofweek
[i + my_filter for i in dr]

上面的例子中,n表示增加一天CDaydr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。

【CAUTION】不要使用部分Offset

在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。

2. 偏置字符串

前面提到了关于date_rangefreq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。

pd.date_range('20200101','20200331',freq='MS')#月初
pd.date_range('20200101','20200331', freq=pd.offsets.MonthBegin())
pd.date_range('20200101','20200331', freq='M') # 月末
pd.date_range('20200101','20200331', freq=pd.offsets.MonthEnd())
pd.date_range('20200101','20200110', freq='B') # 工作日
pd.date_range('20200101','20200110', freq=pd.offsets.BDay())
pd.date_range('20200101','20200201', freq='W-MON') # 周一
pd.date_range('20200101','20200201', freq=pd.offsets.CDay(weekmask='Mon'))
pd.date_range('20200101','20200201', freq='WOM-1MON') # 每月第一个周一
pd.date_range('20200101','20200201', freq=pd.offsets.WeekOfMonth(week=0,weekday=0))

各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理

五、时序中的滑窗与分组

1. 滑动窗口

所谓时序的滑窗函数,即把滑动窗口用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30BOLL指标可以如下写出:


import matplotlib.pyplot as plt
idx = pd.date_range('20200101', '20201231', freq='B')
np.random.seed(2020)
data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列
s = pd.Series(data,index=idx)
s.head()


import pandas as pd

amount = pd.Series([100, 90, 110, 150, 110, 130, 80, 90, 100, 150])
print(amount. Rolling(3).sum())


import matplotlib.pyplot as plt
idx = pd.date_range('20200101', '20201231', freq='B')
np.random.seed(2020)
data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列
s = pd.Series(data, index=idx)
r = s.rolling('30D')#每月的计算
plt.plot(s)
plt.title('BOLL LINES')
plt.plot(r.mean())
plt.plot(r.mean()+r.std()*2)
plt.plot(r.mean()-r.std()*2)
s.shift(freq='50D').head()
#shift50天移动
my_series = pd.Series(s.index)
my_series.head()
my_series.diff(1).head()
#进行微分之后得到序列

2. 重采样

重采样对象resample和第四章中分组对象groupby的用法类似,前者是针对时间序列的分组计算而设计的分组对象。
对上面的序列计算每10天的均值:

s.resample('10D').mean().head()
s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差

resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:

idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')
data = np.random.randint(-1,2,len(idx)).cumsum()
s = pd.Series(data,index=idx)
s.head()

下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35

s.resample('7min').mean().head()

从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start

s.resample('7min', origin='start').mean().head()

在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳:

s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01', '2020-12-31'))
s.resample('M').mean().head()
s.resample('MS').mean().head() # 结果一样,但索引不同

指定label向后偏移
series.resample('W',label='left').sum()
series.resample('W',closed='left').sum()
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值