Pandas 2 使用指南:时间序列/日期功能


pandas包含了广泛的功能和特性,用于处理各个领域的时间序列数据。使用NumPy的 datetime64timedelta64数据类型,pandas整合了许多其他Python库(如 scikits.timeseries)的功能,并为操作时间序列数据提供了大量的新功能。

例如,pandas支持:

从各种来源和格式解析时间序列信息

import datetime

dti = pd.to_datetime(
    ["1/1/2018", np.datetime64("2018-01-01"), datetime.datetime(2018, 1, 1)]
)


dti
Out[3]: DatetimeIndex(['2018-01-01', '2018-01-01', '2018-01-01'], dtype='datetime64[ns]', freq=None)

生成固定频率的日期和时间序列

dti = pd.date_range("2018-01-01", periods=3, freq="h")

dti
Out[5]: 
DatetimeIndex(['2018-01-01 00:00:00', '2018-01-01 01:00:00',
               '2018-01-01 02:00:00'],
              dtype='datetime64[ns]', freq='h')

操作和转换带有时区信息的日期时间

dti = dti.tz_localize("UTC")

dti
Out[7]: 
DatetimeIndex(['2018-01-01 00:00:00+00:00', '2018-01-01 01:00:00+00:00',
               '2018-01-01 02:00:00+00:00'],
              dtype='datetime64[ns, UTC]', freq='h')

dti.tz_convert("US/Pacific")
Out[8]: 
DatetimeIndex(['2017-12-31 16:00:00-08:00', '2017-12-31 17:00:00-08:00',
               '2017-12-31 18:00:00-08:00'],
              dtype='datetime64[ns, US/Pacific]', freq='h')

对时间序列进行重新采样或转换为特定频率

idx = pd.date_range("2018-01-01", periods=5, freq="h")

ts = pd.Series(range(len(idx)), index=idx)

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

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

使用绝对或相对时间增量进行日期和时间运算

friday = pd.Timestamp("2018-01-05")

friday.day_name()
Out[14]: 'Friday'

# 加1天
saturday = friday + pd.Timedelta("1 day")

saturday.day_name()
Out[16]: 'Saturday'

# 加1个工作日(星期五 --> 星期一)
monday = friday + pd.offsets.BDay()

monday.day_name()
Out[18]: 'Monday'

pandas提供了一套相对紧凑和自包含的工具,用于执行上述任务和更多任务。

概述

pandas涵盖了4个常见的与时间相关的概念:

  1. 日期时间:具有时区支持的特定日期和时间。类似于标准库中的datetime.datetime
  2. 时间增量:绝对的时间持续。类似于标准库中的datetime.timedelta
  3. 时间跨度:由时间点及其关联频率定义的时间跨度。
  4. 日期偏移:遵循日历算术的相对时间持续。类似于dateutil包中的dateutil.relativedelta.relativedelta
概念标量类数组类pandas数据类型主要创建方法
日期时间TimestampDatetimeIndexdatetime64[ns]datetime64[ns, tz]to_datetimedate_range
时间增量TimedeltaTimedeltaIndextimedelta64[ns]to_timedeltatimedelta_range
时间跨度PeriodPeriodIndexperiod[freq]Periodperiod_range
日期偏移DateOffsetNoneNoneDateOffset

对于时间序列数据,将时间组件表示为SeriesDataFrame的索引是常见的做法,以便可以根据时间元素进行操作。

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也可以直接支持时间组件作为数据本身。

pd.Series(pd.date_range("2000", freq="D", periods=3))
Out[20]: 
0   2000-01-01
1   2000-01-02
2   2000-01-03
dtype: datetime64[ns]

当将datetimetimedeltaPeriod数据传递给这些构造函数时,SeriesDataFrame具有扩展的数据类型支持和功能。但是,DateOffset数据将被存储为object数据。

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]

pd.Series([pd.DateOffset(1), pd.DateOffset(2)])
Out[22]: 
0         <DateOffset>
1    <2 * DateOffsets>
dtype: object

pd.Series(pd.date_range("1/1/2011", freq="ME", periods=3))
Out[23]: 
0   2011-01-31
1   2011-02-28
2   2011-03-31
dtype: datetime64[ns]

最后,pandas将空的日期时间、时间增量和时间跨度表示为NaT,这对于表示缺失或空的日期值非常有用,并且与np.nan在浮点数据中的行为类似。

pd.Timestamp(pd.NaT)
Out[24]: NaT

pd.Timedelta(pd.NaT)
Out[25]: NaT

pd.Period(pd.NaT)
Out[26]: NaT

# 等式的行为类似于np.nan
pd.NaT == pd.NaT
Out[27]: False

时间戳 vs. 时间跨度

时间戳数据是与时间点相关联的最基本类型的时间序列数据。对于pandas对象,它意味着使用时间点。

import datetime

pd.Timestamp(datetime.datetime(2012, 5, 1))
Out[29]: Timestamp('2012-05-01 00:00:00')

pd.Timestamp("2012-05-01")
Out[30]: Timestamp('2012-05-01 00:00:00')

pd.Timestamp(2012, 5, 1)
Out[31]: Timestamp('2012-05-01 00:00:00')

然而,在许多情况下,将变量与时间跨度关联起来更为自然。Period表示的时间跨度可以明确指定,也可以从日期时间字符串格式中推断出来。

例如:

pd.Period("2011-01")
Out[32]: Period('2011-01', 'M')

pd.Period("2012-05", freq="D")
Out[33]: Period('2012-05-01', 'D')

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

dates = [
    pd.Timestamp("2012-05-01"),
    pd.Timestamp("2012-05-02"),
    pd.Timestamp("2012-05-03"),
]


ts = pd.Series(np.random.randn(3), dates)

type(ts.index)
Out[36]: pandas.core.indexes.datetimes.DatetimeIndex

ts.index
Out[37]: DatetimeIndex(['2012-05-01', '2012-05-02', '2012-05-03'], dtype='datetime64[ns]', freq=None)

ts
Out[38]: 
2012-05-01    0.469112
2012-05-02   -0.282863
2012-05-03   -1.509059
dtype: float64

periods = [pd.Period("2012-01"), pd.Period("2012-02"), pd.Period("2012-03")]

ts = pd.Series(np.random.randn(3), periods)

type(ts.index)
Out[41]: pandas.core.indexes.period.PeriodIndex

ts.index
Out[42]: PeriodIndex(['2012-01', '2012-02', '2012-03'], dtype='period[M]')

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

pd.to_datetime(pd.Series(["Jul 31, 2009", "Jan 10, 2010", None]))
Out[44]: 
0   2009-07-31
1   2010-01-10
2          NaT
dtype: datetime64[ns]

pd.to_datetime(["2005/11/23", "2010/12/31"])
Out[45]: DatetimeIndex(['2005-11-23', '2010-12-31'], dtype='datetime64[ns]', freq=None)

如果使用以日期为首的日期(即欧洲风格),可以传递dayfirst标志:

pd.to_datetime(["04-01-2012 10:00"], dayfirst=True)
Out[46]: DatetimeIndex(['2012-01-04 10:00:00'], dtype='datetime64[ns]', freq=None)

pd.to_datetime(["04-14-2012 10:00"], dayfirst=True)
Out[47]: DatetimeIndex(['2012-04-14 10:00:00'], dtype='datetime64[ns]', freq=None)

警告

您可以看到上面的示例中,dayfirst不是严格的。如果日期不能以日期为首进行解析,它将被解析为dayfirstFalse,并且还会引发警告。

如果将单个字符串传递给to_datetime,它将返回单个TimestampTimestamp也可以接受字符串输入,但它不接受像dayfirstformat这样的字符串解析选项,因此如果需要这些选项,请使用to_datetime

pd.to_datetime("2010/11/12")
Out[48]: Timestamp('2010-11-12 00:00:00')

pd.Timestamp("2010/11/12")
Out[49]: Timestamp('2010-11-12 00:00:00')

你也可以直接使用 DatetimeIndex 构造函数:

pd.DatetimeIndex(["2018-01-01", "2018-01-03", "2018-01-05"])
Out[50]: DatetimeIndex(['2018-01-01', '2018-01-03', '2018-01-05'], dtype='datetime64[ns]', freq=None)

可以传入字符串 ‘infer’ 来设置索引的频率为创建时推断的频率:

pd.DatetimeIndex(["2018-01-01", "2018-01-03", "2018-01-05"], freq="infer")
Out[51]: DatetimeIndex(['2018-01-01', '2018-01-03', '2018-01-05'], dtype='datetime64[ns]', freq='2D')

提供 format 参数

除了必需的日期时间字符串外,还可以传入一个 format 参数来确保特定的解析方式。这样做还可以大大加快转换速度。

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

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

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

从多个 DataFrame 列组装日期时间

还可以将整数或字符串列的 DataFrame 传递给 to_datetime,以组装为 TimestampsSeries

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


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

只需传递需要组装的列。

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

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

  • 必需的:yearmonthday
  • 可选的:hourminutesecondmillisecondmicrosecondnanosecond

无效数据

默认行为 errors='raise' 是在无法解析时引发错误:

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

File ~/work/pandas/pandas/pandas/core/tools/datetimes.py:1099, in to_datetime(arg, errors, dayfirst, yearfirst, utc, format, exact, unit, infer_datetime_format, origin, cache)
   1097         result = _convert_and_box_cache(argc, cache_array)
   1098     else:
-> 1099         result = convert_listlike(argc, format)
   1100 else:
   1101     result = convert_listlike(np.array([arg]), format)[0]

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

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

File strptime.pyx:501, in pandas._libs.tslibs.strptime.array_strptime()

File strptime.pyx:451, in pandas._libs.tslibs.strptime.array_strptime()

File strptime.pyx:583, in pandas._libs.tslibs.strptime._parse_with_format()

ValueError: time data "asd" doesn't match format "%Y/%m/%d", at position 1. You might want to try:
    - passing `format` if your strings have a consistent format;
    - passing `format='ISO8601'` if your strings are all ISO8601 but not necessarily in exactly the same format;
    - passing `format='mixed'`, and the format will be inferred for each element individually. You might want to use `dayfirst` alongside this.

传入 errors='coerce' 将无法解析的数据转换为 NaT(不是时间):

pd.to_datetime(["2009/07/31", "asd"], errors="coerce")
Out[58]: DatetimeIndex(['2009-07-31', 'NaT'], dtype='datetime64[ns]', freq=None)

Epoch 时间戳

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

pd.to_datetime(
    [1349720105, 1349806505, 1349892905, 1349979305, 1350065705], unit="s"
)

Out[59]: 
DatetimeIndex(['2012-10-08 18:15:05', '2012-10-09 18:15:05',
               '2012-10-10 18:15:05', '2012-10-11 18:15:05',
               '2012-10-12 18:15:05'],
              dtype='datetime64[ns]', freq=None)

pd.to_datetime(
    [1349720105100, 1349720105200, 1349720105300, 1349720105400, 1349720105500],
    unit="ms",
)

Out[60]: 
DatetimeIndex(['2012-10-08 18:15:05.100000', '2012-10-08 18:15:05.200000',
               '2012-10-08 18:15:05.300000', '2012-10-08 18:15:05.400000',
               '2012-10-08 18:15:05.500000'],
              dtype='datetime64[ns]', freq=None)

注意

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

使用指定了 tz 参数的 epoch 时间戳构造 TimestampDatetimeIndex 会引发 ValueError。如果你有另一个时区中的墙上时间的 epoch 时间,可以将 epoch 时间读取为时区无关的时间戳,然后将其本地化到适当的时区:

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

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

注意

Epoch 时间戳将四舍五入到最近的纳秒。

警告

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

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

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

另请参阅

使用 origin 参数

从时间戳到 epoch

要反转上面的操作,即从 Timestamp 转换为 ‘unix’ epoch:

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

