将级别添加到pandas MultiIndex

Yaw*_*war 76 python pandas

我有一个DataFrame,在一些分组后创建了一个MultiIndex:

import numpy as np
import pandas as p
from numpy.random import randn

df = p.DataFrame({
    'A' : ['a1', 'a1', 'a2', 'a3']
  , 'B' : ['b1', 'b2', 'b3', 'b4']
  , 'Vals' : randn(4)
}).groupby(['A', 'B']).sum()

df

Output>            Vals
Output> A  B           
Output> a1 b1 -1.632460
Output>    b2  0.596027
Output> a2 b3 -0.619130
Output> a3 b4 -0.002009
Run Code Online (Sandbox Code Playgroud)

如何将一个级别添加到MultiIndex,以便将其转换为:

Output>                       Vals
Output> FirstLevel A  B           
Output> Foo        a1 b1 -1.632460
Output>               b2  0.596027
Output>            a2 b3 -0.619130
Output>            a3 b4 -0.002009
Run Code Online (Sandbox Code Playgroud)

Rut*_*ies 106

您可以先将其添加为普通列,然后将其附加到当前索引,这样:

df['Firstlevel'] = 'Foo'
df.set_index('Firstlevel', append=True, inplace=True)
Run Code Online (Sandbox Code Playgroud)

如果需要,可以更改订单:

df.reorder_levels(['Firstlevel', 'A', 'B'])
Run Code Online (Sandbox Code Playgroud)

结果如下:

                      Vals
Firstlevel A  B           
Foo        a1 b1  0.871563
              b2  0.494001
           a2 b3 -0.167811
           a3 b4 -1.353409
Run Code Online (Sandbox Code Playgroud)

  • 如果使用带有MultiIndex列索引的数据框执行此操作,则会添加级别,这在大多数情况下可能无关紧要,但如果您依赖元数据进行其他操作,则可能会增加级别. (2认同)
  • 这只适用于行索引,不适用于列索引。 (2认同)

oka*_*tal 106

使用以下方法在一行中执行此操作的好方法pandas.concat():

import pandas as pd

pd.concat([df], keys=['Foo'], names=['Firstlevel'])
Run Code Online (Sandbox Code Playgroud)

这可以推广到许多数据框,请参阅文档.

  • 这通过添加`axis = 1`为列添加一个级别特别好,因为`df.columns'没有像索引一样的"set_index"方法,这总是让我感到烦恼. (22认同)
  • 甚至更简洁:`pd.concat({'Foo':df},names = ['Firstlevel'])` (4认同)
  • 我花了一段时间才意识到,如果您在`['Foo','Bar']`中有多个用于FirstLevel的键,则第一个参数也需要具有相应的长度,即,[[df] * len(['Foo','Bar'])`! (3认同)
  • 这种方法的缺点是它创建了整个 DataFrame 的副本。 (3认同)
  • 这很好,因为它也适用于`pd.Series`对象,而目前接受的答案(从2013年开始)则不然. (2认同)

cxr*_*ers 9

我认为这是一个更通用的解决方案:

# Convert index to dataframe
old_idx = df.index.to_frame()

# Insert new level at specified location
old_idx.insert(0, 'new_level_name', new_level_values)

# Convert back to MultiIndex
df.index = pandas.MultiIndex.from_frame(old_idx)
Run Code Online (Sandbox Code Playgroud)

与其他答案相比有一些优点:

  • 新级别可以添加到任何位置,而不仅仅是顶部。
  • 它纯粹是对索引的一种操作,不需要像串联技巧那样操作数据。
  • 它不需要添加列作为中间步骤,这可以破坏多级列索引。

  • 我真的很喜欢这个答案。进行任何类型的多索引操作都非常灵活且直接。当然,此方法也可以应用于 MultiIndex 列“df.columns.to_frame()” (2认同)

Sam*_*yer 6

我从cxrodgers 答案中做了一个小功能,恕我直言,这是最好的解决方案,因为它纯粹在索引上工作,独立于任何数据帧或系列。

我添加了一个修复:该to_frame()方法将为没有索引级别的索引级别发明新名称。因此,新索引将具有旧索引中不存在的名称。我添加了一些代码来恢复此名称更改。

下面是代码,我自己使用了一段时间,看起来效果很好。如果您发现任何问题或边缘情况,我将非常有必要调整我的答案。

import pandas as pd

def _handle_insert_loc(loc: int, n: int) -> int:
    """
    Computes the insert index from the right if loc is negative for a given size of n.
    """
    return n + loc + 1 if loc < 0 else loc


