Python 更改多索引级别:“;ValueError:在级别0上,代码最大值>;=“标高长度”;

Python 更改多索引级别:“;ValueError:在级别0上,代码最大值>;=“标高长度”;,python,pandas,multi-index,Python,Pandas,Multi Index,我很难更改一些代码,这些代码将多索引的'date'部分调整为MonthEnd,如下所示(取决于'date'部分位于位置0): offset=pd.offset.MonthEnd() df.index.set_levels(df.index.levels[0]+偏移量,level=0,inplace=True) inplace参数在pandas=1.2.1中标记为已弃用(出于充分的理由,我完全赞成) 在重构代码时,我想我也希望使用命名级别('date'),而不是int级别(0),以便于可读性和可

我很难更改一些代码,这些代码将
多索引的
'date'
部分调整为
MonthEnd
,如下所示(取决于
'date'
部分位于位置0):

offset=pd.offset.MonthEnd()
df.index.set_levels(df.index.levels[0]+偏移量,level=0,inplace=True)
inplace
参数在
pandas=1.2.1
中标记为已弃用(出于充分的理由,我完全赞成)

在重构代码时,我想我也希望使用命名级别(
'date'
),而不是
int
级别(
0
),以便于可读性和可维护性。因此,我写道:

level='date'
mi=df.index
df=df.set\u索引(mi.set\u级别(mi.unique(级别)+偏移量,级别=级别)
这很好地工作了,直到它遇到了一个
df
,它是另一个df的副本,带有
多索引的子集

考虑以下设置作为一个简单的示例:

def get_example_df(notbefore=None):
np.random.seed(0)
n=3
日期=pd.日期范围('2000',频率=MS',周期=n)
名称=列表('ab')
df=pd.DataFrame(
np.random.randint(10,size=n*len(名称)),
列=['x'],
index=pd.MultiIndex.from_产品([日期,名称],
名称=('date','name'))
)
如果之前没有:
dates=df.index.get_level_值('date')
df=df.loc[日期>=notbefore]
返回df
级别='日期'
offset=pd.offset.MonthEnd()
没有截断,一切都很好:

df=get\u example\u df() >>>df x 日期名称 2000-01-01 a 5 b 0 2000-02-01甲3 b 3 2000-03-01 a 7 b 9 #注: >>>测向索引代码[0] 数组([0,0,1,1,2,2],dtype=int8) >>>mi=df.index >>>df.set_索引(mi.set_级别(mi.unique(级别)+偏移,级别=级别)) x 日期名称 2000-01-31 a 5 b 0 2000-02-29 a 3 b 3 2000-03-31 a 7 b 9
但是,当
多索引
是一个视图时(因为
notbefore
不是
None
),那么它就相当糟糕了:

df=get_example_df(notbefore='2000-02-15') >>>df x 日期名称 2000-03-01 a 7 b 9 >>>mi=df.index >>>df.set_索引(mi.set_级别(mi.unique(级别)+偏移,级别=级别)) ... ValueError:在级别0上,代码max(2)>=级别(1)的长度。注意:此索引处于不一致状态
问题是当
df
为截断值时,
mi.codes[0]
不是从0开始的:

>>df.index.code[0]
数组([2,2],dtype=int8)
因此,我们面临的不幸情况是:

>len(df.index.levels[0])
3.
>>>len(df.index.get_level_值(level))
2.
>>>len(df.index.unique(级别))
1.
并且唯一可以分配回该级别的(在添加
偏移量之后)是
df.index.levels[0]

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

level_idx=df.index.names.index('date'))
#级别_idx现在为0
mi=df.index
mi=mi.set_levels(mi.levels[level_idx]+偏移量,level=level_idx)
现在:

>>mi
多索引([('2000-03-31','a'),
('2000-03-31','b'),
名称=[“日期”,“名称”])
>>>mi.代码[0]
数组([2,2],dtype=int8)#与前面一样
这感觉是错误的。最好有一个
.set_unique()
,它将是
.unique()
的强大对应物,即使
.code
不是从0开始的。这也是低效的(例如,当
.unique()
值比
多索引的整个长度少得多时)


我遗漏了什么吗?

根据我们的讨论,我认为您可能需要删除:

这在pandas版本中是新的:
在0.20.0版本中是新的。

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

我相信您可能需要删除未使用的级别,但只需确认
df1=get\u example\u df(notbefore='2000-02-15')
mi=df1.index.remove\u unused\u levels()
然后
df1.set\u index(mi.unique(level)+offset,level=level))
?太好了!如果你能把它作为一个答案发布,我就可以接受并升级了。这有多方便,你知道吗?(我的意思是,我们支持pandas早在
pandas>=0.24
)它是从
0.20.0受支持的。
:)
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