Python:将不同列的值分组到时间桶中

Python: Grouping values of different columns into time buckets

假设你有这个 DataFrame:

Name    Item    Date    value1  value2
Marc    bike    21-Dec-17   7   1000
Marc    bike    05-Jan-18   9   2000
Marc    bike    27-Jul-18   4   500
John    house   14-Dec-17   4   500
John    house   02-Feb-18   6   500
John    house   07-Feb-18   8   1000
John    house   16-Feb-18   2   1000
John    house   05-Dec-21   7   1000
John    house   27-Aug-25   8   500
John    car     17-Apr-18   4   500

我想将每个名称-项目组合的 value1 和 value2 放入每月存储桶(接下来 48 个月的每个第 3 个星期三)。

所以每个组合有49个时间段,每个月的value1和value2之和:Marc/bike, John/house, John/car, ...

John/house 的解决方案如下所示:

Name    Item    TimeBucket  value1  value2
John    house   20-Dec-17   4   500
John    house   17-Jan-18   0   0
John    house   21-Feb-18   16  2500
John    house   21-Mar-18   0   0
John    house   18-Apr-18   0   0
John    house   …           0   0
John    house   17-Nov-21   0   0
John    house   15-Dec-21   7   1000
John    house   rest        8   500

我无法得到 pandas 的结果。我能想到的唯一解决方案是逐行遍历数据框,但我真的很想避免这样做。有什么优雅的方法吗?

这个问题其实归结为三个步骤:

1。如何找到每个月的第三个星期三?

这可能不是最优雅的解决方案,但您可以 通过屏蔽 一个 pandas DatetimeIndex 来过滤掉每个月的第三个星期三,其中包含时间范围内的每一天。

# generate a DatetimeIndex for all days in the relevant time frame
from datetime import datetime
start = datetime(2017, 12, 1)
end = datetime(2022, 1, 31)
days = pd.date_range(start, end, freq='D')

# filter out only the third wednesday of each month
import itertools
third_wednesdays = []
for year, month in itertools.product(range(2017, 2023), range(1,13)):
    mask = (days.weekday == 2) & \
        (days.year == year) & \
        (days.month == month)
    if len(days[mask]) > 0:
        third_wednesdays.append(days[mask][2])
bucket_lower_bounds = pd.DatetimeIndex(third_wednesdays)

将结果列表转换为 DatetimeIndex,以便您可以将其用作第 2 步中 bin 的下限。

2。如何对 DataFrame 的值进行装箱?

然后,一旦您将存储桶列表作为 DatetimeIndex,您可以简单地 使用 panda's cut function 将每个日期分配给存储桶 。将日期列转换为整数,然后将它们传递给 cut,然后将结果转换回日期:

time_buckets = pd.to_datetime(
    pd.cut(
        x = pd.to_numeric(df['Date']), 
        bins = pd.to_numeric(bucket_lower_bounds), 
        labels = bucket_lower_bounds[:-1]
    )
)

系列 time_buckets 将原始数据框的每个索引值分配给存储桶的下限。我们现在可以简单地将它添加到原始数据框中:

df['TimeBucket'] = time_buckets

结果应该看起来像这样(不是 NaT 代表 "rest" 桶):

    Name    Item    Date    value1  value2  TimeBucket
0   Marc    bike    2017-12-21  7   1000    2017-12-20
1   Marc    bike    2018-01-05  9   2000    2017-12-20
2   Marc    bike    2018-07-27  4   500     2018-07-18
3   John    house   2017-12-14  4   500     NaT
4   John    house   2018-02-02  6   500     2018-01-17
5   John    house   2018-02-07  8   1000    2018-01-17
6   John    house   2018-02-16  2   1000    2018-01-17
7   John    house   2021-12-05  7   1000    2021-11-17
8   John    house   2025-08-27  8   500     NaT
9   John    car     2018-04-17  4   500     2018-03-21

3。如何聚合合并的 DataFrame?

现在就像使用groupby 获取名称、项目和存储桶的每个组合 的总和一样简单:

df.groupby(['Name','Item','TimeBucket']).sum()

结果:

Name    Item    TimeBucket  value1  value2
John    car     2018-03-21  4       500
        house   2018-01-17  16      2500
                2021-11-17  7       1000
Marc    bike    2017-12-20  16      3000
                2018-07-18  4       500

不幸的是,NaT values are excluded from groupby。如果您还需要对这些求和,也许最简单的方法是确保您的存储桶列表对于输入范围内的每个日期至少有一个存储桶。

编辑:第 2 步需要 pandas 版本 >= 0.18.1。