【问题标题】:Replace pandas groupby and apply to increase performance替换pandas groupby并申请提升性能
【发布时间】:2020-06-05 17:31:28
【问题描述】:

我正在使用 pandas groupby 并申请从包含 1.5 亿行和以下列的 DataFrame 开始:

Id  Created     Item    Stock   Price
1   2019-01-01  Item 1  200     10
1   2019-01-01  Item 2  100     15
2   2019-01-01  Item 1  200     10

到一个包含 220 万条记录的列表,如下所示:

[{
  "Id": 1,
  "Created": "2019-01-01",
  "Items": [
    {"Item":"Item 1", "Stock": 200, "Price": 10},
    {"Item":"Item 2", "Stock": 100, "Price": 5}
    ]
},
{
  "Id": 2,
  "Created": "2019-01-01",
  "Items": [
    {"Item":"Item 1", "Stock": 200, "Price": 10}
    ]
}]

主要使用这行代码:

df.groupby(['Id', 'Created']).apply(lambda x: x[['Item', 'Stock', 'Price']].to_dict(orient='records'))

这需要相当长的时间,据我了解,这样的操作对于 pandas 来说是很繁重的。有没有一种非 Pandas 的方式来完成同样的任务但性能更高?

编辑:该操作需要 55 分钟,我在 AWS 中使用 ScriptProcessor,它可以让我指定我想要的电量。

编辑 2: 因此,使用 artonas 解决方案我越来越接近: 这是我现在设法制作的:

defaultdict(<function __main__.<lambda>()>,
            {'1': defaultdict(list,
                         {'Id': '1',
                          'Created':'2019-01-01',
                          'Items': [{'Item': Item2, 'Stock': 100, 'Price': 15},
                                    {'Item': Item1, 'Stock': 200, 'Price': 10}]
                         })
            },
           {'2': defaultdict(list,
                         {'Id': '2',
                          'Created':'2019-01-01',
                          'Items': [{'Item': Item1, 'Stock': 200, 'Price': 10}]
                         })
            },

但是如何从上面到这个呢?

[{
  "Id": 1,
  "Created": "2019-01-01",
  "Items": [
    {"Item":"Item 1", "Stock": 200, "Price": 10},
    {"Item":"Item 2", "Stock": 100, "Price": 5}
    ]
},
{
  "Id": 2,
  "Created": "2019-01-01",
  "Items": [
    {"Item":"Item 1", "Stock": 200, "Price": 10}
    ]
}]

基本上我只对所有记录的“defaultdict(list,”之后的部分感兴趣。我需要将它放在一个不依赖于作为键的 Id 的列表中。

编辑 3:上次更新包含我的生产数据集的结果。 通过 artona 提供的公认答案,我设法从 55 分钟缩短到 7(!) 分钟。并且没有对我的代码进行任何重大更改。 Phung Duy Phong 提供的解决方案让我从 55 分钟到 17 分钟,也不错。

【问题讨论】:

  • 您能否在您的数据框中分享一些行,以及您的预期输出
  • 是的,我更新了帖子。
  • 如果您正在寻找某种快速“矢量化”numpy 解决方案 - 不。这不是那种数值数组任务。我可以看到这样做 python 字典,但它仍然涉及遍历所有行。
  • 是的,我不确定我在寻找什么,只是可能会提高性能的替代方案。感谢您的意见。
  • @Josef 这个列表应该被排序(从最低到最高的id)?

标签: python pandas numpy pandas-groupby


【解决方案1】:

使用 collections.defaultdict 和 itertuples。它仅在行上迭代一次。

In [105]: %timeit df.groupby(['Id', 'Created']).apply(lambda x: x[['Item', 'Stock', 'Price']].to_dict(orient='records'))
10.1 s ± 44.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [107]:from collections import defaultdict
     ...:def create_dict():
     ...:     dict_ids = defaultdict(lambda : defaultdict(list))
     ...:     for row in df.itertuples():
     ...:          dict_ids[row.Id][row.Created].append({"Item": row.Item, "Stock": row.Stock, "Price": row.Price})
     ...:     list_of_dicts = [{"Id":key_id, "Created":key_created, "Items": values} for key_id, value_id in dict_ids.items() for key_created, values in value_id.items()]
     ...:     return list_of_dicts

In [108]: %timeit create_dict()
4.58 s ± 417 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

【讨论】:

  • 酷!会调查的。谢谢!
  • 这更快,但我没有得到我想要的结果......但也许这只是一些小的调整。我没有得到 row.Id、row.Created 的字段名称
  • 您能分享错误跟踪吗? df.index 是从 ids 创建的吗?
  • 你希望如何访问数据 - 你能分享这个例子吗?按项目索引,如dict_ids[1]["2019-01-01"]?
  • 经过一些小改动后,我几乎就在那儿了……唯一剩下的就是我只想用 dict_ids[0 (like an index)] 而不是 Id 本身来访问它。我将更新我的主要帖子,以便您可以看到我现在得到的输出
【解决方案2】:

如果数据帧是干净排序的,这意味着同一对(IdCreated)的所有行都是连续的,您可以简单地迭代它们。但是由于 pandas 必须为每一行构建一个新的 Series,所以迭代数据帧的成本很高,我会直接迭代底层的 numpy 数组。

代码可以是:

records = []
Id = None

for i in range(len(df)):
    if df['Id'].values[i] != Id or df['Created'].values[i] != created:
        items = []
        Id = df['Id'].values[i]
        created = df['Created'].values[i]
        records.append({'Id': Id, 'Created': created,
                'Items': items})

    items.append({x: df[x].values[i]
              for x in ['Item', 'Stock', 'Price']})

如果数据最初没有排序,您可以尝试使用 pandas 对数据框进行排序,然后使用上面的代码

【讨论】:

  • 我之前试过这个,但觉得不够快。谢谢
  • 我可以在我的系统上以 30 秒的时间对 150 万行进行测试。我没有足够的内存来测试 1.5 亿...
  • 是的,所以如果 1,5 需要 30 秒,可以估计 150 需要大约。 3000 秒或 50 分钟。还不够好。 Artonas 解决方案将击败这一点。
【解决方案3】:

尝试以下操作:

df['Items'] = df.loc[:, ['X', 'Y', 'Z']].to_dict(orient='records')
df.groupby(['ID', 'CREATED'])['Items'].apply(list).reset_index().to_dict(orient='records')

【讨论】:

  • 我会试一试,然后将结果回复您。
  • 我用大约 1m 条记录测试它,在我的电脑上它肯定快得多,但不知道你电脑上的内存使用情况,因为它在 3 列中复制数据,我认为要小心你的内存使用情况
  • 几乎快了三倍! (删除错误编辑)
  • 那些 level_0 字段可能在reset_index,你可能想看看
  • 所以我用这个解决方案从 55 分钟缩短到了 17 分钟,考虑到所需的小改动,这很好。在接受之前,我会尝试弄清楚 artona 提供的解决方案。
猜你喜欢
  • 1970-01-01
  • 2019-09-08
  • 2021-05-13
  • 2018-06-25
  • 1970-01-01
  • 2021-08-12
  • 1970-01-01
  • 1970-01-01
  • 2021-07-08
相关资源
最近更新 更多