계층적 인덱스 - 정렬함수

물류회사에서 고객정보를 관리하는데, 고객의 주소를 index로 하여 데이터를 관리한다고 가정해보자.

가장 계층인 시(군)에서부터 작은 계층인 번지까지 하나씩 내려올 것이다. ex> 서울특별시 강남구 삼성로 212

이렇게 계층적인 정보를 index로 사용하고 싶을 때, 이것을 계층적 인덱싱이라고 한다.


10개 랜덤수를 Series를 만드는데, 인덱스를 2차원 리스트(혹은 2차원 array)로 주면, 층이 2개인 index를 가지게 된다.
s = pd.Series(np.random.randn(10),
               index=[["a", "a", "a", "b", "b", "b", "c", "c", "d", "d"],
                      [1, 2, 3, 1, 2, 3, 1, 2, 2, 3]])
image

첫번 째 층에는 a부터 d까지, 2번째 층에서는 1~3까지의 정수가 나와있다. 실제로 Series.의 index를 확인해보면 MultiIndex라고 표시되어있다.
image

이렇게 계층적 인덱스가 있을 때, 인덱싱을 어떻게 수행하는지 알아보자.
가장 바깥층 인덱스부터 인덱싱하는 것이 원칙이다.

  • s["b"] 를 통해서, 정수로 성분 n-1번째 인덱싱 으로 쓰이는 s[ ], 가장 바깥층을 index명을 적어서, 그 하위계층의 인덱스+성분을 얻게 된다.
    (원래는 정수를 넣어서 s[0]을 하면 1번째 성분, s[1]을 하면 2번째 성분이 나오는 Series의 성분인덱싱으로 쓰이는 곳이었다)
    image
  • 가장 바깥층의 index명으로 범위인덱싱도 할 수 있다.
    s["b":"c"]
    image
  • 인덱스란에 ()소괄호를 넣어, (상위계층,하위계층) 형식으로 넣으면 하위 계층까지 지정해서 인덱싱할 수 있다.
    s[ ("b", 3)]
    image
    확인 결과, 소괄호 없어도 된다. [  ,  ]인덱싱처럼 콤마를 주어 하위계층을 입력하면 [첫번째 층, 두번째 층]을 지정해서 인덱싱할 수 있다.
    s[ "b", 3]
    (원래, []안에 인자가 여러개 들어갔을 때, 같은 위상에 있는 연속적인 값을 입력할 때는 소괄호를 통해 입력한다)
    image
    아! 그래서 행 인덱싱은 따로 .loc[]를 이용했구나, 2차원의 경우 열인덱싱에서 계층적인덱싱하라고??
    열인덱싱란에 [행,렬]을 적어봤자 행렬이 아니라, 계층적인덱싱이다.
    .loc[]의 인덱싱란에 적어야 [행,렬]이 된다.
  • 마찬가지로, s[첫번째 층 인덱스, 두번째층 인덱스]로 인덱싱하는데,  콜론을 이용하여, 2번째 층의 인덱스가 2인 것만 뽑아낼 수 있다.
    s[ : , 2 ]
    image


DataFrame에서도 계층적 인덱싱을 해보자.
Series에서는, index만 2차원 리스트형식으로 줬지만, DataFrame에서는 columns까지 2차원으로 주자.
df = pd.DataFrame(np.arange(12).reshape((4, 3)),
                                index=[["a", "a", "b", "b"],
                                       [1, 2, 1, 2]],
                                columns=[["Seoul", "Seoul", "Busan"],
                                         ["Green", "Red", "Green"]])

image

  • 복수 계층의 인덱스 와 칼럼에 이름을 붙힐 때는, 파이썬의 리스트 형식으로 주면 된다.
    df.index.names = [ "key1", "key2"]
    df.columns.names = ["city","color"]
    image


