首发于 pandas手册
【S01E10】pandas之时间序列

【S01E10】pandas之时间序列

I.    时间序列基础
II.   生成DatetimeIndex
      i)  date_range函数
               pd.date_range('2018/12/1',periods=31)
      ii) 读取csv/excel时指定index_col、parse_dates关键字
               df = pd.read_csv(path, engine='python', encoding='utf_8_sig', 
                                index_col=0, parse_dates=True)
      iii)df.index = pd.to_datetime(df['col'])
               df.index = pd.to_datetime(df['Unnamed: 0'])
      iv)pd.DatetimeIndex
               pd.DatetimeIndex(['2018/4/14','2019/4/14'])
III.  索引为DatetimeIndex的pandas对象的索引和切片
      索引为DatetimeIndex的Series的索引和切片
      i) 索引
      ii)切片
      索引为DatetimeIndex的DataFrame的索引和切片
      i) 索引    ps. 获取每天特定时间的数据: s[time(10,0)]
      ii)切片    ps. 获取每天特定时间段的数据: s[time(10,0):time(10,1)]
VII.  将DatetimeIndex转换为PeriodIndex及其反向过程
      i) 将DatetimeIndex转换为PeriodIndex————to_period
      ii)将PeriodIndex转换为DatetimeIndex————to_timestamp
VIII. 重采样(resampling)
      DatetimeIndex Resamping
      i) 降采样
          s.resample('m').count()
          s.resample('m',kind='period').count()
          s.resample('3min').ohlc()
      ii)升采样和插值
          df.resample('d').ffill(limit=2)
      PeriodIndex Resamping
      i) 降采样
          frame.resample('a-dec').count()
      ii)升采样和插值
          annual_frame.resample('m', convention='end').count()
IX.   移动窗口函数(moving window function)
      i) window为整数
      ii)window为日期偏移量
X.    频率和时间、日期的偏移
       i)  显式创建pd.tseries.offsets对象
            from pandas.tseries.offsets import Hour, Minute
       ii) 使用字符串别名
            pd.date_range('2018/12/25', '2018/12/26',freq='4h30min' )
       iii)时间、日期的偏移
            s = '2018-12-08 00:08'
            d = parse(s) + Minute(2) 
            df.loc[d]
XI. 移动数据————shift、rollforward、rollback
       i)  索引保持不动,将数据前移或后移————shift
            df.tip_pct = df.tip_pct.shift(1)   
       ii) 数据保持不动,将时间戳前移或后移————shift
            s.shift(2, freq='M')
       iii)通过锚点偏移量对日期进行位移
            datetime(2018,3,5) + MonthEnd(2)
            MonthEnd().rollforward(datetime(2018,3,5))
            df.groupby(MonthEnd().rollforward).count()



时间序列(time series)指的是分布在不同时间戳(timestamp)/时期(period)上的值形成的数据集。它可以是按特定频率均匀分隔的,也可以是非均匀分隔的。


I. 时间序列基础


pandas中时间序列的索引分两种,即DatetimeIndex和PeriodIndex,其中最常见的是第一种。

DatetimeIndex

DatetimeIndex是由一个个Timestamp(时间戳)组成的,而Timestamp对象可以根据需要自动转化为datetime对象,所以我们可以把DatetimeIndex看作一个每个索引值都是datetime对象的索引

和普通的index一样,不同DatetimeIndex的Pandas对象的算术运算会自动按索引对齐

和普通索引不一样的是,DatetimeIndex还有一些独有的方法和属性

这些方法和属性有时候很有用,举个例子,假设我打算求下面这个DataFrame按年、月分组求和的累计交易笔数和累计盈利笔数就可以利用DatetimeIndex的year和month属性

再修改下列名和索引名就可以用了


PeriodIndex

Period对象表示的是一个时间间隔,如年、月、日、时、分,PeriodIndex可以看作是每个索引值都是Period对象的索引

我们可以通过pd.Period()方法主动构造得到Period对象,此时我们需要指定频率(详细的频率及说明见下方的【时间序列基础频率表】)

这个Period对象指的是2018年一整年

Period对象还有一些跟Datetime对象类似的特性,对一个Period对象加减一个整数就相当于对其按当前频率进行位移


时间序列基础频率表

