pandas之时间序列

对于时间序列的处理在数据处理方面还是比较重要的一块。这篇来记录一下时间序列的一些知识点。

python标准库包含日期(date)和时间(time)数据的数据类型。

经常使用的也就是datetime、time以及calendar模块。datetime以毫秒形式存储日期和时间。

datetime模块中的数据类型
date 以公历形式存储日历日期(年、月、日)
time 将时间存储为时、分、秒、毫秒
datetime 存储日期和时间
timedelta 表示两个datetime值之间的差(日、秒、毫秒)
In [5]: from datetime import datetime
In [6]: now = datetime.now()
In [7]: now
Out[7]: datetime.datetime(2018, 6, 25, 14, 38, 8, 361612)
In [9]: now.year
Out[9]: 2018
In [10]: now.month
Out[10]: 6
In [11]: now.day
Out[11]: 25

datetime.timedelta表示两个datetime对象之间的时间差。给datetime对象加上或减去一个或多个timedelta,会产生一个新的对象。

In [13]: delta = now - datetime(1995,6,26,20,10)
In [14]: delta
Out[14]: datetime.timedelta(8399, 66488, 361612)
In [15]: delta.days
Out[15]: 8399
In [16]: delta.seconds
Out[16]: 66488
字符串和datetime之间的转换

利用str 或strftime(传入一个格式化的字符串) 将日期转换为字符串,

利用 datetime.strptime 将字符串转换为日期,这种方式是已知格式对日期解析的最佳方式。

In [17]: str(now)
Out[17]: '2018-06-25 14:38:08.361612'

In [18]: now.strftime('%Y-%m-%d')
Out[18]: '2018-06-25'

In [19]: str_date = '2018-06-25'
In [20]: datetime.strptime(str_date,'%Y-%m-%d')
Out[20]: datetime.datetime(2018, 6, 25, 0, 0)

datetime 格式定义:

%Y

4位数的年

%y

2位数的年

%m

2位数的月[01,12]

%d

2位数的日[01,31]

%H

时(24小时制)[00,23]

%l

时(12小时制)[01,12]

%M

2位数的分[00,59]

%S

秒[00,61]有闰秒的存在

%w

用整数表示的星期几[0(星期天),6]

%F

%Y-%m-%d简写形式例如,2017-06-27

%D

%m/%d/%y简写形式

一些第三方库也有对时间的解析:

In [21]: from dateutil.parser import parse
In [22]: parse(str_date)
Out[22]: datetime.datetime(2018, 6, 25, 0, 0)

有些日期日出现在月前面传入  dayfirst=True 即可解决:

In [23]: parse('25/6/2018',dayfirst=True)
Out[23]: datetime.datetime(2018, 6, 25, 0, 0)

pandas 中对日期解析的方法 to_datetime 可以解析多种不同类型的日期表示形式:

 In [25]: date =  ['2018-6-26', '2018-6-25']
In [26]: pd.to_datetime(date)
Out[26]: DatetimeIndex(['2018-06-26', '2018-06-25'], dtype='dat
etime64[ns]', freq=None)

pandas中的时间序列基础

In [51]: dates = ['2018-06-20','2018-06-21','2018-06-22','2018
    ...: -06-23','2018-06-24','2018-06-25','2018-06-26','2018-
    ...: 06-27']
In [52]: ts = pd.Series(np.random.randn(8),index=pd.to_datetim
    ...: e(dates))
In [53]: ts
Out[53]: 
2018-06-20    0.116617
2018-06-21   -0.160468
2018-06-22   -0.457980
2018-06-23   -0.994871
2018-06-24    1.028115
2018-06-25   -0.223018
2018-06-26    1.264067
2018-06-27    0.054394
dtype: float64
In [54]: ts.index
Out[54]: 
DatetimeIndex(['2018-06-20', '2018-06-21', '2018-06-22', '2018-06-23','2018-06-24', '2018-06-25',
'2018-06-26', '2018-06-27'],dtype='datetime64[ns]', freq=None)

pandas不同索引的时间序列之间的算术运算会自动按日期对齐

In [56]: ts + ts[::2]
Out[56]: 
2018-06-20    0.233233
2018-06-21         NaN
2018-06-22   -0.915960
2018-06-23         NaN
2018-06-24    2.056230
2018-06-25         NaN
2018-06-26    2.528134
2018-06-27         NaN
dtype: float64
时间序列的索引、选取方法:

1)一般索引,传入对应的index

In [57]: ts[ts.index[3]]
Out[57]: -0.9948705675890571

2)一个可以被解析为日期的字符串

