Pandas的多级索引(MultiIndex)的行为是否会随着层级数量的变化而改变?

编程语言 2026-07-09

我有一个行索引为多级且层级超过两个的Pandas Series。

arrays = [
    ["bar", "baz",  "foo",   "qux"],
    ["one", "two",],
    ["a", "b","c" ]
]

index = pd.MultiIndex.from_product(arrays, names=["first", "second","third"])

s = pd.Series(np.random.randn(24), index=index)

我能够像下面这样对这个DataFrame进行切片:

df.loc[(slice(None), slice(None), "a")

输出如下

first  second
bar    one       1.574135
       two       1.557756
baz    one      -0.023177
       two      -0.537326
foo    one      -0.183099
       two      -1.776498
qux    one       0.425121
       two       0.451778
dtype: float64

为了让某些绘图更方便,我重置了索引,将第三列作为行的一部分嵌入进来。

s = s.reset_index(level = [2])

                third   0
first   second      
bar one a   1.574135
        one b   -2.450800
        one c   0.966331
        two a   1.557756
        two b   -0.800615
        two c   0.405982
baz one a   -0.023177
        one b   1.743043
        one c   0.847148
        two a   -0.537326
        two b   -1.157622
        two c   -0.229345
foo one a   -0.183099
        one b   1.047875
        one c   -1.980324
        two a   -1.776498
        two b   -1.169729
        two c   -1.509195
qux one a   0.425121
        one b   0.903984
        one c   -0.455681
        two a   0.451778
        two b   0.215866
        two c   -0.090196

现在,使用相同的格式进行切片时会得到KeyError

s.loc[(slice(None),"one")]

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File c:filepath\pandas\core\indexes\base.py:3812, in Index.get_loc(self, key)
   3811 try:
-> 3812     return self._engine.get_loc(casted_key)
   3813 except KeyError as err:

File pandas/_libs/index.pyx:167, in pandas._libs.index.IndexEngine.get_loc()

File pandas/_libs/index.pyx:196, in pandas._libs.index.IndexEngine.get_loc()

File pandas/_libs/hashtable_class_helper.pxi:7088, in pandas._libs.hashtable.PyObjectHashTable.get_item()

File pandas/_libs/hashtable_class_helper.pxi:7096, in pandas._libs.hashtable.PyObjectHashTable.get_item()

KeyError: 'one'

The above exception was the direct cause of the following exception:

KeyError                                  Traceback (most recent call last)
Cell In[65], line 1
----> 1 s.loc[(slice(None),"one")]

File c:filepath\pandas\core\indexing.py:1184, in _LocationIndexer.__getitem__(self, key)
   1182     if self._is_scalar_access(key):
...
   3822     #  InvalidIndexError. Otherwise we fall through and re-raise
   3823     #  the TypeError.
   3824     self._check_indexing_error(key)

KeyError: 'one'

摸了半天头后,我想也许在切片时只传入了两个参数,pandas以为第二个是列切片,尽管它在括号内。

果不其然,在外侧再加一个slice(None),以让pandas明确第二个参数不是用于列,结果是

s.loc[(slice(None),"one"),slice(None)]


            third   0
first   second      
bar one a   1.574135
        one b   -2.450800
        one c   0.966331
baz one a   -0.023177
        one b   1.743043
        one c   0.847148
foo one a   -0.183099
        one b   1.047875
        one c   -1.980324
qux one a   0.425121
        one b   0.903984
        one c   -0.455681

为什么pandas会这样工作?这只是一个怪现象吗?将来我应该怎么做来避免这个问题?

解决方案

当你重置索引时,你创建的是DataFrame,而不是Series。 在DataFrame上使用 loc 需要 [row_indexer, column_indexer] _getitem_tuple_same_dim 似乎就是该函数。

    def _getitem_tuple_same_dim(self, tup: tuple):
        """
        Index with indexers that should return an object of the same dimension
        as self.obj.

        This is only called after a failed call to _getitem_lowerdim.
        """
        retval = self.obj
        # Selecting columns before rows is significantly faster
        start_val = (self.ndim - len(tup)) + 1
        for i, key in enumerate(reversed(tup)):
            i = self.ndim - i - start_val
            if com.is_null_slice(key):
                continue

            retval = getattr(retval, self.name)._getitem_axis(key, axis=i)
            # We should never have retval.ndim < self.ndim, as that should
            #  be handled by the _getitem_lowerdim call above.
            assert retval.ndim == self.ndim

        if retval is self.obj:
            # if all axes were a null slice (`df.loc[:, :]`), ensure we still
            # return a new object (https://github.com/pandas-dev/pandas/pull/49469)
            retval = retval.copy(deep=False)

        return retval

self.ndim 对于Series为 1,对于DataFrame为 2

于是发生的情况是 start_val = (2 - 2) + 1,接着它对元组 for i, key in enumerate(reversed(tup)): 进行逆序迭代,因此对你包含 (slice(None), "one") 的元组的第一次迭代变成 i=0; key='one'。它之所以进行反转,是因为显然“先选择列再选择行要快得多”。这意味着你原始元组的最后一个值 (slice(None), "one") 正在寻找名为 one 的列,而该列并不存在,于是你就得到KeyError。

如果把它完整推导下去

start_val = (2 - 2) + 1 = 1
i = 0, key = 'one'  # from enumerate(reversed(tup))
i = 2 - 0 - 1 = 1  # i gets reassigned
_getitem_axis('one', axis=1) # axis=1 means columns which results in a KeyError
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章