Переиндексация для расширения и заполнения значения только на одном уровне мультииндекса

У меня есть фрейм данных с индексом (месяц, 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.

Пожалуйста, добавьте ожидаемый выходной фрейм данных

sammywemmy 04.06.2024 23:16

Я считаю, что угадал правильный результат, см. мое редактирование моего ответа. В противном случае некоторые образцы вывода были бы оценены по достоинству.

Cameron Riddell 05.06.2024 15:47
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
2
83
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Предполагая:

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

Я не рассматривал возможность группировки по чему-то другому, кроме индекса — очень здорово!

FooBar 04.06.2024 09:28

Я попытался реализовать это на своих реальных данных, где у меня есть ~350 уникальных значений для A и B соответственно — это не очень хорошо масштабируется, поскольку итеративно проходит через все группы. Есть ли альтернатива?

FooBar 04.06.2024 15:08

В любом случае вам придется перебирать каждую группу, чтобы найти минимальную/максимальную дату. Возможно, можно будет распараллелить использование parallel_pandas , поскольку операции независимы.

mozway 04.06.2024 16:31

Решение @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.

FooBar 04.06.2024 15:14

Вы также можете попробовать здесь шаблон 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 04.06.2024 16:13

@sammywemmy, но это работает не так, как ожидалось - повторная выборка здесь только заполняет пробелы внутри каждой группы, но не гарантирует, что все они имеют одинаковую общую дату начала/окончания.

FooBar 04.06.2024 17:37

Можете ли вы рассказать об этом подробнее? Отображаемый результат соответствует ожидаемому результату, а также другим ответам здесь.

Cameron Riddell 04.06.2024 18:24

@FooBar, если вам нужна одинаковая дата начала и окончания, тогда группировка не требуется. Кроме того, ответ здесь такой же, как у Mozway

sammywemmy 04.06.2024 23:16

@sammywemmy, да, я даже этого не заметил!

FooBar 05.06.2024 07:44

Другие вопросы по теме

Разделите большой текстовый файл на более мелкие текстовые файлы при обнаружении определенной строки, но с минимальным количеством строк
Python: 2 (или более) условные строки в группе по agg
Каков синтаксис использования группировки по индексу столбца, а не по имени столбца?
Избегание итерации в pandas, когда я хочу обновить значение в столбце x, когда условие истинно, где x задан другим столбцом
Более разумный способ создать разницу между двумя кадрами данных панд?
Объедините столбцы в один столбец, используя панды
Как вычислить расхождение сходимости скользящего среднего без использования функции pandas ewm?
Данные опроса за многие периоды: преобразование в текущий и предыдущий период (от широкого формата до длинного)
Как применить различные типы областей ошибок к некоторым частям фасетной сетки
Разделение столбцов фрейма данных Python Pandas на основе предыдущей уникальной комбинации столбцов