代码之家  ›  专栏  ›  技术社区  ›  FLab

熊猫:将掩码应用于多索引数据帧

  •  4
  • FLab  · 技术社区  · 7 年前

    我有一个带有多索引列的pandas数据框架,有3个级别:

    import itertools
    import numpy as np
    
    def mklbl(prefix, n):
        return ["%s%s" % (prefix, i) for i in range(n)]
    
    
    miindex = pd.MultiIndex.from_product([mklbl('A', 4)])
    
    micolumns = pd.MultiIndex.from_tuples(list(itertools.product(['A', 'B'], ['a', 'b', 'c'], ['foo', 'bar'])),
                                          names=['lvl0', 'lvl1', 'lvl2'])
    
    dfmi = pd.DataFrame(np.arange(len(miindex) * len(micolumns)).reshape((len(miindex), len(micolumns))),
                        index=miindex,
                        columns=micolumns).sort_index().sort_index(axis=1)
    
    lvl0   A                       B                    
    lvl1   a       b       c       a       b       c    
    lvl2 bar foo bar foo bar foo bar foo bar foo bar foo
    A0     1   0   3   2   5   4   7   6   9   8  11  10
    A1    13  12  15  14  17  16  19  18  21  20  23  22
    A2    25  24  27  26  29  28  31  30  33  32  35  34
    A3    37  36  39  38  41  40  43  42  45  44  47  46
    

    我想基于另一个数据帧屏蔽这个数据帧,该数据帧具有索引的最后两个级别:

    cols = micolumns.droplevel(0).unique()
    a_mask = pd.DataFrame(np.random.randn(len(dfmi.index), len(cols)), index=dfmi.index, columns=cols)
    a_mask = (np.sign(a_mask) > 0).astype(bool)
    
            a             b             c       
          foo    bar    foo    bar    foo    bar
    A0  False  False  False   True   True  False
    A1   True  False   True  False   True   True
    A2   True   True   True   True  False  False
    A3   True  False  False   True   True  False
    

    我想做的是根据 a_mask . 假设我想将原始条目设置为零,当 a_面罩 是真的。

    我试着使用 pd.IndexSlice ,但它会无声地失败(即,我可以运行以下代码,但没有效果:

    dfmi.loc[:, pd.IndexSlice[:, a_mask]] = 0  #dfmi is unchanged
    

    如何实现这一点,有什么建议吗?

    编辑 在我的用例中,标签是用笛卡尔乘积构造的,因此会有(lev0,lev1,lev2)的所有组合。 但情况是,lev0可以假设2个值{A,B},而lev1可以假设3个值{A,B,c}

    4 回复  |  直到 7 年前
        1
  •  3
  •   BENY    7 年前

    我认为用这种方法更安全。

    dfmi.where(a_mask.loc[:,dfmi.columns.droplevel(0)].values,0)
    Out[191]: 
    lvl0   A               B            
    lvl1   a       b       a       b    
    lvl2 bar foo bar foo bar foo bar foo
    A0     0   0   0   2   0   0   0   6
    A1     9   8  11   0  13  12  15   0
    A2     0  16  19  18   0  20  23  22
    A3    25   0   0   0  29   0   0   0
    
        2
  •  2
  •   JoeCondron    7 年前

    我会这样做:

    mask = pd.concat({k: a_mask for k in dfmi.columns.levels[0]}, axis=1)
    dfmi.where(~mask, 0)
    
        3
  •  1
  •   Divakar    7 年前

    使用底层阵列数据进行现场编辑以提高内存效率(不会创建任何其他数据帧)-

    d = len(dfmi.columns.levels[0])
    n = dfmi.shape[1]//d
    for i in range(0,d*n,n):
        dfmi.values[:,i:i+n][a_mask] = 0
    

    样本运行-

    In [833]: dfmi
    Out[833]: 
    lvl0   A                       B                    
    lvl1   a       b       c       a       b       c    
    lvl2 bar foo bar foo bar foo bar foo bar foo bar foo
    A0     1   0   3   2   5   4   7   6   9   8  11  10
    A1    13  12  15  14  17  16  19  18  21  20  23  22
    A2    25  24  27  26  29  28  31  30  33  32  35  34
    A3    37  36  39  38  41  40  43  42  45  44  47  46
    
    In [834]: a_mask
    Out[834]: 
            a             b             c       
          foo    bar    foo    bar    foo    bar
    A0   True   True   True  False  False  False
    A1  False   True  False  False   True  False
    A2  False   True   True   True  False  False
    A3  False  False  False  False  False   True
    
    In [835]: d = len(dfmi.columns.levels[0])
         ...: n = dfmi.shape[1]//d
         ...: for i in range(0,d*n,n):
         ...:     dfmi.values[:,i:i+n][a_mask] = 0
    
    In [836]: dfmi
    Out[836]: 
    lvl0   A                       B                    
    lvl1   a       b       c       a       b       c    
    lvl2 bar foo bar foo bar foo bar foo bar foo bar foo
    A0     0   0   0   2   5   4   0   0   0   8  11  10
    A1    13   0  15  14   0  16  19   0  21  20   0  22
    A2    25   0   0   0  29  28  31   0   0   0  35  34
    A3    37  36  39  38  41   0  43  42  45  44  47   0
    
        4
  •  0
  •   Scott Boston    7 年前

    更新的解决方案更多roboust,而不是级别值的硬编码:

    lvl0_values = dfmi.columns.get_level_values(0).unique()
    pd.concat([dfmi[i].mask(a_mask.rename_axis(['lvl1','lvl2'],axis=1),0) for i in lvl0_values],
              keys=lvl0_values, axis=1)
    

    输出:

    lvl0   A               B            
    lvl1   a       b       a       b    
    lvl2 bar foo bar foo bar foo bar foo
    A0     1   0   0   0   5   0   0   0
    A1     9   0  11   0  13   0  15   0
    A2    17  16  19   0  21  20  23   0
    A3     0  24   0  26   0  28   0  30
    

    一种方法是:

    pd.concat([dfmi['A'].mask(a_mask.rename_axis(['lvl1','lvl2'],axis=1),0),
               dfmi['B'].mask(a_mask.rename_axis(['lvl1','lvl2'],axis=1),0)],
               keys=['A','B'], axis=1)
    
    print(a_mask)
    
    lvl1      a             b       
    lvl2    foo    bar    foo    bar
    A0     True  False   True   True
    A1     True  False   True  False
    A2    False  False   True  False
    A3    False   True  False   True
    

    输出:

           A               B            
    lvl1   a       b       a       b    
    lvl2 bar foo bar foo bar foo bar foo
    A0     1   0   0   0   5   0   0   0
    A1     9   0  11   0  13   0  15   0
    A2    17  16  19   0  21  20  23   0
    A3     0  24   0  26   0  28   0  30