pandas 时间序列操作

转载出处:https://my.oschina.net/lionets/blog/280855


数据类型及操作


Python 标准库的 datetime

datetime 模块中的 datetime、 time、 calendar 等类都可以用来存储时间类型以及进行一些转换和运算操作。

lang:python
>>> from datetime import datetime
>>> now = datetime.now()
>>> now
datetime.datetime(2014, 6, 17, 15, 56, 19, 313193)
>>> delta = datetime(2010,2,2)-datetime(2010,2,1)
>>> delta
datetime.timedelta(1)
>>> now + delta
datetime.datetime(2014, 6, 18, 15, 56, 19, 313193)

datetime 对象间的减法运算会得到一个 timedelta 对象,表示一个时间段。

datetime 对象与它所保存的字符串格式时间戳之间可以互相转换。str() 函数是可用的,但更推荐datetime.strptime() 方法。这个方法可以实现双向转换。

lang:python
>>> str(now)
'2014-06-17 15:56:19.313193'
>>> now.strftime('%Y-%m-%d')
'2014-06-17'
>>> datetime.strptime('2010-01-01','%Y-%m-%d')
datetime.datetime(2010, 1, 1, 0, 0)

如 %Y 这种格式代表了某种具体的意义,但用着很麻烦。因此可以使用一个名为 dateutil 第三方包的 parser.parse() 函数实现自动转义,它几乎可以解析任何格式(这也可能会带来麻烦)。

lang:python
>>> from dateutil.parser import parse
>>> parse('01-02-2010',dayfirst=True)
datetime.datetime(2010, 2, 1, 0, 0)
>>> parse('01-02-2010')
datetime.datetime(2010, 1, 2, 0, 0)
>>> parse('55')
datetime.datetime(2055, 6, 17, 0, 0)


pandas 的 TimeStamp

pandas 最基本的时间日期对象是一个从 Series 派生出来的子类 TimeStamp,这个对象与 datetime 对象保有高度兼容性,可通过 pd.to_datetime() 函数转换。(一般是从 datetime 转换为 Timestamp)

lang:python
>>> pd.to_datetime(now)
Timestamp('2014-06-17 15:56:19.313193', tz=None)
>>> pd.to_datetime(np.nan)
NaT


pandas 的时间序列

pandas 最基本的时间序列类型就是以时间戳(TimeStamp)为 index 元素的 Series 类型。

lang:python
>>> dates = [datetime(2011,1,1),datetime(2011,1,2),datetime(2011,1,3)]
>>> ts = Series(np.random.randn(3),index=dates)
>>> ts
2011-01-01    0.362289
2011-01-02    0.586695
2011-01-03   -0.154522
dtype: float64
>>> type(ts)
<class 'pandas.core.series.Series'>
>>> ts.index
<class 'pandas.tseries.index.DatetimeIndex'>
[2011-01-01, ..., 2011-01-03]
Length: 3, Freq: None, Timezone: None
>>> ts.index[0]
Timestamp('2011-01-01 00:00:00', tz=None)

时间序列之间的算术运算会自动按时间对齐。

索引、选取、子集构造

时间序列只是 index 比较特殊的 Series ,因此一般的索引操作对时间序列依然有效。其特别之处在于对时间序列索引的操作优化。如使用各种字符串进行索引:

lang:python
>>> ts['20110101']
0.36228897878097266
>>> ts['2011-01-01']
0.36228897878097266
>>> ts['01/01/2011']
0.36228897878097266

对于较长的序列,还可以只传入 “年” 或 “年月” 选取切片:

lang:python
>>> ts
2011-01-01    0.362289
2011-01-02    0.586695
2011-01-03   -0.154522
2012-12-25    0.111869
dtype: float64
>>> ts['2012']
2012-12-25    0.111869
dtype: float64
>>> ts['2011-1-2':'2012-12']
2011-01-02    0.586695
2011-01-03   -0.154522
2012-12-25    0.111869
dtype: float64

除了这种字符串切片方式外,还有一种实例方法可用:ts.truncate(after='2011-01-03')

值得注意的是,切片时使用的字符串时间戳并不必存在于 index 之中,如 ts.truncate(before='3055') 也是合法的。

日期的范围、频率以及移动


pandas 中的时间序列一般被默认为不规则的,即没有固定的频率。但出于分析的需要,我们可以通过插值的方式将序列转换为具有固定频率的格式。一种快捷方式是使用 .resample(rule) 方法:

lang:python
>>> ts
2011-01-01    0.362289
2011-01-02    0.586695
2011-01-03   -0.154522
2011-01-06    0.222958
dtype: float64
>>> ts.resample('D')
2011-01-01    0.362289
2011-01-02    0.586695
2011-01-03   -0.154522
2011-01-04         NaN
2011-01-05         NaN
2011-01-06    0.222958
Freq: D, dtype: float64


生成日期范围

pd.date_range() 可用于生成指定长度的 DatetimeIndex。参数可以是起始结束日期,或单给一个日期,加一个时间段参数。日期是包含的。

lang:python
>>> pd.date_range('20100101','20100110')
<class 'pandas.tseries.index.DatetimeIndex'>
[2010-01-01, ..., 2010-01-10]
Length: 10, Freq: D, Timezone: None
>>> pd.date_range(start='20100101',periods=10)
<class 'pandas.tseries.index.DatetimeIndex'>
[2010-01-01, ..., 2010-01-10]
Length: 10, Freq: D, Timezone: None
>>> pd.date_range(end='20100110',periods=10)
<class 'pandas.tseries.index.DatetimeIndex'>
[2010-01-01, ..., 2010-01-10]
Length: 10, Freq: D, Timezone: None

默认情况下,date_range 会按天计算时间点。这可以通过 freq 参数进行更改,如 “BM” 代表 bussiness end of month。

lang:python
>>> pd.date_range('20100101','20100601',freq='BM')
<class 'pandas.tseries.index.DatetimeIndex'>
[2010-01-29, ..., 2010-05-31]
Length: 5, Freq: BM, Timezone: None


频率和日期偏移量

pandas 中的频率是由一个基础频率和一个乘数组成的。基础频率通常以一个字符串别名表示,如上例中的 “BM”。对于每个基础频率,都有一个被称为日期偏移量(date offset)的对象与之对应。可以通过实例化日期偏移量来创建某种频率:

lang:python
>>> Hour()

>>> Hour(2)
<2 * Hours>
>>> Hour(1) + Minute(30)
<90 * Minutes>

但一般来说不必这么麻烦,使用前面提过的字符串别名来创建频率就可以了:

lang:python
>>> pd.date_range('00:00','12:00',freq='1h20min')
<class 'pandas.tseries.index.DatetimeIndex'>
[2014-06-17 00:00:00, ..., 2014-06-17 12:00:00]
Length: 10, Freq: 80T, Timezone: None

可用的别名,可以通过 help() 或 文档来查询,这里就不写了。

移动(超前和滞后)数据

移动(shifting)指的是沿着时间轴将数据前移或后移。Series 和 DataFrame 都有一个 .shift() 方法用于执行单纯的移动操作,index 维持不变:

lang:python
>>> ts
2011-01-01    0.362289
2011-01-02    0.586695
2011-01-03   -0.154522
2011-01-06    0.222958
dtype: float64
>>> ts.shift(2)
2011-01-01         NaN
2011-01-02         NaN
2011-01-03    0.362289
2011-01-06    0.586695
dtype: float64
>>> ts.shift(-2)
2011-01-01   -0.154522
2011-01-02    0.222958
2011-01-03         NaN
2011-01-06         NaN
dtype: float64

上例中因为移动操作产生了 NA 值,另一种移动方法是移动 index,而保持数据不变。这种移动方法需要额外提供一个 freq 参数来指定移动的频率:

lang:python
>>> ts.shift(2,freq='D')
2011-01-03    0.362289
2011-01-04    0.586695
2011-01-05   -0.154522
2011-01-08    0.222958
dtype: float64
>>> ts.shift(2,freq='3D')
2011-01-07    0.362289
2011-01-08    0.586695
2011-01-09   -0.154522
2011-01-12    0.222958
dtype: float64


时期及其算术运算


本节使用的时期(period)概念不同于前面的时间戳(timestamp),指的是一个时间段。但在使用上并没有太多不同,pd.Period 类的构造函数仍需要一个时间戳,以及一个 freq 参数。freq 用于指明该 period 的长度,时间戳则说明该 period 在公园时间轴上的位置。

lang:python
>>> p = pd.Period(2010,freq='M')
>>> p
Period('2010-01', 'M')
>>> p + 2
Period('2010-03', 'M')

上例中我给 period 的构造器传了一个 “年” 单位的时间戳和一个 “Month” 的 freq,pandas 便自动把 2010 解释为了 2010-01。

period_range 函数可用于创建规则的时间范围:

lang:python
>>> pd.period_range('2010-01','2010-05',freq='M')
<class 'pandas.tseries.period.PeriodIndex'>
freq: M
[2010-01, ..., 2010-05]
length: 5

PeriodIndex 类保存了一组 period,它可以在任何 pandas 数据结构中被用作轴索引:

lang:python
>>> Series(np.random.randn(5),index=pd.period_range('201001','201005',freq='M'))
2010-01    0.755961
2010-02   -1.074492
2010-03   -0.379719
2010-04    0.153662
2010-05   -0.291157
Freq: M, dtype: float64


时期的频率转换

Period 和 PeriodIndex 对象都可以通过其 .asfreq(freq, method=None, how=None) 方法被转换成别的频率。

lang:python
>>> p = pd.Period('2007',freq='A-DEC')
>>> p.asfreq('M',how='start')
Period('2007-01', 'M')
>>> p.asfreq('M',how='end')
Period('2007-12', 'M')
>>> ts = Series(np.random.randn(1),index=[p])
>>> ts
2007   -0.112347
Freq: A-DEC, dtype: float64
>>> ts.asfreq('M',how='start')
2007-01   -0.112347
Freq: M, dtype: float64


时间戳与时期间相互转换

以时间戳和以时期为 index 的 Series 和 DataFrame 都有一对 .to_period() 和 to_timestamp(how='start') 方法用于互相转换 index 的类型。因为从 period 到 timestamp 的转换涉及到一个取端值的问题,所以需要一个额外的 how 参数,默认为 'start':

lang:python
>>> ts = Series(np.random.randn(5),index=pd.period_range('201001','201005',freq='M'))
>>> ts
2010-01   -0.312160
2010-02    0.962652
2010-03   -0.959478
2010-04    1.240236
2010-05   -0.916218
Freq: M, dtype: float64
>>> ts.to_timestamp()
2010-01-01   -0.312160
2010-02-01    0.962652
2010-03-01   -0.959478
2010-04-01    1.240236
2010-05-01   -0.916218
Freq: MS, dtype: float64
>>> ts.to_timestamp(how='end')
2010-01-31   -0.312160
2010-02-28    0.962652
2010-03-31   -0.959478
2010-04-30    1.240236
2010-05-31   -0.916218
Freq: M, dtype: float64
>>> ts.to_timestamp().to_period()
2010-01-01 00:00:00.000   -0.312160
2010-02-01 00:00:00.000    0.962652
2010-03-01 00:00:00.000   -0.959478
2010-04-01 00:00:00.000    1.240236
2010-05-01 00:00:00.000   -0.916218
Freq: L, dtype: float64
>>> ts.to_timestamp().to_period('M')
2010-01   -0.312160
2010-02    0.962652
2010-03   -0.959478
2010-04    1.240236
2010-05   -0.916218
Freq: M, dtype: float64


重采样及频率转换


重采样(resampling)指的是将时间序列从一个频率转换到另一个频率的过程。pandas 对象都含有一个.resample(freq, how=None, axis=0, fill_method=None, closed=None, label=None, convention='start', kind=None, loffset=None, limit=None, base=0) 方法用于实现这个过程。

本篇最前面曾用 resample 规整化过时间序列。当时进行的是插值操作,因为原索引的频率与给出的 freq 参数相同。resample 方法更多的应用场合是 freq 发生改变的时候,这时操作就分为升采样(upsampling)和降采样(downsampling)两种。具体的区别都体现在参数里。

lang:python
>>> ts
2010-01   -0.312160
2010-02    0.962652
2010-03   -0.959478
2010-04    1.240236
2010-05   -0.916218
Freq: M, dtype: float64
>>> ts.resample('D',fill_method='ffill')#升采样
2010-01-01   -0.31216
2010-01-02   -0.31216
2010-01-03   -0.31216
2010-01-04   -0.31216
2010-01-05   -0.31216
2010-01-06   -0.31216
2010-01-07   -0.31216
2010-01-08   -0.31216
2010-01-09   -0.31216
2010-01-10   -0.31216
2010-01-11   -0.31216
2010-01-12   -0.31216
2010-01-13   -0.31216
2010-01-14   -0.31216
2010-01-15   -0.31216
...
2010-05-17   -0.916218
2010-05-18   -0.916218
2010-05-19   -0.916218
2010-05-20   -0.916218
2010-05-21   -0.916218
2010-05-22   -0.916218
2010-05-23   -0.916218
2010-05-24   -0.916218
2010-05-25   -0.916218
2010-05-26   -0.916218
2010-05-27   -0.916218
2010-05-28   -0.916218
2010-05-29   -0.916218
2010-05-30   -0.916218
2010-05-31   -0.916218
Freq: D, Length: 151
>>> ts.resample('A-JAN',how='sum')#降采样
2010   -0.312160
2011    0.327191
Freq: A-JAN, dtype: float64

你可能感兴趣的:(Python)