Агрегация вложенных DataFrames, определяемых наборами индексов без цикла

Предположим, у меня есть DataFrame Pandas, я приведу простой пример:

import pandas as pd
df = pd.DataFrame(columns=["A", "B"], data = [(1, 2), (4, 5), (7, 8), (10, 11)])

У меня есть набор индексов, давайте сделаем его простым и случайным:

inds = [(0, 1, 3), (0, 1, 2), (1, 2, 3)]

Я хочу агрегировать данные в соответствии с этими индексами следующим образом, например, если операция агрегирования является средним значением, которое я бы получил:

А Б df.loc[inds[0], "A"].mean()df.loc[inds[0], "B"].mean()df.loc[inds[1], "A"].mean()df.loc[inds[1], "B"].mean()df.loc[inds[2], "A"].mean()df.loc[inds[2], "B"].mean()

Есть ли способ выполнить это в чистых пандах без написания цикла?

Это очень похоже на операцию типа df.groupby, а затем .agg, но я не нашел способа создать объект GroupBy из пользовательского набора индексов.

Можете ли вы предоставить, как выглядит ожидаемый результат?

iBeMeltin 21.05.2024 18:36

DataFrame, соответствующий таблице в сообщении.

DimB 22.05.2024 11:19
Почему в 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 может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
4
2
92
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Редактировать: показано, как добиться этого с помощью groupby, но, конечно, «значительно проще думать об этом как о проблеме выбора по индексу»; см. ответ от @HenryEcker.


Вариант 1 (reindex + groupby)

s = pd.Series(inds).explode()

out = df.reindex(s).groupby(s.index).mean()

out

     A    B
0  5.0  6.0 # i.e. A: (1+4+10)/3, B: (2+5+11)/3, etc.
1  4.0  5.0
2  7.0  8.0

Объяснение

  • Используйте inds, чтобы создать pd.Series (здесь: s), и примените series.explode. Значения индекса функционируют как идентификаторы групп:
# intermediate series ('group 0, 1, 2')

0    0
0    1
0    3
1    0
1    1
1    2
2    1
2    2
2    3
dtype: object

Вариант 2 (merge + groupby)

out = (
    df.merge(
        pd.Series(inds, name='g').explode(), 
        left_index=True, 
        right_on='g', 
        how='right'
        )
    .drop(columns=['g'])
    .groupby(level=0)
    .mean()
    )

# same result

Объяснение

  • Как и в варианте 1, мы создаем pd.Series и разбираем его, но на этот раз мы добавляем имя, которое нам понадобится для слияния на следующем шаге.
  • Теперь используйте df.merge с how=right, чтобы добавить значения из df, используя значения g из нашей серии и индекс из df в качестве ключей.
  • Наконец, отбросьте столбец 'g' (df.drop ), примените df.groupby к индексу (level=0) и получите groupby.mean.

В этом случае, хотя решение через призму группировки панд, безусловно, возможно, значительно проще думать об этом как о проблеме выбора по индексу.

Функция pandas groupby в первую очередь предназначена для упрощения преобразования данных в индексы, которые можно использовать для идентификации групп. Однако в этом случае мы начинаем с жизнеспособных значений индекса, поэтому нам не нужна помощь функции для повторного создания этих значений.

Мы можем просто преобразовать наш DataFrame в_numpy, а затем использовать inds, чтобы выбрать нужные значения.

df.to_numpy()[inds, :]

# [[[ 1  2]
#   [ 4  5]
#   [10 11]]
# 
#  [[ 1  2]
#   [ 4  5]
#   [ 7  8]]
# 
#  [[ 4  5]
#   [ 7  8]
#   [10 11]]]

Отсюда легко взять среднее по любому измерению, которое мы хотим.

a = df.to_numpy()[inds, :].mean(axis=1)

# [[5. 6.]
#  [4. 5.]
#  [7. 8.]]

Это можно превратить обратно в DataFrame, обернув его в конструктор.

new_df = pd.DataFrame(df.to_numpy()[inds, :].mean(axis=1), columns=df.columns)

#      A    B
# 0  5.0  6.0
# 1  4.0  5.0
# 2  7.0  8.0

При желании можно даже использовать inds в качестве index нашего DataFrame:

new_df = pd.DataFrame(df.to_numpy()[inds, :].mean(axis=1), columns=df.columns, index=inds)

#              A    B
# (0, 1, 3)  5.0  6.0
# (0, 1, 2)  4.0  5.0
# (1, 2, 3)  7.0  8.0

Существует множество готовых агрегаций ndarray, но если необходимо выполнить какую-то пользовательскую агрегацию, используйте apply_along_axis с функцией или лямбда-выражением вместо означает:

def custom_fn(a):
    # Whatever calculations
    return np.mean(a) / np.std(a) + np.ptp(a)


new_df = pd.DataFrame(
    np.apply_along_axis(custom_fn, 1, df.to_numpy()[inds, :]), 
    columns=df.columns, 
    index=inds
)

#                    A          B
# (0, 1, 3)  10.336306  10.603567
# (0, 1, 2)   7.632993   8.041241
# (1, 2, 3)   8.857738   9.265986

+1. И вот я читал о проблеме XY буквально вчера... Это действительно приятно! Очевидно, это то, что должен использовать ОП.

ouroboros1 22.05.2024 06:56

Отличное решение проблемы, о которой я заявил, однако мне следовало взять индексы, которые, возможно, не одинакового размера, например inds = [(0, 1), (0, 1, 2), (0, 1, 2, 3)] потому что моя реальная проблема требует именно таких, и это решение не работает в этом случае, в то время как решение @ouroboros1 работает . Извините за эту ошибку

DimB 22.05.2024 11:34

Да, этот подход масштабируется только в том случае, если индексы имеют обычную длину. Вы, конечно, можете вернуться к некоторой итерации: a = df.to_numpy() и создать из нее новый DF new_df = pd.DataFrame([a[i, :].mean(axis=0) for i in inds]). Вы можете рассмотреть возможность проведения некоторых тестов производительности. Наверняка существуют размеры DF, где понимание массива numpy происходит быстрее, чем операции pandas. Это, конечно, зависит от вашей фактической агрегации и данных.

Henry Ecker 22.05.2024 13:15

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