นำหน้าระดับไปยัง MultiIndex ของแพนด้า


111

ฉันมี 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

ฉันจะเพิ่มระดับให้กับ 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

คำตอบ:


147

วิธีที่ดีในการทำสิ่งนี้ในบรรทัดเดียวโดยใช้pandas.concat():

import pandas as pd

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

วิธีที่สั้นกว่า:

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

นี้สามารถทั่วไปเฟรมข้อมูลจำนวนมากให้ดูเอกสาร


29
นี่เป็นสิ่งที่ดีอย่างยิ่งสำหรับการเพิ่มระดับให้กับคอลัมน์โดยการเพิ่มaxis=1เนื่องจากdf.columnsไม่มีเมธอด "set_index" เหมือนดัชนีซึ่งมักจะทำให้ฉันสับสน
Rutger Kassies

3
นี่เป็นสิ่งที่ดีเพราะมันใช้ได้กับpd.Seriesวัตถุด้วยในขณะที่คำตอบที่ยอมรับในปัจจุบัน (จากปี 2013) ไม่ได้
John

1
ไม่ทำงานอีกต่อไป TypeError: unhashable type: 'list'
cduguet

5
ฉันใช้เวลาสักพักกว่าจะรู้ว่าถ้าคุณมีมากกว่าหนึ่งคีย์สำหรับFirstLevelใน['Foo', 'Bar']อาร์กิวเมนต์แรกจะต้องมีความยาวที่ตรงกันเช่น[df] * len(['Foo', 'Bar'])!
mrclng

7
และกระชับมากยิ่งขึ้น:pd.concat({'Foo': df}, names=['Firstlevel'])
kadee

132

ก่อนอื่นคุณสามารถเพิ่มเป็นคอลัมน์ปกติแล้วผนวกเข้ากับดัชนีปัจจุบันดังนั้น:

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

และเปลี่ยนลำดับหากจำเป็นด้วย:

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

ซึ่งส่งผลให้:

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

2
หากคุณทำสิ่งนี้กับ dataframe ที่มีดัชนีคอลัมน์ MultiIndex ก็จะเพิ่มระดับซึ่งอาจไม่สำคัญในกรณีส่วนใหญ่ แต่ถ้าคุณใช้ข้อมูลเมตาเพื่อทำอย่างอื่น
naught101

23

ฉันคิดว่านี่เป็นวิธีแก้ปัญหาทั่วไปมากกว่า:

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

ข้อดีบางประการเหนือคำตอบอื่น ๆ :

  • ระดับใหม่สามารถเพิ่มได้ในทุกสถานที่ไม่ใช่แค่ระดับบนสุด
  • มันเป็นเพียงการจัดการกับดัชนีและไม่จำเป็นต้องมีการจัดการข้อมูลเช่นเคล็ดลับการต่อข้อมูล
  • ไม่จำเป็นต้องเพิ่มคอลัมน์เป็นขั้นตอนกลางซึ่งสามารถทำลายดัชนีคอลัมน์หลายระดับได้

2

ฉันสร้างฟังก์ชั่นเล็กน้อยจากคำตอบของcxrodgersซึ่ง IMHO เป็นทางออกที่ดีที่สุดเนื่องจากทำงานบนดัชนีเท่านั้นโดยไม่ขึ้นอยู่กับกรอบข้อมูลหรือชุดข้อมูลใด ๆ

มีการแก้ไขอย่างหนึ่งที่ฉันเพิ่ม: 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

ผ่านรหัสที่ไม่เหมาะสมต่อไปนี้:

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

วิธีสร้างมันตั้งแต่เริ่มต้นด้วยหมีแพนด้า 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'])

เช่นเดียวกับโซลูชันของ cxrodgerนี่เป็นวิธีการที่ยืดหยุ่นและหลีกเลี่ยงการปรับเปลี่ยนอาร์เรย์พื้นฐานสำหรับดาต้าเฟรม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.