时间序列/日期功能#
pandas 包含广泛的功能和特性,用于处理所有领域的时序数据。使用 NumPy 的 datetime64
和 timedelta64
数据类型,pandas 整合了来自其他 Python 库(如 scikits.timeseries
)的大量功能,并创建了大量用于操作时序数据的新功能。
例如,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 个与时间相关的通用概念
日期时间:具有时区支持的特定日期和时间。类似于标准库中的
datetime.datetime
。时间增量:绝对时间持续时间。类似于标准库中的
datetime.timedelta
。时间跨度:由时间点及其关联频率定义的时间跨度。
日期偏移量:尊重日历运算的相对时间持续时间。类似于
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
参数来确保特定的解析。这也有可能显著加快转换速度。
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 datetime 文档。
从多个 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
参数相同的字符串 above)。可用的单位在 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')
我们减去纪元(1970 年 1 月 1 日午夜 UTC),然后用“单位”(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 years
。不同的分辨率可以通过 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 的单个字符串索引 DataFrame
行(例如 frame[dtstring]
)已弃用(鉴于它是否索引行或选择列的歧义),并且将在未来版本中删除。使用 .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]
DatetimeIndex
部分字符串索引也适用于具有 MultiIndex
的 DataFrame
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
如果时间戳字符串被视为切片,则可以使用它来索引 DataFrame
的 .loc[]
。
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 月至 3 月 = 1,4 月至 6 月 = 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 |
逻辑值,指示日期是否属于闰年。 |
此外,如果您有一个包含类似日期时间值的 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
,它表示一段持续时间,但遵循特定的日历持续时间规则。例如,一个 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 号(或其他 day_of_month)和日历月末 |
|
|
15 号(或其他 day_of_month)和日历月初 |
|
|
日历季度末 |
|
|
日历季度初 |
|
|
工作日季度末 |
|
|
工作日季度初 |
|
|
零售(又称 52-53 周)季度 |
|
|
日历年末 |
|
|
日历年初 |
|
|
工作日年末 |
|
|
工作日年初 |
|
|
零售(又称 52-53 周)年 |
|
无 |
复活节假期 |
|
|
工作日小时 |
|
|
自定义工作日小时 |
|
|
一天 |
|
|
一小时 |
|
|
一分钟 |
|
|
一秒 |
|
|
1 毫秒 |
|
|
1 微秒 |
|
|
1 纳秒 |
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')
如果 start
时间晚于 end
时间,则表示跨越午夜的营业时间。在这种情况下,营业时间会跨越午夜并延续到第二天。有效的营业时间取决于其是否从有效的 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
定义的间隔内的 timestamps。如果start_date
不符合频率,则返回的 timestamps 将从下一个有效的 timestamp 开始,对于end_date
也是如此,返回的 timestamps 将在之前的有效 timestamp 处停止。
例如,对于偏移 MS
,如果 start_date
不是月份的第一天,则返回的 timestamps 将从下一月的第一天开始。如果 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
之间的有效 timestamps。如果这些不是给定频率的有效 timestamps,它将滚动到 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/string 来覆盖。
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 0x7fac095fd2d0>),
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 0x7fac095fd2d0>),
Holiday: Columbus Day (month=10, day=1, offset=<DateOffset: weekday=MO(+2)>)]
重采样#
pandas 具有一个简单、强大且高效的功能,用于在频率转换期间执行重采样操作(例如,将每秒数据转换为每 5 分钟数据)。这在金融应用中非常常见,但并不限于此。
resample()
是一个基于时间的 groupby,然后对每个组进行缩减方法。请参阅一些 食谱示例,了解一些高级策略。
可以使用 resample()
方法直接从 DataFrameGroupBy
对象中调用,请参阅 分组文档。
基础#
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 可用的任何内置方法都可作为返回对象的 method,包括 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
警告
label
和 closed
的默认值为 'left',适用于除 'ME'、'YE'、'QE'、'BME'、'BYE'、'BQE' 和 'W' 之外的所有频率偏移量,这些偏移量的默认值为 '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
请注意,星期日的 value 如何被拉回到前一个星期五。要获得将星期日的 value 推到星期一的行为,请改用
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’,将结果索引转换为时间戳和时间跨度表示。默认情况下,resample
保留输入表示。
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
实例。类似于 聚合 API、分组 API 和 窗口 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
我们可以使用标准的取项操作来选择特定的列或列组。
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
没有日期时间类型的索引,而是希望根据框架中的日期时间类型的列进行重采样,则可以将其传递给 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
的日期时间类型级别进行重采样,则可以将其名称或位置传递给 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
来调整 bin 的起点#
分组的 bin 是根据时间序列起点日期的开始进行调整的。这适用于以天为单位的频率(如 30D
)或均匀划分一天的频率(如 90s
或 1min
)。对于不符合此条件的某些频率,这可能会导致不一致。要更改此行为,您可以使用参数 origin
指定一个固定的时间戳。
例如
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
在这里我们可以看到,当使用 origin
及其默认值 ('start_day'
) 时,'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
时间增量来调整 bin,该时间增量将添加到默认的 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
请注意在最后一个示例中使用 'start'
来表示 origin
。在这种情况下,origin
将被设置为时间序列的第一个值。
反向重采样#
版本 1.3.0 中的新增功能。
有时我们需要固定 bin 的末尾来进行反向重采样,而不是调整 bin 的开头,以获得给定的 freq
。反向重采样默认将 closed
设置为 'right'
,因为最后一个值应该被视为最后一个 bin 的边缘点。
我们可以将 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
。这将把起点设置为最大 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
作为最后一个 bin 的右边缘,因为以下计算。
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
代表一段时间(例如,一天、一个月、一个季度等)。您可以通过 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')
从周期中添加和减去整数会根据其自身的频率移动周期。不允许在具有不同 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
,请参考 Period 数据类型。
Period 数据类型#
PeriodIndex
有一个自定义的 period
数据类型。这是一种 pandas 扩展数据类型,类似于 带时区的 dtype (datetime64[ns, tz]
)。
period
数据类型包含 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
数据类型可以在 .astype(...)
中使用。它允许更改 PeriodIndex
的 freq
,例如 .asfreq()
,并将 DatetimeIndex
转换为 PeriodIndex
,例如 to_period()
。
# 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 现在支持使用非单调索引进行部分字符串切片。
您可以像使用 DatetimeIndex
一样,将日期和字符串传递给带有 PeriodIndex
的 Series
和 DataFrame
。有关详细信息,请参考 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 进行频率转换和重采样#
可以通过 asfreq
方法转换 Period
和 PeriodIndex
的频率。让我们从以 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')
转换为“超周期”(例如,年度频率是季度频率的超周期)会自动返回包含输入周期的超周期
In [406]: p = pd.Period("2011-12", freq="M")
In [407]: p.asfreq("Y-NOV")
Out[407]: Period('2012', 'Y-NOV')
请注意,由于我们转换为以 11 月结束的年度频率,因此 2011 年 12 月的月度周期实际上位于 2012 年 Y-NOV 周期中。
使用锚定频率进行周期转换对于处理经济学、商业和其他领域中常见的各种季度数据特别有用。许多组织根据其财年开始和结束的月份来定义季度。因此,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 的数据,反之亦然,可以使用 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' 可用于返回周期开始或结束时的时间戳
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
在周期和时间戳之间转换使一些方便的算术函数可以使用。在以下示例中,我们将以 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 限制,然后您可以使用PeriodIndex
和/或Series
的Periods
来进行计算。
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
对象来处理不同时区中的时间戳。
使用时区#
默认情况下,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
可以保存一系列 Timestamp
对象,这些对象可能具有不同的 UTC 偏移量,并且不能用一个 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
方法对日期时间进行本地化。
警告
请注意,对于未来的时间,任何时区库都无法保证时区(和 UTC)之间的正确转换,因为时区相对于 UTC 的偏移量可能会被相应的政府更改。
警告
如果您使用的是 2038-01-18 之后的日期,由于底层库中存在由 2038 年问题引起的当前缺陷,将不会对时区感知日期应用夏令时 (DST) 调整。如果底层库得到修复,将应用 DST 转换。
例如,对于两个位于英国夏令时(因此通常为 GMT+1)的日期,以下两个断言都将评估为真
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)
折叠#
对于模棱两可的时间,pandas 支持明确指定关键字参数 fold。由于夏令时,在从夏季时间切换到冬季时间时,一个时钟时间可能会出现两次;fold 描述了类似日期时间的对象对应于时钟第一次(0)或第二次(1)到达模棱两可的时间。fold 仅在从朴素的 datetime.datetime
(有关详细信息,请参阅 datetime 文档)或从 Timestamp
或从组件(见下文)构造时受支持。仅支持 dateutil
时区(有关处理模棱两可日期时间的 dateutil
方法,请参阅 dateutil 文档),因为 pytz
时区不支持 fold(有关 pytz
如何处理模棱两可日期时间的详细信息,请参阅 pytz 文档)。要使用 pytz
对模棱两可的日期时间进行本地化,请使用 Timestamp.tz_localize()
。一般来说,如果需要直接控制如何处理模棱两可的日期时间,我们建议使用 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
代表夏令时,False
代表非夏令时。支持bool
值的数组,用于表示一系列时间。
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
以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]
具有时区**感知**值的Series
以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
方法操作时区信息。此方法可以在不同的时区感知数据类型之间进行转换。
# 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 目前不支持时区(即使它正在以本地时区打印!),因此对于时区感知数据,会返回 Timestamp 对象数组。
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)
通过转换为 Timestamp 对象数组,它保留了时区信息。例如,当转换回 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]')