【问题标题】:Pandas multiply dataframes with multiindex and overlapping index levelsPandas 将数据帧与多索引和重叠索引级别相乘
【发布时间】:2017-05-20 11:26:51
【问题描述】:

我正在努力完成一项应该很简单的任务,但它并没有像我想象的那样工作。我有两个数字数据框 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)        

我尝试使用带有 level 关键字的 pandas 乘法函数:

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

但它会抛出一个错误:“TypeError: Join on level between two MultiIndex objects is ambiguous”

执行此操作的正确方法是什么?

【问题讨论】:

    标签: python pandas


    【解决方案1】:

    我只需在较小形状的DF 上使用DF.reindex 来匹配较大形状DF's 的索引,然后向前填充其中存在的值。然后做乘法。

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

    演示:

    准备一些数据:

    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
    

    现在您甚至可以在DF.multiply 中提供level 参数,以便在匹配的索引处进行广播。

    【讨论】:

    • 伟大而干净的解决方案!
    • 对于其他使用这个(很好的)答案的人,请注意,较大数据框中的“额外”索引级别需要排在最后。
    • 这里甚至不需要使用填充方法。如果 A 有不同的列名,但您想保留 B 的列名,只需使用值属性,如 B * A.reindex(B.index).values
    【解决方案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] 以多维数组的形式获取数据帧中的视图。多维性是根据多索引数据帧的层次结构来维护的。因此,第一个数据帧将具有三个级别(包括列),第二个数据帧具有四个级别。因此,我们有a0a1 对应于输入数据帧df0df1,导致a0a1 分别具有34 维度。

    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's 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's solution
    In [33]: %timeit B.multiply(A.reindex(B.index, method='ffill'))
    1000 loops, best of 3: 625 µs per loop
    
    # @root's 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
    

    【讨论】:

    • 我不明白为什么要投反对票。似乎 OP 需要一个纯 pandas 解决方案,如果确实存在的话。但这不是贬低某人的理由。无论如何,我投了赞成票。
    • 我认为这种方法的一个缺点是数据框必须为 MultiIndex 的所有级别的每个组合具有值。否则重塑将不起作用。
    【解决方案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 的匹配部分对 A 进行索引,然后将索引设置为匹配来实现的。如果我更聪明的话,我就能想出一种本地方法来让它工作,但我还没有。 :-(

    【讨论】:

    • 这是一个超级简洁的解决方案。
    猜你喜欢
    • 2021-07-09
    • 2020-09-25
    • 2021-01-10
    • 2019-05-16
    • 1970-01-01
    • 2018-06-24
    • 1970-01-01
    • 1970-01-01
    • 2019-01-22
    相关资源
    最近更新 更多