#python #pandas #pandas-groupby
#python #pandas #pandas-groupby
Вопрос:
У меня есть большая таблица со многими идентификаторами продуктов и iso_codes: всего 2 миллиона строк. Таким образом, ответ должен (если возможно) также учитывать проблемы с памятью, у меня 16 ГБ памяти.
Я хотел бы видеть для каждой комбинации (id, iso_code), какое количество возвращаемых элементов находится перед buy_date в строке (таким образом, кумулятивно), но есть одна загвоздка:
я хочу подсчитывать только возвраты, полученные от предыдущих продаж, где return_date находится перед buy_date, на который я смотрю.
Я добавил столбец items_returned в качестве примера: это столбец, который должен быть вычислен.
Идея заключается в следующем:
На момент продажи я могу рассчитывать только те возвраты, которые уже произошли, а не те, которые произойдут в будущем.
Я попробовал комбинацию df.groupby(['id', 'iso_code']).transform(np.cumsum)
и .transform(lambda x: only count returns that happened before my buy_date)
, но не смог понять, как сделать a .groupby.transform(np.cumsum)
с применением этих особых условий.
Аналогичный вопрос для купленных товаров, где я считаю кумулятивные товары только за дни, меньшие, чем мой buy_date.
Надеюсь, вы сможете мне помочь.
Пример результирующей таблицы:
------- ------ ------------ ---------- ------------ --------------- ---------------- ------------------
| 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:
Похоже, для этого требуется перекрестное слияние:
(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
Комментарии:
1. Привет, Куанг, спасибо за ваше элегантное решение. Это работает для меньших данных, но у меня возникают проблемы с памятью при применении этого к моему фрейму данных с 2 миллионами строк: невозможно выделить 3,69 гигабайт для массива с формой (3, 165038559) и типом данных object.
2. Привет, Куанг, спасибо за обновление! Я нашел решение .apply() слишком медленным, для завершения потребовалась вечность. Итак, теперь я использую ваше первое решение: поскольку я работаю в облаке, я просто переключил свою виртуальную машину на одну с большим объемом памяти (32 ГБ), и тогда она работает. Мне нравится, что вы использовали цепочку методов в своем ответе 🙂