时间序列 / 日期功能#

pandas 包含广泛的功能和特性,用于处理所有领域的时序数据。使用 NumPy 的 datetime64timedelta64 数据类型,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[us]', 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[us]', 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[us, 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[us, 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 个通用的时间相关概念

  1. 日期时间:具有时区支持的特定日期和时间。类似于标准库中的 datetime.datetime

  2. 时间差:一个绝对时间持续。类似于标准库中的 datetime.timedelta

  3. 时间跨度:由一个时间点及其关联频率定义的时间跨度。

  4. 日期偏移:一个尊重日历算术的相对时间持续。类似于 dateutil 包中的 dateutil.relativedelta.relativedelta

概念

标量类

数组类

pandas 数据类型

主要创建方法

日期时间

Timestamp

DatetimeIndex

datetime64[ns]datetime64[ns, tz]

to_datetimedate_range

时间差

Timedelta

TimedeltaIndex

timedelta64[ns]

to_timedeltatimedelta_range

时间跨度

Period

PeriodIndex

period[freq]

Periodperiod_range

日期偏移量

DateOffset

None

None

DateOffset

对于时序数据,通常将时间分量表示为 SeriesDataFrame 的索引,以便可以相对于时间元素进行操作。

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

然而,SeriesDataFrame 也可以直接支持将时间分量作为数据本身。

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[us]

SeriesDataFrame 对传递给这些构造函数的 datetimetimedeltaPeriod 数据具有扩展的数据类型支持和功能。然而,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[us]

最后,pandas 将 null 日期时间、时间差和时间跨度表示为 NaT,这对于表示缺失或 null 的日期类值非常有用,并且行为类似于 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

时间戳 vs. 时间跨度#

带时间戳的数据是最基本的时间序列数据类型,它将值与时间点关联起来。对于 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')

TimestampPeriod 可以作为索引。 TimestampPeriod 的列表会自动强制转换为 DatetimeIndexPeriodIndex

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.DatetimeIndex

In [37]: ts.index
Out[37]: DatetimeIndex(['2012-05-01', '2012-05-02', '2012-05-03'], dtype='datetime64[us]', 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.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[us]

In [45]: pd.to_datetime(["2005/11/23", "2010/12/31"])
Out[45]: DatetimeIndex(['2005-11-23', '2010-12-31'], dtype='datetime64[us]', 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[us]', 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[us]', freq=None)

警告

从上面的示例中可以看到 dayfirst 并不是严格的。如果日期无法以日优先的方式解析,它将被解析为 dayfirstFalse,并且还会发出警告。

如果您将单个字符串传递给 to_datetime,它将返回单个 TimestampTimestamp 也可以接受字符串输入,但它不接受 dayfirstformat 等字符串解析选项,因此如果需要这些选项,请使用 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[us]', 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[us]', freq='2D')

在大多数情况下,将字符串解析为日期时间(使用 to_datetime()DatetimeIndexTimestamp 中的任何一个)将生成具有微秒(“us”)单位的对象。此规则的例外是,如果您的字符串具有纳秒精度,在这种情况下,结果将具有“ns”单位。

In [52]: pd.to_datetime(["2016-01-01 02:03:04"]).unit
Out[52]: 'us'

In [53]: pd.to_datetime(["2016-01-01 02:03:04.123"]).unit
Out[53]: 'us'

In [54]: pd.to_datetime(["2016-01-01 02:03:04.123456"]).unit
Out[54]: 'us'

In [55]: pd.to_datetime(["2016-01-01 02:03:04.123456789"]).unit
Out[55]: 'ns'

在 3.0.0 版中更改:以前,to_datetime()DatetimeIndex 总是将字符串解析为“ns”单位。在 pandas 2.x 版本中,Timestamp 可以根据输入字符串的特异性生成“s”、“ms”、“us”或“ns”中的任何一个。

提供 format 参数#

除了必需的日期时间字符串外,还可以传递 format 参数以确保特定的解析。这也可以大大加快转换速度。

In [56]: pd.to_datetime("2010/11/12", format="%Y/%m/%d")
Out[56]: Timestamp('2010-11-12 00:00:00')

In [57]: pd.to_datetime("12-11-2010 00:00", format="%d-%m-%Y %H:%M")
Out[57]: Timestamp('2010-11-12 00:00:00')

有关指定 format 选项时可用的选择的更多信息,请参阅 Python 的 datetime 文档

从多个 DataFrame 列组装日期时间#

您还可以传递一个整数或字符串列的 DataFrame 来组装成 TimestampSeries

In [58]: df = pd.DataFrame(
   ....:     {"year": [2015, 2016], "month": [2, 3], "day": [4, 5], "hour": [2, 3]}
   ....: )
   ....: 

In [59]: pd.to_datetime(df)
Out[59]: 
0   2015-02-04 02:00:00
1   2016-03-05 03:00:00
dtype: datetime64[us]

您可以只传递您需要的列进行组装。

In [60]: pd.to_datetime(df[["year", "month", "day"]])
Out[60]: 
0   2015-02-04
1   2016-03-05
dtype: datetime64[us]

pd.to_datetime 查找列名中日期时间组件的标准标识,包括

  • 必需:yearmonthday

  • 可选:hourminutesecondmillisecondmicrosecondnanosecond

无效数据#

默认行为 errors='raise',当无法解析时会引发异常。

In [61]: pd.to_datetime(['2009/07/31', 'asd'], errors='raise')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[61], line 1
----> 1 pd.to_datetime(['2009/07/31', 'asd'], errors='raise')

File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:1072, in to_datetime(arg, errors, dayfirst, yearfirst, utc, format, exact, unit, origin, cache)
   1070         result = _convert_and_box_cache(argc, cache_array)
   1071     else:
-> 1072         result = convert_listlike(argc, format)
   1073 else:
   1074     result = convert_listlike(np.array([arg]), format)[0]

File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:435, in _convert_listlike_datetimes(arg, format, name, utc, unit, errors, dayfirst, yearfirst, exact)
    433 # `format` could be inferred, or user didn't ask for mixed-format parsing.
    434 if format is not None and format != "mixed":
--> 435     return _array_strptime_with_fallback(arg, name, utc, format, exact, errors)
    437 result, tz_parsed = objects_to_datetime64(
    438     arg,
    439     dayfirst=dayfirst,
   (...)    443     allow_object=True,
    444 )
    446 if tz_parsed is not None:
    447     # We can take a shortcut since the datetime64 numpy array
    448     # is in UTC

File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:470, in _array_strptime_with_fallback(arg, name, utc, fmt, exact, errors)
    459 def _array_strptime_with_fallback(
    460     arg,
    461     name,
   (...)    465     errors: str,
    466 ) -> Index:
    467     """
    468     Call array_strptime, with fallback behavior depending on 'errors'.
    469     """
--> 470     result, tz_out = array_strptime(arg, fmt, exact=exact, errors=errors, utc=utc)
    471     if tz_out is not None:
    472         unit = np.datetime_data(result.dtype)[0]

File ~/work/pandas/pandas/pandas/_libs/tslibs/strptime.pyx:563, in pandas._libs.tslibs.strptime.array_strptime()

File ~/work/pandas/pandas/pandas/_libs/tslibs/strptime.pyx:511, in pandas._libs.tslibs.strptime.array_strptime()

File ~/work/pandas/pandas/pandas/_libs/tslibs/strptime.pyx:617, in pandas._libs.tslibs.strptime._parse_with_format()

ValueError: time data "asd" doesn't match format "%Y/%m/%d". 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 [62]: pd.to_datetime(["2009/07/31", "asd"], errors="coerce")
Out[62]: DatetimeIndex(['2009-07-31', 'NaT'], dtype='datetime64[us]', freq=None)

时间纪元戳#

pandas 支持将整数或浮点数时间纪元转换为 TimestampDatetimeIndex。默认单位是纳秒,因为 Timestamp 对象在内部就是这样存储的。但是,时间纪元通常以其他 unit 存储,可以指定。这些是从 origin 参数指定的起点计算的。

In [63]: pd.to_datetime(
   ....:     [1349720105, 1349806505, 1349892905, 1349979305, 1350065705], unit="s"
   ....: )
   ....: 
Out[63]: 
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[s]', freq=None)

In [64]: pd.to_datetime(
   ....:     [1349720105100, 1349720105200, 1349720105300, 1349720105400, 1349720105500],
   ....:     unit="ms",
   ....: )
   ....: 
Out[64]: 
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[ms]', freq=None)

注意

unit 参数不使用与前面讨论的 format 参数相同的字符串。可用单位在 pandas.to_datetime() 的文档中列出。

使用 tz 参数构造具有时间纪元戳的 TimestampDatetimeIndex 将引发 ValueError。如果您在其他时区的墙上时间有时间纪元,您可以将时间纪元读取为无时区的带时时间戳,然后本地化到相应的时区。

In [65]: pd.Timestamp(1262347200000000000).tz_localize("US/Pacific")
Out[65]: Timestamp('2010-01-01 12:00:00-0800', tz='US/Pacific')

In [66]: pd.DatetimeIndex([1262347200000000000]).tz_localize("US/Pacific")
Out[66]: DatetimeIndex(['2010-01-01 12:00:00-08:00'], dtype='datetime64[ns, US/Pacific]', freq=None)

注意

时间纪元时间将被四舍五入到最近的纳秒。

警告

浮点数时间纪元的转换可能导致不准确和意外的结果。Python 浮点数 在十进制中约有 15 位精度。从浮点数到高精度 Timestamp 的转换过程中舍入是不可避免的。要实现精确精度,唯一的方法是使用固定宽度类型(例如 int64)。

In [67]: pd.to_datetime([1490195805.433, 1490195805.433502912], unit="s")
Out[67]: DatetimeIndex(['2017-03-22 15:16:45.433000088', '2017-03-22 15:16:45.433502913'], dtype='datetime64[ns]', freq=None)

In [68]: pd.to_datetime(1490195805433502912, unit="ns")
Out[68]: Timestamp('2017-03-22 15:16:45.433502912')

另请参阅

使用 origin 参数

从时间戳到时间纪元#

要反转上述操作,即从 Timestamp 转换为“unix”时间纪元。

In [69]: stamps = pd.date_range("2012-10-08 18:15:05", periods=4, freq="D")

In [70]: stamps
Out[70]: 
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[us]', freq='D')

我们减去时间纪元(UTC 1970 年 1 月 1 日午夜),然后向下取整除以“单位”(1 秒)。

In [71]: (stamps - pd.Timestamp("1970-01-01")) // pd.Timedelta("1s")
Out[71]: Index([1349720105, 1349806505, 1349892905, 1349979305], dtype='int64')

另一种执行此转换的常用方法是直接转换为整数数据类型。请注意,由此产生的精确整数将取决于 datetime64 数据类型的特定单位或分辨率。

In [72]: stamps.astype(np.int64)
Out[72]: Index([1349720105000000, 1349806505000000, 1349892905000000, 1349979305000000], dtype='int64')

In [73]: stamps.as_unit("s").astype(np.int64)
Out[73]: Index([1349720105, 1349806505, 1349892905, 1349979305], dtype='int64')

In [74]: stamps.as_unit("ns").astype(np.int64)
Out[74]: 
Index([1349720105000000000, 1349806505000000000, 1349892905000000000,
       1349979305000000000],
      dtype='int64')

使用 origin 参数#

使用 origin 参数,可以为创建 DatetimeIndex 指定一个替代的起始点。例如,使用 1960-01-01 作为起始日期。

In [75]: pd.to_datetime([1, 2, 3], unit="D", origin=pd.Timestamp("1960-01-01"))
Out[75]: DatetimeIndex(['1960-01-02', '1960-01-03', '1960-01-04'], dtype='datetime64[s]', freq=None)

默认值为 origin='unix',默认为 1970-01-01 00:00:00。通常称为“unix epoch”或 POSIX time。

In [76]: pd.to_datetime([1, 2, 3], unit="D")
Out[76]: DatetimeIndex(['1970-01-02', '1970-01-03', '1970-01-04'], dtype='datetime64[s]', freq=None)

生成时间戳范围#

要生成具有时间戳的索引,您可以使用 DatetimeIndexIndex 构造函数,并传递日期时间对象的列表。

In [77]: dates = [
   ....:     datetime.datetime(2012, 5, 1),
   ....:     datetime.datetime(2012, 5, 2),
   ....:     datetime.datetime(2012, 5, 3),
   ....: ]
   ....: 

# Note the frequency information
In [78]: index = pd.DatetimeIndex(dates)

In [79]: index
Out[79]: DatetimeIndex(['2012-05-01', '2012-05-02', '2012-05-03'], dtype='datetime64[us]', freq=None)

# Automatically converted to DatetimeIndex
In [80]: index = pd.Index(dates)

In [81]: index
Out[81]: DatetimeIndex(['2012-05-01', '2012-05-02', '2012-05-03'], dtype='datetime64[us]', freq=None)

实际上,这会变得非常麻烦,因为我们通常需要一个具有大量时间戳的长索引。如果我们只需要固定频率的时间戳,我们可以使用 date_range()bdate_range() 函数来创建 DatetimeIndexdate_range 的默认频率是**日历日**,而 bdate_range 的默认频率是**工作日**。

In [82]: start = datetime.datetime(2011, 1, 1)

In [83]: end = datetime.datetime(2012, 1, 1)

In [84]: index = pd.date_range(start, end)

In [85]: index
Out[85]: 
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[us]', length=366, freq='D')

In [86]: index = pd.bdate_range(start, end)

In [87]: index
Out[87]: 
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[us]', length=260, freq='B')

诸如 date_rangebdate_range 等便捷函数可以使用各种 频率别名

In [88]: pd.date_range(start, periods=1000, freq="ME")
Out[88]: 
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[us]', length=1000, freq='ME')

In [89]: pd.bdate_range(start, periods=250, freq="BQS")
Out[89]: 
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[us]', length=250, freq='BQS-JAN')

date_rangebdate_range 可以使用 startendperiodsfreq 等各种参数组合轻松生成日期范围。开始和结束日期严格包含在内,因此不会生成超出指定范围的日期。

In [90]: pd.date_range(start, end, freq="BME")
Out[90]: 
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[us]', freq='BME')

In [91]: pd.date_range(start, end, freq="W")
Out[91]: 
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[us]', freq='W-SUN')

In [92]: pd.bdate_range(end=end, periods=20)
Out[92]: 
DatetimeIndex(['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[us]', freq='B')

In [93]: pd.bdate_range(start=start, periods=20)
Out[93]: 
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[us]', freq='B')

指定 startendperiods 将生成一个从 startend(含)的均匀分布的日期范围,结果 DatetimeIndex 中包含 periods 个元素。

In [94]: pd.date_range("2018-01-01", "2018-01-05", periods=5)
Out[94]: 
DatetimeIndex(['2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04',
               '2018-01-05'],
              dtype='datetime64[us]', freq=None)

In [95]: pd.date_range("2018-01-01", "2018-01-05", periods=10)
Out[95]: 
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[us]', freq=None)

自定义频率范围#

bdate_range 还可以通过使用 weekmaskholidays 参数生成自定义频率日期的范围。仅当传递自定义频率字符串时才会使用这些参数。

In [96]: weekmask = "Mon Wed Fri"

In [97]: holidays = [datetime.datetime(2011, 1, 5), datetime.datetime(2011, 3, 14)]

In [98]: pd.bdate_range(start, end, freq="C", weekmask=weekmask, holidays=holidays)
Out[98]: 
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[us]', length=154, freq='C')

In [99]: pd.bdate_range(start, end, freq="CBMS", weekmask=weekmask)
Out[99]: 
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[us]', freq='CBMS')

另请参阅

自定义工作日

时间戳限制#

时间戳表示的限制取决于所选的分辨率。对于纳秒分辨率,使用 64 位整数表示的时间跨度限制约为 584 年。

In [100]: pd.Timestamp.min
Out[100]: Timestamp('1677-09-21 00:12:43.145224193')

In [101]: pd.Timestamp.max
Out[101]: Timestamp('2262-04-11 23:47:16.854775807')

选择秒分辨率时,可用范围增加到 +/- 2.9e11 。可以通过 as_unit 将不同分辨率相互转换。

索引#

DatetimeIndex 的主要用途之一是作为 pandas 对象的索引。DatetimeIndex 类包含许多与时间序列相关的优化。

  • 预先计算并缓存了各种偏移量的广泛日期范围,以便使生成后续日期范围非常快速(只需获取一个切片)。

  • 使用 pandas 对象的 shift 方法进行快速移位。

  • 具有相同频率的 DatetimeIndex 对象的联合(union)操作非常快速(对于快速数据对齐很重要)。

  • 通过 yearmonth 等属性快速访问日期字段。

  • 诸如 snap 的正则化函数和非常快速的 asof 逻辑。

DatetimeIndex 对象具有常规 Index 对象的所有基本功能,以及大量高级时间序列特定方法,可轻松进行频率处理。

另请参阅

重新索引方法

注意

虽然 pandas 不强制要求您拥有排序的日期索引,但如果日期未排序,其中一些方法可能会产生意外或不正确的结果。

DatetimeIndex 可用作常规索引,并提供其所有智能功能,如选择、切片等。

In [102]: rng = pd.date_range(start, end, freq="BME")

In [103]: ts = pd.Series(np.random.randn(len(rng)), index=rng)

In [104]: ts.index
Out[104]: 
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[us]', freq='BME')

In [105]: ts[:5].index
Out[105]: 
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-29',
               '2011-05-31'],
              dtype='datetime64[us]', freq='BME')

In [106]: ts[::2].index
Out[106]: 
DatetimeIndex(['2011-01-31', '2011-03-31', '2011-05-31', '2011-07-29',
               '2011-09-30', '2011-11-30'],
              dtype='datetime64[us]', freq='2BME')

部分字符串索引#

可以作为索引参数传递日期和可解析为时间戳的字符串。

In [107]: ts["1/31/2011"]
Out[107]: np.float64(0.11920871129693428)

In [108]: ts[datetime.datetime(2011, 12, 25):]
Out[108]: 
2011-12-30    0.56702
Freq: BME, dtype: float64

In [109]: ts["10/31/2011":"12/31/2011"]
Out[109]: 
2011-10-31    0.271860
2011-11-30   -0.424972
2011-12-30    0.567020
Freq: BME, dtype: float64

为了方便访问更长的时间序列,您还可以传递年份或年份和月份作为字符串。

In [110]: ts["2011"]
Out[110]: 
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 [111]: ts["2011-6"]
Out[111]: 
2011-06-30    1.071804
Freq: BME, dtype: float64

这种切片方式适用于具有 DatetimeIndexDataFrame。由于部分字符串选择是标签切片的一种形式,因此端点**将**被包含。这包括匹配包含日期的相关时间。

警告

使用 getitem(例如 frame[dtstring])使用*单个*字符串对 DataFrame 进行索引,从 pandas 1.2.0 开始已弃用(因为其歧义性,不确定是索引行还是选择列),并将在未来版本中移除。使用 .loc(例如 frame.loc[dtstring])的等效方法仍然受支持。

In [112]: dft = pd.DataFrame(
   .....:     np.random.randn(100000, 1),
   .....:     columns=["A"],
   .....:     index=pd.date_range("20130101", periods=100000, freq="min"),
   .....: )
   .....: 

In [113]: dft
Out[113]: 
                            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 [114]: dft.loc["2013"]
Out[114]: 
                            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 [115]: dft["2013-1":"2013-2"]
Out[115]: 
                            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 [116]: dft["2013-1":"2013-2-28"]
Out[116]: 
                            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 [117]: dft["2013-1":"2013-2-28 00:00:00"]
Out[117]: 
                            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 [118]: dft["2013-1-15":"2013-1-15 12:30:00"]
Out[118]: 
                            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 的部分字符串索引也适用于具有 MultiIndexDataFrame

In [119]: 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 [120]: dft2
Out[120]: 
                              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 [121]: dft2.loc["2013-01-05"]
Out[121]: 
                              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 [122]: idx = pd.IndexSlice

In [123]: dft2 = dft2.swaplevel(0, 1).sort_index()

In [124]: dft2.loc[idx[:, "2013-01-05"], :]
Out[124]: 
                              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 [125]: df = pd.DataFrame([0], index=pd.DatetimeIndex(["2019-01-01"], tz="US/Pacific"))

In [126]: df
Out[126]: 
                           0
2019-01-01 00:00:00-08:00  0

In [127]: df["2019-01-01 12:00:00+04:00":"2019-01-01 13:00:00+04:00"]
Out[127]: 
                           0
2019-01-01 00:00:00-08:00  0

切片 vs. 精确匹配#

作为索引参数使用的相同字符串,可以被视为切片或精确匹配,具体取决于索引的分辨率。如果字符串的精度低于索引,它将被视为切片,否则将被视为精确匹配。

考虑一个具有分钟分辨率索引的 Series 对象。

In [128]: 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 [129]: series_minute.index.resolution
Out[129]: 'minute'

精度低于分钟的时间戳字符串将返回一个 Series 对象。

In [130]: series_minute["2011-12-31 23"]
Out[130]: 
2011-12-31 23:59:00    1
dtype: int64

分钟分辨率(或更高精度)的时间戳字符串将返回一个标量,即它不会被强制转换为切片。

In [131]: series_minute["2011-12-31 23:59"]
Out[131]: np.int64(1)

In [132]: series_minute["2011-12-31 23:59:00"]
Out[132]: np.int64(1)

如果索引分辨率是秒,那么分钟精度时间戳将返回一个 Series

In [133]: 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 [134]: series_second.index.resolution
Out[134]: 'second'

In [135]: series_second["2011-12-31 23:59"]
Out[135]: 
2011-12-31 23:59:59    1
dtype: int64

如果时间戳字符串被视为切片,它也可以用于使用 .loc[] 索引 DataFrame

In [136]: dft_minute = pd.DataFrame(
   .....:     {"a": [1, 2, 3], "b": [4, 5, 6]}, index=series_minute.index
   .....: )
   .....: 

In [137]: dft_minute.loc["2011-12-31 23"]
Out[137]: 
                     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 [138]: dft_minute.loc["2011-12-31 23:59"]
Out[138]: 
a    1
b    4
Name: 2011-12-31 23:59:00, dtype: int64

另请注意,DatetimeIndex 的分辨率不能小于日。

In [139]: series_monthly = pd.Series(
   .....:     [1, 2, 3], pd.DatetimeIndex(["2011-12", "2012-01", "2012-02"])
   .....: )
   .....: 

In [140]: series_monthly.index.resolution
Out[140]: 'day'

In [141]: series_monthly["2011-12"]  # returns Series
Out[141]: 
2011-12-01    1
dtype: int64

精确索引#

如前一节所述,使用部分字符串索引 DatetimeIndex 取决于时间段的“准确性”,换句话说,间隔相对于索引分辨率的精确程度。相比之下,使用 Timestampdatetime 对象进行索引是精确的,因为这些对象具有精确的含义。它们也遵循*包含两个端点*的语义。

这些 Timestampdatetime 对象具有精确的 hours, minutes,seconds,即使它们没有被明确指定(它们是 0)。

In [142]: dft[datetime.datetime(2013, 1, 1): datetime.datetime(2013, 2, 28)]
Out[142]: 
                            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 [143]: dft[
   .....:     datetime.datetime(2013, 1, 1, 10, 12, 0): datetime.datetime(
   .....:         2013, 2, 28, 10, 12, 0
   .....:     )
   .....: ]
   .....: 
Out[143]: 
                            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 [144]: rng2 = pd.date_range("2011-01-01", "2012-01-01", freq="W")

In [145]: ts2 = pd.Series(np.random.randn(len(rng2)), index=rng2)

In [146]: ts2.truncate(before="2011-11", after="2011-12")
Out[146]: 
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 [147]: ts2["2011-11":"2011-12"]
Out[147]: 
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 [148]: ts2.iloc[[0, 2, 6]].index
Out[148]: DatetimeIndex(['2011-01-02', '2011-01-16', '2011-02-13'], dtype='datetime64[us]', freq=None)

时间/日期组件#

可以从 Timestamp 或像 DatetimeIndex 这样的时间戳集合中访问几个时间/日期属性。

属性

描述

year

日期的年份。

month

日期的月份。

day

日期的天数。

hour

日期的小时。

minute

日期的分钟。

second

日期的秒。

microsecond

日期的微秒。

nanosecond

日期的纳秒。

date

返回 datetime.date(不包含时区信息)。

time

返回 datetime.time(不包含时区信息)。

timetz

返回带时区信息的本地时间 datetime.time。

一年中的第几天

一年中的序数日。

一年中的第几天

一年中的序数日。

星期几

星期几的数字,周一=0,周日=6。

星期几

星期几的数字,周一=0,周日=6。

weekday

星期几的数字,周一=0,周日=6。

季度

日期的季度: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

逻辑值,指示是否为一年的最后一天(由频率定义)。

是否闰年

逻辑值,指示日期是否属于闰年。

注意

您可以使用 DatetimeIndex.isocalendar().week 来访问一年中的星期信息。

此外,如果您有一个带有日期时间类值的 Series,那么您可以通过 .dt 访问器访问这些属性,详细信息请参阅关于 .dt 访问器 的部分。

您可以从 ISO 8601 标准中获取 ISO 年的年、周和日组件。

In [149]: idx = pd.date_range(start="2019-12-29", freq="D", periods=4)

In [150]: idx.isocalendar()
Out[150]: 
            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 [151]: idx.to_series().dt.isocalendar()
Out[151]: 
            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')被用来指定一个频率,该频率定义了

这些频率字符串映射到 DateOffset 对象及其子类。 DateOffset 类似于 Timedelta,它表示时间间隔但遵循特定的日历时间间隔规则。例如,Timedelta 的一天将始终将 datetimes 增加 24 小时,而 DateOffset 的一天会将 datetimes 增加到第二天的同一时间,无论一天代表 23、24 还是 25 小时(由于夏令时)。但是,所有小于或等于一小时的 DateOffset 子类(HourMinuteSecondMilliMicroNano)都像 Timedelta 一样工作,并尊重绝对时间。

基本的 DateOffset 的作用类似于 dateutil.relativedeltarelativedelta 文档),它将日期时间按指定的相应日历时间间隔进行偏移。算术运算符(+)可用于执行偏移。