DataFrame의 열에 대한 계층적 인덱싱을 해보자.

  • DataFrame의 열도 가장 상위계층을 먼저 인덱싱해야한다.
    df["Seoul"]
    image
  • 이제 상위계층의 하위계층까지 지정해서 인덱싱해보자. 열의 최하위계층까지 간다면 하나의 열만 추출하므로 Series형태로 나올 것이다.
    인덱스는 그대로 계층적 인덱싱을 하고 있다.
    df["Seoul", "Green"]
    image

행의 인덱싱도 기존 인덱싱과 유사하게 최상위계층부터 인덱싱해야한다.

  • df.loc["a"] 를 통해 상위계층 a를 인덱스로 가지는 dataFrame과
    df.loc["a", 1]를 통해 상위a , 하위 1이라는 인덱스를 가지는 하나의 -> Series형태로 얻을 수 있다.
    이때, Series의 인덱스는 기존 DataFrame의 계층적인 컬럼들이 인덱스로 들어가는 것을 확인할 수 있다.
    image


.loc를 이용해서, 행과 열을 동시에 계층적 인덱싱을 할 수 있다( 다만 각 행, 렬에서 순차적으로)

  • 행과 열을 동시에 계층적 인덱싱할 때, 소괄호()를 통해서 상위->하위계층으로 들어갈 수 있다.
    df.loc["b", ("Seoul","Red")]
    상위 b행 ,  상위 Seoul열-> 하위 Red열 인덱싱
    image
    df.loc[("b", 2), "Busan"] 를 통해서,   index( b->2)  / columns Busan만 인덱싱 할 수 있다.
    image
  • 행과 열을 동시에 인덱싱할 때는,  모든 계층을 다 타고 내려가면, 특정 성분이 나올 것이다.
    (행 or 열 하나만 인덱싱하여 최하위까지 가면, Series가 나온다)
    df.loc[("b", 1), ("Seoul", "Green")]
    image


계층적 인덱스에도 인덱스를 기준으로 정렬을 할 수 있다.

이전시간에는 df.sort_index() 만 호출하면 인덱스가 자동으로 오름차순으로 정렬이 됬었다.
여기에 level이라는 인자를 주어, 해당하는 인덱스의 층수(정수 인덱스 : 0,1,2,…)를 주면, 그 층의 인덱스를 오름차순으로 정렬할 수 있다.
(sort_index()에 axis인자를 주지 않으면, 열에 따른 행 방향(axis=0) 기준으로 정렬한다. )

  • df.sort_index(axis=0, level=1) 를 통해,
    행 방향(↓)으로 행index가 + 2번째 층의 인덱스(key2)가 오름차순 되도록 정렬시킨다.
    image
  • level인자에 정수 인덱스가 번거롭다면, 층의 이름을 명시해서 사용할 수 있다.
    image
  • 행에 따른 열 방향(→)으로 columns을 오름차순 정렬하면서, 계층을 명시할 수 있다.
    df.sort_index(axis=1, level=0) 를 통해 1번째 계층인 city를 알파벳 순서로 busan->seoul 순으로 오름차순 정렬 된다.
    df.sort_index(axis=1, level=1) 를 통해 2번째 계층인 color 오름차순으로 green->red 순으로 정렬된다.
    image


계층적 인덱싱에서, 성분(값)기준 정렬을 할 수 있다.
이전 13강에서 sort_values( by ="칼럼명")를 통해  특정 칼럼의 성분을 기준으로 오름차순으로 정렬했었다.
(sort_values by = 칼럼명 은 하나로 묶여서 생각하자)
여기에 ()소괄호를 이용해서 계층적 인덱싱을 하면 된다.

  • df.sort_values(by=("Busan","Green")) 를 통하여,  Busan계층의 Green열의 성분들을 오름차순 정렬이 된다.
    image



계층적 인덱스 - 통계함수

