Pandas时间序列分析
原生Python时间类型
datetime以及timedelta
原生Python中,datetime模块是经常用到的时间库,其中有datetime类型表示一个特定的时间点,timedelta类型,表示两个datetime类型的时间间隔
1 2 from datetime import datetimefrom datetime import timedelta
datetime类型可以表示年月日时分秒微秒
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 >>> In[]:dt = datetime(2022 ,12 ,31 ,12 ,12 ,39 ,1234 ) print (dt.year)print (dt.month)print (dt.day)print (dt.hour)print (dt.minute)print (dt.second)print (dt.microsecond)>>> Out[]:2022 12 31 12 12 39 1234
1 2 3 4 5 6 7 >>> In[]:now = datetime.now() now >>> Out[]:datetime.datetime(2023 , 9 , 18 , 21 , 16 , 33 , 888535 )
两个时间对象相减则可以获取二者之间的时间差对象。下列代码说明,这两个时间相差261天32643秒887301微秒。但是delta对象没有时分属性。
1 2 3 4 5 6 7 8 9 10 11 12 >>> In[]:delta = now - dt print (delta)print (delta.days)print (delta.seconds)print (delta.microseconds)>>> Out[]:261 days, 9 :03:54.887301 261 32634 887301
1 2 3 4 5 6 >>> In[]:timedelta(days = 200 ,seconds = 15 ,microseconds = 12355 ) datetime.timedelta(days=200 , seconds=15 , microseconds=12355 )
timedelta和datetime类型之间是可以进行加减算术运算的,得到另一个datetime对象
1 2 3 4 5 6 7 >>> In[]:print (dt + delta)print (dt + 2 *delta)>>> Out[]:2023 -09-18 21 :16 :33.888535 2024 -06-06 06:20 :28.775836
字符串与时间对象的转换
dateimte库中的strftime意为strformattime,其作用为将datetime对象转换为字符串形式,此时需要指明转换为字符串后的格式;strptime意为strparsertime,其作用为将字符串形式的日期转换为datetime对象,也需指明传入的字符串的日期格式。
常见的日期格式如下所示:
%Y:4位数年份
%y:2位数年份
%m:月份
%d:日期
%H:24小时制时
%I:12小时制时
%M:分
%S:秒
1 2 3 4 5 >>> In[]:dt.strftime('%Y-%m-%d %H:%M:%S' ) >>> Out[]:'2022-12-31 12:12:39'
1 2 3 4 5 >>> In[]:datetime.strptime('6/30/2022 12:30' ,'%m/%d/%Y %H:%M' ) >>> Out[]:datetime.datetime(2022 , 6 , 30 , 12 , 30 )
pandas时间序列
pandas中最基础的时间类型是时间戳Series,可以使用pd.to_datetime将字符串列表(array-like)转换为时间戳Series。可以发现,得到的结果是一个DatetimeIndex,这是一个广义上的Series对象,其中的每个元素都是一个时间戳对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 >>> In[]:import numpy as npimport pandas as pddatestrs = ["2011-07-06 12:00:00" , "2011-08-06 00:00:00" ] DTI = pd.to_datetime(datestrs) DTI >>> Out[]:DatetimeIndex(['2011-07-06 12:00:00' , '2011-08-06 00:00:00' ], dtype='datetime64[ns]' , freq=None ) >>> In[]:DTI[0 ] >>> Out[]:Timestamp('2011-07-06 12:00:00' )
pandas中,None也可以称为一个时间戳对象,使用NaT(Not a Time)对其进行表示,其类似于NaN,可以使用isna方法进行检测。
1 2 3 4 5 6 7 8 9 10 11 >>> In[]:pd.to_datetime(["2011-07-06 12:00:00" , "2011-08-06 00:00:00" ,None ]) >>> Out[]:DatetimeIndex(['2011-07-06 12:00:00' , '2011-08-06 00:00:00' , 'NaT' ], dtype='datetime64[ns]' , freq=None ) >>> In[]:pd.isna(pd.to_datetime(["2011-07-06 12:00:00" , "2011-08-06 00:00:00" ,None ])) >>> Out[]:array([False , False , True ])
除了字符串,python的datetime对象也适用于pd.to_datetime方法
1 2 3 4 5 6 7 8 9 >>> In[]:dates = [datetime(2011 , 1 , 2 ), datetime(2011 , 1 , 5 ), datetime(2011 , 1 , 7 ), datetime(2011 , 1 , 8 ), datetime(2011 , 1 , 10 ), datetime(2011 , 1 , 12 )] pd.to_datetime(dates) >>> Out[]:DatetimeIndex(['2011-01-02' , '2011-01-05' , '2011-01-07' , '2011-01-08' ,'2011-01-10' , '2011-01-12' ],dtype='datetime64[ns]' , freq=None )
DatetimeIndex对象一般用来作为一个数字Series或者DataFrame的索引
1 2 3 4 5 6 7 8 9 10 11 12 >>> In[]:ts = pd.Series(np.random.randint(1 ,10 ,6 ),index=pd.to_datetime(dates)) ts >>> Out[]:2011 -01-02 8 2011 -01-05 2 2011 -01-07 1 2011 -01-08 2 2011 -01-10 2 2011 -01-12 5 dtype: int64
当然也可以不必这么麻烦,直接将dates作为索引也可以得到相同的效果
1 2 3 4 5 6 7 8 9 10 11 12 >>> In[]:ts = pd.Series(np.random.randint(1 ,10 ,6 ),index=dates) ts >>> Out[]:2011 -01-02 1 2011 -01-05 5 2011 -01-07 7 2011 -01-08 9 2011 -01-10 8 2011 -01-12 7 dtype: int64
但是不可以直接将字符串作为索引,此时得到的索引类型并不是DatetimeIndex类型
1 2 3 4 5 6 7 8 9 >>> In[]:ts_str = pd.Series(np.random.randint(1 ,10 ,2 ), index=["2011-07-06 12:00:00" , "2011-08-06 00:00:00" ]) ts_str >>> Out[]:2011 -07-06 12 :00 :00 9 2011 -08-06 00 :00 :00 6 dtype: int64
对比结果如下
1 2 3 4 5 6 7 8 9 >>> In[]:ts_str.index ts.index >>> Out[]:Index(['2011-07-06 12:00:00' , '2011-08-06 00:00:00' ], dtype='object' ) DatetimeIndex(['2011-01-02' , '2011-01-05' , '2011-01-07' , '2011-01-08' ,'2011-01-10' , '2011-01-12' ], dtype='datetime64[ns]' , freq=None )
DatetimeInde对象的索引
DatetimeIndex对象与一般的数字Index在索引取值方面并无不同:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 >>> In[]:ts[0 ] >>> Out[]:8 >>> In[]:ts[0 :2 ] >>> Out[]:2011 -01-02 8 2011 -01-05 2 dtype: int64 >>> In[]:ts[::2 ] >>> Out[]:2011 -01-02 8 2011 -01-07 1 2011 -01-10 2 dtype: int64
可以使用索引本身的值进行取值或者切片
1 2 3 4 5 >>> In[]:ts[ts.index[2 ]] >>> Out[]:1
为了方便起见,也可以直接使用字符串形式进行索引
1 2 3 4 5 >>> In[]:ts['2011-01-02' ] >>> Out[]:8
由于按照value进行切片,因此此时的切片是包含两端的
1 2 3 4 5 6 7 8 >>> In[]:ts['2011-01-02' :'2011-01-07' ] >>> Out[]:2011 -01-02 8 2011 -01-05 2 2011 -01-07 1 dtype: int64
甚至可以只包含某个年份或者月份,这样pandas将会把满足这些条件的日期索引出来相当于是进行布尔掩码索引
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 >>> In[]:ts_long = pd.Series(np.random.randn(500 ), index=pd.date_range('2000-01-01' ,periods=500 ,freq='d' )) ts_long >>> Out[]:2000 -01-01 0.979330 2000 -01-02 -0.868795 2000 -01-03 -0.439165 2000 -01-04 0.803966 2000 -01-05 0.012652 ... 2001 -05-10 -0.513777 2001 -05-11 0.813053 2001 -05-12 0.508245 2001 -05-13 -1.782211 2001 -05-14 1.427013 Freq: D, Length: 500 , dtype: float64 >>> In[]:ts_long['2000' ] >>> Out[]:2000 -01-01 0.979330 2000 -01-02 -0.868795 2000 -01-03 -0.439165 2000 -01-04 0.803966 2000 -01-05 0.012652 ... 2000 -12 -27 0.071322 2000 -12 -28 0.422151 2000 -12 -29 -0.016263 2000 -12 -30 -0.934628 2000 -12 -31 1.014883 Freq: D, Length: 366 , dtype: float64 >>> In[]:ts_long['2000-01' ] >>> Out[]:2000 -01-01 0.979330 2000 -01-02 -0.868795 2000 -01-03 -0.439165 2000 -01-04 0.803966 2000 -01-05 0.012652 2000 -01-06 0.822313 2000 -01-07 0.205753 2000 -01-08 0.265532 2000 -01-09 -0.039236 2000 -01-10 0.215647 2000 -01-11 -0.646638 2000 -01-12 0.390420 2000 -01-13 2.390138 2000 -01-14 0.107671 2000 -01-15 0.925517 2000 -01-16 1.692496 2000 -01-17 -2.251996 2000 -01-18 1.684247 2000 -01-19 0.450169 2000 -01-20 0.408878 2000 -01-21 0.798527 2000 -01-22 0.027421 2000 -01-23 0.046561 2000 -01-24 -0.302578 2000 -01-25 1.213720 2000 -01-26 0.524915 2000 -01-27 0.432314 2000 -01-28 1.183822 2000 -01-29 -2.538193 2000 -01-30 -0.861307 2000 -01-31 0.186349 Freq: D, dtype: float64 >>> In[]:ts_long['2000-01-21' :'2000-03' ] >>> Out[]:2000 -01-21 0.798527 2000 -01-22 0.027421 2000 -01-23 0.046561 2000 -01-24 -0.302578 2000 -01-25 1.213720 ... 2000 -03-27 -2.134014 2000 -03-28 -0.800815 2000 -03-29 1.472516 2000 -03-30 -1.901447 2000 -03-31 -0.238811 Freq: D, Length: 71 , dtype: float64
也可以由datetime对象进行索引
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 >>> In[]:ts_long[datetime(2000 ,1 ,21 ):] >>> Out[]:2000 -01-21 0.798527 2000 -01-22 0.027421 2000 -01-23 0.046561 2000 -01-24 -0.302578 2000 -01-25 1.213720 ... 2001 -05-10 -0.513777 2001 -05-11 0.813053 2001 -05-12 0.508245 2001 -05-13 -1.782211 2001 -05-14 1.427013 Freq: D, Length: 480 , dtype: float64
由于时间序列一般是排序的,因此我们也可以使用两个不存在的时间对象进行索引
1 2 3 4 5 6 7 8 9 >>> In[]:ts['2011-01-03' :'2011-01-11' ] >>> Out[]:2011 -01-05 2 2011 -01-07 1 2011 -01-08 2 2011 -01-10 2 dtype: int64
即便时间序列索引是无序的,此时pandas也会按照时间序列的顺序对其进行切片
1 2 3 4 5 6 7 >>> In[]:ts.sort_values()['2011-01-09' :'2011-01-12' ] >>> Out[]:2011 -01-10 2 2011 -01-12 5 dtype: int64
更好的办法是,首先使用sort_values方法对其进行排序,然后再进行切片
1 2 3 4 5 6 7 >>> In[]:ts[ts.index.sort_values()]['2011-01-09' :'2011-01-12' ] >>> Out[]:2011 -01-10 2 2011 -01-12 5 dtype: int64
使用truncate方法完全可以达到相同效果,before参数相当于开始,after参数相当于结尾
1 2 3 4 5 6 7 8 9 >>> In[]:ts.truncate(before='2011-01-06' ,after='2011-01-12' ) >>> Out[]:2011 -01-07 1 2011 -01-08 2 2011 -01-10 2 2011 -01-12 5 dtype: int64
重复的时间索引
pandas是允许索引的值存在重复的,如果遇到了这种情况,那么甚至可以对时间索引进行分组聚合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 >>> In[]:dates = pd.DatetimeIndex(["2000-01-01" , "2000-01-02" , "2000-01-02" ,"2000-01-02" , "2000-01-03" ]) dup_ts = pd.Series(np.arange(5 ), index=dates) dup_ts >>> Out[]:2000 -01-01 0 2000 -01-02 1 2000 -01-02 2 2000 -01-02 3 2000 -01-03 4 dtype: int64 >>> In[]:dup_ts['2000-01-01' ] >>> Out[]: 0 >>> In[]:dup_ts['2000-01-02' ] >>> Out[]:2000 -01-02 1 2000 -01-02 2 2000 -01-02 3 dtype: int64 >>> In[]:dup_ts.groupby(level=0 ).count() >>> Out[]:2000 -01-01 1 2000 -01-02 3 2000 -01-03 1 dtype: int64
时间范围
padnas中的时间范围函数pd.date_range的作用是按照固定的频率产生一定范围内的DatetimeIndex对象,也即是时间戳对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 >>> In[]:index = pd.date_range("2012-04-01" , "2012-06-01" ) index >>> Out[]:DatetimeIndex(['2012-04-01' , '2012-04-02' , '2012-04-03' , '2012-04-04' , '2012-04-05' , '2012-04-06' , '2012-04-07' , '2012-04-08' , '2012-04-09' , '2012-04-10' , '2012-04-11' , '2012-04-12' , '2012-04-13' , '2012-04-14' , '2012-04-15' , '2012-04-16' , '2012-04-17' , '2012-04-18' , '2012-04-19' , '2012-04-20' , '2012-04-21' , '2012-04-22' , '2012-04-23' , '2012-04-24' , '2012-04-25' , '2012-04-26' , '2012-04-27' , '2012-04-28' , '2012-04-29' , '2012-04-30' , '2012-05-01' , '2012-05-02' , '2012-05-03' , '2012-05-04' , '2012-05-05' , '2012-05-06' , '2012-05-07' , '2012-05-08' , '2012-05-09' , '2012-05-10' , '2012-05-11' , '2012-05-12' , '2012-05-13' , '2012-05-14' , '2012-05-15' , '2012-05-16' , '2012-05-17' , '2012-05-18' , '2012-05-19' , '2012-05-20' , '2012-05-21' , '2012-05-22' , '2012-05-23' , '2012-05-24' , '2012-05-25' , '2012-05-26' , '2012-05-27' , '2012-05-28' , '2012-05-29' , '2012-05-30' , '2012-05-31' , '2012-06-01' ], dtype='datetime64[ns]' , freq='D' )
如果仅指定开始或者结尾,那么就还需要传入生成的时间戳数量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 >>> In[]:pd.date_range(start="2012-04-01" , periods=20 ) >>> Out[]:DatetimeIndex(['2012-04-01' , '2012-04-02' , '2012-04-03' , '2012-04-04' , '2012-04-05' , '2012-04-06' , '2012-04-07' , '2012-04-08' , '2012-04-09' , '2012-04-10' , '2012-04-11' , '2012-04-12' , '2012-04-13' , '2012-04-14' , '2012-04-15' , '2012-04-16' , '2012-04-17' , '2012-04-18' , '2012-04-19' , '2012-04-20' ], dtype='datetime64[ns]' , freq='D' ) >>> In[]:pd.date_range(end="2012-6-30" , periods=20 ) >>> Out[]:DatetimeIndex(['2012-06-11' , '2012-06-12' , '2012-06-13' , '2012-06-14' , '2012-06-15' , '2012-06-16' , '2012-06-17' , '2012-06-18' , '2012-06-19' , '2012-06-20' , '2012-06-21' , '2012-06-22' , '2012-06-23' , '2012-06-24' , '2012-06-25' , '2012-06-26' , '2012-06-27' , '2012-06-28' , '2012-06-29' , '2012-06-30' ], dtype='datetime64[ns]' , freq='D' )
也可以指定生成时间的频率,pandas提供了非常丰富的时间频率参数,经常使用的如下:
D: 天
H: 时
T or min: 分
S: 秒
B: 工作日
M: 每月的最后一天
BM: 每月的最后一个工作日(每个月最晚的周五)
MS: 每月的第一天
BMS: 每月的第一个工作日(每个月最早的周一)
W-MON: 指定具星期几(MON,TUE,WED,THU,FRI,SAT,SUN)
WOM-1MON: 指定每个月的第几个星期几
完整的表格如下所示:
Alias
Offset type
Description
D
Day
Calendar daily
B
BusinessDay
Business daily
H
Hour
Hourly
T or min
Minute
Once a minute
S
Second
Once a second
L or ms
Milli
Millisecond (1/1,000 of 1 second)
U
Micro
Microsecond (1/1,000,000 of 1 second)
M
MonthEnd
Last calendar day of month
BM
BusinessMonthEnd
Last business day (weekday) of month
MS
MonthBegin
First calendar day of month
BMS
BusinessMonthBegin
First weekday of month
W-MON, W-TUE, …
Week
Weekly on given day of week (MON, TUE, WED, THU, FRI, SAT, or SUN)
WOM-1MON, WOM-2MON, …
WeekOfMonth
Generate weekly dates in the first, second, third, or fourth week of the month (e.g., WOM-3FRI for the third Friday of each month)
Q-JAN, Q-FEB, …
QuarterEnd
Quarterly dates anchored on last calendar day of each month, for year ending in indicated month (JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, or DEC)
BQ-JAN, BQ-FEB, …
BusinessQuarterEnd
Quarterly dates anchored on last weekday day of each month, for year ending in indicated month
QS-JAN, QS-FEB, …
QuarterBegin
Quarterly dates anchored on first calendar day of each month, for year ending in indicated month
BQS-JAN, BQS-FEB, …
BusinessQuarterBegin
Quarterly dates anchored on first weekday day of each month, for year ending in indicated month
A-JAN, A-FEB, …
YearEnd
Annual dates anchored on last calendar day of given month (JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, or DEC)
BA-JAN, BA-FEB, …
BusinessYearEnd
Annual dates anchored on last weekday of given month
AS-JAN, AS-FEB, …
YearBegin
Annual dates anchored on first day of given month
BAS-JAN, BAS-FEB, …
BusinessYearBegin
Annual dates anchored on first weekday of given month
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 >>> In[]:pd.date_range(datetime.now(),periods=10 ,freq='H' ) >>> Out[]:DatetimeIndex(['2023-09-19 15:38:21.557455' , '2023-09-19 16:38:21.557455' , '2023-09-19 17:38:21.557455' , '2023-09-19 18:38:21.557455' , '2023-09-19 19:38:21.557455' , '2023-09-19 20:38:21.557455' , '2023-09-19 21:38:21.557455' , '2023-09-19 22:38:21.557455' , '2023-09-19 23:38:21.557455' , '2023-09-20 00:38:21.557455' ], dtype='datetime64[ns]' , freq='H' ) >>> In[]:pd.date_range('2023-03-01' ,'2023-12-31' ,freq='BM' ) >>> Out[]:DatetimeIndex(['2023-03-31' , '2023-04-28' , '2023-05-31' , '2023-06-30' , '2023-07-31' , '2023-08-31' , '2023-09-29' , '2023-10-31' , '2023-11-30' , '2023-12-29' ], dtype='datetime64[ns]' , freq='BM' ) >>> In[]:pd.date_range('2023-03-01' ,'2023-12-31' ,freq='M' ) >>> Out[]:DatetimeIndex(['2023-03-31' , '2023-04-30' , '2023-05-31' , '2023-06-30' , '2023-07-31' , '2023-08-31' , '2023-09-30' , '2023-10-31' , '2023-11-30' , '2023-12-31' ], dtype='datetime64[ns]' , freq='M' )
找出 2023-03-01~2023-12-31的所有周五
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 >>> In[]:pd.date_range('2023-03-01 12:30' ,'2023-12-31 12:30' ,freq='W-FRI' ) >>> Out[]:DatetimeIndex(['2023-03-03 12:30:00' , '2023-03-10 12:30:00' , '2023-03-17 12:30:00' , '2023-03-24 12:30:00' , '2023-03-31 12:30:00' , '2023-04-07 12:30:00' , '2023-04-14 12:30:00' , '2023-04-21 12:30:00' , '2023-04-28 12:30:00' , '2023-05-05 12:30:00' , '2023-05-12 12:30:00' , '2023-05-19 12:30:00' , '2023-05-26 12:30:00' , '2023-06-02 12:30:00' , '2023-06-09 12:30:00' , '2023-06-16 12:30:00' , '2023-06-23 12:30:00' , '2023-06-30 12:30:00' , '2023-07-07 12:30:00' , '2023-07-14 12:30:00' , '2023-07-21 12:30:00' , '2023-07-28 12:30:00' , '2023-08-04 12:30:00' , '2023-08-11 12:30:00' , '2023-08-18 12:30:00' , '2023-08-25 12:30:00' , '2023-09-01 12:30:00' , '2023-09-08 12:30:00' , '2023-09-15 12:30:00' , '2023-09-22 12:30:00' , '2023-09-29 12:30:00' , '2023-10-06 12:30:00' , '2023-10-13 12:30:00' , '2023-10-20 12:30:00' , '2023-10-27 12:30:00' , '2023-11-03 12:30:00' , '2023-11-10 12:30:00' , '2023-11-17 12:30:00' , '2023-11-24 12:30:00' , '2023-12-01 12:30:00' , '2023-12-08 12:30:00' , '2023-12-15 12:30:00' , '2023-12-22 12:30:00' , '2023-12-29 12:30:00' ], dtype='datetime64[ns]' , freq='W-FRI' )
找出 2023-03-01~2023-12-31中每个月的第三个周日
1 2 3 4 5 6 7 8 9 >>> In[]:pd.date_range('2023-03-01' ,'2023-12-31' ,freq='WOM-3SUN' ) >>> Out[]:DatetimeIndex(['2023-03-19' , '2023-04-16' , '2023-05-21' , '2023-06-18' , '2023-07-16' , '2023-08-20' , '2023-09-17' , '2023-10-15' , '2023-11-19' , '2023-12-17' ], dtype='datetime64[ns]' , freq='WOM-3SUN' )
找出每个季度末尾的那天.默认情况下,按照1月为第一季度开始
1 2 3 4 5 >>> In[]:pd.date_range('2023-03-01' ,'2023-12-31' ,freq='Q' ) >>> Out[]:DatetimeIndex(['2023-03-31' , '2023-06-30' , '2023-09-30' , '2023-12-31' ], dtype='datetime64[ns]' , freq='Q-DEC' )
指定2月为季度的结尾,并在此基础上,找出某段时间内的每个季度的末尾一天.此时要注意,date_range方法产生时间戳是包含两端的
1 2 3 4 5 >>> In[]:pd.date_range('2023-02-28' ,'2023-12-31' ,freq='Q-FEB' ) >>> Out[]:DatetimeIndex(['2023-02-28' , '2023-05-31' , '2023-08-31' , '2023-11-30' ], dtype='datetime64[ns]' , freq='Q-FEB' )
指定3月份为每年的开始,并且在此基础上求出时间范围内每年的第一个工作日. 注意,此处由于2025年的3月1日以及2日是周末,因此不计入工作日的范畴内
1 2 3 4 5 >>> In[]:pd.date_range('2023-02-28' ,'2025-12-31' ,freq='BAS-MAR' ) >>> Out[]:DatetimeIndex(['2023-03-01' , '2024-03-01' , '2025-03-03' ], dtype='datetime64[ns]' , freq='BAS-MAR' )
日期偏移
date_range中的freq参数本质上传递了一个时间偏移对象,在pandas中可以以如下方式创建时间偏移对象:
1 from pandas.tseries.offsets import Hour,Minute
创建一个1小时的时间偏移对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 >>> In[]:hour = Hour() hour >>> Out[]: <Hour> >>> In[]:minutes = Minute(30 ) minutes >>> Out[]: <30 * Minutes>
时间偏移对象是可以进行算术加减的
1 2 3 4 5 6 >>> In[]:hour+3 *minutes >>> Out[]: <150 * Minutes>
可以使用时间偏移对象进行date_range
1 2 3 4 5 6 7 8 9 >>> In[]:pd.date_range('2023-09-15' ,periods=5 ,freq=hour+3 *minutes) >>> Out[]:DatetimeIndex(['2023-09-15 00:00:00' , '2023-09-15 02:30:00' , '2023-09-15 05:00:00' , '2023-09-15 07:30:00' , '2023-09-15 10:00:00' ], dtype='datetime64[ns]' , freq='150T' )
大部分情况下,我们并不会这样做,而是直接传递字符串即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 >>> In[]:pd.date_range('2023-09-15' ,periods=5 ,freq='3H' ) >>> Out[]:DatetimeIndex(['2023-09-15 00:00:00' , '2023-09-15 03:00:00' , '2023-09-15 06:00:00' , '2023-09-15 09:00:00' , '2023-09-15 12:00:00' ], dtype='datetime64[ns]' , freq='3H' ) >>> In[]:pd.date_range('2023-09-15' ,periods=5 ,freq='90min' ) >>> Out[]:DatetimeIndex(['2023-09-15 00:00:00' , '2023-09-15 01:30:00' , '2023-09-15 03:00:00' , '2023-09-15 04:30:00' , '2023-09-15 06:00:00' ], dtype='datetime64[ns]' , freq='90T' ) >>> In[]:pd.date_range('2023-09-15' ,periods=5 ,freq='2M' ) >>> Out[]:DatetimeIndex(['2023-09-30' , '2023-11-30' , '2024-01-31' , '2024-03-31' , '2024-05-31' ], dtype='datetime64[ns]' , freq='2M' )
时间段对象
时间段对象(Period)是除了时间戳对象之外的另一种 基本时间数据类型,它表示的是某段时间。时段对象的创建可以由pd.Period方法创建,输入的参数就是一个时间戳(表示时段开始时间)以及一个时间频率参数(表示时段持续时间)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 >>> In[]:pd.Period('2022-1-1' ,'3D' ) >>> Out[]:Period('2022-01-01' , '3D' ) >>> In[]:pd.Period('2022-3-5' ,'A-MAY' ).asfreq("D" ,how='start' ) >>> Out[]:Period('2021-06-01' , 'D' )
可以对时段对象进行加减算数操作,表示将时段进行偏置(offset)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 >>> In[]:p = pd.Period('2022' ,'A-MAY' ) p+1 >>> Out[]:Period('2023' , 'A-MAY' ) >>> In[]:p-2 >>> Out[]:Period('2020' , 'A-MAY' )
同样的,也可以使用pd.period_range也可以生成多个时段对象,包含多个时段对象的对象称为PeriodIndex(类似于包含多个时间戳对象的DateTimeIndex)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 >>> In[]:pd.period_range('2022-2' ,'2023-3' ,freq = 'M' ) >>> Out[]:PeriodIndex(['2022-02' , '2022-03' , '2022-04' , '2022-05' , '2022-06' , '2022-07' , '2022-08' , '2022-09' , '2022-10' , '2022-11' , '2022-12' ,'2023-01' , '2023-02' , '2023-03' ], dtype='period[M]' ) >>> In[]: dt = pd.Series(np.random.randn(14 ),index = pd.period_range('2022-2' ,'2023-3' ,freq = 'M' )) dt >>> Out[]:2022 -02 -1.068388 2022 -03 1.438250 2022 -04 1.372934 2022 -05 0.592564 2022 -06 -1.945797 2022 -07 -0.354653 2022 -08 -0.576030 2022 -09 -0.939876 2022 -10 -0.952067 2022 -11 0.432889 2022 -12 1.168628 2023 -01 0.997162 2023 -02 -0.104164 2023 -03 -0.384355 Freq: M, dtype: float64
时段频率转换
时段对象的频率使用asfreq是可以转换的
1 2 3 4 5 6 7 8 >>> In[]: p = pd.Period('2022' ,freq = 'A-DEC' ) p.asfreq('M' ,how='start' ) >>> Out[]:Period('2022-01' , 'M' )
时间戳与时断对象的转换
时间戳转换为时段对象可以使用pd.to_period方法,时段对象转换为时间吹则可以使用pd.to_timestamp 对象
1 2 3 4 5 6 7 >>> In[]: ts = pd.to_datetime(['2022-1' ,'2022-2' ,'2022-3' ,'2022-4' ]) p = ts.to_period() p >>> Out[]:PeriodIndex(['2022-01' , '2022-02' , '2022-03' , '2022-04' ], dtype='period[M]' )
默认情况下,转换为时段对象的频率是自动确认,从时间戳对象中推算出来的,但也是可以手动指定频率的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 >>> In[]: ts = pd.date_range('2022-1-28' ,'2022-2-5' ,freq='D' ) dt = pd.Series(np.random.randn(len (ts)),index=ts) dt >>> Out[]:2022 -01-28 0.613319 2022 -01-29 0.396804 2022 -01-30 -0.544125 2022 -01-31 -0.357917 2022 -02-01 1.536737 2022 -02-02 2.057696 2022 -02-03 -0.730858 2022 -02-04 0.391824 2022 -02-05 -0.888997 Freq: D, dtype: float64
由于指定了频率为月,因此时间戳中的日期数据都被抛弃了.当索引是时间戳的时候,也可以直接对Series或者DataFrame对象使用to_period方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 >>> In[]: dt.to_period(freq="M" ) >>> Out[]:2022 -01 0.613319 2022 -01 0.396804 2022 -01 -0.544125 2022 -01 -0.357917 2022 -02 1.536737 2022 -02 2.057696 2022 -02 -0.730858 2022 -02 0.391824 2022 -02 -0.888997 Freq: M, dtype: float64 >>> In[]:p = pd.period_range('2022-3' ,'2023-2' ,freq='M' ) dt = pd.Series(np.random.randn(len (p)),index = p) dt >>> Out[]:2022 -03 -2.274375 2022 -04 0.626871 2022 -05 -1.011548 2022 -06 2.227270 2022 -07 -0.477682 2022 -08 -0.244246 2022 -09 0.468265 2022 -10 0.195185 2022 -11 0.081317 2022 -12 -1.097176 2023 -01 -0.100736 2023 -02 -1.200283 Freq: M, dtype: float64
由于时段对象表示一段时间,而时间戳对象表示一个时间点,因此将时段转化为时间戳时.需要使用how参数指定转化到该时段对象的开始或者结束点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 >>> In[]:dt.to_timestamp(how='end' ) >>> Out[]:2022 -03-31 23 :59 :59.999999999 -2.274375 2022 -04-30 23 :59 :59.999999999 0.626871 2022 -05-31 23 :59 :59.999999999 -1.011548 2022 -06-30 23 :59 :59.999999999 2.227270 2022 -07-31 23 :59 :59.999999999 -0.477682 2022 -08-31 23 :59 :59.999999999 -0.244246 2022 -09-30 23 :59 :59.999999999 0.468265 2022 -10 -31 23 :59 :59.999999999 0.195185 2022 -11 -30 23 :59 :59.999999999 0.081317 2022 -12 -31 23 :59 :59.999999999 -1.097176 2023 -01-31 23 :59 :59.999999999 -0.100736 2023 -02-28 23 :59 :59.999999999 -1.200283 dtype: float64
重采样
重采样意味着对一个时序数据的频率进行重新规定,由高频向低频的重采样称为向下采样,由低频向高频的采样,称为向上采样。 向下采样的过程是数据分组聚合的过程,比如由月到年的重采,那么就会将所有同属一年的月份分为一组,然后可以进行求平均等聚合方法。向上采样则不会引起数据分组的过程。
时间戳的向下采样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 >>> In[]:ts = pd.date_range('2000-1-1' ,periods=100 ) tsdt = pd.Series(np.random.randn(len (ts)),index=ts) tsdt >>> Out[]:2000 -01-01 -0.383793 2000 -01-02 2.075331 2000 -01-03 0.203583 2000 -01-04 -0.729329 2000 -01-05 -0.678031 ... 2000 -04-05 -0.278502 2000 -04-06 1.329450 2000 -04-07 0.563900 2000 -04-08 0.996625 2000 -04-09 0.548595 Freq: D, Length: 100 , dtype: float64
将时间序列进行重采样,频率为.默认情况下,采样后的时间数据类型和采样前保持一致.采样前是时间戳,采样后也是时间戳,因此采样后的时间只能保持在该月份的某个时间点
1 2 3 4 5 6 7 8 9 >>> In[]:tsdt.resample('M' ).mean() >>> Out[]:2000 -01-31 0.122733 2000 -02-29 -0.076518 2000 -03-31 -0.238215 2000 -04-30 0.384449 Freq: M, dtype: float64
也可以使用kind参数,指定采样后的时间数据类型为时段.这样,就不会再表示该月份中的某个时间点,而是表示该月本身,其实当时间戳数据向下采样时.这种方式是更为合理的
1 2 3 4 5 6 7 8 9 >>> In[]:tsdt.resample('M' ,kind = 'period' ).mean() >>> Out[]:2000 -01 0.122733 2000 -02 -0.076518 2000 -03 -0.238215 2000 -04 0.384449 Freq: M, dtype: float64
1 2 tsdt.resample('M' ).agg(['mean' ,'median' ])
mean
median
2000-01-31
0.122733
0.076847
2000-02-29
-0.076518
-0.269821
2000-03-31
-0.238215
-0.236879
2000-04-30
0.384449
0.548595
向下采样相当于按照低频率的时间将时间序列数据分成了一个个区间.每个时序数据存在且仅存在于某个区间中,因此向下采样的区间是half-open的.默认情况下,区间是左闭右开的,在这种情况下,此时的区间是 [2000-01-01,2000-02-01),[2000-02-01,2000-03-01)…
1 2 3 4 5 6 7 8 9 >>> In[]:tsdt.resample("MS" ).mean() >>> Out[]:2000 -01-01 0.122733 2000 -02-01 -0.076518 2000 -03-01 -0.238215 2000 -04-01 0.384449 Freq: MS, dtype: float64
如果设置区间为左开右闭,则此时的区间表示为(1999-12-01,2000-01-01],(2000-01-01,2000-02-01],(2000-02-01,2000-03-01]…值得注意的是,由于此时设置区间左端为开,因此2000-01-01是不被包含在第二个区间的.因此,还必须设置区间 (1999-12-01,2000-01-01] 来包含这个日期.还有一点需要注意的是,由于MS表示每个月日历开始的那一天,因此完整的区间是一个月的第一天到下个月的第一天
1 2 3 4 5 6 7 8 9 10 >>> In[]:tsdt.resample("MS" ,closed='right' ).mean() >>> Out[]:1999 -12 -01 -0.383793 2000 -01-01 0.114894 2000 -02-01 -0.019938 2000 -03-01 -0.299048 2000 -04-01 0.541478 Freq: MS, dtype: float64
closed参数用来决定区间的左右开闭,如果想要采样以后调整索引的显示,可以如下操作
所谓label=right,本质上 是在将每区间中右边的值作为采样结果的新索引(不管这个值是开还是闭)
1 2 3 4 5 6 7 8 9 10 11 >>> In[]:tsdt.resample('MS' ,closed='right' , label='right' ).mean() >>> Out[]:2000 -01-01 -0.383793 2000 -02-01 0.114894 2000 -03-01 -0.019938 2000 -04-01 -0.299048 2000 -05-01 0.541478 Freq: MS, dtype: float64
ohlc
在金融数据分析中,ohlc表示某股票在市场一天中价格的 open high low close的值,及开盘价,最高价,最低价和收盘价,pandas内置了用来求某时段的ohlc方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 >>> In[]:ts = pd.date_range('2000-01-01' ,periods=100 ,freq='D' ) tsdt = pd.Series(np.random.randn(100 ),index=ts) tsdt >>> Out[]:2000 -01-01 1.312267 2000 -01-02 -0.723157 2000 -01-03 0.300057 2000 -01-04 0.565059 2000 -01-05 0.641482 ... 2000 -04-05 0.448233 2000 -04-06 1.654772 2000 -04-07 0.134817 2000 -04-08 2.054210 2000 -04-09 -1.363439 Freq: D, Length: 100 , dtype: float64
统计出[2000-01-01,2000-02-01)间数据的 ohlc值
1 2 tsdt.resample('MS' ).ohlc()
open
high
low
close
2000-01-01
1.312267
1.482948
-1.607714
0.343055
2000-02-01
1.271338
1.459851
-1.642738
-0.924122
2000-03-01
0.475739
2.475645
-1.888443
0.621041
2000-04-01
2.655665
2.655665
-1.577870
-1.363439
1 tsdt.resample('M' ,kind='period' ).ohlc()
open
high
low
close
2000-01
1.312267
1.482948
-1.607714
0.343055
2000-02
1.271338
1.459851
-1.642738
-0.924122
2000-03
0.475739
2.475645
-1.888443
0.621041
2000-04
2.655665
2.655665
-1.577870
-1.363439
多级索引下的分组
resample当向下采样时,本质是对时间索引做了一个分组,但是如果还有另外的列需要分组时,这是就需要使用pd.Grouper方法对时间数据进行预设分组,而后再使用groupby方法
1 2 3 4 5 times = pd.date_range("2017-05-20 00:00" , freq="1min" , periods=15 ) df = pd.DataFrame({"time" : times.repeat(3 ), "key" : np.tile(["a" , "b" , "c" ], 15 ), "value" : np.arange(15 * 3. )}) df.head(9 )
time
key
value
0
2017-05-20 00:00:00
a
0.0
1
2017-05-20 00:00:00
b
1.0
2
2017-05-20 00:00:00
c
2.0
3
2017-05-20 00:01:00
a
3.0
4
2017-05-20 00:01:00
b
4.0
5
2017-05-20 00:01:00
c
5.0
6
2017-05-20 00:02:00
a
6.0
7
2017-05-20 00:02:00
b
7.0
8
2017-05-20 00:02:00
c
8.0
需要对time进行分组重采样,也需要对key进行分组,在这种情况下, 我们首先使用Grouper方法,预设一个频率为5分钟的分组条件.Grouper相当于是在脱离具体时间序列的条件下,规定了重采样的规则.
1 2 3 4 5 6 >>> In[]:tk = pd.Grouper(freq='5T' ,closed='left' ,label = 'left' ) tk >>> Out[]:TimeGrouper(freq=<5 * Minutes>, axis=0 , sort=True , dropna=True , closed='left' , label='left' , how='mean' , convention='e' , origin='start_day' )
进行真正的分组
1 2 3 4 5 6 7 8 >>> In[]:df.groupby(['key' ,tk]) >>> Out[]:TypeError: Only valid with DatetimeIndex, TimedeltaIndex or PeriodIndex, but got an instance of 'RangeIndex'
下载链接
Pandas时间序列分析