# This particular day contains a day light savings time transition
In [152]: ts = pd.Timestamp("2016-10-30 00:00:00", tz="Europe/Helsinki")

# Respects absolute time
In [153]: ts + pd.Timedelta(days=1)
Out[153]: Timestamp('2016-10-30 23:00:00+0200', tz='Europe/Helsinki')

# Respects calendar time
In [154]: ts + pd.DateOffset(days=1)
Out[154]: Timestamp('2016-10-31 00:00:00+0200', tz='Europe/Helsinki')

In [155]: friday = pd.Timestamp("2018-01-05")

In [156]: friday.day_name()
Out[156]: 'Friday'

# Add 2 business days (Friday --> Tuesday)
In [157]: two_business_days = 2 * pd.offsets.BDay()

In [158]: friday + two_business_days
Out[158]: Timestamp('2018-01-09 00:00:00')

In [159]: (friday + two_business_days).day_name()
Out[159]: 'Tuesday'

大多数 DateOffsets 都有关联的频率字符串或偏移别名,可以传递到 freq 关键字参数。可用日期偏移量和关联的频率字符串如下所示。

日期偏移量

频率字符串

描述

DateOffset

None

通用偏移类,默认为绝对 24 小时

BDayBusinessDay

'B'

工作日(周一至周五)