계층적 인덱스에 통계함수를 적용해보자.
이전에는 df.sum(axis=0) 을 통해서 각 열들에 대해 행 방향(↓)의 합을 구했다.
여기에 level인자를 추가해서 <어디서 계산을 끊어서 갈지>기준이 되는 index계층을 지정하면 된다.
만약 axis=0이라면, 열에 따라 행방향으로 합을 구하므로, 끊어주는 기준은 행(index)이 된다.
만약 axis=1이라면, 행에 따라 열방향으로 합을 구하므로, 끊어주는 기준은 열(columns)이 된다.
즉, 방향에 따라 <끊어서 계산하는 기준>이 level이다.
axis=0으로서, 각 열들(행 방향)으로 합은 구하기 때문에 ---> level은 열이 아닌 index가 기준이 된다.(어디까지의 합인지 끊어주는 기준)

  • df.sum(axis=0, level=0) 을 통해,  각 열들에 대해 행방향으로 합을 각각 구하는데,
    어디까지의 합인지 끊어주는 기준은 level=0인 1번째 index 층이 기준이 된다. 즉, a와 b를 구분해서 각 열의 성분들의 합을 구한다.
    image
  • df.sum(axis=0, level=1) 를 통해, key2라는 열에는 a-1,2/ b-1,2의 계층이 있으나, 그것을 무시하고
    key2 index의 1과 2가 끊어주는 기준이 된다( 1번째 계층 a,b는 무시되어 서로 섞인다)
    image


level에 원하는 인덱스 또는 컬럼의 특정계층 이름(name)을 명시해서 통계함수를 적용할 수 있다.(컬럼명 아님!)
만약 axis=0이라면, 열에 따라 행방향으로 합을 구하므로, 끊어주는 기준은 행(index)이 된다.
만약 axis=1이라면, 행에 따라 열방향으로 합을 구하므로, 끊어주는 기준은 열(columns)이 된다.
즉, 방향에 따라 끊어서 계산하는 기준이 level이다.
image

  • df.mean(axis=1, level="color") 를 통해서, 행에 따른 열방향의 평균인데,  2번째 계층인 color계층을 기준으로 끊어서 평균을 구한다.
    image



DataFrame 특정열의 성분을 계층적 인덱스로 변환

기존의 DataFrame의 특정열에 포함된 값을 계층적 인덱스로 변환하거나 혹은 그 반대로 할 수가 있다.

df2 = pd.DataFrame({'a': range(7), 'b': range(7, 0, -1),
                     'c': ['one', 'one', 'one', 'two', 'two', 'two', 'two'],
                     'd': [0, 1, 2, 0, 1, 2, 3]})

*** range(0부터 총 갯수) / range( 시작수, 포함되지않는 끝수, ) : 내림차순으로 하기 위해서는 폭을 -1로 주어야한다!

image

c와 d열의 성분을 계층적 인덱스로 바꿔보자.

  • set_index()함수에 리스트[]형식으로, 계층순서대로 컬럼명을 주면된다.
    (my : 인덱싱할 때는 [ ] 안에 소괄호로 계층을 주고 / 각종 함수에서는 ( )안에  리스트로 계층을 주네?!)
    df3 = df2.set_index( ["c", "d"] )
    그럼 c의 성분들에 대해 d의 성분들이 알아서 박힌다. 그리고 c, d열은 사라진다
    image
  • c열과 d열을 계층적 인덱스로 주면서, 기존의 c열과 d열을 유지하면서 c,d열을 인덱스로 내려주고 싶다면
    set_index()함수의 인자에,  열->index 으로 내려가는 drop을 False로 주면 된다.
    df2.set_index(["c","d"], drop=False)
    image

현재 존재하는 계층적 인덱스를-> 열로 올리면서 동시에 기본 정수를 index로 주는 함수는 reset_index()함수다.

  • df3.reset_index()
    image


DataFrame 인덱스와 칼럼 전환

DataFrame 모양을 변형하기 위해 아래 예시 데이터를 보자.

df4 = pd.DataFrame(np.arange(6).reshape((2, 3)),
                    index=['Seoul', 'Busan'],
                    columns=['one', 'two', 'three'])
df4.index.name = "city"
df4.columns.name = "number"

image

