在pandas dataframe中添加工作日并跳过假期(使用Python)

7
我有一个数据框,其中包含如下表格中所示的日期。第1个块是应该看起来的样子,第2个块是我仅添加BDays时得到的结果。这是一个完成后应该看起来的例子。我想使用第一列,并在日期上加上5个工作日,但如果5个Bday重叠假期(例如21年2月15日),那么我需要再加上一天。使用 pandas.tseries.offsets import BDay 很容易添加5个Bday,但是在使用数据框时无法跳过节假日。
我尝试使用 pandas.tseries.holiday import USFederalHolidayCalendar、workdays 和 workalendar 模块,但无法弄清楚。有人知道我该怎么做吗?
正确的例子
日期 EXIT DATE +5
2021/02/09 2021/02/17 2021/02/10 2021/02/18
错误的例子
日期 EXIT DATE +5
2021/02/09 2021/02/16 2021/02/10 2021/02/17
以下是我尝试的一些代码示例:
import pandas as pd
from workdays import workday
...
df['DATE'] = workday(df['EXIT DATE +5'], days=5, holidays=holidays)

下一个例子:

import pandas as pd
from pandas.tseries.holiday import USFederalHolidayCalendar
bday_us = pd.offsets.CustomBusinessDay(calendar=USFederalHolidayCalendar())
dt = df['DATE']
df['EXIT DATE +5'] = dt + bday_us

=========================================

最终代码:

以下是我最终选择的代码。由于纽约证交所实际交易的日期,例如总统布什去世的那一天,我不得不手动定义节假日。

import datetime as dt
import pandas as pd
from pandas.tseries.holiday import USFederalHolidayCalendar
from pandas.tseries.offsets import BDay

from pandas.tseries.holiday import AbstractHolidayCalendar, Holiday, nearest_workday, \
    USMartinLutherKingJr, USPresidentsDay, GoodFriday, USMemorialDay, \
    USLaborDay, USThanksgivingDay

class USTradingCalendar(AbstractHolidayCalendar):
    rules = [
        Holiday('NewYearsDay', month=1, day=1, observance=nearest_workday),
        USMartinLutherKingJr,
        USPresidentsDay,
        GoodFriday,
        USMemorialDay,
        Holiday('USIndependenceDay', month=7, day=4, observance=nearest_workday),
        Holiday('BushDay', year=2018, month=12, day=5),
        USLaborDay,
        USThanksgivingDay,
        Holiday('Christmas', month=12, day=25, observance=nearest_workday)
    ]

offset = 5

df = pd.DataFrame(['2019-10-11', '2019-10-14', '2017-04-13', '2018-11-28', '2021-07-02'], columns=['DATE'])
df['DATE'] = pd.to_datetime(df['DATE'])

def offset_date(start, offset):
  return start + pd.offsets.CustomBusinessDay(n=offset, calendar=USTradingCalendar())

df['END'] = df.apply(lambda x: offset_date(x['DATE'], offset), axis=1)
print(df)

这些帖子试图回答我的问题,但对于包含日期的数据框架无效。https://dev59.com/Dmcs5IYBdhLWcg3woVh-和https://dev59.com/b7jna4cB1Zd3GeqP_n8Z - dps
你解决了你的问题吗? - xicocaio
并不是很高效。我希望能够像Excel一样处理它,但是我还没有找到方法。你知道吗? - dps
我写了一个答案,这是你需要的吗? - xicocaio
如果你允许我发表一下个人意见:在之前的版本中,你的问题已经很好了。避免在问题文本中添加最终解决方案。回答自己的问题是可以的,但我建议你遵循这些指南。 - xicocaio
1
  • Holiday('六月节日', month=6, day=19, observance=nearest_workday),
- gregV
2个回答

7

输入数据

df = pd.DataFrame(['2021-02-09', '2021-02-10', '2021-06-28', '2021-06-29', '2021-07-02'], columns=['DATE'])
df['DATE'] = pd.to_datetime(df['DATE'])

使用apply方法的建议解决方案

from pandas.tseries.holiday import USFederalHolidayCalendar
from pandas.tseries.offsets import BDay

def offset_date(start, offset):
  return start + pd.offsets.CustomBusinessDay(n=offset, calendar=USFederalHolidayCalendar())

