데이터 분석/Pandas

10 minutes to pandas - Reshaping

haloaround 2022. 3. 30. 23:13

Hierarchical Indexing (MultiIndex)

In essence, it enables you to store and manipulate data
with an arbitrary number of dimensions
in lower dimensional data structures like Series (1d) and DataFrame (2d) 

 

MultiIndex 의 효용

The reason that the MultiIndex matters is that it can allow you to do grouping, selection
when loading data from a file, you may wish to generate your own MultiIndex when preparing the data set.

 

Index is like an address, that’s how any data point across the dataframe or series can be accessed.
Indices made it very easy to bring more information easily
Index make filtering very easy and also give you space to move forward and backwards in your data

 

 

Creating a MultiIndex (hierarchical index) object


A MultiIndex can be created from 
- a list of arrays (using MultiIndex.from_arrays()),
- an array of tuples (using MultiIndex.from_tuples()),
- a crossed set of iterables (using MultiIndex.from_product()),
- or a DataFrame (using MultiIndex.from_frame()).

df = pd.DataFrame(
    [["bar", "one"], ["bar", "two"], ["foo", "one"], ["foo", "two"]],
    columns=["first", "second"],
)

pd.MultiIndex.from_frame(df)

'''
MultiIndex([('bar', 'one'),
            ('bar', 'two'),
            ('foo', 'one'),
            ('foo', 'two')],
           names=['first', 'second'])
'''
iterables = [["bar", "baz", "foo", "qux"], ["one", "two"]]
pd.MultiIndex.from_product(iterables, names=["first", "second"])

'''
MultiIndex([('bar', 'one'),
            ('bar', 'two'),
            ('baz', 'one'),
            ('baz', 'two'),
            ('foo', 'one'),
            ('foo', 'two'),
            ('qux', 'one'),
            ('qux', 'two')],
           names=['first', 'second'])
'''

 

index 는 행과 열 모두에 적용할 수 있다.
(물론 DataFrame 을 생성할 때에는 index 는 행, columns 는 열이지만!)

df = pd.DataFrame(
    np.random.randn(3, 4), 
    index=["A", "B", "C"], 
    columns=index[:4]
)

 

그리고 행과 열의 위치를 이렇게 바꿀 수도 있다. 

df2 = df.T
df2

 

 

Indexing and Selecting data


 

Using loc

Tuple 은 Hierarcy 로 계층을 나타낼 수 있다. 
[("bar", "two")] 는 level = 0 는 "bar", level = 1 는 "two" 인 인덱스를 지칭한다. 

List 는 하나의 차원에서 여러 키를 지정할 때 사용한다.
[["bar", "baz"]] 는 level = 0 에서 "bar", "baz" 인 인덱스를 지칭한다. 

# level = 0 1차원까지 지정
df.loc["bar"]
df.loc["baz":"foo"]
df.loc[["bar", "baz"]]

# level = 1 2차원까지 지정
df.loc[("bar", "two")]
df.loc[("baz", "two"):"foo"]
df.loc[("baz", "two"):("qux", "one")]
df.loc[[("bar", "two"), ("qux", "one")]]

# level = 0 의 특정 값과 level = 1 의 특정값 
df.loc[(['bar', 'baz'], 'one'), 'A']
df.loc[(['bar', 'baz'], 'one'), ]   #, 필수
df.loc[(['bar', 'baz'], 'one'), :]  #, 필수

 

# You should specify all axes in the .loc specifier
.loc 에서는 가급적 모든 축의 정보를 명시해야한다.  

# 에러를 발생시키는 대표적인 사례들
df.loc[(['bar', 'baz'], 'one')]   #, 필수
df.loc[('bar':'qux', 'one'), ]   # () 안에서 : 사용불가

 

Using slicer

You can use slice(None) to select all the contents of that level.
You do not need to specify all the deeper levels, they will be implied as slice(None).

.loc[] 의 대괄호 내부에서 행과 열을 쉼표로 구분한다. 필요할 경우 () 로 행과 열을 감싼다.
첫번째 level 부터 명시한다. () 안의 , 로 차원을 구분한다. 

 

dfmi.loc[(slice("A1", "A3"), slice(None), ["C1", "C3"]), :]

# 행 level = 0 슬라이싱: 'A1' 이상 'A3' 이하
# 행 level = 1 전체
# 행 level = 2 배열: 'C1' 과 'C3'
# 행 level = 3 전체: 별도 표기 없음



dfmi.loc["A1", (slice(None), "foo")]