In [58]: ts['2018-06-26 ']
Out[58]: 1.264066795280468

3)对于,较长的时间序列,只需传入‘年’或‘年月’可返回对应的数据切片

In [59]: ts['2018-06':]
Out[59]: 
2018-06-20    0.116617
2018-06-21   -0.160468
2018-06-22   -0.457980
2018-06-23   -0.994871
2018-06-24    1.028115
2018-06-25   -0.223018
2018-06-26    1.264067
2018-06-27    0.054394
dtype: float64
4)通过时间范围进行切片索引
In [60]: ts['2018-06-21':'2018-06-25']
Out[60]: 
2018-06-21   -0.160468
2018-06-22   -0.457980
2018-06-23   -0.994871
2018-06-24    1.028115
2018-06-25   -0.223018
dtype: float64
日期的范围、频率及移动

       有的时候我们需要对包含时间的数据按照相对固定的时间频率进行分析,因此pandas中就提供了一套标准时间序列频率以及用于重采样、频率推断、生成固定频率日期范围的工具。

生成日期范围:pandas.date_arnge 生成指定长度的 DatetimeIndex

pandas.date_range(start=None, end=None, periods=None, freq='D',
                   tz=None, normalize=False, name=None, closed=None, **kwargs)
           start : str or datetime-like, optional Left bound for generating dates.
             end : str or datetime-like, optional Right bound for generating dates.
         periods : integer, optional Number of periods to generate.
            freq : str or DateOffset, default ‘D’ (calendar daily) Frequency strings can
                   have multiples, e.g. ‘5H’. See here for a list of frequency aliases.
             tz : str or tzinfo, optional Time zone name for returning localized DatetimeIndex, for example
                  ‘Asia/Hong_Kong’. By default, the resulting DatetimeIndex is timezone-naive.
      normalize : bool, default False Normalize start/end dates to midnight before generating date range.
           name : str, default None Name of the resulting DatetimeIndex.
         closed : {None, ‘left’, ‘right’}, optional Make the interval closed with respect to the given
                  frequency to the ‘left’, ‘right’, or both sides (None, the default).
        **kwargs  For compatibility. Has no effect on the result.

In [188]: date_index = pd.date_range(start='2018-6-27',periods=6)
In [189]: date_index
Out[189]:
DatetimeIndex(['2018-06-27', '2018-06-28', '2018-06-29', '2018-06-30',
               '2018-07-01', '2018-07-02'],dtype='datetime64[ns]', freq='D')
In [190]: date_index = pd.date_range(start='2018-6-27 14:31',periods=6)  # 带时间的保留时间
In [191]: date_index
Out[191]: 
DatetimeIndex(['2018-06-27 14:31:00', '2018-06-28 14:31:00',
               '2018-06-29 14:31:00', '2018-06-30 14:31:00',
               '2018-07-01 14:31:00', '2018-07-02 14:31:00'],
              dtype='datetime64[ns]', freq='D')

In [192]: date_index = pd.date_range(start='2018-6-27 14:31',end='2019-01-01',freq='BM',normalize=True)
In [193]: date_index                               # 生成日期的频率参数  后面会学到各种时间频率
Out[193]: 
DatetimeIndex(['2018-06-29', '2018-07-31', '2018-08-31', '2018-09-28',
               '2018-10-31', '2018-11-30', '2018-12-31'],
               dtype='datetime64[ns]', freq='BM')
