Pandas - Pivot, Stack, Unstack, Melt

Pandas 에서 2차원 data를 Stack, unstack, melt 를 이용해 복합 인덱스를 사용할 수 있고 Pivot 을 이용해 특정 데이터 중심의 2차원 데이터로 생성할 수 있다.

  1. Pivot
  2. Stack & Unstack
  3. Melt
1
2
import pandas as pd
import numpy as np

2차원 테이블

행과 열로 구성된 데이터 집합

1
2
3
4
5
6
7
df = pd.DataFrame(
{'foo' : ['One','One','One','Two','Two','Two'],
'bar': ['A','B','C','A','B','C'],
'baz': [1,2,3,4,5,6] ,
'zoo': ['x','y','z','q','w','t']}
)
df

foo bar baz zoo
0 One A 1 x
1 One B 2 y
2 One C 3 z
3 Two A 4 q
4 Two B 5 w
5 Two C 6 t

Pivot

피봇/피봇 테이블은 2차원 데이터 열에서 공통된 부분을 중심으로 새 테이블 집합을 형성하게 해준다. 피봇은 index, columns, values 라는 이름을 가진 세 가지 parameter를 취한다. 이러한 각 파라미터의 값으로 원래 표에 열 이름을 지정해야 한다.

foo, bar, baz, zoo 컬럼 중에서 foo 에 대해서 정리를 하고 bar 를 컬럼으로 지정하면 아래와 같다.

1
2
df_pivot = df.pivot_table(index='foo', columns='bar', values='baz')
df_pivot

bar A B C
foo
One 1 2 3
Two 4 5 6

ex) nba 데이터를 포지션의 연령별 연봉 테이블로 전환

1
2
dfnba = pd.read_csv('../data/nba.csv')
dfnba

Name Team Number Position Age Height Weight College Salary
0 Avery Bradley Boston Celtics 0.0 PG 25.0 6-2 180.0 Texas 7730337.0
1 Jae Crowder Boston Celtics 99.0 SF 25.0 6-6 235.0 Marquette 6796117.0
2 John Holland Boston Celtics 30.0 SG 27.0 6-5 205.0 Boston University NaN
3 R.J. Hunter Boston Celtics 28.0 SG 22.0 6-5 185.0 Georgia State 1148640.0
4 Jonas Jerebko Boston Celtics 8.0 PF 29.0 6-10 231.0 NaN 5000000.0
... ... ... ... ... ... ... ... ... ...
453 Shelvin Mack Utah Jazz 8.0 PG 26.0 6-3 203.0 Butler 2433333.0
454 Raul Neto Utah Jazz 25.0 PG 24.0 6-1 179.0 NaN 900000.0
455 Tibor Pleiss Utah Jazz 21.0 C 26.0 7-3 256.0 NaN 2900000.0
456 Jeff Withey Utah Jazz 24.0 C 26.0 7-0 231.0 Kansas 947276.0
457 NaN NaN NaN NaN NaN NaN NaN NaN NaN

458 rows × 9 columns

1
pd.options.display.float_format = "{:,.2f}".format

컬럼의 데이터에 공통된 모습이 많이 보인다. 이 중에서 포지션을 기준으로 나이에 따른 연봉을 본다고 가정하면 포지션을 인덱스로하고 나이를 컬럼으로 지정하면 아래와 같다.

1
2
# 포지션의 연령별 연봉 테이블
dfnba.pivot_table(index='Position', columns='Age', values='Salary')

