Pandas умножить числовые кадры с несколькими индексами и перекрывающимися уровнями индексов

Я борюсь с задачей, которая должна быть простой, но она не работает, как я думал. У меня есть два числовых dataframes A и B с несколькими индексами и столбцами ниже:

A =    A    B   C    D
X  1  AX1  BX1 CX1  DX1    
   2  AX2  BX2 CX2  DX2    
   3  AX3  BX3 CX3  DX3    
Y  1  AY1  BY1 CY1  DY1    
   2  AY2  BY2 CY2  DY2
   3  AY3  BY3 CY3  DY3



B =        A     B     C     D
X  1   a  AX1a  BX1a  CX1a  DX1a
       b  AX1b  BX1b  CX1b  DX1b
       c  AX1c  BX1c  CX1c  DX1c        

   2   a  AX2a  BX2a  CX2a  DX2a
       b  AX2b  BX2b  CX2b  DX2b
       c  AX2c  BX2c  CX2c  DX2c 

   3   a  AX3a  BX3a  CX3a  DX3a
       b  AX3b  BX3b  CX3b  DX3b
       c  AX3c  BX3c  CX3c  DX3c 

Y  1   a  AY1a  BY1a  CY1a  DY1a
       b  AY1b  BY1b  CY1b  DY1b
       c  AY1c  BY1c  CY1c  DY1c        

   2   a  AY2a  BY2a  CY2a  DY2a
       b  AY2b  BY2b  CY2b  DY2b
       c  AY2c  BY2c  CY2c  DY2c 

   3   a  AY3a  BY3a  CY3a  DY3a
       b  AY3b  BY3b  CY3b  DY3b
       c  AY3c  BY3c  CY3c  DY3c ## Heading ##

Я хотел бы размножать A * B, транслируя по самому внутреннему уровню B, я хочу получить результирующий фрейм R ниже:

R=              A              B              C              D
X  1   a  (AX1a * AX1)  (BX1a  * BX1)  (CX1a  * CX1)  (DX1a  * DX1)
       b  (AX1b * AX1)  (BX1b  * BX1)  (CX1b  * CX1)  (DX1b  * DX1)
       c  (AX1c * AX1)  (BX1c  * BX1)  (CX1c  * CX1)  (DX1c  * DX1)       

   2   a  (AX2a * AX2)  (BX2a  * BX2)  (CX2a  * CX2)  (DX2a  * DX2)
       b  (AX2b * AX2)  (BX2b  * BX2)  (CX2b  * CX2)  (DX2b  * DX2)
       c  (AX2c * AX2)  (BX2c  * BX2)  (CX2c  * CX2)  (DX2c  * DX2)    

   3   a  (AX3a * AX3)  (BX3a  * BX3)  (CX3a  * CX3)  (DX3a  * DX3)
       b  (AX3b * AX3)  (BX3b  * BX3)  (CX3b  * CX3)  (DX3b  * DX3)
       c  (AX3c * AX3)  (BX3c  * BX3)  (CX3c  * CX3)  (DX3c  * DX3)

Y  1   a  (AY1a * AY1)  (BY1a  * BY1)  (CY1a  * CY1)  (DY1a  * DY1)
       b  (AY1b * AY1)  (BY1b  * BY1)  (CY1b  * CY1)  (DY1b  * DY1)
       c  (AY1c * AY1)  (BY1c  * BY1)  (CY1c  * CY1)  (DY1c  * DY1)       

   2   a  (AY2a * AY2)  (BY2a  * BY2)  (CY2a  * CY2)  (DY2a  * DY2)
       b  (AY2b * AY2)  (BY2b  * BY2)  (CY2b  * CY2)  (DY2b  * DY2)
       c  (AY2c * AY2)  (BY2c  * BY2)  (CY2c  * CY2)  (DY2c  * DY2)    

   3   a  (AY3a * AY3)  (BY3a  * BY3)  (CY3a  * CY3)  (DY3a  * DY3)
       b  (AY3b * AY3)  (BY3b  * BY3)  (CY3b  * CY3)  (DY3b  * DY3)
       c  (AY3c * AY3)  (BY3c  * BY3)  (CY3c  * CY3)  (DY3c  * DY3)        