# 행 level = 0 단수: 'A1'
# 열 level = 0 전체
# 열 level = 1 단수: 'foo'

 

pd.IndexSlice

idx 로 시작하는 [] 배열이므로, 조금 더 가독성이 좋다. 
뒤의 차원은 생략하면 된다. 

idx = pd.IndexSlice
dfmi.loc[idx[:, :, ["C1", "C3"]], idx[:, "foo"]]

# 행 level = 0 전체 / level = 1 전체 / level = 2 C1, C3 
# 컬럼  level = 0 전체, level = 1 foo

 

mask 조건에 부합하는 인덱스만 필터링

특정 값이 조건에 만족하는 경우만 필터링 할 수 있다. 

mask = dfmi[('a', 'foo')] > 200
dfmi.loc[idx[mask, :, ['C1', 'C3']], idx[:, 'foo']]

 

xs()

xs (cross-section) 을 통해 특정 level 차원을 지정할 수 있다.
level 만 잘 명시해주면 되니, 다차원에서 헷갈려할 필요가 없다고.

df.xs("one", level="second")
# df.loc[(slice(None), "one"), :]

df.xs("one", level="second", axis=1)
# df.loc[:, (slice(None), "one")]


df = df.T
df.xs(("one", "bar"), level=("second", "first"), axis=1)

# 생략한 level 에 대해서도 표시하거나, 표시하지 않거나
df.xs('one', level='second', axis=1, drop_level = True)
df.xs('one', level='second', axis=1, drop_level = False)

 

 

Index 정리


 

set_index

제시되는 인덱스로 설정한다. 

dfm = pd.DataFrame(
    {"jim": [0, 0, 1, 1], "joe": ["x", "x", "z", "y"], "jolie": np.random.rand(4)}
)

dfm = dfm.set_index(["jim", "joe"])

dfm
'''
            jolie
jim joe          
0   x    0.490671
    x    0.120248
1   z    0.537020
    y    0.110968
'''

 

reset_index

인덱스를 0, 1, 2 등 RangeIndex 로 설정한다. (drop = False, default)
drop 은 하지 않는다! 다른 정보가 더 필요할 때 'key' 로 사용할 수 있기 때문이다. 

df.reset_index()

 

 

stack, unstack

열과 행 axis 를 바꿔본다. 

tuples = list(
    zip(
        *[
            ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
            ["one", "two", "one", "two", "one", "two", "one", "two"],
        ]
    )
)


index = pd.MultiIndex.from_tuples(tuples, names=["first", "second"])

df = pd.DataFrame(np.random.randn(4, 2), index=index, columns=["A", "B"])

df
'''
                     A         B
first second                    
bar   one    -0.727965 -0.589346
      two     0.339969 -0.693205
baz   one    -0.339355  0.593616
      two     0.884345  1.591431
'''


      
stacked = df.stack()   # 열을 행으로!
stacked
'''
first  second   
bar    one     A   -0.727965
               B   -0.589346
       two     A    0.339969
               B   -0.693205
baz    one     A   -0.339355
               B    0.593616
       two     A    0.884345
               B    1.591431
dtype: float64
'''


stacked.unstack()   # index 의 마지막 차원을 열로
stacked.unstack(1)  # index 의 level = 1 차원을 열로

 

reindex

데이터와 인덱스를 독립적으로 생각하자.
해당 인덱스에 대한 데이터가 없으면 기본적으로 NaN 이다. 

index = ['Firefox', 'Chrome', 'Safari', 'IE10', 'Konqueror']
df = pd.DataFrame({'http_status': [200, 200, 404, 404, 301],
                  'response_time': [0.04, 0.02, 0.07, 0.08, 1.0]},
                  index=index)
                  
df
'''
           http_status  response_time
Firefox            200           0.04
Chrome             200           0.02
Safari             404           0.07
IE10               404           0.08
Konqueror          301           1.00
'''


new_index = ['Safari', 'Iceweasel', 'Comodo Dragon', 'IE10',
             'Chrome']
             
df.reindex(new_index)
'''
               http_status  response_time
Safari               404.0           0.07
Iceweasel              NaN            NaN    # 값이 없는 인덱스 NaN
Comodo Dragon          NaN            NaN    # 값이 없는 인덱스 NaN
IE10                 404.0           0.08
Chrome               200.0           0.02
'''


df.reindex(new_index, fill_value=0)

'''
               http_status  response_time
Safari                 404           0.07
Iceweasel                0           0.00      # 값이 없는 인덱스 NaN
Iceweasel                0           0.00      # 값이 없는 인덱스 NaN
Comodo Dragon            0           0.00      
IE10                   404           0.08
Chrome                 200           0.02
'''


