我有一个如下的数据框
df = pd.DataFrame({"date": pd.date_range(start="2012-03-01", end="2012-03-05"),
"date+1": pd.date_range(start="2012-03-02", end="2012-03-06"),
"date+2": pd.date_range(start="2012-03-03", end="2012-03-07")})
我还有另一个数据框,表示具有开始日期和结束日期的事件,如下所示。
event = pd.DataFrame({"event": ["A", "B"],
"start": ["2012-03-02", "2012-03-04"],
"end": ["2012-03-03", "2012-03-06"]})
event["start"] = pd.to_datetime(event["start"])
event["end"] = pd.to_datetime(event["end"])
我想创建一个掩码数据帧,如果 df 中的任何日期在事件数据帧中任何事件的开始日期和结束日期之间,则该掩码数据帧返回 True。预期的输出应该是
0, 1, 1
1, 1, 1
1, 1, 1
1, 1, 1
1, 1, 0
这个预期的输出对应于 df
2012-03-01, 2012-03-02, 2012-03-03
2012-03-02, 2012-03-03, 2012-03-04
2012-03-03, 2012-03-04, 2012-03-05
2012-03-04, 2012-03-05, 2012-03-06
2012-03-05, 2012-03-06, 2012-03-07
如您所见,只有 2012-03-01 和 2012-03-07 不在事件数据框中的任何事件之间。循环可能计算成本很高。我可以给您建议如何最大程度地减少循环吗?
答案 0 :(得分:4)
您可以使用笛卡尔连接,然后检查日期是否在间隔的开始和结束之间,并进行聚合:
# cartesian join
z = (df
.stack().reset_index().assign(k=1)
.merge(event.assign(k=1)))
# check if date between start and end
z['mask'] = z[0].between(z['start'], z['end'])
# aggregate
df_m = z.groupby(['level_0', 'level_1'])['mask'].max().unstack().astype(int)
df_m
输出:
level_1 date date+1 date+2
level_0
0 0 1 1
1 1 1 1
2 1 1 1
3 1 1 1
4 1 1 0
附言如果您使用的是较新版本的 k=1
(1.2.0+),则可以直接使用 pandas
,而不是在合并之前将 merge(how='cross')
分配给两个帧的技巧
答案 1 :(得分:1)
从 events
创建一个 interval index:
intervals = pd.IntervalIndex.from_tuples([*zip(event.start, event.end)],
closed = 'both')
IntervalIndex([[2012-03-02, 2012-03-03], [2012-03-04, 2012-03-06]],
closed='both',
dtype='interval[datetime64[ns]]')
在 df
上运行 applymap:
df.applymap(lambda df: intervals.contains(df).any()).astype(int)
date date+1 date+2
0 0 1 1
1 1 1 1
2 1 1 1
3 1 1 1
4 1 1 0