别名                 偏移量类型                       说明
D                     Day                             每日历日
B                     BusinessDay                     每工作日
H                     Hour                            每小时
T或min                Minute                          每分钟
S                     Second                          每秒钟
M                     MonthEnd                        每月最后一个日历日
BM                    BusinessMonthEnd                每月最后一个工作日
Q-JAN、Q-FEB          QuarterEnd                      对于以指定月份结束的年度,每季度最后一月的
                                                      最后一个日历日
A-JAN、A-FEB          YearEnd                         每年指定月份的最后一个日历日

II. 生成DatetimeIndex

i)pd.date_range()

Return a fixed frequency DatetimeIndex
date_range(start=None, end=None, periods=None, freq=None, 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
        :ref:`here <timeseries.offset_aliases>` for a list of
        frequency aliases.
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).
重点参数:
         start
         periods
         freq

pd.date_range()方法可用于生成指定长度和频率的DatetimeIndex:

左右边界都包含

默认频率是'D',也可以传入其他频率;起止时间也可以只传其中一个,但此时需要指定periods,表示长度


如果timestamp包括具体的时间,date_range()方法默认会保留该时间值:

也可以选择传入 normalize=True 把时间部分去掉:

所以为什么要传入时间部分呢?

也可以调用list()方法, 将datetimeindex转化为列表:




ii)pd.read_csv(path, index_col=n, parse_dates=True)/

pd.read_excel(path, index_col=n, parse_dates=True)


数据如下:


read_csv

read_excel


iii)df.index = pd.to_datetime(df['col'])

iv)pd.DatetimeIndex

III. 索引为DatetimeIndex的pandas对象的索引和切片

索引为DatetimeIndex的Series的索引和切片

i)索引

传入一个可以被解释为时期的字符串

对于较长的时间序列,只需要传入“年”或“年月”即可轻松选取数据的切片:

(相当于 close['20181101':'20181130'])

或传入datetime



但不能用'201811'这样的形式选取月



ii)切片

传入字符串日期

传入datetime


索引为DatetimeIndex的DataFrame的索引和切片

i)索引

选取年月:


ii)切片

特别一点的切片



那如果我们想获得每天特定时间段的数据该怎么做呢?

且看下面两个例子

生成2012年6月1日9:30至2012年6月1日15:59的DatetimeIndex,频率为T


传入time,索引单个时间点,相当于下面的at_time方法



时间段切片,相当于下面的between_time方法



完整的代码如下

另一个例子更贴近实际应用,假如我们发现某交易对在12月份每天接近24:00时交易量和价格都会出现大幅波动。那么我们现在的目标就是选取每天23:50-23:59的k线数据,然后可视化观察一下是否真的存在这个现象


好像真的有


pandas时间序列的索引和切片是一大利器



IV. 生成PeriodIndex

i)pd.period_range

Return a fixed frequency PeriodIndex, with day (calendar) as the default
frequency
period_range(start=None, end=None, periods=None, freq='D', name=None)
start : string or period-like, default None
        Left bound for generating periods
end : string or period-like, default None
        Right bound for generating periods
periods : integer, default None
        Number of periods to generate
freq : string or DateOffset, default 'D' (calendar daily)
        Frequency alias
name : string, default None
        Name of the resulting PeriodIndex
重点参数:
       start
       periods
       freq


ii)把分开放在不同列中的时间信息合并成一个PeriodIndex

数据:

链接: pan.baidu.com/s/1Pe7Emx

提取码:4dwg


把时间信息分开存放在多列中的数据集适用这种方法

iii)通过pd.PeriodIndex创建

V. 索引为 PeriodIndex 的pandas对象的索引和切片

索引为PeriodIndex的Series的索引和切片

i)索引

ii)切片

索引为PeriodIndex的DataFrame的索引和切片

i)索引

ii)切片

VI. 时期的频率转换

Period和PeriodIndex对象都可以通过asfreq方法转换成其他频率。

i)Period对象转换成别的频率————asfreq

把低频率转化为高频率需要指定how

把年转化为月

有些年度并不以12月作为一年的结束,比如财年,此时指定how='start'/'end'得到的月份要根据财年的结束月份来看

而将高频率转换为低频率时,目标时期(pandas中称superperiod)由初始时期(pandasv中称subperiod)所在的位置决定。



ii)PeriodIndex对象转换成别的频率————asfreq

PeriodIndex的频率转换方式也类似

VII. 将DatetimeIndex转换为PeriodIndex及其反向过程


i)将DatetimeIndex转换为PeriodIndex————to_period

