Pandas:即使对于已经在月末的日期,也能正确获取业务月末日期
Pandas: Get Business Month-End Dates Correctly Even for Dates Already on Month-End
假设我有如下数据框
date,ent_id,val
2021-03-23,101,61
2021-03-12,103,64
2021-03-15,101,32
2021-04-01,103,39
2021-04-02,101,71
2021-04-02,103,79
2021-04-30,101,51
2021-04-30,103,53
2021-05-31,101,28
2021-05-31,103,26
2021-05-31,101,47
2021-05-31,103,61
2021-06-06,101,45
2021-06-06,103,78
2021-06-07,101,23
2021-06-07,103,31
2021-07-31,101,14
2021-07-31,103,02
2021-07-31,101,82
2021-07-31,103,15
我想在包含月末日期的数据框中创建一个附加列
基于以下条件
case
when DAYNAME('date')='Sunday' then days_add(date,-2)
when DAYNAME('date')='Saturday' then days_add(date,-1)
else date
所以输出会像这样
date,ent_id,val,month_end
2021-03-23,101,61,2021-03-31
2021-03-12,103,64,2021-03-31
2021-03-15,101,32,2021-03-31
2021-04-01,103,39,2021-04-30
2021-04-02,101,71,2021-04-30
2021-04-02,103,79,2021-04-30
2021-04-30,101,51,2021-04-30
2021-04-30,103,53,2021-04-30
2021-05-31,101,28,2021-05-31
2021-05-31,103,26,2021-05-31
2021-05-31,101,47,2021-05-31
2021-05-31,103,61,2021-05-31
2021-06-06,101,45,2021-06-30
2021-06-06,103,78,2021-06-30
2021-06-07,101,23,2021-06-30
2021-06-07,103,31,2021-06-30
2021-07-31,101,14,2021-07-31
2021-07-31,103,02,2021-07-31
2021-07-31,101,82,2021-07-31
2021-07-31,103,15,2021-07-31
我的努力
import pandas as pd
from datetime import timedelta
from pandas.tseries.offsets import MonthEnd
import numpy as np
df.loc[(df['date']+MonthEnd(0)).dt.day_name()=='Sunday','month_end'] =[df.loc[(df['date']+MonthEnd(0)).dt.day_name()=='Sunday']['date']+timedelta(days=-2)]
df.loc[(df['date']+MonthEnd(0)).dt.day_name()=='Saturday','month_end'] =[df.loc[(df['date']+MonthEnd(0)).dt.day_name()=='Saturday']['date']+timedelta(days=-1)]
但收到此错误
ValueError: Must have equal len keys and value when setting with an ndarray
欢迎任何其他更好的解决方案
这应该有效:
df['month_end'] = df.date + pd.offsets.MonthEnd(n=0)
df.loc[df.index.day_name()=='Sunday', 'month_end'] -= pd.DateOffset(days=2)
df.loc[df.index.day_name()=='Saturday', 'month_end'] -= pd.DateOffset(days=1)
请注意,如果您对当月的最后一个工作日感兴趣,您可以简单地执行以下操作:
df['month_end'] = df.date + pd.offsets.BusinessMonthEnd(n=0)
您可以使用 pd.offsets.MonthEnd(n=0)
然后调整星期日和星期六。请注意,n=0
是必需的,否则 2021-07-31 将前滚到 2021-08-31。
df['month_end'] = df['date'] + pd.offsets.MonthEnd(n=0)
df.loc[df['month_end'].dt.day_name() == 'Sunday', 'month_end'] -= pd.DateOffset(2)
df.loc[df['month_end'].dt.day_name() == 'Saturday', 'month_end'] -= pd.DateOffset(1)
print(df)
date ent_id val month_end
0 2021-03-23 101 61 2021-03-31
1 2021-03-12 103 64 2021-03-31
2 2021-03-15 101 32 2021-03-31
3 2021-04-01 103 39 2021-04-30
4 2021-04-02 101 71 2021-04-30
5 2021-04-02 103 79 2021-04-30
6 2021-04-30 101 51 2021-04-30
7 2021-04-30 103 53 2021-04-30
8 2021-05-31 101 28 2021-05-31
9 2021-05-31 103 26 2021-05-31
10 2021-05-31 101 47 2021-05-31
11 2021-05-31 103 61 2021-05-31
12 2021-06-06 101 45 2021-06-30
13 2021-06-06 103 78 2021-06-30
14 2021-06-07 101 23 2021-06-30
15 2021-06-07 103 31 2021-06-30
16 2021-07-31 101 14 2021-07-30
17 2021-07-31 103 2 2021-07-30
18 2021-07-31 101 82 2021-07-30
19 2021-07-31 103 15 2021-07-30
请注意,虽然您似乎想获得一个月的最后营业日期,但我们不能简单地使用pd.offsets.BMonthEnd(n=0)
,因为 2021-07-31 仍将是前滚至 2021-08-31。
编辑:使用 BMonthEnd 的解决方案
当我第一次看到这个问题时,我的脑海里立刻浮现出的是,也许我们可以利用pd.offsets.BMonthEnd
来解决这个问题。
据我所知,对于 pd.offsets.MonthEnd
,我们可以使用参数 n=0
(或简称 0
)来确保如果在锚点(在如果日期已经在这种情况下的月末日期,则为锚点)。因此,我最初的尝试是使用pd.offsets.BMonthEnd(n=0)
。令我惊讶的是,它的行为与 MonthEnd 的对应物不同。 2021-07-31
的日期仍向前滚动到 2021-08-31
。
由于这种问题很常见,我想提供一个解决方法,让我们仍然可以使用BMonthEnd
,而不是提供代码来检查和修改周日和周六。
这是使 BMonthEnd
的行为类似于 MonthEnd(n=0)
的解决方法代码:
df['business_month_end'] = df['date'] + pd.offsets.MonthEnd(0) - pd.offsets.MonthBegin() + pd.offsets.BMonthEnd()
这里,MonthEnd(0)
等同于MonthEnd(n=0)
,而MonthEnd()
和BMonthEnd()
不传参数等同于传n=1
(默认) .
机制是我们借用了MonthEnd(n=0)
的特性,即使在锚点上也保持锚定,并得到该日期的月初(应该是同月的第一个日期),然后应用BMonthEnd
函数让我们获取同月的最后一个营业日期(如果日期落在星期日和星期六,则进行调整)。
假设我有如下数据框
date,ent_id,val
2021-03-23,101,61
2021-03-12,103,64
2021-03-15,101,32
2021-04-01,103,39
2021-04-02,101,71
2021-04-02,103,79
2021-04-30,101,51
2021-04-30,103,53
2021-05-31,101,28
2021-05-31,103,26
2021-05-31,101,47
2021-05-31,103,61
2021-06-06,101,45
2021-06-06,103,78
2021-06-07,101,23
2021-06-07,103,31
2021-07-31,101,14
2021-07-31,103,02
2021-07-31,101,82
2021-07-31,103,15
我想在包含月末日期的数据框中创建一个附加列 基于以下条件
case
when DAYNAME('date')='Sunday' then days_add(date,-2)
when DAYNAME('date')='Saturday' then days_add(date,-1)
else date
所以输出会像这样
date,ent_id,val,month_end
2021-03-23,101,61,2021-03-31
2021-03-12,103,64,2021-03-31
2021-03-15,101,32,2021-03-31
2021-04-01,103,39,2021-04-30
2021-04-02,101,71,2021-04-30
2021-04-02,103,79,2021-04-30
2021-04-30,101,51,2021-04-30
2021-04-30,103,53,2021-04-30
2021-05-31,101,28,2021-05-31
2021-05-31,103,26,2021-05-31
2021-05-31,101,47,2021-05-31
2021-05-31,103,61,2021-05-31
2021-06-06,101,45,2021-06-30
2021-06-06,103,78,2021-06-30
2021-06-07,101,23,2021-06-30
2021-06-07,103,31,2021-06-30
2021-07-31,101,14,2021-07-31
2021-07-31,103,02,2021-07-31
2021-07-31,101,82,2021-07-31
2021-07-31,103,15,2021-07-31
我的努力
import pandas as pd
from datetime import timedelta
from pandas.tseries.offsets import MonthEnd
import numpy as np
df.loc[(df['date']+MonthEnd(0)).dt.day_name()=='Sunday','month_end'] =[df.loc[(df['date']+MonthEnd(0)).dt.day_name()=='Sunday']['date']+timedelta(days=-2)]
df.loc[(df['date']+MonthEnd(0)).dt.day_name()=='Saturday','month_end'] =[df.loc[(df['date']+MonthEnd(0)).dt.day_name()=='Saturday']['date']+timedelta(days=-1)]
但收到此错误
ValueError: Must have equal len keys and value when setting with an ndarray
欢迎任何其他更好的解决方案
这应该有效:
df['month_end'] = df.date + pd.offsets.MonthEnd(n=0)
df.loc[df.index.day_name()=='Sunday', 'month_end'] -= pd.DateOffset(days=2)
df.loc[df.index.day_name()=='Saturday', 'month_end'] -= pd.DateOffset(days=1)
请注意,如果您对当月的最后一个工作日感兴趣,您可以简单地执行以下操作:
df['month_end'] = df.date + pd.offsets.BusinessMonthEnd(n=0)
您可以使用 pd.offsets.MonthEnd(n=0)
然后调整星期日和星期六。请注意,n=0
是必需的,否则 2021-07-31 将前滚到 2021-08-31。
df['month_end'] = df['date'] + pd.offsets.MonthEnd(n=0)
df.loc[df['month_end'].dt.day_name() == 'Sunday', 'month_end'] -= pd.DateOffset(2)
df.loc[df['month_end'].dt.day_name() == 'Saturday', 'month_end'] -= pd.DateOffset(1)
print(df)
date ent_id val month_end
0 2021-03-23 101 61 2021-03-31
1 2021-03-12 103 64 2021-03-31
2 2021-03-15 101 32 2021-03-31
3 2021-04-01 103 39 2021-04-30
4 2021-04-02 101 71 2021-04-30
5 2021-04-02 103 79 2021-04-30
6 2021-04-30 101 51 2021-04-30
7 2021-04-30 103 53 2021-04-30
8 2021-05-31 101 28 2021-05-31
9 2021-05-31 103 26 2021-05-31
10 2021-05-31 101 47 2021-05-31
11 2021-05-31 103 61 2021-05-31
12 2021-06-06 101 45 2021-06-30
13 2021-06-06 103 78 2021-06-30
14 2021-06-07 101 23 2021-06-30
15 2021-06-07 103 31 2021-06-30
16 2021-07-31 101 14 2021-07-30
17 2021-07-31 103 2 2021-07-30
18 2021-07-31 101 82 2021-07-30
19 2021-07-31 103 15 2021-07-30
请注意,虽然您似乎想获得一个月的最后营业日期,但我们不能简单地使用pd.offsets.BMonthEnd(n=0)
,因为 2021-07-31 仍将是前滚至 2021-08-31。
编辑:使用 BMonthEnd 的解决方案
当我第一次看到这个问题时,我的脑海里立刻浮现出的是,也许我们可以利用pd.offsets.BMonthEnd
来解决这个问题。
据我所知,对于 pd.offsets.MonthEnd
,我们可以使用参数 n=0
(或简称 0
)来确保如果在锚点(在如果日期已经在这种情况下的月末日期,则为锚点)。因此,我最初的尝试是使用pd.offsets.BMonthEnd(n=0)
。令我惊讶的是,它的行为与 MonthEnd 的对应物不同。 2021-07-31
的日期仍向前滚动到 2021-08-31
。
由于这种问题很常见,我想提供一个解决方法,让我们仍然可以使用BMonthEnd
,而不是提供代码来检查和修改周日和周六。
这是使 BMonthEnd
的行为类似于 MonthEnd(n=0)
的解决方法代码:
df['business_month_end'] = df['date'] + pd.offsets.MonthEnd(0) - pd.offsets.MonthBegin() + pd.offsets.BMonthEnd()
这里,MonthEnd(0)
等同于MonthEnd(n=0)
,而MonthEnd()
和BMonthEnd()
不传参数等同于传n=1
(默认) .
机制是我们借用了MonthEnd(n=0)
的特性,即使在锚点上也保持锚定,并得到该日期的月初(应该是同月的第一个日期),然后应用BMonthEnd
函数让我们获取同月的最后一个营业日期(如果日期落在星期日和星期六,则进行调整)。