时间序列 / 日期功能#
pandas 包含了处理各种领域时间序列数据的广泛功能和特性。通过使用 NumPy 的 datetime64
和 timedelta64
数据类型,pandas 整合了 scikits.timeseries
等其他 Python 库的许多功能,并为操作时间序列数据创建了大量新功能。
例如,pandas 支持
从各种来源和格式解析时间序列信息
In [1]: import datetime
In [2]: dti = pd.to_datetime(
...: ["1/1/2018", np.datetime64("2018-01-01"), datetime.datetime(2018, 1, 1)]
...: )
...:
In [3]: dti
Out[3]: DatetimeIndex(['2018-01-01', '2018-01-01', '2018-01-01'], dtype='datetime64[ns]', freq=None)
生成固定频率的日期和时间跨度序列
In [4]: dti = pd.date_range("2018-01-01", periods=3, freq="h")
In [5]: dti
Out[5]:
DatetimeIndex(['2018-01-01 00:00:00', '2018-01-01 01:00:00',
'2018-01-01 02:00:00'],
dtype='datetime64[ns]', freq='h')
处理和转换带时区信息的日期时间
In [6]: dti = dti.tz_localize("UTC")
In [7]: dti
Out[7]:
DatetimeIndex(['2018-01-01 00:00:00+00:00', '2018-01-01 01:00:00+00:00',
'2018-01-01 02:00:00+00:00'],
dtype='datetime64[ns, UTC]', freq='h')
In [8]: dti.tz_convert("US/Pacific")
Out[8]:
DatetimeIndex(['2017-12-31 16:00:00-08:00', '2017-12-31 17:00:00-08:00',
'2017-12-31 18:00:00-08:00'],
dtype='datetime64[ns, US/Pacific]', freq='h')
将时间序列重新采样或转换为特定频率
In [9]: idx = pd.date_range("2018-01-01", periods=5, freq="h")
In [10]: ts = pd.Series(range(len(idx)), index=idx)
In [11]: ts
Out[11]:
2018-01-01 00:00:00 0
2018-01-01 01:00:00 1
2018-01-01 02:00:00 2
2018-01-01 03:00:00 3
2018-01-01 04:00:00 4
Freq: h, dtype: int64
In [12]: ts.resample("2h").mean()
Out[12]:
2018-01-01 00:00:00 0.5
2018-01-01 02:00:00 2.5
2018-01-01 04:00:00 4.0
Freq: 2h, dtype: float64
使用绝对或相对时间增量执行日期和时间算术运算
In [13]: friday = pd.Timestamp("2018-01-05")
In [14]: friday.day_name()
Out[14]: 'Friday'
# Add 1 day
In [15]: saturday = friday + pd.Timedelta("1 day")
In [16]: saturday.day_name()
Out[16]: 'Saturday'
# Add 1 business day (Friday --> Monday)
In [17]: monday = friday + pd.offsets.BDay()
In [18]: monday.day_name()
Out[18]: 'Monday'
pandas 提供了一套相对紧凑且自包含的工具,用于执行上述任务以及更多功能。
概述#
pandas 包含 4 个与时间相关的通用概念
日期时间 (Date times): 一个带时区支持的特定日期和时间。类似于标准库中的
datetime.datetime
。时间差 (Time deltas): 一个绝对的时间持续时间。类似于标准库中的
datetime.timedelta
。时间跨度 (Time spans): 由时间点及其相关频率定义的时间范围。
日期偏移 (Date offsets): 一种遵循日历算术的相对时间持续时间。类似于
dateutil
包中的dateutil.relativedelta.relativedelta
。
概念 |
标量类 |
数组类 |
pandas 数据类型 |
主要创建方法 |
---|---|---|---|---|
日期时间 |
|
|
|
|
时间差 |
|
|
|
|
时间跨度 |
|
|
|
|
日期偏移 |
|
|
|
|
对于时间序列数据,通常将时间分量表示在 Series
或 DataFrame
的索引中,以便可以相对于时间元素执行操作。
In [19]: pd.Series(range(3), index=pd.date_range("2000", freq="D", periods=3))
Out[19]:
2000-01-01 0
2000-01-02 1
2000-01-03 2
Freq: D, dtype: int64
然而,Series
和 DataFrame
也可以直接支持将时间分量作为数据本身。
In [20]: pd.Series(pd.date_range("2000", freq="D", periods=3))
Out[20]:
0 2000-01-01
1 2000-01-02
2 2000-01-03
dtype: datetime64[ns]
Series
和 DataFrame
在传递给其构造函数时,对 datetime
、timedelta
和 Period
数据具有扩展的数据类型支持和功能。然而,DateOffset
数据将被存储为 object
数据。
In [21]: pd.Series(pd.period_range("1/1/2011", freq="M", periods=3))
Out[21]:
0 2011-01
1 2011-02
2 2011-03
dtype: period[M]
In [22]: pd.Series([pd.DateOffset(1), pd.DateOffset(2)])
Out[22]:
0 <DateOffset>
1 <2 * DateOffsets>
dtype: object
In [23]: pd.Series(pd.date_range("1/1/2011", freq="ME", periods=3))
Out[23]:
0 2011-01-31
1 2011-02-28
2 2011-03-31
dtype: datetime64[ns]
最后,pandas 将空日期时间、时间差和时间跨度表示为 NaT
,这对于表示缺失或空的日期类值非常有用,其行为类似于 np.nan
对于浮点数据的行为。
In [24]: pd.Timestamp(pd.NaT)
Out[24]: NaT
In [25]: pd.Timedelta(pd.NaT)
Out[25]: NaT
In [26]: pd.Period(pd.NaT)
Out[26]: NaT
# Equality acts as np.nan would
In [27]: pd.NaT == pd.NaT
Out[27]: False
时间戳与时间跨度#
时间戳数据是时间序列数据最基本的类型,它将值与时间点相关联。对于 pandas 对象,这意味着使用时间点。
In [28]: import datetime
In [29]: pd.Timestamp(datetime.datetime(2012, 5, 1))
Out[29]: Timestamp('2012-05-01 00:00:00')
In [30]: pd.Timestamp("2012-05-01")
Out[30]: Timestamp('2012-05-01 00:00:00')
In [31]: pd.Timestamp(2012, 5, 1)
Out[31]: Timestamp('2012-05-01 00:00:00')
然而,在许多情况下,将变化变量等事物与时间跨度相关联更为自然。Period
表示的时间跨度可以显式指定,或从日期时间字符串格式中推断出来。
例如
In [32]: pd.Period("2011-01")
Out[32]: Period('2011-01', 'M')
In [33]: pd.Period("2012-05", freq="D")
Out[33]: Period('2012-05-01', 'D')
Timestamp
和 Period
可以作为索引。由 Timestamp
和 Period
组成的列表会自动强制转换为 DatetimeIndex
和 PeriodIndex
。
In [34]: dates = [
....: pd.Timestamp("2012-05-01"),
....: pd.Timestamp("2012-05-02"),
....: pd.Timestamp("2012-05-03"),
....: ]
....:
In [35]: ts = pd.Series(np.random.randn(3), dates)
In [36]: type(ts.index)
Out[36]: pandas.core.indexes.datetimes.DatetimeIndex
In [37]: ts.index
Out[37]: DatetimeIndex(['2012-05-01', '2012-05-02', '2012-05-03'], dtype='datetime64[ns]', freq=None)
In [38]: ts
Out[38]:
2012-05-01 0.469112
2012-05-02 -0.282863
2012-05-03 -1.509059
dtype: float64
In [39]: periods = [pd.Period("2012-01"), pd.Period("2012-02"), pd.Period("2012-03")]
In [40]: ts = pd.Series(np.random.randn(3), periods)
In [41]: type(ts.index)
Out[41]: pandas.core.indexes.period.PeriodIndex
In [42]: ts.index
Out[42]: PeriodIndex(['2012-01', '2012-02', '2012-03'], dtype='period[M]')
In [43]: ts
Out[43]:
2012-01 -1.135632
2012-02 1.212112
2012-03 -0.173215
Freq: M, dtype: float64
pandas 允许您捕获这两种表示形式并在它们之间进行转换。在底层,pandas 使用 Timestamp
的实例表示时间戳,使用 DatetimeIndex
的实例表示时间戳序列。对于规则时间跨度,pandas 使用 Period
对象表示标量值,使用 PeriodIndex
表示跨度序列。未来版本将提供更好的支持,用于处理具有任意起点和终点的不规则间隔。
转换为时间戳#
要将 Series
或类列表对象(例如字符串、纪元或它们的混合)转换为日期类对象,可以使用 to_datetime
函数。当传递一个 Series
时,它返回一个 Series
(具有相同的索引),而类列表对象则转换为 DatetimeIndex
In [44]: pd.to_datetime(pd.Series(["Jul 31, 2009", "Jan 10, 2010", None]))
Out[44]:
0 2009-07-31
1 2010-01-10
2 NaT
dtype: datetime64[ns]
In [45]: pd.to_datetime(["2005/11/23", "2010/12/31"])
Out[45]: DatetimeIndex(['2005-11-23', '2010-12-31'], dtype='datetime64[ns]', freq=None)
如果您使用的日期格式是日优先(即欧洲风格),可以传递 dayfirst
标志
In [46]: pd.to_datetime(["04-01-2012 10:00"], dayfirst=True)
Out[46]: DatetimeIndex(['2012-01-04 10:00:00'], dtype='datetime64[ns]', freq=None)
In [47]: pd.to_datetime(["04-14-2012 10:00"], dayfirst=True)
Out[47]: DatetimeIndex(['2012-04-14 10:00:00'], dtype='datetime64[ns]', freq=None)
警告
您在上面的例子中看到 dayfirst
并不严格。如果一个日期无法按照日优先解析,它将像 dayfirst
为 False
那样进行解析,并且会发出警告。
如果您将单个字符串传递给 to_datetime
,它将返回一个 Timestamp
。 Timestamp
也可以接受字符串输入,但它不接受 dayfirst
或 format
等字符串解析选项,因此如果需要这些选项,请使用 to_datetime
。
In [48]: pd.to_datetime("2010/11/12")
Out[48]: Timestamp('2010-11-12 00:00:00')
In [49]: pd.Timestamp("2010/11/12")
Out[49]: Timestamp('2010-11-12 00:00:00')
您也可以直接使用 DatetimeIndex
构造函数
In [50]: pd.DatetimeIndex(["2018-01-01", "2018-01-03", "2018-01-05"])
Out[50]: DatetimeIndex(['2018-01-01', '2018-01-03', '2018-01-05'], dtype='datetime64[ns]', freq=None)
可以传入字符串 ‘infer’,以便在创建时将索引的频率设置为推断出的频率
In [51]: pd.DatetimeIndex(["2018-01-01", "2018-01-03", "2018-01-05"], freq="infer")
Out[51]: DatetimeIndex(['2018-01-01', '2018-01-03', '2018-01-05'], dtype='datetime64[ns]', freq='2D')
提供 format 参数#
除了必需的日期时间字符串外,还可以传递 format
参数以确保特定的解析。这还可能显著加快转换速度。
In [52]: pd.to_datetime("2010/11/12", format="%Y/%m/%d")
Out[52]: Timestamp('2010-11-12 00:00:00')
In [53]: pd.to_datetime("12-11-2010 00:00", format="%d-%m-%Y %H:%M")
Out[53]: Timestamp('2010-11-12 00:00:00')
有关指定 format
选项时可用的更多信息,请参见 Python 日期时间文档。
从多个 DataFrame 列组装日期时间#
您还可以传递一个由整数或字符串列组成的 DataFrame
,将其组装成一个由 Timestamps
组成的 Series
。
In [54]: df = pd.DataFrame(
....: {"year": [2015, 2016], "month": [2, 3], "day": [4, 5], "hour": [2, 3]}
....: )
....:
In [55]: pd.to_datetime(df)
Out[55]:
0 2015-02-04 02:00:00
1 2016-03-05 03:00:00
dtype: datetime64[ns]
您可以仅传递需要组装的列。
In [56]: pd.to_datetime(df[["year", "month", "day"]])
Out[56]:
0 2015-02-04
1 2016-03-05
dtype: datetime64[ns]
pd.to_datetime
在列名中查找日期时间分量的标准名称,包括
必需:
year
,month
,day
可选:
hour
,minute
,second
,millisecond
,microsecond
,nanosecond
无效数据#
默认行为 errors='raise'
是在无法解析时引发错误
In [57]: pd.to_datetime(['2009/07/31', 'asd'], errors='raise')
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[57], line 1
----> 1 pd.to_datetime(['2009/07/31', 'asd'], errors='raise')
File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:1099, in to_datetime(arg, errors, dayfirst, yearfirst, utc, format, exact, unit, infer_datetime_format, origin, cache)
1097 result = _convert_and_box_cache(argc, cache_array)
1098 else:
-> 1099 result = convert_listlike(argc, format)
1100 else:
1101 result = convert_listlike(np.array([arg]), format)[0]
File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:433, in _convert_listlike_datetimes(arg, format, name, utc, unit, errors, dayfirst, yearfirst, exact)
431 # `format` could be inferred, or user didn't ask for mixed-format parsing.
432 if format is not None and format != "mixed":
--> 433 return _array_strptime_with_fallback(arg, name, utc, format, exact, errors)
435 result, tz_parsed = objects_to_datetime64(
436 arg,
437 dayfirst=dayfirst,
(...)
441 allow_object=True,
442 )
444 if tz_parsed is not None:
445 # We can take a shortcut since the datetime64 numpy array
446 # is in UTC
File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:467, in _array_strptime_with_fallback(arg, name, utc, fmt, exact, errors)
456 def _array_strptime_with_fallback(
457 arg,
458 name,
(...)
462 errors: str,
463 ) -> Index:
464 """
465 Call array_strptime, with fallback behavior depending on 'errors'.
466 """
--> 467 result, tz_out = array_strptime(arg, fmt, exact=exact, errors=errors, utc=utc)
468 if tz_out is not None:
469 unit = np.datetime_data(result.dtype)[0]
File strptime.pyx:501, in pandas._libs.tslibs.strptime.array_strptime()
File strptime.pyx:451, in pandas._libs.tslibs.strptime.array_strptime()
File strptime.pyx:583, in pandas._libs.tslibs.strptime._parse_with_format()
ValueError: time data "asd" doesn't match format "%Y/%m/%d", at position 1. You might want to try:
- passing `format` if your strings have a consistent format;
- passing `format='ISO8601'` if your strings are all ISO8601 but not necessarily in exactly the same format;
- passing `format='mixed'`, and the format will be inferred for each element individually. You might want to use `dayfirst` alongside this.
传递 errors='coerce'
将无法解析的数据转换为 NaT
(不是时间)
In [58]: pd.to_datetime(["2009/07/31", "asd"], errors="coerce")
Out[58]: DatetimeIndex(['2009-07-31', 'NaT'], dtype='datetime64[ns]', freq=None)
纪元时间戳#
pandas 支持将整数或浮点纪元时间转换为 Timestamp
和 DatetimeIndex
。默认单位是纳秒,因为 Timestamp
对象在内部就是这样存储的。然而,纪元通常存储在另一个可以指定的 unit
中。这些是根据 origin
参数指定的起点计算的。
In [59]: pd.to_datetime(
....: [1349720105, 1349806505, 1349892905, 1349979305, 1350065705], unit="s"
....: )
....:
Out[59]:
DatetimeIndex(['2012-10-08 18:15:05', '2012-10-09 18:15:05',
'2012-10-10 18:15:05', '2012-10-11 18:15:05',
'2012-10-12 18:15:05'],
dtype='datetime64[ns]', freq=None)
In [60]: pd.to_datetime(
....: [1349720105100, 1349720105200, 1349720105300, 1349720105400, 1349720105500],
....: unit="ms",
....: )
....:
Out[60]:
DatetimeIndex(['2012-10-08 18:15:05.100000', '2012-10-08 18:15:05.200000',
'2012-10-08 18:15:05.300000', '2012-10-08 18:15:05.400000',
'2012-10-08 18:15:05.500000'],
dtype='datetime64[ns]', freq=None)
注意
unit
参数使用的字符串与 上面 讨论的 format
参数使用的字符串不同)。可用的单位列在 pandas.to_datetime()
的文档中。
使用指定了 tz
参数的纪元时间戳构造 Timestamp
或 DatetimeIndex
将引发 ValueError。如果您在另一个时区有墙上时间的纪元,可以将这些纪元读取为时区天真的时间戳,然后本地化到相应的时区
In [61]: pd.Timestamp(1262347200000000000).tz_localize("US/Pacific")
Out[61]: Timestamp('2010-01-01 12:00:00-0800', tz='US/Pacific')
In [62]: pd.DatetimeIndex([1262347200000000000]).tz_localize("US/Pacific")
Out[62]: DatetimeIndex(['2010-01-01 12:00:00-08:00'], dtype='datetime64[ns, US/Pacific]', freq=None)
注意
纪元时间将四舍五入到最接近的纳秒。
警告
浮点纪元时间的转换可能导致不准确和意想不到的结果。Python 浮点数 在十进制中有大约 15 位精度。从浮点数转换为高精度 Timestamp
期间的四舍五入是不可避免的。获得精确精度的唯一方法是使用固定宽度类型(例如 int64)。
In [63]: pd.to_datetime([1490195805.433, 1490195805.433502912], unit="s")
Out[63]: DatetimeIndex(['2017-03-22 15:16:45.433000088', '2017-03-22 15:16:45.433502913'], dtype='datetime64[ns]', freq=None)
In [64]: pd.to_datetime(1490195805433502912, unit="ns")
Out[64]: Timestamp('2017-03-22 15:16:45.433502912')
另请参阅
从时间戳到纪元#
要执行与上面相反的操作,即将 Timestamp
转换为“unix”纪元
In [65]: stamps = pd.date_range("2012-10-08 18:15:05", periods=4, freq="D")
In [66]: stamps
Out[66]:
DatetimeIndex(['2012-10-08 18:15:05', '2012-10-09 18:15:05',
'2012-10-10 18:15:05', '2012-10-11 18:15:05'],
dtype='datetime64[ns]', freq='D')
我们减去纪元(UTC 1970 年 1 月 1 日午夜),然后向下取整除以“单位”(1 秒)。
In [67]: (stamps - pd.Timestamp("1970-01-01")) // pd.Timedelta("1s")
Out[67]: Index([1349720105, 1349806505, 1349892905, 1349979305], dtype='int64')
使用 origin
参数#
使用 origin
参数,可以指定创建 DatetimeIndex
的替代起点。例如,使用 1960-01-01 作为起始日期
In [68]: pd.to_datetime([1, 2, 3], unit="D", origin=pd.Timestamp("1960-01-01"))
Out[68]: DatetimeIndex(['1960-01-02', '1960-01-03', '1960-01-04'], dtype='datetime64[ns]', freq=None)
默认设置为 origin='unix'
,默认为 1970-01-01 00:00:00
。通常称为“Unix 纪元”或 POSIX 时间。
In [69]: pd.to_datetime([1, 2, 3], unit="D")
Out[69]: DatetimeIndex(['1970-01-02', '1970-01-03', '1970-01-04'], dtype='datetime64[ns]', freq=None)
生成时间戳范围#
要生成带时间戳的索引,可以使用 DatetimeIndex
或 Index
构造函数,并传入日期时间对象列表
In [70]: dates = [
....: datetime.datetime(2012, 5, 1),
....: datetime.datetime(2012, 5, 2),
....: datetime.datetime(2012, 5, 3),
....: ]
....:
# Note the frequency information
In [71]: index = pd.DatetimeIndex(dates)
In [72]: index
Out[72]: DatetimeIndex(['2012-05-01', '2012-05-02', '2012-05-03'], dtype='datetime64[ns]', freq=None)
# Automatically converted to DatetimeIndex
In [73]: index = pd.Index(dates)
In [74]: index
Out[74]: DatetimeIndex(['2012-05-01', '2012-05-02', '2012-05-03'], dtype='datetime64[ns]', freq=None)
在实践中,这会变得非常繁琐,因为我们通常需要一个非常长的、包含大量时间戳的索引。如果我们需要规则频率的时间戳,可以使用 date_range()
和 bdate_range()
函数来创建 DatetimeIndex
。date_range
的默认频率是日历日,而 bdate_range
的默认频率是工作日
In [75]: start = datetime.datetime(2011, 1, 1)
In [76]: end = datetime.datetime(2012, 1, 1)
In [77]: index = pd.date_range(start, end)
In [78]: index
Out[78]:
DatetimeIndex(['2011-01-01', '2011-01-02', '2011-01-03', '2011-01-04',
'2011-01-05', '2011-01-06', '2011-01-07', '2011-01-08',
'2011-01-09', '2011-01-10',
...
'2011-12-23', '2011-12-24', '2011-12-25', '2011-12-26',
'2011-12-27', '2011-12-28', '2011-12-29', '2011-12-30',
'2011-12-31', '2012-01-01'],
dtype='datetime64[ns]', length=366, freq='D')
In [79]: index = pd.bdate_range(start, end)
In [80]: index
Out[80]:
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
'2011-01-07', '2011-01-10', '2011-01-11', '2011-01-12',
'2011-01-13', '2011-01-14',
...
'2011-12-19', '2011-12-20', '2011-12-21', '2011-12-22',
'2011-12-23', '2011-12-26', '2011-12-27', '2011-12-28',
'2011-12-29', '2011-12-30'],
dtype='datetime64[ns]', length=260, freq='B')
date_range
和 bdate_range
等便捷函数可以利用各种 频率别名
In [81]: pd.date_range(start, periods=1000, freq="ME")
Out[81]:
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-30',
'2011-05-31', '2011-06-30', '2011-07-31', '2011-08-31',
'2011-09-30', '2011-10-31',
...
'2093-07-31', '2093-08-31', '2093-09-30', '2093-10-31',
'2093-11-30', '2093-12-31', '2094-01-31', '2094-02-28',
'2094-03-31', '2094-04-30'],
dtype='datetime64[ns]', length=1000, freq='ME')
In [82]: pd.bdate_range(start, periods=250, freq="BQS")
Out[82]:
DatetimeIndex(['2011-01-03', '2011-04-01', '2011-07-01', '2011-10-03',
'2012-01-02', '2012-04-02', '2012-07-02', '2012-10-01',
'2013-01-01', '2013-04-01',
...
'2071-01-01', '2071-04-01', '2071-07-01', '2071-10-01',
'2072-01-01', '2072-04-01', '2072-07-01', '2072-10-03',
'2073-01-02', '2073-04-03'],
dtype='datetime64[ns]', length=250, freq='BQS-JAN')
date_range
和 bdate_range
可以轻松地使用 start
、end
、periods
和 freq
等参数的各种组合来生成日期范围。起始日期和结束日期是严格包含的,因此不会生成指定范围之外的日期
In [83]: pd.date_range(start, end, freq="BME")
Out[83]:
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='BME')
In [84]: pd.date_range(start, end, freq="W")
Out[84]:
DatetimeIndex(['2011-01-02', '2011-01-09', '2011-01-16', '2011-01-23',
'2011-01-30', '2011-02-06', '2011-02-13', '2011-02-20',
'2011-02-27', '2011-03-06', '2011-03-13', '2011-03-20',
'2011-03-27', '2011-04-03', '2011-04-10', '2011-04-17',
'2011-04-24', '2011-05-01', '2011-05-08', '2011-05-15',
'2011-05-22', '2011-05-29', '2011-06-05', '2011-06-12',
'2011-06-19', '2011-06-26', '2011-07-03', '2011-07-10',
'2011-07-17', '2011-07-24', '2011-07-31', '2011-08-07',
'2011-08-14', '2011-08-21', '2011-08-28', '2011-09-04',
'2011-09-11', '2011-09-18', '2011-09-25', '2011-10-02',
'2011-10-09', '2011-10-16', '2011-10-23', '2011-10-30',
'2011-11-06', '2011-11-13', '2011-11-20', '2011-11-27',
'2011-12-04', '2011-12-11', '2011-12-18', '2011-12-25',
'2012-01-01'],
dtype='datetime64[ns]', freq='W-SUN')
In [85]: pd.bdate_range(end=end, periods=20)
Out[85]:
DatetimeIndex(['2011-12-05', '2011-12-06', '2011-12-07', '2011-12-08',
'2011-12-09', '2011-12-12', '2011-12-13', '2011-12-14',
'2011-12-15', '2011-12-16', '2011-12-19', '2011-12-20',
'2011-12-21', '2011-12-22', '2011-12-23', '2011-12-26',
'2011-12-27', '2011-12-28', '2011-12-29', '2011-12-30'],
dtype='datetime64[ns]', freq='B')
In [86]: pd.bdate_range(start=start, periods=20)
Out[86]:
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
'2011-01-07', '2011-01-10', '2011-01-11', '2011-01-12',
'2011-01-13', '2011-01-14', '2011-01-17', '2011-01-18',
'2011-01-19', '2011-01-20', '2011-01-21', '2011-01-24',
'2011-01-25', '2011-01-26', '2011-01-27', '2011-01-28'],
dtype='datetime64[ns]', freq='B')
指定 start
、end
和 periods
将生成一个从 start
到 end
的均匀间隔日期范围(包含两端),结果 DatetimeIndex
中包含 periods
个元素
In [87]: pd.date_range("2018-01-01", "2018-01-05", periods=5)
Out[87]:
DatetimeIndex(['2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04',
'2018-01-05'],
dtype='datetime64[ns]', freq=None)
In [88]: pd.date_range("2018-01-01", "2018-01-05", periods=10)
Out[88]:
DatetimeIndex(['2018-01-01 00:00:00', '2018-01-01 10:40:00',
'2018-01-01 21:20:00', '2018-01-02 08:00:00',
'2018-01-02 18:40:00', '2018-01-03 05:20:00',
'2018-01-03 16:00:00', '2018-01-04 02:40:00',
'2018-01-04 13:20:00', '2018-01-05 00:00:00'],
dtype='datetime64[ns]', freq=None)
自定义频率范围#
bdate_range
还可以通过使用 weekmask
和 holidays
参数生成自定义频率日期范围。这些参数仅在传递自定义频率字符串时使用。
In [89]: weekmask = "Mon Wed Fri"
In [90]: holidays = [datetime.datetime(2011, 1, 5), datetime.datetime(2011, 3, 14)]
In [91]: pd.bdate_range(start, end, freq="C", weekmask=weekmask, holidays=holidays)
Out[91]:
DatetimeIndex(['2011-01-03', '2011-01-07', '2011-01-10', '2011-01-12',
'2011-01-14', '2011-01-17', '2011-01-19', '2011-01-21',
'2011-01-24', '2011-01-26',
...
'2011-12-09', '2011-12-12', '2011-12-14', '2011-12-16',
'2011-12-19', '2011-12-21', '2011-12-23', '2011-12-26',
'2011-12-28', '2011-12-30'],
dtype='datetime64[ns]', length=154, freq='C')
In [92]: pd.bdate_range(start, end, freq="CBMS", weekmask=weekmask)
Out[92]:
DatetimeIndex(['2011-01-03', '2011-02-02', '2011-03-02', '2011-04-01',
'2011-05-02', '2011-06-01', '2011-07-01', '2011-08-01',
'2011-09-02', '2011-10-03', '2011-11-02', '2011-12-02'],
dtype='datetime64[ns]', freq='CBMS')
另请参阅
时间戳限制#
时间戳表示的限制取决于所选的分辨率。对于纳秒分辨率,使用 64 位整数表示的时间跨度限制在约 584 年
In [93]: pd.Timestamp.min
Out[93]: Timestamp('1677-09-21 00:12:43.145224193')
In [94]: pd.Timestamp.max
Out[94]: Timestamp('2262-04-11 23:47:16.854775807')
选择秒分辨率时,可用范围增加到 +/- 2.9e11 年
。不同的分辨率可以通过 as_unit
互相转换。
另请参阅
索引#
DatetimeIndex
的主要用途之一是作为 pandas 对象的索引。DatetimeIndex
类包含许多与时间序列相关的优化
各种偏移量的大范围日期在底层预先计算并缓存,以便使后续日期范围的生成非常快速(只需获取一个切片)。
使用 pandas 对象的
shift
方法进行快速移动。具有相同频率的重叠
DatetimeIndex
对象的并集运算非常快(对于快速数据对齐很重要)。通过
year
,month
等属性快速访问日期字段。规则化函数如
snap
以及非常快速的asof
逻辑。
DatetimeIndex
对象具有普通 Index
对象的所有基本功能,以及一系列用于轻松处理频率的高级时间序列特定方法。
另请参阅
注意
虽然 pandas 不强制要求您拥有一个有序的日期索引,但如果日期未排序,其中一些方法的行为可能会出乎意料或不正确。
DatetimeIndex
可以像普通索引一样使用,并提供所有智能功能,如选择、切片等。
In [95]: rng = pd.date_range(start, end, freq="BME")
In [96]: ts = pd.Series(np.random.randn(len(rng)), index=rng)
In [97]: ts.index
Out[97]:
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='BME')
In [98]: ts[:5].index
Out[98]:
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-29',
'2011-05-31'],
dtype='datetime64[ns]', freq='BME')
In [99]: ts[::2].index
Out[99]:
DatetimeIndex(['2011-01-31', '2011-03-31', '2011-05-31', '2011-07-29',
'2011-09-30', '2011-11-30'],
dtype='datetime64[ns]', freq='2BME')
部分字符串索引#
可以将日期和能解析为时间戳的字符串作为索引参数传递
In [100]: ts["1/31/2011"]
Out[100]: 0.11920871129693428
In [101]: ts[datetime.datetime(2011, 12, 25):]
Out[101]:
2011-12-30 0.56702
Freq: BME, dtype: float64
In [102]: ts["10/31/2011":"12/31/2011"]
Out[102]:
2011-10-31 0.271860
2011-11-30 -0.424972
2011-12-30 0.567020
Freq: BME, dtype: float64
为了方便访问较长的时间序列,您还可以将年份或年份和月份作为字符串传递
In [103]: ts["2011"]
Out[103]:
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: BME, dtype: float64
In [104]: ts["2011-6"]
Out[104]:
2011-06-30 1.071804
Freq: BME, dtype: float64
这种类型的切片也适用于具有 DatetimeIndex
的 DataFrame
。由于部分字符串选择是一种标签切片形式,因此端点将被包含在内。这包括匹配包含日期上的时间
警告
从 pandas 1.2.0 开始,使用 getitem(例如 frame[dtstring]
)使用单个字符串索引 DataFrame
的行已被弃用(考虑到是索引行还是选择列的歧义性),并将在未来版本中移除。使用 .loc
的等效方法(例如 frame.loc[dtstring]
)仍然受支持。
In [105]: dft = pd.DataFrame(
.....: np.random.randn(100000, 1),
.....: columns=["A"],
.....: index=pd.date_range("20130101", periods=100000, freq="min"),
.....: )
.....:
In [106]: dft
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]
In [107]: dft.loc["2013"]
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-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 [108]: dft["2013-1":"2013-2"]
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"]
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-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 [110]: dft["2013-1":"2013-2-28 00:00:00"]
Out[110]:
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 [111]: dft["2013-1-15":"2013-1-15 12:30:00"]
Out[111]:
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]
具有 MultiIndex
的 DataFrame
也支持 DatetimeIndex
部分字符串索引
In [112]: 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 [113]: dft2
Out[113]:
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 [114]: dft2.loc["2013-01-05"]
Out[114]:
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 [115]: idx = pd.IndexSlice
In [116]: dft2 = dft2.swaplevel(0, 1).sort_index()
In [117]: dft2.loc[idx[:, "2013-01-05"], :]
Out[117]:
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
使用字符串索引进行切片也遵循 UTC 偏移。
In [118]: df = pd.DataFrame([0], index=pd.DatetimeIndex(["2019-01-01"], tz="US/Pacific"))
In [119]: df
Out[119]:
0
2019-01-01 00:00:00-08:00 0
In [120]: df["2019-01-01 12:00:00+04:00":"2019-01-01 13:00:00+04:00"]
Out[120]:
0
2019-01-01 00:00:00-08:00 0
切片与精确匹配#
用作索引参数的同一个字符串可以被视为切片或精确匹配,这取决于索引的分辨率。如果字符串的精度低于索引,它将被视为切片;否则,将被视为精确匹配。
考虑一个索引分辨率为分钟的 Series
对象
In [121]: 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 [122]: series_minute.index.resolution
Out[122]: 'minute'
精度低于分钟的时间戳字符串会得到一个 Series
对象。
In [123]: series_minute["2011-12-31 23"]
Out[123]:
2011-12-31 23:59:00 1
dtype: int64
具有分钟分辨率(或更精确)的时间戳字符串会得到一个标量,也就是说,它不会被转换为切片。
In [124]: series_minute["2011-12-31 23:59"]
Out[124]: 1
In [125]: series_minute["2011-12-31 23:59:00"]
Out[125]: 1
如果索引分辨率是秒,那么分钟精度的时间戳会得到一个 Series
。
In [126]: 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 [127]: series_second.index.resolution
Out[127]: 'second'
In [128]: series_second["2011-12-31 23:59"]
Out[128]:
2011-12-31 23:59:59 1
dtype: int64
如果时间戳字符串被视为切片,它也可以用于使用 .loc[]
索引 DataFrame
。
In [129]: dft_minute = pd.DataFrame(
.....: {"a": [1, 2, 3], "b": [4, 5, 6]}, index=series_minute.index
.....: )
.....:
In [130]: dft_minute.loc["2011-12-31 23"]
Out[130]:
a b
2011-12-31 23:59:00 1 4
警告
然而,如果字符串被视为精确匹配,DataFrame
的 []
中的选择将是列级别的而不是行级别的,参见 索引基础。例如,dft_minute['2011-12-31 23:59']
将引发 KeyError
,因为 '2012-12-31 23:59'
与索引具有相同的分辨率,且没有名为该字符串的列
为了始终获得无歧义的选择,无论行被视为切片还是单个选择,请使用 .loc
。
In [131]: dft_minute.loc["2011-12-31 23:59"]
Out[131]:
a 1
b 4
Name: 2011-12-31 23:59:00, dtype: int64
另请注意,DatetimeIndex
的分辨率不能低于天。
In [132]: series_monthly = pd.Series(
.....: [1, 2, 3], pd.DatetimeIndex(["2011-12", "2012-01", "2012-02"])
.....: )
.....:
In [133]: series_monthly.index.resolution
Out[133]: 'day'
In [134]: series_monthly["2011-12"] # returns Series
Out[134]:
2011-12-01 1
dtype: int64
精确索引#
如前一节所述,使用部分字符串索引 DatetimeIndex
取决于时期的“精度”,换句话说,即间隔相对于索引分辨率的具体程度。相比之下,使用 Timestamp
或 datetime
对象进行索引是精确的,因为这些对象具有精确的含义。它们也遵循包含两个端点的语义。
这些 Timestamp
和 datetime
对象具有精确的 hours, minutes,
和 seconds
,即使它们没有被显式指定(它们为 0
)。
In [135]: dft[datetime.datetime(2013, 1, 1): datetime.datetime(2013, 2, 28)]
Out[135]:
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 [136]: dft[
.....: datetime.datetime(2013, 1, 1, 10, 12, 0): datetime.datetime(
.....: 2013, 2, 28, 10, 12, 0
.....: )
.....: ]
.....:
Out[136]:
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 [137]: rng2 = pd.date_range("2011-01-01", "2012-01-01", freq="W")
In [138]: ts2 = pd.Series(np.random.randn(len(rng2)), index=rng2)
In [139]: ts2.truncate(before="2011-11", after="2011-12")
Out[139]:
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 [140]: ts2["2011-11":"2011-12"]
Out[140]:
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
,尽管频率会丢失
In [141]: ts2.iloc[[0, 2, 6]].index
Out[141]: DatetimeIndex(['2011-01-02', '2011-01-16', '2011-02-13'], dtype='datetime64[ns]', freq=None)
时间/日期分量#
可以从 Timestamp
或像 DatetimeIndex
这样的时间戳集合中访问几个时间/日期属性。
属性 |
描述 |
---|---|
year (年) |
日期时间的年份 |
month (月) |
日期时间的月份 |
day (日) |
日期时间的日期 |
hour (时) |
日期时间的小时 |
minute (分) |
日期时间的分钟 |
second (秒) |
日期时间的秒钟 |
microsecond (微秒) |
日期时间的微秒 |
nanosecond (纳秒) |
日期时间的纳秒 |
date (日期) |
返回 datetime.date (不包含时区信息) |
time (时间) |
返回 datetime.time (不包含时区信息) |
timetz (带时区的时间) |
以本地时间返回带有时区信息的 datetime.time |
dayofyear (一年中的天) |
一年中的序号日 |
day_of_year (一年中的天) |
一年中的序号日 |
weekofyear (一年中的周) |
一年中的周序号 |
week (周) |
一年中的周序号 |
dayofweek (一周中的天) |
一周中的天数,周一=0,周日=6 |
day_of_week (一周中的天) |
一周中的天数,周一=0,周日=6 |
weekday (工作日) |
一周中的天数,周一=0,周日=6 |
quarter (季度) |
日期的季度:一月-三月 = 1,四月-六月 = 2,等等。 |
days_in_month (月中天数) |
日期时间所在月份的天数 |
is_month_start (是否月初) |
逻辑值,指示是否为月份的第一天(由频率定义) |
is_month_end (是否月末) |
逻辑值,指示是否为月份的最后一天(由频率定义) |
is_quarter_start (是否季度初) |
逻辑值,指示是否为季度的第一天(由频率定义) |
is_quarter_end (是否季度末) |
逻辑值,指示是否为季度的最后一天(由频率定义) |
is_year_start (是否年初) |
逻辑值,指示是否为年份的第一天(由频率定义) |
is_year_end |
逻辑值,指示是否为年份的最后一天(由频率定义) |
is_leap_year |
逻辑值,指示日期是否属于闰年 |
此外,如果您有一个包含日期时间类值(datetimelike
)的 Series
,则可以通过 .dt
访问器来访问这些属性,具体细节请参阅 .dt 访问器 一节。
您可以从 ISO 8601 标准中获取 ISO 年的年、周和日组成部分
In [142]: idx = pd.date_range(start="2019-12-29", freq="D", periods=4)
In [143]: idx.isocalendar()
Out[143]:
year week day
2019-12-29 2019 52 7
2019-12-30 2020 1 1
2019-12-31 2020 1 2
2020-01-01 2020 1 3
In [144]: idx.to_series().dt.isocalendar()
Out[144]:
year week day
2019-12-29 2019 52 7
2019-12-30 2020 1 1
2019-12-31 2020 1 2
2020-01-01 2020 1 3
DateOffset 对象#
在前面的示例中,频率字符串(例如 'D'
)用于指定频率,该频率定义了
在使用
date_range()
时DatetimeIndex
中日期时间的间隔方式Period
或PeriodIndex
的频率
这些频率字符串映射到一个 DateOffset
对象及其子类。DateOffset
类似于 Timedelta
,后者表示一段持续时间,但 DateOffset
遵循特定的日历持续时间规则。例如,Timedelta
的一天总是将 datetimes
增加 24 小时,而 DateOffset
的一天会将 datetimes
增加到下一天的同一时间,无论由于夏令时一天是 23、24 还是 25 小时。然而,所有小于或等于一小时的 DateOffset
子类(Hour
、Minute
、Second
、Milli
、Micro
、Nano
)的行为类似于 Timedelta
并遵守绝对时间。
基本的 DateOffset
类似于 dateutil.relativedelta
(relativedelta 文档),后者按指定的相应日历持续时间移动日期时间。可以使用算术运算符(+
)来执行移动。
# This particular day contains a day light savings time transition
In [145]: ts = pd.Timestamp("2016-10-30 00:00:00", tz="Europe/Helsinki")
# Respects absolute time
In [146]: ts + pd.Timedelta(days=1)
Out[146]: Timestamp('2016-10-30 23:00:00+0200', tz='Europe/Helsinki')
# Respects calendar time
In [147]: ts + pd.DateOffset(days=1)
Out[147]: Timestamp('2016-10-31 00:00:00+0200', tz='Europe/Helsinki')
In [148]: friday = pd.Timestamp("2018-01-05")
In [149]: friday.day_name()
Out[149]: 'Friday'
# Add 2 business days (Friday --> Tuesday)
In [150]: two_business_days = 2 * pd.offsets.BDay()
In [151]: friday + two_business_days
Out[151]: Timestamp('2018-01-09 00:00:00')
In [152]: (friday + two_business_days).day_name()
Out[152]: 'Tuesday'
大多数 DateOffsets
都有相关的频率字符串或偏移别名,可以作为 freq
关键字参数传入。可用的日期偏移量和相关的频率字符串如下所示
日期偏移量 |
频率字符串 |
描述 |
---|---|---|
无 |
通用偏移类,默认为绝对 24 小时 |
|
|
工作日(周一至周五) |
|
|
自定义工作日 |
|
|
一周,可选择锚定到一周中的某一天 |
|
|
每月第 y 周的第 x 天 |
|
|
每月最后一周的第 x 天 |
|
|
日历月末 |
|
|
日历月初 |
|
|
工作月末 |
|
|
工作月初 |
|
|
自定义工作月末 |
|
|
自定义工作月初 |
|
|
每月的 15 日(或其他指定日期)和日历月末 |
|
|
每月的 15 日(或其他指定日期)和日历月初 |
|
|
日历季度末 |
|
|
日历季度初 |
|
|
工作季度末 |
|
|
工作季度初 |
|
|
零售业(又称 52-53 周)季度 |
|
|
日历年末 |
|
|
日历年初 |
|
|
工作年末 |
|
|
工作年初 |
|
|
零售业(又称 52-53 周)年 |
|
无 |
复活节假日 |
|
|
工作时 |
|
|
自定义工作时 |
|
|
一个绝对日期 |
|
|
一小时 |
|
|
一分钟 |
|
|
一秒 |
|
|
一毫秒 |
|
|
一微秒 |
|
|
一纳秒 |
DateOffsets
还提供了 rollforward()
和 rollback()
方法,分别用于相对于偏移量将日期向前或向后移动到有效的偏移日期。例如,由于工作日偏移量仅在工作日运行,它会将落在周末(周六和周日)的日期向前滚动到周一。
In [153]: ts = pd.Timestamp("2018-01-06 00:00:00")
In [154]: ts.day_name()
Out[154]: 'Saturday'
# BusinessHour's valid offset dates are Monday through Friday
In [155]: offset = pd.offsets.BusinessHour(start="09:00")
# Bring the date to the closest offset date (Monday)
In [156]: offset.rollforward(ts)
Out[156]: Timestamp('2018-01-08 09:00:00')
# Date is brought to the closest offset date first and then the hour is added
In [157]: ts + offset
Out[157]: Timestamp('2018-01-08 10:00:00')
这些操作默认会保留时间(小时、分钟等)信息。要将时间重置为午夜,请在应用操作之前或之后使用 normalize()
(取决于您是否希望在操作中包含时间信息)。
In [158]: ts = pd.Timestamp("2014-01-01 09:00")
In [159]: day = pd.offsets.Day()
In [160]: day + ts
Out[160]: Timestamp('2014-01-02 09:00:00')
In [161]: (day + ts).normalize()
Out[161]: Timestamp('2014-01-02 00:00:00')
In [162]: ts = pd.Timestamp("2014-01-01 22:00")
In [163]: hour = pd.offsets.Hour()
In [164]: hour + ts
Out[164]: Timestamp('2014-01-01 23:00:00')
In [165]: (hour + ts).normalize()
Out[165]: Timestamp('2014-01-01 00:00:00')
In [166]: (hour + pd.Timestamp("2014-01-01 23:30")).normalize()
Out[166]: Timestamp('2014-01-02 00:00:00')
参数化偏移量#
有些偏移量在创建时可以进行“参数化”,从而产生不同的行为。例如,用于生成周数据的 Week
偏移量接受一个 weekday
参数,这使得生成的日期总是落在周中的特定一天。
In [167]: d = datetime.datetime(2008, 8, 18, 9, 0)
In [168]: d
Out[168]: datetime.datetime(2008, 8, 18, 9, 0)
In [169]: d + pd.offsets.Week()
Out[169]: Timestamp('2008-08-25 09:00:00')
In [170]: d + pd.offsets.Week(weekday=4)
Out[170]: Timestamp('2008-08-22 09:00:00')
In [171]: (d + pd.offsets.Week(weekday=4)).weekday()
Out[171]: 4
In [172]: d - pd.offsets.Week()
Out[172]: Timestamp('2008-08-11 09:00:00')
normalize
选项在加法和减法中有效。
In [173]: d + pd.offsets.Week(normalize=True)
Out[173]: Timestamp('2008-08-25 00:00:00')
In [174]: d - pd.offsets.Week(normalize=True)
Out[174]: Timestamp('2008-08-11 00:00:00')
另一个例子是使用特定的结束月份对 YearEnd
进行参数化
In [175]: d + pd.offsets.YearEnd()
Out[175]: Timestamp('2008-12-31 09:00:00')
In [176]: d + pd.offsets.YearEnd(month=6)
Out[176]: Timestamp('2009-06-30 09:00:00')
将偏移量用于 Series
/ DatetimeIndex
#
偏移量可以与 Series
或 DatetimeIndex
一起使用,将偏移量应用于每个元素。
In [177]: rng = pd.date_range("2012-01-01", "2012-01-03")
In [178]: s = pd.Series(rng)
In [179]: rng
Out[179]: DatetimeIndex(['2012-01-01', '2012-01-02', '2012-01-03'], dtype='datetime64[ns]', freq='D')
In [180]: rng + pd.DateOffset(months=2)
Out[180]: DatetimeIndex(['2012-03-01', '2012-03-02', '2012-03-03'], dtype='datetime64[ns]', freq=None)
In [181]: s + pd.DateOffset(months=2)
Out[181]:
0 2012-03-01
1 2012-03-02
2 2012-03-03
dtype: datetime64[ns]
In [182]: s - pd.DateOffset(months=2)
Out[182]:
0 2011-11-01
1 2011-11-02
2 2011-11-03
dtype: datetime64[ns]
如果偏移量类直接映射到 Timedelta
(Day
、Hour
、Minute
、Second
、Micro
、Milli
、Nano
),则可以像 Timedelta
一样使用它 - 更多示例请参阅 Timedelta 一节。
In [183]: s - pd.offsets.Day(2)
Out[183]:
0 2011-12-30
1 2011-12-31
2 2012-01-01
dtype: datetime64[ns]
In [184]: td = s - pd.Series(pd.date_range("2011-12-29", "2011-12-31"))
In [185]: td
Out[185]:
0 3 days
1 3 days
2 3 days
dtype: timedelta64[ns]
In [186]: td + pd.offsets.Minute(15)
Out[186]:
0 3 days 00:15:00
1 3 days 00:15:00
2 3 days 00:15:00
dtype: timedelta64[ns]
请注意,某些偏移量(例如 BQuarterEnd
)没有向量化实现。它们仍然可以使用,但计算速度可能会慢很多,并且会显示 PerformanceWarning
。
In [187]: rng + pd.offsets.BQuarterEnd()
Out[187]: DatetimeIndex(['2012-03-30', '2012-03-30', '2012-03-30'], dtype='datetime64[ns]', freq=None)
自定义工作日#
CDay
或 CustomBusinessDay
类提供了一个参数化的 BusinessDay
类,可用于创建考虑当地假日和周末习俗的自定义工作日日历。
举一个有趣的例子,看看埃及,那里实行周五至周六的周末制。
In [188]: weekmask_egypt = "Sun Mon Tue Wed Thu"
# They also observe International Workers' Day so let's
# add that for a couple of years
In [189]: holidays = [
.....: "2012-05-01",
.....: datetime.datetime(2013, 5, 1),
.....: np.datetime64("2014-05-01"),
.....: ]
.....:
In [190]: bday_egypt = pd.offsets.CustomBusinessDay(
.....: holidays=holidays,
.....: weekmask=weekmask_egypt,
.....: )
.....:
In [191]: dt = datetime.datetime(2013, 4, 30)
In [192]: dt + 2 * bday_egypt
Out[192]: Timestamp('2013-05-05 00:00:00')
让我们映射到工作日名称
In [193]: dts = pd.date_range(dt, periods=5, freq=bday_egypt)
In [194]: pd.Series(dts.weekday, dts).map(pd.Series("Mon Tue Wed Thu Fri Sat Sun".split()))
Out[194]:
2013-04-30 Tue
2013-05-02 Thu
2013-05-05 Sun
2013-05-06 Mon
2013-05-07 Tue
Freq: C, dtype: object
假日日历可用于提供假日列表。更多信息请参阅 假日日历 一节。
In [195]: from pandas.tseries.holiday import USFederalHolidayCalendar
In [196]: bday_us = pd.offsets.CustomBusinessDay(calendar=USFederalHolidayCalendar())
# Friday before MLK Day
In [197]: dt = datetime.datetime(2014, 1, 17)
# Tuesday after MLK Day (Monday is skipped because it's a holiday)
In [198]: dt + bday_us
Out[198]: Timestamp('2014-01-21 00:00:00')
遵守特定假日日历的月度偏移量可以按常规方式定义。
In [199]: bmth_us = pd.offsets.CustomBusinessMonthBegin(calendar=USFederalHolidayCalendar())
# Skip new years
In [200]: dt = datetime.datetime(2013, 12, 17)
In [201]: dt + bmth_us
Out[201]: Timestamp('2014-01-02 00:00:00')
# Define date index with custom offset
In [202]: pd.date_range(start="20100101", end="20120101", freq=bmth_us)
Out[202]:
DatetimeIndex(['2010-01-04', '2010-02-01', '2010-03-01', '2010-04-01',
'2010-05-03', '2010-06-01', '2010-07-01', '2010-08-02',
'2010-09-01', '2010-10-01', '2010-11-01', '2010-12-01',
'2011-01-03', '2011-02-01', '2011-03-01', '2011-04-01',
'2011-05-02', '2011-06-01', '2011-07-01', '2011-08-01',
'2011-09-01', '2011-10-03', '2011-11-01', '2011-12-01'],
dtype='datetime64[ns]', freq='CBMS')
注意
频率字符串“C”用于指示使用了 CustomBusinessDay DateOffset,需要注意的是,CustomBusinessDay 是一个参数化类型,CustomBusinessDay 的实例可能有所不同,而这无法从频率字符串“C”中检测出来。因此,用户需要确保在应用程序中一致地使用频率字符串“C”。
工作时#
BusinessHour
类在 BusinessDay
上提供工作时表示,允许使用特定的开始和结束时间。
默认情况下,BusinessHour
使用 9:00 - 17:00 作为工作时间。添加 BusinessHour
将以小时频率增加 Timestamp
。如果目标 Timestamp
不在工作时间内,则移至下一个工作时间然后增加。如果结果超出工作时间的结束,剩余的小时数将添加到下一个工作日。
In [203]: bh = pd.offsets.BusinessHour()
In [204]: bh
Out[204]: <BusinessHour: bh=09:00-17:00>
# 2014-08-01 is Friday
In [205]: pd.Timestamp("2014-08-01 10:00").weekday()
Out[205]: 4
In [206]: pd.Timestamp("2014-08-01 10:00") + bh
Out[206]: Timestamp('2014-08-01 11:00:00')
# Below example is the same as: pd.Timestamp('2014-08-01 09:00') + bh
In [207]: pd.Timestamp("2014-08-01 08:00") + bh
Out[207]: Timestamp('2014-08-01 10:00:00')
# If the results is on the end time, move to the next business day
In [208]: pd.Timestamp("2014-08-01 16:00") + bh
Out[208]: Timestamp('2014-08-04 09:00:00')
# Remainings are added to the next day
In [209]: pd.Timestamp("2014-08-01 16:30") + bh
Out[209]: Timestamp('2014-08-04 09:30:00')
# Adding 2 business hours
In [210]: pd.Timestamp("2014-08-01 10:00") + pd.offsets.BusinessHour(2)
Out[210]: Timestamp('2014-08-01 12:00:00')
# Subtracting 3 business hours
In [211]: pd.Timestamp("2014-08-01 10:00") + pd.offsets.BusinessHour(-3)
Out[211]: Timestamp('2014-07-31 15:00:00')
您还可以通过关键字指定 start
和 end
时间。参数必须是表示 hour:minute
的 str
或 datetime.time
实例。将秒、微秒和纳秒指定为工作时会导致 ValueError
。
In [212]: bh = pd.offsets.BusinessHour(start="11:00", end=datetime.time(20, 0))
In [213]: bh
Out[213]: <BusinessHour: bh=11:00-20:00>
In [214]: pd.Timestamp("2014-08-01 13:00") + bh
Out[214]: Timestamp('2014-08-01 14:00:00')
In [215]: pd.Timestamp("2014-08-01 09:00") + bh
Out[215]: Timestamp('2014-08-01 12:00:00')
In [216]: pd.Timestamp("2014-08-01 18:00") + bh
Out[216]: Timestamp('2014-08-01 19:00:00')
传递晚于 end
的 start
时间表示跨午夜的工作时间。在这种情况下,工作时间会跨越午夜并延伸到第二天。有效的工作时间通过其是否从有效的 BusinessDay
开始来区分。
In [217]: bh = pd.offsets.BusinessHour(start="17:00", end="09:00")
In [218]: bh
Out[218]: <BusinessHour: bh=17:00-09:00>
In [219]: pd.Timestamp("2014-08-01 17:00") + bh
Out[219]: Timestamp('2014-08-01 18:00:00')
In [220]: pd.Timestamp("2014-08-01 23:00") + bh
Out[220]: Timestamp('2014-08-02 00:00:00')
# Although 2014-08-02 is Saturday,
# it is valid because it starts from 08-01 (Friday).
In [221]: pd.Timestamp("2014-08-02 04:00") + bh
Out[221]: Timestamp('2014-08-02 05:00:00')
# Although 2014-08-04 is Monday,
# it is out of business hours because it starts from 08-03 (Sunday).
In [222]: pd.Timestamp("2014-08-04 04:00") + bh
Out[222]: Timestamp('2014-08-04 18:00:00')
将 BusinessHour.rollforward
和 rollback
应用于非工作时间,结果会是下一个工作时间的开始或前一天的结束。与其他偏移量不同,根据定义,BusinessHour.rollforward
的输出可能与 apply
的结果不同。
这是因为一天的工作时间结束等于下一天的工作时间开始。例如,在默认工作时间(9:00 - 17:00)下,2014-08-01 17:00
和 2014-08-04 09:00
之间没有间隔(0 分钟)。
# This adjusts a Timestamp to business hour edge
In [223]: pd.offsets.BusinessHour().rollback(pd.Timestamp("2014-08-02 15:00"))
Out[223]: Timestamp('2014-08-01 17:00:00')
In [224]: pd.offsets.BusinessHour().rollforward(pd.Timestamp("2014-08-02 15:00"))
Out[224]: Timestamp('2014-08-04 09:00:00')
# It is the same as BusinessHour() + pd.Timestamp('2014-08-01 17:00').
# And it is the same as BusinessHour() + pd.Timestamp('2014-08-04 09:00')
In [225]: pd.offsets.BusinessHour() + pd.Timestamp("2014-08-02 15:00")
Out[225]: Timestamp('2014-08-04 10:00:00')
# BusinessDay results (for reference)
In [226]: pd.offsets.BusinessHour().rollforward(pd.Timestamp("2014-08-02"))
Out[226]: Timestamp('2014-08-04 09:00:00')
# It is the same as BusinessDay() + pd.Timestamp('2014-08-01')
# The result is the same as rollworward because BusinessDay never overlap.
In [227]: pd.offsets.BusinessHour() + pd.Timestamp("2014-08-02")
Out[227]: Timestamp('2014-08-04 10:00:00')
BusinessHour
将周六和周日视为假日。要使用任意假日,可以使用 CustomBusinessHour
偏移量,如下一小节所述。
自定义工作时#
CustomBusinessHour
是 BusinessHour
和 CustomBusinessDay
的结合,允许您指定任意假日。CustomBusinessHour
的工作方式与 BusinessHour
相同,只是它会跳过指定的自定义假日。
In [228]: from pandas.tseries.holiday import USFederalHolidayCalendar
In [229]: bhour_us = pd.offsets.CustomBusinessHour(calendar=USFederalHolidayCalendar())
# Friday before MLK Day
In [230]: dt = datetime.datetime(2014, 1, 17, 15)
In [231]: dt + bhour_us
Out[231]: Timestamp('2014-01-17 16:00:00')
# Tuesday after MLK Day (Monday is skipped because it's a holiday)
In [232]: dt + bhour_us * 2
Out[232]: Timestamp('2014-01-21 09:00:00')
您可以使用 BusinessHour
和 CustomBusinessDay
都支持的关键字参数。
In [233]: bhour_mon = pd.offsets.CustomBusinessHour(start="10:00", weekmask="Tue Wed Thu Fri")
# Monday is skipped because it's a holiday, business hour starts from 10:00
In [234]: dt + bhour_mon * 2
Out[234]: Timestamp('2014-01-21 10:00:00')
偏移别名#
许多常用的时间序列频率都有字符串别名。我们将这些别名称为 偏移别名。
别名 |
描述 |
---|---|
B |
工作日频率 |
C |
自定义工作日频率 |
D |
日历日频率 |
W |
每周频率 |
ME |
月末频率 |
SME |
半月月末频率(每月 15 日和月末) |
BME |
工作月末频率 |
CBME |
自定义工作月末频率 |
MS |
月初频率 |
SMS |
半月月初频率(每月 1 日和 15 日) |
BMS |
工作月初频率 |
CBMS |
自定义工作月初频率 |
QE |
季度末频率 |
BQE |
工作季度末频率 |
QS |
季度初频率 |
BQS |
工作季度初频率 |
YE |
年末频率 |
BYE |
工作年末频率 |
YS |
年初频率 |
BYS |
工作年初频率 |
h |
每小时频率 |
bh |
工作时频率 |
cbh |
自定义工作时频率 |
min |
每分钟频率 |
s |
每秒频率 |
ms |
毫秒 |
us |
微秒 |
ns |
纳秒 |
自 2.2.0 版本弃用: 别名 H
、BH
、CBH
、T
、S
、L
、U
和 N
已弃用,推荐使用别名 h
、bh
、cbh
、min
、s
、ms
、us
和 ns
。
注意
使用上述偏移别名时,应注意
date_range()
、bdate_range()
等函数只会返回在由start_date
和end_date
定义的区间内的 timestamp。如果start_date
不对应于频率,则返回的 timestamp 将从下一个有效的 timestamp 开始;对于end_date
也是如此,返回的 timestamp 将在之前一个有效的 timestamp 处停止。
例如,对于偏移量 MS
,如果 start_date
不是月份的第一天,返回的 timestamp 将从下一个月的第一天开始。如果 end_date
不是月份的第一天,最后一个返回的 timestamp 将是对应月份的第一天。
In [235]: dates_lst_1 = pd.date_range("2020-01-06", "2020-04-03", freq="MS")
In [236]: dates_lst_1
Out[236]: DatetimeIndex(['2020-02-01', '2020-03-01', '2020-04-01'], dtype='datetime64[ns]', freq='MS')
In [237]: dates_lst_2 = pd.date_range("2020-01-01", "2020-04-01", freq="MS")
In [238]: dates_lst_2
Out[238]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01', '2020-04-01'], dtype='datetime64[ns]', freq='MS')
从上面的示例可以看出,date_range()
和 bdate_range()
只会返回 start_date
和 end_date
之间的有效 timestamp。如果这些日期对于给定的频率不是有效的 timestamp,start_date
将向前滚动到下一个有效值(end_date
则向后滚动到前一个有效值)。
周期别名#
许多常用的时间序列频率都有字符串别名。我们将这些别名称为 周期别名。
别名 |
描述 |
---|---|
B |
工作日频率 |
D |
日历日频率 |
W |
每周频率 |
M |
每月频率 |
Q |
每季度频率 |
Y |
每年频率 |
h |
每小时频率 |
min |
每分钟频率 |
s |
每秒频率 |
ms |
毫秒 |
us |
微秒 |
ns |
纳秒 |
自 2.2.0 版本弃用: 别名 A
、H
、T
、S
、L
、U
和 N
已弃用,推荐使用别名 Y
、h
、min
、s
、ms
、us
和 ns
。
组合别名#
如前所述,别名和偏移量实例在大多数函数中可以互换使用
In [239]: pd.date_range(start, periods=5, freq="B")
Out[239]:
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
'2011-01-07'],
dtype='datetime64[ns]', freq='B')
In [240]: pd.date_range(start, periods=5, freq=pd.offsets.BDay())
Out[240]:
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
'2011-01-07'],
dtype='datetime64[ns]', freq='B')
您可以组合使用日期和日内偏移量
In [241]: pd.date_range(start, periods=10, freq="2h20min")
Out[241]:
DatetimeIndex(['2011-01-01 00:00:00', '2011-01-01 02:20:00',
'2011-01-01 04:40:00', '2011-01-01 07:00:00',
'2011-01-01 09:20:00', '2011-01-01 11:40:00',
'2011-01-01 14:00:00', '2011-01-01 16:20:00',
'2011-01-01 18:40:00', '2011-01-01 21:00:00'],
dtype='datetime64[ns]', freq='140min')
In [242]: pd.date_range(start, periods=10, freq="1D10us")
Out[242]:
DatetimeIndex([ '2011-01-01 00:00:00', '2011-01-02 00:00:00.000010',
'2011-01-03 00:00:00.000020', '2011-01-04 00:00:00.000030',
'2011-01-05 00:00:00.000040', '2011-01-06 00:00:00.000050',
'2011-01-07 00:00:00.000060', '2011-01-08 00:00:00.000070',
'2011-01-09 00:00:00.000080', '2011-01-10 00:00:00.000090'],
dtype='datetime64[ns]', freq='86400000010us')
锚定偏移量#
对于某些频率,您可以指定锚定后缀
别名 |
描述 |
---|---|
W-SUN |
每周频率(星期日)。与“W”相同 |
W-MON |
每周频率(星期一) |
W-TUE |
每周频率(星期二) |
W-WED |
每周频率(星期三) |
W-THU |
每周频率(星期四) |
W-FRI |
每周频率(星期五) |
W-SAT |
每周频率(星期六) |
(B)Q(E)(S)-DEC |
每季度频率,年度在 12 月结束。与“QE”相同 |
(B)Q(E)(S)-JAN |
每季度频率,年度在 1 月结束 |
(B)Q(E)(S)-FEB |
每季度频率,年度在 2 月结束 |
(B)Q(E)(S)-MAR |
每季度频率,年度在 3 月结束 |
(B)Q(E)(S)-APR |
每季度频率,年度在 4 月结束 |
(B)Q(E)(S)-MAY |
每季度频率,年度在 5 月结束 |
(B)Q(E)(S)-JUN |
每季度频率,年度在 6 月结束 |
(B)Q(E)(S)-JUL |
每季度频率,年度在 7 月结束 |
(B)Q(E)(S)-AUG |
每季度频率,年度在 8 月结束 |
(B)Q(E)(S)-SEP |
每季度频率,年度在 9 月结束 |
(B)Q(E)(S)-OCT |
每季度频率,年度在 10 月结束 |
(B)Q(E)(S)-NOV |
每季度频率,年度在 11 月结束 |
(B)Y(E)(S)-DEC |
每年频率,锚定在 12 月末。与“YE”相同 |
(B)Y(E)(S)-JAN |
每年频率,锚定在 1 月末 |
(B)Y(E)(S)-FEB |
每年频率,锚定在 2 月末 |
(B)Y(E)(S)-MAR |
每年频率,锚定在 3 月末 |
(B)Y(E)(S)-APR |
每年频率,锚定在 4 月末 |
(B)Y(E)(S)-MAY |
每年频率,锚定在 5 月末 |
(B)Y(E)(S)-JUN |
每年频率,锚定在 6 月末 |
(B)Y(E)(S)-JUL |
每年频率,锚定在 7 月末 |
(B)Y(E)(S)-AUG |
每年频率,锚定在 8 月末 |
(B)Y(E)(S)-SEP |
每年频率,锚定在 9 月末 |
(B)Y(E)(S)-OCT |
每年频率,锚定在 10 月末 |
(B)Y(E)(S)-NOV |
每年频率,锚定在 11 月末 |
这些可以用作 date_range
、bdate_range
、DatetimeIndex
的构造函数以及 pandas 中各种其他时间序列相关函数的参数。
锚定偏移量语义#
对于那些锚定在特定频率(MonthEnd
、MonthBegin
、WeekEnd
等)的开始或结束的偏移量,向前和向后滚动时适用以下规则。
当 n
不为 0 时,如果给定的日期不在锚定点上,它会捕捉到下一个(上一个)锚定点,然后额外向前或向后移动 |n|-1
步。
In [243]: pd.Timestamp("2014-01-02") + pd.offsets.MonthBegin(n=1)
Out[243]: Timestamp('2014-02-01 00:00:00')
In [244]: pd.Timestamp("2014-01-02") + pd.offsets.MonthEnd(n=1)
Out[244]: Timestamp('2014-01-31 00:00:00')
In [245]: pd.Timestamp("2014-01-02") - pd.offsets.MonthBegin(n=1)
Out[245]: Timestamp('2014-01-01 00:00:00')
In [246]: pd.Timestamp("2014-01-02") - pd.offsets.MonthEnd(n=1)
Out[246]: Timestamp('2013-12-31 00:00:00')
In [247]: pd.Timestamp("2014-01-02") + pd.offsets.MonthBegin(n=4)
Out[247]: Timestamp('2014-05-01 00:00:00')
In [248]: pd.Timestamp("2014-01-02") - pd.offsets.MonthBegin(n=4)
Out[248]: Timestamp('2013-10-01 00:00:00')
如果给定的日期 *正好在* 锚定点上,它会向前或向后移动 |n|
点。
In [249]: pd.Timestamp("2014-01-01") + pd.offsets.MonthBegin(n=1)
Out[249]: Timestamp('2014-02-01 00:00:00')
In [250]: pd.Timestamp("2014-01-31") + pd.offsets.MonthEnd(n=1)
Out[250]: Timestamp('2014-02-28 00:00:00')
In [251]: pd.Timestamp("2014-01-01") - pd.offsets.MonthBegin(n=1)
Out[251]: Timestamp('2013-12-01 00:00:00')
In [252]: pd.Timestamp("2014-01-31") - pd.offsets.MonthEnd(n=1)
Out[252]: Timestamp('2013-12-31 00:00:00')
In [253]: pd.Timestamp("2014-01-01") + pd.offsets.MonthBegin(n=4)
Out[253]: Timestamp('2014-05-01 00:00:00')
In [254]: pd.Timestamp("2014-01-31") - pd.offsets.MonthBegin(n=4)
Out[254]: Timestamp('2013-10-01 00:00:00')
当 n=0
时,如果日期在锚定点上则不移动,否则向前滚动到下一个锚定点。
In [255]: pd.Timestamp("2014-01-02") + pd.offsets.MonthBegin(n=0)
Out[255]: Timestamp('2014-02-01 00:00:00')
In [256]: pd.Timestamp("2014-01-02") + pd.offsets.MonthEnd(n=0)
Out[256]: Timestamp('2014-01-31 00:00:00')
In [257]: pd.Timestamp("2014-01-01") + pd.offsets.MonthBegin(n=0)
Out[257]: Timestamp('2014-01-01 00:00:00')
In [258]: pd.Timestamp("2014-01-31") + pd.offsets.MonthEnd(n=0)
Out[258]: Timestamp('2014-01-31 00:00:00')
假日 / 假日日历#
假日和日历提供了一种简单的方式来定义假日规则,以便与 CustomBusinessDay
一起使用或用于其他需要预定义假日集的分析。AbstractHolidayCalendar
类提供了返回假日列表所需的所有方法,只需在特定的假日日历类中定义 rules
。此外,start_date
和 end_date
类属性决定了生成假日的日期范围。这些属性应在 AbstractHolidayCalendar
类上被覆盖,以便该范围适用于所有日历子类。USFederalHolidayCalendar
是唯一存在的日历,主要用作开发其他日历的示例。
对于固定日期的假日(例如美国阵亡将士纪念日或 7 月 4 日),如果假日落在周末或其他非工作日,则观察规则决定何时庆祝该假日。已定义的观察规则有
规则 |
描述 |
---|---|
nearest_workday |
将周六移至周五,将周日移至周一 |
sunday_to_monday |
将周日移至下一个周一 |
next_monday_or_tuesday |
将周六移至周一,将周日/周一移至周二 |
previous_friday |
将周六和周日移至前一个周五” |
next_monday |
将周六和周日移至下一个周一 |
假日和假日日历的定义示例
In [259]: from pandas.tseries.holiday import (
.....: Holiday,
.....: USMemorialDay,
.....: AbstractHolidayCalendar,
.....: nearest_workday,
.....: MO,
.....: )
.....:
In [260]: class ExampleCalendar(AbstractHolidayCalendar):
.....: rules = [
.....: USMemorialDay,
.....: Holiday("July 4th", month=7, day=4, observance=nearest_workday),
.....: Holiday(
.....: "Columbus Day",
.....: month=10,
.....: day=1,
.....: offset=pd.DateOffset(weekday=MO(2)),
.....: ),
.....: ]
.....:
In [261]: cal = ExampleCalendar()
In [262]: cal.holidays(datetime.datetime(2012, 1, 1), datetime.datetime(2012, 12, 31))
Out[262]: DatetimeIndex(['2012-05-28', '2012-07-04', '2012-10-08'], dtype='datetime64[ns]', freq=None)
- 提示:
weekday=MO(2) 与 2 * Week(weekday=2) 相同
使用此日历,创建索引或执行偏移量算术运算会跳过周末和假日(即阵亡将士纪念日/7 月 4 日)。例如,下面使用 ExampleCalendar
定义了一个自定义工作日偏移量。与任何其他偏移量一样,它可用于创建 DatetimeIndex
或添加到 datetime
或 Timestamp
对象。
In [263]: pd.date_range(
.....: start="7/1/2012", end="7/10/2012", freq=pd.offsets.CDay(calendar=cal)
.....: ).to_pydatetime()
.....:
Out[263]:
array([datetime.datetime(2012, 7, 2, 0, 0),
datetime.datetime(2012, 7, 3, 0, 0),
datetime.datetime(2012, 7, 5, 0, 0),
datetime.datetime(2012, 7, 6, 0, 0),
datetime.datetime(2012, 7, 9, 0, 0),
datetime.datetime(2012, 7, 10, 0, 0)], dtype=object)
In [264]: offset = pd.offsets.CustomBusinessDay(calendar=cal)
In [265]: datetime.datetime(2012, 5, 25) + offset
Out[265]: Timestamp('2012-05-29 00:00:00')
In [266]: datetime.datetime(2012, 7, 3) + offset
Out[266]: Timestamp('2012-07-05 00:00:00')
In [267]: datetime.datetime(2012, 7, 3) + 2 * offset
Out[267]: Timestamp('2012-07-06 00:00:00')
In [268]: datetime.datetime(2012, 7, 6) + offset
Out[268]: Timestamp('2012-07-09 00:00:00')
日期范围由 AbstractHolidayCalendar
的 start_date
和 end_date
类属性定义。默认值如下所示。
In [269]: AbstractHolidayCalendar.start_date
Out[269]: Timestamp('1970-01-01 00:00:00')
In [270]: AbstractHolidayCalendar.end_date
Out[270]: Timestamp('2200-12-31 00:00:00')
通过将属性设置为 datetime/Timestamp/字符串,可以覆盖这些日期。
In [271]: AbstractHolidayCalendar.start_date = datetime.datetime(2012, 1, 1)
In [272]: AbstractHolidayCalendar.end_date = datetime.datetime(2012, 12, 31)
In [273]: cal.holidays()
Out[273]: DatetimeIndex(['2012-05-28', '2012-07-04', '2012-10-08'], dtype='datetime64[ns]', freq=None)
可以使用 get_calendar
函数按名称访问每个日历类,该函数返回一个假日类实例。任何导入的日历类都将通过此函数自动可用。HolidayCalendarFactory
还提供了一个简单的接口,用于创建日历的组合日历或具有附加规则的日历。
In [274]: from pandas.tseries.holiday import get_calendar, HolidayCalendarFactory, USLaborDay
In [275]: cal = get_calendar("ExampleCalendar")
In [276]: cal.rules
Out[276]:
[Holiday: Memorial Day (month=5, day=31, offset=<DateOffset: weekday=MO(-1)>),
Holiday: July 4th (month=7, day=4, observance=<function nearest_workday at 0x7fe8b0553c70>),
Holiday: Columbus Day (month=10, day=1, offset=<DateOffset: weekday=MO(+2)>)]
In [277]: new_cal = HolidayCalendarFactory("NewExampleCalendar", cal, USLaborDay)
In [278]: new_cal.rules
Out[278]:
[Holiday: Labor Day (month=9, day=1, offset=<DateOffset: weekday=MO(+1)>),
Holiday: Memorial Day (month=5, day=31, offset=<DateOffset: weekday=MO(-1)>),
Holiday: July 4th (month=7, day=4, observance=<function nearest_workday at 0x7fe8b0553c70>),
Holiday: Columbus Day (month=10, day=1, offset=<DateOffset: weekday=MO(+2)>)]
重采样#
pandas 提供了一个简单、强大且高效的功能,用于在频率转换期间执行重采样操作(例如,将秒级数据转换为 5 分钟级数据)。这在金融应用中极其常见,但不限于此。
resample()
是一个基于时间的 groupby,然后在每个分组上应用一个归约方法。请参阅一些 cookbook 示例 以了解一些高级策略。
resample()
方法可以直接从 DataFrameGroupBy
对象使用,请参阅 groupby 文档。
基础知识#
In [290]: rng = pd.date_range("1/1/2012", periods=100, freq="s")
In [291]: ts = pd.Series(np.random.randint(0, 500, len(rng)), index=rng)
In [292]: ts.resample("5Min").sum()
Out[292]:
2012-01-01 25103
Freq: 5min, dtype: int64
resample
函数非常灵活,允许你指定许多不同的参数来控制频率转换和重采样操作。
通过 GroupBy 可用的任何内置方法都可以作为返回对象的方法使用,包括 sum
, mean
, std
, sem
, max
, min
, median
, first
, last
, ohlc
In [293]: ts.resample("5Min").mean()
Out[293]:
2012-01-01 251.03
Freq: 5min, dtype: float64
In [294]: ts.resample("5Min").ohlc()
Out[294]:
open high low close
2012-01-01 308 460 9 205
In [295]: ts.resample("5Min").max()
Out[295]:
2012-01-01 460
Freq: 5min, dtype: int64
对于向下采样,可以将 closed
设置为 'left' 或 'right' 以指定间隔的哪一端是闭合的。
In [296]: ts.resample("5Min", closed="right").mean()
Out[296]:
2011-12-31 23:55:00 308.000000
2012-01-01 00:00:00 250.454545
Freq: 5min, dtype: float64
In [297]: ts.resample("5Min", closed="left").mean()
Out[297]:
2012-01-01 251.03
Freq: 5min, dtype: float64
label
等参数用于操作结果标签。label
指定结果是用间隔的开始还是结束进行标记。
In [298]: ts.resample("5Min").mean() # by default label='left'
Out[298]:
2012-01-01 251.03
Freq: 5min, dtype: float64
In [299]: ts.resample("5Min", label="left").mean()
Out[299]:
2012-01-01 251.03
Freq: 5min, dtype: float64
警告
除了 'ME', 'YE', 'QE', 'BME', 'BYE', 'BQE' 和 'W' 之外,所有频率偏移的 label
和 closed
的默认值都是 '**left**',这些频率的默认值都是 'right'。
这可能会无意中导致“向前看”,即稍后时间的值被拉回到之前的时间,如下面的使用 BusinessDay
频率的示例所示。
In [300]: s = pd.date_range("2000-01-01", "2000-01-05").to_series()
In [301]: s.iloc[2] = pd.NaT
In [302]: s.dt.day_name()
Out[302]:
2000-01-01 Saturday
2000-01-02 Sunday
2000-01-03 NaN
2000-01-04 Tuesday
2000-01-05 Wednesday
Freq: D, dtype: object
# default: label='left', closed='left'
In [303]: s.resample("B").last().dt.day_name()
Out[303]:
1999-12-31 Sunday
2000-01-03 NaN
2000-01-04 Tuesday
2000-01-05 Wednesday
Freq: B, dtype: object
注意星期日的值是如何被拉回到之前的星期五的。要获得星期日的值被推迟到星期一的行为,请改用
In [304]: s.resample("B", label="right", closed="right").last().dt.day_name()
Out[304]:
2000-01-03 Sunday
2000-01-04 Tuesday
2000-01-05 Wednesday
2000-01-06 NaN
Freq: B, dtype: object
axis
参数可以设置为 0 或 1,它允许你对 DataFrame
的指定轴进行重采样。
kind
可以设置为 'timestamp' 或 'period',用于将结果索引转换为 timestamp 或 time span 表示,或从 timestamp 或 time span 表示转换。默认情况下,resample
保留输入表示。
重采样 period 数据时(详见下文),convention
可以设置为 'start' 或 'end'。它指定了低频率周期如何转换为高频率周期。
向上采样#
对于向上采样,你可以指定向上采样的方式以及使用 limit
参数对创建的间隙进行插值。
# from secondly to every 250 milliseconds
In [305]: ts[:2].resample("250ms").asfreq()
Out[305]:
2012-01-01 00:00:00.000 308.0
2012-01-01 00:00:00.250 NaN
2012-01-01 00:00:00.500 NaN
2012-01-01 00:00:00.750 NaN
2012-01-01 00:00:01.000 204.0
Freq: 250ms, dtype: float64
In [306]: ts[:2].resample("250ms").ffill()
Out[306]:
2012-01-01 00:00:00.000 308
2012-01-01 00:00:00.250 308
2012-01-01 00:00:00.500 308
2012-01-01 00:00:00.750 308
2012-01-01 00:00:01.000 204
Freq: 250ms, dtype: int64
In [307]: ts[:2].resample("250ms").ffill(limit=2)
Out[307]:
2012-01-01 00:00:00.000 308.0
2012-01-01 00:00:00.250 308.0
2012-01-01 00:00:00.500 308.0
2012-01-01 00:00:00.750 NaN
2012-01-01 00:00:01.000 204.0
Freq: 250ms, dtype: float64
稀疏重采样#
稀疏时间序列是指相对于你要重采样的时间跨度,你拥有的数据点非常少。对稀疏序列进行简单的向上采样可能会生成大量中间值。当你不想使用方法填充这些值时,例如 fill_method
为 None
时,中间值将被填充为 NaN
。
由于 resample
是一个基于时间的 groupby,下面是一种仅对不全是 NaN
的分组进行高效重采样的方法。
In [308]: rng = pd.date_range("2014-1-1", periods=100, freq="D") + pd.Timedelta("1s")
In [309]: ts = pd.Series(range(100), index=rng)
如果我们想对序列的完整范围进行重采样
In [310]: ts.resample("3min").sum()
Out[310]:
2014-01-01 00:00:00 0
2014-01-01 00:03:00 0
2014-01-01 00:06:00 0
2014-01-01 00:09:00 0
2014-01-01 00:12:00 0
..
2014-04-09 23:48:00 0
2014-04-09 23:51:00 0
2014-04-09 23:54:00 0
2014-04-09 23:57:00 0
2014-04-10 00:00:00 99
Freq: 3min, Length: 47521, dtype: int64
我们可以转而仅对那些包含数据点的分组进行重采样,如下所示
In [311]: from functools import partial
In [312]: from pandas.tseries.frequencies import to_offset
In [313]: def round(t, freq):
.....: freq = to_offset(freq)
.....: td = pd.Timedelta(freq)
.....: return pd.Timestamp((t.value // td.value) * td.value)
.....:
In [314]: ts.groupby(partial(round, freq="3min")).sum()
Out[314]:
2014-01-01 0
2014-01-02 1
2014-01-03 2
2014-01-04 3
2014-01-05 4
..
2014-04-06 95
2014-04-07 96
2014-04-08 97
2014-04-09 98
2014-04-10 99
Length: 100, dtype: int64
聚合#
resample()
方法返回一个 pandas.api.typing.Resampler
实例。与 aggregating API、groupby API 和 window API 类似,Resampler
可以选择性地进行重采样。
对 DataFrame
进行重采样时,默认情况下会对所有列使用相同的函数。
In [315]: df = pd.DataFrame(
.....: np.random.randn(1000, 3),
.....: index=pd.date_range("1/1/2012", freq="s", periods=1000),
.....: columns=["A", "B", "C"],
.....: )
.....:
In [316]: r = df.resample("3min")
In [317]: r.mean()
Out[317]:
A B C
2012-01-01 00:00:00 -0.033823 -0.121514 -0.081447
2012-01-01 00:03:00 0.056909 0.146731 -0.024320
2012-01-01 00:06:00 -0.058837 0.047046 -0.052021
2012-01-01 00:09:00 0.063123 -0.026158 -0.066533
2012-01-01 00:12:00 0.186340 -0.003144 0.074752
2012-01-01 00:15:00 -0.085954 -0.016287 -0.050046
我们可以使用标准的 getitem 选择一个或多个特定的列。
In [318]: r["A"].mean()
Out[318]:
2012-01-01 00:00:00 -0.033823
2012-01-01 00:03:00 0.056909
2012-01-01 00:06:00 -0.058837
2012-01-01 00:09:00 0.063123
2012-01-01 00:12:00 0.186340
2012-01-01 00:15:00 -0.085954
Freq: 3min, Name: A, dtype: float64
In [319]: r[["A", "B"]].mean()
Out[319]:
A B
2012-01-01 00:00:00 -0.033823 -0.121514
2012-01-01 00:03:00 0.056909 0.146731
2012-01-01 00:06:00 -0.058837 0.047046
2012-01-01 00:09:00 0.063123 -0.026158
2012-01-01 00:12:00 0.186340 -0.003144
2012-01-01 00:15:00 -0.085954 -0.016287
你可以传递函数列表或字典进行聚合,输出一个 DataFrame
。
In [320]: r["A"].agg(["sum", "mean", "std"])
Out[320]:
sum mean std
2012-01-01 00:00:00 -6.088060 -0.033823 1.043263
2012-01-01 00:03:00 10.243678 0.056909 1.058534
2012-01-01 00:06:00 -10.590584 -0.058837 0.949264
2012-01-01 00:09:00 11.362228 0.063123 1.028096
2012-01-01 00:12:00 33.541257 0.186340 0.884586
2012-01-01 00:15:00 -8.595393 -0.085954 1.035476
对重采样的 DataFrame
,你可以传递函数列表应用到每一列,这将生成具有层次化索引的聚合结果。
In [321]: r.agg(["sum", "mean"])
Out[321]:
A ... C
sum mean ... sum mean
2012-01-01 00:00:00 -6.088060 -0.033823 ... -14.660515 -0.081447
2012-01-01 00:03:00 10.243678 0.056909 ... -4.377642 -0.024320
2012-01-01 00:06:00 -10.590584 -0.058837 ... -9.363825 -0.052021
2012-01-01 00:09:00 11.362228 0.063123 ... -11.975895 -0.066533
2012-01-01 00:12:00 33.541257 0.186340 ... 13.455299 0.074752
2012-01-01 00:15:00 -8.595393 -0.085954 ... -5.004580 -0.050046
[6 rows x 6 columns]
通过向 aggregate
传递一个字典,你可以对 DataFrame
的列应用不同的聚合。
In [322]: r.agg({"A": "sum", "B": lambda x: np.std(x, ddof=1)})
Out[322]:
A B
2012-01-01 00:00:00 -6.088060 1.001294
2012-01-01 00:03:00 10.243678 1.074597
2012-01-01 00:06:00 -10.590584 0.987309
2012-01-01 00:09:00 11.362228 0.944953
2012-01-01 00:12:00 33.541257 1.095025
2012-01-01 00:15:00 -8.595393 1.035312
函数名称也可以是字符串。要使字符串有效,它必须在重采样对象上实现。
In [323]: r.agg({"A": "sum", "B": "std"})
Out[323]:
A B
2012-01-01 00:00:00 -6.088060 1.001294
2012-01-01 00:03:00 10.243678 1.074597
2012-01-01 00:06:00 -10.590584 0.987309
2012-01-01 00:09:00 11.362228 0.944953
2012-01-01 00:12:00 33.541257 1.095025
2012-01-01 00:15:00 -8.595393 1.035312
此外,你还可以分别为每一列指定多个聚合函数。
In [324]: r.agg({"A": ["sum", "std"], "B": ["mean", "std"]})
Out[324]:
A B
sum std mean std
2012-01-01 00:00:00 -6.088060 1.043263 -0.121514 1.001294
2012-01-01 00:03:00 10.243678 1.058534 0.146731 1.074597
2012-01-01 00:06:00 -10.590584 0.949264 0.047046 0.987309
2012-01-01 00:09:00 11.362228 1.028096 -0.026158 0.944953
2012-01-01 00:12:00 33.541257 0.884586 -0.003144 1.095025
2012-01-01 00:15:00 -8.595393 1.035476 -0.016287 1.035312
如果 DataFrame
没有类似 datetime 的索引,但你希望基于 frame 中的类似 datetime 的列进行重采样,则可以将其传递给 on
关键字。
In [325]: df = pd.DataFrame(
.....: {"date": pd.date_range("2015-01-01", freq="W", periods=5), "a": np.arange(5)},
.....: index=pd.MultiIndex.from_arrays(
.....: [[1, 2, 3, 4, 5], pd.date_range("2015-01-01", freq="W", periods=5)],
.....: names=["v", "d"],
.....: ),
.....: )
.....:
In [326]: df
Out[326]:
date a
v d
1 2015-01-04 2015-01-04 0
2 2015-01-11 2015-01-11 1
3 2015-01-18 2015-01-18 2
4 2015-01-25 2015-01-25 3
5 2015-02-01 2015-02-01 4
In [327]: df.resample("ME", on="date")[["a"]].sum()
Out[327]:
a
date
2015-01-31 6
2015-02-28 4
同样,如果你想通过 MultiIndex
中类似 datetime 的层级进行重采样,可以将其名称或位置传递给 level
关键字。
In [328]: df.resample("ME", level="d")[["a"]].sum()
Out[328]:
a
d
2015-01-31 6
2015-02-28 4
迭代分组#
有了 Resampler
对象,迭代分组数据就非常自然,其功能类似于 itertools.groupby()
。
In [329]: small = pd.Series(
.....: range(6),
.....: index=pd.to_datetime(
.....: [
.....: "2017-01-01T00:00:00",
.....: "2017-01-01T00:30:00",
.....: "2017-01-01T00:31:00",
.....: "2017-01-01T01:00:00",
.....: "2017-01-01T03:00:00",
.....: "2017-01-01T03:05:00",
.....: ]
.....: ),
.....: )
.....:
In [330]: resampled = small.resample("h")
In [331]: for name, group in resampled:
.....: print("Group: ", name)
.....: print("-" * 27)
.....: print(group, end="\n\n")
.....:
Group: 2017-01-01 00:00:00
---------------------------
2017-01-01 00:00:00 0
2017-01-01 00:30:00 1
2017-01-01 00:31:00 2
dtype: int64
Group: 2017-01-01 01:00:00
---------------------------
2017-01-01 01:00:00 3
dtype: int64
Group: 2017-01-01 02:00:00
---------------------------
Series([], dtype: int64)
Group: 2017-01-01 03:00:00
---------------------------
2017-01-01 03:00:00 4
2017-01-01 03:05:00 5
dtype: int64
更多信息请参见 迭代分组 或 Resampler.__iter__
。
使用 origin
或 offset
调整分箱的开始#
分组的分箱根据时间序列起始点的当天的开始进行调整。这对于一天倍数的频率(例如 30D
)或能整除一天的频率(例如 90s
或 1min
)工作良好。这可能会与某些不符合此标准的频率产生不一致。要改变此行为,你可以使用参数 origin
指定一个固定的 Timestamp。
例如
In [332]: start, end = "2000-10-01 23:30:00", "2000-10-02 00:30:00"
In [333]: middle = "2000-10-02 00:00:00"
In [334]: rng = pd.date_range(start, end, freq="7min")
In [335]: ts = pd.Series(np.arange(len(rng)) * 3, index=rng)
In [336]: ts
Out[336]:
2000-10-01 23:30:00 0
2000-10-01 23:37:00 3
2000-10-01 23:44:00 6
2000-10-01 23:51:00 9
2000-10-01 23:58:00 12
2000-10-02 00:05:00 15
2000-10-02 00:12:00 18
2000-10-02 00:19:00 21
2000-10-02 00:26:00 24
Freq: 7min, dtype: int64
这里我们可以看到,当使用默认值 (`'start_day'`) 的 origin
时,'2000-10-02 00:00:00'
之后的结果会因时间序列的起始点而不同。
In [337]: ts.resample("17min", origin="start_day").sum()
Out[337]:
2000-10-01 23:14:00 0
2000-10-01 23:31:00 9
2000-10-01 23:48:00 21
2000-10-02 00:05:00 54
2000-10-02 00:22:00 24
Freq: 17min, dtype: int64
In [338]: ts[middle:end].resample("17min", origin="start_day").sum()
Out[338]:
2000-10-02 00:00:00 33
2000-10-02 00:17:00 45
Freq: 17min, dtype: int64
这里我们可以看到,当将 origin
设置为 `'epoch'` 时,'2000-10-02 00:00:00'
之后的结果会因时间序列的起始点而相同。
In [339]: ts.resample("17min", origin="epoch").sum()
Out[339]:
2000-10-01 23:18:00 0
2000-10-01 23:35:00 18
2000-10-01 23:52:00 27
2000-10-02 00:09:00 39
2000-10-02 00:26:00 24
Freq: 17min, dtype: int64
In [340]: ts[middle:end].resample("17min", origin="epoch").sum()
Out[340]:
2000-10-01 23:52:00 15
2000-10-02 00:09:00 39
2000-10-02 00:26:00 24
Freq: 17min, dtype: int64
如果需要,你可以为 origin
使用一个自定义的时间戳。
In [341]: ts.resample("17min", origin="2001-01-01").sum()
Out[341]:
2000-10-01 23:30:00 9
2000-10-01 23:47:00 21
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
In [342]: ts[middle:end].resample("17min", origin=pd.Timestamp("2001-01-01")).sum()
Out[342]:
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
如果需要,你只需使用一个 offset
Timedelta 来调整分箱,该 Timedelta 将添加到默认的 origin
中。这两个示例对于这个时间序列是等效的。
In [343]: ts.resample("17min", origin="start").sum()
Out[343]:
2000-10-01 23:30:00 9
2000-10-01 23:47:00 21
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
In [344]: ts.resample("17min", offset="23h30min").sum()
Out[344]:
2000-10-01 23:30:00 9
2000-10-01 23:47:00 21
2000-10-02 00:04:00 54
2000-10-02 00:21:00 24
Freq: 17min, dtype: int64
注意在最后一个示例中 origin
使用了 `'start'`。在这种情况下,origin
将被设置为时间序列的第一个值。
向后重采样#
在 1.3.0 版本中新增。
有时,我们不是调整分箱的开始,而是需要固定分箱的结束,以使用给定的 freq
进行向后重采样。向后重采样默认将 closed
设置为 `'right'`,因为最后一个值应被视为最后一个分箱的边缘点。
我们可以将 origin
设置为 `'end'`。特定 Timestamp
索引对应的值表示从当前 Timestamp
减去 freq
到当前 Timestamp
(右闭合)的重采样结果。
In [345]: ts.resample('17min', origin='end').sum()
Out[345]:
2000-10-01 23:35:00 0
2000-10-01 23:52:00 18
2000-10-02 00:09:00 27
2000-10-02 00:26:00 63
Freq: 17min, dtype: int64
此外,与 `'start_day'` 选项不同,`end_day` 受到支持。这将把 origin 设置为最大 Timestamp
的向上取整午夜时间。
In [346]: ts.resample('17min', origin='end_day').sum()
Out[346]:
2000-10-01 23:38:00 3
2000-10-01 23:55:00 15
2000-10-02 00:12:00 45
2000-10-02 00:29:00 45
Freq: 17min, dtype: int64
上述结果使用 '2000-10-02 00:29:00'
作为最后一个分箱的右边缘,因为进行了以下计算。
In [347]: ceil_mid = rng.max().ceil('D')
In [348]: freq = pd.offsets.Minute(17)
In [349]: bin_res = ceil_mid - freq * ((ceil_mid - rng.max()) // freq)
In [350]: bin_res
Out[350]: Timestamp('2000-10-02 00:29:00')
时间跨度表示#
pandas 中,规律的时间间隔由 Period
对象表示,而 Period
对象的序列则收集在 PeriodIndex
中,可以使用便捷函数 period_range
创建。
Period#
Period
代表一段时间跨度(例如,一天、一个月、一个季度等)。你可以使用 freq
关键字指定跨度,使用如下的频率别名。由于 freq
代表 Period
的跨度,它不能像“-3D”那样为负。
In [351]: pd.Period("2012", freq="Y-DEC")
Out[351]: Period('2012', 'Y-DEC')
In [352]: pd.Period("2012-1-1", freq="D")
Out[352]: Period('2012-01-01', 'D')
In [353]: pd.Period("2012-1-1 19:00", freq="h")
Out[353]: Period('2012-01-01 19:00', 'h')
In [354]: pd.Period("2012-1-1 19:00", freq="5h")
Out[354]: Period('2012-01-01 19:00', '5h')
对 period 加上或减去整数会按照 period 自身的频率移动 period。不同 freq
(跨度) 的 Period
之间不允许进行算术运算。
In [355]: p = pd.Period("2012", freq="Y-DEC")
In [356]: p + 1
Out[356]: Period('2013', 'Y-DEC')
In [357]: p - 3
Out[357]: Period('2009', 'Y-DEC')
In [358]: p = pd.Period("2012-01", freq="2M")
In [359]: p + 2
Out[359]: Period('2012-05', '2M')
In [360]: p - 1
Out[360]: Period('2011-11', '2M')
In [361]: p == pd.Period("2012-01", freq="3M")
Out[361]: False
如果 Period
的频率是天或更高频率(D
, h
, min
, s
, ms
, us
和 ns
),如果结果可以具有相同的频率,则可以添加 offsets
和类似 timedelta
的对象。否则,将引发 ValueError
。
In [362]: p = pd.Period("2014-07-01 09:00", freq="h")
In [363]: p + pd.offsets.Hour(2)
Out[363]: Period('2014-07-01 11:00', 'h')
In [364]: p + datetime.timedelta(minutes=120)
Out[364]: Period('2014-07-01 11:00', 'h')
In [365]: p + np.timedelta64(7200, "s")
Out[365]: Period('2014-07-01 11:00', 'h')
In [366]: p + pd.offsets.Minute(5)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
File period.pyx:1824, in pandas._libs.tslibs.period._Period._add_timedeltalike_scalar()
File timedeltas.pyx:278, in pandas._libs.tslibs.timedeltas.delta_to_nanoseconds()
File np_datetime.pyx:661, in pandas._libs.tslibs.np_datetime.convert_reso()
ValueError: Cannot losslessly convert units
The above exception was the direct cause of the following exception:
IncompatibleFrequency Traceback (most recent call last)
Cell In[366], line 1
----> 1 p + pd.offsets.Minute(5)
File period.pyx:1845, in pandas._libs.tslibs.period._Period.__add__()
File period.pyx:1826, in pandas._libs.tslibs.period._Period._add_timedeltalike_scalar()
IncompatibleFrequency: Input cannot be converted to Period(freq=h)
如果 Period
具有其他频率,则只能添加相同的 offsets
。否则,将引发 ValueError
。
In [367]: p = pd.Period("2014-07", freq="M")
In [368]: p + pd.offsets.MonthEnd(3)
Out[368]: Period('2014-10', 'M')
In [369]: p + pd.offsets.MonthBegin(3)
---------------------------------------------------------------------------
IncompatibleFrequency Traceback (most recent call last)
Cell In[369], line 1
----> 1 p + pd.offsets.MonthBegin(3)
File period.pyx:1847, in pandas._libs.tslibs.period._Period.__add__()
File period.pyx:1837, in pandas._libs.tslibs.period._Period._add_offset()
File period.pyx:1732, in pandas._libs.tslibs.period.PeriodMixin._require_matching_freq()
IncompatibleFrequency: Input has different freq=3M from Period(freq=M)
计算具有相同频率的 Period
实例之间的差值将返回它们之间的频率单位数量。
In [370]: pd.Period("2012", freq="Y-DEC") - pd.Period("2002", freq="Y-DEC")
Out[370]: <10 * YearEnds: month=12>
PeriodIndex 和 period_range#
规律的 Period
对象序列可以收集在 PeriodIndex
中,可以使用 period_range
便利函数进行构建。
In [371]: prng = pd.period_range("1/1/2011", "1/1/2012", freq="M")
In [372]: prng
Out[372]:
PeriodIndex(['2011-01', '2011-02', '2011-03', '2011-04', '2011-05', '2011-06',
'2011-07', '2011-08', '2011-09', '2011-10', '2011-11', '2011-12',
'2012-01'],
dtype='period[M]')
也可以直接使用 PeriodIndex
构造函数。
In [373]: pd.PeriodIndex(["2011-1", "2011-2", "2011-3"], freq="M")
Out[373]: PeriodIndex(['2011-01', '2011-02', '2011-03'], dtype='period[M]')
传递乘数频率会输出一个具有倍数跨度的 Period
序列。
In [374]: pd.period_range(start="2014-01", freq="3M", periods=4)
Out[374]: PeriodIndex(['2014-01', '2014-04', '2014-07', '2014-10'], dtype='period[3M]')
如果 start
或 end
是 Period
对象,它们将被用作 PeriodIndex
的锚定端点,其频率与 PeriodIndex
构造函数的频率匹配。
In [375]: pd.period_range(
.....: start=pd.Period("2017Q1", freq="Q"), end=pd.Period("2017Q2", freq="Q"), freq="M"
.....: )
.....:
Out[375]: PeriodIndex(['2017-03', '2017-04', '2017-05', '2017-06'], dtype='period[M]')
就像 DatetimeIndex
一样,PeriodIndex
也可以用于索引 pandas 对象。
In [376]: ps = pd.Series(np.random.randn(len(prng)), prng)
In [377]: ps
Out[377]:
2011-01 -2.916901
2011-02 0.514474
2011-03 1.346470
2011-04 0.816397
2011-05 2.258648
2011-06 0.494789
2011-07 0.301239
2011-08 0.464776
2011-09 -1.393581
2011-10 0.056780
2011-11 0.197035
2011-12 2.261385
2012-01 -0.329583
Freq: M, dtype: float64
PeriodIndex
支持与 Period
相同的加法和减法规则。
In [378]: idx = pd.period_range("2014-07-01 09:00", periods=5, freq="h")
In [379]: idx
Out[379]:
PeriodIndex(['2014-07-01 09:00', '2014-07-01 10:00', '2014-07-01 11:00',
'2014-07-01 12:00', '2014-07-01 13:00'],
dtype='period[h]')
In [380]: idx + pd.offsets.Hour(2)
Out[380]:
PeriodIndex(['2014-07-01 11:00', '2014-07-01 12:00', '2014-07-01 13:00',
'2014-07-01 14:00', '2014-07-01 15:00'],
dtype='period[h]')
In [381]: idx = pd.period_range("2014-07", periods=5, freq="M")
In [382]: idx
Out[382]: PeriodIndex(['2014-07', '2014-08', '2014-09', '2014-10', '2014-11'], dtype='period[M]')
In [383]: idx + pd.offsets.MonthEnd(3)
Out[383]: PeriodIndex(['2014-10', '2014-11', '2014-12', '2015-01', '2015-02'], dtype='period[M]')
PeriodIndex
有其自己的名为 period
的 dtype,请参见 Period Dtypes。
Period Dtypes#
PeriodIndex
有一个自定义的 period
dtype。这是一个 pandas 扩展 dtype,类似于 时区感知 dtype (datetime64[ns, tz]
)。
period
dtype 保存 freq
属性,并使用 period[freq]
表示,例如 period[D]
或 period[M]
,使用 频率字符串。
In [384]: pi = pd.period_range("2016-01-01", periods=3, freq="M")
In [385]: pi
Out[385]: PeriodIndex(['2016-01', '2016-02', '2016-03'], dtype='period[M]')
In [386]: pi.dtype
Out[386]: period[M]
period
dtype 可以在 .astype(...)
中使用。它允许像 .asfreq()
一样改变 PeriodIndex
的 freq
,以及像 to_period()
一样将 DatetimeIndex
转换为 PeriodIndex
。
# change monthly freq to daily freq
In [387]: pi.astype("period[D]")
Out[387]: PeriodIndex(['2016-01-31', '2016-02-29', '2016-03-31'], dtype='period[D]')
# convert to DatetimeIndex
In [388]: pi.astype("datetime64[ns]")
Out[388]: DatetimeIndex(['2016-01-01', '2016-02-01', '2016-03-01'], dtype='datetime64[ns]', freq='MS')
# convert to PeriodIndex
In [389]: dti = pd.date_range("2011-01-01", freq="ME", periods=3)
In [390]: dti
Out[390]: DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31'], dtype='datetime64[ns]', freq='ME')
In [391]: dti.astype("period[M]")
Out[391]: PeriodIndex(['2011-01', '2011-02', '2011-03'], dtype='period[M]')
PeriodIndex 部分字符串索引#
PeriodIndex 现在支持对非单调索引进行部分字符串切片。
你可以将日期和字符串传递给带有 PeriodIndex
的 Series
和 DataFrame
,方式与 DatetimeIndex
相同。有关详细信息,请参阅 DatetimeIndex 部分字符串索引。
In [392]: ps["2011-01"]
Out[392]: -2.9169013294054507
In [393]: ps[datetime.datetime(2011, 12, 25):]
Out[393]:
2011-12 2.261385
2012-01 -0.329583
Freq: M, dtype: float64
In [394]: ps["10/31/2011":"12/31/2011"]
Out[394]:
2011-10 0.056780
2011-11 0.197035
2011-12 2.261385
Freq: M, dtype: float64
传递一个代表比 PeriodIndex
更低频率的字符串会返回部分切片数据。
In [395]: ps["2011"]
Out[395]:
2011-01 -2.916901
2011-02 0.514474
2011-03 1.346470
2011-04 0.816397
2011-05 2.258648
2011-06 0.494789
2011-07 0.301239
2011-08 0.464776
2011-09 -1.393581
2011-10 0.056780
2011-11 0.197035
2011-12 2.261385
Freq: M, dtype: float64
In [396]: dfp = pd.DataFrame(
.....: np.random.randn(600, 1),
.....: columns=["A"],
.....: index=pd.period_range("2013-01-01 9:00", periods=600, freq="min"),
.....: )
.....:
In [397]: dfp
Out[397]:
A
2013-01-01 09:00 -0.538468
2013-01-01 09:01 -1.365819
2013-01-01 09:02 -0.969051
2013-01-01 09:03 -0.331152
2013-01-01 09:04 -0.245334
... ...
2013-01-01 18:55 0.522460
2013-01-01 18:56 0.118710
2013-01-01 18:57 0.167517
2013-01-01 18:58 0.922883
2013-01-01 18:59 1.721104
[600 rows x 1 columns]
In [398]: dfp.loc["2013-01-01 10h"]
Out[398]:
A
2013-01-01 10:00 -0.308975
2013-01-01 10:01 0.542520
2013-01-01 10:02 1.061068
2013-01-01 10:03 0.754005
2013-01-01 10:04 0.352933
... ...
2013-01-01 10:55 -0.865621
2013-01-01 10:56 -1.167818
2013-01-01 10:57 -2.081748
2013-01-01 10:58 -0.527146
2013-01-01 10:59 0.802298
[60 rows x 1 columns]
与 DatetimeIndex
一样,端点将包含在结果中。下面的例子切片了从 10:00 到 11:59 的数据。
In [399]: dfp["2013-01-01 10h":"2013-01-01 11h"]
Out[399]:
A
2013-01-01 10:00 -0.308975
2013-01-01 10:01 0.542520
2013-01-01 10:02 1.061068
2013-01-01 10:03 0.754005
2013-01-01 10:04 0.352933
... ...
2013-01-01 11:55 -0.590204
2013-01-01 11:56 1.539990
2013-01-01 11:57 -1.224826
2013-01-01 11:58 0.578798
2013-01-01 11:59 -0.685496
[120 rows x 1 columns]
使用 PeriodIndex 进行频率转换和重采样#
Period
和 PeriodIndex
的频率可以通过 asfreq
方法进行转换。让我们从结束于 12 月的 2011 财年开始。
In [400]: p = pd.Period("2011", freq="Y-DEC")
In [401]: p
Out[401]: Period('2011', 'Y-DEC')
我们可以将其转换为月度频率。使用 how
参数,我们可以指定是返回起始月份还是结束月份。
In [402]: p.asfreq("M", how="start")
Out[402]: Period('2011-01', 'M')
In [403]: p.asfreq("M", how="end")
Out[403]: Period('2011-12', 'M')
为了方便起见,提供了缩写 's' 和 'e'。
In [404]: p.asfreq("M", "s")
Out[404]: Period('2011-01', 'M')
In [405]: p.asfreq("M", "e")
Out[405]: Period('2011-12', 'M')
转换为“超周期”(例如,年度频率是季度频率的超周期)会自动返回包含输入 period 的超周期。
In [406]: p = pd.Period("2011-12", freq="M")
In [407]: p.asfreq("Y-NOV")
Out[407]: Period('2012', 'Y-NOV')
注意,由于我们转换为以 11 月结束的年度频率,2011 年 12 月的月度 period 实际上属于 2012 Y-NOV period。
带有锚定频率的 Period 转换对于处理经济学、商业及其他领域常见的各种季度数据特别有用。许多组织根据其财年开始和结束的月份来定义季度。因此,2011 年第一季度可能开始于 2010 年或 2011 年的几个月后。通过锚定频率,pandas 支持所有季度频率,从 Q-JAN
到 Q-DEC
。
Q-DEC
定义常规日历季度。
In [408]: p = pd.Period("2012Q1", freq="Q-DEC")
In [409]: p.asfreq("D", "s")
Out[409]: Period('2012-01-01', 'D')
In [410]: p.asfreq("D", "e")
Out[410]: Period('2012-03-31', 'D')
Q-MAR
定义财年结束于 3 月。
In [411]: p = pd.Period("2011Q4", freq="Q-MAR")
In [412]: p.asfreq("D", "s")
Out[412]: Period('2011-01-01', 'D')
In [413]: p.asfreq("D", "e")
Out[413]: Period('2011-03-31', 'D')
表示之间的转换#
带时间戳的数据可以使用 to_period
转换为 PeriodIndex-ed 数据,反之则使用 to_timestamp
。
In [414]: rng = pd.date_range("1/1/2012", periods=5, freq="ME")
In [415]: ts = pd.Series(np.random.randn(len(rng)), index=rng)
In [416]: ts
Out[416]:
2012-01-31 1.931253
2012-02-29 -0.184594
2012-03-31 0.249656
2012-04-30 -0.978151
2012-05-31 -0.873389
Freq: ME, dtype: float64
In [417]: ps = ts.to_period()
In [418]: ps
Out[418]:
2012-01 1.931253
2012-02 -0.184594
2012-03 0.249656
2012-04 -0.978151
2012-05 -0.873389
Freq: M, dtype: float64
In [419]: ps.to_timestamp()
Out[419]:
2012-01-01 1.931253
2012-02-01 -0.184594
2012-03-01 0.249656
2012-04-01 -0.978151
2012-05-01 -0.873389
Freq: MS, dtype: float64
记住可以使用 's' 和 'e' 返回 period 开始或结束时的时间戳。
In [420]: ps.to_timestamp("D", how="s")
Out[420]:
2012-01-01 1.931253
2012-02-01 -0.184594
2012-03-01 0.249656
2012-04-01 -0.978151
2012-05-01 -0.873389
Freq: MS, dtype: float64
在 period 和 timestamp 之间进行转换可以使用一些方便的算术函数。在下面的示例中,我们将一个财年结束于 11 月的季度频率转换为季度结束后的次月月末上午 9 点。
In [421]: prng = pd.period_range("1990Q1", "2000Q4", freq="Q-NOV")
In [422]: ts = pd.Series(np.random.randn(len(prng)), prng)
In [423]: ts.index = (prng.asfreq("M", "e") + 1).asfreq("h", "s") + 9
In [424]: ts.head()
Out[424]:
1990-03-01 09:00 -0.109291
1990-06-01 09:00 -0.637235
1990-09-01 09:00 -1.735925
1990-12-01 09:00 2.096946
1991-03-01 09:00 -1.039926
Freq: h, dtype: float64
表示超出边界的时间跨度#
如果你的数据超出了 Timestamp
的范围,请参见 Timestamp limitations,则可以使用 PeriodIndex
和/或由 Period
组成的 Series
进行计算。
In [425]: span = pd.period_range("1215-01-01", "1381-01-01", freq="D")
In [426]: span
Out[426]:
PeriodIndex(['1215-01-01', '1215-01-02', '1215-01-03', '1215-01-04',
'1215-01-05', '1215-01-06', '1215-01-07', '1215-01-08',
'1215-01-09', '1215-01-10',
...
'1380-12-23', '1380-12-24', '1380-12-25', '1380-12-26',
'1380-12-27', '1380-12-28', '1380-12-29', '1380-12-30',
'1380-12-31', '1381-01-01'],
dtype='period[D]', length=60632)
从基于 int64
的 YYYYMMDD 表示进行转换。
In [427]: s = pd.Series([20121231, 20141130, 99991231])
In [428]: s
Out[428]:
0 20121231
1 20141130
2 99991231
dtype: int64
In [429]: def conv(x):
.....: return pd.Period(year=x // 10000, month=x // 100 % 100, day=x % 100, freq="D")
.....:
In [430]: s.apply(conv)
Out[430]:
0 2012-12-31
1 2014-11-30
2 9999-12-31
dtype: period[D]
In [431]: s.apply(conv)[2]
Out[431]: Period('9999-12-31', 'D')
这些可以轻松转换为 PeriodIndex
。
In [432]: span = pd.PeriodIndex(s.apply(conv))
In [433]: span
Out[433]: PeriodIndex(['2012-12-31', '2014-11-30', '9999-12-31'], dtype='period[D]')
时区处理#
pandas 使用 pytz
和 dateutil
库或标准库中的 datetime.timezone
对象,为处理不同时区的 timestamp 提供了丰富的支持。
使用时区#
默认情况下,pandas 对象是时区无感知的。
In [434]: rng = pd.date_range("3/6/2012 00:00", periods=15, freq="D")
In [435]: rng.tz is None
Out[435]: True
要将这些日期本地化到特定时区(为无感知日期分配特定时区),可以使用 tz_localize
方法或 date_range()
、Timestamp
或 DatetimeIndex
中的 tz
关键字参数。你可以传递 pytz
或 dateutil
时区对象,或者 Olson 时区数据库字符串。Olson 时区字符串默认返回 pytz
时区对象。要返回 dateutil
时区对象,请在字符串前加上 dateutil/
。
在
pytz
中,你可以使用from pytz import common_timezones, all_timezones
找到常见(和不太常见)时区的列表。dateutil
使用操作系统时区,因此没有固定的可用列表。对于常见时区,名称与pytz
相同。
In [436]: import dateutil
# pytz
In [437]: rng_pytz = pd.date_range("3/6/2012 00:00", periods=3, freq="D", tz="Europe/London")
In [438]: rng_pytz.tz
Out[438]: <DstTzInfo 'Europe/London' LMT-1 day, 23:59:00 STD>
# dateutil
In [439]: rng_dateutil = pd.date_range("3/6/2012 00:00", periods=3, freq="D")
In [440]: rng_dateutil = rng_dateutil.tz_localize("dateutil/Europe/London")
In [441]: rng_dateutil.tz
Out[441]: tzfile('/usr/share/zoneinfo/Europe/London')
# dateutil - utc special case
In [442]: rng_utc = pd.date_range(
.....: "3/6/2012 00:00",
.....: periods=3,
.....: freq="D",
.....: tz=dateutil.tz.tzutc(),
.....: )
.....:
In [443]: rng_utc.tz
Out[443]: tzutc()
# datetime.timezone
In [444]: rng_utc = pd.date_range(
.....: "3/6/2012 00:00",
.....: periods=3,
.....: freq="D",
.....: tz=datetime.timezone.utc,
.....: )
.....:
In [445]: rng_utc.tz
Out[445]: datetime.timezone.utc
注意,UTC
时区在 dateutil
中是一个特殊情况,应该显式构造为 dateutil.tz.tzutc
的实例。你也可以先显式构造其他时区对象。
In [446]: import pytz
# pytz
In [447]: tz_pytz = pytz.timezone("Europe/London")
In [448]: rng_pytz = pd.date_range("3/6/2012 00:00", periods=3, freq="D")
In [449]: rng_pytz = rng_pytz.tz_localize(tz_pytz)
In [450]: rng_pytz.tz == tz_pytz
Out[450]: True
# dateutil
In [451]: tz_dateutil = dateutil.tz.gettz("Europe/London")
In [452]: rng_dateutil = pd.date_range("3/6/2012 00:00", periods=3, freq="D", tz=tz_dateutil)
In [453]: rng_dateutil.tz == tz_dateutil
Out[453]: True
要将一个时区感知的 pandas 对象从一个时区转换为另一个时区,可以使用 tz_convert
方法。
In [454]: rng_pytz.tz_convert("US/Eastern")
Out[454]:
DatetimeIndex(['2012-03-05 19:00:00-05:00', '2012-03-06 19:00:00-05:00',
'2012-03-07 19:00:00-05:00'],
dtype='datetime64[ns, US/Eastern]', freq=None)
注意
使用 pytz
时区时,对于相同的时区输入,DatetimeIndex
将构造一个与 Timestamp
不同的时区对象。一个 DatetimeIndex
可以包含可能具有不同 UTC 偏移量的 Timestamp
对象集合,无法用一个 pytz
时区实例简洁地表示,而一个 Timestamp
代表一个具有特定 UTC 偏移量的时间点。
In [455]: dti = pd.date_range("2019-01-01", periods=3, freq="D", tz="US/Pacific")
In [456]: dti.tz
Out[456]: <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>
In [457]: ts = pd.Timestamp("2019-01-01", tz="US/Pacific")
In [458]: ts.tz
Out[458]: <DstTzInfo 'US/Pacific' PST-1 day, 16:00:00 STD>
警告
注意库之间的转换。对于某些时区,pytz
和 dateutil
对时区的定义不同。这对于不常见的时区来说问题更大,而不是对于像 US/Eastern
这样的“标准”时区。
警告
请注意,不同版本的时区库中的时区定义可能不被视为相等。这可能会在使用一个版本本地化存储数据,然后使用另一个不同版本对数据进行操作时导致问题。有关如何处理这种情况,请参阅此处。
警告
对于 pytz
时区,将时区对象直接传递到 datetime.datetime
构造函数是错误的(例如,datetime.datetime(2011, 1, 1, tzinfo=pytz.timezone('US/Eastern'))
)。相反,需要使用 pytz
时区对象上的 localize
方法来本地化 datetime。
警告
请注意,对于未来的时间,任何时区库都无法保证时区之间(以及与 UTC 之间)的正确转换,因为时区相对于 UTC 的偏移量可能会被相关政府更改。
警告
如果您使用的日期超出 2038-01-18,由于底层库目前存在由 2038 年问题导致的缺陷,日光节约时间 (DST) 对时区感知日期的调整将不会生效。如果底层库得到修复,DST 过渡将生效。
例如,对于两个属于英国夏令时(因此通常为 GMT+1)的日期,以下断言都评估为 True:
In [459]: d_2037 = "2037-03-31T010101"
In [460]: d_2038 = "2038-03-31T010101"
In [461]: DST = "Europe/London"
In [462]: assert pd.Timestamp(d_2037, tz=DST) != pd.Timestamp(d_2037, tz="GMT")
In [463]: assert pd.Timestamp(d_2038, tz=DST) == pd.Timestamp(d_2038, tz="GMT")
在底层,所有时间戳都以 UTC 格式存储。来自时区感知 DatetimeIndex
或 Timestamp
的值将其字段(日、小时、分钟等)本地化到相应的时区。然而,具有相同 UTC 值的时间戳即使位于不同时区,仍被视为相等。
In [464]: rng_eastern = rng_utc.tz_convert("US/Eastern")
In [465]: rng_berlin = rng_utc.tz_convert("Europe/Berlin")
In [466]: rng_eastern[2]
Out[466]: Timestamp('2012-03-07 19:00:00-0500', tz='US/Eastern')
In [467]: rng_berlin[2]
Out[467]: Timestamp('2012-03-08 01:00:00+0100', tz='Europe/Berlin')
In [468]: rng_eastern[2] == rng_berlin[2]
Out[468]: True
在不同时区中的 Series
之间进行操作将产生 UTC Series
,数据将基于 UTC 时间戳对齐。
In [469]: ts_utc = pd.Series(range(3), pd.date_range("20130101", periods=3, tz="UTC"))
In [470]: eastern = ts_utc.tz_convert("US/Eastern")
In [471]: berlin = ts_utc.tz_convert("Europe/Berlin")
In [472]: result = eastern + berlin
In [473]: result
Out[473]:
2013-01-01 00:00:00+00:00 0
2013-01-02 00:00:00+00:00 2
2013-01-03 00:00:00+00:00 4
Freq: D, dtype: int64
In [474]: result.index
Out[474]:
DatetimeIndex(['2013-01-01 00:00:00+00:00', '2013-01-02 00:00:00+00:00',
'2013-01-03 00:00:00+00:00'],
dtype='datetime64[ns, UTC]', freq='D')
要删除时区信息,请使用 tz_localize(None)
或 tz_convert(None)
。tz_localize(None)
将删除时区并产生本地时间表示。tz_convert(None)
将在转换为 UTC 时间后删除时区。
In [475]: didx = pd.date_range(start="2014-08-01 09:00", freq="h", periods=3, tz="US/Eastern")
In [476]: didx
Out[476]:
DatetimeIndex(['2014-08-01 09:00:00-04:00', '2014-08-01 10:00:00-04:00',
'2014-08-01 11:00:00-04:00'],
dtype='datetime64[ns, US/Eastern]', freq='h')
In [477]: didx.tz_localize(None)
Out[477]:
DatetimeIndex(['2014-08-01 09:00:00', '2014-08-01 10:00:00',
'2014-08-01 11:00:00'],
dtype='datetime64[ns]', freq=None)
In [478]: didx.tz_convert(None)
Out[478]:
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
'2014-08-01 15:00:00'],
dtype='datetime64[ns]', freq='h')
# tz_convert(None) is identical to tz_convert('UTC').tz_localize(None)
In [479]: didx.tz_convert("UTC").tz_localize(None)
Out[479]:
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
'2014-08-01 15:00:00'],
dtype='datetime64[ns]', freq=None)
Fold#
对于有歧义的时间,pandas 支持明确指定仅限关键字的 fold 参数。由于夏令时,当从夏季切换到冬季时,一个挂钟时间可能会出现两次;fold 描述了 datetime-like 对象是对应于挂钟第一次 (0) 还是第二次 (1) 达到有歧义时间。Fold 仅支持从朴素 (naive) 的 datetime.datetime
构造(详情请参阅 datetime 文档),或从 Timestamp
构造,或从组件构造(见下文)。仅支持 dateutil
时区(有关处理有歧义 datetime 的 dateutil
方法,请参阅 dateutil 文档),因为 pytz
时区不支持 fold(有关 pytz
如何处理有歧义 datetime 的详细信息,请参阅 pytz 文档)。要使用 pytz
本地化有歧义的 datetime,请使用 Timestamp.tz_localize()
。总的来说,如果您需要直接控制如何处理有歧义的 datetime,我们建议在本地化时依赖 Timestamp.tz_localize()
。
In [480]: pd.Timestamp(
.....: datetime.datetime(2019, 10, 27, 1, 30, 0, 0),
.....: tz="dateutil/Europe/London",
.....: fold=0,
.....: )
.....:
Out[480]: Timestamp('2019-10-27 01:30:00+0100', tz='dateutil//usr/share/zoneinfo/Europe/London')
In [481]: pd.Timestamp(
.....: year=2019,
.....: month=10,
.....: day=27,
.....: hour=1,
.....: minute=30,
.....: tz="dateutil/Europe/London",
.....: fold=1,
.....: )
.....:
Out[481]: Timestamp('2019-10-27 01:30:00+0000', tz='dateutil//usr/share/zoneinfo/Europe/London')
本地化时的有歧义时间#
tz_localize
可能无法确定时间戳的 UTC 偏移量,因为本地时区的夏令时 (DST) 导致某些时间一天内出现两次(“时钟回拨”)。以下选项可用:
'raise'
:抛出pytz.AmbiguousTimeError
(默认行为)'infer'
:尝试根据时间戳的单调性确定正确的偏移量'NaT'
:将有歧义的时间替换为NaT
bool
:True
表示 DST 时间,False
表示非 DST 时间。对于时间序列,支持布尔值数组。
In [482]: rng_hourly = pd.DatetimeIndex(
.....: ["11/06/2011 00:00", "11/06/2011 01:00", "11/06/2011 01:00", "11/06/2011 02:00"]
.....: )
.....:
这将会失败,因为存在有歧义的时间('11/06/2011 01:00'
)。
In [483]: rng_hourly.tz_localize('US/Eastern')
---------------------------------------------------------------------------
AmbiguousTimeError Traceback (most recent call last)
Cell In[483], line 1
----> 1 rng_hourly.tz_localize('US/Eastern')
File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:293, in DatetimeIndex.tz_localize(self, tz, ambiguous, nonexistent)
286 @doc(DatetimeArray.tz_localize)
287 def tz_localize(
288 self,
(...)
291 nonexistent: TimeNonexistent = "raise",
292 ) -> Self:
--> 293 arr = self._data.tz_localize(tz, ambiguous, nonexistent)
294 return type(self)._simple_new(arr, name=self.name)
File ~/work/pandas/pandas/pandas/core/arrays/_mixins.py:81, in ravel_compat.<locals>.method(self, *args, **kwargs)
78 @wraps(meth)
79 def method(self, *args, **kwargs):
80 if self.ndim == 1:
---> 81 return meth(self, *args, **kwargs)
83 flags = self._ndarray.flags
84 flat = self.ravel("K")
File ~/work/pandas/pandas/pandas/core/arrays/datetimes.py:1088, in DatetimeArray.tz_localize(self, tz, ambiguous, nonexistent)
1085 tz = timezones.maybe_get_tz(tz)
1086 # Convert to UTC
-> 1088 new_dates = tzconversion.tz_localize_to_utc(
1089 self.asi8,
1090 tz,
1091 ambiguous=ambiguous,
1092 nonexistent=nonexistent,
1093 creso=self._creso,
1094 )
1095 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
1096 dtype = tz_to_dtype(tz, unit=self.unit)
File tzconversion.pyx:371, in pandas._libs.tslibs.tzconversion.tz_localize_to_utc()
AmbiguousTimeError: Cannot infer dst time from 2011-11-06 01:00:00, try using the 'ambiguous' argument
通过指定以下选项来处理这些有歧义的时间。
In [484]: rng_hourly.tz_localize("US/Eastern", ambiguous="infer")
Out[484]:
DatetimeIndex(['2011-11-06 00:00:00-04:00', '2011-11-06 01:00:00-04:00',
'2011-11-06 01:00:00-05:00', '2011-11-06 02:00:00-05:00'],
dtype='datetime64[ns, US/Eastern]', freq=None)
In [485]: rng_hourly.tz_localize("US/Eastern", ambiguous="NaT")
Out[485]:
DatetimeIndex(['2011-11-06 00:00:00-04:00', 'NaT', 'NaT',
'2011-11-06 02:00:00-05:00'],
dtype='datetime64[ns, US/Eastern]', freq=None)
In [486]: rng_hourly.tz_localize("US/Eastern", ambiguous=[True, True, False, False])
Out[486]:
DatetimeIndex(['2011-11-06 00:00:00-04:00', '2011-11-06 01:00:00-04:00',
'2011-11-06 01:00:00-05:00', '2011-11-06 02:00:00-05:00'],
dtype='datetime64[ns, US/Eastern]', freq=None)
本地化时不存在的时间#
夏令时 (DST) 过渡也可能将本地时间提前 1 小时,从而产生不存在的本地时间(“时钟向前拨”)。通过 nonexistent
参数可以控制本地化具有不存在时间的时间序列的行为。以下选项可用:
'raise'
:抛出pytz.NonExistentTimeError
(默认行为)'NaT'
:将不存在的时间替换为NaT
'shift_forward'
:将不存在的时间向前移动到最接近的真实时间'shift_backward'
:将不存在的时间向后移动到最接近的真实时间timedelta 对象:按 timedelta 持续时间移动不存在的时间
In [487]: dti = pd.date_range(start="2015-03-29 02:30:00", periods=3, freq="h")
# 2:30 is a nonexistent time
默认情况下,本地化不存在的时间会引发错误。
In [488]: dti.tz_localize('Europe/Warsaw')
---------------------------------------------------------------------------
NonExistentTimeError Traceback (most recent call last)
Cell In[488], line 1
----> 1 dti.tz_localize('Europe/Warsaw')
File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:293, in DatetimeIndex.tz_localize(self, tz, ambiguous, nonexistent)
286 @doc(DatetimeArray.tz_localize)
287 def tz_localize(
288 self,
(...)
291 nonexistent: TimeNonexistent = "raise",
292 ) -> Self:
--> 293 arr = self._data.tz_localize(tz, ambiguous, nonexistent)
294 return type(self)._simple_new(arr, name=self.name)
File ~/work/pandas/pandas/pandas/core/arrays/_mixins.py:81, in ravel_compat.<locals>.method(self, *args, **kwargs)
78 @wraps(meth)
79 def method(self, *args, **kwargs):
80 if self.ndim == 1:
---> 81 return meth(self, *args, **kwargs)
83 flags = self._ndarray.flags
84 flat = self.ravel("K")
File ~/work/pandas/pandas/pandas/core/arrays/datetimes.py:1088, in DatetimeArray.tz_localize(self, tz, ambiguous, nonexistent)
1085 tz = timezones.maybe_get_tz(tz)
1086 # Convert to UTC
-> 1088 new_dates = tzconversion.tz_localize_to_utc(
1089 self.asi8,
1090 tz,
1091 ambiguous=ambiguous,
1092 nonexistent=nonexistent,
1093 creso=self._creso,
1094 )
1095 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
1096 dtype = tz_to_dtype(tz, unit=self.unit)
File tzconversion.pyx:431, in pandas._libs.tslibs.tzconversion.tz_localize_to_utc()
NonExistentTimeError: 2015-03-29 02:30:00
将不存在的时间转换为 NaT
或移动时间。
In [489]: dti
Out[489]:
DatetimeIndex(['2015-03-29 02:30:00', '2015-03-29 03:30:00',
'2015-03-29 04:30:00'],
dtype='datetime64[ns]', freq='h')
In [490]: dti.tz_localize("Europe/Warsaw", nonexistent="shift_forward")
Out[490]:
DatetimeIndex(['2015-03-29 03:00:00+02:00', '2015-03-29 03:30:00+02:00',
'2015-03-29 04:30:00+02:00'],
dtype='datetime64[ns, Europe/Warsaw]', freq=None)
In [491]: dti.tz_localize("Europe/Warsaw", nonexistent="shift_backward")
Out[491]:
DatetimeIndex(['2015-03-29 01:59:59.999999999+01:00',
'2015-03-29 03:30:00+02:00',
'2015-03-29 04:30:00+02:00'],
dtype='datetime64[ns, Europe/Warsaw]', freq=None)
In [492]: dti.tz_localize("Europe/Warsaw", nonexistent=pd.Timedelta(1, unit="h"))
Out[492]:
DatetimeIndex(['2015-03-29 03:30:00+02:00', '2015-03-29 03:30:00+02:00',
'2015-03-29 04:30:00+02:00'],
dtype='datetime64[ns, Europe/Warsaw]', freq=None)
In [493]: dti.tz_localize("Europe/Warsaw", nonexistent="NaT")
Out[493]:
DatetimeIndex(['NaT', '2015-03-29 03:30:00+02:00',
'2015-03-29 04:30:00+02:00'],
dtype='datetime64[ns, Europe/Warsaw]', freq=None)
时区 Series 操作#
具有时区朴素值(naive values)的 Series
表示为 dtype datetime64[ns]
。
In [494]: s_naive = pd.Series(pd.date_range("20130101", periods=3))
In [495]: s_naive
Out[495]:
0 2013-01-01
1 2013-01-02
2 2013-01-03
dtype: datetime64[ns]
具有时区感知值(aware values)的 Series
表示为 dtype datetime64[ns, tz]
,其中 tz
是时区。
In [496]: s_aware = pd.Series(pd.date_range("20130101", periods=3, tz="US/Eastern"))
In [497]: s_aware
Out[497]:
0 2013-01-01 00:00:00-05:00
1 2013-01-02 00:00:00-05:00
2 2013-01-03 00:00:00-05:00
dtype: datetime64[ns, US/Eastern]
这两种 Series
的时区信息都可以通过 .dt
访问器进行操作,请参阅dt 访问器部分。
例如,将朴素时间戳本地化并转换为时区感知。
In [498]: s_naive.dt.tz_localize("UTC").dt.tz_convert("US/Eastern")
Out[498]:
0 2012-12-31 19:00:00-05:00
1 2013-01-01 19:00:00-05:00
2 2013-01-02 19:00:00-05:00
dtype: datetime64[ns, US/Eastern]
时区信息也可以使用 astype
方法进行操作。此方法可以在不同的时区感知 dtype 之间进行转换。
# convert to a new time zone
In [499]: s_aware.astype("datetime64[ns, CET]")
Out[499]:
0 2013-01-01 06:00:00+01:00
1 2013-01-02 06:00:00+01:00
2 2013-01-03 06:00:00+01:00
dtype: datetime64[ns, CET]
注意
对 Series
使用 Series.to_numpy()
返回数据的 NumPy 数组。NumPy 目前不支持时区(尽管它以本地时区进行*打印*!),因此对于时区感知数据,返回的是 Timestamps 的对象数组。
In [500]: s_naive.to_numpy()
Out[500]:
array(['2013-01-01T00:00:00.000000000', '2013-01-02T00:00:00.000000000',
'2013-01-03T00:00:00.000000000'], dtype='datetime64[ns]')
In [501]: s_aware.to_numpy()
Out[501]:
array([Timestamp('2013-01-01 00:00:00-0500', tz='US/Eastern'),
Timestamp('2013-01-02 00:00:00-0500', tz='US/Eastern'),
Timestamp('2013-01-03 00:00:00-0500', tz='US/Eastern')],
dtype=object)
通过转换为 Timestamps 的对象数组,可以保留时区信息。例如,当转换回 Series 时:
In [502]: pd.Series(s_aware.to_numpy())
Out[502]:
0 2013-01-01 00:00:00-05:00
1 2013-01-02 00:00:00-05:00
2 2013-01-03 00:00:00-05:00
dtype: datetime64[ns, US/Eastern]
然而,如果您想要一个实际的 NumPy datetime64[ns]
数组(值已转换为 UTC),而不是对象数组,您可以指定 dtype
参数。
In [503]: s_aware.to_numpy(dtype="datetime64[ns]")
Out[503]:
array(['2013-01-01T05:00:00.000000000', '2013-01-02T05:00:00.000000000',
'2013-01-03T05:00:00.000000000'], dtype='datetime64[ns]')