将 `pandas` 频率字符串转换为 `DateOffset`

Posted

技术标签:

【中文标题】将 `pandas` 频率字符串转换为 `DateOffset`【英文标题】:Convert `pandas` frequency string to `DateOffset` 【发布时间】:2021-04-29 14:28:13 【问题描述】:

我有一个时区感知pandasDateTimeIndex,我想将其提前一个时间步长,时间步长由其.freq 属性指定。但是,这样做不尊重时区信息:

import pandas as pd
i = pd.date_range('2020-03-28', freq='D', periods=3, tz='Europe/Amsterdam')
# DatetimeIndex(['2020-03-28 00:00:00+01:00', '2020-03-29 00:00:00+01:00',
#                '2020-03-30 00:00:00+02:00'],
#               dtype='datetime64[ns, Europe/Amsterdam]', freq='D')

i + i.freq
# Not what I want; second timestamp is advanced by 24h instead of 23h and is no longer at midnight:
# DatetimeIndex(['2020-03-29 00:00:00+01:00', '2020-03-30 01:00:00+02:00',
#                '2020-03-31 00:00:00+02:00'],
#               dtype='datetime64[ns, Europe/Amsterdam]', freq='D')

的作用是使用pd.DateOffset:

i + pd.DateOffset(days=1)
# What I want; all timestamps at midnight (I just need to re-set the .freq attribute):
# DatetimeIndex(['2020-03-29 00:00:00+01:00', '2020-03-30 00:00:00+02:00',
#                '2020-03-31 00:00:00+02:00'],
#               dtype='datetime64[ns, Europe/Amsterdam]', freq=None)

但是,由于我事先不知道索引的频率是多少,我想使用i.freq 的值来获得正确的DateOffset。有没有办法做到这一点? (除了使用长 if... elif... elif... 块。)

当然也欢迎其他解决方案。

This 是我发现的与此相关的唯一其他问题,但我不能在这里使用它:

i + pd.tseries.frequencies.to_offset(i.freq)
# Not what I want:
# DatetimeIndex(['2020-03-29 00:00:00+01:00', '2020-03-30 01:00:00+02:00',
#                '2020-03-31 00:00:00+02:00'],
#               dtype='datetime64[ns, Europe/Amsterdam]', freq='D')

(实际上,后一项返回的正是i.freq。)

非常感谢。

编辑 (1)

正如 cmets 中所建议的那样,在某些情况下使用 .shift(1) 是有效的,包括我上面所说的情况......

i.shift(1)
# What I want; all timestamps at midnight:
# DatetimeIndex(['2020-03-29 00:00:00+01:00', '2020-03-30 00:00:00+02:00',
#                '2020-03-31 00:00:00+02:00'],
#               dtype='datetime64[ns, Europe/Amsterdam]', freq='D')

...但不是全部。事实上,将原始索引中的开始日期提前一天会导致时间戳被丢弃,其余的都是错误的:

i2 = pd.date_range('2020-03-29', freq='D', periods=3, tz='Europe/Amsterdam')
# DatetimeIndex(['2020-03-29 00:00:00+01:00', '2020-03-30 00:00:00+02:00',
#               '2020-03-31 00:00:00+02:00'],
#              dtype='datetime64[ns, Europe/Amsterdam]', freq='D')

i2.shift(1)
# Not what I want: timestamps not at midnight, and one got dropped!
# DatetimeIndex(['2020-03-30 01:00:00+02:00', '2020-03-31 01:00:00+02:00'],
#               dtype='datetime64[ns, Europe/Amsterdam]', freq='D')

编辑(2)

正如@MrFruppes 的回答所建议的那样,使用i.freq.nanos 属性作为pd.DateOffset 的输入...

i + pd.DateOffset(nanoseconds=i.freq.nanos)
# What I want; all timestamps at midnight (I just need to re-set the .freq attribute):
# DatetimeIndex(['2020-03-29 00:00:00+01:00', '2020-03-30 00:00:00+02:00',
#                '2020-03-31 00:00:00+02:00'],
#               dtype='datetime64[ns, Europe/Amsterdam]', freq=None)