通过使用to_period方法,可以将由时间戳索引的Series和DataFrame对象转换为时期索引:


PeriodIndex的频率默认是从DatetimeIndex的频率推断而来的,也可以指定其他频率。

PeriodIndex中出现重复的Period是允许的

ii)将PeriodIndex转换为DatetimeIndex————to_timestamp


VIII. 重采样(resampling)

(下面这段重采样的定义文字来自 Wes McKinney 《利用Python进行数据分析》)

重采样(resampling)指的是将时间序列从一个频率转换到另一个频率的处理过程。将高频率数据聚合到低频率称为降采样(downsampling),而将低频率数据转换到高频率则称为升采样(upsampling)。 并不是所有的重采样都能被划分到这两个大类中。例如,将W-WED(每周三)转换到W-FRI(每周五)既不是降采样也不是升采样。
Convenience method for frequency conversion and resampling of time
series.  Object must have a datetime-like index (DatetimeIndex,
PeriodIndex, or TimedeltaIndex), or pass datetime-like values
to the on or level keyword.
resample(rule, how=None, axis=0, fill_method=None, closed=None, label=None, 
         convention='start', kind=None, loffset=None, limit=None, base=0, 
         on=None, level=None)
rule : string
       the offset string or object representing target conversion
axis : int, optional, default 0
closed : {'right', 'left'}
        Which side of bin interval is closed. The default is 'left'
        for all frequency offsets except for 'M', 'A', 'Q', 'BM',
        'BA', 'BQ', and 'W' which all have a default of 'right'.
label : {'right', 'left'}
        Which bin edge label to label bucket with. The default is 'left'
        for all frequency offsets except for 'M', 'A', 'Q', 'BM',
        'BA', 'BQ', and 'W' which all have a default of 'right'.
convention : {'start', 'end', 's', 'e'}
        For PeriodIndex only, controls whether to use the start or end of
        当重采样时期时,将低频率转换到高频率所采用的约定('start'或'end'),
        默认为'start'
kind: {'timestamp', 'period'}, optional
        Pass 'timestamp' to convert the resulting index to a
        ``DateTimeIndex`` or 'period' to convert it to a ``PeriodIndex``.
        By default the input representation is retained.
        聚合到时期('Period')或时间戳('timestamp'),默认聚合到时间序列的索引类型
loffset : timedelta
        Adjust the resampled time labels
        面元标签的时间校正值,比如'-1s'/Second(-1)用于将聚合标签调早1秒
base : int, default 0
        For frequencies that evenly subdivide 1 day, the "origin" of the
        aggregated intervals. For example, for '5min' frequency, base could
        range from 0 through 4. Defaults to 0
on : string, optional
        For a DataFrame, column to use instead of index for resampling.
        Column must be datetime-like.
level : string or int, optional
        For a MultiIndex, level (name or number) to use for
        resampling.  Level must be datetime-like.

DatetimeIndex Resamping

i)降采样

(下面这段定义文字摘录自 Wes McKinney 《利用Python进行数据分析》)

将数据聚合到规整的低频率是一件非常普通的时间序列处理任务。待聚合的数据不必拥有固定的频率,期望的频率会自动定义聚合的面元边界,这些面元用于将时间序列拆分为多个片段。 各时间段都是半开放的。一个数据点只能属于一个时间段,所有时间段的并集必须能组成整个时间帧

example 1:



example 2:


"1分钟"数据被转换为"5分钟"数据的处理过程:

除了可以对resampler对象调用mean、count、sum、min、max、median、first、last等函数外,还可以对其调用ohlc,即计算各面元的四个值:open(开盘价)、high(最高价)、low(最低价)、close(收盘价)。对resampler对象调用 .ohlc() 即可得到含有这四种聚合值的DataFrame

更自由的函数定义方式是我们之前提到过的 apply方法, 对其传入"列名:函数名"键值对的字典,我们就可以对不同的列定义不同的聚合函数。下面结合K线图的生成及转化简单介绍一下:


原始数据like this,字段分别为时间,交易类型,量,价格

先简单处理一下

处理后的数据如下所示

接下来降采样,用apply(dict)的方式生成一分钟K线,注意看

然后是K线转化,但因为我们前面得到是一个MultiIndex的df,所以需要先处理一下columns

接下来是类似的操作


