我假设您的数据框名为df。我还将假设给定 ClientID 的日期不会重复,并且是按升序排列的(如果不是这种情况,请进行 groupby 求和并对结果进行排序,以便它是)。
我的解决方案的要点是,对于给定的 ClientID 和 Date。
- 使用 groupby.transform 按 ClientID 拆分此问题。
- 使用
rolling 检查接下来的 7 行中是否存在 1 周时间跨度内的日期。
- 在这 7 行中,时间跨度内的日期标记为 True (=1)。未标记的日期为 False (=0)。
- 在这 7 行中,将 Orders 列乘以日期的 True/False 标记。
- 对结果求和。
实际上,我们使用 8 行,因为例如 SuMoTuWeThFrSaSu 有 8 天。
让这变得困难的是rolling 一次聚合一个列,因此显然不允许您在聚合时处理多个列。如果是这样,您可以使用日期列进行过滤,并使用它来汇总订单。
但有一个漏洞:如果您愿意通过索引将它们偷运进来,您可以使用多个列!
我使用了一些辅助函数。注意a被理解为一个pandas系列,有8行,值为“Orders”,索引中有“Date”。
很想知道真实数据的性能如何。
import pandas as pd
data = {
'ClientID': {0: 123, 1: 123, 2: 123, 3: 456, 4: 456, 5: 456},
'Date': {0: '2020-03-01', 1: '2020-03-05', 2: '2020-03-10',
3: '2020-02-22', 4: '2020-02-25', 5: '2020-02-28'},
'Orders': {0: 23, 1: 10, 2: 7, 3: 3, 4: 15, 5: 5}
}
df = pd.DataFrame(data)
# Make sure the dates are datetimes
df['Date'] = pd.to_datetime(df['Date'])
# Put into index so we can smuggle them through "rolling"
df = df.set_index(['ClientID', 'Date'])
def date(a):
# get the "Date" index-column from the dataframe
return a.index.get_level_values('Date')
def previous_week(a):
# get a column of 0s and 1s identifying the previous week,
# (compared to the date in the last row in a).
return (date(a) >= date(a)[-1] - pd.DateOffset(days=7)) * (date(a) < date(a)[-1])
def previous_week_order_total(a):
#compute the order total for the previous week
return sum(previous_week(a) * a)
def total_last_week(group):
# for a "ClientID" compute all the "previous week order totals"
return group.rolling(8, min_periods=1).apply(previous_week_order_total, raw=False)
# Ok, actually compute this
df['Orders_Last_Week'] = df.groupby(['ClientID']).transform(total_last_week)
# Reset the index back so you can have the ClientID and Date columns back
df = df.reset_index()
上面的代码依赖于过去一周最多包含 7 行数据的事实,即一周中的 7 天(尽管在您的示例中,它实际上少于 7 天)
如果您的时间窗口不是一周,您需要根据时间戳的最佳划分替换所有对一周长度的引用。
例如,如果您的日期时间戳间隔不小于 1 秒,并且您对 1 分钟的时间窗口感兴趣(例如,“Orders_last_minute”),请将 pd.DateOffset(days=7) 替换为 pd.DateOffset(seconds=60) 和 @987654329 @与group.rolling(61,....)
显然,这段代码有点悲观:对于每一行,它总是查看 61 行,在这种情况下。不幸的是rolling 没有提供合适的可变窗口大小功能。我怀疑在某些情况下,利用数据帧按日期排序这一事实的 python 循环可能比这种部分矢量化的解决方案运行得更快。