offset = 5
df['END'] = df.apply(lambda x: offset_date(x['DATE'], offset), axis=1)

    DATE        END
    2021-02-09  2021-02-17
    2021-02-10  2021-02-18
    2021-06-28  2021-07-06
    2021-06-29  2021-07-07
    2021-07-02  2021-07-12

注:如果您想使用特定的日历(例如纽约证交所),而不是默认的 USFederalHolidayCalendar,我建议按照此答案中的说明创建一个自定义日历。

替代方法(不推荐使用)

据我所知,目前 pandas 不支持您的问题的向量化处理。但如果您想采用类似于您提到的方法,可以按照以下步骤操作。

首先,您需要定义一个包括所有可能需要的时间段的远期日期 end,并使用它来创建一个假期列表。

holidays = USFederalHolidayCalendar().holidays(start='2021-02-09', end='2030-02-09')

然后,你需要将假期列表通过 holidays 参数传递给 CustomBusinessDay ,而不是使用 calendar 来生成所需的偏移量。

offset = 5
bday_us = pd.offsets.CustomBusinessDay(n=offset, holidays=holidays)
df['END'] = df['DATE'] + bday_us

然而,即使它看起来像是一个真正的矢量化解决方案,这种方法并不是真正的矢量化解决方案。请参见以下SO答案以获取进一步的澄清。在幕后,这种方法可能会执行一种效率不高的转换。这就是为什么它会产生以下警告的原因。

性能警告:正在将非矢量化DateOffset应用于Series或DatetimeIndex


这也可以。我很想看看是否可以在不应用于数据框的情况下实现。 - fthomson
@fthomson 刚刚更新了我的回答。但是回答你的评论,是可以实现的,但这可能不是一个好主意。我在我的回答中提到了你代码中阻止你获得所需结果的小细节。 - xicocaio
我实际上也写了一个解决方案。但是我不知道日期偏移量没有向量化。也许使用applymap / map会有轻微的性能优势? - fthomson
@fthomson Mapapply并没有太大的区别,它们都本质上是略微优化的for循环。我认为你不会获得比这更好的改进。很高兴你的解决方案能够工作。但是,如果性能是一个问题,仅为获取最后一个元素而使用日期范围可能不是最佳方法。根据数据框的大小和日期范围,您可能需要为成千上万行创建相对较大的日期范围,以选择最后一个元素。从这个意义上讲,使用apply和日期偏移量将为您提供更快速和更少内存占用的结果。 - xicocaio
1
@davidp13 非常感谢。此外,日历问题已在其他问题中得到解决,特别是这个。顺便说一句,我的硕士论文是关于交易领域的,所以我对交易日期也有很多麻烦,哈哈。 - xicocaio
显示剩余5条评论

0

这是一种实现方式

import pandas as pd
from pandas.tseries.holiday import USFederalHolidayCalendar
from datetime import timedelta as td

def get_exit_date(date):
    holiday_list = cals.holidays(start=date, end=date + td(weeks=2)).tolist()
    # 6 periods since start date is included in set
    n_bdays = pd.bdate_range(start=date, periods=6, freq='C', holidays=holiday_list)
    return n_bdays[-1]

df = pd.read_clipboard()
cals = USFederalHolidayCalendar()
# I would convert this to datetime
df['DATE'] = pd.to_datetime(df['DATE'])
df['EXIT DATE +5'] = df['DATE'].apply(get_exit_date)

这是使用bdate_range返回日期时间索引

结果:

    DATE    EXIT DATE +5
0   2021-02-09  2021-02-17
1   2021-02-10  2021-02-18

另一个选项是,不必动态创建假日列表。您也可以选择一个开始日期,将其留在函数外部,像这样:
def get_exit_date(date):
    # 6 periods since start date is included in set
    n_bdays = pd.bdate_range(start=date, periods=6, freq='C', holidays=holiday_list)
    return n_bdays[-1]

df = pd.read_clipboard()
cals = USFederalHolidayCalendar()
holiday_list = cals.holidays(start='2021-01-01').tolist()
# I would convert this to datetime
df['DATE'] = pd.to_datetime(df['DATE'])
df['EXIT DATE +5'] = df['DATE'].apply(get_exit_date)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接