Age 19.00 20.00 21.00 22.00 23.00 24.00 25.00 26.00 27.00 28.00 ... 31.00 32.00 33.00 34.00 35.00 36.00 37.00 38.00 39.00 40.00
Position
C NaN 5,143,140.00 1,571,000.00 3,476,698.20 2,121,999.50 4,532,003.33 10,881,995.00 3,041,850.82 5,004,260.50 7,635,761.83 ... 10,338,218.00 8,208,695.50 9,500,000.00 6,258,000.00 7,448,760.00 947,276.00 NaN 222,888.00 NaN 5,250,000.00
PF NaN 2,369,838.00 2,397,408.00 1,601,105.80 2,399,120.50 2,577,551.44 2,195,476.60 7,228,086.75 9,217,098.43 5,268,839.17 ... 5,323,787.00 14,346,365.00 2,630,241.40 6,469,277.50 2,624,593.50 2,877,470.00 6,666,667.00 NaN NaN 8,500,000.00
PG NaN 3,316,290.00 1,944,080.00 2,381,130.00 1,627,769.00 4,652,526.50 5,422,085.80 10,038,174.80 5,944,070.17 5,021,965.17 ... 7,467,596.40 4,082,425.33 2,226,179.67 8,395,104.00 NaN 2,170,465.00 NaN NaN 947,726.00 250,750.00
SF NaN 1,979,976.00 1,404,480.00 2,401,364.60 2,760,134.36 5,067,491.60 3,382,640.73 7,322,325.20 10,532,567.00 1,996,608.71 ... 10,960,320.25 9,720,195.75 NaN 261,894.00 947,276.00 1,721,559.75 25,000,000.00 3,376,000.00 NaN NaN
SG 1,930,440.00 1,749,840.00 2,215,710.43 2,055,241.00 1,388,251.18 3,205,720.53 1,782,834.89 9,872,690.29 4,815,524.62 6,354,000.00 ... 7,085,000.00 2,041,138.00 2,233,533.33 12,579,269.50 3,512,173.75 3,311,138.00 NaN 1,880,638.00 4,088,019.00 NaN

5 rows × 22 columns

Stack & Unstack

2차원 테이블은 행 과 열이 순차적 값으로 교차하게 되어 있다. 스택은 컬럼의 값을 아래-위로 배치를 시킨다고 상상이 된다. 그래서 스택과 언스택은 이렇게 생각된다.

  • stack : 2차원 컬럼의 내용을 수직방향으로 쌓는 구조, 즉 새로운 인덱스가 더해진다.
  • unstack : 인덱스 구성요소를 한 단계 컬럼으로 만들며 수평방향으로 쌓게 한다.

stack

pandas reshaping

1
2
3
4
5
6
7
df_single_level = pd.DataFrame(
[['Mostly cloudy', 10], ['Sunny', 12]],
index=['London', 'Oxford'],
columns=['Weather', 'Wind']
)
df_single_level

Weather Wind
London Mostly cloudy 10
Oxford Sunny 12

index와 weather, wind 라는 컬럼을 stack을 호출하면 weather-wind 관련 인덱스를 생성하고 데이터를 나열한다.

1
df_single_level.stack()
London  Weather    Mostly cloudy
        Wind                  10
Oxford  Weather            Sunny
        Wind                  12
dtype: object

다른 데이터를 살펴보자.

1
2
3
4
5
6
7
8
9
10
11
12
# MultiIndex
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(8, 2), index=index, columns=["A", "B"])
df

A B
first second
bar one -1.301694 -0.013259
two -0.197846 0.879890
baz one 0.718211 -0.739434
two -0.140217 0.071260
foo one -1.142268 -2.606413
two 1.119145 0.109402
qux one -0.504167 -1.703280
two 1.064976 1.011060

위 데이터는 stack()을 하면 A, B 컬럼이 MultiIndex 로 추가되며 A, B 컬럼 데이터 포인트가 배치된다.

1
df.stack()
first  second   
bar    one     A   -1.301694
               B   -0.013259
       two     A   -0.197846
               B    0.879890
baz    one     A    0.718211
               B   -0.739434
       two     A   -0.140217
               B    0.071260
foo    one     A   -1.142268
               B   -2.606413
       two     A    1.119145
               B    0.109402
qux    one     A   -0.504167
               B   -1.703280
       two     A    1.064976
               B    1.011060
dtype: float64
1
2
df2 = df[:4]
df2

A B
first second
bar one -1.301694 -0.013259
two -0.197846 0.879890
baz one 0.718211 -0.739434
two -0.140217 0.071260
1
2
stacked = df2.stack()
stacked
first  second   
bar    one     A   -1.301694
               B   -0.013259
       two     A   -0.197846
               B    0.879890
baz    one     A    0.718211
               B   -0.739434
       two     A   -0.140217
               B    0.071260
dtype: float64

Unstack

pandas reshaping

1
stacked
first  second   
bar    one     A   -1.301694
               B   -0.013259
       two     A   -0.197846
               B    0.879890
baz    one     A    0.718211
               B   -0.739434
       two     A   -0.140217
               B    0.071260
dtype: float64
1
stacked.unstack()

A B
first second
bar one -1.301694 -0.013259
two -0.197846 0.879890
baz one 0.718211 -0.739434
two -0.140217 0.071260

레벨을 지정

1
stacked.unstack(1)

