按MultiIndex级别或子级别切片pandas DataFrame

12 投票
3 回答
12322 浏览
提问于 2025-04-18 02:11

受到这个回答的启发,以及对这个问题没有简单答案的困扰,我写了一些小代码,让在MultiIndex层级中筛选变得更简单。

def _filter_series(x, level_name, filter_by):
    """
    Filter a pd.Series or pd.DataFrame x by `filter_by` on the MultiIndex level
    `level_name`

    Uses `pd.Index.get_level_values()` in the background. `filter_by` is either
    a string or an iterable.
    """
    if isinstance(x, pd.Series) or isinstance(x, pd.DataFrame):
        if type(filter_by) is str:
            filter_by = [filter_by]

        index = x.index.get_level_values(level_name).isin(filter_by)
        return x[index]
    else:
        print "Not a pandas object"

不过,如果我对pandas开发团队了解得越来越多(我正在慢慢了解!),我觉得他们应该已经有一个很好的方法来做到这一点,只是我还不知道是什么!

我说得对吗?

3 个回答

1

你可以使用filter这个方法来做一些事情。例如,在这个链接中的问题里提到的例子:

In [188]: df.filter(like='0630', axis=0)
Out[188]: 
                      sales        cogs    net_pft
STK_ID RPT_Date                                   
876    20060630   857483000   729541000   67157200
       20070630  1146245000  1050808000  113468500
       20080630  1932470000  1777010000  133756300
2254   20070630   501221000   289167000  118012200

目前,filter方法正在进行重构(在即将发布的0.14版本中),会增加一个level参数(因为现在如果同样的标签出现在索引的不同层级时,可能会出现问题)。

5

使用新版本的多重索引切片功能,这个操作非常简单,版本是master/0.14(很快就会发布),你可以在这里查看相关信息。

目前有一个开放的问题,目的是让这个语法更简单(其实并不难),你可以在这里找到。比如说,像这样写:df.loc[{ 'third' : ['C1','C3'] }]我觉得是合理的。

下面是如何操作的(需要使用master/0.14版本):

In [2]: def mklbl(prefix,n):
   ...:     return ["%s%s" % (prefix,i)  for i in range(n)]
   ...: 


In [11]: index = MultiIndex.from_product([mklbl('A',4),
mklbl('B',2),
mklbl('C',4),
mklbl('D',2)],names=['first','second','third','fourth'])

In [12]: columns = ['value']

In [13]: df = DataFrame(np.arange(len(index)*len(columns)).reshape((len(index),len(columns))),index=index,columns=columns).sortlevel()

In [14]: df
Out[14]: 
                           value
first second third fourth       
A0    B0     C0    D0          0
                   D1          1
             C1    D0          2
                   D1          3
             C2    D0          4
                   D1          5
             C3    D0          6
                   D1          7
      B1     C0    D0          8
                   D1          9
             C1    D0         10
                   D1         11
             C2    D0         12
                   D1         13
             C3    D0         14
                   D1         15
A1    B0     C0    D0         16
                   D1         17
             C1    D0         18
                   D1         19
             C2    D0         20
                   D1         21
             C3    D0         22
                   D1         23
      B1     C0    D0         24
                   D1         25
             C1    D0         26
                   D1         27
             C2    D0         28
                   D1         29
             C3    D0         30
                   D1         31
A2    B0     C0    D0         32
                   D1         33
             C1    D0         34
                   D1         35
             C2    D0         36
                   D1         37
             C3    D0         38
                   D1         39
      B1     C0    D0         40
                   D1         41
             C1    D0         42
                   D1         43
             C2    D0         44
                   D1         45
             C3    D0         46
                   D1         47
A3    B0     C0    D0         48
                   D1         49
             C1    D0         50
                   D1         51
             C2    D0         52
                   D1         53
             C3    D0         54
                   D1         55
      B1     C0    D0         56
                   D1         57
             C1    D0         58
                   D1         59
                             ...

[64 rows x 1 columns]

在所有层级中创建一个索引器,选择所有条目。

In [15]: indexer = [slice(None)]*len(df.index.names)

让我们关心的层级只包含我们需要的条目。

In [16]: indexer[df.index.names.index('third')] = ['C1','C3']

选择它(这里要注意,这必须是一个元组!)

In [18]: df.loc[tuple(indexer),:]
Out[18]: 
                           value
first second third fourth       
A0    B0     C1    D0          2
                   D1          3
             C3    D0          6
                   D1          7
      B1     C1    D0         10
                   D1         11
             C3    D0         14
                   D1         15
A1    B0     C1    D0         18
                   D1         19
             C3    D0         22
                   D1         23
      B1     C1    D0         26
                   D1         27
             C3    D0         30
                   D1         31
A2    B0     C1    D0         34
                   D1         35
             C3    D0         38
                   D1         39
      B1     C1    D0         42
                   D1         43
             C3    D0         46
                   D1         47
A3    B0     C1    D0         50
                   D1         51
             C3    D0         54
                   D1         55
      B1     C1    D0         58
                   D1         59
             C3    D0         62
                   D1         63

[32 rows x 1 columns]
6

我其实给joris的回答点了赞……但不幸的是,他提到的重构在0.14版本中并没有发生,在0.17版本中也没有。所以目前我想给你推荐一个简单粗暴的解决方案(显然是从Jeff的方案中得来的):

def filter_by(df, constraints):
    """Filter MultiIndex by sublevels."""
    indexer = [constraints[name] if name in constraints else slice(None)
               for name in df.index.names]
    return df.loc[tuple(indexer)] if len(df.shape) == 1 else df.loc[tuple(indexer),]

pd.Series.filter_by = filter_by
pd.DataFrame.filter_by = filter_by

……可以这样使用

df.filter_by({'level_name' : value})

这里的value实际上可以是一个单一的值,也可以是一个列表,或者一个切片……

(在面板和更高维度的元素上没有经过测试,但我预计它应该能工作)

撰写回答