CDayCustomBusinessDay

'C'

自定义工作日

Week

'W'

一周,可选地以一周中的某一天为锚点

WeekOfMonth

'WOM'

每个月的第 y 周的第 x 天

LastWeekOfMonth

'LWOM'

每个月的最后一周的第 x 天

MonthEnd

'ME'

日历月尾

MonthBegin

'MS'

日历月首

BMonthEndBusinessMonthEnd

'BME'

工作日月末

BMonthBeginBusinessMonthBegin

'BMS'

工作日月初

CBMonthEndCustomBusinessMonthEnd

'CBME'

自定义工作日月末

CBMonthBeginCustomBusinessMonthBegin

'CBMS'

自定义工作日月初

SemiMonthEnd

'SME'

每月 15 日(或其他日期)和日历月尾

SemiMonthBegin

'SMS'

每月 15 日(或其他日期)和日历月首

QuarterEnd

'QE'

日历季度末

QuarterBegin

'QS'

日历季度初

BQuarterEnd

'BQE

工作日季度末

BQuarterBegin

'BQS'

工作日季度初

FY5253Quarter

'REQ'

零售(又称 52-53 周)季度

HalfYearEnd

'HYE'

日历半年末

HalfYearBegin

'HYS'

日历半年初

BHalfYearEnd

'BHYE

工作日半年末

BHalfYearBegin

'BHYS'

工作日半年初

YearEnd

'YE'

日历年末

YearBegin

'YS''BYS'

日历年首

BYearEnd

'BYE'

工作日年末

BYearBegin

'BYS'

工作日年首

FY5253

'RE'

零售(又称 52-53 周)年

Easter

None

复活节假期

BusinessHour

'bh'

工作日小时

CustomBusinessHour

'cbh'

自定义工作日小时

Day

'D'

一天

Hour

'h'

一小时

Minute

'min'

一分钟

Second

's'

一秒

Milli

'ms'

一毫秒

Micro

'us'

一微秒

Nano

'ns'

一纳秒

DateOffsets 还有 rollforward()rollback() 方法,分别用于将日期向前或向后移动到相对于偏移量的有效偏移日期。例如,工作日偏移量会将落在周末(周六和周日)的日期向前滚动到周一,因为工作日偏移量作用于工作日。

In [160]: ts = pd.Timestamp("2018-01-06 00:00:00")

In [161]: ts.day_name()
Out[161]: 'Saturday'

# BusinessHour's valid offset dates are Monday through Friday
In [162]: offset = pd.offsets.BusinessHour(start="09:00")

# Bring the date to the closest offset date (Monday)
In [163]: offset.rollforward(ts)
Out[163]: Timestamp('2018-01-08 09:00:00')

# Date is brought to the closest offset date first and then the hour is added
In [164]: ts + offset
Out[164]: Timestamp('2018-01-08 10:00:00')

这些操作默认会保留时间(小时、分钟等)信息。要将时间重置为午夜,请在应用操作之前或之后使用 normalize()(具体取决于您是否希望操作包含时间信息)。

In [165]: ts = pd.Timestamp("2014-01-01 09:00")

In [166]: day = pd.offsets.Day()

In [167]: day + ts
Out[167]: Timestamp('2014-01-02 09:00:00')

In [168]: (day + ts).normalize()
Out[168]: Timestamp('2014-01-02 00:00:00')

In [169]: ts = pd.Timestamp("2014-01-01 22:00")

In [170]: hour = pd.offsets.Hour()

In [171]: hour + ts
Out[171]: Timestamp('2014-01-01 23:00:00')

In [172]: (hour + ts).normalize()
Out[172]: Timestamp('2014-01-01 00:00:00')

In [173]: (hour + pd.Timestamp("2014-01-01 23:30")).normalize()
Out[173]: Timestamp('2014-01-02 00:00:00')

参数化偏移量#

某些偏移量在创建时可以被“参数化”,以产生不同的行为。例如,用于生成每周数据的 Week 偏移量接受一个 weekday 参数,该参数导致生成的日期始终位于一周中的特定某一天。

In [174]: d = datetime.datetime(2008, 8, 18, 9, 0)

In [175]: d
Out[175]: datetime.datetime(2008, 8, 18, 9, 0)

In [176]: d + pd.offsets.Week()
Out[176]: Timestamp('2008-08-25 09:00:00')

In [177]: d + pd.offsets.Week(weekday=4)
Out[177]: Timestamp('2008-08-22 09:00:00')

In [178]: (d + pd.offsets.Week(weekday=4)).weekday()
Out[178]: 4

In [179]: d - pd.offsets.Week()
Out[179]: Timestamp('2008-08-11 09:00:00')

normalize 选项将对加法和减法生效。

In [180]: d + pd.offsets.Week(normalize=True)
Out[180]: Timestamp('2008-08-25 00:00:00')

In [181]: d - pd.offsets.Week(normalize=True)
Out[181]: Timestamp('2008-08-11 00:00:00')

另一个例子是使用特定结束月份对 YearEnd 进行参数化。

In [182]: d + pd.offsets.YearEnd()
Out[182]: Timestamp('2008-12-31 09:00:00')

In [183]: d + pd.offsets.YearEnd(month=6)
Out[183]: Timestamp('2009-06-30 09:00:00')

将偏移量与 Series / DatetimeIndex 结合使用#

偏移量可以与 SeriesDatetimeIndex 一起使用,将偏移量应用于每个元素。

In [184]: rng = pd.date_range("2012-01-01", "2012-01-03")

In [185]: s = pd.Series(rng)

In [186]: rng
Out[186]: DatetimeIndex(['2012-01-01', '2012-01-02', '2012-01-03'], dtype='datetime64[us]', freq='D')

In [187]: rng + pd.DateOffset(months=2)
Out[187]: DatetimeIndex(['2012-03-01', '2012-03-02', '2012-03-03'], dtype='datetime64[us]', freq=None)

In [188]: s + pd.DateOffset(months=2)
Out[188]: 
0   2012-03-01
1   2012-03-02
2   2012-03-03
dtype: datetime64[us]

In [189]: s - pd.DateOffset(months=2)
Out[189]: 
0   2011-11-01
1   2011-11-02
2   2011-11-03
dtype: datetime64[us]

如果偏移类直接映射到 TimedeltaHourMinuteSecondMicroMilliNano),则可以像 Timedelta 一样使用它——有关更多示例,请参阅 Timedelta 部分

In [190]: s - pd.offsets.Day(2)
Out[190]: 
0   2011-12-30
1   2011-12-31
2   2012-01-01
dtype: datetime64[us]

In [191]: td = s - pd.Series(pd.date_range("2011-12-29", "2011-12-31"))

In [192]: td
Out[192]: 
0   3 days
1   3 days
2   3 days
dtype: timedelta64[us]

In [193]: td + pd.offsets.Minute(15)
Out[193]: 
0   3 days 00:15:00
1   3 days 00:15:00
2   3 days 00:15:00
dtype: timedelta64[us]

请注意,某些偏移量(例如 BQuarterEnd)没有向量化实现。它们仍然可以使用,但可能计算速度非常慢,并会显示 PerformanceWarning

In [194]: rng + pd.offsets.BQuarterEnd()
Out[194]: DatetimeIndex(['2012-03-30', '2012-03-30', '2012-03-30'], dtype='datetime64[us]', freq=None)

自定义工作日#

CDayCustomBusinessDay 类提供了参数化的 BusinessDay 类,可用于创建考虑了当地节假日和当地周末习俗的自定义工作日日历。

作为一个有趣的例子,让我们看看埃及,那里实行周五-周六周末。

In [195]: weekmask_egypt = "Sun Mon Tue Wed Thu"

# They also observe International Workers' Day so let's
# add that for a couple of years
In [196]: holidays = [
   .....:     "2012-05-01",
   .....:     datetime.datetime(2013, 5, 1),
   .....:     np.datetime64("2014-05-01"),
   .....: ]
   .....: 

In [197]: bday_egypt = pd.offsets.CustomBusinessDay(
   .....:     holidays=holidays,
   .....:     weekmask=weekmask_egypt,
   .....: )
   .....: 

In [198]: dt = datetime.datetime(2013, 4, 30)

In [199]: dt + 2 * bday_egypt
Out[199]: Timestamp('2013-05-05 00:00:00')

让我们映射到星期几的名称。

In [200]: dts = pd.date_range(dt, periods=5, freq=bday_egypt)

In [201]: pd.Series(dts.weekday, dts).map(pd.Series("Mon Tue Wed Thu Fri Sat Sun".split()))
Out[201]: 
2013-04-30    Tue
2013-05-02    Thu
2013-05-05    Sun
2013-05-06    Mon
2013-05-07    Tue
Freq: C, dtype: str

节假日历可用于提供节假日列表。有关更多信息,请参阅 节假日历 部分。

In [202]: from pandas.tseries.holiday import USFederalHolidayCalendar

In [203]: bday_us = pd.offsets.CustomBusinessDay(calendar=USFederalHolidayCalendar())

# Friday before MLK Day
In [204]: dt = datetime.datetime(2014, 1, 17)

# Tuesday after MLK Day (Monday is skipped because it's a holiday)
In [205]: dt + bday_us
Out[205]: Timestamp('2014-01-21 00:00:00')

可以按照常规方式定义考虑了特定节假日历的月度偏移量。

In [206]: bmth_us = pd.offsets.CustomBusinessMonthBegin(calendar=USFederalHolidayCalendar())

# Skip new years
In [207]: dt = datetime.datetime(2013, 12, 17)

In [208]: dt + bmth_us
Out[208]: Timestamp('2014-01-02 00:00:00')

