赞
踩
datetime64
和
timedelta64
数据类型,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个常见的与时间相关的概念:
datetime.datetime
。datetime.timedelta
。dateutil
包中的dateutil.relativedelta.relativedelta
。概念 | 标量类 | 数组类 | pandas数据类型 | 主要创建方法 |
---|---|---|---|---|
日期时间 | Timestamp | DatetimeIndex | datetime64[ns] 或datetime64[ns, tz] | to_datetime 或date_range |
时间增量 | Timedelta | TimedeltaIndex | timedelta64[ns] | to_timedelta 或timedelta_range |
时间跨度 | Period | PeriodIndex | period[freq] | Period 或period_range |
日期偏移 | DateOffset | None | None | DateOffset |
对于时间序列数据,将时间组件表示为Series
或DataFrame
的索引是常见的做法,以便可以根据时间元素进行操作。
pd.Series(range(3), index=pd.date_range("2000", freq="D", periods=3))
Out[19]:
2000-01-01 0
2000-01-02 1
2000-01-03 2
Freq: D, dtype: int64
但是,Series
和DataFrame
也可以直接支持时间组件作为数据本身。
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]
当将datetime
、timedelta
和Period
数据传递给这些构造函数时,Series
和DataFrame
具有扩展的数据类型支持和功能。但是,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
时间戳数据是与时间点相关联的最基本类型的时间序列数据。对于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')
Timestamp
和Period
都可以用作索引。Timestamp
和Period
的列表会自动转换为DatetimeIndex
和PeriodIndex
。
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
不是严格的。如果日期不能以日期为首进行解析,它将被解析为dayfirst
为False
,并且还会引发警告。
如果将单个字符串传递给to_datetime
,它将返回单个Timestamp
。Timestamp
也可以接受字符串输入,但它不接受像dayfirst
或format
这样的字符串解析选项,因此如果需要这些选项,请使用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
参数来确保特定的解析方式。这样做还可以大大加快转换速度。
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
传递给 to_datetime
,以组装为 Timestamps
的 Series
。
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
会查找列名中日期时间组件的标准指定,包括:
year
、month
、day
hour
、minute
、second
、millisecond
、microsecond
、nanosecond
默认行为 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)
pandas 支持将整数或浮点数的 epoch 时间转换为 Timestamp
和 DatetimeIndex
。默认单位是纳秒,因为 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 时间戳构造 Timestamp
或 DatetimeIndex
会引发 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')
另请参阅
要反转上面的操作,即从 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)
要生成带有时间戳的索引,可以使用 DatetimeIndex
或 Index
构造函数,并传入一个日期时间对象的列表:
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_range
和 bdate_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_range
和 bdate_range
可以根据 start
、end
、periods
和 freq
等参数的不同组合生成一系列日期。起始日期和结束日期是严格包含的,因此不在指定范围内的日期将不会生成:
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')
通过指定 start
、end
和 periods
参数,可以生成从 start
到 end
的一系列均匀间隔的日期,其中 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
还可以通过使用 weekmask
和 holidays
参数生成自定义频率的日期范围。这些参数仅在传递自定义频率字符串时使用。
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
类包含许多与时间序列相关的优化功能:
shift
方法进行快速移位。DatetimeIndex
对象的联合非常快速(对于快速数据对齐很重要)。year
、month
等属性快速访问日期字段。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
这种切片方法也适用于具有DatetimeIndex
的DataFrame
。由于部分字符串选择是一种标签切片的形式,所以端点将被包括在内。这将包括在包含日期上匹配的时间:
警告
使用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
部分字符串索引也适用于具有MultiIndex
的DataFrame
:
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
取决于期间的“准确性”,换句话说,与索引的分辨率相比,间隔有多具体。相比之下,使用Timestamp
或datetime
对象进行索引是精确的,因为这些对象具有确切的含义。这些对象也遵循包括两个端点的语义。
这些Timestamp
和datetime
对象具有确切的小时,分钟
和秒
,即使它们没有明确指定(它们为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)
可以从Timestamp
或DatetimeIndex
等时间戳集合中访问多个时间/日期属性。
属性 | 描述 |
---|---|
year | 年份 |
month | 月份 |
day | 日 |
hour | 小时 |
minute | 分钟 |
second | 秒 |
microsecond | 微秒 |
nanosecond | 纳秒 |
date | 返回datetime.date (不包含时区信息) |
time | 返回datetime.time (不包含时区信息) |
timetz | 返回带有时区信息的本地时间datetime.time |
dayofyear | 一年中的第几天 |
day_of_year | 一年中的第几天 |
weekofyear | 一年中的第几周 |
week | 一年中的第几周 |
dayofweek | 一周中的第几天,星期一为0,星期日为6 |
day_of_week | 一周中的第几天,星期一为0,星期日为6 |
weekday | 一周中的第几天,星期一为0,星期日为6 |
quarter | 季度,1表示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
在前面的示例中,使用频率字符串(例如'D'
)来指定定义:
date_range()
时,DatetimeIndex
中的日期时间如何排列Period
或 PeriodIndex
的频率这些频率字符串映射到一个 DateOffset
对象及其子类。DateOffset
类似于 Timedelta
,表示一段时间的持续时间,但遵循特定的日历持续时间规则。例如,Timedelta
的一天始终会使日期时间增加 24 小时,而 DateOffset
的一天会使日期时间增加到下一天的相同时间,无论一天是由于夏令时而表示 23、24 还是 25 小时。然而,所有小于或等于一小时的 DateOffset
子类(Hour
、Minute
、Second
、Milli
、Micro
、Nano
)的行为类似于 Timedelta
,并且遵守绝对时间。
基本的 DateOffset
类似于 dateutil.relativedelta
(relativedelta 文档),它将日期时间按指定的日历持续时间进行偏移。可以使用算术运算符(+
)来执行偏移。
# 这一天包含了夏令时的转换 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 小时 |
BDay 或 BusinessDay | 'B' | 工作日(工作日) |
CDay 或 CustomBusinessDay | 'C' | 自定义工作日 |
Week | 'W' | 一周,可选择以一周的某一天为锚点 |
WeekOfMonth | 'WOM' | 每月的第 x 天的第 y 周 |
LastWeekOfMonth | 'LWOM' | 每月的最后一周的第 x 天 |
MonthEnd | 'ME' | 月末 |
MonthBegin | 'MS' | 月初 |
BMonthEnd 或 BusinessMonthEnd | 'BME' | 工作月末 |
BMonthBegin 或 BusinessMonthBegin | 'BMS' | 工作月初 |
CBMonthBegin 或 CustomBusinessMonthBegin | '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周)年 |
Easter | None | 复活节假期 |
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
偏移量可以与 Series
或 DatetimeIndex
一起使用,将偏移量应用于每个元素。
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]
如果偏移量类直接映射到 Timedelta
(Day
、Hour
、Minute
、Second
、Micro
、Milli
、Nano
),则可以像使用 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)
CDay
或 CustomBusinessDay
类提供了一个参数化的 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')
您还可以通过关键字指定start
和end
时间。参数必须是具有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.rollforward
和rollback
应用于工作时间之外的时间会导致下一个工作小时的开始或前一天的结束。与其他偏移量不同,根据定义,BusinessHour.rollforward
的输出可能与apply
产生不同的结果。
这是因为一天的工作小时结束时间等于下一天的工作小时开始时间。例如,在默认的工作小时(9:00 - 17:00)下,2014-08-01 17:00
和2014-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
偏移量,如下一小节所述。
CustomBusinessHour
是BusinessHour
和CustomBusinessDay
的混合体,允许您指定任意假日。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')
您可以使用BusinessHour
和CustomBusinessDay
支持的关键字参数。
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起已弃用:*别名H
、BH
、CBH
、T
、S
、L
、U
和N
已弃用,而别名h
、bh
、cbh
、min
、s
、ms
、us
和ns
则取而代之。
注意
例如,对于偏移量 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_date
和 end_date
之间的有效时间戳。如果这些对于给定频率来说不是有效的时间戳,它们将滚动到下一个 start_date
的值(对于 end_date
来说是上一个值)。
一些常用的时间序列频率有一些字符串别名。我们将这些别名称为周期别名。
别名 | 描述 |
---|---|
B | 工作日频率 |
D | 日历日频率 |
W | 周频率 |
M | 月频率 |
Q | 季度频率 |
Y | 年频率 |
h | 小时频率 |
min | 分钟频率 |
s | 秒频率 |
ms | 毫秒 |
us | 微秒 |
ns | 纳秒 |
自版本2.2.0起已弃用: 别名 A
、H
、T
、S
、L
、U
和 N
已弃用,推荐使用别名 Y
、h
、min
、s
、ms
、us
和 ns
。
正如我们之前看到的,别名和偏移实例在大多数函数中是可互换的:
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_range
、bdate_range
、DatetimeIndex
的构造函数的参数,以及 pandas 中其他与时间序列相关的函数的参数。
对于那些锚定在特定频率(MonthEnd
、MonthBegin
、WeekEnd
等)的偏移量,以下规则适用于向前和向后滚动。
当 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_date
和 end_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
或添加到 datetime
或 Timestamp
对象中。
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')
范围由 AbstractHolidayCalendar
的 start_date
和 end_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
与 asfreq
和 reindex
相关的是 fillna()
方法,在 缺失数据部分 中有文档说明。
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
函数非常灵活,允许您指定许多不同的参数来控制频率转换和重新采样操作。
返回的对象的任何内置方法都可以作为方法使用,包括 sum
、mean
、std
、sem
、max
、min
、median
、first
、last
、ohlc
:
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_method
是 None
,那么中间值将被填充为 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__
以获取更多信息。
origin
或offset
调整分组的起始位置分组的区间根据时间序列起始点的当天开始时间进行调整。这在频率是一天的倍数(如30D
)或能够整除一天的频率(如90s
或1min
)时效果良好。但是,对于一些不满足这些条件的频率,可能会出现不一致的情况。要更改此行为,可以使用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
表示一段时间(例如一天、一个月、一个季度等)。可以使用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
的频率是每天或更高(D
、h
、min
、s
、ms
、us
和ns
),则可以添加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>
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]')
如果start
或end
是Period
对象,则它们将用作与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(...)
中使用。它允许更改 PeriodIndex
的 freq
,就像 .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 现在支持使用非单调索引进行部分字符串切片。
您可以像 DatetimeIndex
一样,通过日期和字符串将其传递给 Series
和 DataFrame
,详细信息请参见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]
可以通过 asfreq
方法将 Period
和 PeriodIndex
的频率进行转换。让我们从截止到 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-JAN
到 Q-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
和/或 Series
的 Periods
来进行计算。
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 提供了丰富的支持,可以使用 pytz
和 dateutil
库,或者使用标准库中的 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()
、Timestamp
或 DatetimeIndex
中使用 tz
关键字参数。可以传递 pytz
或 dateutil
的时区对象,或者 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>
警告
注意在不同库之间进行转换时要小心。对于某些时区,pytz
和 dateutil
对该时区的定义可能不同。这对于不常见的时区比“标准”时区(如 US/Eastern
)更成问题。
警告
请注意,跨不同版本的时区库的时区定义可能不被视为相等。这可能会在使用一个版本本地化的存储数据并在另一个版本上操作时导致问题。有关如何处理这种情况,请参见这里。
警告
对于 pytz
时区,直接将时区对象传递给 datetime.datetime
构造函数是不正确的(例如,datetime.datetime(2011, 1, 1, tzinfo=pytz.timezone('US/Eastern'))
)。相反,需要使用 pytz
时区对象上的 localize
方法对日期进行本地化。
警告
请注意,对于未来的时间,任何时区库都无法保证在时区之间(和 UTC 之间)进行正确转换,因为时区的 UTC 偏移量可能会被相应的政府更改。
警告
如果您使用的是 2038-01-18 之后的日期,由于底层库的当前缺陷导致的 2038 年问题,时区感知的日期不会应用夏令时(DST)调整。如果底层库被修复,DST 转换将会应用。
例如,对于两个处于英国夏令时的日期(通常为 GMT+1),以下两个断言都为真:
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 存储。来自时区感知的 DatetimeIndex
或 Timestamp
的值将其字段(天、小时、分钟等)本地化到时区。但是,具有相同 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)
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
bool
:True
表示 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'
:将不存在的时间向后移动到最接近的真实时间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)
具有时区无关值的 Series
以 datetime64[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]
具有时区感知值的 Series
以 datetime64[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 使用指南:4、IO工具(文本、CSV、HDF5等)
Pandas 2 使用指南:8、写时复制(Copy-on-Write,CoW)
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 使用指南:18、Groupby:拆分-应用-合并 split-apply-combine
Pandas 2 使用指南:19、窗口操作 Windowing operations
Pandas 2 使用指南:20、时间序列/日期功能
Pandas 2 使用指南:21、时间差 Timedelta
Pandas 2 使用指南:23、提升性能 Enhancing performance
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。