【问题标题】:change multiindex level: "ValueError: On level 0, code max >= length of level"更改多索引级别:“ValueError:在级别 0,代码最大值 >= 级别长度”
【发布时间】:2021-05-17 04:34:54
【问题描述】:

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

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

事实证明,当df 被截断时,mi.codes[0] 不是从 0 开始的:

>>> 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

感觉不对。即使.codes 不是从 0 开始,拥有一个与 .unique() 相对应的 .set_unique() 也会很好。它也是低效的(例如,当 .unique() 值比MultiIndex 的全长。

我错过了什么吗?

【问题讨论】:

  • 我相信您可能需要删除未使用的级别remove_unused_levels,但只需确认df1 =get_example_df(notbefore='2000-02-15')mi = df1.index.remove_unused_levels() 然后df1.set_index(mi.set_levels(mi.unique(level) + offset, level=level))
  • 太棒了!如果您可以将其发布为答案,那么我可以接受并支持。
  • 便携性如何,你知道吗? (我的意思是,我们早在 pandas>=0.24 就支持 pandas)
  • 0.20.0.得到支持:)

标签: python pandas multi-index


【解决方案1】:
from pandas.tseries.offsets import MonthEnd

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

df=get_example_df()
endOfMonth=[pd.to_datetime(x[0].strftime("%Y-%m"))+MonthEnd(1) for x in df.index]
df.reset_index(inplace=True) 
df['date']=endOfMonth
df=df.set_index(['date','name'])
print(df)



 date       name  x 
 2000-01-31 a     5
            b     0
 2000-02-29 a     3
            b     3
 2000-03-31 a     7
            b     9

【讨论】:

    【解决方案2】:

    根据我们的讨论,我认为您可能希望删除 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))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-04-20
      • 2022-01-25
      • 2021-01-28
      • 2019-05-28
      • 1970-01-01
      • 2018-07-08
      • 1970-01-01
      相关资源
      最近更新 更多