In [194]: date_index = pd.date_range(start='2018-6-27 14:31',periods=3,normalize=True) 
In [195]: date_index                         # 保留时间到午夜 好像不咋管用,就是不显示时间了呗
Out[195]: DatetimeIndex(['2018-06-27', '2018-06-28', '2018-06-29'], dtype='datetime64[ns]', freq='D')
频率和日期偏移

日期偏移对象在 pandas.tseries.offsets 之中

In [200]: import pandas.tseries.offsets as offs
In [201]: offs.Hour()
Out[201]: <Hour>
In [202]: offs.Hour(4)  # 4h 的偏移量
Out[202]: <4 * Hours>
In [203]: pd.date_range(start='2018-6-27 14:00:00',periods=3,freq='4h')  # 这种方式可以
Out[203]: 
DatetimeIndex(['2018-06-27 14:00:00', '2018-06-27 18:00:00',
               '2018-06-27 22:00:00'],
              dtype='datetime64[ns]', freq='4H')
In [204]: pd.date_range(start='2018-6-27 14:00:00',periods=3,freq='1h30min')  # 这种也可以
Out[204]: 
DatetimeIndex(['2018-06-27 14:00:00', '2018-06-27 15:30:00',
               '2018-06-27 17:00:00'],
              dtype='datetime64[ns]', freq='90T')

下面是pandas中的频率代码和日期偏移量类型:

别名 偏移量类型 说明
D Day 每日历日
B BusinessDay 每工作日
H Hour 每小时
T/min Minute 每分
S Second 每秒
L/ms Million 每毫秒
U Micro 每微妙
M MonthEnd 每月最后一个日历日
BM BusinessMonthEnd 每月最后一个工作日
MS MonthBegin 每月第一个日历日
BMS BusinessMonthBegin 每月第一个工作日
W-MON、W-TUE… Week 从指定的星期几开始算起,每周
WOM-1MON、WOM-2MON… WeekOfMonth 产生每月第一、二、三、四周的星期几,例如WOM-1MON表示每月的第一个星期一
Q-JAN、Q-FEB… QuarterEnd 对于以指定月份(JAN、FEB、…、DEC)结束的年度,每季度的最后一月的最后一个日历日
BQ-JAN、BQ-FEB… BusinessQuarterEnd 对于以指定月份(JAN、FEB、…、DEC)结束的年度,每季度的最后一月的最后一个工作日
QS-JAN、QS-FEB… QuarterBegin 对于以指定月份(JAN、FEB、…、DEC)结束的年度,每季度的最后一月的第一个日历日
BQS-JAN、BQS-FEB… BusinessQuarterBegin 对于以指定月份(JAN、FEB、…、DEC)结束的年度,每季度的最后一月的第一个工作日
A-JAN、A-FEB… YearEnd 每年指定月份最后一个日历日
BA-JAN、BA-FEB… BusinessYearEnd 每年指定月份最后一个工作日
AS-JAN、AS-FEB… YearBegin 每月指定月份第一个日历日
BAS-JAN、BAS-FEB… BusinessYearBegin 每月指定月份第一个工作日
In [206]: rng = pd.date_range('2018-1-1','2018-6-27',freq='WOM-3FRI')   # 每月第三个星期五
In [207]: rng
Out[207]: 
DatetimeIndex(['2018-01-19', '2018-02-16', '2018-03-16', '2018-04-20',
               '2018-05-18', '2018-06-15'],
               dtype='datetime64[ns]', freq='WOM-3FRI')
移动数据: shift,rollforward,rollback
In [209]: ts = pd.Series(np.random.randn(4),index=pd.date_rang e('1/1/2018', periods=4, freq='M'))
In [210]: ts
Out[210]: 
2018-01-31   -0.266415
2018-02-28    1.328264
2018-03-31   -0.938386
2018-04-30    1.874953
Freq: M, dtype: float64

