首页 > 解决方案 > 更改多索引级别:“ValueError:在级别 0,代码最大值 >= 级别长度”

问题描述

我很难更改一些将 a 的'date'部分调整MultiIndex为这样的代码(依赖于该部分位于位置 0MonthEnd的事实):'date'

offset = pd.offsets.MonthEnd()
df.index.set_levels(df.index.levels[0] + offset, level=0, inplace=True)

inplace论点被标记为已弃用pandas=1.2.1(出于充分的理由,我完全赞成)。

在重构代码时,我想我还想使用命名级别 ( 'date'),而不是int级别 ( 0),以便于阅读和维护。所以,我写道:

level = 'date'
mi = df.index
df = df.set_index(mi.set_level(mi.unique(level) + offset, level=level)

这工作得很好,直到它遇到一个df是另一个副本的副本,其中包含MultiIndex.

考虑以下设置作为一个最小示例:

def get_example_df(notbefore=None):
    np.random.seed(0)
    n = 3
    dates = pd.date_range('2000', freq='MS', periods=n)
    names = list('ab')
    df = pd.DataFrame(
        np.random.randint(10, size=n * len(names)),
        columns=['x'],
        index=pd.MultiIndex.from_product([dates, names],
                                         names=('date', 'name'))
    )
    if notbefore:
        dates = df.index.get_level_values('date')
        df = df.loc[dates >= notbefore]
    return df


level = 'date'
offset = pd.offsets.MonthEnd()

没有截断,一切都很好:

>>> df = get_example_df()
>>> df
                 x
date       name   
2000-01-01 a     5
           b     0
2000-02-01 a     3
           b     3
2000-03-01 a     7
           b     9

# note:
>>> df.index.codes[0]
array([0, 0, 1, 1, 2, 2], dtype=int8)

>>> mi = df.index
>>> df.set_index(mi.set_levels(mi.unique(level) + offset, level=level))
                 x
date       name   
2000-01-31 a     5
           b     0
2000-02-29 a     3
           b     3
2000-03-31 a     7
           b     9

但是,当MultiIndex是一个视图(因为notbefore不是None)时,它会变得相当糟糕:

>>> df = get_example_df(notbefore='2000-02-15')
>>> df
                 x
date       name   
2000-03-01 a     7
           b     9

>>> mi = df.index
>>> df.set_index(mi.set_levels(mi.unique(level) + offset, level=level))
...
ValueError: On level 0, code max (2) >= length of level (1). NOTE: this index is in an inconsistent state

事实证明,问题在于当被截断mi.codes[0]时,它不会从 0 开始:df

>>> df.index.codes[0]
array([2, 2], dtype=int8)

所以我们遇到了不幸的情况:

>>> len(df.index.levels[0])
3

>>> len(df.index.get_level_values(level))
2

>>> len(df.index.unique(level))
1

并且唯一可以分配(在添加 后offset)回到级别的是df.index.levels[0]

对于我的新代码,我唯一能想到的似乎可靠工作的是:

level_idx = df.index.names.index('date')
# level_idx is now 0
mi = df.index
mi = mi.set_levels(mi.levels[level_idx] + offset, level=level_idx)

现在:

>>> mi
MultiIndex([('2000-03-31', 'a'),
            ('2000-03-31', 'b')],
           names=['date', 'name'])

>>> mi.codes[0]
array([2, 2], dtype=int8)  # as before

那感觉不对。即使不是从 0 开始,最好有.set_unique()一个与..unique().codes.unique()MultiIndex

我错过了什么吗?

标签: pythonpandasmulti-index

解决方案


根据我们的讨论,我认为您可能想要删除unused levels

这是熊猫版本中的新功能:New in version 0.20.0.

mi = df1.index.remove_unused_levels()
df1.set_index(mi.set_levels(mi.unique(level) + offset, level=level))

推荐阅读