Добавить пропущенные даты к каждой группе
Есть датафрейм:
df = pd.DataFrame({'id':['b21', 'b21', 'c56', 'b21', 'c56', 'c56'],
'status':['created', 'delivered', 'created', 'done', 'delivered', 'done'],
'date':['2023-01-13', '2023-01-15', '2023-01-15', '2023-01-18', '2023-01-19', '2023-01-20']})
df['date'] = pd.to_datetime(df['date'])
| id | status | date |
|---|---|---|
| b21 | created | 2023-01-13 |
| b21 | delivered | 2023-01-15 |
| c56 | created | 2023-01-15 |
| b21 | done | 2023-01-18 |
| c56 | delivered | 2023-01-19 |
| c56 | done | 2023-01-20 |
Необходимо добавить даты, которых нет в датафрейме и заполнить их предыдущими значениями. При этом заполнить так, чтобы для каждого id даты не выходили за рамки даты старта (status = 'created') и даты окончания (status = 'done')
В итоге должен получиться такой датафрейм:
| id | status | date |
|---|---|---|
| b21 | created | 2023-01-13 |
| b21 | created | 2023-01-14 |
| b21 | delivered | 2023-01-15 |
| c56 | created | 2023-01-15 |
| b21 | delivered | 2023-01-16 |
| c56 | created | 2023-01-16 |
| b21 | delivered | 2023-01-17 |
| c56 | created | 2023-01-17 |
| b21 | done | 2023-01-18 |
| c56 | created | 2023-01-18 |
| c56 | delivered | 2023-01-19 |
| c56 | done | 2023-01-20 |
Похожий вопрос уже существует, однако решение, которое там предлагают, не работает из - за пересечения дат в разных группах. Это приводит к ошибке ValueError: cannot reindex on an axis with duplicate labels
P.S. Нашёл способ сделать это через цикл:
result = pd.DataFrame()
for i in df['id'].unique():
new_df = df[df['id'] == i]
d_range = pd.date_range(new_df['date'].min(), new_df['date'].max(), freq='D').to_frame(name='date')
new_df = new_df.merge(d_range, how='right', on='date').fillna(method = 'ffill')
result = pd.concat([result, new_df]).sort_values(['date', 'id'])
result
Однако реальный датасет будет довольно объёмным, поэтому хотелось бы найти способ увеличить производительность методами pandas
Ответы (1 шт):
Можно использовать groupby()
result = df.groupby('id', group_keys=False).apply(
lambda x: x.merge(pd.date_range(x['date'].min(), x['date'].max(), freq='D').to_series(name='date'), how='right',
on='date').fillna(method='ffill')).sort_values('date')
id status date
0 b21 created 2023-01-13
1 b21 created 2023-01-14
2 b21 delivered 2023-01-15
0 c56 created 2023-01-15
3 b21 delivered 2023-01-16
1 c56 created 2023-01-16
4 b21 delivered 2023-01-17
2 c56 created 2023-01-17
5 b21 done 2023-01-18
3 c56 created 2023-01-18
4 c56 delivered 2023-01-19
5 c56 done 2023-01-20