stamps
Out[66]: 
DatetimeIndex(['2012-10-08 18:15:05', '2012-10-09 18:15:05',
               '2012-10-10 18:15:05', '2012-10-11 18:15:05'],
              dtype='datetime64[ns]', freq='D')

我们减去 epoch(1970 年 1 月 1 日 UTC 的午夜),然后进行地板除以“unit”(1 秒)。

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

使用 origin 参数

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

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

默认设置为 origin='unix',默认为 1970-01-01 00:00:00。通常称为 ‘unix epoch’ 或 POSIX 时间。

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

生成时间戳范围

要生成带有时间戳的索引,可以使用 DatetimeIndexIndex 构造函数,并传入一个日期时间对象的列表:

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


# 注意频率信息
index = pd.DatetimeIndex(dates)

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

# 自动转换为 DatetimeIndex
index = pd.Index(dates)

index
Out[74]: DatetimeIndex(['2012-05-01', '2012-05-02', '2012-05-03'], dtype='datetime64[ns]', freq=None)
start = datetime.datetime(2011, 1, 1)

end = datetime.datetime(2012, 1, 1)

index = pd.date_range(start, end)

index
Out[78]: 
DatetimeIndex(['2011-01-01', '2011-01-02', '2011-01-03', '2011-01-04',
               '2011-01-05', '2011-01-06', '2011-01-07', '2011-01-08',
               '2011-01-09', '2011-01-10',
               ...
               '2011-12-23', '2011-12-24', '2011-12-25', '2011-12-26',
               '2011-12-27', '2011-12-28', '2011-12-29', '2011-12-30',
               '2011-12-31', '2012-01-01'],
              dtype='datetime64[ns]', length=366, freq='D')

index = pd.bdate_range(start, end)

index
Out[80]: 
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
               '2011-01-07', '2011-01-10', '2011-01-11', '2011-01-12',
               '2011-01-13', '2011-01-14',
               ...
               '2011-12-19', '2011-12-20', '2011-12-21', '2011-12-22',
               '2011-12-23', '2011-12-26', '2011-12-27', '2011-12-28',
               '2011-12-29', '2011-12-30'],
              dtype='datetime64[ns]', length=260, freq='B')

date_rangebdate_range 等便捷函数可以利用各种频率别名

pd.date_range(start, periods=1000, freq="ME")
Out[81]: 
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-30',
               '2011-05-31', '2011-06-30', '2011-07-31', '2011-08-31',
               '2011-09-30', '2011-10-31',
               ...
               '2093-07-31', '2093-08-31', '2093-09-30', '2093-10-31',
               '2093-11-30', '2093-12-31', '2094-01-31', '2094-02-28',
               '2094-03-31', '2094-04-30'],
              dtype='datetime64[ns]', length=1000, freq='ME')

pd.bdate_range(start, periods=250, freq="BQS")
Out[82]: 
DatetimeIndex(['2011-01-03', '2011-04-01', '2011-07-01', '2011-10-03',
               '2012-01-02', '2012-04-02', '2012-07-02', '2012-10-01',
               '2013-01-01', '2013-04-01',
               ...
               '2071-01-01', '2071-04-01', '2071-07-01', '2071-10-01',
               '2072-01-01', '2072-04-01', '2072-07-01', '2072-10-03',
               '2073-01-02', '2073-04-03'],
              dtype='datetime64[ns]', length=250, freq='BQS-JAN')

date_rangebdate_range 可以根据 startendperiodsfreq 等参数的不同组合生成一系列日期。起始日期和结束日期是严格包含的,因此不在指定范围内的日期将不会生成:

pd.date_range(start, end, freq="BME")
Out[83]: 
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-29',
               '2011-05-31', '2011-06-30', '2011-07-29', '2011-08-31',
               '2011-09-30', '2011-10-31', '2011-11-30', '2011-12-30'],
              dtype='datetime64[ns]', freq='BME')

pd.date_range(start, end, freq="W")
Out[84]: 
DatetimeIndex(['2011-01-02', '2011-01-09', '2011-01-16', '2011-01-23',
               '2011-01-30', '2011-02-06', '2011-02-13', '2011-02-20',
               '2011-02-27', '2011-03-06', '2011-03-13', '2011-03-20',
               '2011-03-27', '2011-04-03', '2011-04-10', '2011-04-17',
               '2011-04-24', '2011-05-01', '2011-05-08', '2011-05-15',
               '2011-05-22', '2011-05-29', '2011-06-05', '2011-06-12',
               '2011-06-19', '2011-06-26', '2011-07-03', '2011-07-10',
               '2011-07-17', '2011-07-24', '2011-07-31', '2011-08-07',
               '2011-08-14', '2011-08-21', '2011-08-28', '2011-09-04',
               '2011-09-11', '2011-09-18', '2011-09-25', '2011-10-02',
               '2011-10-09', '2011-10-16', '2011-10-23', '2011-10-30',
               '2011-11-06', '2011-11-13', '2011-11-20', '2011-11-27',
               '2011-12-04', '2011-12-11', '2011-12-18', '2011-12-25',
               '2012-01-01'],
              dtype='datetime64[ns]', freq='W-SUN')

pd.bdate_range(end=end, periods=20)
Out[85]: 
DatetimeIndex(['2011-12-05', '2011-12-06', '2011-12-07', '2011-12-08',
               '2011-12-09', '2011-12-12', '2011-12-13', '2011-12-14',
               '2011-12-15', '2011-12-16', '2011-12-19', '2011-12-20',
               '2011-12-21', '2011-12-22', '2011-12-23', '2011-12-26',
               '2011-12-27', '2011-12-28', '2011-12-29', '2011-12-30'],
              dtype='datetime64[ns]', freq='B')

pd.bdate_range(start=start, periods=20)
Out[86]: 
DatetimeIndex(['2011-01-03', '2011-01-04', '2011-01-05', '2011-01-06',
               '2011-01-07', '2011-01-10', '2011-01-11', '2011-01-12',
               '2011-01-13', '2011-01-14', '2011-01-17', '2011-01-18',
               '2011-01-19', '2011-01-20', '2011-01-21', '2011-01-24',
               '2011-01-25', '2011-01-26', '2011-01-27', '2011-01-28'],
              dtype='datetime64[ns]', freq='B')

通过指定 startendperiods 参数,可以生成从 startend 的一系列均匀间隔的日期,其中 periods 参数指定了结果 DatetimeIndex 中的元素个数:

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

pd.date_range("2018-01-01", "2018-01-05", periods=10)
Out[88]: 
DatetimeIndex(['2018-01-01 00:00:00', '2018-01-01 10:40:00',
               '2018-01-01 21:20:00', '2018-01-02 08:00:00',
               '2018-01-02 18:40:00', '2018-01-03 05:20:00',
               '2018-01-03 16:00:00', '2018-01-04 02:40:00',
               '2018-01-04 13:20:00', '2018-01-05 00:00:00'],
              dtype='datetime64[ns]', freq=None)

自定义频率范围

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

weekmask = "Mon Wed Fri"

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

pd.bdate_range(start, end, freq="C", weekmask=weekmask, holidays=holidays)
Out[91]: 
DatetimeIndex(['2011-01-03', '2011-01-07', '2011-01-10', '2011-01-12',
               '2011-01-14', '2011-01-17', '2011-01-19', '2011-01-21',
               '2011-01-24', '2011-01-26',
               ...
               '2011-12-09', '2011-12-12', '2011-12-14', '2011-12-16',
               '2011-12-19', '2011-12-21', '2011-12-23', '2011-12-26',
               '2011-12-28', '2011-12-30'],
              dtype='datetime64[ns]', length=154, freq='C')

pd.bdate_range(start, end, freq="CBMS", weekmask=weekmask)
Out[92]: 
DatetimeIndex(['2011-01-03', '2011-02-02', '2011-03-02', '2011-04-01',
               '2011-05-02', '2011-06-01', '2011-07-01', '2011-08-01',
               '2011-09-02', '2011-10-03', '2011-11-02', '2011-12-02'],
              dtype='datetime64[ns]', freq='CBMS')

参见

自定义工作日

时间戳限制

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

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

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

选择秒分辨率时,可用范围增加到 +/- 2.9e11 年。不同的分辨率可以通过 as_unit 转换为彼此。

参见

表示超出范围的时间跨度

索引

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

  • 预先计算并缓存了各种偏移量的大量日期范围,以便在生成后续日期范围时非常快速(只需抓取一个切片)。
  • 使用 pandas 对象上的 shift 方法进行快速移位。
  • 具有相同频率的重叠 DatetimeIndex 对象的联合非常快速(对于快速数据对齐很重要)。
  • 通过 yearmonth 等属性快速访问日期字段。
  • snap 和非常快速的 asof 逻辑等正则化函数。

DatetimeIndex 对象具有常规 Index 对象的所有基本功能,以及一系列高级时间序列特定方法,用于方便的频率处理。

参见

重新索引方法

注意

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

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

rng = pd.date_range(start, end, freq="BME")

ts = pd.Series(np.random.randn(len(rng)), index=rng)

ts.index
Out[97]: 
DatetimeIndex(['2011-01-31', '2011-02-28', '2011-03-31', '2011-04-29',
               '2011-05-31', '2011-06-30', '2011-07-29', '2011-08-31',
               '2011-09-30', '2011-10-31', '2011-11-30', '2011-12-30'],
              dtype='datetime64[ns]', freq='BME')

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

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

部分字符串索引

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

ts["1/31/2011"]
Out[100]: 0.11920871129693428

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

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

为了方便访问较长的时间序列,您还可以将年份或年份和月份作为字符串传入:

ts["2011"]
Out[103]: 
2011-01-31    0.119209
2011-02-28   -1.044236
2011-03-31   -0.861849
2011-04-29   -2.104569
2011-05-31   -0.494929
2011-06-30    1.071804
2011-07-29    0.721555
2011-08-31   -0.706771
2011-09-30   -1.039575
2011-10-31    0.271860
2011-11-30   -0.424972
2011-12-30    0.567020
Freq: BME, dtype: float64

ts["2011-6"]
Out[104]: 
2011-06-30    1.071804
Freq: BME, dtype: float64

这种切片方法也适用于具有DatetimeIndexDataFrame。由于部分字符串选择是一种标签切片的形式,所以端点将被包括在内。这将包括在包含日期上匹配的时间:

警告

使用getitem(例如frame[dtstring])对DataFrame行进行索引在pandas 1.2.0中已被弃用(由于不确定是索引行还是选择列)并将在将来的版本中删除。仍然支持使用.loc(例如frame.loc[dtstring])进行等效操作。

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


dft
Out[106]: 
                            A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-03-11 10:35:00 -0.747967
2013-03-11 10:36:00 -0.034523
2013-03-11 10:37:00 -0.201754
2013-03-11 10:38:00 -1.509067
2013-03-11 10:39:00 -1.693043

[100000 rows x 1 columns]

dft.loc["2013"]
Out[107]: 
                            A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-03-11 10:35:00 -0.747967
2013-03-11 10:36:00 -0.034523
2013-03-11 10:37:00 -0.201754
2013-03-11 10:38:00 -1.509067
2013-03-11 10:39:00 -1.693043

[100000 rows x 1 columns]

这从月份的第一天开始,并包括该月的最后一个日期和时间:

dft["2013-1":"2013-2"]
Out[108]: 
                            A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-02-28 23:55:00  0.850929
2013-02-28 23:56:00  0.976712
2013-02-28 23:57:00 -2.693884
2013-02-28 23:58:00 -1.575535
2013-02-28 23:59:00 -1.573517

[84960 rows x 1 columns]

这指定了一个包括该月所有时间的停止时间:

dft["2013-1":"2013-2-28"]
Out[109]: 
                            A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-02-28 23:55:00  0.850929
2013-02-28 23:56:00  0.976712
2013-02-28 23:57:00 -2.693884
2013-02-28 23:58:00 -1.575535
2013-02-28 23:59:00 -1.573517