...但是当我们尝试前进到下月初时它会中断:

i3 = pd.date_range('2020-03-01', freq='MS', periods=3, tz='Europe/Amsterdam')
# DatetimeIndex(['2020-03-01 00:00:00+01:00', '2020-04-01 00:00:00+02:00',
#                '2020-05-01 00:00:00+02:00'],
#               dtype='datetime64[ns, Europe/Amsterdam]', freq='MS')

i3 + pd.DateOffset(nanoseconds=i3.freq.nanos)
Traceback (most recent call last):

  File "<ipython-input-58-f3a32c654a6e>", line 1, in <module>
    i3 + pd.DateOffset(nanoseconds=i3.freq.nanos)

  File "pandas\_libs\tslibs\offsets.pyx", line 690, in pandas._libs.tslibs.offsets.BaseOffset.nanos.__get__

ValueError: <MonthBegin> is a non-fixed frequency

【问题讨论】:

你可能想多了。您可以使用shift 来移动数据,而不是移动索引。 该死的。 2 天的尝试和测试在 1 分钟内得到答复。但是,它有效!非常感谢! :) 啊,我发现了一个不起作用的情况;我将附加到问题中。 【参考方案1】:

如果你有一个固定的频率,你可以使用频率的nanos 属性。例如:

import pandas as pd
i = pd.date_range('2020-03-29', freq='D', periods=3, tz='Europe/Amsterdam')
# DatetimeIndex(['2020-03-29 00:00:00+01:00', '2020-03-30 00:00:00+02:00',
#               '2020-03-31 00:00:00+02:00'],
#              dtype='datetime64[ns, Europe/Amsterdam]', freq='D')

i + pd.DateOffset(nanoseconds=i.freq.nanos)
# DatetimeIndex(['2020-03-30 00:00:00+02:00', '2020-03-31 00:00:00+02:00',
#                '2020-04-01 00:00:00+02:00'],
#               dtype='datetime64[ns, Europe/Amsterdam]', freq=None)

【讨论】:

感谢您的回答@MrFuppes。它似乎适用于 D 频率...但我的单元测试因 `ValueError: is a non-fixed frequency` 而中断 MS 频率 @ElRudi,是的,刚刚滚动浏览了src,也发现了...无论如何,如果有人只有固定的频率,我会留下答案。或者可以使用它来获得更好的解决方案;-)【参考方案2】:

pd.DateOffset 也不是通用的。这是我目前通过所有单元测试的,但我愿意改进:

if i.tz is None:
    raise AttributeError("Index is missing timezone information.")

# Get right timestamp for each index value, based on the frequency.
# . This one breaks for 'MS':
# i + pd.DateOffset(nanoseconds=i.freq.nanos)
# . This drops a value at some DST transitions:
# i.shift(1)
# . This one gives wrong value at DST transitions:
# i + i.freq

if i.freq == "15T": # period length always the same
    ts_right = i + pd.Timedelta(hours=0.25)
elif i.freq == "H": # period length always the same
    ts_right = i + pd.Timedelta(hours=1)
else:               # period length dependent on calendar
    if i.freq == "D":
        kwargs = "days": 1
    elif i.freq == "MS":
        kwargs = "months": 1
    elif i.freq == "QS":
        kwargs = "months": 3
    elif i.freq == "AS":
        kwargs = "years": 1
    else:
        raise ValueError(f"Invalid frequency: i.freq.")
    ts_right = i + pd.DateOffset(**kwargs)

(我只实现了与我的用例相关的 .freq 值。)

【讨论】:

以上是关于将 `pandas` 频率字符串转换为 `DateOffset`的主要内容,如果未能解决你的问题,请参考以下文章

时间序列--日期的范围频率及移动

如何在 pandas python 中将字符串转换为日期时间格式?

如何将 Pandas 数据框中的字符串转换为“日期”数据类型?

Python数据分析pandas日期范围date_range

将 datetime.date 的表示形式转换为 pandas.Timestamp 的表示形式

如何使用空值将字符串转换为日期时间 - python,pandas?