Mam DataFrame z MultiIndex utworzoną po pewnym zgrupowaniu:
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
Jak dodać poziom do MultiIndex, aby zamienić go na coś takiego:
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
axis=1
, ponieważdf.columns
nie ma metody „set_index” takiej jak index, co zawsze mnie niepokoi.pd.Series
obiektów, podczas gdy obecnie akceptowana odpowiedź (z 2013 roku) nie.FirstLevel
tak jak w['Foo', 'Bar']
pierwszym argumencie również trzeba będzie mieć odpowiednią długość, tj.[df] * len(['Foo', 'Bar'])
!pd.concat({'Foo': df}, names=['Firstlevel'])
Możesz najpierw dodać go jako zwykłą kolumnę, a następnie dołączyć do bieżącego indeksu, więc:
df['Firstlevel'] = 'Foo' df.set_index('Firstlevel', append=True, inplace=True)
W razie potrzeby zmień kolejność za pomocą:
df.reorder_levels(['Firstlevel', 'A', 'B'])
Co skutkuje w:
Vals Firstlevel A B Foo a1 b1 0.871563 b2 0.494001 a2 b3 -0.167811 a3 b4 -1.353409
źródło
Myślę, że jest to bardziej ogólne rozwiązanie:
# 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)
Niektóre zalety w stosunku do innych odpowiedzi:
źródło
Zrobiłem małą funkcję z odpowiedzi cxrodgers , która IMHO jest najlepszym rozwiązaniem, ponieważ działa wyłącznie na indeksie, niezależnie od jakiejkolwiek ramki danych lub serii.
Jest jedna poprawka, którą dodałem:
to_frame()
metoda wymyśli nowe nazwy dla poziomów indeksu, które ich nie mają. W związku z tym nowy indeks będzie miał nazwy, których nie ma w starym indeksie. Dodałem kod, aby cofnąć tę zmianę nazwy.Poniżej znajduje się kod, sam go używałem przez jakiś czas i wydaje się, że działa dobrze. Jeśli znajdziesz jakieś problemy lub skrajne przypadki, będę bardzo zobowiązany do dostosowania mojej odpowiedzi.
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
Przeszedł następujący kod nieprzesłany:
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)
źródło
Co powiesz na zbudowanie go od podstaw za pomocą pand.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'])
Podobnie jak w przypadku rozwiązania cxrodger , jest to metoda elastyczna i pozwala na uniknięcie modyfikowania podstawowej tablicy dla ramki danych.
źródło