Pandas分组累积转换与条件

5
我有一张大表格,包含许多产品ID和iso_codes:总共有200万行。因此,答案(如果可能的话)也应考虑内存问题,我的内存为16 GB。
我想看到对于每个(id,iso_code)组合,在购买日期之前返回的物品数量在行中累积,但有一个注意点:
我只想计算从先前销售中发生的并且退货日期早于我所查看的购买日期的退货。
我已添加items_returned列作为示例: 这是应该计算的列。 想法如下:
在销售时,我只能计算已经发生的退货,而不能计算将来会发生的退货。
我尝试了df.groupby(['id','iso_code']).transform(np.cumsum).transform(lambda x: only count returns that happened before my buy_date)的组合,但无法想出如何应用这些特殊条件进行.groupby.transform(np.cumsum)
对于购买的物品,类似的问题,我只计算小于我的购买日期的天数的物品累积。
希望你可以帮助我。 示例结果表格:
+-------+------+------------+----------+------------+---------------+----------------+------------------+
|   row |   id | iso_code   |   return | buy_date   | return_date   |   items_bought |   items_returned |
|-------+------+------------+----------+------------+---------------+----------------+------------------|
|     0 |  177 | DE         |        1 | 2019-05-16 | 2019-05-24    |              0 |                0 |
|     1 |  177 | DE         |        1 | 2019-05-29 | 2019-06-03    |              1 |                1 |
|     2 |  177 | DE         |        1 | 2019-10-27 | 2019-11-06    |              2 |                2 |
|     3 |  177 | DE         |        0 | 2019-11-06 | None          |              3 |                2 |
|     4 |  177 | DE         |        1 | 2019-11-18 | 2019-11-28    |              4 |                3 |
|     5 |  177 | DE         |        1 | 2019-11-21 | 2019-12-11    |              5 |                3 |
|     6 |  177 | DE         |        1 | 2019-11-25 | 2019-12-06    |              6 |                3 |
|     7 |  177 | DE         |        0 | 2019-11-30 | None          |              7 |                4 |
|     8 |  177 | DE         |        1 | 2020-04-30 | 2020-05-27    |              8 |                6 |
|     9 |  177 | DE         |        1 | 2020-04-30 | 2020-09-18    |              8 |                6 |
+-------+------+------------+----------+------------+---------------+----------------+------------------+

示例代码:

import pandas as pd
from io import StringIO

df_text = """
row id  iso_code    return  buy_date    return_date
0   177 DE  1   2019-05-16  2019-05-24
1   177 DE  1   2019-05-29  2019-06-03
2   177 DE  1   2019-10-27  2019-11-06
3   177 DE  0   2019-11-06  None
4   177 DE  1   2019-11-18  2019-11-28
5   177 DE  1   2019-11-21  2019-12-11
6   177 DE  1   2019-11-25  2019-12-06
7   177 DE  0   2019-11-30  None
8   177 DE  1   2020-04-30  2020-05-27
9   177 DE  1   2020-04-30  2020-09-18
"""

df = pd.read_csv(StringIO(df_text), sep='\t', index_col=0)

df['items_bought'] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 8]
df['items_returned'] = [0, 1, 2, 2, 3, 3, 3, 4, 6, 6]
1个回答

1
这似乎需要进行交叉合并:
(df[['id','iso_code', 'buy_date']].reset_index()
   .merge(df[['id','iso_code', 'return','return_date','buy_date']], on=['id','iso_code'])
   .assign(items_returned=lambda x: x['return_date'].lt(x['buy_date_x'])*x['return'],
           items_bought=lambda x: x['buy_date_y'].lt(x['buy_date_x']))
   .groupby('row')[['items_bought','items_returned']].sum()
)

输出:

     items_bought  items_returned
row                              
0               0               0
1               1               1
2               2               2
3               3               2
4               4               3
5               5               3
6               6               3
7               7               4
8               8               6
9               8               6

对于更大的数据,由于内存要求,跨合并并不理想。因此我们可以使用 groupby(),这样我们只需要在较小的组上进行合并:
def myfunc(df):
    return (df[['id','iso_code', 'buy_date']].reset_index()
   .merge(df[['id','iso_code', 'return','return_date','buy_date']], on=['id','iso_code'])
   .assign(items_returned=lambda x: x['return_date'].lt(x['buy_date_x'])*x['return'],
           items_bought=lambda x: x['buy_date_y'].lt(x['buy_date_x']))
   .groupby('row')[['items_bought','items_returned']].sum()
)

df.groupby(['id','iso_code']).apply(myfunc).reset_index(level=[0,1], drop=True)

而你会得到相同的输出:

     items_bought  items_returned
row                              
0               0               0
1               1               1
2               2               2
3               3               2
4               4               3
5               5               3
6               6               3
7               7               4
8               8               6
9               8               6

嗨,Quang,感谢您的优雅解决方案。这适用于较小的数据,但是当我将其应用于我的200万行数据框时,我遇到了内存问题:无法为形状为(3,165038559)且数据类型为对象的数组分配3.69 GiB。 - Sander van den Oord
1
嗨 Quang,谢谢你的更新!我发现 .apply() 的解决方案太慢了,需要很长时间才能完成。所以我现在使用你的第一个解决方案:由于我正在云端工作,我只需将我的虚拟机切换到更多内存(32GB)的虚拟机上,然后它就可以正常工作了。我喜欢你在答案中使用了方法链接 :) - Sander van den Oord

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接