developer tip

Pandas MultiIndex DataFrame에서 행 선택

optionbox 2020. 11. 12. 08:09
반응형

Pandas MultiIndex DataFrame에서 행 선택


목표와 동기

MultiIndexAPI는하지만, 그것에 대해 모든 것이 완전히 구조의 측면, 작업 및 관련 작업에서 이해된다, 지난 몇 년 동안 인기를 얻고있다.

한 가지 중요한 작업은 필터링 입니다. 필터링은 일반적인 요구 사항이지만 사용 사례는 다양합니다. 따라서 특정 방법과 기능은 일부 사용 사례에 다른 것보다 더 적용 가능합니다.

요약하면이 게시물의 목적은 몇 가지 일반적인 필터링 문제와 사용 사례를 다루고 이러한 문제를 해결하기위한 다양한 방법을 시연하고 적용 가능성을 논의하는 것입니다. 이 게시물에서 다루고 자하는 몇 가지 높은 수준의 질문은 다음과 같습니다.

  • 단일 값 / 라벨을 기준으로 슬라이스
  • 하나 이상의 수준에서 여러 레이블을 기반으로 슬라이스
  • 부울 조건 및 표현식 필터링
  • 어떤 상황에서 적용 가능한 방법

이러한 문제는 아래에 열거 된 6 개의 구체적인 질문으로 분류되었습니다. 간단하게하기 위해 아래 설정의 예제 DataFrames에는 두 수준 만 있으며 중복 인덱스 키가 없습니다. 문제에 제시된 대부분의 솔루션은 N 레벨로 일반화 할 수 있습니다.

이 게시물은 MultiIndex를 만드는 방법, 할당 작업을 수행하는 방법 또는 성능 관련 토론을 다루지 않을 것입니다 (이들은 나중에 별도의 주제 임).


질문

질문 1-6은 아래 설정과 관련하여 질문됩니다.

mux = pd.MultiIndex.from_arrays([
    list('aaaabbbbbccddddd'),
    list('tuvwtuvwtuvwtuvw')
], names=['one', 'two'])

df = pd.DataFrame({'col': np.arange(len(mux))}, mux)

         col
one two     
a   t      0
    u      1
    v      2
    w      3
b   t      4
    u      5
    v      6
    w      7
    t      8
c   u      9
    v     10
d   w     11
    t     12
    u     13
    v     14
    w     15

질문 1 : 단일 항목
선택 "1"수준에서 "a"가있는 행을 어떻게 선택합니까?

         col
one two     
a   t      0
    u      1
    v      2
    w      3

또한 출력에서 ​​레벨 "1"을 어떻게 드롭 할 수 있습니까?

     col
two     
t      0
u      1
v      2
w      3

질문 1b
"2"수준에서 "t"값을 가진 모든 행을 어떻게 슬라이스합니까?

         col
one two     
a   t      0
b   t      4
    t      8
d   t     12

질문 2 : 레벨에서 여러 값
선택 레벨 "1"에서 항목 "b"및 "d"에 해당하는 행을 어떻게 선택할 수 있습니까?

         col
one two     
b   t      4
    u      5
    v      6
    w      7
    t      8
d   w     11
    t     12
    u     13
    v     14
    w     15

질문 2b
"2"수준에서 "t"및 "w"에 해당하는 모든 값을 얻으려면 어떻게해야합니까?

         col
one two     
a   t      0
    w      3
b   t      4
    w      7
    t      8
d   w     11
    t     12
    w     15

질문 3 : 단일 횡단면 분할 (x, y)
횡단면, 즉 인덱스에 대한 특정 값이있는 단일 행을 검색하려면 어떻게해야 df합니까? 특히, 어떻게의 단면을 검색 할 ('c', 'u')의해 주어진,

         col
one two     
c   u      9

질문 4 : 여러 횡단면 분할 , 및에 [(a, b), (c, d), ...]
해당하는 두 행을 어떻게 선택 합니까?('c', 'u')('a', 'w')

         col