Я попытался использовать функцию pandas умножить с ключевым словом level, выполнив:

b.multiply(a, level=[0,1])

но он выдает ошибку: "TypeError: объединение на уровне между двумя объектами MultiIndex неоднозначно"

Каков правильный способ выполнения этой операции?

Ответ 1

Я просто использовал DF.reindex в меньшей форме DF, чтобы соответствовать индексу более крупного DF's формы и форварда заполнить значения, присутствующие в нем. Затем сделайте умножение.

B.multiply(A.reindex(B.index, method='ffill'))             # Or method='pad'

Demo:

Подготовить некоторые данные:

np.random.seed(42)
midx1 = pd.MultiIndex.from_product([['X', 'Y'], [1,2,3]])
midx2 = pd.MultiIndex.from_product([['X', 'Y'], [1,2,3], ['a','b','c']])
A = pd.DataFrame(np.random.randint(0,2,(6,4)), midx1, list('ABCD'))
B = pd.DataFrame(np.random.randint(2,4,(18,4)), midx2, list('ABCD'))

Маленький DF:

>>> A

     A  B  C  D
X 1  0  1  0  0
  2  0  1  0  0
  3  0  1  0  0
Y 1  0  0  1  0
  2  1  1  1  0
  3  1  0  1  1

Большой DF:

>>> B 

      A  B  C  D
X 1 a  3  3  3  3
    b  3  3  2  2
    c  3  3  3  2
  2 a  3  2  2  2
    b  2  2  3  3
    c  3  3  3  2
  3 a  3  3  2  3
    b  2  3  2  3
    c  3  2  2  2
Y 1 a  2  2  2  2
    b  2  3  3  2
    c  3  3  3  3
  2 a  2  3  2  3
    b  3  3  2  3
    c  2  3  2  3
  3 a  2  2  3  2
    b  3  3  3  3
    c  3  3  3  3

Умножая их, убедившись, что они имеют общую ось индекса на всех уровнях:

>>> B.multiply(A.reindex(B.index, method='ffill'))

       A  B  C  D
X 1 a  0  3  0  0
    b  0  3  0  0
    c  0  3  0  0
  2 a  0  2  0  0
    b  0  2  0  0
    c  0  3  0  0
  3 a  0  3  0  0
    b  0  3  0  0
    c  0  2  0  0
Y 1 a  0  0  2  0
    b  0  0  3  0
    c  0  0  3  0
  2 a  2  3  2  0
    b  3  3  2  0
    c  2  3  2  0
  3 a  2  0  3  2
    b  3  0  3  3
    c  3  0  3  3

Теперь вы можете даже поставить параметр level в DF.multiply для того, чтобы трансляция имела место в этих соответствующих индексах.

Ответ 2

Предлагаемый подход

Мы говорим о broadcasting, поэтому я хотел бы включить здесь NumPy supported broadcasting.

Код решения будет выглядеть примерно так:

def numpy_broadcasting(df0, df1):
    m,n,r = map(len,df1.index.levels)
    a0 = df0.values.reshape(m,n,-1)
    a1 = df1.values.reshape(m,n,r,-1)
    out = (a1*a0[...,None,:]).reshape(-1,a1.shape[-1])
    df_out = pd.DataFrame(out, index=df1.index, columns=df1.columns)
    return df_out

Основная идея:

1] Получить представления в dataframe как многомерные массивы. Многомерность поддерживается в соответствии с структурой уровня мультииндекса. Таким образом, первый блок данных будет иметь три уровня (включая столбцы), а второй - четыре уровня. Таким образом, мы имеем a0 и a1, соответствующие входным кадрам данных df0 и df1, в результате получим a0 и a1 с размерами 3 и 4.

2) Теперь идет вещательная часть. Мы просто расширяем a0, чтобы иметь 4 измерения, введя новую ось в третью позицию. Эта новая ось будет соответствовать третьей оси от df1. Это позволяет нам выполнять умножение по элементам.

3) Наконец, чтобы получить выходной мультидакс данных, мы просто преобразуем продукт.

