판다 다중 인덱스에 레벨 추가
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
다음을 사용하여 한 줄로 이 작업을 수행하는 좋은 방법pandas.concat()
:
import pandas as pd
pd.concat([df], keys=['Foo'], names=['Firstlevel'])
더 짧은 방법:
pd.concat({'Foo': df}, names=['Firstlevel'])
이는 많은 데이터 프레임으로 일반화할 수 있습니다. 문서를 참조하십시오.
먼저 일반 열로 추가한 다음 현재 인덱스에 추가할 수 있습니다.
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
저는 이것이 더 일반적인 해결책이라고 생각합니다.
# 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)
다른 답변에 비해 몇 가지 이점:
- 새 레벨은 맨 위뿐만 아니라 모든 위치에서 추가할 수 있습니다.
- 이것은 순전히 인덱스에 대한 조작이며 연결 트릭과 같이 데이터를 조작할 필요가 없습니다.
- 여러 수준의 열 인덱스가 손상될 수 있는 중간 단계로 열을 추가할 필요가 없습니다.
저는 cxrodgers 답변으로 약간의 기능을 만들었습니다. IMHO는 데이터 프레임이나 시리즈에 관계없이 순수하게 인덱스에서 작동하기 때문에 최고의 솔루션입니다.
제가 추가한 한 가지 수정 사항이 있습니다.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)
를 사용한 다른 답변.이것은 이 이전 답변을 일반화합니다.
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
판다와 함께 처음부터 다시 만들어 보는 것은 어떨까요?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의 솔루션과 유사하게 이 방법은 유연한 방법이며 데이터 프레임의 기본 배열을 수정하지 않아도 됩니다.
언급URL : https://stackoverflow.com/questions/14744068/prepend-a-level-to-a-pandas-multiindex
'IT' 카테고리의 다른 글
데이터에 추가한 후 Vue에서 PK를 렌더링하지 않음 (0) | 2023.06.12 |
---|---|
휠 파일 설치 (0) | 2023.06.12 |
ggplot2에서 산점도 행렬(쌍() 등가) 생성 (0) | 2023.06.12 |
데이터 프레임별 판다 그룹에 키별로 액세스하는 방법 (0) | 2023.06.07 |
사용자를 전달하지 않고 ApiController 작업 내에서 현재 사용자를 가져옵니다.매개 변수로서의 ID (0) | 2023.06.07 |