one two     
c   u      9
a   w      3

질문 5 : 레벨 당 슬라이스 된 하나의 항목
레벨 "1"의 "a"또는 레벨 "2"의 "t"에 해당하는 모든 행을 검색하려면 어떻게해야합니까?

         col
one two     
a   t      0
    u      1
    v      2
    w      3
b   t      4
    t      8
d   t     12

질문 6 : 임의
슬라이스 특정 단면을 슬라이스하려면 어떻게해야합니까? "a"와 "b"의 경우 하위 수준이 "u"및 "v"인 모든 행을 선택하고 "d"의 경우 하위 수준이 "w"인 행을 선택하고 싶습니다.

         col
one two     
a   u      1
    v      2
b   u      5
    v      6
d   w     11
    w     15

질문 7은 숫자 수준으로 구성된 고유 한 설정을 사용합니다.

np.random.seed(0)
mux2 = pd.MultiIndex.from_arrays([
    list('aaaabbbbbccddddd'),
    np.random.choice(10, size=16)
], names=['one', 'two'])

df2 = pd.DataFrame({'col': np.arange(len(mux2))}, mux2)

         col
one two     
a   5      0
    0      1
    3      2
    3      3
b   7      4
    9      5
    3      6
    5      7
    2      8
c   4      9
    7     10
d   6     11
    8     12
    8     13
    1     14
    6     15

질문 7 : 숫자 수준의 부등식 기반 필터링
수준 "2"의 값이 5보다 큰 모든 행을 가져 오려면 어떻게합니까?

         col
one two     
b   7      4
    9      5
c   7     10
d   6     11
    8     12
    8     13
    6     15

멀티 인덱싱 / 고급 인덱싱

참고이
게시물은 다음과 같은 방식으로 구성됩니다.

  1. OP에 제시된 질문은 하나씩 처리됩니다.
  2. 각 질문에 대해이 문제를 해결하고 예상 된 결과를 얻는 데 적용 할 수있는 하나 이상의 방법이 시연됩니다.

참고 (이것과 매우 유사 함)는 추가 기능, 구현 세부 사항 및 당면한 주제에 대한 간단한 정보에 관심이있는 독자를 위해 포함됩니다. 이 노트는 문서를 샅샅이 뒤지고 다양한 모호한 기능을 발견하고 내 자신의 (제한된) 경험을 통해 수집되었습니다.

모든 코드 샘플은 pandas v0.23.4, python3.7 에서 생성 및 테스트되었습니다 . 명확하지 않거나 사실적으로 잘못된 것이 있거나 사용 사례에 적용 할 수있는 해결책을 찾지 못한 경우 자유롭게 편집을 제안하거나 의견에 대한 설명을 요청하거나 새 질문을여십시오. 해당되는 경우 .... .