[84960 rows x 1 columns]

这指定了一个精确的停止时间(与上述不同):

dft["2013-1":"2013-2-28 00:00:00"]
Out[110]: 
                            A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-02-27 23:56:00  1.197749
2013-02-27 23:57:00  0.720521
2013-02-27 23:58:00 -0.072718
2013-02-27 23:59:00 -0.681192
2013-02-28 00:00:00 -0.557501

[83521 rows x 1 columns]

我们在包含的端点上停止,因为它是索引的一部分:

dft["2013-1-15":"2013-1-15 12:30:00"]
Out[111]: 
                            A
2013-01-15 00:00:00 -0.984810
2013-01-15 00:01:00  0.941451
2013-01-15 00:02:00  1.559365
2013-01-15 00:03:00  1.034374
2013-01-15 00:04:00 -1.480656
...                       ...
2013-01-15 12:26:00  0.371454
2013-01-15 12:27:00 -0.930806
2013-01-15 12:28:00 -0.069177
2013-01-15 12:29:00  0.066510
2013-01-15 12:30:00 -0.003945

[751 rows x 1 columns]

DatetimeIndex部分字符串索引也适用于具有MultiIndexDataFrame

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"]]
    ),
)


dft2
Out[113]: 
                              A
2013-01-01 00:00:00 a -0.298694
                    b  0.823553
2013-01-01 12:00:00 a  0.943285
                    b -1.479399
2013-01-02 00:00:00 a -1.643342
...                         ...
2013-01-04 12:00:00 b  0.069036
2013-01-05 00:00:00 a  0.122297
                    b  1.422060
2013-01-05 12:00:00 a  0.370079
                    b  1.016331

[20 rows x 1 columns]

dft2.loc["2013-01-05"]
Out[114]: 
                              A
2013-01-05 00:00:00 a  0.122297
                    b  1.422060
2013-01-05 12:00:00 a  0.370079
                    b  1.016331

idx = pd.IndexSlice

dft2 = dft2.swaplevel(0, 1).sort_index()

dft2.loc[idx[:, "2013-01-05"], :]
Out[117]: 
                              A
a 2013-01-05 00:00:00  0.122297
  2013-01-05 12:00:00  0.370079
b 2013-01-05 00:00:00  1.422060
  2013-01-05 12:00:00  1.016331

使用字符串索引进行切片还会考虑UTC偏移。

df = pd.DataFrame([0], index=pd.DatetimeIndex(["2019-01-01"], tz="US/Pacific"))

df
Out[119]: 
                           0
2019-01-01 00:00:00-08:00  0

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

切片与精确匹配

相同的字符串作为索引参数可以根据索引的分辨率被视为切片或精确匹配。如果字符串比索引不准确,则将被视为切片,否则将被视为精确匹配。

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

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"]
    ),
)


series_minute.index.resolution
Out[122]: 'minute'

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

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

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

series_minute["2011-12-31 23:59"]
Out[124]: 1

series_minute["2011-12-31 23:59:00"]
Out[125]: 1

如果索引分辨率为秒,则具有分钟精度的时间戳字符串会返回一个Series

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"]
    ),
)


series_second.index.resolution
Out[127]: 'second'

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

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

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


dft_minute.loc["2011-12-31 23"]
Out[130]: 
                     a  b
2011-12-31 23:59:00  1  4

警告

但是,如果字符串被视为精确匹配,则DataFrame中的[]选择将按列而不是按行进行,参见Indexing Basics。例如,dft_minute['2011-12-31 23:59']将引发KeyError,因为'2012-12-31 23:59'的分辨率与索引相同,没有这样的列名:

要始终进行明确的选择,无论行是被视为切片还是单个选择,请使用.loc

dft_minute.loc["2011-12-31 23:59"]
Out[131]: 
a    1
b    4
Name: 2011-12-31 23:59:00, dtype: int64

还要注意,DatetimeIndex的分辨率不能低于天。

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


series_monthly.index.resolution
Out[133]: 'day'

series_monthly["2011-12"]  # 返回Series
Out[134]: 
2011-12-01    1
dtype: int64

精确索引

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

这些Timestampdatetime对象具有确切的小时,分钟,即使它们没有明确指定(它们为0)。

dft[datetime.datetime(2013, 1, 1): datetime.datetime(2013, 2, 28)]
Out[135]: 
                            A
2013-01-01 00:00:00  0.276232
2013-01-01 00:01:00 -1.087401
2013-01-01 00:02:00 -0.673690
2013-01-01 00:03:00  0.113648
2013-01-01 00:04:00 -1.478427
...                       ...
2013-02-27 23:56:00  1.197749
2013-02-27 23:57:00  0.720521
2013-02-27 23:58:00 -0.072718
2013-02-27 23:59:00 -0.681192
2013-02-28 00:00:00 -0.557501

[83521 rows x 1 columns]

没有默认值。

dft[
    datetime.datetime(2013, 1, 1, 10, 12, 0): datetime.datetime(
        2013, 2, 28, 10, 12, 0
    )
]

Out[136]: 
                            A
2013-01-01 10:12:00  0.565375
2013-01-01 10:13:00  0.068184
2013-01-01 10:14:00  0.788871
2013-01-01 10:15:00 -0.280343
2013-01-01 10:16:00  0.931536
...                       ...
2013-02-28 10:08:00  0.148098
2013-02-28 10:09:00 -0.388138
2013-02-28 10:10:00  0.139348
2013-02-28 10:11:00  0.085288
2013-02-28 10:12:00  0.950146

[83521 rows x 1 columns]

截断和高级索引

rng2 = pd.date_range("2011-01-01", "2012-01-01", freq="W")

ts2 = pd.Series(np.random.randn(len(rng2)), index=rng2)

ts2.truncate(before="2011-11", after="2011-12")
Out[139]: 
2011-11-06    0.437823
2011-11-13   -0.293083
2011-11-20   -0.059881
2011-11-27    1.252450
Freq: W-SUN, dtype: float64

ts2["2011-11":"2011-12"]
Out[140]: 
2011-11-06    0.437823
2011-11-13   -0.293083
2011-11-20   -0.059881
2011-11-27    1.252450
2011-12-04    0.046611
2011-12-11    0.059478
2011-12-18   -0.286539
2011-12-25    0.841669
Freq: W-SUN, dtype: float64

即使是复杂的花式索引,破坏了DatetimeIndex的频率规律,也会得到一个DatetimeIndex,尽管频率会丢失:

ts2.iloc[[0, 2, 6]].index
Out[141]: DatetimeIndex(['2011-01-02', '2011-01-16', '2011-02-13'], dtype='datetime64[ns]', freq=None)

时间/日期组件

可以从TimestampDatetimeIndex等时间戳集合中访问多个时间/日期属性。

属性描述
year年份
month月份
day
hour小时
minute分钟
second
microsecond微秒
nanosecond纳秒
date返回datetime.date(不包含时区信息)
time返回datetime.time(不包含时区信息)
timetz返回带有时区信息的本地时间datetime.time
dayofyear一年中的第几天
day_of_year一年中的第几天
weekofyear一年中的第几周
week一年中的第几周
dayofweek一周中的第几天,星期一为0,星期日为6
day_of_week一周中的第几天,星期一为0,星期日为6
weekday一周中的第几天,星期一为0,星期日为6
quarter季度,1表示1月到3月,2表示4月到6月,以此类推
days_in_month该月的天数
is_month_start逻辑值,表示是否为月初(由频率定义)
is_month_end逻辑值,表示是否为月末(由频率定义)
is_quarter_start逻辑值,表示是否为季度初(由频率定义)
is_quarter_end逻辑值,表示是否为季度末(由频率定义)
is_year_start逻辑值,表示是否为年初(由频率定义)
is_year_end逻辑值,表示是否为年末(由频率定义)
is_leap_year逻辑值,表示日期是否为闰年

此外,如果有一个包含日期时间值的Series,则可以通过.dt访问器访问这些属性,详细信息请参见.dt 访问器一节。

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

idx = pd.date_range(start="2019-12-29", freq="D", periods=4)

idx.isocalendar()
Out[143]: 
            year  week  day
2019-12-29  2019    52    7
2019-12-30  2020     1    1
2019-12-31  2020     1    2
2020-01-01  2020     1    3

idx.to_series().dt.isocalendar()
Out[144]: 
            year  week  day
2019-12-29  2019    52    7
2019-12-30  2020     1    1
2019-12-31  2020     1    2
2020-01-01  2020     1    3

DateOffset 对象

在前面的示例中,使用频率字符串(例如'D')来指定定义:

这些频率字符串映射到一个 DateOffset 对象及其子类。DateOffset 类似于 Timedelta,表示一段时间的持续时间,但遵循特定的日历持续时间规则。例如,Timedelta 的一天始终会使日期时间增加 24 小时,而 DateOffset 的一天会使日期时间增加到下一天的相同时间,无论一天是由于夏令时而表示 23、24 还是 25 小时。然而,所有小于或等于一小时的 DateOffset 子类(HourMinuteSecondMilliMicroNano)的行为类似于 Timedelta,并且遵守绝对时间。

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

# 这一天包含了夏令时的转换
ts = pd.Timestamp("2016-10-30 00:00:00", tz="Europe/Helsinki")

# 尊重绝对时间
ts + pd.Timedelta(days=1)
Out[146]: Timestamp('2016-10-30 23:00:00+0200', tz='Europe/Helsinki')

# 尊重日历时间
ts + pd.DateOffset(days=1)
Out[147]: Timestamp('2016-10-31 00:00:00+0200', tz='Europe/Helsinki')

friday = pd.Timestamp("2018-01-05")

friday.day_name()
Out[149]: 'Friday'

# 增加 2 个工作日(星期五 --> 星期二)
two_business_days = 2 * pd.offsets.BDay()

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

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

大多数 DateOffset 都有关联的频率字符串,或称为偏移别名,可以传递给 freq 关键字参数。下表列出了可用的日期偏移和关联的频率字符串:

日期偏移频率字符串描述
DateOffset通用偏移类,默认为 24 小时
BDayBusinessDay'B'工作日(工作日)
CDayCustomBusinessDay'C'自定义工作日
Week'W'一周,可选择以一周的某一天为锚点
WeekOfMonth'WOM'每月的第 x 天的第 y 周
LastWeekOfMonth'LWOM'每月的最后一周的第 x 天
MonthEnd'ME'月末
MonthBegin'MS'月初
BMonthEndBusinessMonthEnd'BME'工作月末
BMonthBeginBusinessMonthBegin'BMS'工作月初
CBMonthBeginCustomBusinessMonthBegin'CBMS'自定义的工作月开始
SemiMonthEnd'SME'15号(或其他日期)和月末
SemiMonthBegin'SMS'15号(或其他日期)和月初
QuarterEnd'QE'季度末
QuarterBegin'QS'季度初
BQuarterEnd'BQE工作季度末
BQuarterBegin'BQS'工作季度初
FY5253Quarter'REQ'零售(又称52-53周)季度
YearEnd'YE'年末
YearBegin'YS''BYS'年初
BYearEnd'BYE'工作年末
BYearBegin'BYS'工作年初
FY5253'RE'零售(又称52-53周)年
EasterNone复活节假期
BusinessHour'bh'工作小时
CustomBusinessHour'cbh'自定义的工作小时
Day'D'一天
Hour'h'一小时
Minute'min'一分钟
Second's'一秒
Milli'ms'一毫秒
Micro'us'一微秒
Nano'ns'一纳秒

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

ts = pd.Timestamp("2018-01-06 00:00:00")

ts.day_name()
Out[154]: '星期六'

# BusinessHour 的有效偏移日期是周一到周五
offset = pd.offsets.BusinessHour(start="09:00")

# 将日期调整到最接近的偏移日期(周一)
offset.rollforward(ts)
Out[156]: Timestamp('2018-01-08 09:00:00')