In [211]: ts.shift(2)  # 前移
Out[211]: 
2018-01-31         NaN
2018-02-28         NaN
2018-03-31   -0.266415
2018-04-30    1.328264
Freq: M, dtype: float64

In [212]: ts.shift(-2)  # 后移
Out[212]: 
2018-01-31   -0.938386
2018-02-28    1.874953
2018-03-31         NaN
2018-04-30         NaN
Freq: M, dtype: float64

In [213]: ts.shift(2,freq='M')  # 带频率的移动
Out[213]: 
2018-03-31   -0.266415
2018-04-30    1.328264
2018-05-31   -0.938386
2018-06-30    1.874953
Freq: M, dtype: float64
shift 通常用于计算一个或多个时间序列的百分比变化: ts/ts.shift( ) - 1
如果加的是锚点偏移量(比如 MonthEnd 当月月底)第一次增量会将原日期向前滚动到符合频率规则的下一个日期:
In [214]: now = datetime.now()
In [215]: now
Out[215]: datetime.datetime(2018, 6, 26, 15, 30, 30, 259357)
In [216]: now + offs.MonthEnd()
Out[216]: Timestamp('2018-06-30 15:30:30.259357')
In [217]: off = offs.MonthEnd()
In [218]: off.rollforward(now)
Out[218]: Timestamp('2018-06-30 15:30:30.259357')
In [219]: off.rollback(now)
Out[219]: Timestamp('2018-05-31 15:30:30.259357')

这里还有个巧妙的用法,结合groupby 和滚动方法 :

In [223]: ts = pd.Series(np.random.randn(20),index=pd.date_range('1/15/2018', periods=20, freq='4d'))
In [224]: ts
Out[224]: 
2018-01-15   -0.071915
2018-01-19    2.403895
............
2018-04-01    1.046591
Freq: 4D, dtype: float64
In [225]: ts.groupby(off.rollforward).mean()
Out[225]: 
2018-01-31   -0.351028
2018-02-28   -0.495122
2018-03-31   -0.457727
2018-04-30    1.046591
更简单的方式:
In [229]: ts.resample('M').mean()
Out[229]: 
2018-01-31   -0.351028
2018-02-28   -0.495122
2018-03-31   -0.457727
2018-04-30    1.046591
Freq: M, dtype: float64

重采样及频率转换

重采样(resampling)指的是将时间序列从一个频率转换到另一频率的处理过程。

resample 参数:

参数 说明
freq 表示重采样频率,例如‘M’、‘5min’,Second(15)
how=’mean’            (已废弃) 用于产生聚合值的函数名或数组函数,例如‘mean’、‘ohlc’、np.max等,默认是‘mean’,其他常用的值由:‘first’、‘last’、‘median’、‘max’、‘min’
axis=0 默认是纵轴,横轴设置axis=1
fill_method = None 升采样时如何插值,比如‘ffill’、‘bfill’等
closed = ‘left’ 在降采样时,各时间段的哪一段是闭合的,‘right’或‘left’,默认‘left’
label= ‘left’ 在降采样时,如何设置聚合值的标签,例如,9:30-9:35会被标记成9:30还是9:35,默认9:35
loffset = None 面元标签的时间校正值,比如‘-1s’或Second(-1)用于将聚合标签调早1秒
limit=None 在向前或向后填充时,允许填充的最大时期数
kind = None 聚合到时期(‘period’)或时间戳(‘timestamp’),默认聚合到时间序列的索引类型
convention = None 当重采样时期时,将低频率转换到高频率所采用的约定(start或end)。默认‘end’
In [229]: ts.resample('M').mean()
Out[229]: 
2018-01-31   -0.351028
2018-02-28   -0.495122
2018-03-31   -0.457727
2018-04-30    1.046591
Freq: M, dtype: float64

降采样:将高频率聚合到低频率成为降采样

有一个频率为 1 min 的时间序列:

In [231]: rng = pd.date_range('2018-6-26',periods=10,freq='T')
In [232]: ts = pd.Series(np.random.randn(10),index=rng)
In [233]: ts
Out[233]: 
2018-06-26 00:00:00    0.616423
2018-06-26 00:01:00   -0.815095
2018-06-26 00:02:00   -0.593021
2018-06-26 00:03:00    1.388699
2018-06-26 00:04:00   -0.900769
2018-06-26 00:05:00   -1.941041
2018-06-26 00:06:00    0.264235
2018-06-26 00:07:00   -1.185064
2018-06-26 00:08:00    0.242785
2018-06-26 00:09:00    0.098842
Freq: T, dtype: float64

将时间序列聚合到 5mins :

In [253]: rng = pd.date_range('2018-6-26',periods=12,freq='T')
In [254]: ts = pd.Series(np.arange(12),index=rng)
In [255]: ts.resample('5min').sum()
Out[255]: 
2018-06-26 00:00:00    10
2018-06-26 00:05:00    35
2018-06-26 00:10:00    21
Freq: 5T, dtype: int32

In [256]: ts.resample('5min',closed='right').sum()  # 包含了右边界,以左边界为标记
Out[256]: 
2018-06-25 23:55:00     0
2018-06-26 00:00:00    15
2018-06-26 00:05:00    40
2018-06-26 00:10:00    11
Freq: 5T, dtype: int32

In [258]: ts.resample('5min',closed='left').sum()   # 包含了左边界,以左边界为标记
Out[258]: 
2018-06-26 00:00:00    10
2018-06-26 00:05:00    35
2018-06-26 00:10:00    21
Freq: 5T, dtype: int32

In [259]: ts.resample('5min',closed='right',label='right').sum()  # 包含了右边界,以右边界为标记
Out[259]: 
2018-06-26 00:00:00     0
2018-06-26 00:05:00    15
2018-06-26 00:10:00    40
2018-06-26 00:15:00    11
Freq: 5T, dtype: int32

In [260]: ts.resample('5min',closed='right',label='left').sum()
Out[260]: 
2018-06-25 23:55:00     0
2018-06-26 00:00:00    15
2018-06-26 00:05:00    40
2018-06-26 00:10:00    11
Freq: 5T, dtype: int32

通过groupby进行重采样

只需要传入一个能够访问时间序列的索引上的阻断的函数即可。

In [270]: rng = pd.date_range('2018-6-26',periods=100,freq='D')
In [271]: ts = pd.Series(np.arange(100),index=rng)
In [272]: ts.groupby(lambda x:x.month).mean()
Out[272]: 
6      2.0
7     20.0
8     51.0
9     81.5
10    98.0
dtype: float64

升采样和插值

将数据从低频转换到高频,就是升采样。升采样会引入缺失值,此时就需要插值处理。

插值方式跟 fillna 和reindex 一样。

In [299]: frame = pd.DataFrame(np.random.randn(2, 4),index=pd.
     ...: date_range('1/1/2000', periods=2, freq='W-WED'),colu
     ...: mns=['Colorado', 'Texas', 'New York', 'Ohio'])

In [300]: frame.resample('W-THU').ffill()
Out[300]: 
            Colorado     Texas  New York      Ohio
2000-01-06  1.202063 -0.703830  0.050375 -0.158644
2000-01-13 -0.276499  2.183782  0.565678  0.584314

In [301]: frame.resample('D').ffill()
Out[301]: 
            Colorado     Texas  New York      Ohio
2000-01-05  1.202063 -0.703830  0.050375 -0.158644
2000-01-06  1.202063 -0.703830  0.050375 -0.158644
2000-01-07  1.202063 -0.703830  0.050375 -0.158644
2000-01-08  1.202063 -0.703830  0.050375 -0.158644
2000-01-09  1.202063 -0.703830  0.050375 -0.158644
2000-01-10  1.202063 -0.703830  0.050375 -0.158644
2000-01-11  1.202063 -0.703830  0.050375 -0.158644
2000-01-12 -0.276499  2.183782  0.565678  0.584314

猜你喜欢

转载自blog.csdn.net/qq_42413820/article/details/80801485