# Define date index with custom offset
In [209]: pd.date_range(start="20100101", end="20120101", freq=bmth_us)
Out[209]: 
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[us]', freq='CBMS')

注意

频率字符串“C”用于指示使用了 CustomBusinessDay DateOffset。需要注意的是,由于 CustomBusinessDay 是一个参数化的类型,CustomBusinessDay 的实例可能会有所不同,并且这从“C”频率字符串无法检测到。因此,用户需要确保“C”频率字符串在用户应用程序中保持一致。

工作日小时#

BusinessHour 类提供了基于 BusinessDay 的工作日小时表示,允许使用特定的开始和结束时间。

默认情况下,BusinessHour 使用 9:00 - 17:00 作为工作时间。添加 BusinessHour 将按小时频率增加 Timestamp。如果目标 Timestamp 超出了工作时间,则移至下一个工作日小时,然后增加它。如果结果超出了工作时间结束时间,剩余的小时将被添加到下一个工作日。

In [210]: bh = pd.offsets.BusinessHour()

In [211]: bh
Out[211]: <BusinessHour: bh=09:00-17:00>

# 2014-08-01 is Friday
In [212]: pd.Timestamp("2014-08-01 10:00").weekday()
Out[212]: 4

In [213]: pd.Timestamp("2014-08-01 10:00") + bh
Out[213]: Timestamp('2014-08-01 11:00:00')

# Below example is the same as: pd.Timestamp('2014-08-01 09:00') + bh
In [214]: pd.Timestamp("2014-08-01 08:00") + bh
Out[214]: Timestamp('2014-08-01 10:00:00')

# If the results is on the end time, move to the next business day
In [215]: pd.Timestamp("2014-08-01 16:00") + bh
Out[215]: Timestamp('2014-08-04 09:00:00')

# Remainings are added to the next day
In [216]: pd.Timestamp("2014-08-01 16:30") + bh
Out[216]: Timestamp('2014-08-04 09:30:00')

# Adding 2 business hours
In [217]: pd.Timestamp("2014-08-01 10:00") + pd.offsets.BusinessHour(2)
Out[217]: Timestamp('2014-08-01 12:00:00')

# Subtracting 3 business hours
In [218]: pd.Timestamp("2014-08-01 10:00") + pd.offsets.BusinessHour(-3)
Out[218]: Timestamp('2014-07-31 15:00:00')

您还可以通过关键字参数指定 startend 时间。参数必须是具有 hour:minute 表示法的 strdatetime.time 实例。指定秒、微秒和纳秒作为工作日小时会导致 ValueError

In [219]: bh = pd.offsets.BusinessHour(start="11:00", end=datetime.time(20, 0))

In [220]: bh
Out[220]: <BusinessHour: bh=11:00-20:00>

In [221]: pd.Timestamp("2014-08-01 13:00") + bh
Out[221]: Timestamp('2014-08-01 14:00:00')

In [222]: pd.Timestamp("2014-08-01 09:00") + bh
Out[222]: Timestamp('2014-08-01 12:00:00')

In [223]: pd.Timestamp("2014-08-01 18:00") + bh
Out[223]: Timestamp('2014-08-01 19:00:00')

start 时间设置得晚于 end 表示午夜的工作小时。在这种情况下,工作小时会跨越午夜并延伸到第二天。有效的日间工作时间取决于是否从有效的 BusinessDay 开始。

In [224]: bh = pd.offsets.BusinessHour(start="17:00", end="09:00")

In [225]: bh
Out[225]: <BusinessHour: bh=17:00-09:00>

In [226]: pd.Timestamp("2014-08-01 17:00") + bh
Out[226]: Timestamp('2014-08-01 18:00:00')

In [227]: pd.Timestamp("2014-08-01 23:00") + bh
Out[227]: Timestamp('2014-08-02 00:00:00')

# Although 2014-08-02 is Saturday,
# it is valid because it starts from 08-01 (Friday).
In [228]: pd.Timestamp("2014-08-02 04:00") + bh
Out[228]: 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 [229]: pd.Timestamp("2014-08-04 04:00") + bh
Out[229]: Timestamp('2014-08-04 18:00:00')

BusinessHour.rollforwardrollback 应用于非工作时间会导致下一个工作小时开始或前一天的结束。与其他偏移量不同,BusinessHour.rollforward 根据定义可能产生与 apply 不同的结果。

这是因为一天的结束工作时间等于第二天的开始工作时间。例如,在默认工作时间(9:00 - 17:00)下,2014-08-01 17:002014-08-04 09:00 之间没有间隔(0 分钟)。

# This adjusts a Timestamp to business hour edge
In [230]: pd.offsets.BusinessHour().rollback(pd.Timestamp("2014-08-02 15:00"))
Out[230]: Timestamp('2014-08-01 17:00:00')

In [231]: pd.offsets.BusinessHour().rollforward(pd.Timestamp("2014-08-02 15:00"))
Out[231]: 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 [232]: pd.offsets.BusinessHour() + pd.Timestamp("2014-08-02 15:00")
Out[232]: Timestamp('2014-08-04 10:00:00')

# BusinessDay results (for reference)
In [233]: pd.offsets.BusinessHour().rollforward(pd.Timestamp("2014-08-02"))
Out[233]: 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 [234]: pd.offsets.BusinessHour() + pd.Timestamp("2014-08-02")
Out[234]: Timestamp('2014-08-04 10:00:00')

BusinessHour 将周六和周日视为节假日。要使用任意节假日,您可以使用 CustomBusinessHour 偏移量,如下一子节所述。

自定义工作小时#

CustomBusinessHourBusinessHourCustomBusinessDay 的混合体,它允许您指定任意节假日。 CustomBusinessHour 的工作方式与 BusinessHour 相同,只是它会跳过指定的自定义节假日。

In [235]: from pandas.tseries.holiday import USFederalHolidayCalendar

In [236]: bhour_us = pd.offsets.CustomBusinessHour(calendar=USFederalHolidayCalendar())

# Friday before MLK Day
In [237]: dt = datetime.datetime(2014, 1, 17, 15)

In [238]: dt + bhour_us
Out[238]: Timestamp('2014-01-17 16:00:00')

# Tuesday after MLK Day (Monday is skipped because it's a holiday)
In [239]: dt + bhour_us * 2
Out[239]: Timestamp('2014-01-21 09:00:00')

您可以使用 BusinessHourCustomBusinessDay 支持的关键字参数。

In [240]: 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 [241]: dt + bhour_mon * 2
Out[241]: 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

microseconds

ns

nanoseconds

注意

使用上述偏移别名时,应注意像 date_range()bdate_range() 这样的函数只返回在 start_dateend_date 定义的区间内的时间戳。如果 start_date 与频率不匹配,则返回的时间戳将从下一个有效时间戳开始,对于 end_date 也是如此,返回的时间戳将停止在上一个有效时间戳。

例如,对于 MS 偏移量,如果 start_date 不是月初,则返回的时间戳将从下个月的第一天开始。如果 end_date 不是月初的第一天,则最后一个返回的时间戳将是对应月份的第一天。

In [242]: dates_lst_1 = pd.date_range("2020-01-06", "2020-04-03", freq="MS")

In [243]: dates_lst_1
Out[243]: DatetimeIndex(['2020-02-01', '2020-03-01', '2020-04-01'], dtype='datetime64[us]', freq='MS')

In [244]: dates_lst_2 = pd.date_range("2020-01-01", "2020-04-01", freq="MS")

In [245]: dates_lst_2
Out[245]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01', '2020-04-01'], dtype='datetime64[us]', freq='MS')

在上例中,我们可以看到 date_range()bdate_range() 只返回 start_dateend_date 之间的有效时间戳。如果这些不是给定频率的有效时间戳,它将滚动到 start_date 的下一个值(对于 end_date 则为上一个值)。

期间别名#

许多字符串别名用于常见的有用的时间序列频率。我们将这些别名称为期间别名

别名

描述

B

工作日频率

D

日历日频率

W

周频率

M

月频率

Q

季度频率

Y

年频率

h

小时频率

min

分钟频率

s

秒频率

ms

毫秒

us

microseconds

ns

nanoseconds

组合别名#

如前所述,别名和偏移实例在大多数函数中是可互换的。

In [246]: pd.date_range(start, periods=5, freq="B")
Out[246]: 
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
               '2011-01-07'],
              dtype='datetime64[us]', freq='B')

In [247]: pd.date_range(start, periods=5, freq=pd.offsets.BDay())
Out[247]: 
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
               '2011-01-07'],
              dtype='datetime64[us]', freq='B')

您可以组合日偏移量和日内偏移量。

In [248]: pd.date_range(start, periods=10, freq="2h20min")
Out[248]: 
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[us]', freq='140min')

In [249]: pd.date_range(start, periods=10, freq="1D10us")
Out[249]: 
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[us]', freq='86400000010us')

锚定偏移量#

对于某些频率,您可以指定一个锚定后缀。

别名

描述

W-SUN

周频率(周日)。同 'W'

W-MON

周频率(周一)

W-TUE

周频率(周二)

W-WED

周频率(周三)

W-THU

周频率(周四)

W-FRI

周频率(周五)

W-SAT

周频率(周六)

(B)Q(E)(S)-DEC

季度频率,年末为十二月。同 'QE'

(B)Q(E)(S)-JAN

季度频率,年末为一月

(B)Q(E)(S)-FEB

季度频率,年末为二月

(B)Q(E)(S)-MAR

季度频率,年末为三月

(B)Q(E)(S)-APR

季度频率,年末为四月

(B)Q(E)(S)-MAY

季度频率,年末为五月

(B)Q(E)(S)-JUN

季度频率,年末为六月

(B)Q(E)(S)-JUL

季度频率,年末为七月

(B)Q(E)(S)-AUG

季度频率,年末为八月

(B)Q(E)(S)-SEP

季度频率,年末为九月

(B)Q(E)(S)-OCT

季度频率,年末为十月

(B)Q(E)(S)-NOV

季度频率,年末为十一月

(B)Y(E)(S)-DEC

年频率,锚定在十二月尾。同 'YE'

(B)Y(E)(S)-JAN

年频率,锚定在一月尾

(B)Y(E)(S)-FEB

年频率,锚定在二月尾

(B)Y(E)(S)-MAR

年频率,锚定在三月尾

(B)Y(E)(S)-APR

年频率,锚定在四月尾

(B)Y(E)(S)-MAY

年频率,锚定在五月尾

(B)Y(E)(S)-JUN

年频率,锚定在六月尾

(B)Y(E)(S)-JUL

年频率,锚定在七月尾

(B)Y(E)(S)-AUG

年频率,锚定在八月尾

(B)Y(E)(S)-SEP

年频率,锚定在九月尾

(B)Y(E)(S)-OCT

年频率,锚定在十月尾

(B)Y(E)(S)-NOV

年频率,锚定在十一月尾

这些可以用作 date_rangebdate_rangeDatetimeIndex 的构造函数,以及 pandas 中其他各种与时间序列相关的函数的参数。

锚定偏移量语义#

对于那些锚定在特定频率的开始或结束(MonthEndMonthBeginWeekEnd 等)的偏移量,以下规则适用于向前和向后滚动。

n 不为 0 时,如果给定日期不在锚定点上,则将其吸附到下一个(上一个)锚定点,并额外向前或向后移动 |n|-1 步。

In [250]: pd.Timestamp("2014-01-02") + pd.offsets.MonthBegin(n=1)
Out[250]: Timestamp('2014-02-01 00:00:00')

In [251]: pd.Timestamp("2014-01-02") + pd.offsets.MonthEnd(n=1)
Out[251]: Timestamp('2014-01-31 00:00:00')

In [252]: pd.Timestamp("2014-01-02") - pd.offsets.MonthBegin(n=1)
Out[252]: Timestamp('2014-01-01 00:00:00')

In [253]: pd.Timestamp("2014-01-02") - pd.offsets.MonthEnd(n=1)
Out[253]: Timestamp('2013-12-31 00:00:00')

In [254]: pd.Timestamp("2014-01-02") + pd.offsets.MonthBegin(n=4)
Out[254]: Timestamp('2014-05-01 00:00:00')

In [255]: pd.Timestamp("2014-01-02") - pd.offsets.MonthBegin(n=4)
Out[255]: Timestamp('2013-10-01 00:00:00')

如果给定日期锚定点上,则向前或向后移动 |n| 点。

In [256]: pd.Timestamp("2014-01-01") + pd.offsets.MonthBegin(n=1)
Out[256]: Timestamp('2014-02-01 00:00:00')

