Data Whale第20期组队学习 Pandas学习—时序数据
- 一、时序中的基本对象
- 二、时间戳
- 2.1 Timestamp的构造与属性
- 2.2 Datetime序列的生成
- 2.3 dt对象
- 2.4 时间戳的切片与索引
- 三、时间差
- 3.1 Timedelta的生成
- 3.2 Timedelta的运算
- 四、 日期偏置
- 4.1 Offset对象
- 4.2 偏置字符串
- 五、时序中的滑窗与分组
- 5.1 滑动窗口
- 5.2 重采样
- 参考文献
一、时序中的基本对象
时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。
第一,会出现时间戳(Date times)的概念,即’2020-9-7 08:00:00’和’2020-9-7 10:00:00’这两个时间点分别代表了上课和下课的时刻,在 pandas 中称为 Timestamp 。同时,一系列的时间戳可以组成 DatetimeIndex ,而将它放到 Series 中后, Series 的类型就变为了 datetime64[ns] ,如果有涉及时区则为 datetime64[ns, tz] ,其中tz是timezone的简写。
第二,会出现时间差(Time deltas)的概念,即上课需要的时间,两个 Timestamp 做差就得到了时间差,pandas中利用 Timedelta 来表示。类似的,一系列的时间差就组成了 TimedeltaIndex , 而将它放到 Series 中后, Series 的类型就变为了 timedelta64[ns] 。
第三,会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在 pandas 利用 Period 来表示。类似的,一系列的时间段就组成了 PeriodIndex , 而将它放到 Series 中后, Series 的类型就变为了 Period 。
第四,会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而 pandas 中的 DateOffset 就出现了。同时, pandas 中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
序号 | 概念 | 单元素类型 | 数组类型 | pandas数据类型 |
1 | Date times | Timestamp | DatetimeIndex | datetime64[ns] |
2 | Time deltas | Timedelta | TimedeltaIndex | timedelta64[ns] |
3 | Time spans | Period | PeriodIndex | period[freq] |
4 | Date offsets | DateOffset | None | None |
二、时间戳
2.1 Timestamp的构造与属性
利用 pd.Timestamp 实现单个时间戳的生成,
import pandas as pd
import numpy as np
# 常见日期格式都能被成功地转换
Ts=pd.Timestamp('2021/1/7')
print("Ts=",Ts)
# Ts= 2021-01-07 00:00:00
Ts1=pd.Timestamp('2021-1-7 16:10:30')
print("Ts1=",Ts1)
# Ts1= 2021-01-07 16:10:30
"通过 year, month, day, hour, min, second 可以获取具体的数值"
print("获取具体的数值:",Ts1.year,Ts1.month,Ts1.day,Ts1.hour,Ts1.minute,Ts1.second)
# 获取具体的数值: 2021 1 7 16 10 30
在 pandas 中,时间戳的最小精度为纳秒 ns ,由于使用了64位存储,可以表示的时间范围大约可以进行如下计算:
通过 pd.Timestamp.max 和 pd.Timestamp.min 可以获取时间戳表示的范围,可以看到确实表示的区间年数大小
print("pd.Timestamp.max=",pd.Timestamp.max)
# pd.Timestamp.max= 2262-04-11 23:47:16.854775807
print("pd.Timestamp.min=",pd.Timestamp.min)
# pd.Timestamp.min= 1677-09-21 00:12:43.145225
print("年数:",pd.Timestamp.max.year-pd.Timestamp.min.year)
# 年数: 585
2.2 Datetime序列的生成
一组时间戳可以组成时间序列,可以用 to_datetime 和 date_range 来生成。其中, to_datetime 能够把一列时间戳格式的对象转换成为 datetime64[ns] 类型的时间序列.
print("pd.to_datetime(['2020-05-02','2020-12-24','2021-01-09'])=",
pd.to_datetime(['2020-05-02','2020-12-24','2021-01-09']))
# pd.to_datetime(['2020-05-02','2020-12-24','2021-01-09'])= DatetimeIndex(['2020-05-02', '2020-12-24', '2021-01-09'], dtype='datetime64[ns]', freq=None)
df=pd.read_csv('D:/binchen/txzq/data/learn_pandas.csv')
time=pd.to_datetime(df.Test_Date)
print("time.head()=",time.head())
# time.head()= 0 2019-10-05
# 1 2019-09-04
# 2 2019-09-12
# 3 2020-01-03
# 4 2019-11-06
# Name: Test_Date, dtype: datetime64[ns]
"在极少数情况,时间戳的格式不满足转换时,可以强制使用 format 进行匹配"
temp=pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
print("temp=",temp)
# temp= DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
"注意上面由于传入的是列表,而非 pandas 内部的 Series ,因此返回的是 DatetimeIndex ,"
"如果想要转为 datetime64[ns] 的序列,需要显式用 Series 转化"
print("pd.Series(temp).head()=",pd.Series(temp).head())
# pd.Series(temp).head()= 0 2020-01-01
# 1 2020-01-03
# dtype: datetime64[ns]
"还存在一种把表的多列时间属性拼接转为时间序列的 to_datetime 操作,此时的列名必须和以下给定的时间关键词列名一致"
df_date_cols = pd.DataFrame({'year': [2020, 2021],'month': [1, 5],'day': [1, 29],
'hour': [10, 20],'minute': [30, 50],'second': [20, 40]})
print(" pd.to_datetime(df_date_cols)=", pd.to_datetime(df_date_cols))
# pd.to_datetime(df_date_cols)= 0 2020-01-01 10:30:20
# 1 2021-05-29 20:50:40
# dtype: datetime64[ns]
date_range 是一种生成连续间隔时间的一种方法,其重要的参数为 start, end, freq, periods ,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含。
print("pd.date_range('2020-10-1','2020-12-21', freq='10D')=",
pd.date_range('2020-10-1','2020-12-21', freq='10D'))
# pd.date_range('2020-10-1','2020-12-21', freq='10D')= DatetimeIndex(['2020-10-01', '2020-10-11', '2020-10-21', '2020-10-31',
# '2020-11-10', '2020-11-20', '2020-11-30', '2020-12-10',
# '2020-12-20'],
# dtype='datetime64[ns]', freq='10D')
# freq 参数与 DateOffset 对象紧密相关
print("pd.date_range('2020-1-1','2020-2-28', periods=6)=",
pd.date_range('2020-1-1','2020-2-28', periods=6))
# pd.date_range('2020-1-1','2020-2-28', periods=6)= DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
# '2020-01-24 04:48:00', '2020-02-04 19:12:00',
# '2020-02-16 09:36:00', '2020-02-28 00:00:00'],
# dtype='datetime64[ns]', freq=None)
一种改变序列采样频率的方法 asfreq ,它能够根据给定的 freq 对序列进行类似于 reindex 的操作。
num=pd.Series(np.random.rand(5),index=pd.to_datetime(['2020-10-%d'%i for i in range(1,10,2)]))
print("num.head()=",num.head())
# num.head()= 2020-10-01 0.432171
# 2020-10-03 0.214229
# 2020-10-05 0.128184
# 2020-10-07 0.835918
# 2020-10-09 0.881686
# dtype: float64
print("num.asfreq('D').head()=",num.asfreq('D').head())
# num.asfreq('D').head()= 2020-10-01 0.096306
# 2020-10-02 NaN
# 2020-10-03 0.543096
# 2020-10-04 NaN
# 2020-10-05 0.747743
# Freq: D, dtype: float64
print("num.asfreq('12H').head()=",num.asfreq('12H').head())
# num.asfreq('12H').head()= 2020-10-01 00:00:00 0.386634
# 2020-10-01 12:00:00 NaN
# 2020-10-02 00:00:00 NaN
# 2020-10-02 12:00:00 NaN
# 2020-10-03 00:00:00 0.088646
# Freq: 12H, dtype: float64
2.3 dt对象
如同 category, string 的序列上定义了 cat, str 来完成分类数据和文本数据的操作,在时序类型的序列上定义了 dt 对象来完成许多时间序列的相关操作。这里对于 datetime64[ns] 类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
第一类操作的常用属性包括: date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter ,其中 daysinmonth, quarter 分别表示月中的第几天和季度。
num1= pd.Series(pd.date_range('2020-10-1','2020-10-31', freq='D'))
print("num1.dt.date=",num1.dt.date)
print("num1.dt.time",num1.dt.time)
print("num1.dt.day=",num1.dt.day)
print("num1.dt.daysinmonth=",num1.dt.daysinmonth)
在这些属性中,经常使用的是 dayofweek ,它返回了周中的星期情况,周一为0、周二为1,以此类推
print("num1.dt.dayofweek=",num1.dt.dayofweek)
"可以通过 month_name, day_name 返回英文的月名和星期名,注意它们是方法而不是属性"
print("num1.dt.month_name()=",num1.dt.month_name())
print("num1.dt.day_name()=",num1.dt.day_name())
print("num1.dt.dayofweek=",num1.dt.dayofweek)
"可以通过 month_name, day_name 返回英文的月名和星期名,注意它们是方法而不是属性"
print("num1.dt.month_name()=",num1.dt.month_name())
print("num1.dt.day_name()=",num1.dt.day_name())
"第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天"
print("num1.dt.is_year_start=",num1.dt.is_year_start )
print("num1.dt.is_year_end=",num1.dt.is_year_end)
print("num1.dt.is_month_start=",num1.dt.is_month_start)
print("num1.dt.is_month_end=",num1.dt.is_month_end)
"第三类的取整操作包含 round, ceil, floor ,它们的公共参数为 freq ,"
"常用的包括 H, min, S (小时、分钟、秒)"
num2=pd.Series(pd.date_range('2020-12-1 20:35:00',
'2020-12-1 21:35:00',freq='45min'))
print("num2=",num2)
# num2= 0 2020-12-01 20:35:00
# 1 2020-12-01 21:20:00
# dtype: datetime64[ns]
print("num2.dt.round('1H')=",num2.dt.round('1H'))
# num2.dt.round('1H')= 0 2020-12-01 21:00:00
# 1 2020-12-01 21:00:00
# dtype: datetime64[ns]
print("num2.dt.ceil('1H')=",num2.dt.ceil('1H'))
# num2.dt.ceil('1H')= 0 2020-12-01 21:00:00
# 1 2020-12-01 22:00:00
# dtype: datetime64[ns]
print("num2.dt.floor('1H')=",num2.dt.floor('1H'))
# num2.dt.floor('1H')= 0 2020-12-01 20:00:00
# 1 2020-12-01 21:00:00
# dtype: datetime64[ns]
2.4 时间戳的切片与索引
时间戳序列作为索引使用。如果想要选出某个子时间戳序列,第一类方法是利用 dt 对象和布尔条件联合使用,另一种方式是利用切片,后者常用于连续时间戳。
num3= pd.Series(np.random.randint(2,size=366),
index=pd.date_range('2020-01-01','2020-12-31'))
idx= pd.Series(num3.index).dt
print("num3.head()=",num3.head())
# 每月的第一天或者最后一天
print("num3[(idx.is_month_start|idx.is_month_end).values].head()=",
num3[(idx.is_month_start|idx.is_month_end).values].head())
# num3[(idx.is_month_start|idx.is_month_end).values].head()= 2020-01-01 0
# 2020-01-31 0
# 2020-02-01 1
# 2020-02-29 0
# 2020-03-01 1
# dtype: int32
"双休日"
print("num3[idx.dayofweek.isin([5,6]).values].head()=",
num3[idx.dayofweek.isin([5,6]).values].head())
"取出单日值"
print("num3['2020-01-01']=",num3['2020-01-01'],num3['20200101'])
"取出十月"
print("num3['2020-10'].head()=",num3['2020-10'].head())
# num3['2020-10'].head()= 2020-10-01 0
# 2020-10-02 1
# 2020-10-03 0
# 2020-10-04 1
# 2020-10-05 1
# Freq: D, dtype: int32
# 取出5月初至7月15日
print("num3['2020-05':'2020-7-15'].head()=",
num3['2020-05':'2020-7-15'].head())
# num3['2020-05':'2020-7-15'].head()= 2020-05-01 0
# 2020-05-02 1
# 2020-05-03 1
# 2020-05-04 0
# 2020-05-05 1
# Freq: D, dtype: int32
print("num3['2020-05':'2020-7-15'].tail()=",
num3['2020-05':'2020-7-15'].tail())
# num3['2020-05':'2020-7-15'].tail()= 2020-07-11 1
# 2020-07-12 0
# 2020-07-13 0
# 2020-07-14 1
# 2020-07-15 0
三、时间差
3.1 Timedelta的生成
时间差可以理解为两个时间戳的差,这里也可以通过 pd.Timedelta 来构造。
print("pd.Timestamp('20201102 08:00:00')-pd.Timestamp('20201101 07:55:00')=",
pd.Timestamp('20201102 08:00:00')-pd.Timestamp('20201101 07:55:00'))
# pd.Timestamp('20201102 08:00:00')-pd.Timestamp('20201101 07:55:00')= 1 days 00:05:00
print("pd.Timedelta(days=1, minutes=25)=",pd.Timedelta(days=1, minutes=25))
# pd.Timedelta(days=1, minutes=25)= 1 days 00:25:00
print("pd.Timedelta('1 days 25 minutes')=",pd.Timedelta('1 days 25 minutes'))
# pd.Timedelta('1 days 25 minutes')= 1 days 00:25:00
生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns]
num4 = pd.to_timedelta(df.Time_Record)
print("num4.head()=",num4.head())
# num4.head()= 0 0 days 00:04:34
# 1 0 days 00:04:20
# 2 0 days 00:05:22
# 3 0 days 00:04:08
# 4 0 days 00:05:22
# Name: Time_Record, dtype: timedelta64[ns]
"与 date_range 一样,时间差序列也可以用 timedelta_range 来生成,它们两者具有一致的参数"
print("pd.timedelta_range('0s', '1000s', freq='6min')=",
pd.timedelta_range('0s', '1000s', freq='6min'))
# pd.timedelta_range('0s', '1000s', freq='6min')= TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
print("pd.timedelta_range('0s', '1000s', periods=3)=",
pd.timedelta_range('0s', '1000s', periods=3))
# pd.timedelta_range('0s', '1000s', periods=3)= TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
对于 Timedelta 序列,同样也定义了 dt 对象,上面主要定义了的属性包括 days, seconds, mircroseconds, nanoseconds ,它们分别返回了对应的时间差特征。需要注意的是,这里的 seconds 不是指单纯的秒,而是对天数取余后剩余的秒数。
print("pd.timedelta_range('0s', '1000s', periods=3)=",
pd.timedelta_range('0s', '1000s', periods=3))
# pd.timedelta_range('0s', '1000s', periods=3)= TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
print("num4.dt.seconds.head()=",num4.dt.seconds.head())
"不想对天数取余而直接对应秒数,可以使用 total_seconds"
print("num4.dt.total_seconds().head()=",num4.dt.total_seconds().head())
# num4.dt.total_seconds().head()= 0 274.0
# 1 260.0
# 2 322.0
# 3 248.0
# 4 322.0
# Name: Time_Record, dtype: float64
"与时间戳序列类似,取整函数也是可以在 dt 对象上使用的"
print("pd.to_timedelta(df.Time_Record).dt.round('min').head()",
pd.to_timedelta(df.Time_Record).dt.round('min').head())
# pd.to_timedelta(df.Time_Record).dt.round('min').head() 0 0 days 00:05:00
# 1 0 days 00:04:00
# 2 0 days 00:05:00
# 3 0 days 00:04:00
# 4 0 days 00:05:00
# Name: Time_Record, dtype: timedelta64[ns]
3.2 Timedelta的运算
时间差支持的常用运算有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算。
td1=pd.Timedelta(days=10)
td2= pd.Timedelta(days=23)
ts = pd.Timestamp('20200202')
print("td1*5=",td1*5)
# td1*5= 50 days 00:00:00
print("td2 - td1=",td2 - td1)
# td2 - td1= 13 days 00:00:00
print("ts + td1=",ts + td1)
# ts + td1= 2020-02-12 00:00:00
print("ts - td1=",ts - td1)
# ts - td1= 2020-01-23 00:00:00
"这些运算都可以移植到时间差的序列上"
td3 = pd.timedelta_range(start='10 days', periods=5)
td4 = pd.timedelta_range(start='12 hours',freq='2H',periods=5)
ts1= pd.date_range('20200101', '20200105')
print("td4*10=",td4*10)
#td2*10= 230 days 00:00:00
print("td3-td4=",td3-td4)
# td3-td4= TimedeltaIndex([ '9 days 12:00:00', '10 days 10:00:00', '11 days 08:00:00',
# '12 days 06:00:00', '13 days 04:00:00'],
# dtype='timedelta64[ns]', freq=None)
print("td4+pd.Timestamp('20200101')=",td4+pd.Timestamp('20200101'))
# td4+pd.Timestamp('20200101')= DatetimeIndex(['2020-01-01 12:00:00', '2020-01-01 14:00:00',
# '2020-01-01 16:00:00', '2020-01-01 18:00:00',
# '2020-01-01 20:00:00'],
# dtype='datetime64[ns]', freq='2H')
print("td4 + ts",td4 + ts)
# td4 + ts DatetimeIndex(['2020-02-02 12:00:00', '2020-02-02 14:00:00',
# '2020-02-02 16:00:00', '2020-02-02 18:00:00',
# '2020-02-02 20:00:00'],
# dtype='datetime64[ns]', freq='2H')
四、 日期偏置
4.1 Offset对象
日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。
print("pd.Timestamp('20201231') + pd.offsets.WeekOfMonth(week=2,weekday=0)=",
pd.Timestamp('20201231') + pd.offsets.WeekOfMonth(week=2,weekday=0))
# pd.Timestamp('20201231') + pd.offsets.WeekOfMonth(week=2,weekday=0)= 2021-01-18 00:00:00
print("pd.Timestamp('20201207') + pd.offsets.BDay(30)=",
pd.Timestamp('20201207') + pd.offsets.BDay(30))
# pd.Timestamp('20201207') + pd.offsets.BDay(30)= 2021-01-18 00:00:00
"当使用 + 时获取离其最近的下一个日期,当使用 - 时获取离其最近的上一个日期"
print("pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=3,weekday=0)=",
pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=3,weekday=0))
# pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=3,weekday=0)= 2020-08-24 00:00:00
print("pd.Timestamp('20200107') - pd.offsets.BDay(30)=",
pd.Timestamp('20200107') - pd.offsets.BDay(30))
# pd.Timestamp('20200107') - pd.offsets.BDay(30)= 2019-11-26 00:00:00
print("pd.Timestamp('20210907') + pd.offsets.MonthEnd()=",
pd.Timestamp('20210907') + pd.offsets.MonthEnd())
# pd.Timestamp('20210907') + pd.offsets.MonthEnd()= 2021-09-30 00:00:00
在文档罗列的 Offset 中,需要介绍一个特殊的 Offset 对象 CDay ,其中的 holidays, weekmask 参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期。
my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
dr = pd.date_range('20201108', '20201111')
print("dr.to_series().dt.dayofweek",dr.to_series().dt.dayofweek)
# dr.to_series().dt.dayofweek 2020-11-08 6
# 2020-11-09 0
# 2020-11-10 1
# 2020-11-11 2
# Freq: D, dtype: int64
print("[i + my_filter for i in dr]=",[i + my_filter for i in dr])
# [i + my_filter for i in dr]= [Timestamp('2020-11-11 00:00:00'), Timestamp('2020-11-11 00:00:00'), Timestamp('2020-11-11 00:00:00'), Timestamp('2020-11-13 00:00:00')]
4.2 偏置字符串
关于 date_range 的 freq 取值可用 Offset 对象,同时在 pandas 中几乎每一个 Offset 对象绑定了日期偏置字符串( frequencies strings/offset aliases ),可以指定 Offset 对应的字符串来替代使用。
print("pd.date_range('20201101','20210131', freq='MS')=",
pd.date_range('20201101','20210131', freq='MS'))# # 月初
# pd.date_range('20201101','20210131', freq='MS')= DatetimeIndex(['2020-11-01', '2020-12-01', '2021-01-01'], dtype='datetime64[ns]', freq='MS')
print("pd.date_range('20201101','20210131', freq='M')=",
pd.date_range('20201101','20210131', freq='M'))# # 月末
# pd.date_range('20201101','20210131', freq='M')= DatetimeIndex(['2020-11-30', '2020-12-31', '2021-01-31'], dtype='datetime64[ns]', freq='M')
print("pd.date_range('20201101','20201110', freq='B')=",
pd.date_range('20201101','20201110', freq='B')) # # 工作日
# pd.date_range('20201101','20201110', freq='B')= DatetimeIndex(['2020-11-02', '2020-11-03', '2020-11-04', '2020-11-05',
# '2020-11-06', '2020-11-09', '2020-11-10'],
# dtype='datetime64[ns]', freq='B')
print("pd.date_range('20200101','20200201', freq='W-MON') =",
pd.date_range('20200101','20200201', freq='W-MON') )
# pd.date_range('20200101','20200201', freq='W-MON') = DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
print("pd.date_range('20200101','20200201',freq='WOM-1MON')=",
pd.date_range('20200101','20200201',freq='WOM-1MON'))
# pd.date_range('20200101','20200201',freq='WOM-1MON')= DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
"等价于使用如下的 Offset 对象:"
print("pd.date_range('20200101','20200331',freq=pd.offsets.MonthBegin())=",
pd.date_range('20200101','20200331',freq=pd.offsets.MonthBegin()))
print("pd.date_range('20200101','20200331',freq=pd.offsets.MonthEnd())=",
pd.date_range('20200101','20200331',freq=pd.offsets.MonthEnd()))
print("pd.date_range('20200101','20200110', freq=pd.offsets.BDay())=",
pd.date_range('20200101','20200110', freq=pd.offsets.BDay()))
print("pd.date_range('20200101','20200201',freq=pd.offsets.CDay(weekmask='Mon'))=",
pd.date_range('20200101','20200201',freq=pd.offsets.CDay(weekmask='Mon')))
print("pd.date_range('20200101','20200201',freq=pd.offsets.WeekOfMonth(week=0,weekday=0))=",
pd.date_range('20200101','20200201',freq=pd.offsets.WeekOfMonth(week=0,weekday=0)))
五、时序中的滑窗与分组
5.1 滑动窗口
所谓时序的滑窗函数,即把滑动窗口用 freq 关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为 BOLL 指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是 N 日均值线、 N 日均值加两倍 N 日标准差线、 N 日均值减两倍 N 日标准差线。
import matplotlib.pyplot as plt
idx = pd.date_range('20210101', '20211231', freq='B')
np.random.seed(2021)
data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列
num5 = pd.Series(data,index=idx)
print("num5.head()=",num5.head())
# num5.head()= 2021-01-01 -1
# 2021-01-04 -1
# 2021-01-05 -1
# 2021-01-06 -2
# 2021-01-07 -2
# Freq: B, dtype: int32
r=num5.rolling('30D')
plt.plot(num5)
plt.title('BOLL LINES')
plt.plot(r.mean())
plt.plot(r.mean()+r.std()*2)
plt.plot(r.mean()-r.std()*2)
plt.show()
print("num5.shift(freq='50D').head()=",num5.shift(freq='50D').head())
# num5.shift(freq='50D').head()= 2021-02-20 -1
# 2021-02-23 -1
# 2021-02-24 -1
# 2021-02-25 -2
# 2021-02-26 -2
# dtype: int32
datetime64[ns] 的序列进行 diff 后就能够得到 timedelta64[ns] 的序列,这能够使用户方便地观察有序时间序列的间隔。
y_series = pd.Series(num5.index)
print("my_series.head()=",my_series.head())
# my_series.head()= 0 2021-01-01
# 1 2021-01-04
# 2 2021-01-05
# 3 2021-01-06
# 4 2021-01-07
# dtype: datetime64[ns]
print("my_series.diff(1).head()=",my_series.diff(1).head())
# my_series.diff(1).head()= 0 NaT
# 1 3 days
# 2 1 days
# 3 1 days
# 4 1 days
# dtype: timedelta64[ns]
5.2 重采样
重采样对象 resample 和第四章中分组对象 groupby 的用法类似,前者是针对时间序列的分组计算而设计的分组对象。
"对上面的序列计算每10天的均值"
print("num5.resample('10D').mean().head()=",num5.resample('10D').mean().head())
# num5.resample('10D').mean().head()= 2021-01-01 -1.333333
# 2021-01-11 -0.500000
# 2021-01-21 1.142857
# 2021-01-31 2.857143
# 2021-02-10 2.750000
# Freq: 10D, dtype: float64
"如果没有内置定义的处理函数,可以通过 apply 方法自定义"
print(",num5.resample('10D').apply(lambda x:x.max()-x.min()).head()=",
num5.resample('10D').apply(lambda x:x.max()-x.min()).head())
# ,num5.resample('10D').apply(lambda x:x.max()-x.min()).head()= 2021-01-01 1
# 2021-01-11 2
# 2021-01-21 3
# 2021-01-31 2
# 2021-02-10 2
# Freq: 10D, dtype: int32
在 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)
print("s.head()=",s.head() )
# s.head()= 2020-01-01 08:26:35 -1
# 2020-01-01 08:27:52 -2
# 2020-01-01 08:29:09 -2
# 2020-01-01 08:30:26 -1
# 2020-01-01 08:31:43 0
# Freq: 77S, dtype: int32
print("s.resample('7min').mean().head()",s.resample('7min').mean().head())
# s.resample('7min').mean().head() 2020-01-01 08:24:00 -1.500000
# 2020-01-01 08:31:00 0.400000
# 2020-01-01 08:38:00 -0.333333
# 2020-01-01 08:45:00 -2.600000
# 2020-01-01 08:52:00 -2.166667
# Freq: 7T, dtype: float64
"从序列的最小时间戳开始依次增加 freq 进行分组,此时可以指定 origin 参数为 start"
print("s.resample('7min', origin='start').mean().head()",
s.resample('7min', origin='start').mean().head())
# s.resample('7min', origin='start').mean().head() 2020-01-01 08:26:35 -1.000000
# 2020-01-01 08:33:35 0.400000
# 2020-01-01 08:40:35 -1.166667
# 2020-01-01 08:47:35 -2.400000
# 2020-01-01 08:54:35 -2.333333
# Freq: 7T, dtype: float64
"在返回值中,要注意索引一般是取组的第一个时间戳,但 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'))
print("s.resample('MS').mean().head()",s.resample('MS').mean().head() )
print("s.resample('M').mean().head()=",s.resample('M').mean().head())**
参考文献
1、https://datawhalechina.github.io/joyful-pandas/build/html/%E7%9B%AE%E5%BD%95/ch10.html