pandas MultiIndex 的多个切片器

pandas multiple slicers of MultiIndex

提问人:goweon 提问时间:5/23/2023 更新时间:8/11/2023 访问量:83

问:

我想从具有多个独立索引器的 MultiIndex 中选择多个列。例如

df = pd.DataFrame(
    np.zeros((2,4)),
    columns=pd.MultiIndex.from_product([('a','b'),(1,2)])
)

从此 DataFrame

    a       b   
    1   2   1   2
0   0   0   0   0
1   0   0   0   0

我想选择加号下的所有列,比如 ,但我不想明确指定下面列的所有级别。'a'('b', 1)df[[('a', 1), ('a', 2), ('b', 1)]]'a'

什么不起作用:

  • df[['a', ('b', 1)]]:KeyError: "[('b', 1)] not in index"
  • df.loc[:, ['a', ('b', 1)]]:KeyError: "[('b', 1)] not in index"
  • df[[('a', slice(None)), ('b', 1)]]:TypeError: unhashable type: 'slice'
  • df.loc[:, [pd.IndexSlice['a', :], ('b', 1)]]:TypeError: unhashable type: 'slice'

我希望能够做的另一件类似的事情是:加上('a', 1)pd.IndexSlice[:, 2]

Pandas Slice 多索引

评论


答:

1赞 Ynjxsjmh 5/23/2023 #1

您可以单独选择列并连接它们。

out = pd.concat([df.xs('a', axis=1, drop_level=False),
                 df.xs(('b', 1), axis=1, drop_level=False)],
                axis=1)
print(out)

     a         b
     1    2    1
0  0.0  0.0  0.0
1  0.0  0.0  0.0
0赞 rhug123 5/23/2023 #2

这是一个使用两个布尔掩码的选项

m1 = df.columns.get_level_values(0).isin(['a'])
m2 = df.columns.get_level_values(1).isin([1])

df.loc[:,m1|m2]

输出:

     a         b
     1    2    1
0  0.0  0.0  0.0
1  0.0  0.0  0.0

评论

0赞 goweon 5/24/2023
这可能是最快的,但不那么灵活,因为它不使用标准的切片语法
1赞 PaulS 5/23/2023 #3

另一种可能的解决方案:

df.loc[:,('a', slice(None))].join(df.loc[:,('b', 1)])

或者:

df[[(x,y) for x, y in df.columns if (x == 'a') | ((x == 'b') & (y == 1))]]

输出:

     a         b
     1    2    1
0  0.0  0.0  0.0
1  0.0  0.0  0.0
0赞 sammywemmy 5/24/2023 #4

对于共享的示例,切片效果很好:

df.loc(axis=1)[('a',1):('b',1)]
     a         b
     1    2    1
0  0.0  0.0  0.0
1  0.0  0.0  0.0

根据您的反馈,您需要更灵活的东西 - 标准切片语法不适合这里。一种选择是select_columns

# pip install pyjanitor
import janitor
df.select_columns({0:'a'}, ('b',2))
     a         b
     1    2    2
0  0.0  0.0  0.0
1  0.0  0.0  0.0

该语法允许在带有字典的级别(键是级别,值是标签)或通过元组在 MultiIndex 上进行选择。

评论

0赞 goweon 5/30/2023
这还不够笼统,没有用,因为切片仅适用于示例,因为列恰好彼此相邻。即您可以使用此方法选择加号,但不能选择加号'a'('b', 1)'a'('b', 2)
0赞 sammywemmy 5/31/2023
答案是特定于你问的问题。您要求的不再是切片,而是一些通用/灵活的选项。
0赞 goweon 8/11/2023 #5

我最终编写了一个辅助函数来做到这一点。它采用一个切片器数组,并独立使用每个切片器,然后收集所有选定的列。

def mimsc(col_specs):
    # usage: df.loc[msms(['A', ('B', 'X')])]
    def slicer(df):
        cols = []
        dfc = df.columns.to_frame()
        for cs in col_specs:
            cols.append(dfc.loc[cs])
        all_cols = pd.concat(cols, ignore_index=True)
        return pd.MultiIndex.from_frame(all_cols)
    return slicer

用法

df.loc[:, mimsc(['a', ('b', 1)])]
df.loc[:, mimsc([('b', 1), pd.IndexSlice[:, 2]])]

下面是一个更通用的版本,也适用于索引

def mims(col_specs, axis=1):
    def slicer(df):
        cols = []
        if axis==1:
            dfc = df.columns.to_frame()
        elif axis==0:
            dfc = df.index.to_frame()
        for cs in col_specs:
            col = dfc.loc[cs, :]
            if isinstance(col, pd.Series):
                col = dfc.loc[[cs], :]
            cols.append(col)
        all_cols = pd.concat(cols, ignore_index=True)
        return pd.MultiIndex.from_frame(all_cols)
    return slicer

df.T.loc[mims(['a', ('b', 1)], axis=0), :]
df.T.loc[mims([('b', 1), pd.IndexSlice[:, 2]], axis=0), :]