Hice una pequeña función de la respuesta de cxrodgers , que en mi humilde opinión es la mejor solución ya que funciona puramente en un índice, independiente de cualquier marco de datos o serie.
Hay una solución que agregué: el to_frame()
método inventará nuevos nombres para los niveles de índice que no tienen uno. Como tal, el nuevo índice tendrá nombres que no existen en el índice anterior. Agregué un código para revertir este cambio de nombre.
A continuación se muestra el código, lo he usado por un tiempo y parece funcionar bien. Si encuentra algún problema o casos extremos, estaría muy obligado a ajustar mi respuesta.
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)
new_index_names.insert(loc, name)
new_index = pd.MultiIndex.from_frame(old_index_df, names=new_index_names)
return new_index
Pasó el siguiente código de prueba unitaria:
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")
self.assertEqual([None, None], i1.names)
self.assertTrue(np.all(i1.get_level_values(0) == "foo"))
self.assertTrue(np.all(i1.get_level_values(1) == df.index))
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)
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))
axis=1
, yadf.columns
que no tiene el método "set_index" como el índice, lo que siempre me molesta.