def add_index_level(old_index: pd.Index, value: Any, name: str = None, loc: int = 0) -> pd.MultiIndex:
    """
    Expand a (multi)index by adding a level to it.

    :param old_index: The index to expand
    :param name: The name of the new index level
    :param value: Scalar or list-like, the values of the new index level
    :param loc: Where to insert the level in the index, 0 is at the front, negative values count back from the rear end
    :return: A new multi-index with the new level added
    """
    loc = _handle_insert_loc(loc, len(old_index.names))
    old_index_df = old_index.to_frame()
    old_index_df.insert(loc, name, value)
    new_index_names = list(old_index.names)  # sometimes new index level names are invented when converting to a df,
    new_index_names.insert(loc, name)        # here the original names are reconstructed
    new_index = pd.MultiIndex.from_frame(old_index_df, names=new_index_names)
    return new_index
Run Code Online (Sandbox Code Playgroud)

它通过了以下单元测试代码:

import unittest

import numpy as np
import pandas as pd

class TestPandaStuff(unittest.TestCase):

    def test_add_index_level(self):
        df = pd.DataFrame(data=np.random.normal(size=(6, 3)))
        i1 = add_index_level(df.index, "foo")

        # it does not invent new index names where there are missing
        self.assertEqual([None, None], i1.names)

        # the new level values are added
        self.assertTrue(np.all(i1.get_level_values(0) == "foo"))
        self.assertTrue(np.all(i1.get_level_values(1) == df.index))

        # it does not invent new index names where there are missing
        i2 = add_index_level(i1, ["x", "y"]*3, name="xy", loc=2)
        i3 = add_index_level(i2, ["a", "b", "c"]*2, name="abc", loc=-1)
        self.assertEqual([None, None, "xy", "abc"], i3.names)

        # the new level values are added
        self.assertTrue(np.all(i3.get_level_values(0) == "foo"))
        self.assertTrue(np.all(i3.get_level_values(1) == df.index))
        self.assertTrue(np.all(i3.get_level_values(2) == ["x", "y"]*3))
        self.assertTrue(np.all(i3.get_level_values(3) == ["a", "b", "c"]*2))

        # df.index = i3
        # print()
        # print(df)
Run Code Online (Sandbox Code Playgroud)


nor*_*ius 5

另一个答案使用from_tuples(). 这概括了之前的答案

key = "Foo"
name = "First"
# If df.index.nlevels > 1:
df.index = pd.MultiIndex.from_tuples(((key, *item) for item in df.index),
                                     names=[name]+df.index.names)
# If df.index.nlevels == 1:
# df.index = pd.MultiIndex.from_tuples(((key, item) for item in df.index),
#                                      names=[name]+df.index.names)
Run Code Online (Sandbox Code Playgroud)

我喜欢这种方法,因为

  • 它只修改索引(没有不必要的正文复制操作)
  • 它适用于两个轴(行索引和列索引)
  • 它仍然可以写成一行行

将以上内容包装在函数中可以更轻松地在行索引和列索引之间以及单级索引和多级索引之间切换:

def prepend_index_level(index, key, name=None):
    names = index.names
    if index.nlevels==1:
        # Sequence of tuples
        index = ((item,) for item in index)

    tuples_gen = ((key,)+item for item in index)
    return pd.MultiIndex.from_tuples(tuples_gen, names=[name]+names)

df.index = prepend_index_level(df.index, key="Foo", name="First")
df.columns = prepend_index_level(df.columns, key="Bar", name="Top")

# Top               Bar
#                  Vals
# First A  B
# Foo   a1 b1 -0.446066
#          b2 -0.248027
#       a2 b3  0.522357
#       a3 b4  0.404048
Run Code Online (Sandbox Code Playgroud)

最后,可以通过在任何索引级别插入键来进一步概括上述内容:

def insert_index_level(index, key, name=None, level=0):
    def insert_(pos, seq, value):
        seq = list(seq)
        seq.insert(pos, value)
        return tuple(seq)

    names = insert_(level, index.names, name)
    if index.nlevels==1:
        # Sequence of tuples.
        index = ((item,) for item in index)
    
    tuples_gen = (insert_(level, item, key) for item in index)
    return pd.MultiIndex.from_tuples(tuples_gen, names=names)

df.index = insert_index_level(df.index, key="Foo", name="Last", level=2)
df.columns = insert_index_level(df.columns, key="Bar", name="Top", level=0)

# Top              Bar
#                 Vals
# A  B  Last
# a1 b1 Foo  -0.595949
#    b2 Foo  -1.621233
# a2 b3 Foo  -0.748917
# a3 b4 Foo   2.147814
Run Code Online (Sandbox Code Playgroud)