#python #pandas
Вопрос:
У меня есть фрейм данных (mydf) с датами для каждой группы с ежемесячной периодичностью, как показано ниже:
Dt Id Sales
2021-03-01 B 2
2021-04-01 B 42
2021-05-01 B 20
2021-06-01 B 4
2020-10-01 A 47
2020-11-01 A 67
2020-12-01 A 46
Я хочу заполнить dt для каждой группы до максимальной даты в столбце даты, начиная с даты идентификатора, одновременно заполняя 0 для столбца продаж. Таким образом, каждая группа начинается со своей собственной даты начала, но заканчивается в ту же дату окончания.
Так, например, ID=A начнется с 2020-10-01 и дойдет до 2021-06-03, а значение для заполненных дат будет равно 0.
Таким образом, результат будет
Dt Id Sales
2021-03-01 B 2
2021-04-01 B 42
2021-05-01 B 20
2021-06-01 B 4
2020-10-01 A 46
2020-11-01 A 47
2020-12-01 A 67
2021-01-01 A 0
2021-02-01 A 0
2021-03-01 A 0
2021-04-01 A 0
2021-05-01 A 0
2021-06-01 A 0
Я пробовал переиндексировать, но вместо добавления диапазона дат вручную я хочу использовать даты в группах.
Мой код таков :
f = lambda x: x.reindex(pd.date_range('2020-10-01', '2021-06-01', freq='MS', name='Dt'))
mydf = mydf.set_index('Dt').groupby('Id').apply(f).drop('Id', axis=1).fillna(0)
mydf = mydf.reset_index()
Комментарии:
1. Ваш пример не очень понятен. Вы продублировали «2021-06-01» для B, и пример не соответствует текстовому описанию (для A повторная выборка отсутствует). Можете ли вы улучшить вопрос?
2. @mozway Идентификаторы в выходных данных были продублированы по ошибке. Теперь все ясно? Спасибо
Ответ №1:
Альтернативное использование pd.MultiIndex
с пониманием списка:
s = (pd.MultiIndex.from_tuples([[x, d]
for x, y in df.groupby("Id")["Dt"]
for d in pd.date_range(min(y), max(df["Dt"]), freq="MS")], names=["Id", "Dt"]))
print (df.set_index(["Id", "Dt"]).reindex(s, fill_value=0).reset_index())
Ответ №2:
Давайте попробуем:
- Получение минимального значения для каждой группы с помощью
groupby.min
- Добавьте новый столбец в агрегированные минуты, которые называются
max
, в котором хранятся максимальные значения из кадра, используяSeries.max
наDt
- Создайте человека
date_range
для каждой группы на основе значенийmin
иmax
Series.explode
в строки, чтобы иметь фрейм данных, представляющий новый индекс.MultiIndex.from_frame
Создайте в кадре данных с помощью.reindex
reindex
сmidx
помощью и установитеfillvalue=0
# Get Min Per Group
dates = mydf.groupby('Id')['Dt'].min().to_frame(name='min')
# Get max from Frame
dates['max'] = mydf['Dt'].max()
# Create MultiIndex with separate Date ranges per Group
midx = pd.MultiIndex.from_frame(
dates.apply(
lambda x: pd.date_range(x['min'], x['max'], freq='MS'), axis=1
).explode().reset_index(name='Dt')[['Dt', 'Id']]
)
# Reindex
mydf = (
mydf.set_index(['Dt', 'Id'])
.reindex(midx, fill_value=0)
.reset_index()
)
mydf
:
Dt Id Sales
0 2020-10-01 A 47
1 2020-11-01 A 67
2 2020-12-01 A 46
3 2021-01-01 A 0
4 2021-02-01 A 0
5 2021-03-01 A 0
6 2021-04-01 A 0
7 2021-05-01 A 0
8 2021-06-01 A 0
9 2021-03-01 B 2
10 2021-04-01 B 42
11 2021-05-01 B 20
12 2021-06-01 B 4
Фрейм данных:
import pandas as pd
mydf = pd.DataFrame({
'Dt': ['2021-03-01', '2021-04-01', '2021-05-01', '2021-06-01', '2020-10-01',
'2020-11-01', '2020-12-01'],
'Id': ['B', 'B', 'B', 'B', 'A', 'A', 'A'],
'Sales': [2, 42, 20, 4, 47, 67, 46]
})
mydf['Dt'] = pd.to_datetime(mydf['Dt'])
Комментарии:
1. Спасибо вам за предоставленное решение. Однако я не хочу начинать даты для Id=B с самой ранней даты кадра данных. Независимо от идентификатора, я хочу сохранить дату начала прежней и продолжить заполнение до конца максимальной даты. Извините, если я не был ясен раньше.
2. Каждая группа стартует в свою собственную дату начала. Но все группы заканчиваются в одну и ту же дату окончания?
Ответ №3:
Вот другой подход:
from itertools import product
# compute the min-max date range
date_range = pd.date_range(*mydf['Dt'].agg(['min', 'max']), freq='MS', name='Dt')
# make MultiIndex per group, keep only values above min date per group
idx = pd.MultiIndex.from_tuples([e for Id,Dt_min in mydf.groupby('Id')['Dt'].min().items()
for e in list(product(date_range[date_range>Dt_min],
[Id]))
])
# concatenate the original dataframe and the missing indexes
mydf = mydf.set_index(['Dt', 'Id'])
mydf = pd.concat([mydf,
mydf.reindex(idx.difference(mydf.index)).fillna(0)]
).sort_index(level=1).reset_index()
mydf
выход:
Dt Id Sales
0 2020-10-01 A 47.0
1 2020-11-01 A 67.0
2 2020-12-01 A 46.0
3 2021-01-01 A 0.0
4 2021-02-01 A 0.0
5 2021-03-01 A 0.0
6 2021-04-01 A 0.0
7 2021-05-01 A 0.0
8 2021-06-01 A 0.0
9 2021-03-01 B 2.0
10 2021-04-01 B 42.0
11 2021-05-01 B 20.0
12 2021-06-01 B 4.0
Ответ №4:
Мы можем использовать complete
функцию from pyjanitor
для отображения недостающих значений:
Преобразование Dt
в дату и время:
df['Dt'] = pd.to_datetime(df['Dt'])
Создайте сопоставление Dt
с новыми значениями через pd.date_range
и установите частоту начала месяца ( MS
):
max_time = df.Dt.max()
new_values = {"Dt": lambda df:pd.date_range(df.min(), max_time, freq='1MS')}
# pip install pyjanitor
import janitor
import pandas as pd
df.complete([new_values], by='Id').fillna(0)
Id Dt Sales
0 A 2020-10-01 47.0
1 A 2020-11-01 67.0
2 A 2020-12-01 46.0
3 A 2021-01-01 0.0
4 A 2021-02-01 0.0
5 A 2021-03-01 0.0
6 A 2021-04-01 0.0
7 A 2021-05-01 0.0
8 A 2021-06-01 0.0
9 B 2021-03-01 2.0
10 B 2021-04-01 42.0
11 B 2021-05-01 20.0
12 B 2021-06-01 4.0
Придерживаясь только Панд, мы можем комбинировать apply
, с groupby
и reindex
; к счастью, Dt
является уникальным, поэтому мы можем безопасно переиндексировать:
(df
.set_index('Dt')
.groupby('Id')
.apply(lambda df: df.reindex(pd.date_range(df.index.min(),
max_time,
freq='1MS'),
fill_value = 0)
)
.drop(columns='Id')
.rename_axis(['Id', 'Dt'])
.reset_index())
Id Dt Sales
0 A 2020-10-01 47
1 A 2020-11-01 67
2 A 2020-12-01 46
3 A 2021-01-01 0
4 A 2021-02-01 0
5 A 2021-03-01 0
6 A 2021-04-01 0
7 A 2021-05-01 0
8 A 2021-06-01 0
9 B 2021-03-01 2
10 B 2021-04-01 42
11 B 2021-05-01 20
12 B 2021-06-01 4