Pandas:修改特定级别的 Multiindex

Posted

技术标签:

【中文标题】Pandas:修改特定级别的 Multiindex【英文标题】:Pandas: Modify a particular level of Multiindex 【发布时间】:2015-05-22 21:46:12 【问题描述】:

我有一个带有 Multiindex 的数据框,并且想修改 Multiindex 的一个特定级别。例如,第一级可能是字符串,我可能想从该索引级别删除空格:

df.index.levels[1] = [x.replace(' ', '') for x in df.index.levels[1]]

但是,上面的代码导致错误:

TypeError: 'FrozenList' does not support mutable operations.

我知道我可以 reset_index 并修改列,然后重新创建 Multiindex,但我想知道是否有更优雅的方法可以直接修改 Multiindex 的一个特定级别。

【问题讨论】:

不,索引是不可变的。如果你想改变它,你必须重新制作它。 ***.com/a/26629643/2230844 【参考方案1】:

在cmets中提到,索引是不可变的,修改时必须重新制作,但您不必为此使用reset_index,您可以直接创建一个新的多索引:

df.index = pd.MultiIndex.from_tuples([(x[0], x[1].replace(' ', ''), x[2]) for x in df.index])

此示例适用于 3 级索引,您要在其中修改中间级别。您需要针对不同级别的大小更改元组的大小。

更新

John 的改进在性能方面非常出色,但正如 cmets 中所述,它会导致错误。因此,这是经过小幅改进的更正实现:

df.index.set_levels(
    df.index.levels[0].str.replace(' ',''), 
    level=0,
    inplace=True,  # If False, you will need to use `df.index = ...`
)

如果您想使用 级别名称 而不是数字,则需要另一个小的变体:

df.index.set_levels(
    df.index.levels[df.index.names.index('level_name')].str.replace(' ',''), 
    level='level_name',
    inplace=True,
)

【讨论】:

【参考方案2】:

感谢@cxrodgers 的评论,我认为最快的方法是:

df.index = df.index.set_levels(df.index.levels[0].str.replace(' ', ''), level=0)

旧的、更长的答案:

我发现@Shovalt 建议的列表理解有效,但在我的机器上感觉很慢(使用超过 10,000 行的数据框)。

相反,我可以使用.set_levels 方法,这对我来说要快一些。

%timeit pd.MultiIndex.from_tuples([(x[0].replace(' ',''), x[1]) for x in df.index])
1 loop, best of 3: 394 ms per loop

%timeit df.index.set_levels(df.index.get_level_values(0).str.replace(' ',''), level=0)
10 loops, best of 3: 134 ms per loop

实际上,我只需要添加一些文本。 .set_levels 的速度更快:

%timeit pd.MultiIndex.from_tuples([('00'+x[0], x[1]) for x in df.index])
100 loops, best of 3: 5.18 ms per loop

%timeit df.index.set_levels('00'+df.index.get_level_values(0), level=0)
1000 loops, best of 3: 1.38 ms per loop

%timeit df.index.set_levels('00'+df.index.levels[0], level=0)
1000 loops, best of 3: 331 µs per loop

此解决方案基于@denfromufa 评论链接中的答案...

python - Multiindex and timezone - Frozen list error - Stack Overflow

【讨论】:

这似乎比构建新索引更快更优雅。我还要补充一点,在大多数情况下,您只需执行inplace=True 其实我觉得你的代码有错误,应该是df.index.levels[0]df.index.get_level_values(0)。这也是他们在您链接的答案中的做法 您无法使用.get_level_values 吗?你用的是哪个版本的熊猫?我在 v0.22.0 上,两者似乎都给了我相同的结果,但您的建议仅使用 .levels[0].get_level_values(0) 快得多。我会将此添加到我的答案中。 get_level_valueslevels 做的事情不同...。我不完全理解,但第一个为您提供每一行的该级别的值,而levels只给你不同的级别值,或者类似的东西。 @John +1 但使用df.index.unqiue(level=0) 而不是df.index.levels[0]df.index.get_level_values(0)。它更安全,专为这种情况而设计。尤其是get_level_values,它可能在重复级别条目上发生冲突。【参考方案3】:

其他答案工作正常。根据多索引的结构,直接在关卡上应用地图比构建新的多索引要快得多。

我使用以下函数来修改特定的索引级别。它也适用于单级索引。

def map_index_level(index, mapper, level=0):
    """
    Returns a new Index or MultiIndex, with the level values being mapped.
    """
    assert(isinstance(index, pd.Index))
    if isinstance(index, pd.MultiIndex):
        new_level = index.levels[level].map(mapper)
        new_index = index.set_levels(new_level, level=level)
    else:
        # Single level index.
        assert(level==0)
        new_index = index.map(mapper)
    return new_index

用法:

df = pd.DataFrame([[1,2],[3,4]])
df.index = pd.MultiIndex.from_product([["a"],["i","ii"]])
df.columns = ["x","y"]

df.index = map_index_level(index=df.index, mapper=str.upper, level=1)
df.columns = map_index_level(index=df.columns, mapper="x":"foo", "y":"bar")

# Result:
#       foo  bar
# a I     1    2
#   II    3    4

注意:上述方法仅在mapper 保留级别值的唯一性时有效!在上面的示例中,mapper = "i": "new", "ii": "new" 将在 set_index() 中失败并返回 ValueError: Level values must be unique。可以禁用完整性检查,将上述代码修改为:

new_index = index.set_levels(new_level, level=level,
                             verify_integrity=False)

但最好不要!请参阅set_levels 的文档。

【讨论】:

以上是关于Pandas:修改特定级别的 Multiindex的主要内容,如果未能解决你的问题,请参考以下文章

将 Android Studio 配置为仅显示特定级别的 API

使用分类索引值对特定级别的多索引系列进行排序

在 NXLog 中选择特定级别的 Windows 应用程序日志的问题

php 对于在注册新网站时已经拥有特定级别的专业版网站的用户自动应用折扣。这将打折

填补 MultiIndex Pandas Dataframe 中的日期空白

不同长度的pandas groupby元组-ValueError:在通过级别中找不到值:MultiIndex