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。
假设你有这个 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。