У меня есть фрейм данных с индексом (месяц, A, B):
foo N
month A B
1983-03-01 3 9 0 1
1983-06-01 3 9 0 1
1983-09-01 3 9 0 1
1983-11-01 4 5 0 1
1984-05-01 4 5 0 1
1984-06-01 3 9 0 1
1984-09-01 3 9 0 2
Я хотел бы заполнить все недостающие даты при условии, что в индексе существует определенная комбинация (A, B). Чего я не хочу делать, так это заполнять индекс для всех комбинаций (A, B).
То есть я хотел бы иметь для (A=3, B=9) и (A=4, B=5) индексы месяцев с 1983-03-01 по 1984-09-01 и 0 с для заполнения. Но я не хочу, чтобы были записи (A=3, B=5) или (A=4, B=9).
Если бы это был один индекс, я мог бы просто
idx = pd.date_range(df['month'].min(), df['month'].max(), freq='M')
df = df.set_index('month')
df.index = df.reindex(idx, fill_value=0)
Как бы я поступил в этой ситуации?
Стоит отметить, что это решение должно масштабироваться с большим количеством уникальных значений для A, B.
Я считаю, что угадал правильный результат, см. мое редактирование моего ответа. В противном случае некоторые образцы вывода были бы оценены по достоинству.






