Pandas 사용방법 정리 - 4
Updated:
14. 정렬, 순위
정렬 관련 메서드들은 아래와 같다. 마지막 rank 메서드는 순위를 매기는데 사용한다.
method | 설명 | dataframe | series | index |
---|---|---|---|---|
sort_values | 값으로 정렬 | v | v | v |
sort_index | 인덱스 정렬 | v | v | |
argsort | 값을 정렬하는 정수 인덱스 | v | v | |
searchsorted | 값이 추가될 때 정렬이 유지되는 인덱스 | v | v | |
sort | v | |||
sortlevel | v | |||
rank | 순위 | v | v |
가장 자주 사용하는 메서드는 sort_values, sort_index, rank다. 예제를 통해 세부 기능을 확인해 보자.
14-1. sort_values
sort_values 메서드는 값을 기준으로 정렬한다.
# 예제 데이터: hierarchical index를 가진 dataframe
df = pd.DataFrame({'area': ['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c'],
'cond': ['x', 'y', 'z', 'x', 'y', 'z', 'x', 'y', 'z'],
'expr': [6, 3, 8, 9, 3, 1, 6, 3, 5],
'pred': [5, 4, 10, 7, 2, 4, 7, 5, 7],
'size': [1, 3, 2, 5, 3, 5, 6, 3, 3]})
df.set_index(['area', 'cond'], inplace=True)
"""df.sort_values(by, axis=0, ascending=True, inplace=False,
kind='quicksort', na_position='last', ignore_index=False)
- by: str, list of str
- axis: 0, 1, index, columns
- ascending: bool, list of bool
- na_position: 'first', 'last'
"""
# by 인수에 지정한 column label의 값을 기준으로 정렬
# list of column labels을 지정하면 지정한 순서대로 정렬
df.sort_values('expr')
df.sort_values(['pred', 'size'])
# axis=1은 by에 지정한 row label의 값을 기준으로 정렬
# hierarchical index인 경우 index tuple로 명확히 row를 선택해 줘야 함
df.sort_values(('a', 'x'), axis=1)
# ascending=False는 내림차순 정렬
# by=list of label 이면 ascending 역시 list of bool을 입력
df.sort_values('expr', ascending=False)
df.sort_values(['pred', 'size'], ascending=[False, True])
# na_position 인수는 정렬할 때 NaN의 위치를 지정
df['expr'][np.random.randint(0, 8, 3)] = np.NaN
df.sort_values('expr')
df.sort_values('expr', na_position='first')
14-2. sort_index
sort_index 메서드는 index 또는 columns를 정렬한다.
# 예제 데이터: hierarchical index를 가진 dataframe
df = pd.DataFrame({'area': ['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c'],
'cond': ['x', 'y', 'z', 'x', 'y', 'z', 'x', 'y', 'z'],
'expr': [6, 3, 8, 9, 3, 1, 6, 3, 5],
'pred': [5, 4, 10, 7, 2, 4, 7, 5, 7],
'size': [1, 3, 2, 5, 3, 5, 6, 3, 3]})
df.set_index(['area', 'cond'], inplace=True)
"""df.sort_index(axis=0, level=None, ascending=True, inplace=False,
kind='quicksort', na_position='last', sort_remaining=True,
ignore_index=False)
- axis: 0, 1, index, columns
- level: int, level name, list of int, list of level names
"""
# axis=0는 row index를 정렬하며 level은 hierarchical index의 경우
# indexname이나 정수를 지정
df.sort_index(level=1)
df.sort_index(level='cond')
# 보통 swaplevel 후, outer index를 정렬할 때 자주 사용
df.swaplevel().sort_index()
# level 인수는 list of int, list of level names를 지정할 수 있음
df.stack().sort_index(level=[2, 1, 0])
# axis=1은 column index를 정렬
df.unstack('cond').swaplevel(axis=1).sort_index(axis=1, level='cond')
14-3. rank
rank는 순위를 매긴다. 순위를 매기는 여러 방법이 차이를 이해해 보자.
# 예제 데이터: hierarchical index를 가진 dataframe
df = pd.DataFrame({'area': ['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c'],
'cond': ['x', 'y', 'z', 'x', 'y', 'z', 'x', 'y', 'z'],
'expr': [6, 3, np.nan, 9, 3, np.nan, 6, 3, 5],
'pred': [5, 4, 10, 7, 2, 4, 7, 5, 7],
'size': [1, 3, 2, np.nan, 3, 5, 6, 3, 3]})
df.set_index(['area', 'cond'], inplace=True)
"""df.rank(axis=0, method='average', numeric_only=None, na_option='keep',
ascending=True, pct=False)
- 1부터 n까지 순위를 매기는데, 같은 값들의 경우는 순위에 평균값을 지정
하는것이 디폴트
- axis: 0, 1, 'index', 'columns'
- method: 'average', 'min', 'max', 'first', 'dense'
- numeric_only=True면 수치로 된 열만 순위를 매김
- na_option: 'keep', 'top', 'bottom'
- pct=True면 퍼센트 형태로 순위를 보여줌
"""
# rank method를 이해하기 위해, 정렬된 수치열 하나로 테스트
subdf = df[['pred']].sort_values('pred')
target = subdf.pred
subdf.assign(mtd_avg=target.rank(),
mtd_min=target.rank(method='min'),
mtd_max=target.rank(method='max'),
mtd_frt=target.rank(method='first'),
mtd_den=target.rank(method='dense'),
mtd_den_pct=target.rank(method='dense', pct=True))
15. 치환
여기서는 dataframe이나 series의 값을 다른 값으로 치환하는 메서드에 대해 알아본다.
15-1. replace
df = pd.DataFrame({'A': ['i', 'j', 'k', 'l', 'm'],
'B': [0, 1, 2, 3, 4],
'C': [5, 6, 7, 8, 9]}, index=list('abcde'))
"""<df|se>.replace(to_replace=None, value=None, inplace=False, limit=None,
regex=False, method='pad')
- to_replace에 입력된 값을 value로 치환
- to_replace: numeric, str, regex
list of numeric, list of str, list of regex
dict
- limit: method 인수에 지정한 방법으로 치환될 값의 개수
- method: 'pad', 'ffill', 'bfill', 'None'
"""
# to_replace=old, value=new
# -----------------------------------------------------------------------
# to_replace 인수에 지정한 값을 value에 지정한 값으로 치환
df.replace('i', 'iii')
df.replace(0, 999)
# to_replace=list, value=new or list of new values
# -----------------------------------------------------------------------
# 여러 값을 한번에 바꿀 수 있음
df.replace(['i', 'k', 0, 4], 999)
df.replace(['i', 'k', 0, 4], ['iii', 'kkk', 1000, 444]) # 1:1 대응
# to_replace=dict or nested dict
# -----------------------------------------------------------------------
# value=None인 상태에서 to_replace=dict면 dict={old:new, ...}
df.replace({'i':'iii', 'k':'kkk', 0:1000, 8:888})
# value=None이 아닌 상태에서 to_replace=dict면
# dict={column:'old', ...}, value={column: 'new', ...} 방식으로 치환
df.replace({'A':'i', 'B':2, 'C':9}, value=999)
df.replace({'A':'i', 'B':2, 'C':9}, value={'A':'iii', 'B':222, 'C':999})
# to_replace=nested dict 는 nested dict = {column: {old:new}, ...}
df.replace({'A':{'i':'iii'}, 'B':{2:222, 3:333}, 'C':{8:888, 9:999}})
# to_replace=regex, value=new, regex=True
# -----------------------------------------------------------------------
# 치환할 값을 정규표현식으로 정의할 수 있는데 regex=True로 설정해야 함
df.replace(r'[ikm]', 'iii', regex=True)
# 치환할 값을 정규표현식으로 여러개를 입력하는 것은 list, dict 모두 같은
# 방식으로 동작함
df.replace([r'[ikm]', r'[^ikm]'], ['iii', 'jj'], regex=True)
df.replace({r'[ikm]':'iii', r'[^ikm]':'jj'}, regex=True)
df.replace({'A': {r'[ikm]':'iii', r'[^ikm]':'jj'}}, regex=True)
# method=...
# -----------------------------------------------------------------------
# value 인수로 치환 값을 지정하지 않고, method로 값을 채울 수 있음
df.replace(['k', 'l', 1, 2, 6, 7], method='ffill')
df.replace(['k', 'l', 1, 2, 6, 7], method='bfill')
15-2. where, mask
dataframe.where(cond, other=nan, …) 메서드는 dataframe과 동일한 크기인 cond의 원소가 True이면 원래의 dataframe 값을 사용하고, False이면 other의 값으로 치환한다. dataframe.mask(cond, other=nan, …)은 where 메서드와 반대로 cond가 True이면 other의 값으로 치환한다.
"""<df|se>.where(cond, other=nan, inplace=False, axis=None, level=None,
errors='raise', try_cast=False)
- cond 인수에 입력한 condition이 False면 other의 값으로 변경
- cond: bool series/dataframe, array-like, callable
True면 원래값을 유지하고, False면 other에 있는 값으로 교체
- other: scalar, series/dataframe, callable
cond이 False일때 교체될 값을 지정
"""
df = pd.DataFrame({'A': [6, 3, 8, 9, 3, 1, 6, 3, 5],
'B': [5, 4, 1, 7, 2, 4, 7, 5, 7],
'C': [1, 3, 2, 5, 3, 5, 6, 3, 3]})
# cond 인수의 조건이 True면 값을 유지하고, False면 other의 값으로 변경
df.where(df == 6)
df.where(df == 6, 0)
df.where(df == 6, -df)
# cond 인수에는 callable이 입력될 수 있는데, callable의 인수로 self가 전달되며,
# self와 동일한 크기의 boolean이 리턴되야 함
df.where(lambda x: (x == 6) | (x == 1))
16. read, write
분석하고자 하는 실무 데이터는 대개 excel, csv, txt 파일 등에 임의의 형식으로 저장되어 있고 이것을 python으로 불러와서 사용하게 된다. pandas의 pd.read_excel
, pd.read_csv
같은 함수들을 사용하면 파일에 있는 데이터를 쉽게 dataframe 형태로 불러올 수 있다. dataframe을 csv나 excel로 저장하려면 df.to_csv
, df.to_excel
메서드를 이용하면 된다. 아래 표는 pandas에서 사용 가능한 read 관련 함수와 write 관련 메서드이다.
- read 관련 pandas top-level functions
method | 설명 | pandas |
---|---|---|
read_csv | v | |
read_table | v | |
read_excel | v | |
read_clipboard | v | |
read_json | v | |
read_html | v | |
read_pickle | v | |
read_hdf | v | |
read_feather | v | |
read_fwf | v | |
read_gbq | v | |
read_orc | v | |
read_parquet | v | |
read_sas | v | |
read_spss | v | |
read_sql | v | |
read_sql_query | v | |
read_sql_table | v | |
read_stata | v |
- write 관련 methods
method | 설명 | pandas | dataframe | series |
---|---|---|---|---|
to_csv | v | v | ||
to_excel | v | v | ||
to_clipboard | v | v | ||
to_json | v | v | ||
to_html | v | |||
to_pickle | v | v | ||
to_markdown | v | v | ||
to_feather | v | |||
to_latex | v | v | ||
to_sql | v | v | ||
to_stata | v |
17. discretization
여기서는 데이터를 일정 간격으로 분리하는 방법에 대해 살펴본다. 이와 관련된 pandas top-level function은 cut과 qcut이다.
pd.cut(x, bins, ...)
- bins에 입력된 간격으로 x를 분리
pd.qcut(x, q, ...)
- x를 분위 (quantile) 기준으로 분리
17-1. pd.cut
bins에 입력된 간격으로 입력된 array 또는 series의 원소를 분류한다. pd.cut의 리턴은 Categorical 객체이거나 Categorical 객체로 만들어진 series다. Categorical에 대해서는 이후에 더 자세히 다룬다.
"""pd.cut(x, bins, right=True, labels=None, retbins=False, precision=3,
include_lowest=False, duplicates='raise')
Bin values into discrete intervals
- x: 1d ndarray or series
- bins: int, list of scalars, IntervalIndex
- right: 각각의 bin이 어느쪽 edge를 포함하는가 결정
- labels: array, False, None; bins의 labels을 지정
"""
# pd.cut의 ouput은 x 인수에 입력된 객체의 형태에 따라 다르다.
# ndarray가 입력되면 Categorical 객체가 리턴되고,
# series가 입력되면 Categorical 객체를 values로 하는 series가 리턴된다.
#---- x=ndarray, bins=int
# x에 입력된 최소/최대값 범위를 bins=int 수 만큼 equal-sized bins를 만들어
# x의 각 원소를 분류한다. 리턴 객체는 Categorical 임을 확인
cats = pd.cut(np.arange(10), 4)
type(cats) # Categorical
cats.codes
cats.categories # IntervalIndex
# cats.categories를 확인해 보면 첫 번째 interval이 (-0.009, 2.25] 인데,
# 이것은 bins=int로 입력되면 x에 입력된 최소, 최대값을 interval에 포함시키기
# 위해 (max-min)*0.001 크기로 범위를 확장하기 때문이다. (pd.cut? 참고)
#---- right 인수
# right=False는 각각의 bin의 영역의 왼쪽을 닫는다.
pd.cut(np.arange(10), 4, right=False)
#---- labels 인수
# 각각의 bin에 label을 붙인다. bins의 수와 동일해야 한다.
cats = pd.cut(np.arange(10), 4, labels=['a', 'b', 'c', 'd'])
cats.codes
cats.categories # Index
#---- retbins 인수
# bins에 int를 입력한 경우, retbins=True를 지정하면
# bin edges가 저장된 sequence of scalars가 리턴된다.
cats, bins = pd.cut(np.arange(10), 4, retbins=True)
# labels 인수로 bins에 label을 붙이면 interval 정보를 얻을 수 없는데
# 이 때 retbins=True로 intevals을 알 수 있다.
cats, bins = pd.cut(np.arange(10), 4, labels=['a', 'b', 'c', 'd'], retbins=True)
#---- x=ndarray, bins=list-of-scalars
# bins=list-of-scalars 로 bins의 범위를 직접 지정할 수 있다.
# 즉, unequal intervals을 지정할 수 있다.
pd.cut(np.arange(10), [-999, 2, 5, 9, +999])
# 생성되는 bins의 수는 len(list-of-scalars) - 1 이다.
pd.cut(np.arange(10), [-999, 2, 5, 9, +999], labels=['a', 'b', 'c', 'd'])
# interval을 직접 정의하는 것이기 때문에 일부 원소는 포함이 안될 수 있고
# NaN으로 처리된다.
pd.cut(np.arange(10), [1, 6, 8])
#---- x=series
# x가 series면 Categorical 객체가 values인 series가 리턴된다.
se = pd.Series(np.arange(10))
out = pd.cut(se, 4)
out.values # Categorical
out.values.codes
out.values.categories
# 나머지 기능은 위에서 살펴본 것과 동일하다.
pd.cut(se, 4, labels=['a', 'b', 'c', 'd'])
pd.cut(se, 4, retbins=True)
pd.cut(se, 4, right=False)
#---- 응용
# pd.cut, pd.value_counts 함수를 이용한 histogram
se = pd.Series(np.random.randn(10000), name='value')
pd.cut(se, 32).value_counts().sort_index().plot(kind='bar', use_index=False)
17-2. pd.qcut
pd.qcut은 분위 (quantile) 기반 분류 함수이다. 즉, 1000개의 데이터를 4개의 bins로 qcut하면 각 bin에 250개씩 들어가도록 bins을 구성한다. 나머지 기능은 pd.cut과 동일하다.
"""pd.qcut(x, q, labels=None, retbins=False, precision=3, duplicates='raise')
- Quantile-based discretization function
- x: 1d ndarray or series
- q: int, list of ratio numbers like [0, 0.25, 0.5, 0.75, 1.0]
- labels: array, False, None; bins의 labels을 지정
"""
#---- q=int
# pd.qcut(x, q)는 x의 최소/최대 범위에서 x의 원소들이 각각의 bin에
# 거의 동일한 수만큼 배치되도록 bins의 범위를 구성한다.
cats = pd.qcut([0, 1, 2, 3, 4, 9, 10], 2)
cats.value_counts()
cats = pd.qcut(np.random.randn(998), 4)
cats.value_counts()
# 다른 인수는 pd.cut에서 설명한 것과 동일하다.
nums = np.random.randn(998)
# labels 인수
cats = pd.qcut(nums, 4, labels=list('abcd'))
cats.value_counts()
# retbins 인수
cats, bins = pd.qcut(nums, 4, labels=list('abcd'), retbins=True)
#---- q=list-of-ratio-numbers
# quantile bins의 비율을 직접 정의할 수 있다. 아래는 20:60:20 으로
# quantile bins을 만든다.
cats = pd.qcut(np.random.randn(1000), [0, 0.2, 0.8, 1.0])
cats.value_counts()
# bins의 정의에 따라 포함되지 않는 원소는 NaN으로 처리된다.
cats = pd.qcut(np.random.randn(1000), [0.1, 0.2, 0.8, 0.9], precision=2)
cats.value_counts(dropna=False)
18. str
문자열 값을 갖는 series와 index에 대해 문자열 연산을 하고 싶을 때, map 메서드를 사용할 수 있다.
se1 = pd.Series(list('abcdef'))
se1.map(lambda x: x.upper())
se1.map(lambda x: x.center(9, '*'))
그러나 series 또는 index에 대한 문자열 연산은 str 클래스를 통해 접근하는 vectorized string methods를 사용하는 것이 더 좋다. series 객체와 index 객체는 str attribute를 통해 아래와 같은 문자열 메서드를 사용할 수 있다.
method | method | method | method |
---|---|---|---|
capitalize | isalnum | join | find, rfind |
casefold | isalpha | len | index, rindex |
cat | isdecimal | lower | center, ljust, rjust |
contains | isdigit | upper | partition, rpartition |
count | islower | title | split, rsplit |
decode | isnumeric | match | strip, lstrip, rstrip |
encode | isspace | normalize | slice |
endswith | istitle | pad | slice_replace |
startswith | isupper | repeat | swapcase |
extract | replace | translate | |
extractall | get | wrap | |
findall | get_dummies | zfill |
몇 가지 예제를 통해 사용법을 확인해 보자.
se1 = pd.Series(list('abcdef'))
se2 = pd.Series(list('ghijkl'))
se1.str.upper()
se1.str.center(9, '*')
se1.str.cat(sep=', ') # 모든 원소 연결
se1.str.cat(se2, sep=' ') # 두 series의 원소 대 원소 연결
se1.str.contains('b')
se1.str.contains('b|d', regex=True)
pd.Series(['1', '20', '300', '4000']).str.zfill(5)
19. method chaining
19-1. assign, query
dataframe에 새로운 열을 추가하는 방법은 indexing을 사용하거나 또는 12장 함수적용에서 다루었던 assign 메서드를 사용하는 것이다. 둘의 차이는 indexing으로 열을 추가하면 곧바로 원본 dataframe에 열이 추가되는 inplace=True 효과가 있고, assign 메서드로 열을 추가하면 열이 추가된 dataframe을 리턴하기 때문에 inplace=False인 상태이다.
df = pd.DataFrame({'v': ['i', 'i', 'j', 'j'],
'w': ['a', 'b', 'a', 'b'],
'x': [1, 2, 3, 4]})
# indexing
df['y'] = [5, 6, 7, 8]
# assign method
df.assign(z = [9, 10, 11, 12])
5장 subset selection에서 다루었던 query 메서드는 문자열로 조건을 입력해서 subset을 추출할 수 있는데, 이것 역시 boolean indexing으로도 할 수 있다. boolean indexing에 비해 query의 장점은 boolean expression에서 self을 지정해 줄 필요가 없다는 것이다.
df = pd.DataFrame({'v': ['i', 'i', 'j', 'j'],
'w': ['a', 'b', 'a', 'b'],
'x': [1, 2, 3, 4]})
# boolean indexing
df[df.x > 2]
# query method
df.query('x > 2')
위와 같은 특징으로 인해 assign 메서드와 query 메서드는 method chaining에 사용 할 수 있다. method chainging은 임시 dataframe variable을 만들지 않아도 되는 장점이 있다. 예를 들어 새로운 열을 추가한 후, 추가된 열에 대한 조건에 따라 subset selection을 하는 방법은 아래와 같다.
df = pd.DataFrame({'v': ['i', 'i', 'j', 'j'],
'w': ['a', 'b', 'a', 'b'],
'x': [1, 2, 3, 4]})
df.assign(y = lambda d: d.x**2 -4).query('y > 0')
19-2 pipe
pipe 메서드는 method chaining에 자주 사용된다. pipe 메서드가 어떤 역할인지 아래 예제를 통해 확인하자.
# 어떤 연산을 하는 callable의 첫 번째 입력 인수가 dataframe이라고 하자.
line = lambda tb, slope, intercept: slope*tb + intercept
# 아래와 같이 함수를 호출하여 연산을 수행할 수 있다.
df = pd.DataFrame(np.arange(10).reshape(5, 2), columns=['x', 'y'])
line(df, 2, -3)
# 위와 동일한 결과를 pipe 메서드로 얻을 수 있다.
df.pipe(line, 2, -3)
pandas 객체의 메서드 대부분은 self와 동일한 type을 리턴하므로 assign, query, pipe 등과 마찬가지로 method chaining을 할 수 있다.
df = pd.DataFrame({'area': ['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c'],
'cond': ['x', 'y', 'z', 'x', 'y', 'z', 'x', 'y', 'z'],
'is' : ['T', 'T', 'F', 'F', 'T', 'F', 'F', 'T', 'T'],
'expr': [6, 3, 8, 9, 3, 1, 6, 3, 5],
'pred': [5, 4, 10, 7, 2, 4, 7, 5, 7],
'size': [1, 3, 2, 5, 3, 5, 6, 3, 3]})
line = lambda tb, slope, intercept: slope*tb['value'] + intercept
(df.assign(dev = lambda d: d.expr - d.pred)
.query('dev < 0').groupby('area').mean().melt().pipe(line, 2, -3))
20. Categorical
중복이 많은 데이터를 다룰 때 Categorical data를 사용할 수 있다. Categorical data 사용의 장점은 메모리 효율과 계산 효율을 높이며 정렬 순서를 사용자가 정의하여 여러 연산에 활용할 수 있다는 점이다. 여기서 정리한 내용들은 아래 링크를 참고한 것이다.
20-1. 소개
아래는 Categorical 객체에 대한 간단한 설명이다.
# 중복이 많은 문자열 리스트로 series를 만든다고 하자.
data = ['apple', 'orange', 'apple', 'apple'] * 200
se1 = pd.Series(data)
se1.unique()
se1.value_counts()
# 위와 같이 중복이 많은 경우 메모리와 계산 효율을 높이기 위해
# dimension table과 그것을 참조하는 integer keys로 표현할 수 있다.
dimtb = pd.Series(['apple', 'orange'])
keys = pd.Series([0, 1, 0, 0] * 200)
se2 = dimtb.take(keys)
se2.unique()
se2.value_counts()
# Categorical은 위 dimension table과 integer keys를 구현한 데이터 구조이다.
cat = pd.Categorical(data)
cat.codes # integer keys
cat.categories # dimension table
# Categorical 객체는 series의 values가 될 수 있다.
# 즉, dimension table과 integer keys로 표현된 일차원 데이터로 생각할 수 있다.
se3 = pd.Series(cat)
isinstance(se3.values, pd.Categorical)
se3.unique()
se3.value_counts()
# 중복 데이터가 많을수록 Categorical 객체의 메모리 효율은 좋아진다.
# 보통의 series와 categorical 객체로 만든 series의 메모리 사용 비교
se1.name = 'ordinary'
se3.name = 'category'
pd.concat([se1, se3], axis=1).memory_usage(index=False, deep=False)
pd.concat([se1, se3], axis=1).memory_usage(index=False, deep=True)
20-2. Categorical 생성
Categorical 객체를 생성하는 방법은 pd.Categorical
클래스를 사용하는 것이다. 예제를 통해 생성방법을 정리한다.
"""pd.Categorical(values, categories=None, ordered=None,
dtype=None, fastpath=False)
Represent a categorical variable in classic R / S-plus fashion
- values: Categorical을 만들 값들을 입력
categories 인수에 입력한 목록에 없는 값이 values에 있으면
NaN으로 처리
- categories: Categorical을 만드는 unique category 목록
입력하지 않으면 values의 unique 목록을 categories로 할당
- ordered: True로 지정하면 ordered categorical로 간주되며, 리턴되는
categorical은 categories 인수에 입력된 순서로 정렬
"""
#-- pd.Categorical(values=list)
# values 인수에 입력한 목록의 unique 목록이 categories attribute에 저장되며,
# codes attribute에 integer keys가 저장된다.
cat1 = pd.Categorical([2, 3, 1, 4, 2, 1, 3])
cat1.codes
cat1.categories
# 주의해서 봐야할 것은 categories가 자동으로 정렬되어 저장된다는 점이다.
# 이것은 어휘순 (lexical order)으로 정렬된 것이다.
cat2 = pd.Categorical(list('bcadbac'))
cat2.codes
cat2.categories
#-- pd.Categorical(..., categories=list)
# categories 인수에는 unique 목록을 정의할 수 있다.
# categories에 포함되지 않는 values는 NaN으로 맵핑되며, codes에는 -1로 저장된다.
cat3 = pd.Categorical(list('bcadbac'), categories=['b', 'c', 'a'])
cat3[3]
cat3.codes
# categories 인수에 입력한 순서 그대로 categories attribute에 저장된다.
cat3.categories
#-- pd.Categorical(..., ordered=True)
# ordered=True를 지정하면 생성된 categorical 객체는 ordered categorical이 되며,
# categorical를 출력했을 때 categories가 [a < b < c < ...] 방식으로 표시된다.
# 그리고 ordered attributes가 True로 저장된다.
cat4 = pd.Categorical(list('bcadbac'), ordered=True)
cat4.ordered
#-- pd.Categorical.from_codes(codes, categories)
# integer codes와 categories가 있다면 from_codes 메서드를 이용해서
# Categorical 객체를 생성할 수 있다.
"""pd.Categorical.from_codes(codes, categories=None,
ordered=None, dtype=None)
Make a Categorical type from codes and categories or dtype.
- codes: array-like of int
categories의 각 category를 지시하는 정수값. -1은 NaN을 맵핑
- categories: category의 unique 목록
입력하지 않으면 dtype에 categories 정보가 있어야 함
- ordered: True면 ordered categorical 객체가 리턴
"""
codes = [1, 2, 0, 3, 1, 0, 1]
cat5 = pd.Categorical.from_codes(codes, ['a', 'b', 'c', 'd'])
20-3. sort_values
Categorical 데이터를 사용하는 장점 중 하나는 값 정렬에 대한 정렬 기준을 categories 인수를 통해 지정할 수 있다는 것이다. 아래 예를 통해 확인한다.
# 아래와 같은 데이터를 값으로 하는 Series가 있다.
data = ['two', 'three', 'one', 'one', 'three', 'four', 'two', 'four']
se = pd.Series(data)
# 위 series를 값으로 정렬하면 lexical order로 정렬되기 때문에 logical order인
# 'one', 'two', 'three', 'four' 순서로 정렬된 결과를 얻을 수 없다.
se.sort_values()
# 위 data를 가지고 categories 인수 지정없이 Categorical 객체를 생성해 보자.
# categories 인수에 목록을 지정하지 않으면 data의 unique 목록이 정렬되어
# categories attribute에 저장된다.
cat1 = pd.Categorical(data)
cat1.categories
# Categorical 객체는 정렬될 때 categories에 정의된 순서로 정렬되기 때문에
# 위 series 정렬과 동일한 결과를 얻는다.
cat1.sort_values()
# 따라서 categories를 logical order로 지정해주면 원하는 정렬 결과를 얻는다.
cat2 = pd.Categorical(data, categories=['one', 'two', 'three', 'four'])
cat2.categories
cat2.sort_values()
20-4. ordered categorical
앞에서 Categorical 객체 생성 시 ordered=True
를 지정하면 ordered categorical 객체가 생성된다고 하였다. unordered categorical 인 경우 min, max 같은 크기 순서와 관련된 연산을 할 수 없다. 이런 연산을 하려면 categories의 크기 순서를 정해줘야 하는데 이것이 ordered categorical이다. 예제를 통해 확인하자.
# 아래와 같이 unordered categorical 객체는 순서와 관련된 min 메서드 사용 시
# TypeError가 발생한다.
data = [22, 33, 11, 44, 22, 11, 33]
cat1 = pd.Categorical(data)
try:
cat1.min()
except TypeError as err:
print('TypeError: {}'.format(err))
# ordered=True를 지정하여 ordered categorical을 생성할 수 있고,
# 객체의 출력 정보에 categories가 11 < 22 < 33 < 44 임을 알 수 있다.
cat2 = pd.Categorical(data, ordered=True)
cat2 # Categories (4, int64): [11 < 22 < 33 < 44]
# 크기 순서가 정의되어 있기 때문에 min, max를 구할 수 있다.
cat2.min()
cat2.max()
# 같은 방식으로 categories 인수 지정으로 logical order로
# min, max를 정의할 수 있다.
data = ['two', 'three', 'one', 'one', 'three', 'four', 'two', 'four']
cat3 = pd.Categorical(data, categories=['one', 'two', 'three', 'four'],
ordered=True)
cat3.min()
cat3.max()
20-5. Categorical methods
Categorical 객체의 메서드는 categories의 추가, 삭제, 이름 변경, 순서 변경, 재지정 및 ordered Categorical 설정, 해제에 관련된 것들이다.
add_categories(new_categories, inplace=False)
remove_categories(removals, inplace=False)
remove_unused_categories(inplace=False)
rename_categories(new_categories, inplace=False)
reorder_categories(new_categories, ordered=None, inplace=False)
set_categories(new_categories, ordered=None, rename=False, inplace=False)
as_ordered(inplace=False)
as_unordered(inplace=False)
예제를 통해 위 메서드의 동작을 확인한다. 주의해서 볼 것은 categories를 추가, 삭제, 변경할 때 원소들이 유지되는지, 변경되는지도 확인해야 한다는 것이다.
cat = pd.Categorical(list('abcabcdd'), categories=['a', 'b', 'c', 'd'])
#---- add_categories: categories 추가
cat.add_categories(['e', 'f'])
#---- remove_categories: categories 삭제
# 삭제한 categories를 참조하는 원소도 NaN으로 변경
# 즉, codes가 -1이 됨
cat.remove_categories(['b', 'd'])
#---- remove_unused_categories: 사용되지 않는 categories 삭제
temp = cat.add_categories(['e', 'f'])
temp.remove_unused_categories()
#---- rename_categories: categories의 이름 변경
# categories가 이름이 변경되면 원소도 그에 따라 변경
# rename_categories의 인수는 list, dictionary mapper, callable 형식을 지원
# list는 기존 categories와 크기가 동일해야 함
cat.rename_categories(['aa', 'bb', 'cc', 'dd'])
# dictionary mapper는 {old:new, ...} 형식으로 지정
cat.rename_categories({'a':'aa', 'b':'bb'})
# callable은 category가 하나씩 callable 인수로 전달
cat.rename_categories(lambda c: c.upper())
#---- reorder_categories: categories의 순서 변경
# old categories 모두를 포함하는 new order categories를 입력해야 함
temp = cat.reorder_categories(['b', 'c', 'a', 'd'])
temp.sort_values()
# categories 순서 변경과 함께 ordered Categorical을 만들 수 있음
cat.reorder_categories(['b', 'c', 'a', 'd'], ordered=True)
#---- set_categories(new_cat, ordered=None, rename=False)
# reset_categories는 old categories에 대해서는 유지하고 새로운
# categories를 동시에 입력할 수 있다.
# 이를 통해 reorder, add, rename의 등이 같이 들어 있는 셈이다.
# old categories를 재배치 하면 reorder 기능을 한다.
cat.set_categories(['b', 'a', 'd', 'c'])
# 주의할 점은 reorder_categories와는 다르게 old categories를 잘못 입력해도
# 검사하지 않고, 새로운 category에 대해 add 한 효과가 난다.
cat.set_categories(['b', 'a', 'd', 'cc'])
# rename=True를 입력하면 rename_categories와 같다.
cat.set_categories(['b', 'a', 'dd', 'cc'], rename=True)
# rename을 하면서 새로운 category를 추가도 할 수 있다.
cat.set_categories(['b', 'a', 'dd', 'cc', 'ee'], rename=True)
#---- as_ordered, as_unordered
# ordered categorical, unordered categorical을 만들어 준다.
cat = cat.as_ordered()
cat.ordered
cat = cat.as_unordered()
cat.ordered
20-6. dtype, astype
categorical은 series의 values로 사용된다. 따라서 pd.Series
클래스로 series의 생성할 때 곧바로 series의 values가 categorical data가 되도록 할 수 있다. 그리고 보통의 series를 astype 메서드를 사용해서 series의 values를 categorical로 바꿀 수 있다. 예제를 통해 확인해 보자.
#---- series 생성 시 values를 categorical로 만드는 방법 1
data = ['two', 'three', 'one', 'one', 'three', 'four', 'two', 'four']
se = pd.Series(data, dtype='category')
isinstance(se.values, pd.Categorical)
se.values.codes
se.values.categories
#---- series 생성 시 values를 cateogircal로 만드는 방법 2
# 방법 1은 cateogories를 지정할 수 없다. cateogories를 지정하려면
# CategoricalDtype 클래스를 이용하면 된다.
cattype = pd.CategoricalDtype(['one', 'two', 'three', 'four'])
se = pd.Series(data, dtype=cattype)
se.values.categories
# ordered categorical 도 생성할 수 있다.
cattype = pd.CategoricalDtype(['one', 'two', 'three', 'four'], ordered=True)
se = pd.Series(data, dtype=cattype)
se.values.ordered
#---- 기존 series의 values를 categorical로 만드는 방법
se = pd.Series(data)
se = se.astype('category')
se.values
# 마찬가지로 CategoricalDtype을 사용할 수 있다.
se = pd.Series(data)
cattype = pd.CategoricalDtype(['one', 'two', 'three', 'four'])
se = se.astype(cattype)
se.values
20-7. CategoricalAccesor
어떤 series의 values attribute가 Categorical이면 series에서 categorical의 attributes에 접근하도록 하는 cat
클래스를 사용할 수 있다.
se = pd.Series(list('ccababdadb'), dtype='category')
se.cat.codes
se.cat.categories
se.cat.ordered
# se.values.<categorical method> 는 categorical 객체를 리턴하기 때문에
# series를 유지할 수 없고, 따라서 series의 method chaining을 할 수 없다.
# cat 클래스를 이용하면 그대로 series가 리턴된다.
se = se.cat.reorder_categories(['b', 'c', 'a', 'd'])
se.sort_values()
se = se.cat.add_categories(['e', 'f'])
se = se.cat.remove_unused_categories()
Leave a comment