Saya memiliki DataFrame dengan MultiIndex yang dibuat setelah beberapa pengelompokan:
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
Bagaimana cara menambahkan level ke MultiIndex sehingga saya mengubahnya menjadi seperti:
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
, karenadf.columns
tidak memiliki metode "set_index" seperti indeks, yang selalu mengganggu saya.pd.Series
objek, sedangkan jawaban yang diterima saat ini (dari 2013) tidak.FirstLevel
seperti pada['Foo', 'Bar']
argumen pertama juga harus memiliki panjang yang sesuai, yaitu[df] * len(['Foo', 'Bar'])
!pd.concat({'Foo': df}, names=['Firstlevel'])
Anda dapat menambahkannya terlebih dahulu sebagai kolom normal dan kemudian menambahkannya ke indeks saat ini, jadi:
df['Firstlevel'] = 'Foo' df.set_index('Firstlevel', append=True, inplace=True)
Dan ubah urutannya jika diperlukan dengan:
df.reorder_levels(['Firstlevel', 'A', 'B'])
Yang mengakibatkan:
Vals Firstlevel A B Foo a1 b1 0.871563 b2 0.494001 a2 b3 -0.167811 a3 b4 -1.353409
sumber
Saya pikir ini adalah solusi yang lebih umum:
# 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)
Beberapa keunggulan dibandingkan jawaban lainnya:
sumber
Saya membuat sedikit fungsi dari jawaban cxrodgers , yang IMHO adalah solusi terbaik karena bekerja murni pada indeks, terlepas dari bingkai atau seri data apa pun.
Ada satu perbaikan yang saya tambahkan:
to_frame()
metode ini akan menemukan nama baru untuk tingkat indeks yang tidak memilikinya. Dengan demikian indeks baru akan memiliki nama yang tidak ada di indeks lama. Saya menambahkan beberapa kode untuk mengembalikan perubahan nama ini.Di bawah ini adalah kodenya, saya telah menggunakannya sendiri untuk sementara waktu dan tampaknya berfungsi dengan baik. Jika Anda menemukan masalah atau kasus tepi, saya akan sangat berkewajiban untuk menyesuaikan jawaban saya.
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
Itu melewati kode unittest berikut:
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)
sumber
Bagaimana jika membangunnya dari awal dengan 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'])
Mirip dengan solusi cxrodger , ini adalah metode yang fleksibel dan menghindari modifikasi larik yang mendasari untuk dataframe.
sumber