Thêm một cấp độ vào MultiIndex của gấu trúc


101

Tôi có DataFrame với MultiIndex được tạo sau một số nhóm:

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

Làm cách nào để thêm một cấp độ vào MultiIndex để biến nó thành một thứ như sau:

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

Câu trả lời:


138

Một cách hay để làm điều này trong một dòng bằng cách sử dụng pandas.concat():

import pandas as pd

pd.concat([df], keys=['Foo'], names=['Firstlevel'])

Một cách thậm chí còn ngắn hơn:

pd.concat({'Foo': df}, names=['Firstlevel'])

Điều này có thể được tổng quát hóa cho nhiều khung dữ liệu, hãy xem tài liệu .


28
Điều này đặc biệt tốt cho việc thêm một cấp độ vào các cột bằng cách thêm axis=1, vì df.columnsnó không có phương thức "set_index" như chỉ mục, điều này luôn khiến tôi khó chịu.
Rutger Kassies

2
Điều này là tốt vì nó cũng hoạt động cho pd.Seriescác đối tượng, trong khi câu trả lời hiện được chấp nhận (từ năm 2013) thì không.
John

1
Không hoạt động nữa. TypeError: unhashable type: 'list'
cduguet 11/1118

5
Tôi đã mất một lúc để nhận ra rằng nếu bạn có nhiều hơn một khóa cho FirstLevelnhư trong ['Foo', 'Bar']đối số đầu tiên thì cũng sẽ cần phải có độ dài tương ứng, tức là [df] * len(['Foo', 'Bar']),!
mrclng 13/1218

7
Và ngắn gọn hơn:pd.concat({'Foo': df}, names=['Firstlevel'])
kadee

123

Trước tiên, bạn có thể thêm nó như một cột bình thường và sau đó thêm nó vào chỉ mục hiện tại, vì vậy:

df['Firstlevel'] = 'Foo'
df.set_index('Firstlevel', append=True, inplace=True)

Và thay đổi thứ tự nếu cần với:

df.reorder_levels(['Firstlevel', 'A', 'B'])

Kết quả là:

                      Vals
Firstlevel A  B           
Foo        a1 b1  0.871563
              b2  0.494001
           a2 b3 -0.167811
           a3 b4 -1.353409

2
Nếu bạn làm điều này với khung dữ liệu có chỉ mục cột MultiIndex, nó sẽ thêm các cấp, điều này có thể không quan trọng trong hầu hết các trường hợp, nhưng có thể xảy ra, nếu bạn đang dựa vào siêu dữ liệu cho việc khác.
naught101

16

Tôi nghĩ đây là một giải pháp chung hơn:

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

Một số ưu điểm so với các câu trả lời khác:

  • Cấp độ mới có thể được thêm ở bất kỳ vị trí nào, không chỉ ở trên cùng.
  • Nó hoàn toàn là một thao tác trên chỉ mục và không yêu cầu thao tác dữ liệu, giống như thủ thuật nối.
  • Nó không yêu cầu thêm một cột làm bước trung gian, điều này có thể phá vỡ các chỉ mục cột nhiều cấp.

2

Tôi đã tạo một hàm nhỏ trong câu trả lời cxrodgers , IMHO là giải pháp tốt nhất vì nó hoạt động hoàn toàn trên một chỉ mục, độc lập với bất kỳ khung hoặc chuỗi dữ liệu nào.

Có một bản sửa lỗi mà tôi đã thêm: to_frame()phương pháp này sẽ phát minh ra các tên mới cho các cấp chỉ mục không có. Như vậy chỉ mục mới sẽ có các tên không tồn tại trong chỉ mục cũ. Tôi đã thêm một số mã để hoàn nguyên sự thay đổi tên này.

Dưới đây là đoạn mã, tôi đã tự mình sử dụng nó trong một thời gian và nó có vẻ hoạt động tốt. Nếu bạn tìm thấy bất kỳ vấn đề hoặc trường hợp khó khăn nào, tôi rất có trách nhiệm phải điều chỉnh câu trả lời của mình.

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

Nó đã vượt qua mã độc nhất sau:

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)

0

Làm thế nào về việc xây dựng nó từ đầu với pandas.MultiIndex.from_tuples ?

df.index = p.MultiIndex.from_tuples(
    [(nl, A, B) for nl, (A, B) in
        zip(['Foo'] * len(df), df.index)],
    names=['FirstLevel', 'A', 'B'])

Tương tự như giải pháp của cxrodger , đây là một phương pháp linh hoạt và tránh sửa đổi mảng cơ bản cho khung dữ liệu.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.