다음은 우리가 자주 다시 살펴볼 몇 가지 일반적인 관용구 (이후에는 Four Idioms라고 함)에 대한 소개입니다.

  1. DataFrame.loc-라벨 별 선택을위한 일반적인 솔루션 ( pd.IndexSlice슬라이스와 관련된 더 복잡한 애플리케이션의 경우 + )

  2. DataFrame.xs -Series / DataFrame에서 특정 단면을 추출합니다.

  3. DataFrame.query-슬라이싱 및 / 또는 필터링 작업을 동적으로 지정합니다 (즉, 동적으로 평가되는 표현식으로. 다른 시나리오보다 일부 시나리오에 더 적용 가능합니다. 또한 다중 인덱스에 대한 쿼리에 대한 문서의이 섹션을 참조하십시오 .

  4. 를 사용하여 생성 된 마스크를 사용한 부울 인덱싱 MultiIndex.get_level_values( Index.isin특히 여러 값으로 필터링 할 때 종종와 함께 사용됨). 이것은 또한 일부 상황에서 매우 유용합니다.

주어진 상황에 적용 할 수있는 내용을 더 잘 이해하기 위해 4 개의 관용구 측면에서 다양한 슬라이싱 및 필터링 문제를 살펴 보는 것이 좋습니다. 모든 관용구가 모든 상황에서 똑같이 잘 작동하지는 않는다는 것을 이해하는 것이 매우 중요합니다. 관용구가 아래 문제에 대한 잠재적 인 해결책으로 나열되지 않았다면 이는 관용구를 해당 문제에 효과적으로 적용 할 수 없음을 의미합니다.


질문 1

"1"수준에서 "a"가있는 행을 어떻게 선택합니까?

         col
one two     
a   t      0
    u      1
    v      2
    w      3

loc대부분의 상황에 적용 할 수있는 범용 솔루션으로을 사용할 수 있습니다 .

df.loc[['a']]

이 시점에서

TypeError: Expected tuple, got str

이는 이전 버전의 pandas를 사용하고 있음을 의미합니다. 업그레이드를 고려하십시오! 그렇지 않으면 df.loc[('a', slice(None)), :].

또는 xs단일 단면을 추출하므로 여기에서 사용할 수 있습니다 . levelsaxis인수에 유의하십시오 (여기에서 합리적인 기본값을 가정 할 수 있음).

df.xs('a', level=0, axis=0, drop_level=False)
# df.xs('a', drop_level=False)

여기서는 결과 (우리가 슬라이스 한 수준)에서 "1"수준이 떨어지는 drop_level=False것을 방지하기 위해 인수가 필요합니다 xs.

여기에 또 다른 옵션은 다음을 사용하는 것입니다 query.

df.query("one == 'a'")

인덱스에 이름이없는 경우 쿼리 문자열을 "ilevel_0 == 'a'".

마지막으로 get_level_values:

df[df.index.get_level_values('one') == 'a']
# If your levels are unnamed, or if you need to select by position (not label),
# df[df.index.get_level_values(0) == 'a']

또한 출력에서 ​​레벨 "1"을 어떻게 드롭 할 수 있습니까?

     col
two     
t      0
u      1
v      2
w      3

이것은 다음 중 하나를 사용하여 쉽게 수행 할 수 있습니다.

df.loc['a'] # Notice the single string argument instead the list.

또는,

df.xs('a', level=0, axis=0, drop_level=True)
# df.xs('a')

drop_level인수를 생략 할 수 있습니다 ( True기본적으로 가정 됨 ).

참고
필터링 된 DataFrame은 DataFrame을 인쇄 할 때 표시되지 않더라도 여전히 모든 수준을 가질 수 있습니다. 예를 들면

v = df.loc[['a']]
print(v)
         col
one two     
a   t      0
    u      1
    v      2
    w      3

print(v.index)
MultiIndex(levels=[['a', 'b', 'c', 'd'], ['t', 'u', 'v', 'w']],
           labels=[[0, 0, 0, 0], [0, 1, 2, 3]],
           names=['one', 'two'])

다음을 사용하여 이러한 수준을 제거 할 수 있습니다 MultiIndex.remove_unused_levels.

v.index = v.index.remove_unused_levels()

print(v.index)
MultiIndex(levels=[['a'], ['t', 'u', 'v', 'w']],
           labels=[[0, 0, 0, 0], [0, 1, 2, 3]],
           names=['one', 'two'])

질문 1b

레벨 "2"에서 값 "t"를 가진 모든 행을 어떻게 슬라이스합니까?

         col
one two     
a   t      0
b   t      4
    t      8
d   t     12

직관적으로 다음과 slice()같은 것을 원할 것입니다 .

df.loc[(slice(None), 't'), :]

It Just Works! ™하지만 투박합니다. pd.IndexSlice여기서 API를 사용하여보다 자연스러운 슬라이싱 구문을 촉진 할 수 있습니다.

idx = pd.IndexSlice
df.loc[idx[:, 't'], :]

이것은 훨씬 더 깨끗합니다.

참고 열에
후행 슬라이스가 :필요한 이유는 무엇 입니까? loc두 축 ( axis=0또는 axis=1)을 따라 선택하고 슬라이스하는 데 사용할 수 있기 때문 입니다. 슬라이스가 수행 될 축을 명시 적으로 명시하지 않으면 작업이 모호해집니다. 슬라이싱 에 대한 문서 에서 큰 빨간색 상자를 참조하십시오 .

모호한 음영을 제거 loc하려면 axis매개 변수를 허용합니다 .

df.loc(axis=0)[pd.IndexSlice[:, 't']]

axis매개 변수가 없으면 (즉,을 수행하여 df.loc[pd.IndexSlice[:, 't']]) slicing이 열에있는 것으로 가정 KeyError하고이 상황에서 a 가 발생합니다.

이것은 슬라이서에 설명되어 있습니다. 그러나이 게시물의 목적을 위해 모든 축을 명시 적으로 지정합니다.

으로 xs, 그것은이다

df.xs('t', axis=0, level=1, drop_level=False)

으로 query, 그것은이다

df.query("two == 't'")
# Or, if the first level has no name, 
# df.query("ilevel_1 == 't'") 

마지막으로을 사용 get_level_values하면

df[df.index.get_level_values('two') == 't']
# Or, to perform selection by position/integer,
# df[df.index.get_level_values(1) == 't']

모두 같은 효과입니다.


질문 2

레벨 "1"에서 항목 "b"및 "d"에 해당하는 행을 어떻게 선택할 수 있습니까?

         col
one two     
b   t      4
    u      5
    v      6
    w      7
    t      8
d   w     11
    t     12
    u     13
    v     14
    w     15

loc을 사용하면 목록을 지정하여 유사한 방식으로 수행됩니다.

df.loc[['b', 'd']]

위의 "b"및 "d"선택 문제를 해결하려면 다음을 사용할 수도 있습니다 query.

items = ['b', 'd']
df.query("one in @items")
# df.query("one == @items", parser='pandas')
# df.query("one in ['b', 'd']")
# df.query("one == ['b', 'd']", parser='pandas')

참고
예, 기본 파서는 'pandas'이지만이 구문이 일반적으로 파이썬이 아니라는 점을 강조하는 것이 중요합니다. Pandas 파서는 표현식과 약간 다른 구문 분석 트리를 생성합니다. 이는 일부 작업을보다 직관적으로 지정하기 위해 수행됩니다. 자세한 내용은 pd.eval ()을 사용하여 pandas의 Dynamic Expression Evaluation에 대한 내 게시물을 참조하십시오 .

그리고 get_level_values+ Index.isin:

df[df.index.get_level_values("one").isin(['b', 'd'])]

질문 2b

레벨 "2"에서 "t"및 "w"에 해당하는 모든 값을 어떻게 얻을 수 있습니까?

         col
one two     
a   t      0
    w      3
b   t      4
    w      7
    t      8
d   w     11
    t     12
    w     15

loc이 가능 단지 와 연동 해에 pd.IndexSlice.

df.loc[pd.IndexSlice[:, ['t', 'w']], :] 

제 대장 :pd.IndexSlice[:, ['t', 'w']]수단은, 상기 제 레벨을 가로 질러 슬라이스. 쿼리되는 수준의 깊이가 증가함에 따라 더 많은 슬라이스를 지정해야합니다. 그러나 슬라이스되는 수준 이상으로 더 많은 수준을 지정할 필요는 없습니다 .

으로는 query,이입니다

items = ['t', 'w']
df.query("two in @items")
# df.query("two == @items", parser='pandas') 
# df.query("two in ['t', 'w']")
# df.query("two == ['t', 'w']", parser='pandas')

부착 get_level_values하고 Index.isin(상기와 유사 함) :

df[df.index.get_level_values('two').isin(['t', 'w'])]

질문 3

인덱스에 대한 특정 값이있는 단일 행과 같은 횡단면을 어떻게 검색 df합니까? 특히, 어떻게의 단면을 검색 할 ('c', 'u')의해 주어진,

         col
one two     
c   u      9

loc키 튜플을 지정하여 사용 합니다.

df.loc[('c', 'u'), :]

또는,

df.loc[pd.IndexSlice[('c', 'u')]]

참고이
시점에서 PerformanceWarning다음과 같은 문제가 발생할 수 있습니다 .

PerformanceWarning: indexing past lexsort depth may impact performance.

이는 색인이 정렬되지 않았 음을 의미합니다. pandas는 최적의 검색 및 검색을 위해 정렬되는 인덱스 (이 경우 문자열 값을 다루기 때문에 사전 순)에 의존합니다. 빠른 수정은 DataFrame.sort_index. 이러한 쿼리를 여러 번 동시에 수행하려는 경우 성능 관점에서 특히 바람직합니다.

df_sort = df.sort_index()
df_sort.loc[('c', 'u')]

MultiIndex.is_lexsorted()색인이 정렬되었는지 여부를 확인 하는 사용할 수도 있습니다 . 이 함수는 True또는 False그에 따라 반환합니다 . 이 함수를 호출하여 추가 정렬 단계가 필요한지 여부를 결정할 수 있습니다.

를 사용하면 xs다른 모든 인수를 적절한 기본값으로 설정하여 단일 튜플을 첫 번째 인수로 다시 전달합니다.

df.xs(('c', 'u'))

를 사용 query하면 상황이 약간 어색해집니다.

df.query("one == 'c' and two == 'u'")

이제 일반화하기가 상대적으로 어려울 것임을 알 수 있습니다. 그러나이 특정 문제에 대해서는 여전히 괜찮습니다.

여러 수준에 걸친 액세스로 get_level_values계속 사용할 수 있지만 권장되지는 않습니다.

m1 = (df.index.get_level_values('one') == 'c')
m2 = (df.index.get_level_values('two') == 'u')
df[m1 & m2]

질문 4

('c', 'u')및에 해당하는 두 행을 어떻게 선택 ('a', 'w')합니까?

         col
one two     
c   u      9
a   w      3

를 사용하면 다음과 loc같이 간단합니다.

df.loc[[('c', 'u'), ('a', 'w')]]
# df.loc[pd.IndexSlice[[('c', 'u'), ('a', 'w')]]]

를 사용하면 query횡단면과 레벨을 반복하여 쿼리 문자열을 동적으로 생성해야합니다.

cses = [('c', 'u'), ('a', 'w')]
levels = ['one', 'two']
# This is a useful check to make in advance.
assert all(len(levels) == len(cs) for cs in cses) 

query = '(' + ') or ('.join([
    ' and '.join([f"({l} == {repr(c)})" for l, c in zip(levels, cs)]) 
    for cs in cses
]) + ')'

print(query)
# ((one == 'c') and (two == 'u')) or ((one == 'a') and (two == 'w'))

df.query(query)

100 % 권장하지 않습니다! 그러나 가능합니다.


질문 5

"1"수준의 "a"또는 "2"수준의 "t"에 해당하는 모든 행을 검색하려면 어떻게해야합니까?

         col
one two     
a   t      0
    u      1
    v      2
    w      3
b   t      4
    t      8
d   t     12

이 사실에 할 매우 어려운 loc정확성을 보장 하고 여전히 코드 선명도를 유지. df.loc[pd.IndexSlice['a', 't']]잘못된 경우 df.loc[pd.IndexSlice[('a', 't')]](예 : 단면 선택) 로 해석됩니다 . pd.concat각 레이블을 개별적으로 처리 하는 솔루션을 생각할 수 있습니다 .

pd.concat([
    df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])

         col
one two     
a   t      0
    u      1
    v      2
    w      3
    t      0   # Does this look right to you? No, it isn't!
b   t      4
    t      8
d   t     12

그러나 행 중 하나가 중복되었음을 알 수 있습니다. 이는 해당 행이 두 슬라이싱 조건을 모두 충족하여 두 번 표시 되었기 때문입니다. 대신해야합니다.

v = pd.concat([
        df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
v[~v.index.duplicated()]

그러나 DataFrame에 기본적으로 원하는 중복 인덱스가 포함되어 있으면 유지되지 않습니다. 극도로주의하여 사용하십시오 .

를 사용 query하면 이것은 어리석게도 간단합니다.

df.query("one == 'a' or two == 't'")

With get_level_values, this is still simple, but not as elegant:

m1 = (df.index.get_level_values('one') == 'c')
m2 = (df.index.get_level_values('two') == 'u')
df[m1 | m2]

Question 6

How can I slice specific cross sections? For "a" and "b", I would like to select all rows with sub-levels "u" and "v", and for "d", I would like to select rows with sub-level "w".

         col
one two     
a   u      1
    v      2
b   u      5
    v      6
d   w     11
    w     15

This is a special case that I've added to help understand the applicability of the Four Idioms—this is one case where none of them will work effectively, since the slicing is very specific, and does not follow any real pattern.

Usually, slicing problems like this will require explicitly passing a list of keys to loc. One way of doing this is with:

keys = [('a', 'u'), ('a', 'v'), ('b', 'u'), ('b', 'v'), ('d', 'w')]
df.loc[keys, :]

If you want to save some typing, you will recognise that there is a pattern to slicing "a", "b" and its sublevels, so we can separate the slicing task into two portions and concat the result:

pd.concat([
     df.loc[(('a', 'b'), ('u', 'v')), :], 
     df.loc[('d', 'w'), :]
   ], axis=0)

Slicing specification for "a" and "b" is slightly cleaner (('a', 'b'), ('u', 'v')) because the same sub-levels being indexed are the same for each level.


Question 7

How do I get all rows where values in level "two" are greater than 5?

         col
one two     
b   7      4
    9      5
c   7     10
d   6     11
    8     12
    8     13
    6     15

This can be done using query,

df2.query("two > 5")

And get_level_values.

df2[df2.index.get_level_values('two') > 5]

Note
Similar to this example, we can filter based on any arbitrary condition using these constructs. In general, it is useful to remember that loc and xs are specifically for label-based indexing, while query and get_level_values are helpful for building general conditional masks for filtering.


Bonus Question

What if I need to slice a MultiIndex column?

Actually, most solutions here are applicable to columns as well, with minor changes. Consider:

np.random.seed(0)
mux3 = pd.MultiIndex.from_product([
        list('ABCD'), list('efgh')
], names=['one','two'])

df3 = pd.DataFrame(np.random.choice(10, (3, len(mux))), columns=mux3)
print(df3)

one  A           B           C           D         
two  e  f  g  h  e  f  g  h  e  f  g  h  e  f  g  h
0    5  0  3  3  7  9  3  5  2  4  7  6  8  8  1  6
1    7  7  8  1  5  9  8  9  4  3  0  3  5  0  2  3
2    8  1  3  3  3  7  0  1  9  9  0  4  7  3  2  7

These are the following changes you will need to make to the Four Idioms to have them working with columns.

  1. To slice with loc, use

    df3.loc[:, ....] # Notice how we slice across the index with `:`. 
    

    Or,

    df3.loc[:, pd.IndexSlice[...]]
    
  2. To use xs as appropriate, just pass an argument axis=1.

  3. You can access the column level values directly using df.columns.get_level_values. You will then need to do something like

    df.loc[:, {condition}] 
    

    Where {condition} represents some condition built using columns.get_level_values.

  4. To use query, your only option is to transpose, query on the index, and transpose again:

    df3.T.query(...).T
    

    Not recommended, use one of the other 3 options.

참고URL : https://stackoverflow.com/questions/53927460/select-rows-in-pandas-multiindex-dataframe

반응형