In [257]: pd.Timestamp("2014-01-31") + pd.offsets.MonthEnd(n=1)
Out[257]: Timestamp('2014-02-28 00:00:00')

In [258]: pd.Timestamp("2014-01-01") - pd.offsets.MonthBegin(n=1)
Out[258]: Timestamp('2013-12-01 00:00:00')

In [259]: pd.Timestamp("2014-01-31") - pd.offsets.MonthEnd(n=1)
Out[259]: Timestamp('2013-12-31 00:00:00')

In [260]: pd.Timestamp("2014-01-01") + pd.offsets.MonthBegin(n=4)
Out[260]: Timestamp('2014-05-01 00:00:00')

In [261]: pd.Timestamp("2014-01-31") - pd.offsets.MonthBegin(n=4)
Out[261]: Timestamp('2013-10-01 00:00:00')

n=0 时,如果日期在锚定点上则不移动,否则滚动到下一个锚定点。

In [262]: pd.Timestamp("2014-01-02") + pd.offsets.MonthBegin(n=0)
Out[262]: Timestamp('2014-02-01 00:00:00')

In [263]: pd.Timestamp("2014-01-02") + pd.offsets.MonthEnd(n=0)
Out[263]: Timestamp('2014-01-31 00:00:00')

In [264]: pd.Timestamp("2014-01-01") + pd.offsets.MonthBegin(n=0)
Out[264]: Timestamp('2014-01-01 00:00:00')

In [265]: pd.Timestamp("2014-01-31") + pd.offsets.MonthEnd(n=0)
Out[265]: Timestamp('2014-01-31 00:00:00')

节假日 / 节假日历#

节假日和节假日历提供了一种简单的方式来定义用于 CustomBusinessDay 或其他需要预定义节假日集的分析中的节假日规则。AbstractHolidayCalendar 类提供了返回节假日列表所需的所有必要方法,并且只需要在特定的节假日历类中定义 rules。此外,start_dateend_date 类属性决定了节假日生成的日期范围。这些应该在 AbstractHolidayCalendar 类上进行覆盖,以便将范围应用于所有子类。 USFederalHolidayCalendar 是唯一存在的日历,主要用作开发其他日历的示例。

对于固定日期的节假日(例如,美国阵亡将士纪念日或 7 月 4 日),如果节假日落在周末或其他非工作日,则由追溯规则决定何时庆祝该节假日。定义的追溯规则是:

规则

描述

next_workday

将周六和周日移至周一

previous_workday

将周六和周日移至周五

nearest_workday

将周六移至周五,周日移至周一

before_nearest_workday

应用 nearest_workday,然后移动到该日期之前的下一个工作日

after_nearest_workday

应用 nearest_workday,然后移动到该日期之后的下一个工作日

sunday_to_monday

将周日移至下一个周一

next_monday_or_tuesday

将周六移至周一,将周日/周一移至周二

previous_friday

将周六和周日移至上一个周五

next_monday

将周六和周日移至下一个周一

weekend_to_monday

next_monday

节假日和节假日历定义示例

In [266]: from pandas.tseries.holiday import (
   .....:     Holiday,
   .....:     USMemorialDay,
   .....:     AbstractHolidayCalendar,
   .....:     nearest_workday,
   .....:     MO,
   .....: )
   .....: 

In [267]: 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 [268]: cal = ExampleCalendar()

In [269]: cal.holidays(datetime.datetime(2012, 1, 1), datetime.datetime(2012, 12, 31))
Out[269]: DatetimeIndex(['2012-05-28', '2012-07-04', '2012-10-08'], dtype='datetime64[us]', freq=None)
提示:

weekday=MO(2) 等同于 2 * Week(weekday=2)

使用此日历,创建索引或进行偏移量算术将跳过周末和节假日(即,阵亡将士纪念日/7 月 4 日)。例如,以下代码使用 ExampleCalendar 定义了一个自定义工作日偏移量。与其他任何偏移量一样,它可用于创建 DatetimeIndex 或添加到 datetimeTimestamp 对象。

In [270]: pd.date_range(
   .....:     start="7/1/2012", end="7/10/2012", freq=pd.offsets.CDay(calendar=cal)
   .....: ).to_pydatetime()
   .....: 
Out[270]: 
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 [271]: offset = pd.offsets.CustomBusinessDay(calendar=cal)

In [272]: datetime.datetime(2012, 5, 25) + offset
Out[272]: Timestamp('2012-05-29 00:00:00')

In [273]: datetime.datetime(2012, 7, 3) + offset
Out[273]: Timestamp('2012-07-05 00:00:00')

In [274]: datetime.datetime(2012, 7, 3) + 2 * offset
Out[274]: Timestamp('2012-07-06 00:00:00')

In [275]: datetime.datetime(2012, 7, 6) + offset
Out[275]: Timestamp('2012-07-09 00:00:00')

范围由 AbstractHolidayCalendarstart_dateend_date 类属性定义。默认值如下所示。

In [276]: AbstractHolidayCalendar.start_date
Out[276]: Timestamp('1970-01-01 00:00:00')

In [277]: AbstractHolidayCalendar.end_date
Out[277]: Timestamp('2200-12-31 00:00:00')

可以通过将属性设置为 datetime/Timestamp/string 来覆盖这些日期。

In [278]: AbstractHolidayCalendar.start_date = datetime.datetime(2012, 1, 1)

In [279]: AbstractHolidayCalendar.end_date = datetime.datetime(2012, 12, 31)

In [280]: cal.holidays()
Out[280]: DatetimeIndex(['2012-05-28', '2012-07-04', '2012-10-08'], dtype='datetime64[us]', freq=None)

任何导入的节假日历类都可以通过 get_calendar 函数按名称访问,该函数返回一个节假日历类实例。此外,HolidayCalendarFactory 提供了一个简单的接口来创建组合了多个节假日历或带有附加规则的节假日历。

In [281]: from pandas.tseries.holiday import get_calendar, HolidayCalendarFactory, USLaborDay

In [282]: cal = get_calendar("ExampleCalendar")

In [283]: cal.rules
Out[283]: 
[Holiday: Memorial Day (month=5, day=31, offset=<DateOffset: weekday=MO(-1)>),
 Holiday: July 4th (month=7, day=4, observance=<function nearest_workday at 0x7fc1068a2160>),
 Holiday: Columbus Day (month=10, day=1, offset=<DateOffset: weekday=MO(+2)>)]

In [284]: new_cal = HolidayCalendarFactory("NewExampleCalendar", cal, USLaborDay)

In [285]: new_cal.rules
Out[285]: 
[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 0x7fc1068a2160>),
 Holiday: Columbus Day (month=10, day=1, offset=<DateOffset: weekday=MO(+2)>)]

重采样#

pandas 提供了简单、强大且高效的功能,用于在频率转换期间执行重采样操作(例如,将秒级数据转换为 5 分钟级数据)。这在金融应用中非常常见,但不仅限于金融应用。

resample() 是一个基于时间的 groupby,然后对每个组执行一个归约方法。有关一些高级策略,请参阅一些 cookbook 示例

resample() 方法可以直接从 DataFrameGroupBy 对象使用,请参阅 groupby 文档

基础#

In [297]: rng = pd.date_range("1/1/2012", periods=100, freq="s")

In [298]: ts = pd.Series(np.random.randint(0, 500, len(rng)), index=rng)

In [299]: ts.resample("5Min").sum()
Out[299]: 
2012-01-01    25103
Freq: 5min, dtype: int64

resample 函数非常灵活,允许您指定许多不同的参数来控制频率转换和重采样操作。

GroupBy 可用的任何内置方法都可以作为返回对象的成员方法使用,包括 summeanstdsemmaxminmedianfirstlastohlc

In [300]: ts.resample("5Min").mean()
Out[300]: 
2012-01-01    251.03
Freq: 5min, dtype: float64

In [301]: ts.resample("5Min").ohlc()
Out[301]: 
            open  high  low  close
2012-01-01   308   460    9    205

In [302]: ts.resample("5Min").max()
Out[302]: 
2012-01-01    460
Freq: 5min, dtype: int64

对于降采样,可以设置 closed 为 'left' 或 'right' 来指定区间的哪个端点是闭合的。

In [303]: ts.resample("5Min", closed="right").mean()
Out[303]: 
2011-12-31 23:55:00    308.000000
2012-01-01 00:00:00    250.454545
Freq: 5min, dtype: float64

In [304]: ts.resample("5Min", closed="left").mean()
Out[304]: 
2012-01-01    251.03
Freq: 5min, dtype: float64

label 这样的参数用于操作结果标签。 label 指定结果是使用区间的开始还是结束来标记。

In [305]: ts.resample("5Min").mean()  # by default label='left'
Out[305]: 
2012-01-01    251.03
Freq: 5min, dtype: float64

In [306]: ts.resample("5Min", label="left").mean()
Out[306]: 
2012-01-01    251.03
Freq: 5min, dtype: float64

警告

对于所有频率偏移量,labelclosed 的默认值均为 'left',但 'ME'、'YE'、'QE'、'BME'、'BYE'、'BQE' 和 'W' 除外,它们都默认为 'right'。

这可能会无意中导致向前看,即将稍晚时间的值回溯到稍早时间,例如下面的使用 BusinessDay 频率的示例。

In [307]: s = pd.date_range("2000-01-01", "2000-01-05").to_series()

In [308]: s.iloc[2] = pd.NaT

In [309]: s.dt.day_name()
Out[309]: 
2000-01-01     Saturday
2000-01-02       Sunday
2000-01-03          NaN
2000-01-04      Tuesday
2000-01-05    Wednesday
Freq: D, dtype: str

# default: label='left', closed='left'
In [310]: s.resample("B").last().dt.day_name()
Out[310]: 
1999-12-31       Sunday
2000-01-03          NaN
2000-01-04      Tuesday
2000-01-05    Wednesday
Freq: B, dtype: str

请注意,周日的值是如何被回溯到前一个周五的。要获得将周日的值推迟到周一的行为,请改用

In [311]: s.resample("B", label="right", closed="right").last().dt.day_name()
Out[311]: 
2000-01-03       Sunday
2000-01-04      Tuesday
2000-01-05    Wednesday
2000-01-06          NaN
Freq: B, dtype: str

`axis` 参数可以设置为 0 或 1,允许您为 `DataFrame` 对指定轴进行重采样。

`kind` 可以设置为 ‘timestamp’ 或 ‘period’,以便将生成的索引转换为/从时间戳和时间跨度表示。默认情况下,`resample` 会保留输入表示。

`convention` 在重采样周期数据(详见下文)时可以设置为 ‘start’ 或 ‘end’。它指定了低频周期如何转换为高频周期。

上采样#

对于上采样,您可以指定一种上采样方式以及 `limit` 参数来插值创建的间隙。

# from secondly to every 250 milliseconds
In [312]: ts[:2].resample("250ms").asfreq()
Out[312]: 
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 [313]: ts[:2].resample("250ms").ffill()
Out[313]: 
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 [314]: ts[:2].resample("250ms").ffill(limit=2)
Out[314]: 
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 [315]: rng = pd.date_range("2014-1-1", periods=100, freq="D") + pd.Timedelta("1s")

In [316]: ts = pd.Series(range(100), index=rng)

如果我们想重采样到序列的完整范围

In [317]: ts.resample("3min").sum()
Out[317]: 
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 [318]: from functools import partial

In [319]: from pandas.tseries.frequencies import to_offset

