- 11.2 时间序列基础
- 索引、选取、子集构造
- 带有重复索引的时间序列
11.2 时间序列基础
pandas最基本的时间序列类型就是以时间戳(通常以Python字符串或datatime对象表示)为索引的Series:
In [39]: from datetime import datetimeIn [40]: 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)]In [41]: ts = pd.Series(np.random.randn(6), index=dates)In [42]: tsOut[42]:2011-01-02 -0.2047082011-01-05 0.4789432011-01-07 -0.5194392011-01-08 -0.5557302011-01-10 1.9657812011-01-12 1.393406dtype: float64
这些datetime对象实际上是被放在一个DatetimeIndex中的:
In [43]: ts.indexOut[43]:DatetimeIndex(['2011-01-02', '2011-01-05', '2011-01-07', '2011-01-08','2011-01-10', '2011-01-12'],dtype='datetime64[ns]', freq=None)
跟其他Series一样,不同索引的时间序列之间的算术运算会自动按日期对齐:
In [44]: ts + ts[::2]Out[44]:2011-01-02 -0.4094152011-01-05 NaN2011-01-07 -1.0388772011-01-08 NaN2011-01-10 3.9315612011-01-12 NaNdtype: float64
ts[::2] 是每隔两个取一个。
pandas用NumPy的datetime64数据类型以纳秒形式存储时间戳:
In [45]: ts.index.dtypeOut[45]: dtype('<M8[ns]')
DatetimeIndex中的各个标量值是pandas的Timestamp对象:
In [46]: stamp = ts.index[0]In [47]: stampOut[47]: Timestamp('2011-01-02 00:00:00')
只要有需要,TimeStamp可以随时自动转换为datetime对象。此外,它还可以存储频率信息(如果有的话),且知道如何执行时区转换以及其他操作。稍后将对此进行详细讲解。
索引、选取、子集构造
当你根据标签索引选取数据时,时间序列和其它的pandas.Series很像:
In [48]: stamp = ts.index[2]In [49]: ts[stamp]Out[49]: -0.51943871505673811
还有一种更为方便的用法:传入一个可以被解释为日期的字符串:
In [50]: ts['1/10/2011']Out[50]: 1.9657805725027142In [51]: ts['20110110']Out[51]: 1.9657805725027142
对于较长的时间序列,只需传入“年”或“年月”即可轻松选取数据的切片:
In [52]: longer_ts = pd.Series(np.random.randn(1000),....: index=pd.date_range('1/1/2000', periods=1000))In [53]: longer_tsOut[53]:2000-01-01 0.0929082000-01-02 0.2817462000-01-03 0.7690232000-01-04 1.2464352000-01-05 1.0071892000-01-06 -1.2962212000-01-07 0.2749922000-01-08 0.2289132000-01-09 1.3529172000-01-10 0.886429...2002-09-17 -0.1392982002-09-18 -1.1599262002-09-19 0.6189652002-09-20 1.3738902002-09-21 -0.9835052002-09-22 0.9309442002-09-23 -0.8116762002-09-24 -1.8301562002-09-25 -0.1387302002-09-26 0.334088Freq: D, Length: 1000, dtype: float64In [54]: longer_ts['2001']Out[54]:2001-01-01 1.5995342001-01-02 0.4740712001-01-03 0.1513262001-01-04 -0.5421732001-01-05 -0.4754962001-01-06 0.1064032001-01-07 -1.3082282001-01-08 2.1731852001-01-09 0.5645612001-01-10 -0.190481...2001-12-22 0.0003692001-12-23 0.9008852001-12-24 -0.4548692001-12-25 -0.8645472001-12-26 1.1291202001-12-27 0.0578742001-12-28 -0.4337392001-12-29 0.0926982001-12-30 -1.3978202001-12-31 1.457823Freq: D, Length: 365, dtype: float64
这里,字符串“2001”被解释成年,并根据它选取时间区间。指定月也同样奏效:
In [55]: longer_ts['2001-05']Out[55]:2001-05-01 -0.6225472001-05-02 0.9362892001-05-03 0.7500182001-05-04 -0.0567152001-05-05 2.3006752001-05-06 0.5694972001-05-07 1.4894102001-05-08 1.2642502001-05-09 -0.7618372001-05-10 -0.331617...2001-05-22 0.5036992001-05-23 -1.3878742001-05-24 0.2048512001-05-25 0.6037052001-05-26 0.5456802001-05-27 0.2354772001-05-28 0.1118352001-05-29 -1.2515042001-05-30 -2.9493432001-05-31 0.634634Freq: D, Length: 31, dtype: float64
datetime对象也可以进行切片:
In [56]: ts[datetime(2011, 1, 7):]Out[56]:2011-01-07 -0.5194392011-01-08 -0.5557302011-01-10 1.9657812011-01-12 1.393406dtype: float64
由于大部分时间序列数据都是按照时间先后排序的,因此你也可以用不存在于该时间序列中的时间戳对其进行切片(即范围查询):
In [57]: tsOut[57]:2011-01-02 -0.2047082011-01-05 0.4789432011-01-07 -0.5194392011-01-08 -0.5557302011-01-10 1.9657812011-01-12 1.393406dtype: float64In [58]: ts['1/6/2011':'1/11/2011']Out[58]:2011-01-07 -0.5194392011-01-08 -0.5557302011-01-10 1.965781dtype: float64
跟之前一样,你可以传入字符串日期、datetime或Timestamp。注意,这样切片所产生的是原时间序列的视图,跟NumPy数组的切片运算是一样的。
这意味着,没有数据被复制,对切片进行修改会反映到原始数据上。
此外,还有一个等价的实例方法也可以截取两个日期之间TimeSeries:
In [59]: ts.truncate(after='1/9/2011')Out[59]:2011-01-02 -0.2047082011-01-05 0.4789432011-01-07 -0.5194392011-01-08 -0.555730dtype: float64
面这些操作对DataFrame也有效。例如,对DataFrame的行进行索引:
In [60]: dates = pd.date_range('1/1/2000', periods=100, freq='W-WED')In [61]: long_df = pd.DataFrame(np.random.randn(100, 4),....: index=dates,....: columns=['Colorado', 'Texas',....: 'New York', 'Ohio'])In [62]: long_df.loc['5-2001']Out[62]:Colorado Texas New York Ohio2001-05-02 -0.006045 0.490094 -0.277186 -0.7072132001-05-09 -0.560107 2.735527 0.927335 1.5139062001-05-16 0.538600 1.273768 0.667876 -0.9692062001-05-23 1.676091 -0.817649 0.050188 1.9513122001-05-30 3.260383 0.963301 1.201206 -1.852001
带有重复索引的时间序列
在某些应用场景中,可能会存在多个观测数据落在同一个时间点上的情况。下面就是一个例子:
In [63]: dates = pd.DatetimeIndex(['1/1/2000', '1/2/2000', '1/2/2000',....: '1/2/2000', '1/3/2000'])In [64]: dup_ts = pd.Series(np.arange(5), index=dates)In [65]: dup_tsOut[65]:2000-01-01 02000-01-02 12000-01-02 22000-01-02 32000-01-03 4dtype: int64
通过检查索引的is_unique属性,我们就可以知道它是不是唯一的:
In [66]: dup_ts.index.is_uniqueOut[66]: False
对这个时间序列进行索引,要么产生标量值,要么产生切片,具体要看所选的时间点是否重复:
In [67]: dup_ts['1/3/2000'] # not duplicatedOut[67]: 4In [68]: dup_ts['1/2/2000'] # duplicatedOut[68]:2000-01-02 12000-01-02 22000-01-02 3dtype: int64
假设你想要对具有非唯一时间戳的数据进行聚合。一个办法是使用groupby,并传入level=0:
In [69]: grouped = dup_ts.groupby(level=0)In [70]: grouped.mean()Out[70]:2000-01-01 02000-01-02 22000-01-03 4dtype: int64In [71]: grouped.count()Out[71]:2000-01-01 12000-01-02 32000-01-03 1dtype: int64