# 首先将日期调整到最接近的偏移日期,然后再添加小时
ts + offset
Out[157]: Timestamp('2018-01-08 10:00:00')

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

ts = pd.Timestamp("2014-01-01 09:00")

day = pd.offsets.Day()

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

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

ts = pd.Timestamp("2014-01-01 22:00")

hour = pd.offsets.Hour()

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

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

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

参数化偏移量

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

d = datetime.datetime(2008, 8, 18, 9, 0)

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

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

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

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

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

normalize 选项对于加法和减法都有效。

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

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

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

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

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

使用偏移量与 Series / DatetimeIndex

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

rng = pd.date_range("2012-01-01", "2012-01-03")

s = pd.Series(rng)

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

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

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

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

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

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

td = s - pd.Series(pd.date_range("2011-12-29", "2011-12-31"))

td
Out[185]: 
0   3 days
1   3 days
2   3 days
dtype: timedelta64[ns]

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

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

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

自定义工作日

CDayCustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可以用于创建考虑本地假期和本地周末惯例的自定义工作日日历。

以埃及为例,他们采用星期五和星期六作为周末。

weekmask_egypt = "星期日 星期一 星期二 星期三 星期四"

# 他们还庆祝国际劳动节,所以让我们为几年添加这个假期
holidays = [
    "2012-05-01",
    datetime.datetime(2013, 5, 1),
    np.datetime64("2014-05-01"),
]


bday_egypt = pd.offsets.CustomBusinessDay(
    holidays=holidays,
    weekmask=weekmask_egypt,
)


dt = datetime.datetime(2013, 4, 30)

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

让我们将日期映射到工作日名称:

dts = pd.date_range(dt, periods=5, freq=bday_egypt)

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

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

from pandas.tseries.holiday import USFederalHolidayCalendar

bday_us = pd.offsets.CustomBusinessDay(calendar=USFederalHolidayCalendar())

# MLK日前的星期五
dt = datetime.datetime(2014, 1, 17)

# MLK日后的星期二(星期一被跳过,因为那是假日)
dt + bday_us
Out[198]: Timestamp('2014-01-21 00:00:00')

可以按照通常的方式定义遵循特定假日日历的月度偏移量。

bmth_us = pd.offsets.CustomBusinessMonthBegin(calendar=USFederalHolidayCalendar())

# 跳过新年
dt = datetime.datetime(2013, 12, 17)

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

# 使用自定义偏移量定义日期索引
pd.date_range(start="20100101", end="20120101", freq=bmth_us)
Out[202]: 
DatetimeIndex(['2010-01-04', '2010-02-01', '2010-03-01', '2010-04-01',
               '2010-05-03', '2010-06-01', '2010-07-01', '2010-08-02',
               '2010-09-01', '2010-10-01', '2010-11-01', '2010-12-01',
               '2011-01-03', '2011-02-01', '2011-03-01', '2011-04-01',
               '2011-05-02', '2011-06-01', '2011-07-01', '2011-08-01',
               '2011-09-01', '2011-10-03', '2011-11-01', '2011-12-01'],
              dtype='datetime64[ns]', freq='CBMS')

注意

频率字符串’C’用于指示使用CustomBusinessDay DateOffset,重要的是要注意,由于CustomBusinessDay是一个参数化类型,CustomBusinessDay的实例可能不同,这无法从’C’频率字符串中检测出来。因此,用户需要确保在应用程序中一致使用’C’频率字符串。

工作小时

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

默认情况下,BusinessHour使用9:00 - 17:00作为工作时间。添加BusinessHour将按小时频率递增Timestamp。如果目标Timestamp不在工作时间内,则移动到下一个工作小时并递增。如果结果超过工作时间结束,则将剩余小时添加到下一个工作日。

bh = pd.offsets.BusinessHour()

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

# 2014-08-01是星期五
pd.Timestamp("2014-08-01 10:00").weekday()
Out[205]: 4

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

# 下面的示例与:pd.Timestamp('2014-08-01 09:00') + bh相同
pd.Timestamp("2014-08-01 08:00") + bh
Out[207]: Timestamp('2014-08-01 10:00:00')

# 如果结果在结束时间上,移动到下一个工作日
pd.Timestamp("2014-08-01 16:00") + bh
Out[208]: Timestamp('2014-08-04 09:00:00')

# 剩余时间添加到下一天
pd.Timestamp("2014-08-01 16:30") + bh
Out[209]: Timestamp('2014-08-04 09:30:00')

# 添加2个工作小时
pd.Timestamp("2014-08-01 10:00") + pd.offsets.BusinessHour(2)
Out[210]: Timestamp('2014-08-01 12:00:00')

# 减去3个工作小时
pd.Timestamp("2014-08-01 10:00") + pd.offsets.BusinessHour(-3)
Out[211]: Timestamp('2014-07-31 15:00:00')

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

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

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

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

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

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

start时间设置为晚于end表示午夜工作小时。在这种情况下,工作小时超过午夜并延伸到第二天。有效的工作小时通过它是否从有效的BusinessDay开始来区分。

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

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

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

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

# 尽管2014-08-02是星期六,
# 但它是有效的,因为它从08-01(星期五)开始。
pd.Timestamp("2014-08-02 04:00") + bh
Out[221]: Timestamp('2014-08-02 05:00:00')

# 尽管2014-08-04是星期一,
# 但它不在工作时间内,因为它从08-03(星期日)开始。
pd.Timestamp("2014-08-04 04:00") + bh
Out[222]: Timestamp('2014-08-04 18:00:00')

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

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

# 这将将Timestamp调整为工作小时边缘
pd.offsets.BusinessHour().rollback(pd.Timestamp("2014-08-02 15:00"))
Out[223]: Timestamp('2014-08-01 17:00:00')

pd.offsets.BusinessHour().rollforward(pd.Timestamp("2014-08-02 15:00"))
Out[224]: Timestamp('2014-08-04 09:00:00')

# 它与BusinessHour() + pd.Timestamp('2014-08-01 17:00')相同。
# 它与BusinessHour() + pd.Timestamp('2014-08-04 09:00')相同
pd.offsets.BusinessHour() + pd.Timestamp("2014-08-02 15:00")
Out[225]: Timestamp('2014-08-04 10:00:00')

# BusinessDay结果(供参考)
pd.offsets.BusinessHour().rollforward(pd.Timestamp("2014-08-02"))
Out[226]: Timestamp('2014-08-04 09:00:00')

# 它与BusinessDay() + pd.Timestamp('2014-08-01')相同
# 结果与rollworward相同,因为BusinessDay从不重叠。
pd.offsets.BusinessHour() + pd.Timestamp("2014-08-02")
Out[227]: Timestamp('2014-08-04 10:00:00')

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

自定义工作小时

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

from pandas.tseries.holiday import USFederalHolidayCalendar

bhour_us = pd.offsets.CustomBusinessHour(calendar=USFederalHolidayCalendar())

# MLK日前的星期五
dt = datetime.datetime(2014, 1, 17, 15)

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

# MLK日后的星期二(星期一被跳过,因为那是假日)
dt + bhour_us * 2
Out[232]: Timestamp('2014-01-21 09:00:00')

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

bhour_mon = pd.offsets.CustomBusinessHour(start="10:00", weekmask="Tue Wed Thu Fri")

# 星期一被跳过,因为那是假日,工作小时从10:00开始
dt + bhour_mon * 2
Out[234]: Timestamp('2014-01-21 10:00:00')

偏移别名

为常见的时间序列频率提供了一些字符串别名。我们将这些别名称为偏移别名

别名描述
B工作日频率
C自定义工作日频率
D日历日频率
W周频率
ME月末频率
SME半月末频率(15号和月末)
BME工作月末频率
CBME自定义工作月末频率
MS月初频率
SMS半月初频率(1号和15号)
BMS工作月初频率
CBMS自定义工作月初频率
QE季度末频率
BQE工作季度末频率
QS季度初频率
BQS工作季度初频率
YE年末频率
BYE工作年末频率
YS年初频率
BYS工作年初频率
h小时频率
bh工作小时频率
cbh自定义工作小时频率
min分钟频率
s秒频率
ms毫秒
us微秒
ns纳秒

*自版本2.2.0起已弃用:*别名HBHCBHTSLUN已弃用,而别名hbhcbhminsmsusns则取而代之。

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

dates_lst_1 = pd.date_range("2020-01-06", "2020-04-03", freq="MS")

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

dates_lst_2 = pd.date_range("2020-01-01", "2020-04-01", freq="MS")

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

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

周期别名

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

别名描述
B工作日频率
D日历日频率
W周频率
M月频率
Q季度频率
Y年频率
h小时频率
min分钟频率
s秒频率
ms毫秒
us微秒
ns纳秒

自版本2.2.0起已弃用: 别名 AHTSLUN 已弃用,推荐使用别名 Yhminsmsusns

组合别名

正如我们之前看到的,别名和偏移实例在大多数函数中是可互换的:

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

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

您可以组合日和小时内的偏移量:

pd.date_range(start, periods=10, freq="2h20min")
Out[241]: 
DatetimeIndex(['2011-01-01 00:00:00', '2011-01-01 02:20:00',
               '2011-01-01 04:40:00', '2011-01-01 07:00:00',
               '2011-01-01 09:20:00', '2011-01-01 11:40:00',
               '2011-01-01 14:00:00', '2011-01-01 16:20:00',
               '2011-01-01 18:40:00', '2011-01-01 21:00:00'],
              dtype='datetime64[ns]', freq='140min')

pd.date_range(start, periods=10, freq="1D10us")
Out[242]: 
DatetimeIndex([       '2011-01-01 00:00:00', '2011-01-02 00:00:00.000010',
               '2011-01-03 00:00:00.000020', '2011-01-04 00:00:00.000030',
               '2011-01-05 00:00:00.000040', '2011-01-06 00:00:00.000050',
               '2011-01-07 00:00:00.000060', '2011-01-08 00:00:00.000070',
               '2011-01-09 00:00:00.000080', '2011-01-10 00:00:00.000090'],
              dtype='datetime64[ns]', freq='86400000010us')

锚定偏移量

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

别名描述
W-SUN周频率(星期日)。与 ‘W’ 相同
W-MON周频率(星期一)
W-TUE周频率(星期二)
W-WED周频率(星期三)
W-THU周频率(星期四)
W-FRI周频率(星期五)
W-SAT周频率(星期六)
(B)Q(E)(S)-DEC季度频率,年份以12月结束。与 ‘QE’ 相同
(B)Q(E)(S)-JAN季度频率,年份以1月结束
(B)Q(E)(S)-FEB季度频率,年份以2月结束
(B)Q(E)(S)-MAR季度频率,年份以3月结束
(B)Q(E)(S)-APR季度频率,年份以4月结束
(B)Q(E)(S)-MAY季度频率,年份以5月结束
(B)Q(E)(S)-JUN季度频率,年份以6月结束
(B)Q(E)(S)-JUL季度频率,年份以7月结束
(B)Q(E)(S)-AUG季度频率,年份以8月结束
(B)Q(E)(S)-SEP季度频率,年份以9月结束
(B)Q(E)(S)-OCT季度频率,年份以10月结束
(B)Q(E)(S)-NOV季度频率,年份以11月结束
(B)Y(E)(S)-DEC年度频率,锚定在12月结束。与 ‘YE’ 相同
(B)Y(E)(S)-JAN年度频率,锚定在1月结束
(B)Y(E)(S)-FEB年度频率,锚定在2月结束
(B)Y(E)(S)-MAR年度频率,锚定在3月结束
(B)Y(E)(S)-APR年度频率,锚定在4月结束
(B)Y(E)(S)-MAY年度频率,锚定在5月结束
(B)Y(E)(S)-JUN年度频率,锚定在6月结束
(B)Y(E)(S)-JUL年度频率,锚定在7月结束
(B)Y(E)(S)-AUG年度频率,锚定在8月结束
(B)Y(E)(S)-SEP年度频率,锚定在9月结束
(B)Y(E)(S)-OCT年度频率,锚定在10月结束
(B)Y(E)(S)-NOV年度频率,锚定在11月结束

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