second one two
first
bar A -1.301694 -0.197846
B -0.013259 0.879890
baz A 0.718211 -0.140217
B -0.739434 0.071260
1
stacked.unstack(2)

A B
first second
bar one -1.301694 -0.013259
two -0.197846 0.879890
baz one 0.718211 -0.739434
two -0.140217 0.071260
1

Melt

melt() 는 ID 변수를 기준으로 원래 데이터셋에 있던 여러개의 칼럼 이름을 ‘variable’ 칼럼에 위에서 아래로 길게 쌓아놓고, ‘value’ 칼럼에 ID와 variable에 해당하는 값을 넣어주는 식으로 데이터를 재구조화합니다.

1
2
3
4
5
6
7
8
9
cheese = pd.DataFrame(
{
"first": ["John", "Mary"],
"last": ["Doe", "Bo"],
"height": [5.5, 6.0],
"weight": [130, 150],
}
)
cheese

first last height weight
0 John Doe 5.50 130
1 Mary Bo 6.00 150
1
2
# colums중 height, weight 를 row 로 융합
cheese.melt(id_vars=['first','last']) # first, last 인덱스로 나머지 컬럼은 value 로 치환

first last variable value
0 John Doe height 5.50
1 Mary Bo height 6.00
2 John Doe weight 130.00
3 Mary Bo weight 150.00
1
cheese.melt(id_vars=['first','last'], value_name='quantity') 

first last variable quantity
0 John Doe height 5.50
1 Mary Bo height 6.00
2 John Doe weight 130.00
3 Mary Bo weight 150.00
1

참고

  1. pandas.DataFrame.stack

Pandas/Numpy 숫자의 출력 옵션 조정

numpy 와 pandas 에서 수를 출력할 때 형식, 크기 및 범위를 설정할 수 있다. 간단히 보면 아래 테이블 같이 형식을 바꿔준다.

column 변환 column
1e6 precision 1,000,000
1.1e-6 format 0.1

아래 요약한 옵션 방법을 사용해서 Numpy 와 Pandas에 있는 숫자를 출력할 때 표현방법, 표기법, 환률, 정밀도 등을 변경해 사용할 수 있다

numpy 출력 형식 변경

numpy 숫자 출력 형식 변경

numpy.set_printoptions 을 사용할 수 있다.

1
2
numpy.set_printoptions(precision=None, threshold=None, edgeitems=None, linewidth=None, suppress=None,
nanstr=None, infstr=None, formatter=None, sign=None, floatmode=None, *, legacy=None)

현재 출력형식 확인

np.get_printoptions() 으로 현재 상태를 출력할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
> np.get_printoptions()
{'edgeitems': 3,
'threshold': 1000,
'floatmode': 'maxprec',
'precision': 8,
'suppress': False,
'linewidth': 75,
'nanstr': 'nan',
'infstr': 'inf',
'sign': '-',
'formatter': None,
'legacy': False
}

Numpy 에서 실수 Float 의 출력 형식을 바꾸는 몇가지 사례를 보자

- formatter 이용

1
2
import numpy as np
np.set_printoptions(formatter={'float_kind': lambda x: "{0:0.3f}".format(x)})

- precision 이용

1
2
3
> np.set_printoptions(precision=4)
> np.array([1.123456789])
[1.1235]

- threshold 이용

개수가 많은 아이템을 출력할 때 요약해 출력할 수 있다.

1
2
3
> np.set_printoptions(threshold=5)
> np.arange(10)
array([0, 1, 2, ..., 7, 8, 9])

numpy.printoptions 사용

numpy.printoptions 를 with 구문과 함께 사용해 제한된 출력 조정을 할 수 있다.

출력시 printoptions 를 with 구문과 사용할 수 있다. set_printoptions 의 인자를 동일하게 적용할 수 있다

  • precision, threshold, edgeitems, linewidth, suppress, nanstr, infstr, formatter, sign, floatmode
1
numpy.printoptions(*args, **kwargs)[source]

소수점 출력 변경

1
2
3
4
5
6
> np.array([2.0]) / 3
array([0.66666667])


> with np.printoptions(precision=3):
> print( np.array([2.0]) / 3 )

pandas 숫자 출력 형식 변경

pandas에서 몇 가지 옵션을 바꾸는 방법을 정리해 보자. pandas의 옵션은 pd.options 를 사용한다.

pd.options.display

출력의 형태, 표기를 변경하는 것은 pd.options.display 아래에 있다. 여기서 사용할 수 있는 옵션은 describe_option() 으로 확인할 수 있다.