除了resample()方法,还可以使用groupby()方法对数据进行降采样。如果需要根据月份进行分组,只需传入一个能够访问时间序列的索引上的这些字段的函数即可:


因为重采样中降采样出现的频率会更高,所以这里介绍得更详细一些。



ii)升采样和插值

如果是将数据从低频转换到高频,就无需聚合。

升采样默认会引入缺失值,如果想用前后数据填充缺失,调用ffill,bfill等方法即可(resampling的填充和插值方式跟fillna和reindex的一样),也可以在其中传入limit参数,限制填充的距离


PeriodIndex Resamping

i)降采样

对索引是PeriodIndex的数据进行降采样


ii)升采样和插值

convention默认为'start',也可以手动设置为'end'





IX. 移动窗口函数(moving window function)

Provides rolling window calculations.
rolling(window, min_periods=None, center=False, win_type=None, on=None, axis=0, closed=None)
window : int, or offset
         Size of the moving window. This is the number of observations used for
         calculating the statistic. Each window will be a fixed size.
         If its an offset then this will be the time period of each window. Each
         window will be a variable sized based on the observations included in
         the time-period. This is only valid for datetimelike indexes. This is
         new in 0.19.0
min_periods : int, default None
         Minimum number of observations in window required to have a value
         (otherwise result is NA). For a window that is specified by an offset,
         this will default to 1.
center : boolean, default False
         Set the labels at the center of the window.
win_type : string, default None
         Provide a window type. If ``None``, all points are evenly weighted.
         See the notes below for further information.
on : string, optional
         For a DataFrame, column on which to calculate
         the rolling window, rather than the index
closed : string, default None
         Make the interval closed on the 'right', 'left', 'both' or
         'neither' endpoints.
         For offset-based windows, it defaults to 'right'.
         For fixed windows, defaults to 'both'. Remaining cases not implemented
         for fixed windows.

i)window为整数

如果只传入一个表示移动窗口大小的整数(这里是5),sample.rolling(5).mean()计算的就是11月连续5天收盘价的算数平均值,此时min_periods默认是None

当然,我们可以手动设置min_periods,如果min_periods=1,前面11-1、11-2、11-5、11-6对应的值就不会为空了,它们分别会变成24.42、(24.42 + 24.62)/2、(24.42 + 24.62 + 24.04)/3、(24.42 + 24.62 + 24.04 + 24.14)/4。换言之,在window是一个整数的情况下,如果观测值的数量不足移动窗口的大小,且设置了min_periods,则从长度满足min_periods的位置开始计算统计量。

ii)window为日期偏移量

而如果向window传入的是日期偏移量,且索引为DatetimeIndex,那么每个移动窗口的大小就是对应的偏移量的大小。当window是一个日期偏移量的时候,min_periods默认为1。此时11-5、11-6、11-7对应的分别就是(24.42+24.62+24.04)/3、(24.62+24.04+24.14)/3、(24.04+24.14+23.85)/3,因为对应的是日历日嘛。那能不能传入工作日呢?不行,因为BusinessDays并不是一个固定的频率

特别需要注意的是,当window为偏移量的时候,closed默认为右侧关闭。例如下面这个demo, 00:03:29 对应移动窗口是 00:02:30 00:03:29 ,,所以不计算 00:02:29 的价格在内

如果对DataFrame调用rolling().mean()之类的函数时,会将转换应用到所有的列上:



X. 频率和时间、日期的偏移

i)显式创建pd.tseries.offsets对象

下面这段pandas基本频率的描述来自Wes McKinney 《利用Python进行数据分析》

pandas中的频率是由一个基础频率(base frequency)和一个乘数组成的。基础频率都用一个字符串别名表示,比如'M'表示每月,'H'表示每小时。对于每个基础频率,都有一个被称为日期偏移量(date offset)的对象与之对应。


ii)使用字符串别名

其实我们可以用'4h30min'这样的字符串别名替代Hour(4)+Minute(30)

换个偏移量


iii)时间、日期的偏移

>>>df = pd.read_csv(path, engine='python', encoding='utf_8_sig',index_col='time',parse_dates=True)
>>>s = '2018-12-08 00:08'
>>>d = parse(s) + Minute(2)  
Timestamp('2018-12-08 00:10:00')
>>>df.loc[d]
open          3.341530e+03
high          3.343600e+03
low           3.335010e+03
close         3.339980e+03
volume        2.214765e+05
OBV           1.255913e+06