数据分析篇 | Pandas 时间序列 – 日期时间索引
- 2019 年 12 月 22 日
- 筆記
部字符串索引切片 vs. 精准匹配精确索引截断与花式索引日期/时间组件
DatetimeIndex
主要用作 Pandas 对象的索引。DatetimeIndex
类为时间序列做了很多优化:
- 预计算了各种偏移量的日期范围,并在后台缓存,让后台生成后续日期范围的速度非常快(仅需抓取切片)。
- 在 Pandas 对象上使用
shift
与tshift
方法进行快速偏移。 - 合并具有相同频率的重叠
DatetimeIndex
对象的速度非常快(这点对快速数据对齐非常重要)。 - 通过
year
、month
等属性快速访问日期字段。 snap
等正则函数与超快的asof
逻辑。
DatetimeIndex
对象支持全部常规 Index
对象的基本用法,及一些列简化频率处理的高级时间序列专有方法。
参阅:重置索引 注意:Pandas 不强制排序日期索引,但如果日期没有排序,可能会引发可控范围之外的或不正确的操作。
DatetimeIndex
可以当作常规索引,支持选择、切片等方法。
In [94]: rng = pd.date_range(start, end, freq='BM') In [95]: ts = pd.Series(np.random.randn(len(rng)), index=rng) In [96]: ts.index Out[96]: DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-29', '2011-05-31', '2011-06-30', '2011-07-29', '2011-08-31', '2011-09-30', '2011-10-31', '2011-11-30', '2011-12-30'], dtype='datetime64[ns]', freq='BM') In [97]: ts[:5].index Out[97]: DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-29', '2011-05-31'], dtype='datetime64[ns]', freq='BM') In [98]: ts[::2].index Out[98]: DatetimeIndex(['2011-01-31', '2011-03-31', '2011-05-31', '2011-07-29', '2011-09-30', '2011-11-30'], dtype='datetime64[ns]', freq='2BM')
局部字符串索引
能解析为时间戳的日期与字符串可以作为索引的参数:
In [99]: ts['1/31/2011'] Out[99]: 0.11920871129693428 In [100]: ts[datetime.datetime(2011, 12, 25):] Out[100]: 2011-12-30 0.56702 Freq: BM, dtype: float64 In [101]: ts['10/31/2011':'12/31/2011'] Out[101]: 2011-10-31 0.271860 2011-11-30 -0.424972 2011-12-30 0.567020 Freq: BM, dtype: float64
Pandas 为访问较长的时间序列提供了便捷方法,年、年月字符串均可:
In [102]: ts['2011'] Out[102]: 2011-01-31 0.119209 2011-02-28 -1.044236 2011-03-31 -0.861849 2011-04-29 -2.104569 2011-05-31 -0.494929 2011-06-30 1.071804 2011-07-29 0.721555 2011-08-31 -0.706771 2011-09-30 -1.039575 2011-10-31 0.271860 2011-11-30 -0.424972 2011-12-30 0.567020 Freq: BM, dtype: float64 In [103]: ts['2011-6'] Out[103]: 2011-06-30 1.071804 Freq: BM, dtype: float64
带 DatetimeIndex
的 DateFrame
也支持这种切片方式。局部字符串是标签切片的一种形式,这种切片也包含截止时点,即,与日期匹配的时间也会包含在内:
In [104]: dft = pd.DataFrame(np.random.randn(100000, 1), columns=['A'], .....: index=pd.date_range('20130101', periods=100000, freq='T')) .....: In [105]: dft Out[105]: A 2013-01-01 00:00:00 0.276232 2013-01-01 00:01:00 -1.087401 2013-01-01 00:02:00 -0.673690 2013-01-01 00:03:00 0.113648 2013-01-01 00:04:00 -1.478427 ... ... 2013-03-11 10:35:00 -0.747967 2013-03-11 10:36:00 -0.034523 2013-03-11 10:37:00 -0.201754 2013-03-11 10:38:00 -1.509067 2013-03-11 10:39:00 -1.693043 [100000 rows x 1 columns] In [106]: dft['2013'] Out[106]: A 2013-01-01 00:00:00 0.276232 2013-01-01 00:01:00 -1.087401 2013-01-01 00:02:00 -0.673690 2013-01-01 00:03:00 0.113648 2013-01-01 00:04:00 -1.478427 ... ... 2013-03-11 10:35:00 -0.747967 2013-03-11 10:36:00 -0.034523 2013-03-11 10:37:00 -0.201754 2013-03-11 10:38:00 -1.509067 2013-03-11 10:39:00 -1.693043 [100000 rows x 1 columns]
下列代码截取了自 1 月 1 日凌晨起,至 2 月 28 日午夜的日期与时间。
In [107]: dft['2013-1':'2013-2'] Out[107]: A 2013-01-01 00:00:00 0.276232 2013-01-01 00:01:00 -1.087401 2013-01-01 00:02:00 -0.673690 2013-01-01 00:03:00 0.113648 2013-01-01 00:04:00 -1.478427 ... ... 2013-02-28 23:55:00 0.850929 2013-02-28 23:56:00 0.976712 2013-02-28 23:57:00 -2.693884 2013-02-28 23:58:00 -1.575535 2013-02-28 23:59:00 -1.573517 [84960 rows x 1 columns]
下列代码截取了包含截止日期及其时间在内的日期与时间。
In [108]: dft['2013-1':'2013-2-28'] Out[108]: A 2013-01-01 00:00:00 0.276232 2013-01-01 00:01:00 -1.087401 2013-01-01 00:02:00 -0.673690 2013-01-01 00:03:00 0.113648 2013-01-01 00:04:00 -1.478427 ... ... 2013-02-28 23:55:00 0.850929 2013-02-28 23:56:00 0.976712 2013-02-28 23:57:00 -2.693884 2013-02-28 23:58:00 -1.575535 2013-02-28 23:59:00 -1.573517 [84960 rows x 1 columns]
下列代码指定了精准的截止时间,注意此处的结果与上述截取结果的区别:
In [109]: dft['2013-1':'2013-2-28 00:00:00'] Out[109]: A 2013-01-01 00:00:00 0.276232 2013-01-01 00:01:00 -1.087401 2013-01-01 00:02:00 -0.673690 2013-01-01 00:03:00 0.113648 2013-01-01 00:04:00 -1.478427 ... ... 2013-02-27 23:56:00 1.197749 2013-02-27 23:57:00 0.720521 2013-02-27 23:58:00 -0.072718 2013-02-27 23:59:00 -0.681192 2013-02-28 00:00:00 -0.557501 [83521 rows x 1 columns]
截止时间是索引的一部分,包含在截取的内容之内:
In [110]: dft['2013-1-15':'2013-1-15 12:30:00'] Out[110]: A 2013-01-15 00:00:00 -0.984810 2013-01-15 00:01:00 0.941451 2013-01-15 00:02:00 1.559365 2013-01-15 00:03:00 1.034374 2013-01-15 00:04:00 -1.480656 ... ... 2013-01-15 12:26:00 0.371454 2013-01-15 12:27:00 -0.930806 2013-01-15 12:28:00 -0.069177 2013-01-15 12:29:00 0.066510 2013-01-15 12:30:00 -0.003945 [751 rows x 1 columns]
0.18.0 版新增。
DatetimeIndex
局部字符串索引还支持多层索引 DataFrame
。
In [111]: dft2 = pd.DataFrame(np.random.randn(20, 1), .....: columns=['A'], .....: index=pd.MultiIndex.from_product( .....: [pd.date_range('20130101', periods=10, freq='12H'), .....: ['a', 'b']])) .....: In [112]: dft2 Out[112]: A 2013-01-01 00:00:00 a -0.298694 b 0.823553 2013-01-01 12:00:00 a 0.943285 b -1.479399 2013-01-02 00:00:00 a -1.643342 ... ... 2013-01-04 12:00:00 b 0.069036 2013-01-05 00:00:00 a 0.122297 b 1.422060 2013-01-05 12:00:00 a 0.370079 b 1.016331 [20 rows x 1 columns] In [113]: dft2.loc['2013-01-05'] Out[113]: A 2013-01-05 00:00:00 a 0.122297 b 1.422060 2013-01-05 12:00:00 a 0.370079 b 1.016331 In [114]: idx = pd.IndexSlice In [115]: dft2 = dft2.swaplevel(0, 1).sort_index() In [116]: dft2.loc[idx[:, '2013-01-05'], :] Out[116]: A a 2013-01-05 00:00:00 0.122297 2013-01-05 12:00:00 0.370079 b 2013-01-05 00:00:00 1.422060 2013-01-05 12:00:00 1.016331
0.25.0 版新增。
字符串索引切片支持 UTC 偏移。
In [117]: df = pd.DataFrame([0], index=pd.DatetimeIndex(['2019-01-01'], tz='US/Pacific')) In [118]: df Out[118]: 0 2019-01-01 00:00:00-08:00 0 In [119]: df['2019-01-01 12:00:00+04:00':'2019-01-01 13:00:00+04:00'] Out[119]: 0 2019-01-01 00:00:00-08:00 0
切片 vs. 精准匹配
0.20.0 版新增。
基于索引的精度,字符串既可用于切片,也可用于精准匹配。字符串精度比索引精度低,就是切片,比索引精度高,则是精准匹配。
In [120]: series_minute = pd.Series([1, 2, 3], .....: pd.DatetimeIndex(['2011-12-31 23:59:00', .....: '2012-01-01 00:00:00', .....: '2012-01-01 00:02:00'])) .....: In [121]: series_minute.index.resolution Out[121]: 'minute'
下例中的时间戳字符串没有 Series
对象的精度高。series_minute
到秒
,时间戳字符串只到分
。
In [122]: series_minute['2011-12-31 23'] Out[122]: 2011-12-31 23:59:00 1 dtype: int64
精度为分钟(或更高精度)的时间戳字符串,给出的是标量,不会被当作切片。
In [123]: series_minute['2011-12-31 23:59'] Out[123]: 1 In [124]: series_minute['2011-12-31 23:59:00'] Out[124]: 1
索引的精度为秒时,精度为分钟的时间戳返回的是 Series
。
In [125]: series_second = pd.Series([1, 2, 3], .....: pd.DatetimeIndex(['2011-12-31 23:59:59', .....: '2012-01-01 00:00:00', .....: '2012-01-01 00:00:01'])) .....: In [126]: series_second.index.resolution Out[126]: 'second' In [127]: series_second['2011-12-31 23:59'] Out[127]: 2011-12-31 23:59:59 1 dtype: int64
用时间戳字符串切片时,还可以用 []
索引 DataFrame
。
In [128]: dft_minute = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]}, .....: index=series_minute.index) .....: In [129]: dft_minute['2011-12-31 23'] Out[129]: a b 2011-12-31 23:59:00 1 4
警告:字符串执行精确匹配时,用
[]
按列,而不是按行截取DateFrame
,参阅索引基础。如,dft_minute ['2011-12-31 23:59']
会触发KeyError
,这是因为2012-12-31 23:59
与索引的精度一样,但没有叫这个名字的列。
为了实现精准切片,要用 .loc
对行进行切片或选择。
In [130]: dft_minute.loc['2011-12-31 23:59'] Out[130]: a 1 b 4 Name: 2011-12-31 23:59:00, dtype: int64
注意,DatetimeIndex
精度不能低于日。
In [131]: series_monthly = pd.Series([1, 2, 3], .....: pd.DatetimeIndex(['2011-12', '2012-01', '2012-02'])) .....: In [132]: series_monthly.index.resolution Out[132]: 'day' In [133]: series_monthly['2011-12'] # 返回的是 Series Out[133]: 2011-12-01 1 dtype: int64
精确索引
正如上节所述,局部字符串依靠时间段的精度索引 DatetimeIndex
,即时间间隔与索引精度相关。反之,用 Timestamp
或 datetime
索引更精准,这些对象指定的时间更精确。注意,精确索引包含了起始时点。
就算没有显式指定,Timestamp
与datetime
也支持 hours
、minutes
、seconds
,默认值为 0。
In [134]: dft[datetime.datetime(2013, 1, 1):datetime.datetime(2013, 2, 28)] Out[134]: A 2013-01-01 00:00:00 0.276232 2013-01-01 00:01:00 -1.087401 2013-01-01 00:02:00 -0.673690 2013-01-01 00:03:00 0.113648 2013-01-01 00:04:00 -1.478427 ... ... 2013-02-27 23:56:00 1.197749 2013-02-27 23:57:00 0.720521 2013-02-27 23:58:00 -0.072718 2013-02-27 23:59:00 -0.681192 2013-02-28 00:00:00 -0.557501 [83521 rows x 1 columns]
不用默认值。
In [135]: dft[datetime.datetime(2013, 1, 1, 10, 12, 0): .....: datetime.datetime(2013, 2, 28, 10, 12, 0)] .....: Out[135]: A 2013-01-01 10:12:00 0.565375 2013-01-01 10:13:00 0.068184 2013-01-01 10:14:00 0.788871 2013-01-01 10:15:00 -0.280343 2013-01-01 10:16:00 0.931536 ... ... 2013-02-28 10:08:00 0.148098 2013-02-28 10:09:00 -0.388138 2013-02-28 10:10:00 0.139348 2013-02-28 10:11:00 0.085288 2013-02-28 10:12:00 0.950146 [83521 rows x 1 columns]
截断与花式索引
truncate()
便捷函数与切片类似。注意,与切片返回的是部分匹配日期不同, truncate
假设 DatetimeIndex
里未标明时间组件的值为 0。
In [136]: rng2 = pd.date_range('2011-01-01', '2012-01-01', freq='W') In [137]: ts2 = pd.Series(np.random.randn(len(rng2)), index=rng2) In [138]: ts2.truncate(before='2011-11', after='2011-12') Out[138]: 2011-11-06 0.437823 2011-11-13 -0.293083 2011-11-20 -0.059881 2011-11-27 1.252450 Freq: W-SUN, dtype: float64 In [139]: ts2['2011-11':'2011-12'] Out[139]: 2011-11-06 0.437823 2011-11-13 -0.293083 2011-11-20 -0.059881 2011-11-27 1.252450 2011-12-04 0.046611 2011-12-11 0.059478 2011-12-18 -0.286539 2011-12-25 0.841669 Freq: W-SUN, dtype: float64
花式索引返回 DatetimeIndex
, 但因为打乱了 DatetimeIndex
频率,丢弃了频率信息,见 freq=None
:
In [140]: ts2[[0, 2, 6]].index Out[140]: DatetimeIndex(['2011-01-02', '2011-01-16', '2011-02-13'], dtype='datetime64[ns]', freq=None)
日期/时间组件
以下日期/时间属性可以访问 Timestamp
或 DatetimeIndex
。
属性 |
说明 |
---|---|
year |
datetime 的年 |
month |
datetime 的月 |
day |
datetime 的日 |
hour |
datetime 的小时 |
minute |
datetime 的分钟 |
second |
datetime 的秒 |
microsecond |
datetime 的微秒 |
nanosecond |
datetime 的纳秒 |
date |
返回 datetime.date(不包含时区信息) |
time |
返回 datetime.time(不包含时区信息) |
timetz |
返回带本地时区信息的 datetime.time |
dayofyear |
一年里的第几天 |
weekofyear |
一年里的第几周 |
week |
一年里的第几周 |
dayofweek |
一周里的第几天,Monday=0, Sunday=6 |
weekday |
一周里的第几天,Monday=0, Sunday=6 |
weekday_name |
这一天是星期几 (如,Friday) |
quarter |
日期所处的季节:Jan-Mar = 1 等 |
days_in_month |
日期所在的月有多少天 |
is_month_start |
逻辑判断是不是月初(由频率定义) |
is_month_end |
逻辑判断是不是月末(由频率定义) |
is_quarter_start |
逻辑判断是不是季初(由频率定义) |
is_quarter_end |
逻辑判断是不是季末(由频率定义) |
is_year_start |
逻辑判断是不是年初(由频率定义) |
is_year_end |
逻辑判断是不是年末(由频率定义) |
is_leap_year |
逻辑判断是不是日期所在年是不是闰年 |
参照 .dt 访问器 一节介绍的知识点,Series
的值为 datetime
时,还可以用 .dt
访问这些属性。