Stellen Sie einem Pandas MultiIndex ein Level voran


98

Ich habe einen DataFrame mit einem MultiIndex, der nach einer Gruppierung erstellt wurde:

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

Wie stelle ich dem MultiIndex ein Level voran, damit ich daraus etwas mache wie:

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

Antworten:


134

Eine gute Möglichkeit, dies in einer Zeile zu tun pandas.concat():

import pandas as pd

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

Ein noch kürzerer Weg:

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

Dies kann auf viele Datenrahmen verallgemeinert werden, siehe die Dokumente .


28
Dies ist besonders nützlich, um den Spalten durch Hinzufügen eine Ebene hinzuzufügen axis=1, da die df.columnsMethode "set_index" nicht wie der Index vorhanden ist, was mich immer nervt.
Rutger Kassies

2
Das ist schön, weil es auch für pd.SeriesObjekte funktioniert , während die aktuell akzeptierte Antwort (ab 2013) dies nicht tut.
John

1
Funktioniert nicht mehr. TypeError: nicht zerlegbar Typ: 'list'
cduguet

5
Es hat eine Weile gedauert, bis mir klar wurde, dass, wenn Sie mehr als einen Schlüssel für FirstLevelwie im ['Foo', 'Bar']ersten Argument haben, auch die entsprechende Länge benötigt wird, dh [df] * len(['Foo', 'Bar'])!
Mrclng

7
Und noch prägnanter:pd.concat({'Foo': df}, names=['Firstlevel'])
Kadee

122

Sie können es zuerst als normale Spalte hinzufügen und dann an den aktuellen Index anhängen.

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

Und ändern Sie die Reihenfolge bei Bedarf mit:

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

Was in ... endet:

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

2
Wenn Sie dies mit einem Datenrahmen mit einem MultiIndex-Spaltenindex tun, werden Ebenen hinzugefügt, was in den meisten Fällen wahrscheinlich keine Rolle spielt, aber möglicherweise, wenn Sie sich für etwas anderes auf die Metadaten verlassen.
naught101

16

Ich denke, das ist eine allgemeinere Lösung:

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

Einige Vorteile gegenüber den anderen Antworten:

  • Das neue Level kann an jedem Ort hinzugefügt werden, nicht nur oben.
  • Es handelt sich lediglich um eine Manipulation des Index und erfordert keine Manipulation der Daten wie beim Verkettungstrick.
  • Es ist nicht erforderlich, eine Spalte als Zwischenschritt hinzuzufügen, wodurch mehrstufige Spaltenindizes beschädigt werden können.

2

Ich habe eine kleine Funktion aus der Antwort von cxrodgers gemacht. IMHO ist die beste Lösung, da sie unabhängig von Datenrahmen oder Serien nur mit einem Index funktioniert.

Es gibt einen Fix, den ich hinzugefügt habe: Die to_frame()Methode erfindet neue Namen für Indexstufen, die keinen haben. Daher hat der neue Index Namen, die im alten Index nicht vorhanden sind. Ich habe Code hinzugefügt, um diese Namensänderung rückgängig zu machen.

Unten ist der Code, ich habe ihn selbst für eine Weile benutzt und er scheint gut zu funktionieren. Wenn Sie Probleme oder Randfälle finden, wäre ich sehr verpflichtet, meine Antwort anzupassen.

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

Es wurde der folgende unittest Code übergeben:

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)

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.