149

いくつかのグループ化の後に作成されたMultiIndexを持つDataFrameがあります:

import numpy as np
import pandas as pd
from numpy.random import randn

df = pd.DataFrame({'A' : ['a1', 'a1', 'a2', 'a3'], 
                   'B' : ['b1', 'b2', 'b3', 'b4'], 
                   'Vals' : randn(4)}
                 ).groupby(['A', 'B']).sum()

#            Vals
# A  B           
# a1 b1 -1.632460
#    b2  0.596027
# a2 b3 -0.619130
# a3 b4 -0.002009

MultiIndexの前にレベルを追加して、次のようなものに変換するにはどうすればよいですか。

#                       Vals
# FirstLevel A  B           
# Foo        a1 b1 -1.632460
#               b2  0.596027
#            a2 b3 -0.619130
#            a3 b4 -0.002009
4

6 に答える 6

199

を使用して1行でこれを行うための良い方法pandas.concat()

import pandas as pd

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

さらに短い方法:

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

これは多くのデータフレームに一般化できます。ドキュメントを参照してください。

于 2017-02-07T16:11:06.527 に答える
149

最初に通常の列として追加してから、現在のインデックスに追加できます。

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

そして、必要に応じて次のように順序を変更します。

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

その結果:

                      Vals
Firstlevel A  B           
Foo        a1 b1  0.871563
              b2  0.494001
           a2 b3 -0.167811
           a3 b4 -1.353409
于 2013-02-07T08:37:45.803 に答える
38

これはより一般的な解決策だと思います。

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

他の答えに対するいくつかの利点:

  • 新しいレベルは、最上部だけでなく、任意の場所に追加できます。
  • これは純粋にインデックスの操作であり、連結トリックのようにデータを操作する必要はありません。
  • マルチレベルの列インデックスを壊す可能性のある中間ステップとして列を追加する必要はありません。
于 2019-05-23T15:39:45.897 に答える
6

私はcxrodgersの回答から小さな関数を作成しました。これは、データフレームや系列に関係なく、純粋にインデックスで機能するため、IMHOが最適なソリューションです。

私が追加した修正が1つあります。to_frame()メソッドは、名前がないインデックスレベルの新しい名前を発明します。そのため、新しいインデックスには、古いインデックスには存在しない名前が付けられます。この名前の変更を元に戻すためのコードを追加しました。

以下はコードです、私はそれをしばらく自分で使用しました、そしてそれはうまくいくようです。問題やエッジケースを見つけた場合、私は答えを調整する義務があります。

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

次のユニットテストコードに合格しました。

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)
于 2019-09-17T18:09:58.080 に答える
3

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'])

cxrodgerのソリューションと同様に、これは柔軟な方法であり、データフレームの基になる配列を変更する必要がありません。

于 2020-07-11T17:40:54.113 に答える
0

を使用した別の回答from_tuples()これは、この前の答えを一般化します。

key = "Foo"
name = "First"
# If df.index.nlevels > 1:
df.index = pd.MultiIndex.from_tuples(((key, *item) for item in df.index),
                                     names=[name]+df.index.names)
# If df.index.nlevels == 1:
# df.index = pd.MultiIndex.from_tuples(((key, item) for item in df.index),
#                                      names=[name]+df.index.names)

私はこのアプローチが好きです。

  • インデックスを変更するだけです(本文の不要なコピーアクションはありません)
  • 両方の軸(行と列のインデックス)で機能します
  • それでもワンライナーとして書くことができます

上記を関数でラップすると、行インデックスと列インデックスの切り替え、およびシングルレベルインデックスとマルチレベルインデックスの切り替えが簡単になります。

def prepend_index_level(index, key, name=None):
    names = index.names
    if index.nlevels==1:
        # Sequence of tuples
        index = ((item,) for item in index)

    tuples_gen = ((key,)+item for item in index)
    return pd.MultiIndex.from_tuples(tuples_gen, names=[name]+names)

df.index = prepend_index_level(df.index, key="Foo", name="First")
df.columns = prepend_index_level(df.columns, key="Bar", name="Top")

# Top               Bar
#                  Vals
# First A  B
# Foo   a1 b1 -0.446066
#          b2 -0.248027
#       a2 b3  0.522357
#       a3 b4  0.404048

最後に、上記は、任意のインデックスレベルでキーを挿入することにより、さらに一般化できます。

def insert_index_level(index, key, name=None, level=0):
    def insert_(pos, seq, value):
        seq = list(seq)
        seq.insert(pos, value)
        return tuple(seq)

    names = insert_(level, index.names, name)
    if index.nlevels==1:
        # Sequence of tuples.
        index = ((item,) for item in index)
    
    tuples_gen = (insert_(level, item, key) for item in index)
    return pd.MultiIndex.from_tuples(tuples_gen, names=names)

df.index = insert_index_level(df.index, key="Foo", name="Last", level=2)
df.columns = insert_index_level(df.columns, key="Bar", name="Top", level=0)

# Top              Bar
#                 Vals
# A  B  Last
# a1 b1 Foo  -0.595949
#    b2 Foo  -1.621233
# a2 b3 Foo  -0.748917
# a3 b4 Foo   2.147814
于 2021-10-04T19:36:23.990 に答える