锚定偏移量的语义

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

对于 n=0 的情况,如果日期在锚点上,则不移动,否则向前滚动到下一个锚点。

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

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

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

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

节假日/节假日日历

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

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

规则描述
nearest_workday将星期六移至星期五,将星期日移至星期一
sunday_to_monday将星期日移至下周一
next_monday_or_tuesday将星期六移至下周一,将星期日/星期一移至下周二
previous_friday将星期六和星期日移至上周五
如何定义假期和假期日历的示例:
from pandas.tseries.holiday import (
    Holiday,
    USMemorialDay,
    AbstractHolidayCalendar,
    nearest_workday,
    MO,
)


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)),
        ),
    ]


cal = ExampleCalendar()

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

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

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

pd.date_range(
    start="7/1/2012", end="7/10/2012", freq=pd.offsets.CDay(calendar=cal)
).to_pydatetime()

Out[263]: 
array([datetime.datetime(2012, 7, 2, 0, 0),
       datetime.datetime(2012, 7, 3, 0, 0),
       datetime.datetime(2012, 7, 5, 0, 0),
       datetime.datetime(2012, 7, 6, 0, 0),
       datetime.datetime(2012, 7, 9, 0, 0),
       datetime.datetime(2012, 7, 10, 0, 0)], dtype=object)

offset = pd.offsets.CustomBusinessDay(calendar=cal)

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

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

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

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

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

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

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

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

AbstractHolidayCalendar.start_date = datetime.datetime(2012, 1, 1)

AbstractHolidayCalendar.end_date = datetime.datetime(2012, 12, 31)

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

可以使用 get_calendar 函数按名称访问每个日历类,该函数返回一个假期类实例。任何导入的日历类都会自动通过此函数可用。此外,HolidayCalendarFactory 提供了一个简单的接口,用于创建组合日历或具有附加规则的日历。

from pandas.tseries.holiday import get_calendar, HolidayCalendarFactory, USLaborDay

cal = get_calendar("ExampleCalendar")

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

new_cal = HolidayCalendarFactory("NewExampleCalendar", cal, USLaborDay)

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

与时间序列相关的实例方法

偏移 / 滞后

可能希望将时间序列中的值向前或向后移动。在所有 pandas 对象上都可以使用 shift() 方法来实现此功能。

ts = pd.Series(range(len(rng)), index=rng)

ts = ts[:5]

ts.shift(1)
Out[281]: 
2012-01-01    NaN
2012-01-02    0.0
2012-01-03    1.0
Freq: D, dtype: float64

shift 方法接受一个 freq 参数,该参数可以接受 DateOffset 类或其他类似 timedelta 的对象,也可以是 offset alias

当指定 freq 时,shift 方法会更改索引中的所有日期,而不是更改数据和索引的对齐:

ts.shift(5, freq="D")
Out[282]: 
2012-01-06    0
2012-01-07    1
2012-01-08    2
Freq: D, dtype: int64

ts.shift(5, freq=pd.offsets.BDay())
Out[283]: 
2012-01-06    0
2012-01-09    1
2012-01-10    2
dtype: int64

ts.shift(5, freq="BME")
Out[284]: 
2012-05-31    0
2012-05-31    1
2012-05-31    2
dtype: int64

请注意,当指定 freq 时,前导条目不再为 NaN,因为数据没有被重新对齐。

频率转换

更改频率的主要函数是 asfreq() 方法。对于 DatetimeIndex,这基本上只是一个薄薄的包装器,但非常方便,它会生成一个 date_range 并调用 reindex

dr = pd.date_range("1/1/2010", periods=3, freq=3 * pd.offsets.BDay())

ts = pd.Series(np.random.randn(3), index=dr)

ts
Out[287]: 
2010-01-01    1.494522
2010-01-06   -0.778425
2010-01-11   -0.253355
Freq: 3B, dtype: float64

ts.asfreq(pd.offsets.BDay())
Out[288]: 
2010-01-01    1.494522
2010-01-04         NaN
2010-01-05         NaN
2010-01-06   -0.778425
2010-01-07         NaN
2010-01-08         NaN
2010-01-11   -0.253355
Freq: B, dtype: float64

asfreq 还提供了一个方便的功能,可以为可能出现在频率转换后的任何间隙指定插值方法。

ts.asfreq(pd.offsets.BDay(), method="pad")
Out[289]: 
2010-01-01    1.494522
2010-01-04    1.494522
2010-01-05    1.494522
2010-01-06   -0.778425
2010-01-07   -0.778425
2010-01-08   -0.778425
2010-01-11   -0.253355
Freq: B, dtype: float64

前向填充 / 后向填充

asfreqreindex 相关的是 fillna() 方法,在 缺失数据部分 中有文档说明。

转换为 Python datetime

DatetimeIndex 可以使用 to_pydatetime 方法转换为 Python 本机的 datetime.datetime 对象数组。

重新采样

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

resample() 是一种基于时间的分组操作,后跟对每个组进行的缩减方法。有关一些高级策略的一些 示例

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

基础知识

rng = pd.date_range("1/1/2012", periods=100, freq="s")

ts = pd.Series(np.random.randint(0, 500, len(rng)), index=rng)

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

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

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

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

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

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

对于降采样,可以将 closed 设置为 ‘left’ 或 ‘right’,以指定间隔的哪一端是闭合的:

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

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

label 参数等用于操作结果标签。label 指定结果是以间隔的开始还是结束标记的。

ts.resample("5Min").mean()  # 默认情况下 label='left'
Out[298]: 
2012-01-01    251.03
Freq: 5min, dtype: float64

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

警告

除了 ‘ME’、‘YE’、‘QE’、‘BME’、‘BYE’、‘BQE’ 和 ‘W’ 之外,所有频率偏移的默认值为 ‘left’。

这可能会导致向前查看,其中稍后的时间的值被拉回到先前的时间,如下面的示例所示,其中使用 BusinessDay 频率:

s = pd.date_range("2000-01-01", "2000-01-05").to_series()

s.iloc[2] = pd.NaT

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

# 默认情况下:label='left',closed='left'
s.resample("B").last().dt.day_name()
Out[303]: 
1999-12-31       Sunday
2000-01-03          NaN
2000-01-04      Tuesday
2000-01-05    Wednesday
Freq: B, dtype: object

注意观察周日的值被拉回到了前一个星期五。要使周日的值被推到星期一,可以使用以下方式:

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

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

kind 参数可以设置为 ‘timestamp’ 或 ‘period’,以将结果索引转换为时间戳和时间跨度表示。默认情况下,resample 保留输入表示。

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

上采样

对于上采样,您可以指定一种上采样方式和 limit 参数,以插值填充创建的间隙:

# 从每秒到每 250 毫秒
ts[:2].resample("250ms").asfreq()
Out[305]: 
2012-01-01 00:00:00.000    308.0
2012-01-01 00:00:00.250      NaN
2012-01-01 00:00:00.500      NaN
2012-01-01 00:00:00.750      NaN
2012-01-01 00:00:01.000    204.0
Freq: 250ms, dtype: float64

ts[:2].resample("250ms").ffill()
Out[306]: 
2012-01-01 00:00:00.000    308
2012-01-01 00:00:00.250    308
2012-01-01 00:00:00.500    308
2012-01-01 00:00:00.750    308
2012-01-01 00:00:01.000    204
Freq: 250ms, dtype: int64

ts[:2].resample("250ms").ffill(limit=2)
Out[307]: 
2012-01-01 00:00:00.000    308.0
2012-01-01 00:00:00.250    308.0
2012-01-01 00:00:00.500    308.0
2012-01-01 00:00:00.750      NaN
2012-01-01 00:00:01.000    204.0
Freq: 250ms, dtype: float64

稀疏重新采样

稀疏时间序列是指在所要重新采样的时间段内,数据点相对较少。简单地对稀疏系列进行上采样可能会产生大量的中间值。当您不想使用填充这些值的方法时,例如 fill_methodNone,那么中间值将被填充为 NaN

由于 resample 是基于时间的分组操作,以下是一种仅重新采样非全为 NaN 的组的有效方法。

rng = pd.date_range("2014-1-1", periods=100, freq="D") + pd.Timedelta("1s")

ts = pd.Series(range(100), index=rng)

如果我们想要将其重新采样为系列的完整范围:

ts.resample("3min").sum()
Out[310]: 
2014-01-01 00:00:00     0
2014-01-01 00:03:00     0
2014-01-01 00:06:00     0
2014-01-01 00:09:00     0
2014-01-01 00:12:00     0
                       ..
2014-04-09 23:48:00     0
2014-04-09 23:51:00     0
2014-04-09 23:54:00     0
2014-04-09 23:57:00     0
2014-04-10 00:00:00    99
Freq: 3min, Length: 47521, dtype: int64

我们可以只重新采样那些有数据点的组,如下所示:

from functools import partial

from pandas.tseries.frequencies import to_offset

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


ts.groupby(partial(round, freq="3min")).sum()
Out[314]: 
2014-01-01     0
2014-01-02     1
2014-01-03     2
2014-01-04     3
2014-01-05     4
              ..
2014-04-06    95
2014-04-07    96
2014-04-08    97
2014-04-09    98
2014-04-10    99
Length: 100, dtype: int64

聚合

resample() 方法返回一个 pandas.api.typing.Resampler 实例。与聚合 API分组 API窗口 API类似,Resampler 可以选择性地重新采样。

对于重新采样的 DataFrame,默认情况下将对所有列使用相同的函数进行操作。

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


r = df.resample("3min")

r.mean()
Out[317]: 
                            A         B         C
2012-01-01 00:00:00 -0.033823 -0.121514 -0.081447
2012-01-01 00:03:00  0.056909  0.146731 -0.024320
2012-01-01 00:06:00 -0.058837  0.047046 -0.052021
2012-01-01 00:09:00  0.063123 -0.026158 -0.066533
2012-01-01 00:12:00  0.186340 -0.003144  0.074752
2012-01-01 00:15:00 -0.085954 -0.016287 -0.050046

我们可以使用标准的 getitem 来选择特定的列或多列。

r["A"].mean()
Out[318]: 
2012-01-01 00:00:00   -0.033823
2012-01-01 00:03:00    0.056909
2012-01-01 00:06:00   -0.058837
2012-01-01 00:09:00    0.063123
2012-01-01 00:12:00    0.186340
2012-01-01 00:15:00   -0.085954
Freq: 3min, Name: A, dtype: float64

r[["A", "B"]].mean()
Out[319]: 
                            A         B
2012-01-01 00:00:00 -0.033823 -0.121514
2012-01-01 00:03:00  0.056909  0.146731
2012-01-01 00:06:00 -0.058837  0.047046
2012-01-01 00:09:00  0.063123 -0.026158
2012-01-01 00:12:00  0.186340 -0.003144
2012-01-01 00:15:00 -0.085954 -0.016287

您可以传递一个函数列表或字典来进行聚合,输出一个 DataFrame

r["A"].agg(["sum", "mean", "std"])
Out[320]: 
                           sum      mean       std
2012-01-01 00:00:00  -6.088060 -0.033823  1.043263
2012-01-01 00:03:00  10.243678  0.056909  1.058534
2012-01-01 00:06:00 -10.590584 -0.058837  0.949264
2012-01-01 00:09:00  11.362228  0.063123  1.028096
2012-01-01 00:12:00  33.541257  0.186340  0.884586
2012-01-01 00:15:00  -8.595393 -0.085954  1.035476

在重新采样的 DataFrame 上,您可以传递一个函数列表,将其应用于每列,产生一个带有分层索引的聚合结果:

r.agg(["sum", "mean"])
Out[321]: 
                             A            ...          C          
                           sum      mean  ...        sum      mean
2012-01-01 00:00:00  -6.088060 -0.033823  ... -14.660515 -0.081447
2012-01-01 00:03:00  10.243678  0.056909  ...  -4.377642 -0.024320
2012-01-01 00:06:00 -10.590584 -0.058837  ...  -9.363825 -0.052021
2012-01-01 00:09:00  11.362228  0.063123  ... -11.975895 -0.066533
2012-01-01 00:12:00  33.541257  0.186340  ...  13.455299  0.074752
2012-01-01 00:15:00  -8.595393 -0.085954  ...  -5.004580 -0.050046

[6 rows x 6 columns]

通过将字典传递给 aggregate,您可以对 DataFrame 的列应用不同的聚合函数:

r.agg({"A": "sum", "B": lambda x: np.std(x, ddof=1)})
Out[322]: 
                             A         B
2012-01-01 00:00:00  -6.088060  1.001294
2012-01-01 00:03:00  10.243678  1.074597
2012-01-01 00:06:00 -10.590584  0.987309
2012-01-01 00:09:00  11.362228  0.944953
2012-01-01 00:12:00  33.541257  1.095025
2012-01-01 00:15:00  -8.595393  1.035312

函数名称也可以是字符串。为了使字符串有效,它必须在重新采样对象上实现:

r.agg({"A": "sum", "B": "std"})
Out[323]: 
                             A         B
2012-01-01 00:00:00  -6.088060  1.001294
2012-01-01 00:03:00  10.243678  1.074597
2012-01-01 00:06:00 -10.590584  0.987309
2012-01-01 00:09:00  11.362228  0.944953
2012-01-01 00:12:00  33.541257  1.095025
2012-01-01 00:15:00  -8.595393  1.035312

此外,您还可以为每列单独指定多个聚合函数。

r.agg({"A": ["sum", "std"], "B": ["mean", "std"]})
Out[324]: 
                             A                   B          
                           sum       std      mean       std
2012-01-01 00:00:00  -6.088060  1.043263 -0.121514  1.001294
2012-01-01 00:03:00  10.243678  1.058534  0.146731  1.074597
2012-01-01 00:06:00 -10.590584  0.949264  0.047046  0.987309
2012-01-01 00:09:00  11.362228  1.028096 -0.026158  0.944953
2012-01-01 00:12:00  33.541257  0.884586 -0.003144  1.095025
2012-01-01 00:15:00  -8.595393  1.035476 -0.016287  1.035312

如果 DataFrame 没有日期时间索引,而是您想要根据帧中的日期时间列进行重新采样,可以将其传递给 on 关键字。

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"],
    ),
)


df
Out[326]: 
                   date  a
v d                       
1 2015-01-04 2015-01-04  0
2 2015-01-11 2015-01-11  1
3 2015-01-18 2015-01-18  2
4 2015-01-25 2015-01-25  3
5 2015-02-01 2015-02-01  4

df.resample("ME", on="date")[["a"]].sum()
Out[327]: 
            a
date         
2015-01-31  6
2015-02-28  4

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

df.resample("ME", level="d")[["a"]].sum()
Out[328]: 
            a
d            
2015-01-31  6
2015-02-28  4

遍历组

有了 Resampler 对象,遍历分组数据非常自然,与 itertools.groupby() 类似:

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",
        ]
    ),
)


resampled = small.resample("h")

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__以获取更多信息。

使用originoffset调整分组的起始位置

分组的区间根据时间序列起始点的当天开始时间进行调整。这在频率是一天的倍数(如30D)或能够整除一天的频率(如90s1min)时效果良好。但是,对于一些不满足这些条件的频率,可能会出现不一致的情况。要更改此行为,可以使用origin参数指定一个固定的时间戳。

例如:

start, end = "2000-10-01 23:30:00", "2000-10-02 00:30:00"

middle = "2000-10-02 00:00:00"

rng = pd.date_range(start, end, freq="7min")

ts = pd.Series(np.arange(len(rng)) * 3, index=rng)

ts
Out[336]: 
2000-10-01 23:30:00     0
2000-10-01 23:37:00     3
2000-10-01 23:44:00     6
2000-10-01 23:51:00     9
2000-10-01 23:58:00    12
2000-10-02 00:05:00    15
2000-10-02 00:12:00    18
2000-10-02 00:19:00    21
2000-10-02 00:26:00    24
Freq: 7min, dtype: int64

在上面的例子中,当使用默认值'start_day'origin时,'2000-10-02 00:00:00'之后的结果根据时间序列的起始点不同而不同:

ts.resample("17min", origin="start_day").sum()
Out[337]: 
2000-10-01 23:14:00     0
2000-10-01 23:31:00     9
2000-10-01 23:48:00    21
2000-10-02 00:05:00    54
2000-10-02 00:22:00    24
Freq: 17min, dtype: int64

ts[middle:end].resample("17min", origin="start_day").sum()
Out[338]: 
2000-10-02 00:00:00    33
2000-10-02 00:17:00    45
Freq: 17min, dtype: int64

当将origin设置为'epoch'时,可以看到'2000-10-02 00:00:00'之后的结果根据时间序列的起始点是相同的:

ts.resample("17min", origin="epoch").sum()
Out[339]: 
2000-10-01 23:18:00     0
2000-10-01 23:35:00    18
2000-10-01 23:52:00    27
2000-10-02 00:09:00    39
2000-10-02 00:26:00    24
Freq: 17min, dtype: int64

ts[middle:end].resample("17min", origin="epoch").sum()
Out[340]: 
2000-10-01 23:52:00    15
2000-10-02 00:09:00    39
2000-10-02 00:26:00    24
Freq: 17min, dtype: int64

如果需要,可以使用自定义的时间戳作为origin

ts.resample("17min", origin="2001-01-01").sum()
Out[341]: 
2000-10-01 23:30:00     9
2000-10-01 23:47:00    21
2000-10-02 00:04:00    54
2000-10-02 00:21:00    24
Freq: 17min, dtype: int64

ts[middle:end].resample("17min", origin=pd.Timestamp("2001-01-01")).sum()
Out[342]: 
2000-10-02 00:04:00    54
2000-10-02 00:21:00    24
Freq: 17min, dtype: int64

如果需要,还可以使用offset时间增量来调整区间的起始位置,该增量将添加到默认的origin中。对于这个时间序列来说,下面两个示例是等价的:

ts.resample("17min", origin="start").sum()
Out[343]: 
2000-10-01 23:30:00     9
2000-10-01 23:47:00    21
2000-10-02 00:04:00    54
2000-10-02 00:21:00    24
Freq: 17min, dtype: int64

ts.resample("17min", offset="23h30min").sum()
Out[344]: 
2000-10-01 23:30:00     9
2000-10-01 23:47:00    21
2000-10-02 00:04:00    54
2000-10-02 00:21:00    24
Freq: 17min, dtype: int64

请注意,最后一个示例中的origin使用了'start'。在这种情况下,origin将设置为时间序列的第一个值。

向后重采样

1.3.0版中的新功能。

有时,我们需要固定区间的结束点来进行向后重采样,并使用给定的freq。向后重采样默认将closed设置为'right',因为最后一个值应该被视为最后一个区间的边界点。

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

ts.resample('17min', origin='end').sum()
Out[345]: 
2000-10-01 23:35:00     0
2000-10-01 23:52:00    18
2000-10-02 00:09:00    27
2000-10-02 00:26:00    63
Freq: 17min, dtype: int64

此外,与'start_day'选项相反,支持end_day。这将将origin设置为最大Timestamp的午夜。

ts.resample('17min', origin='end_day').sum()
Out[346]: 
2000-10-01 23:38:00     3
2000-10-01 23:55:00    15
2000-10-02 00:12:00    45
2000-10-02 00:29:00    45
Freq: 17min, dtype: int64

上面的结果使用2000-10-02 00:29:00作为最后一个区间的右边界,根据以下计算:

ceil_mid = rng.max().ceil('D')

freq = pd.offsets.Minute(17)

