我正在处理一个包含约2600万行和13列的数据集,其中包括两个datetime列arr_date和dep_date。我正在尝试创建一个新的布尔值列,以检查这些日期之间是否有美国假期。 我正在对整个数据框使用apply函数,但是执行时间太慢。该代码现已在Goolge Cloud Platform(24GB ram,4核)上运行了48多个小时。有更快的方法吗?
数据集如下所示: Sample data
我正在使用的代码是-
import pandas as pd
import numpy as np
from pandas.tseries.holiday import USFederalHolidayCalendar as calendar
df = pd.read_pickle('dataGT70.pkl')
cal = calendar()
def mark_holiday(df):
df.apply(lambda x: True if (len(cal.holidays(start=x['dep_date'], end=x['arr_date']))>0 and x['num_days']<20) else False, axis=1)
return df
df = mark_holiday(df)
答案 0 :(得分:1)
您是否已经考虑过为此使用pandas.merge_asof
?
我可以想象带有lambda函数的map
和apply
不能高效地执行。
更新:抱歉,我刚刚读到,如果之间有任何假期,则只需要一个布尔值,这使操作变得容易得多。如果足够,您只需要执行第1-5步,然后按开始/结束日期对作为第5步的结果的DataFrame进行分组,并使用count作为汇总函数即可使范围内的节假日数成为可能。您可以将这个结果加入到原始数据集,类似于下面描述的步骤8。然后用fillna(0)
填充其余的值。做类似joined_df['includes_holiday']= joined_df['joined_count_column']>0
的事情。之后,您可以根据需要再次从数据框中删除joined_count_column
。
如果您使用pandas_merge_asof
,则可以按照以下步骤进行操作(仅当您需要同时在结果DataFrame的开始和结束之间(不仅是布尔值)具有所有假期时,才需要执行步骤6和7):
merge_asof
(如果在期间开始时加入,请使用direction='forward'
,如果使用结束日期,请使用direction='backward'
和{{1} }。how='inner'
的内容。现在,您应该插入一个数字列,为从1到2的各个期间(在步骤5之后获得的假日)之间的假日编号。 (对于从1开始的每个期间)。在下一步中使用unstack来获取假期在列中是必要的。df.sort_values(['start', 'end', 'holiday'], inplace=True)
。您现在拥有的是一个精简的DataFrame,其中包含您的原始时间段,假期按列排列。df.unstack(level=-1)
此操作的结果是一个文件,该文件的原始数据包含日期范围,对于每个日期范围,期间之间的假日存储在数据后面的单独列中。粗略地说,第6步中的编号将假日分配给各列,并且具有以下效果:假日总是从右到左分配给各列(如果第1列为空,则不会在第3列有假日)。 / p>
步骤6可能也有些棘手,但是您可以这样做,例如,添加一个填充有范围的序列,然后对其进行修复,因此可以使用{{1}在每个组中从0或1开始编号。 }或按开头分组,以original_df.merge(df_from_step7, left_on=['start', 'end'], right_index=True, how='left')
结尾,然后将结果结合起来,以从范围序列分配的值中减去。
总的来说,我认为它听起来要复杂得多,应该高效执行。特别是如果您的周期不那么大,因为在步骤5之后,您的结果集应该比原始数据帧小得多,但是即使不是这种情况,它也应该非常有效,因为它可以使用编译后的代码。
答案 1 :(得分:0)
这花了我大约两分钟的时间来运行一个包含两列start_date
和end_date
的30m行的示例数据帧。
该想法是获取在最小开始日期或之后发生的所有假期的排序列表,然后使用bisect
模块中的bisect_left
确定在每个假期或之后发生的下一个假期开始日期。然后将该假期与结束日期进行比较。如果它小于或等于结束日期,则在开始日期和结束日期之间(包括两端),必须在日期范围内至少有一个假期。
from bisect import bisect_left
import pandas as pd
from pandas.tseries.holiday import USFederalHolidayCalendar as calendar
# Create sample dataframe of 10k rows with an interval of 1-19 days.
np.random.seed(0)
n = 10000 # Sample size, e.g. 10k rows.
years = np.random.randint(2010, 2019, n)
months = np.random.randint(1, 13, n)
days = np.random.randint(1, 29, n)
df = pd.DataFrame({'start_date': [pd.Timestamp(*x) for x in zip(years, months, days)],
'interval': np.random.randint(1, 20, n)})
df['end_date'] = df['start_date'] + pd.TimedeltaIndex(df['interval'], unit='d')
df = df.drop('interval', axis=1)
# Get a sorted list of holidays since the fist start date.
hols = calendar().holidays(df['start_date'].min())
# Determine if there is a holiday between the start and end dates (both inclusive).
df['holiday_in_range'] = df['end_date'].ge(
df['start_date'].apply(lambda x: bisect_left(hols, x)).map(lambda x: hols[x]))
>>> df.head(6)
start_date end_date holiday_in_range
0 2015-07-14 2015-07-31 False
1 2010-12-18 2010-12-30 True # 2010-12-24
2 2013-04-06 2013-04-16 False
3 2013-09-12 2013-09-24 False
4 2017-10-28 2017-10-31 False
5 2013-12-14 2013-12-29 True # 2013-12-25
因此,对于给定的start_date
时间戳记(例如2013-12-14
),bisect_right(hols, '2013-12-14')
将产生39,而hols [39]将产生2013-12-25
,下一个假期将落在或在2013-12-14
开始日期之后。下一个假期计算为df['start_date'].apply(lambda x: bisect_left(hols, x)).map(lambda x: hols[x])
。然后将此假期与end_date
进行比较,如果holiday_in_range
大于或等于该假期值,则True
为end_date
,否则假期必须在此之后end_date
。