【发布时间】: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