Пример прогона:

1) Входные данные -

In [369]: df0
Out[369]: 
     A  B  C  D
0 0  3  2  2  3
  1  6  8  1  0
  2  3  5  1  5
1 0  7  0  3  1
  1  7  0  4  6
  2  2  0  5  0

In [370]: df1
Out[370]: 
       A  B  C  D
0 0 0  4  6  1  2
    1  3  3  4  5
    2  8  1  7  4
  1 0  7  2  5  4
    1  8  6  7  5
    2  0  4  7  1
  2 0  1  4  2  2
    1  2  3  8  1
    2  0  0  5  7
1 0 0  8  6  1  7
    1  0  6  1  4
    2  5  4  7  4
  1 0  4  7  0  1
    1  4  2  6  8
    2  3  1  0  6
  2 0  8  4  7  4
    1  0  6  2  0
    2  7  8  6  1

2) Выходной кадр -

In [371]: df_out
Out[371]: 
        A   B   C   D
0 0 0  12  12   2   6
    1   9   6   8  15
    2  24   2  14  12
  1 0  42  16   5   0
    1  48  48   7   0
    2   0  32   7   0
  2 0   3  20   2  10
    1   6  15   8   5
    2   0   0   5  35
1 0 0  56   0   3   7
    1   0   0   3   4
    2  35   0  21   4
  1 0  28   0   0   6
    1  28   0  24  48
    2  21   0   0  36
  2 0  16   0  35   0
    1   0   0  10   0
    2  14   0  30   0

Бенчмаркинг

In [31]: # Setup input dataframes of the same shape as stated in the question
    ...: individuals = list(range(2))
    ...: time = (0, 1, 2)
    ...: index = pd.MultiIndex.from_tuples(list(product(individuals, time)))
    ...: A = pd.DataFrame(data={'A': np.random.randint(0,9,6), \
    ...:                          'B': np.random.randint(0,9,6), \
    ...:                          'C': np.random.randint(0,9,6), \
    ...:                          'D': np.random.randint(0,9,6)
    ...:                          }, index=index)
    ...: 
    ...: 
    ...: individuals = list(range(2))
    ...: time = (0, 1, 2)
    ...: P = (0,1,2)
    ...: index = pd.MultiIndex.from_tuples(list(product(individuals, time, P)))
    ...: B = pd.DataFrame(data={'A': np.random.randint(0,9,18), \
    ...:                          'B': np.random.randint(0,9,18), \
    ...:                          'C': np.random.randint(0,9,18), \
    ...:                          'D': np.random.randint(0,9,18)}, index=index)
    ...: 

# @DSM solution
In [32]: %timeit B * A.loc[B.index.droplevel(2)].set_index(B.index)
1 loops, best of 3: 8.75 ms per loop

# @Nickil Maveli solution
In [33]: %timeit B.multiply(A.reindex(B.index, method='ffill'))
1000 loops, best of 3: 625 µs per loop

# @root solution
In [34]: %timeit B * np.repeat(A.values, 3, axis=0)
1000 loops, best of 3: 487 µs per loop

In [35]: %timeit numpy_broadcasting(A, B)
1000 loops, best of 3: 191 µs per loop

Ответ 3

Обратите внимание, что я не утверждаю, что это правильный способ сделать эту операцию, только чтобы это было одним из способов сделать это. У меня были проблемы с правильной моделью вещания в прошлом.: -/

Короткий вариант заключается в том, что я завершаю трансляцию вручную и создаю соответствующий промежуточный объект:

In [145]: R = B * A.loc[B.index.droplevel(2)].set_index(B.index)

In [146]: A.loc[("X", 2), "C"]
Out[146]: 0.5294149302910357

In [147]: A.loc[("X", 2), "C"] * B.loc[("X", 2, "c"), "C"]
Out[147]: 0.054262618238601339

In [148]: R.loc[("X", 2, "c"), "C"]
Out[148]: 0.054262618238601339

Это работает путем индексирования в с использованием соответствующих частей B, а затем для установки индекса. Если бы я был более умным, я мог бы найти собственный способ заставить это работать, но я еще не знаю.: - (