bin_res = ceil_mid - freq * ((ceil_mid - rng.max()) // freq)

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

时间跨度表示

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

Period

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

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

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

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

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

Period中添加和减去整数会按照其自身的频率移动Period。不同频率(时间跨度)的Period之间不允许进行算术运算。

p = pd.Period("2012", freq="Y-DEC")

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

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

p = pd.Period("2012-01", freq="2M")

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

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

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

如果Period的频率是每天或更高(Dhminsmsusns),则可以添加offsets和类似于timedelta的时间增量,如果结果具有相同的频率。否则,将引发ValueError

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

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

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

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

File timedeltas.pyx:278, in pandas._libs.tslibs.timedeltas.delta_to_nanoseconds()

File np_datetime.pyx:661, in pandas._libs.tslibs.np_datetime.convert_reso()

ValueError: Cannot losslessly convert units

The above exception was the direct cause of the following exception:

IncompatibleFrequency                     Traceback (most recent call last)
Cell In[366], line 1
----> 1 p + pd.offsets.Minute(5)

File period.pyx:1845, in pandas._libs.tslibs.period._Period.__add__()

File period.pyx:1826, in pandas._libs.tslibs.period._Period._add_timedeltalike_scalar()

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

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

p = pd.Period("2014-07", freq="M")

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

File period.pyx:1847, in pandas._libs.tslibs.period._Period.__add__()

File period.pyx:1837, in pandas._libs.tslibs.period._Period._add_offset()

File period.pyx:1732, in pandas._libs.tslibs.period.PeriodMixin._require_matching_freq()

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

对于具有相同频率的Period实例之间的差异,将返回它们之间的频率单位数:

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

PeriodIndex和period_range

Period对象的常规序列可以收集在PeriodIndex中,可以使用period_range便捷函数构建PeriodIndex

prng = pd.period_range("1/1/2011", "1/1/2012", freq="M")

prng
Out[372]: 
PeriodIndex(['2011-01', '2011-02', '2011-03', '2011-04', '2011-05', '2011-06',
             '2011-07', '2011-08', '2011-09', '2011-10', '2011-11', '2011-12',
             '2012-01'],
            dtype='period[M]')

PeriodIndex构造函数也可以直接使用:

pd.PeriodIndex(["2011-1", "2011-2", "2011-3"], freq="M")
Out[373]: PeriodIndex(['2011-01', '2011-02', '2011-03'], dtype='period[M]')

传递乘以频率的输出会生成一个具有乘以时间跨度的Period的序列。

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

如果startendPeriod对象,则它们将用作与PeriodIndex构造函数的频率匹配的锚点端点。

pd.period_range(
    start=pd.Period("2017Q1", freq="Q"), end=pd.Period("2017Q2", freq="Q"), freq="M"
)

Out[375]: PeriodIndex(['2017-03', '2017-04', '2017-05', '2017-06'], dtype='period[M]')

DatetimeIndex一样,PeriodIndex也可以用于索引pandas对象:

ps = pd.Series(np.random.randn(len(prng)), prng)

ps
Out[377]: 
2011-01   -2.916901
2011-02    0.514474
2011-03    1.346470
2011-04    0.816397
2011-05    2.258648
2011-06    0.494789
2011-07    0.301239
2011-08    0.464776
2011-09   -1.393581
2011-10    0.056780
2011-11    0.197035
2011-12    2.261385
2012-01   -0.329583
Freq: M, dtype: float64
`PeriodIndex` 支持与 `Period` 相同的规则进行加减运算。

```python
idx = pd.period_range("2014-07-01 09:00", periods=5, freq="h")

idx
Out[379]: 
PeriodIndex(['2014-07-01 09:00', '2014-07-01 10:00', '2014-07-01 11:00',
             '2014-07-01 12:00', '2014-07-01 13:00'],
            dtype='period[h]')

idx + pd.offsets.Hour(2)
Out[380]: 
PeriodIndex(['2014-07-01 11:00', '2014-07-01 12:00', '2014-07-01 13:00',
             '2014-07-01 14:00', '2014-07-01 15:00'],
            dtype='period[h]')

idx = pd.period_range("2014-07", periods=5, freq="M")

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

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

PeriodIndex 有自己的 period 数据类型,参考Period Dtypes

期间数据类型

PeriodIndex 有一个名为 period 的自定义数据类型。这是一种类似于时区感知数据类型datetime64[ns, tz])的 pandas 扩展数据类型。

period 数据类型保存了 freq 属性,并以 period[freq] 的形式表示,例如 period[D]period[M],使用频率字符串

pi = pd.period_range("2016-01-01", periods=3, freq="M")

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

pi.dtype
Out[386]: period[M]

period 数据类型可以在 .astype(...) 中使用。它允许更改 PeriodIndexfreq,就像 .asfreq() 一样,并将 DatetimeIndex 转换为 PeriodIndex,就像 to_period() 一样:

# 将月度频率更改为日度频率
pi.astype("period[D]")
Out[387]: PeriodIndex(['2016-01-31', '2016-02-29', '2016-03-31'], dtype='period[D]')

# 转换为 DatetimeIndex
pi.astype("datetime64[ns]")
Out[388]: DatetimeIndex(['2016-01-01', '2016-02-01', '2016-03-01'], dtype='datetime64[ns]', freq='MS')

# 转换为 PeriodIndex
dti = pd.date_range("2011-01-01", freq="ME", periods=3)

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

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

PeriodIndex 部分字符串索引

PeriodIndex 现在支持使用非单调索引进行部分字符串切片。

您可以像 DatetimeIndex 一样,通过日期和字符串将其传递给 SeriesDataFrame,详细信息请参见DatetimeIndex Partial String Indexing

ps["2011-01"]
Out[392]: -2.9169013294054507

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

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

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

ps["2011"]
Out[395]: 
2011-01   -2.916901
2011-02    0.514474
2011-03    1.346470
2011-04    0.816397
2011-05    2.258648
2011-06    0.494789
2011-07    0.301239
2011-08    0.464776
2011-09   -1.393581
2011-10    0.056780
2011-11    0.197035
2011-12    2.261385
Freq: M, dtype: float64

dfp = pd.DataFrame(
    np.random.randn(600, 1),
    columns=["A"],
    index=pd.period_range("2013-01-01 9:00", periods=600, freq="min"),
)


dfp
Out[397]: 
                         A
2013-01-01 09:00 -0.538468
2013-01-01 09:01 -1.365819
2013-01-01 09:02 -0.969051
2013-01-01 09:03 -0.331152
2013-01-01 09:04 -0.245334
...                    ...
2013-01-01 18:55  0.522460
2013-01-01 18:56  0.118710
2013-01-01 18:57  0.167517
2013-01-01 18:58  0.922883
2013-01-01 18:59  1.721104

[600 rows x 1 columns]

dfp.loc["2013-01-01 10h"]
Out[398]: 
                         A
2013-01-01 10:00 -0.308975
2013-01-01 10:01  0.542520
2013-01-01 10:02  1.061068
2013-01-01 10:03  0.754005
2013-01-01 10:04  0.352933
...                    ...
2013-01-01 10:55 -0.865621
2013-01-01 10:56 -1.167818
2013-01-01 10:57 -2.081748
2013-01-01 10:58 -0.527146
2013-01-01 10:59  0.802298

[60 rows x 1 columns]

DatetimeIndex 一样,结果中将包含起始点和结束点。下面的示例从 10:00 切片到 11:59。

dfp["2013-01-01 10h":"2013-01-01 11h"]
Out[399]: 
                         A
2013-01-01 10:00 -0.308975
2013-01-01 10:01  0.542520
2013-01-01 10:02  1.061068
2013-01-01 10:03  0.754005
2013-01-01 10:04  0.352933
...                    ...
2013-01-01 11:55 -0.590204
2013-01-01 11:56  1.539990
2013-01-01 11:57 -1.224826
2013-01-01 11:58  0.578798
2013-01-01 11:59 -0.685496

[120 rows x 1 columns]

使用 PeriodIndex 进行频率转换和重采样

可以通过 asfreq 方法将 PeriodPeriodIndex 的频率进行转换。让我们从截止到 12 月的 2011 财年开始:

p = pd.Period("2011", freq="Y-DEC")

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

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

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

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

提供了 ‘s’ 和 ‘e’ 的简写形式以方便使用:

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

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

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

p = pd.Period("2011-12", freq="M")

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

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

使用锚定频率进行期间转换对于处理经济学、商业和其他领域常见的各种季度数据特别有用。许多组织将季度相对于财年开始和结束的月份定义为季度。因此,2011 年第一季度可以从 2010 年开始,也可以从 2011 年的几个月开始。通过锚定频率,pandas 可以适用于所有季度频率 Q-JANQ-DEC

Q-DEC 定义了常规的日历季度:

p = pd.Period("2012Q1", freq="Q-DEC")

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

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

Q-MAR 定义了财年结束在三月:

p = pd.Period("2011Q4", freq="Q-MAR")

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

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

转换表示方式

可以使用 to_period 将时间戳数据转换为 PeriodIndex 数据,使用 to_timestamp 可以将 PeriodIndex 数据转换为时间戳数据:

rng = pd.date_range("1/1/2012", periods=5, freq="ME")

ts = pd.Series(np.random.randn(len(rng)), index=rng)

ts
Out[416]: 
2012-01-31    1.931253
2012-02-29   -0.184594
2012-03-31    0.249656
2012-04-30   -0.978151
2012-05-31   -0.873389
Freq: ME, dtype: float64

ps = ts.to_period()

ps
Out[418]: 
2012-01    1.931253
2012-02   -0.184594
2012-03    0.249656
2012-04   -0.978151
2012-05   -0.873389
Freq: M, dtype: float64

ps.to_timestamp()
Out[419]: 
2012-01-01    1.931253
2012-02-01   -0.184594
2012-03-01    0.249656
2012-04-01   -0.978151
2012-05-01   -0.873389
Freq: MS, dtype: float64

请记住,‘s’ 和 ‘e’ 可以用于返回起始时间戳或结束时间戳:

ps.to_timestamp("D", how="s")
Out[420]: 
2012-01-01    1.931253
2012-02-01   -0.184594
2012-03-01    0.249656
2012-04-01   -0.978151
2012-05-01   -0.873389
Freq: MS, dtype: float64

在期间和时间戳之间进行转换可以使用一些方便的算术函数。在下面的示例中,我们将以 11 月结束的财年季度频率转换为季度结束后一个月的上午 9 点:

prng = pd.period_range("1990Q1", "2000Q4", freq="Q-NOV")

ts = pd.Series(np.random.randn(len(prng)), prng)

ts.index = (prng.asfreq("M", "e") + 1).asfreq("h", "s") + 9

ts.head()
Out[424]: 
1990-03-01 09:00   -0.109291
1990-06-01 09:00   -0.637235
1990-09-01 09:00   -1.735925
1990-12-01 09:00    2.096946
1991-03-01 09:00   -1.039926
Freq: h, dtype: float64

表示超出边界的时间段

如果您的数据超出了 Timestamp 的边界,请参见时间戳限制,那么您可以使用 PeriodIndex 和/或 SeriesPeriods 来进行计算。

span = pd.period_range("1215-01-01", "1381-01-01", freq="D")

span
Out[426]: 
PeriodIndex(['1215-01-01', '1215-01-02', '1215-01-03', '1215-01-04',
             '1215-01-05', '1215-01-06', '1215-01-07', '1215-01-08',
             '1215-01-09', '1215-01-10',
             ...
             '1380-12-23', '1380-12-24', '1380-12-25', '1380-12-26',
             '1380-12-27', '1380-12-28', '1380-12-29', '1380-12-30',
             '1380-12-31', '1381-01-01'],
            dtype='period[D]', length=60632)

要从基于 int64 的 YYYYMMDD 表示中进行转换。

s = pd.Series([20121231, 20141130, 99991231])

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

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


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

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

这些可以很容易地转换为 PeriodIndex

span = pd.PeriodIndex(s.apply(conv))

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

时区处理

pandas 提供了丰富的支持,可以使用 pytzdateutil 库,或者使用标准库中的 datetime.timezone 对象,来处理不同时区的时间戳。

使用时区

默认情况下,pandas 对象是无时区意识的:

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

rng.tz is None
Out[435]: True

要将这些日期本地化到一个时区(为一个无时区的日期分配一个特定的时区),可以使用 tz_localize 方法或者在 date_range()TimestampDatetimeIndex 中使用 tz 关键字参数。可以传递 pytzdateutil 的时区对象,或者 Olson 时区数据库字符串。Olson 时区字符串默认会返回 pytz 的时区对象。要返回 dateutil 的时区对象,在字符串前面添加 dateutil/

  • pytz 中,可以使用 from pytz import common_timezones, all_timezones 找到常见(和不常见)的时区列表。
  • dateutil 使用操作系统的时区,因此没有固定的列表可用。对于常见的时区,名称与 pytz 相同。
import dateutil

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

rng_pytz.tz
Out[438]: <DstTzInfo 'Europe/London' LMT-1 day, 23:59:00 STD>

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

rng_dateutil = rng_dateutil.tz_localize("dateutil/Europe/London")

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

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


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


rng_utc.tz
Out[445]: datetime.timezone.utc

请注意,UTC 时区在 dateutil 中是一个特殊情况,应该明确地构造为 dateutil.tz.tzutc 的实例。您还可以首先明确地构造其他时区对象。

import pytz

# pytz
tz_pytz = pytz.timezone("Europe/London")

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

rng_pytz = rng_pytz.tz_localize(tz_pytz)

rng_pytz.tz == tz_pytz
Out[450]: True

# dateutil
tz_dateutil = dateutil.tz.gettz("Europe/London")

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

rng_dateutil.tz == tz_dateutil
Out[453]: True

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

rng_pytz.tz_convert("US/Eastern")
Out[454]: 
DatetimeIndex(['2012-03-05 19:00:00-05:00', '2012-03-06 19:00:00-05:00',
               '2012-03-07 19:00:00-05:00'],
              dtype='datetime64[ns, US/Eastern]', freq=None)

注意

当使用 pytz 时区时,对于相同的时区输入,DatetimeIndex 会构造一个不同的时区对象,而 Timestamp 则会构造一个相同的时区对象。DatetimeIndex 可以保存一组具有不同 UTC 偏移量的 Timestamp 对象,并且不能用一个 pytz 时区实例简洁地表示。而一个 Timestamp 表示一个具有特定 UTC 偏移量的时间点。

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

dti.tz
Out[456]: <DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>

ts = pd.Timestamp("2019-01-01", tz="US/Pacific")

ts.tz
Out[458]: <DstTzInfo 'US/Pacific' PST-1 day, 16:00:00 STD>

警告

注意在不同库之间进行转换时要小心。对于某些时区,pytzdateutil 对该时区的定义可能不同。这对于不常见的时区比“标准”时区(如 US/Eastern)更成问题。

警告

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

警告

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

警告

请注意,对于未来的时间,任何时区库都无法保证在时区之间(和 UTC 之间)进行正确转换,因为时区的 UTC 偏移量可能会被相应的政府更改。

警告

如果您使用的是 2038-01-18 之后的日期,由于底层库的当前缺陷导致的 2038 年问题,时区感知的日期不会应用夏令时(DST)调整。如果底层库被修复,DST 转换将会应用。

例如,对于两个处于英国夏令时的日期(通常为 GMT+1),以下两个断言都为真:

d_2037 = "2037-03-31T010101"

d_2038 = "2038-03-31T010101"

DST = "Europe/London"

assert pd.Timestamp(d_2037, tz=DST) != pd.Timestamp(d_2037, tz="GMT")

assert pd.Timestamp(d_2038, tz=DST) == pd.Timestamp(d_2038, tz="GMT")

在底层,所有时间戳都以 UTC 存储。来自时区感知的 DatetimeIndexTimestamp 的值将其字段(天、小时、分钟等)本地化到时区。但是,具有相同 UTC 值的时间戳即使位于不同的时区,仍被认为是相等的:

rng_eastern = rng_utc.tz_convert("US/Eastern")

rng_berlin = rng_utc.tz_convert("Europe/Berlin")

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

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

rng_eastern[2] == rng_berlin[2]
Out[468]: True

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

ts_utc = pd.Series(range(3), pd.date_range("20130101", periods=3, tz="UTC"))

eastern = ts_utc.tz_convert("US/Eastern")

berlin = ts_utc.tz_convert("Europe/Berlin")

result = eastern + berlin

result
Out[473]: 
2013-01-01 00:00:00+00:00    0
2013-01-02 00:00:00+00:00    2
2013-01-03 00:00:00+00:00    4
Freq: D, dtype: int64

result.index
Out[474]: 
DatetimeIndex(['2013-01-01 00:00:00+00:00', '2013-01-02 00:00:00+00:00',
               '2013-01-03 00:00:00+00:00'],
              dtype='datetime64[ns, UTC]', freq='D')

要删除时区信息,可以使用 tz_localize(None)tz_convert(None)tz_localize(None) 将删除时区,得到本地时间表示。tz_convert(None) 将在转换为 UTC 时间后删除时区。

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

didx
Out[476]: 
DatetimeIndex(['2014-08-01 09:00:00-04:00', '2014-08-01 10:00:00-04:00',
               '2014-08-01 11:00:00-04:00'],
              dtype='datetime64[ns, US/Eastern]', freq='h')

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

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

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

折叠(Fold)

pd.Timestamp(
    datetime.datetime(2019, 10, 27, 1, 30, 0, 0),
    tz="dateutil/Europe/London",
    fold=0,
)

Out[480]: Timestamp('2019-10-27 01:30:00+0100', tz='dateutil//usr/share/zoneinfo/Europe/London')

pd.Timestamp(
    year=2019,
    month=10,
    day=27,
    hour=1,
    minute=30,
    tz="dateutil/Europe/London",
    fold=1,
)

Out[481]: Timestamp('2019-10-27 01:30:00+0000', tz='dateutil//usr/share/zoneinfo/Europe/London')

本地化时的模糊时间

tz_localize 可能无法确定时间戳的 UTC 偏移量,因为本地时区的夏令时(DST)会导致某些时间在一天内发生两次(“时钟回退”)。以下选项可供选择:

  • 'raise':引发 pytz.AmbiguousTimeError(默认行为)
  • 'infer':尝试根据时间戳的单调性确定正确的偏移量
  • 'NaT':将模糊时间替换为 NaT
  • boolTrue 表示 DST 时间,False 表示非 DST 时间。支持用于时间序列的 bool 值的数组。
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'

rng_hourly.tz_localize('US/Eastern')
---------------------------------------------------------------------------
AmbiguousTimeError                        Traceback (most recent call last)
Cell In[483], line 1
----> 1 rng_hourly.tz_localize('US/Eastern')

File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:293, in DatetimeIndex.tz_localize(self, tz, ambiguous, nonexistent)
    286 @doc(DatetimeArray.tz_localize)
    287 def tz_localize(
    288     self,
   (...)
    291     nonexistent: TimeNonexistent = "raise",
    292 ) -> Self:
--> 293     arr = self._data.tz_localize(tz, ambiguous, nonexistent)
    294     return type(self)._simple_new(arr, name=self.name)

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

File ~/work/pandas/pandas/pandas/core/arrays/datetimes.py:1088, in DatetimeArray.tz_localize(self, tz, ambiguous, nonexistent)
   1085     tz = timezones.maybe_get_tz(tz)
   1086     # Convert to UTC
-> 1088     new_dates = tzconversion.tz_localize_to_utc(
   1089         self.asi8,
   1090         tz,
   1091         ambiguous=ambiguous,
   1092         nonexistent=nonexistent,
   1093         creso=self._creso,
   1094     )
   1095 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
   1096 dtype = tz_to_dtype(tz, unit=self.unit)

File tzconversion.pyx:371, in pandas._libs.tslibs.tzconversion.tz_localize_to_utc()

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

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

rng_hourly.tz_localize("US/Eastern", ambiguous="infer")
Out[484]: 
DatetimeIndex(['2011-11-06 00:00:00-04:00', '2011-11-06 01:00:00-04:00',
               '2011-11-06 01:00:00-05:00', '2011-11-06 02:00:00-05:00'],
              dtype='datetime64[ns, US/Eastern]', freq=None)

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

rng_hourly.tz_localize("US/Eastern", ambiguous=[True, True, False, False])
Out[486]: 
DatetimeIndex(['2011-11-06 00:00:00-04:00', '2011-11-06 01:00:00-04:00',
               '2011-11-06 01:00:00-05:00', '2011-11-06 02:00:00-05:00'],
              dtype='datetime64[ns, US/Eastern]', freq=None)

本地化时的不存在时间

夏令时转换还可能使本地时间向前推进 1 小时,从而创建不存在的本地时间(“时钟向前跳跃”)。可以通过 nonexistent 参数来控制本地化时间序列中不存在时间的行为。以下选项可供选择:

  • 'raise':引发 pytz.NonExistentTimeError(默认行为)
  • 'NaT':将不存在的时间替换为 NaT
  • 'shift_forward':将不存在的时间向前移动到最接近的真实时间
  • 'shift_backward':将不存在的时间向后移动到最接近的真实时间
  • timedelta 对象:将不存在的时间按 timedelta 时长移动
dti = pd.date_range(start="2015-03-29 02:30:00", periods=3, freq="h")

# 2:30 是不存在的时间

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

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

File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:293, in DatetimeIndex.tz_localize(self, tz, ambiguous, nonexistent)
    286 @doc(DatetimeArray.tz_localize)
    287 def tz_localize(
    288     self,
   (...)
    291     nonexistent: TimeNonexistent = "raise",
    292 ) -> Self:
--> 293     arr = self._data.tz_localize(tz, ambiguous, nonexistent)
    294     return type(self)._simple_new(arr, name=self.name)

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

File ~/work/pandas/pandas/pandas/core/arrays/datetimes.py:1088, in DatetimeArray.tz_localize(self, tz, ambiguous, nonexistent)
   1085     tz = timezones.maybe_get_tz(tz)
   1086     # Convert to UTC
-> 1088     new_dates = tzconversion.tz_localize_to_utc(
   1089         self.asi8,
   1090         tz,
   1091         ambiguous=ambiguous,
   1092         nonexistent=nonexistent,
   1093         creso=self._creso,
   1094     )
   1095 new_dates_dt64 = new_dates.view(f"M8[{self.unit}]")
   1096 dtype = tz_to_dtype(tz, unit=self.unit)

File tzconversion.pyx:431, in pandas._libs.tslibs.tzconversion.tz_localize_to_utc()

NonExistentTimeError: 2015-03-29 02:30:00

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

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

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

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

dti.tz_localize("Europe/Warsaw", nonexistent=pd.Timedelta(1, unit="h"))
Out[492]: 
DatetimeIndex(['2015-03-29 03:30:00+02:00', '2015-03-29 03:30:00+02:00',
               '2015-03-29 04:30:00+02:00'],
              dtype='datetime64[ns, Europe/Warsaw]', freq=None)

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

时区序列操作

具有时区无关值的 Seriesdatetime64[ns] 的 dtype 表示。

s_naive = pd.Series(pd.date_range("20130101", periods=3))

s_naive
Out[495]: 
0   2013-01-01
1   2013-01-02
2   2013-01-03
dtype: datetime64[ns]

具有时区感知值的 Seriesdatetime64[ns, tz] 的 dtype 表示,其中 tz 是时区。

s_aware = pd.Series(pd.date_range("20130101", periods=3, tz="US/Eastern"))

s_aware
Out[497]: 
0   2013-01-01 00:00:00-05:00
1   2013-01-02 00:00:00-05:00
2   2013-01-03 00:00:00-05:00
dtype: datetime64[ns, US/Eastern]

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

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

s_naive.dt.tz_localize("UTC").dt.tz_convert("US/Eastern")
Out[498]: 
0   2012-12-31 19:00:00-05:00
1   2013-01-01 19:00:00-05:00
2   2013-01-02 19:00:00-05:00
dtype: datetime64[ns, US/Eastern]

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

# 转换为新的时区
s_aware.astype("datetime64[ns, CET]")
Out[499]: 
0   2013-01-01 06:00:00+01:00
1   2013-01-02 06:00:00+01:00
2   2013-01-03 06:00:00+01:00
dtype: datetime64[ns, CET]

注意

使用 Series.to_numpy()Series 上,返回数据的 NumPy 数组。NumPy 目前不支持时区(尽管它在本地时区打印!),因此对于时区感知数据,返回的是 Timestamps 的对象数组:

s_naive.to_numpy()
Out[500]: 
array(['2013-01-01T00:00:00.000000000', '2013-01-02T00:00:00.000000000',
       '2013-01-03T00:00:00.000000000'], dtype='datetime64[ns]')

s_aware.to_numpy()
Out[501]: 
array([Timestamp('2013-01-01 00:00:00-0500', tz='US/Eastern'),
       Timestamp('2013-01-02 00:00:00-0500', tz='US/Eastern'),
       Timestamp('2013-01-03 00:00:00-0500', tz='US/Eastern')],
      dtype=object)
通过将时间戳转换为对象数组,可以保留时区信息。例如,当转换回 Series 时:

```python
pd.Series(s_aware.to_numpy())
Out[502]: 
0   2013-01-01 00:00:00-05:00
1   2013-01-02 00:00:00-05:00
2   2013-01-03 00:00:00-05:00
dtype: datetime64[ns, US/Eastern]

然而,如果你想要一个实际的 NumPy datetime64[ns] 数组(将值转换为 UTC),而不是一个对象数组,你可以指定 dtype 参数:

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

Pandas 2 使用指南导读

Pandas 2 使用指南:1、十分钟入门Pandas

Pandas 2 使用指南:2、数据结构简介

Pandas 2 使用指南:3、基本功能

Pandas 2 使用指南:4、IO工具(文本、CSV、HDF5等)

Pandas 2 使用指南:5、PyArrow 功能介绍

Pandas 2 使用指南: 6、索引和选择数据

Pandas 2 使用指南:7、多级索引 / 高级索引

Pandas 2 使用指南:8、写时复制(Copy-on-Write,CoW)

Pandas 2 使用指南:9、合并、连接、串联和比较

Pandas 2 使用指南:10、重塑和透视表ReShapingand Pivot Tables

Pandas 2 使用指南:11、处理文本数据 Working with text data

Pandas 2 使用指南:12、处理缺失数据Working with missing data

Pandas 2 使用指南: 13、重复标签 Duplicate Labels

Pandas 2 使用指南:14、分类数据 Categorical data
Pandas 2 使用指南:15、可空整数数据类型、可空布尔数据类型

Pandas 2 使用指南:16、图表可视化

Pandas 2 使用指南:17、表格可视化

Pandas 2 使用指南:18、Groupby:拆分-应用-合并 split-apply-combine

Pandas 2 使用指南:19、窗口操作 Windowing operations

Pandas 2 使用指南:20、时间序列/日期功能
Pandas 2 使用指南:21、时间差 Timedelta

Pandas 2 使用指南:22、选项和设置

Pandas 2 使用指南:23、提升性能 Enhancing performance

Pandas 2 使用指南:24、大规模数据集的扩展

Pandas 2 使用指南:25、稀疏数据结构 Sparse data structures

Pandas 2 使用指南:26、常见问题解答 (FAQ)

Pandas 2 使用指南:27、Cookbook

  • 24
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数智笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值