df.reindex(['http_status', 'user_agent'], axis="columns")
           http_status  user_agent
Firefox            200         NaN
Chrome             200         NaN
Safari             404         NaN
IE10               404         NaN
Konqueror          301         NaN

 

 

align

두개의 차원을 맞춘다. 

level=0 일 경우, 행의 차원을 맞춘다. (없는 값은 default broadcast 한다. 다른 값을 지정해줄 수도 있다. ) 

df = pd.DataFrame(
    [[1, 2, 3, 4], [6, 7, 8, 9]], columns=["D", "B", "E", "A"], index=[1, 2]
)
other = pd.DataFrame(
    [[10, 20, 30, 40], [60, 70, 80, 90], [600, 700, 800, 900]],
    columns=["A", "B", "C", "D"],
    index=[2, 3, 4],
)

df
'''
D  B  E  A
1  1  2  3  4
2  6  7  8  9
'''

other
'''
    A    B    C    D
2   10   20   30   40
3   60   70   80   90
4  600  700  800  900
'''

left, right = df.align(other, join="outer", axis=1)
# 명시된 axis, 열에 대해 index 를 맞춘다. (A, B, C, D, E)로. 없는 값은 NaN 이다.
left
'''
   A  B   C  D  E
1  4  2 NaN  1  3
2  9  7 NaN  6  8
'''

right
'''
    A    B    C    D   E
2   10   20   30   40 NaN
3   60   70   80   90 NaN
4  600  700  800  900 NaN
'''


left, right = df.align(other, join="outer", axis=0)
# 명시된 axis, 행에 대해 index 를 맞춘다. (1,2,3,4)로. 없는 값은 NaN 이다.

left
'''
    D    B    E    A
1  1.0  2.0  3.0  4.0
2  6.0  7.0  8.0  9.0
3  NaN  NaN  NaN  NaN
4  NaN  NaN  NaN  NaN
'''

right
'''
    A      B      C      D
1    NaN    NaN    NaN    NaN
2   10.0   20.0   30.0   40.0
3   60.0   70.0   80.0   90.0
4  600.0  700.0  800.0  900.0
'''


left, right = df.align(other, join="outer", axis=None)
# 전체 axis 에 대해 index 를 맞춘다. 없는 값은 NaN 이다.

left
'''
     A    B   C    D    E
1  4.0  2.0 NaN  1.0  3.0
2  9.0  7.0 NaN  6.0  8.0
3  NaN  NaN NaN  NaN  NaN
4  NaN  NaN NaN  NaN  NaN
'''

right
'''
       A      B      C      D   E
1    NaN    NaN    NaN    NaN NaN
2   10.0   20.0   30.0   40.0 NaN
3   60.0   70.0   80.0   90.0 NaN
4  600.0  700.0  800.0  900.0 NaN
'''

 

다차원의 reindexing 과 alignment

다차원에서 index 가 변경될 경우, 차원에 맞게 broadcasting (aka. 자기복제) 된다.

midx = pd.MultiIndex(
    levels=[["zero", "one"], ["x", "y"]], codes=[[1, 1, 0, 0], [1, 0, 1, 0]]
)

df = pd.DataFrame(np.random.randn(4, 2), index=midx)
df
'''
               0         1
one  y  1.519970 -0.493662
     x  0.600178  0.274230
zero y  0.132885 -0.023688
     x  2.410179  1.450520
'''


df2 = df.groupby(level=0).mean()  
df2 # (2,2)
'''
             0         1
one   1.060074 -0.109716
zero  1.271532  0.713416
'''

df2.reindex(df.index, level=0)  # (2,2) --> (4,2) 값은 broadcasting (차원에 맞게 복제)
               0         1
one  y  1.060074 -0.109716
     x  1.060074 -0.109716
zero y  1.271532  0.713416
     x  1.271532  0.713416

# aligning
df_aligned, df2_aligned = df.align(df2, level=0)
# df (4,2), df2 (2,2)

df_aligned # (4,2) --> (4,2) 그대로 
'''
               0         1
one  y  1.519970 -0.493662
     x  0.600178  0.274230
zero y  0.132885 -0.023688
     x  2.410179  1.450520
'''


df2_aligned # (2,2) --> (4,2) 값은 broadcasting (차원에 맞게 복제) 
'''               0         1
one  y  1.060074 -0.109716
     x  1.060074 -0.109716
zero y  1.271532  0.713416
     x  1.271532  0.713416
'''