【问题标题】:Pandas: Apply mask to multiindex dataframePandas:将掩码应用于多索引数据帧
【发布时间】:2018-05-11 01:06:16
【问题描述】:

我有一个带有 MultiIndex 列的 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_mask 为真时,我想将原始条目设置为零。

我尝试使用pd.IndexSlice,但它静默失败(即我可以运行以下代码,但没有效果:

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

有什么建议可以实现吗?

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

【问题讨论】:

  • 那么,dfmi.Adfmi.B 的形状应该是一样的,对吧?
  • 是的,我的情况是这样

标签: python pandas multi-index


【解决方案1】:

我认为使用这种方式更安全。

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

【讨论】:

  • 很好用的droplevel。我一直在尝试使用空的slicer 却没有得到任何结果。
  • 谢谢,很好的回答
  • @FLab yw~ 快乐编码
【解决方案2】:

我会这样做:

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

【讨论】:

  • 谢谢,这个解决方案有效,但我会在接受之前等待其他建议。我建议进行以下改进,使其更加通用。 mask = pd.concat(dict.fromkeys(dfmi.columns.get_level_values(0), a_mask), axis=1) dfmi = dfmi.mask(mask, 0)
  • 这是一个聪明的解决方案,但在您更新的问题中,标签仍然是简单的“重复”(a,b,c / a,b,c)......如果这个情况并非如此,串联解决方案将不起作用。而且我仍然认为.loc 应该能够以某种方式在这里工作。
  • 例如,如果你要删除dfmi[('A', 'a', 'foo')],你有“不均匀”的数据
  • 在答案中澄清
  • @BradSolomon 添加 .loc 方法。 :-)
【解决方案3】:

使用底层数组数据进行原位编辑以提高内存效率(不创建任何其他数据帧)-

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

【讨论】:

  • 感谢您的回答。您的解决方案是否依赖于每个级别的列具有相同数量的标签(2、2、2)这一事实?
  • @FLab 确实如此!
  • 呃...不幸的是,在我的用例中情况并非如此(我的错误使示例不那么通用,我将对其进行更新)。我真的很喜欢你的这个答案:stackoverflow.com/questions/41493177/…我想知道它是否可以针对这种情况进行修改/概括?
  • @FLab 好吧,只要 dfmi.A 和 dfmi.B 具有相同的形状,这应该可以工作,无论它们的形状是什么。这是唯一的要求。
  • 另一个澄清:您是否假设顶级列级别只有两个条目?如果我有A,B,C,你会做如下吗? a[:,:n][a_mask] = 0 ; a[:,n:2*n][a_mask] = 0; a[:,2*n:][a_mask] = 0
【解决方案4】:

更新的解决方案更强大,而不是对级别值进行硬编码:

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

【讨论】:

    猜你喜欢
    • 2013-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-10
    • 2015-09-13
    相关资源
    最近更新 更多