Pandaların MultiIndex'inin başına bir seviye ekleyin


107

Bazı gruplamalardan sonra oluşturulmuş bir MultiIndex içeren bir DataFrame'im var:

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'in başına nasıl bir seviye ekleyebilirim, böylece onu aşağıdaki gibi bir şeye dönüştürürüm:

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

Yanıtlar:


139

Bunu kullanarak tek satırda yapmanın güzel bir yolu pandas.concat():

import pandas as pd

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

Daha da kısa bir yol:

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

Bu, birçok veri çerçevesine genelleştirilebilir, dokümanlara bakın .


28
Bu, özellikle sütunlara ekleyerek bir seviye eklemek için güzel axis=1, çünkü df.columnsindeks gibi "set_index" metoduna sahip değil ve beni her zaman rahatsız ediyor.
Rutger Kassies

2
Bu güzel çünkü pd.Seriesnesneler için de işe yarıyor , oysa şu anda kabul edilen cevap (2013'ten itibaren) geçerli değil.
John

1
Artık çalışmıyor. TypeError: unhashable type: 'list'
cduguet

5
İlk argümandaki FirstLevelgibi birden fazla anahtarınız ['Foo', 'Bar']varsa, buna karşılık gelen uzunluğa sahip olmanız gerektiğini anlamam biraz zaman aldı, yani [df] * len(['Foo', 'Bar'])!
mrclng

7
Ve daha da kısa:pd.concat({'Foo': df}, names=['Firstlevel'])
kadee

128

Önce onu normal bir sütun olarak ekleyebilir ve ardından mevcut dizine ekleyebilirsiniz, böylece:

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

Ve gerekirse sırayı şu şekilde değiştirin:

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

Hangi sonuç:

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

2
Bunu MultiIndex sütun dizini olan bir veri çerçevesiyle yaparsanız, seviyeler ekler, bu muhtemelen çoğu durumda önemli değildir, ancak başka bir şey için meta verilere güveniyorsanız olabilir.
naught101

23

Bunun daha genel bir çözüm olduğunu düşünüyorum:

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

Diğer cevaplara göre bazı avantajlar:

  • Yeni seviye, sadece en üstte değil, herhangi bir konuma eklenebilir.
  • Bu tamamen dizinde bir değişikliktir ve birleştirme hilesi gibi verileri değiştirmeyi gerektirmez.
  • Çok seviyeli sütun dizinlerini bozabilecek bir ara adım olarak bir sütun eklemeyi gerektirmez.

2

Herhangi bir veri çerçevesi veya dizisinden bağımsız olarak yalnızca bir indeks üzerinde çalıştığı için IMHO en iyi çözüm olan cxrodgers cevabından küçük bir işlev yaptım .

Eklediğim bir düzeltme var: to_frame()yöntem, bir tane olmayan dizin seviyeleri için yeni adlar icat edecek. Bu nedenle, yeni dizin, eski dizinde olmayan adlara sahip olacaktır. Bu isim değişikliğini geri almak için bazı kodlar ekledim.

Aşağıda kod var, bir süredir kendim kullandım ve iyi çalışıyor gibi görünüyor. Herhangi bir sorun veya uç durum bulursanız, cevabımı ayarlamak zorunda kalırım.

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

Aşağıdaki birim test kodunu geçti:

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)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.