Предполагая:
df = pd.DataFrame({'month': pd.to_datetime(['1983-03-01', '1983-06-01', '1983-09-01', '1983-11-01', '1984-05-01', '1984-06-01', '1984-09-01']),
'A': [3, 3, 3, 4, 4, 3, 3],
'B': [9, 9, 9, 5, 5, 9, 9],
'foo': [0, 0, 0, 0, 0, 0, 0],
'N': [1, 1, 1, 1, 1, 1, 2]}
)
Вы можете использовать groupby.apply:
cols = df.columns.difference(['month', 'A', 'B'])
out = (df.set_index('month').groupby(['A', 'B'])[cols]
.apply(lambda x: x.reindex(pd.date_range(x.index.get_level_values('month').min(),
x.index.get_level_values('month').max(),
freq='MS').rename('month'),
fill_value=0))
.reset_index()[df.columns]
)
Выход:
month A B foo N
0 1983-03-01 3 9 0 1
1 1983-04-01 3 9 0 0
2 1983-05-01 3 9 0 0
3 1983-06-01 3 9 0 1
4 1983-07-01 3 9 0 0
5 1983-08-01 3 9 0 0
6 1983-09-01 3 9 0 1
7 1983-10-01 3 9 0 0
8 1983-11-01 3 9 0 0
9 1983-12-01 3 9 0 0
10 1984-01-01 3 9 0 0
11 1984-02-01 3 9 0 0
12 1984-03-01 3 9 0 0
13 1984-04-01 3 9 0 0
14 1984-05-01 3 9 0 0
15 1984-06-01 3 9 0 1
16 1984-07-01 3 9 0 0
17 1984-08-01 3 9 0 0
18 1984-09-01 3 9 0 2
19 1983-11-01 4 5 0 1
20 1983-12-01 4 5 0 0
21 1984-01-01 4 5 0 0
22 1984-02-01 4 5 0 0
23 1984-03-01 4 5 0 0
24 1984-04-01 4 5 0 0
25 1984-05-01 4 5 0 1
Я не рассматривал возможность группировки по чему-то другому, кроме индекса — очень здорово!
Я попытался реализовать это на своих реальных данных, где у меня есть ~350 уникальных значений для A и B соответственно — это не очень хорошо масштабируется, поскольку итеративно проходит через все группы. Есть ли альтернатива?
В любом случае вам придется перебирать каждую группу, чтобы найти минимальную/максимальную дату. Возможно, можно будет распараллелить использование parallel_pandas , поскольку операции независимы.
Решение @mozway превосходно, тем более что расширение ваших данных основано на индексе. Еще один вариант — функция Complete из pyjanitor — она не будет работать с индексом, но работает только со столбцами:
# pip install pyjanitor
import pandas as pd
import janitor
mapping = {'month':lambda df: pd.date_range(df['month'].min(), df['month'].max(),freq='MS')}
df.complete(mapping,by=['A','B'],sort=True,fill_value=0).astype({'foo':int,'N':int})
month A B foo N
0 1983-03-01 3 9 0 1
1 1983-04-01 3 9 0 0
2 1983-05-01 3 9 0 0
3 1983-06-01 3 9 0 1
4 1983-07-01 3 9 0 0
5 1983-08-01 3 9 0 0
6 1983-09-01 3 9 0 1
7 1983-10-01 3 9 0 0
8 1983-11-01 3 9 0 0
9 1983-12-01 3 9 0 0
10 1984-01-01 3 9 0 0
11 1984-02-01 3 9 0 0
12 1984-03-01 3 9 0 0
13 1984-04-01 3 9 0 0
14 1984-05-01 3 9 0 0
15 1984-06-01 3 9 0 1
16 1984-07-01 3 9 0 0
17 1984-08-01 3 9 0 0
18 1984-09-01 3 9 0 2
19 1983-11-01 4 5 0 1
20 1983-12-01 4 5 0 0
21 1984-01-01 4 5 0 0
22 1984-02-01 4 5 0 0
23 1984-03-01 4 5 0 0
24 1984-04-01 4 5 0 0
25 1984-05-01 4 5 0 1
Стоит отметить, что для pyjanitor требуется Python >= 3.7.
Вы также можете попробовать здесь шаблон groupby(…).resample(…).asfreq(), который может расширить ежемесячную частоту внутри каждой группы путем группировки.
import pandas as pd
df = pd.DataFrame({
'month': pd.to_datetime(['1983-03-01', '1983-06-01', '1983-09-01', '1983-11-01', '1984-05-01', '1984-06-01', '1984-09-01']),
'A': [3, 3, 3, 4, 4, 3, 3],
'B': [9, 9, 9, 5, 5, 9, 9],
'foo': [0, 0, 0, 0, 0, 0, 0],
'N': [1, 1, 1, 1, 1, 1, 2],
})
non_grouping_cols = df.columns.difference(['month', 'A', 'B'])
print(
df.set_index('month')
.groupby(['A', 'B']).resample('MS')[non_grouping_cols].asfreq(fill_value=0)
.reset_index()
)
# A B month N foo
# 0 3 9 1983-03-01 1 0
# 1 3 9 1983-04-01 0 0
# 2 3 9 1983-05-01 0 0
# 3 3 9 1983-06-01 1 0
# 4 3 9 1983-07-01 0 0
# 5 3 9 1983-08-01 0 0
# 6 3 9 1983-09-01 1 0
# 7 3 9 1983-10-01 0 0
# 8 3 9 1983-11-01 0 0
# 9 3 9 1983-12-01 0 0
# 10 3 9 1984-01-01 0 0
# 11 3 9 1984-02-01 0 0
# 12 3 9 1984-03-01 0 0
# 13 3 9 1984-04-01 0 0
# 14 3 9 1984-05-01 0 0
# 15 3 9 1984-06-01 1 0
# 16 3 9 1984-07-01 0 0
# 17 3 9 1984-08-01 0 0
# 18 3 9 1984-09-01 2 0
# 19 4 5 1983-11-01 1 0
# 20 4 5 1983-12-01 0 0
# 21 4 5 1984-01-01 0 0
# 22 4 5 1984-02-01 0 0
# 23 4 5 1984-03-01 0 0
# 24 4 5 1984-04-01 0 0
# 25 4 5 1984-05-01 1 0
Если вы хотите, чтобы существующие группы (A, B) сохранялись и чтобы они имели все из одного и того же диапазона дат (от мин (месяц) до максимума (месяц)) вы можете создать ваш собственный MultiIndex из перекрестного соединения этих двух частей.
import pandas as pd
import numpy as np
df = pd.DataFrame({
'month': pd.to_datetime(['1983-03-01', '1983-06-01', '1983-09-01', '1983-11-01', '1984-05-01', '1984-06-01', '1984-09-01']),
'A': [3, 3, 3, 4, 4, 3, 3],
'B': [9, 9, 9, 5, 5, 9, 9],
'foo': [0, 0, 0, 0, 0, 0, 0],
'N': [1, 1, 1, 1, 1, 1, 2],
})
months = pd.date_range(df['month'].min(), df['month'].max(), freq='MS', name='month')
groupings = df[['A', 'B']].drop_duplicates()
index = pd.MultiIndex.from_frame(groupings.merge(months.to_frame(), how='cross'))
print(
df.set_index(['A', 'B', 'month']).reindex(index, fill_value=0)
)
foo N
# A B month
# 3 9 1983-03-01 0 1
# 1983-04-01 0 0
# 1983-05-01 0 0
# 1983-06-01 0 1
# 1983-07-01 0 0
# 1983-08-01 0 0
# 1983-09-01 0 1
# 1983-10-01 0 0
# 1983-11-01 0 0
# 1983-12-01 0 0
# 1984-01-01 0 0
# 1984-02-01 0 0
# 1984-03-01 0 0
# 1984-04-01 0 0
# 1984-05-01 0 0
# 1984-06-01 0 1
# 1984-07-01 0 0
# 1984-08-01 0 0
# 1984-09-01 0 2
# 4 5 1983-03-01 0 0
# 1983-04-01 0 0
# 1983-05-01 0 0
# 1983-06-01 0 0
# 1983-07-01 0 0
# 1983-08-01 0 0
# 1983-09-01 0 0
# 1983-10-01 0 0
# 1983-11-01 0 1
# 1983-12-01 0 0
# 1984-01-01 0 0
# 1984-02-01 0 0
# 1984-03-01 0 0
# 1984-04-01 0 0
# 1984-05-01 0 1
# 1984-06-01 0 0
# 1984-07-01 0 0
# 1984-08-01 0 0
# 1984-09-01 0 0
Это может быть наиболее эффективно, поскольку повторная выборка оптимизирована для группировки.
@sammywemmy, но это работает не так, как ожидалось - повторная выборка здесь только заполняет пробелы внутри каждой группы, но не гарантирует, что все они имеют одинаковую общую дату начала/окончания.
Можете ли вы рассказать об этом подробнее? Отображаемый результат соответствует ожидаемому результату, а также другим ответам здесь.
@FooBar, если вам нужна одинаковая дата начала и окончания, тогда группировка не требуется. Кроме того, ответ здесь такой же, как у Mozway
@sammywemmy, да, я даже этого не заметил!
Пожалуйста, добавьте ожидаемый выходной фрейм данных