In [320]: def round(t, freq):
   .....:     freq = to_offset(freq)
   .....:     td = pd.Timedelta(freq)
   .....:     return pd.Timestamp((t.value // td.value) * td.value)
   .....: 

In [321]: ts.groupby(partial(round, freq="3min")).sum()
Out[321]: 
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、groupby API窗口 API 类似,`Resampler` 可以被选择性地重采样。

重采样 `DataFrame` 时,默认行为是对所有列应用相同的函数。

In [322]: df = pd.DataFrame(
   .....:     np.random.randn(1000, 3),
   .....:     index=pd.date_range("1/1/2012", freq="s", periods=1000),
   .....:     columns=["A", "B", "C"],
   .....: )
   .....: 

In [323]: r = df.resample("3min")

In [324]: r.mean()
Out[324]: 
                            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 [325]: r["A"].mean()
Out[325]: 
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 [326]: r[["A", "B"]].mean()
Out[326]: 
                            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 [327]: r["A"].agg(["sum", "mean", "std"])
Out[327]: 
                           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 [328]: r.agg(["sum", "mean"])
Out[328]: 
                             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 [329]: r.agg({"A": "sum", "B": lambda x: np.std(x, ddof=1)})
Out[329]: 
                             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 [330]: r.agg({"A": "sum", "B": "std"})
Out[330]: 
                             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 [331]: r.agg({"A": ["sum", "std"], "B": ["mean", "std"]})
Out[331]: 
                             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 [332]: 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 [333]: df
Out[333]: 
                   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 [334]: df.resample("MS", on="date")[["a"]].sum()
Out[334]: 
            a
date         
2015-01-01  6
2015-02-01  4

类似地,如果您希望通过 `MultiIndex` 的日期时间类级别进行重采样,则可以将其名称或位置传递给 `level` 关键字。

In [335]: df.resample("MS", level="d")[["a"]].sum()
Out[335]: 
            a
d            
2015-01-01  6
2015-02-01  4

遍历分组#

有了 `Resampler` 对象,遍历分组数据非常自然,并且其功能类似于 `itertools.groupby()`。

In [336]: 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 [337]: resampled = small.resample("h")

In [338]: 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` 指定一个固定的时间戳。

例如:

In [339]: start, end = "2000-10-01 23:30:00", "2000-10-02 00:30:00"

In [340]: middle = "2000-10-02 00:00:00"

In [341]: rng = pd.date_range(start, end, freq="7min")

In [342]: ts = pd.Series(np.arange(len(rng)) * 3, index=rng)

In [343]: ts
Out[343]: 
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 [344]: ts.resample("17min", origin="start_day").sum()
Out[344]: 
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 [345]: ts[middle:end].resample("17min", origin="start_day").sum()
Out[345]: 
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 [346]: ts.resample("17min", origin="epoch").sum()
Out[346]: 
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 [347]: ts[middle:end].resample("17min", origin="epoch").sum()
Out[347]: 
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 [348]: ts.resample("17min", origin="2001-01-01").sum()
Out[348]: 
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 [349]: ts[middle:end].resample("17min", origin=pd.Timestamp("2001-01-01")).sum()
Out[349]: 
2000-10-02 00:04:00    54
2000-10-02 00:21:00    24
Freq: 17min, dtype: int64

如果需要,您可以通过一个 `offset` Timedelta 来调整区间,该 Timedelta 将被添加到默认的 `origin`。对于此时间序列,这两个示例是等效的。

In [350]: ts.resample("17min", origin="start").sum()
Out[350]: 
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 [351]: ts.resample("17min", offset="23h30min").sum()
Out[351]: 
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` 将被设置为时间序列的第一个值。

向后重采样#

有时,我们不需要调整区间的开始点,而是需要固定区间的结束点,以便使用给定的 `freq` 进行向后重采样。向后重采样默认将 `closed` 设置为 `'right'`,因为最后一个值应被视为最后一个区间的边缘点。

我们可以将 `origin` 设置为 `'end'`。特定 `Timestamp` 索引的值代表了从当前 `Timestamp` 减去 `freq` 到当前 `Timestamp` 的重采样结果,并且区间右闭。

In [352]: ts.resample('17min', origin='end').sum()
Out[352]: 
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 [353]: ts.resample('17min', origin='end_day').sum()
Out[353]: 
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 [354]: ceil_mid = rng.max().ceil('D')

In [355]: freq = pd.offsets.Minute(17)

In [356]: bin_res = ceil_mid - freq * ((ceil_mid - rng.max()) // freq)

In [357]: bin_res
Out[357]: Timestamp('2000-10-02 00:29:00')

时间跨度表示#

pandas 中,规则的时间间隔由 `Period` 对象表示,而 `Period` 对象的序列则被收集在 `PeriodIndex` 中,可以使用方便的函数 `period_range` 创建。

Period#

一个 `Period` 代表一个时间跨度(例如,一天、一个月、一个季度等)。您可以使用频率别名(如下所示)通过 `freq` 关键字指定跨度。因为 `freq` 代表一个 `Period` 的跨度,所以它不能为负数,例如 “-3D”。

In [358]: pd.Period("2012", freq="Y-DEC")
Out[358]: Period('2012', 'Y-DEC')

In [359]: pd.Period("2012-1-1", freq="D")
Out[359]: Period('2012-01-01', 'D')

In [360]: pd.Period("2012-1-1 19:00", freq="h")
Out[360]: Period('2012-01-01 19:00', 'h')

In [361]: pd.Period("2012-1-1 19:00", freq="5h")
Out[361]: Period('2012-01-01 19:00', '5h')

在周期上加减整数会按照其自身的频率移动周期。不同 `freq`(跨度)的 `Period` 之间不允许进行算术运算。

In [362]: p = pd.Period("2012", freq="Y-DEC")

In [363]: p + 1
Out[363]: Period('2013', 'Y-DEC')

In [364]: p - 3
Out[364]: Period('2009', 'Y-DEC')

In [365]: p = pd.Period("2012-01", freq="2M")

In [366]: p + 2
Out[366]: Period('2012-05', '2M')

In [367]: p - 1
Out[367]: Period('2011-11', '2M')

In [368]: p == pd.Period("2012-01", freq="3M")
Out[368]: False

如果 `Period` 的频率是每日或更高(`D`, `h`, `min`, `s`, `ms`, `us` 和 `ns`),则可以添加 offsetstimedelta-like 对象,前提是结果具有相同的频率。否则,将引发 `ValueError`。

In [369]: p = pd.Period("2014-07-01 09:00", freq="h")

In [370]: p + pd.offsets.Hour(2)
Out[370]: Period('2014-07-01 11:00', 'h')

In [371]: p + datetime.timedelta(minutes=120)
Out[371]: Period('2014-07-01 11:00', 'h')

In [372]: p + np.timedelta64(7200, "s")
Out[372]: Period('2014-07-01 11:00', 'h')
In [373]: p + pd.offsets.Minute(5)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/work/pandas/pandas/pandas/_libs/tslibs/period.pyx:1907, in pandas._libs.tslibs.period._Period._add_timedeltalike_scalar()

File ~/work/pandas/pandas/pandas/_libs/tslibs/timedeltas.pyx:284, in pandas._libs.tslibs.timedeltas.delta_to_nanoseconds()

File ~/work/pandas/pandas/pandas/_libs/tslibs/np_datetime.pyx:676, 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[373], line 1
----> 1 p + pd.offsets.Minute(5)

File ~/work/pandas/pandas/pandas/_libs/tslibs/period.pyx:1928, in pandas._libs.tslibs.period._Period.__add__()

File ~/work/pandas/pandas/pandas/_libs/tslibs/period.pyx:1909, in pandas._libs.tslibs.period._Period._add_timedeltalike_scalar()

IncompatibleFrequency: Input cannot be converted to Period(freq=h)

如果 `Period` 具有其他频率,则只能添加相同的 offsets。否则,将引发 `ValueError`。

In [374]: p = pd.Period("2014-07", freq="M")

In [375]: p + pd.offsets.MonthEnd(3)
Out[375]: Period('2014-10', 'M')
In [376]: p + pd.offsets.MonthBegin(3)
---------------------------------------------------------------------------
IncompatibleFrequency                     Traceback (most recent call last)
Cell In[376], line 1
----> 1 p + pd.offsets.MonthBegin(3)

File ~/work/pandas/pandas/pandas/_libs/tslibs/period.pyx:1930, in pandas._libs.tslibs.period._Period.__add__()

File ~/work/pandas/pandas/pandas/_libs/tslibs/period.pyx:1920, in pandas._libs.tslibs.period._Period._add_offset()

File ~/work/pandas/pandas/pandas/_libs/tslibs/period.pyx:1766, in pandas._libs.tslibs.period.PeriodMixin._require_matching_freq()

IncompatibleFrequency: Input has different freq=3MS from Period(freq=M)

计算两个具有相同频率的 `Period` 实例之间的差值将返回它们之间频率单位的数量。

In [377]: pd.Period("2012", freq="Y-DEC") - pd.Period("2002", freq="Y-DEC")
Out[377]: <10 * YearEnds: month=12>

PeriodIndex 和 period_range#

规则的 `Period` 对象序列可以收集在 `PeriodIndex` 中,可以使用方便的函数 `period_range` 来构建。

In [378]: prng = pd.period_range("1/1/2011", "1/1/2012", freq="M")

In [379]: prng
Out[379]: 
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 [380]: pd.PeriodIndex(["2011-1", "2011-2", "2011-3"], freq="M")
Out[380]: PeriodIndex(['2011-01', '2011-02', '2011-03'], dtype='period[M]')

传递乘法频率会输出一个 `Period` 序列,该序列具有乘法跨度。

In [381]: pd.period_range(start="2014-01", freq="3M", periods=4)
Out[381]: PeriodIndex(['2014-01', '2014-04', '2014-07', '2014-10'], dtype='period[3M]')

如果 `start` 或 `end` 是 `Period` 对象,它们将作为锚定端点用于与 `PeriodIndex` 构造函数频率匹配的 `PeriodIndex`。

In [382]: pd.period_range(
   .....:     start=pd.Period("2017Q1", freq="Q"), end=pd.Period("2017Q2", freq="Q"), freq="M"
   .....: )
   .....: 
Out[382]: PeriodIndex(['2017-03', '2017-04', '2017-05', '2017-06'], dtype='period[M]')

与 `DatetimeIndex` 类似,`PeriodIndex` 也可以用来索引 pandas 对象。

In [383]: ps = pd.Series(np.random.randn(len(prng)), prng)

In [384]: ps
Out[384]: 
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 [385]: idx = pd.period_range("2014-07-01 09:00", periods=5, freq="h")

In [386]: idx
Out[386]: 
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 [387]: idx + pd.offsets.Hour(2)
Out[387]: 
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 [388]: idx = pd.period_range("2014-07", periods=5, freq="M")

In [389]: idx
Out[389]: PeriodIndex(['2014-07', '2014-08', '2014-09', '2014-10', '2014-11'], dtype='period[M]')

In [390]: idx + pd.offsets.MonthEnd(3)
Out[390]: PeriodIndex(['2014-10', '2014-11', '2014-12', '2015-01', '2015-02'], dtype='period[M]')

`PeriodIndex` 有自己的 dtype,名为 `period`,请参阅 Period Dtypes

Period dtypes#

`PeriodIndex` 有一个自定义的 `period` dtype。这是一个 pandas 扩展 dtype,类似于 时区感知 dtype(`datetime64[ns, tz]`)。

`period` dtype 包含 `freq` 属性,并表示为 `period[freq]`,例如 `period[D]` 或 `period[M]`,使用 频率字符串

In [391]: pi = pd.period_range("2016-01-01", periods=3, freq="M")

In [392]: pi
Out[392]: PeriodIndex(['2016-01', '2016-02', '2016-03'], dtype='period[M]')

In [393]: pi.dtype
Out[393]: period[M]

`period` dtype 可用于 `.astype(...)`。它允许更改 `PeriodIndex` 的 `freq`(如 `.asfreq()`),并将 `DatetimeIndex` 转换为 `PeriodIndex`(如 `to_period()`)。

# change monthly freq to daily freq
In [394]: pi.astype("period[D]")
Out[394]: PeriodIndex(['2016-01-31', '2016-02-29', '2016-03-31'], dtype='period[D]')

# convert to DatetimeIndex
In [395]: pi.astype("datetime64[ns]")
Out[395]: DatetimeIndex(['2016-01-01', '2016-02-01', '2016-03-01'], dtype='datetime64[ns]', freq='MS')

# convert to PeriodIndex
In [396]: dti = pd.date_range("2011-01-01", freq="ME", periods=3)

In [397]: dti
Out[397]: DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31'], dtype='datetime64[us]', freq='ME')

In [398]: dti.astype("period[M]")
Out[398]: PeriodIndex(['2011-01', '2011-02', '2011-03'], dtype='period[M]')

PeriodIndex 字符串部分索引#

PeriodIndex 现在支持非单调索引的字符串部分切片。

您可以在与 `PeriodIndex` 相同的模式下,将日期和字符串传递给带有 `PeriodIndex` 的 `Series` 和 `DataFrame`,就像对待 `DatetimeIndex` 一样。有关详细信息,请参阅 DatetimeIndex 字符串部分索引

In [399]: ps["2011-01"]
Out[399]: np.float64(-2.9169013294054507)

In [400]: ps[datetime.datetime(2011, 12, 25):]
Out[400]: 
2011-12    2.261385
2012-01   -0.329583
Freq: M, dtype: float64

In [401]: ps["10/31/2011":"12/31/2011"]
Out[401]: 
2011-10    0.056780
2011-11    0.197035
2011-12    2.261385
Freq: M, dtype: float64

传递一个表示低于 `PeriodIndex` 的频率的字符串会返回部分切片的数据。

In [402]: ps["2011"]
Out[402]: 
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 [403]: dfp = pd.DataFrame(
   .....:     np.random.randn(600, 1),
   .....:     columns=["A"],
   .....:     index=pd.period_range("2013-01-01 9:00", periods=600, freq="min"),
   .....: )
   .....: 

In [404]: dfp
Out[404]: 
                         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 [405]: dfp.loc["2013-01-01 10h"]
Out[405]: 
                         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 [406]: dfp["2013-01-01 10h":"2013-01-01 11h"]
Out[406]: 
                         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` 方法进行转换。让我们从 2011 财政年度开始,该年度结束于 12 月。

In [407]: p = pd.Period("2011", freq="Y-DEC")

In [408]: p
Out[408]: Period('2011', 'Y-DEC')

我们可以将其转换为月度频率。使用 `how` 参数,我们可以指定返回开始月份还是结束月份。

In [409]: p.asfreq("M", how="start")
Out[409]: Period('2011-01', 'M')

In [410]: p.asfreq("M", how="end")
Out[410]: Period('2011-12', 'M')

提供了缩写 ‘s’ 和 ‘e’ 以方便使用。

In [411]: p.asfreq("M", "s")
Out[411]: Period('2011-01', 'M')

In [412]: p.asfreq("M", "e")
Out[412]: Period('2011-12', 'M')

转换为“超级周期”(例如,年度频率是季度频率的超级周期)会自动返回包含输入周期的超级周期。

In [413]: p = pd.Period("2011-12", freq="M")

In [414]: p.asfreq("Y-NOV")
Out[414]: Period('2012', 'Y-NOV')

请注意,由于我们转换为了以 11 月结束的年度频率,2011 年 12 月的月度周期实际上位于 2012 年 Y-NOV 周期。

带有锚定频率的周期转换对于处理经济、商业和其他领域中常见的各种季度数据特别有用。许多组织根据其财政年度开始和结束的月份来定义季度。因此,2011 年的第一季度可能始于 2010 年或 2011 年的几个月后。通过锚定频率,pandas 适用于所有季度频率 Q-JANQ-DEC

`Q-DEC` 定义了常规日历季度。

In [415]: p = pd.Period("2012Q1", freq="Q-DEC")

In [416]: p.asfreq("D", "s")
Out[416]: Period('2012-01-01', 'D')

In [417]: p.asfreq("D", "e")
Out[417]: Period('2012-03-31', 'D')

`Q-MAR` 定义了财年结束于三月。

In [418]: p = pd.Period("2011Q4", freq="Q-MAR")

In [419]: p.asfreq("D", "s")
Out[419]: Period('2011-01-01', 'D')

In [420]: p.asfreq("D", "e")
Out[420]: Period('2011-03-31', 'D')

表示之间的转换#

可以使用 `to_period` 将带时间戳的数据转换为 `PeriodIndex` 索引的数据,反之亦然,使用 `to_timestamp`。

In [421]: rng = pd.date_range("1/1/2012", periods=5, freq="ME")

In [422]: ts = pd.Series(np.random.randn(len(rng)), index=rng)

In [423]: ts
Out[423]: 
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 [424]: ps = ts.to_period()

In [425]: ps
Out[425]: 
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 [426]: ps.to_timestamp()
Out[426]: 
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 [427]: ps.to_timestamp("D", how="s")
Out[427]: 
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 [428]: prng = pd.period_range("1990Q1", "2000Q4", freq="Q-NOV")

In [429]: ts = pd.Series(np.random.randn(len(prng)), prng)

In [430]: ts.index = (prng.asfreq("M", "e") + 1).asfreq("h", "s") + 9

In [431]: ts.head()
Out[431]: 
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` 和/或 `Periods` 的 `Series` 来进行计算。

In [432]: span = pd.period_range("1215-01-01", "1381-01-01", freq="D")

In [433]: span
Out[433]: 
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 [434]: s = pd.Series([20121231, 20141130, 99991231])

In [435]: s
Out[435]: 
0    20121231
1    20141130
2    99991231
dtype: int64

In [436]: def conv(x):
   .....:     return pd.Period(year=x // 10000, month=x // 100 % 100, day=x % 100, freq="D")
   .....: 

In [437]: s.apply(conv)
Out[437]: 
0    2012-12-31
1    2014-11-30
2    9999-12-31
dtype: period[D]

In [438]: s.apply(conv)[2]
Out[438]: Period('9999-12-31', 'D')

这些可以轻松地转换为 `PeriodIndex`。

In [439]: span = pd.PeriodIndex(s.apply(conv))

In [440]: span
Out[440]: PeriodIndex(['2012-12-31', '2014-11-30', '9999-12-31'], dtype='period[D]')

时区处理#

pandas 使用 `zoneinfo`、`pytz` 和 `dateutil` 库或标准库中的 `datetime.timezone` 对象,提供了丰富的功能来处理不同时区的时间戳。

处理时区#

默认情况下,pandas 对象是时区不感知的。

In [441]: rng = pd.date_range("3/6/2012 00:00", periods=15, freq="D")

In [442]: rng.tz is None
Out[442]: True

要将这些日期本地化到特定时区(为朴素日期分配特定时区),您可以使用 `tz_localize` 方法或在 `date_range()`、`Timestamp` 或 `DatetimeIndex 中使用 `tz` 关键字参数。您可以传递 `zoneinfo`、`pytz` 或 `dateutil` 时区对象,或者 Olson 时区数据库字符串。Olson 时区字符串默认返回 `pytz` 时区对象。要返回 `dateutil` 时区对象,请在字符串前加上 `dateutil/`。

  • 对于 `zoneinfo`,可通过 `zoneinfo.available_timezones()` 获取可用时区列表。

  • 在 `pytz` 中,您可以使用 `pytz.all_timezones` 找到常见(和不常见)时区的列表。

  • `dateutil` 使用操作系统的时区,因此没有固定的列表。对于常见时区,名称与 `pytz` 和 `zoneinfo` 相同。

In [443]: import dateutil

# pytz
In [444]: rng_pytz = pd.date_range("3/6/2012 00:00", periods=3, freq="D", tz="Europe/London")

In [445]: rng_pytz.tz
Out[445]: zoneinfo.ZoneInfo(key='Europe/London')

# dateutil
In [446]: rng_dateutil = pd.date_range("3/6/2012 00:00", periods=3, freq="D")

In [447]: rng_dateutil = rng_dateutil.tz_localize("dateutil/Europe/London")

In [448]: rng_dateutil.tz
Out[448]: tzfile('/usr/share/zoneinfo/Europe/London')

# dateutil - utc special case
In [449]: rng_utc = pd.date_range(
   .....:     "3/6/2012 00:00",
   .....:     periods=3,
   .....:     freq="D",
   .....:     tz=dateutil.tz.tzutc(),
   .....: )
   .....: 

In [450]: rng_utc.tz
Out[450]: tzutc()
# datetime.timezone
In [451]: rng_utc = pd.date_range(
   .....:     "3/6/2012 00:00",
   .....:     periods=3,
   .....:     freq="D",
   .....:     tz=datetime.timezone.utc,
   .....: )
   .....: 

In [452]: rng_utc.tz
Out[452]: datetime.timezone.utc

请注意,`UTC` 时区在 `dateutil` 中是一个特殊情况,应显式构造为 `dateutil.tz.tzutc` 的实例。您也可以先显式构造其他时区对象。

In [453]: import pytz

# pytz
In [454]: tz_pytz = pytz.timezone("Europe/London")

In [455]: rng_pytz = pd.date_range("3/6/2012 00:00", periods=3, freq="D")

In [456]: rng_pytz = rng_pytz.tz_localize(tz_pytz)

In [457]: rng_pytz.tz == tz_pytz
Out[457]: True

# dateutil
In [458]: tz_dateutil = dateutil.tz.gettz("Europe/London")

In [459]: rng_dateutil = pd.date_range("3/6/2012 00:00", periods=3, freq="D", tz=tz_dateutil)

In [460]: rng_dateutil.tz == tz_dateutil
Out[460]: True

要将时区感知 pandas 对象从一个时区转换为另一个时区,您可以使用 `tz_convert` 方法。

In [461]: rng_pytz.tz_convert("US/Eastern")
Out[461]: 
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[us, US/Eastern]', freq=None)

注意

使用 `pytz` 时区时,`DatetimeIndex` 将构造一个与 `Timestamp` 不同的时区对象,即使对于相同的时区输入。`DatetimeIndex` 可以包含一个 `Timestamp 对象集合,这些对象可能具有不同的 UTC 偏移量,并且无法被一个 `pytz` 时区实例简洁地表示,而一个 `Timestamp 代表一个特定 UTC 偏移量的时刻。

In [462]: dti = pd.date_range("2019-01-01", periods=3, freq="D", tz="US/Pacific")

In [463]: dti.tz
Out[463]: zoneinfo.ZoneInfo(key='US/Pacific')

In [464]: ts = pd.Timestamp("2019-01-01", tz="US/Pacific")

In [465]: ts.tz
Out[465]: zoneinfo.ZoneInfo(key='US/Pacific')

警告

请注意库之间的转换。对于某些时区,`pytz` 和 `dateutil` 对该时区的定义不同。这对于非标准时区比对于“标准”时区(如 `US/Eastern`)来说是更严重的问题。

警告

请注意,不同版本时区库之间的时区定义可能不被视为相等。这在处理使用一个版本存储并使用不同版本操作的数据时可能会导致问题。有关如何处理这种情况,请参阅 此处

警告

对于 `pytz` 时区,直接将时区对象传递给 `datetime.datetime` 构造函数(例如,`datetime.datetime(2011, 1, 1, tzinfo=pytz.timezone('US/Eastern'))`)是不正确的。相反,需要使用 `pytz` 时区对象上的 `localize` 方法来本地化日期时间。

警告

请注意,对于未来的日期,时区库无法保证时区(和 UTC)之间的正确转换,因为时区与 UTC 的偏移量可能会因各自政府的决定而改变。

警告

如果您使用 `pytz` 处理 2038-01-18 之后到期的日期,由于底层库因 2038 年问题而存在当前缺陷,因此将不会应用时区感知日期的夏令时 (DST) 调整。如果底层库得到修复,DST 转换将得到应用。

例如,对于两个处于英国夏令时(因此正常情况下为 GMT+1)的日期,以下两个断言都为真。

In [466]: import pytz

In [467]: d_2037 = "2037-03-31T010101"

In [468]: d_2038 = "2038-03-31T010101"

In [469]: DST = pytz.timezone("Europe/London")

In [470]: assert pd.Timestamp(d_2037, tz=DST) != pd.Timestamp(d_2037, tz="GMT")

In [471]: assert pd.Timestamp(d_2038, tz=DST) == pd.Timestamp(d_2038, tz="GMT")

在底层,所有时间戳都以 UTC 存储。来自时区感知 `DatetimeIndex 或 `Timestamp 的值将根据其所在时区进行本地化(例如,日期、小时、分钟等)。但是,具有相同 UTC 时间戳的值即使在不同时区,仍被视为相等。

In [472]: rng_eastern = rng_utc.tz_convert("US/Eastern")

In [473]: rng_berlin = rng_utc.tz_convert("Europe/Berlin")

In [474]: rng_eastern[2]
Out[474]: Timestamp('2012-03-07 19:00:00-0500', tz='US/Eastern')

In [475]: rng_berlin[2]
Out[475]: Timestamp('2012-03-08 01:00:00+0100', tz='Europe/Berlin')

In [476]: rng_eastern[2] == rng_berlin[2]
Out[476]: True

不同时区的 `Series 之间的操作将产生 UTC `Series,将数据对齐到 UTC 时间戳。

In [477]: ts_utc = pd.Series(range(3), pd.date_range("20130101", periods=3, tz="UTC"))

In [478]: eastern = ts_utc.tz_convert("US/Eastern")

In [479]: berlin = ts_utc.tz_convert("Europe/Berlin")

In [480]: result = eastern + berlin

In [481]: result
Out[481]: 
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
dtype: int64

In [482]: result.index
Out[482]: 
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[us, UTC]', freq=None)

要删除时区信息,请使用 `tz_localize(None)` 或 `tz_convert(None)`。`tz_localize(None)` 将删除时区,并返回本地时间表示。`tz_convert(None)` 将在转换为 UTC 时间后删除时区。

In [483]: didx = pd.date_range(start="2014-08-01 09:00", freq="h", periods=3, tz="US/Eastern")

In [484]: didx
Out[484]: 
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[us, US/Eastern]', freq='h')

In [485]: didx.tz_localize(None)
Out[485]: 
DatetimeIndex(['2014-08-01 09:00:00', '2014-08-01 10:00:00',
               '2014-08-01 11:00:00'],
              dtype='datetime64[us]', freq=None)

In [486]: didx.tz_convert(None)
Out[486]: 
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
               '2014-08-01 15:00:00'],
              dtype='datetime64[us]', freq='h')

# tz_convert(None) is identical to tz_convert('UTC').tz_localize(None)
In [487]: didx.tz_convert("UTC").tz_localize(None)
Out[487]: 
DatetimeIndex(['2014-08-01 13:00:00', '2014-08-01 14:00:00',
               '2014-08-01 15:00:00'],
              dtype='datetime64[us]', freq=None)

Fold#

对于模糊时间,pandas 支持显式指定关键字only `fold` 参数。由于夏令时,当从夏令时转换为冬令时时,一个时钟时间可能会出现两次;fold 描述了该日期时间对应于时钟第一次(0)或第二次(1)击中模糊时间。Fold 仅支持从朴素的 `datetime.datetime`(请参阅 datetime 文档获取详情)或从 `Timestamp 或从组件构造(见下文)时。仅支持 `dateutil` 时区(请参阅 dateutil 文档了解处理模糊日期时间的 `dateutil` 方法),因为 `pytz` 时区不支持 fold(请参阅 pytz 文档了解 `pytz` 如何处理模糊日期时间)。要使用 `pytz` 本地化模糊日期时间,请使用 `Timestamp.tz_localize()`。通常,如果您需要直接控制如何处理模糊日期时间,我们建议使用 `Timestamp.tz_localize()`。

In [488]: pd.Timestamp(
   .....:     datetime.datetime(2019, 10, 27, 1, 30, 0, 0),
   .....:     tz="dateutil/Europe/London",
   .....:     fold=0,
   .....: )
   .....: 
Out[488]: Timestamp('2019-10-27 01:30:00+0100', tz='dateutil//usr/share/zoneinfo/Europe/London')

In [489]: pd.Timestamp(
   .....:     year=2019,
   .....:     month=10,
   .....:     day=27,
   .....:     hour=1,
   .....:     minute=30,
   .....:     tz="dateutil/Europe/London",
   .....:     fold=1,
   .....: )
   .....: 
Out[489]: Timestamp('2019-10-27 01:30:00+0000', tz='dateutil//usr/share/zoneinfo/Europe/London')

本地化时的模糊时间#

`tz_localize` 可能无法确定时间戳的 UTC 偏移量,因为本地时区的夏令时 (DST) 会导致一天内某些时间出现两次(“时钟回拨”)。有以下选项可用:

  • `'raise'`:引发一个 `ValueError`(默认行为)。

  • `'infer'`:尝试根据时间戳的单调性确定正确的偏移量。

  • `'NaT'`:将模糊时间替换为 `NaT`。

  • `bool`:`True` 代表 DST 时间,`False` 代表非 DST 时间。支持为时间序列传递一个布尔值数组。

In [490]: 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 [491]: rng_hourly.tz_localize('US/Eastern')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[491], line 1
----> 1 rng_hourly.tz_localize('US/Eastern')

File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:543, in DatetimeIndex.tz_localize(self, tz, ambiguous, nonexistent)
    398 def tz_localize(
    399     self,
    400     tz,
    401     ambiguous: TimeAmbiguous = "raise",
    402     nonexistent: TimeNonexistent = "raise",
    403 ) -> Self:
    404     """
    405     Localize tz-naive Datetime Array/Index to tz-aware Datetime Array/Index.
    406 
   (...)    541     dtype: datetime64[ns, Europe/Warsaw]
    542     """  # noqa: E501
--> 543     arr = self._data.tz_localize(tz, ambiguous, nonexistent)
    544     return type(self)._simple_new(arr, name=self.name)

File ~/work/pandas/pandas/pandas/core/arrays/_mixins.py:83, in ravel_compat.<locals>.method(self, *args, **kwargs)
     80 @wraps(meth)
     81 def method(self, *args, **kwargs):
     82     if self.ndim == 1:
---> 83         return meth(self, *args, **kwargs)
     85     flags = self._ndarray.flags
     86     flat = self.ravel("K")

File ~/work/pandas/pandas/pandas/core/arrays/datetimes.py:1107, in DatetimeArray.tz_localize(self, tz, ambiguous, nonexistent)
   1104     tz = timezones.maybe_get_tz(tz)
   1105     # Convert to UTC
-> 1107     new_dates = tzconversion.tz_localize_to_utc(
   1108         self.asi8,
   1109         tz,
   1110         ambiguous=ambiguous,
   1111         nonexistent=nonexistent,
   1112         creso=self._creso,
   1113     )
   1114 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
   1115 dtype = tz_to_dtype(tz, unit=self.unit)

File ~/work/pandas/pandas/pandas/_libs/tslibs/tzconversion.pyx:370, in pandas._libs.tslibs.tzconversion.tz_localize_to_utc()

ValueError: Cannot infer dst time from 2011-11-06 01:00:00, try using the 'ambiguous' argument

通过指定以下内容来处理这些模糊时间。

In [492]: rng_hourly.tz_localize("US/Eastern", ambiguous="infer")
Out[492]: 
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[us, US/Eastern]', freq=None)

In [493]: rng_hourly.tz_localize("US/Eastern", ambiguous="NaT")
Out[493]: 
DatetimeIndex(['2011-11-06 00:00:00-04:00', 'NaT', 'NaT',
               '2011-11-06 02:00:00-05:00'],
              dtype='datetime64[us, US/Eastern]', freq=None)

In [494]: rng_hourly.tz_localize("US/Eastern", ambiguous=[True, True, False, False])
Out[494]: 
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[us, US/Eastern]', freq=None)

本地化时的不存在时间#

DST 转换也可能将本地时间提前 1 小时,从而创建不存在的本地时间(“时钟前跳”)。本地化具有不存在时间的时序列的行为可以通过 `nonexistent` 参数进行控制。有以下选项可用:

  • `'raise'`:引发一个 `ValueError`(默认行为)。

  • `'NaT'`:将不存在的时间替换为 `NaT`。

  • `'shift_forward'`:将不存在的时间向前移动到最近的实际时间。

  • `'shift_backward'`:将不存在的时间向后移动到最近的实际时间。

  • timedelta 对象:将不存在的时间按 timedelta 持续时间进行偏移。

In [495]: dti = pd.date_range(start="2015-03-29 02:30:00", periods=3, freq="h")

# 2:30 is a nonexistent time

默认情况下,对不存在时间的本地化会引发错误。

In [496]: dti.tz_localize('Europe/Warsaw')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[496], line 1
----> 1 dti.tz_localize('Europe/Warsaw')

File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:543, in DatetimeIndex.tz_localize(self, tz, ambiguous, nonexistent)
    398 def tz_localize(
    399     self,
    400     tz,
    401     ambiguous: TimeAmbiguous = "raise",
    402     nonexistent: TimeNonexistent = "raise",
    403 ) -> Self:
    404     """
    405     Localize tz-naive Datetime Array/Index to tz-aware Datetime Array/Index.
    406 
   (...)    541     dtype: datetime64[ns, Europe/Warsaw]
    542     """  # noqa: E501
--> 543     arr = self._data.tz_localize(tz, ambiguous, nonexistent)
    544     return type(self)._simple_new(arr, name=self.name)

File ~/work/pandas/pandas/pandas/core/arrays/_mixins.py:83, in ravel_compat.<locals>.method(self, *args, **kwargs)
     80 @wraps(meth)
     81 def method(self, *args, **kwargs):
     82     if self.ndim == 1:
---> 83         return meth(self, *args, **kwargs)
     85     flags = self._ndarray.flags
     86     flat = self.ravel("K")

File ~/work/pandas/pandas/pandas/core/arrays/datetimes.py:1107, in DatetimeArray.tz_localize(self, tz, ambiguous, nonexistent)
   1104     tz = timezones.maybe_get_tz(tz)
   1105     # Convert to UTC
-> 1107     new_dates = tzconversion.tz_localize_to_utc(
   1108         self.asi8,
   1109         tz,
   1110         ambiguous=ambiguous,
   1111         nonexistent=nonexistent,
   1112         creso=self._creso,
   1113     )
   1114 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
   1115 dtype = tz_to_dtype(tz, unit=self.unit)

File ~/work/pandas/pandas/pandas/_libs/tslibs/tzconversion.pyx:430, in pandas._libs.tslibs.tzconversion.tz_localize_to_utc()

ValueError: 2015-03-29 02:30:00 is a nonexistent time due to daylight savings time. Try using the 'nonexistent' argument.

将不存在的时间转换为 `NaT` 或移动时间。

In [497]: dti
Out[497]: 
DatetimeIndex(['2015-03-29 02:30:00', '2015-03-29 03:30:00',
               '2015-03-29 04:30:00'],
              dtype='datetime64[us]', freq='h')

In [498]: dti.tz_localize("Europe/Warsaw", nonexistent="shift_forward")
Out[498]: 
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[us, Europe/Warsaw]', freq=None)

In [499]: dti.tz_localize("Europe/Warsaw", nonexistent="shift_backward")
Out[499]: 
DatetimeIndex(['2015-03-29 01:59:59.999999+01:00',
                      '2015-03-29 03:30:00+02:00',
                      '2015-03-29 04:30:00+02:00'],
              dtype='datetime64[us, Europe/Warsaw]', freq=None)

In [500]: dti.tz_localize("Europe/Warsaw", nonexistent=pd.Timedelta(1, unit="h"))
Out[500]: 
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[us, Europe/Warsaw]', freq=None)

In [501]: dti.tz_localize("Europe/Warsaw", nonexistent="NaT")
Out[501]: 
DatetimeIndex(['NaT', '2015-03-29 03:30:00+02:00',
               '2015-03-29 04:30:00+02:00'],
              dtype='datetime64[us, Europe/Warsaw]', freq=None)

时区 Series 操作#

具有时间区域**朴素**值的 `Series 由 `datetime64[ns]` dtype 表示。

In [502]: s_naive = pd.Series(pd.date_range("20130101", periods=3))

In [503]: s_naive
Out[503]: 
0   2013-01-01
1   2013-01-02
2   2013-01-03
dtype: datetime64[us]

具有时间区域**感知**值的 `Series 由 `datetime64[ns, tz]` dtype 表示,其中 `tz` 是时区。

In [504]: s_aware = pd.Series(pd.date_range("20130101", periods=3, tz="US/Eastern"))

In [505]: s_aware
Out[505]: 
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[us, US/Eastern]

这两个 `Series 的时区信息可以通过 `.dt` 访问器进行操作,请参阅 dt 访问器部分

例如,本地化并将朴素时间戳转换为时区感知时间戳。

In [506]: s_naive.dt.tz_localize("UTC").dt.tz_convert("US/Eastern")
Out[506]: 
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[us, US/Eastern]

时区信息也可以使用 astype 方法进行操作。此方法可以在不同的时区感知 dtype 之间进行转换。

# convert to a new time zone
In [507]: s_aware.astype("datetime64[ns, CET]")
Out[507]: 
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 [508]: s_naive.to_numpy()
Out[508]: 
array(['2013-01-01T00:00:00.000000', '2013-01-02T00:00:00.000000',
       '2013-01-03T00:00:00.000000'], dtype='datetime64[us]')

In [509]: s_aware.to_numpy()
Out[509]: 
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 [510]: pd.Series(s_aware.to_numpy())
Out[510]: 
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[us, US/Eastern]

但是,如果您想要一个实际的 NumPy datetime64[ns] 数组(值已转换为 UTC),而不是对象数组,则可以指定 dtype 参数。

In [511]: s_aware.to_numpy(dtype="datetime64[ns]")
Out[511]: 
array(['2013-01-01T05:00:00.000000000', '2013-01-02T05:00:00.000000000',
       '2013-01-03T05:00:00.000000000'], dtype='datetime64[ns]')