계층적 인덱스는 아니다. 이 때,  stack()함수를 사용하면, DataFrame의 최하층 컬럼자체가 –> 인덱스의 최하위 index 층으로 붙게 되면서, Series가 된다. 여기서는 컬럼이 단일층이므로, 해당 칼럼이 넘어가버려 Series가 되는 것이다.

  • df4.stack() 를 통해 단일컬럼(number)가  단일 인덱스(city)의 하위계층으로 붙게 되는 것을 확인할 수 있다.
    기존의 칼럼이 사라지면서, Series의 형태가 되어버린다.
    image
    *** 주의할 점은, stack()함수와 set_index()의 작동방식이 다른 것이다.
    (1) set_index( [ “컬럼1”, “컬럼2” ])는  컬럼이 내려와서 기존의 인덱스를 <대체> 해버린다.
         drop=False로, 칼럼1,2를 살릴 수 도 있다.
    (2) stack()함수는 컬럼의 최하위계층이 기존 인덱스의 <최하위계층으로 내러와서 붙는다>

  • df5.unstack()을 통해서, 인덱스의 최하위 계층(number)를 –> 칼럼의 최하위계층으로 올린다
    image

만일 계층적 인덱스 중 특정계층의 index를  columns으로 올리고 싶다면 level인자로 지정해준다.

  • df5.unstack(level=0)를 통해, 첫번째 계층의 index를 --> 컬럼으로 올린다.
    이전에는 df5.unstack()만하여 최하위 계층의 index가  --> 컬럼으로 올라갔었다.
    image
  • level인자를 특정 인덱스명으로 지정해줘도 똑같이 컬럼으로 특정index층이 올라가는 것을 확인할 수 있다.
    image


이제 2개의 Series를 만들고, concat()함수를 통해, s1밑에다가 s2를 행으로서, 단순연결하여 s3의 Series를 만드는데
keys인자를 통해  s1과 s2의 각각 index이름이 one과 two로 구분된다.
기존에 인덱스가 있는 series들의 합이므로, keys로 준 인덱스명이 상위계층의  인덱스가 되어버림?!!
(axis=1로서 행방향으로 열을 각각 붙혔다면, 칼럼명이 생성되는데 여기서는 index가 있는 상태에서 또 인덱스명으로 들어가므로, 계층적인덱스가 되어버린다)
(concat()함수는 axis인자가 default일 때, 열에 따른 행 방향으로 붙힘, 즉 밑에다가 행으로 붙힌다.)

s1 = pd.Series([0, 1, 2, 3], index=['a', 'b', 'c', 'd'])
s2 = pd.Series([4, 5, 6], index=['c', 'd', 'e'])
s3 = pd.concat([s1, s2], keys=["one", "two"])

image

  • unstack()을 통해 최하위층 index를 칼럼으로 올리자. 이 때, 칼럼으로 올라가면서 index c와 d는 겹치므로, 알아서 2개 행에 담아진다.
    s3.unstack()
    image


이제 좀 더 복잡한 DataFrame을 stack과 unstack()함수를 살펴보자.
기존 계층적 인덱스를 가지고 있는 Series df5를 2개로 나누어 df를 만들고,  거기다가 name이라는 컬럼 전체이름에, 각각 left, right라는 컬럼명도 지정해주었다.

df6 = pd.DataFrame({"left": df5, "right": df5 + 5},
                    columns=["left", "right"])
df6.columns.name = "side"
image

  • df6.unstack()을 통해 최하위 index인 number 라는 인덱스명이 –> 칼럼의 최하위층로 올라간다.
    image
  • unstack()을 할 때, level인자를 주어서, 첫번째 계층(city)을 최하위 칼럼으로 올릴 수 도 있다.
    df6.unstack(level=0)
    image
  • stack과 unstack을 동시에 할 수 도 있다.
    df6.unstack(level="city").stack(level="side") 을 통해,
    city 인덱스는 unstack하여 칼럼으로 올리고, 칼럼명 side는 stack()을 통해 index 최하위계층으로 내려보자.
    image

데이터 분석에 있어서, stack/unstack 등의 계층적 인덱싱을 하면 복잡해지므로, 사실상 비추하는 분야이다.

+ Recent posts