1
2
3
4
5
6
7
> pd.describe_option()
compute.use_bottleneck : bool
Use the bottleneck library to accelerate if it is installed,
the default is True
Valid values: False,True
[default: True] [currently: True]
...

- row, column 출력 개수 조정

  • pd.options.display.max_rows : 표를 출력할 때 최대 행 수입니다.
  • pd.options.display.min_rows : 표를 출력할 때 최소 행 수입니다.
1
2
3
4
5
import pandas as pd
> pd.options.display.max_rows
60
> pd.options.display.min_rows
10

min_row, max_row에 직접 대입하면 해당 옵션의 현재 값이 변경된다.

1
2
3
> pd.options.display.min_rows=100
> pd.options.display.min_rows
100

max_rows에 값을 입력하면 테이블의 최대 행수를 바꿀 수 있다.

이미지 참조 1

1
2
3
> pd.options.display.max_rows = 100
> pd.options.display.max_rows
100

- pd.get_option(), pd.set_option() 함수

pd.get_option() 함수를 이용해서 옵션인자에 대한 정보를 확인할 수 있다.

1
2
> pd.get_option('min_rows')
100

get_option은 옵션 이름의 일부만 일치해도 된다.

1
2
> pd.get_option('min_r')
100

max_rows 수를 설정한다. set_option도 일부만 일치해도 된다.

1
2
3
4
5
6
> pd.set_option('max_rows', 20)
> pd.options.display.max_rows
20
> pd.set_option('max_r', 50)
> pd.options.display.max_rows
50

- 컬럼의 폭 조정

display.max_colwidth 는 보통 50~70자 정도 정해져 있다. 컬럼에 표시되는 텍스트가 50자가 넘으면 ... 줄임 표시가 나타난다.

1
2
> pd.options.display.max_colwidth
50

- chop_threshold

chop_threshold 는 값의 크기 한계를 지정해서 이 값보다 작은 수는 모두 0으로 표시한다.

1
2
3
4
5
6
> pd.options.display.chop_threshold = 0.99
> pd.DataFrame({'x': [10, 1, 0.1]})
> print(x)
0 10.0
1 1.0
2 0.0

숫자 포매팅

다양한 사례는:

- float_format

float_format 는 실수 값을 출력시 소수점의 출력의 정밀도를 조정할 수 있다. 아래 람다 함수 lambda x: f'{x:.1f} 는 실수 x를 받아 소수점 첫째 자리까지 출력해 준다.

1
2
3
4
> pd.options.display.float_format = lambda x: f'{x:.1f}'
> pd.DataFrame({'x': [3.141592]})
x
0 3.1

또한 set_option 을 사용할 수 있다.

1
> pd.set_option('display.float_format', '{:.2f}'.foramt )

금액 단위에 사용하는 천단위 구분을 위해서 {:,.2f} 형식을 사용하면 화폐 단위를 추가하고 천단위 구분자를 추가해 주고 소수점 2자리수 정밀로를 지정한다.

1
2
3
4
5
> pd.set_option('display.float_format', '${:,.2f}'.format )
> pd.DataFrame({'x': [10000000.0, 34589234.4]})
x
0 $10,000,000.00
1 $34,589,234.40

- precision

실수의 소수점은 precision 로 과학적 표기법으로 변환할 자릿수를 지정한다. 아래와 같이 하면 소수점 셋째 자리 밑으로는 과학적 표기법으로 표시합니다.

1
2
3
4
> pd.options.display.precision = 3
> pd.DataFrame({'x': [0.5], 'y': [0.0005]})
x y
0 0.5 5.000e-04

과학적 표기법으로 3.000e-04는 3.000 이다. 자릿수가 아주 작거나 큰 수를 표기할 때 유용합니다.

- 설정 초기화 reset_option()

설정을 초기화할 때 사용한다.

1
2
3
4
# chop_threshold 옵션 초기화
pd.reset_option('display.chop_threshold')
# float_format 옵션 초기화
pd.reset_option('display.float_format')

- 옵션 설명 describe_option()

pd.describe_option(OPTIONS) 를 사용하면 해당 옵션에 대한 설명을 출력해 준다.

1
2
3
4
5
> pd.describe_option("max_rows")
display.max_rows : int
If max_rows is exceeded, switch to truncate view. Depending on
`large_repr`, objects are either centrally truncated or printed as
a summary view. 'None' value means unlimited.

참고

1: Try These Pandas Display Configurations

2: Pandas options