在 pandas 中有效地聚合重新采样的日期时间集合

Posted

技术标签:

【中文标题】在 pandas 中有效地聚合重新采样的日期时间集合【英文标题】:Efficiently aggregate a resampled collection of datetimes in pandas 【发布时间】:2019-06-25 22:25:24 【问题描述】:

给定以下数据集作为 pandas 数据框 df:

index(as DateTime object) |  Name        |  Amount    |  IncomeOutcome
---------------------------------------------------------------
2019-01-28                |  Customer1   |  200.0     |  Income
2019-01-31                |  Customer1   |  200.0     |  Income
2019-01-31                |  Customer2   |  100.0     |  Income
2019-01-28                |  Customer2   |  -100.0    |  Outcome
2019-01-31                |  Customer2   |  -100.0    |  Outcome

我们执行以下步骤:

grouped = df.groupby("Name", "IncomeOutcome")
sampled_by_month = grouped.resample("M")
aggregated = sampled_by_month.agg("MonthlyCount": "size", "Amount": "sum")

所需的输出应如下所示:

Name       |  IncomeOutcome   |  Amount    |  MonthlyCount
------------------------------------------------------------
Customer1  |  Income          |  400.0     |  2
Customer2  |  Income          |  100.0     |  1
Customer2  |  Outcome         |  -200.0    |  2

最后一步表现很差,可能与Pandas Issue #20660有关 我的第一个意图是将所有 datetime 对象转换为 int64,这给我留下了如何按月对转换后的数据重新采样的问题。

对这个问题有什么建议吗?

提前谢谢你

【问题讨论】:

嗨 Ben,您希望获得的最终数据形式是什么?你能做一个你想要的输出的小数据框例子吗?谢谢。 嘿奥利,我更新了描述。我希望这能让我更清楚我想要实现的目标。 sampled_by_month 变量应该保存以日期时间对象数组作为值的组,这似乎很慢。 修复了输入中可能存在的错误。请确认。 是的,似乎是正确的。谢谢! 【参考方案1】:

也许我们可以通过仅对单个列(“数量”,感兴趣的列)进行重新采样来优化您的解决方案。

(df.groupby(["Name", "IncomeOutcome"])['Amount']
   .resample("M")
   .agg(['sum','size'])
   .rename('sum':'Amount', 'size': 'MonthlyCount', axis=1)
   .reset_index(level=-1, drop=True)
   .reset_index())

        Name IncomeOutcome  Amount  MonthlyCount
0  Customer1        Income   400.0             2
1  Customer2        Income   100.0             1
2  Customer2       Outcome  -200.0             2

如果这仍然太慢,那么我认为问题可能是resample groupby 中会减慢速度。也许您可以尝试通过单个 groupby 调用按所有 3 个谓词进行分组。对于日期重采样,请尝试pd.Grouper

(df.groupby(['Name', 'IncomeOutcome', pd.Grouper(freq='M')])['Amount']
   .agg([ ('Amount', 'sum'), ('MonthlyCount', 'size')])
   .reset_index(level=-1, drop=True)
   .reset_index())

        Name IncomeOutcome  Amount  MonthlyCount
0  Customer1        Income   400.0             2
1  Customer2        Income   100.0             1
2  Customer2       Outcome  -200.0             2

在性能方面,这应该会更快。


性能

让我们尝试设置一个更通用的 DataFrame 来进行测试。

# Setup
df_ = df.copy()
df1 = pd.concat([df_.reset_index()] * 100, ignore_index=True)
df = pd.concat([
        df1.replace('Customer1': f'Customeri', 'Customer2': f'Customeri+1') 
        for i in range(1, 98, 2)], ignore_index=True) 
df = df.set_index('index')

df.shape
# (24500, 3)

%%timeit 
(df.groupby(["Name", "IncomeOutcome"])['Amount']
   .resample("M")
   .agg(['sum','size'])
   .rename('sum':'Amount', 'size': 'MonthlyCount', axis=1)
   .reset_index(level=-1, drop=True)
   .reset_index())

%%timeit
(df.groupby(['Name', 'IncomeOutcome', pd.Grouper(freq='M')])['Amount']
   .agg([ ('Amount', 'sum'), ('MonthlyCount', 'size')])
   .reset_index(level=-1, drop=True)
   .reset_index())

1.71 s ± 85.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
24.2 ms ± 1.82 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

【讨论】:

我在 2 天前使用一般数据对其进行了测试,并为您的第二个代码获得了不同的输出 @jezrael 啊,是的,我认为他们的问题存在错误。 I fixed it 并 ping 他们要求他们检查它是否正确。 @coldspeed 感谢您的回答!我明天会检查它并回复你 @Ben 让我知道是否有任何事情。谢谢。 澄清未来读者的一点:使用 pd.Grouper 的第二种方法比第一种方法快得多,它会生成一个没有 NaN 行的 Dataframe。示例:如果您对每日数据进行重新采样,则缺失的天数将不会显示为 NaN 行。只有传统的“重新采样()”会产生缺少天数、周数等的行。我相信这就是总行数与这些方法不同的原因。

以上是关于在 pandas 中有效地聚合重新采样的日期时间集合的主要内容,如果未能解决你的问题,请参考以下文章

pyspark:在日期和时间上重新采样 pyspark 数据帧

Pandas 0.18.1 groupby 和多级聚合错误重新采样

pandas OHLC 聚合随着 OHLC 的时间重新采样

Pandas 重新采样开始日期

Pandas 在日期列上重新采样

使用 Pandas 将每日数据重新采样为每月(日期格式)