Pandas 사용방법 정리 - 3

Updated:

8. 삭제

여기서는 dataframe, series, index 객체의 원소, 행, 열, row index (index), column index (columns) 삭제 방법에 대해 정리한다.

8-1. drop

drop 메서드는 dataframe 행/열 삭제, series, index 객체의 원소 삭제를 하는데 사용한다.

  • <df|se|ix>.drop(...)
# 예제 데이터로 hierarchical index, series, dataframe 준비
rix = pd.MultiIndex.from_arrays([list('abbc'), list('iijj')], names=['rk1', 'rk2'])
cix = pd.MultiIndex.from_arrays([list('xyz'), list('ghi')], names=['ck1', 'ck2'])
se = pd.Series([0, 1, 2, 3], index=rix, name='w')
df = pd.DataFrame(np.arange(12).reshape(4, 3), index=rix, columns=cix)


"""<se|df>.drop(labels=None, axis=0, index=None, columns=None,
                level=None, inplace=False, errors='raise')
    - se 또는 df의 지정 행/열을 삭제한 객체 리턴
"""
# labels을 입력하면 index와 series의 해당하는 원소 또는 dataframe 행을 삭제
df.drop('b')           # outer label
df.drop(['a', 'c'])    # outer label list
df.drop('i', level=1)  # inner label
df.drop([('a', 'i'), ('b', 'j')]) # index tuples

# 열 삭제
df.drop(['x', 'z'], axis=1)
df.drop(columns=['x', 'z'])

# inplace (리턴 없음)
df.drop(index=['b'], inplace=True)


"""ix.drop(codes, level=None, errors='raise')
    - ix의 지정 원소를 삭제한 index 객체 리턴
"""
rix.drop('b')
rix.drop('j', level=1)
rix.drop([('a', 'i'), ('b', 'j')])

8-2. dropna

dropna는 dataframe, series, index의 원소가 NaN이 있을 때 그와 관련된 열/행/원소 삭제와 관련된 메서드이다.

  • <df|se|ix>.dropna
# 예제 데이터
data = [[np.nan, 1,      2, np.nan],
        [np.nan, 0, np.nan,      1],
        [np.nan, 0, np.nan,      3],
        [     1, 2,      3,      4]]
df = pd.DataFrame(data, index=list('abcd'), columns=list('wxyz'))
se = pd.Series([np.nan, 1, 2, np.nan], index=list('abcd'))

"""df.dropna(axis=0, how='any', thresh=None, subset=None, inplace=False)
    - axis: 0, index, 1, columns
    - how: any, all
    - thresh=int
    - subset=labels
"""

# NaN 원소가 없는 행만 리턴
df.dropna()

# NaN 원소가 없는 열만 리턴
df.dropna(axis=1)

# 모든 원소가 NaN인 행 또는 열만 리턴
df.dropna(how='all')
df.dropna(how='all', axis=1)

# subset에 지정한 행/열에 NaN이 있는지 검사하여 리턴
df.dropna(subset=['x', 'y'])
df.dropna(subset=['a'], axis=1)

# thresh는 지정한 정수만큼 NaN이 아닌 수치가 들어있는 행 또는 열을 출력
df.dropna(thresh=3) # NaN이 아닌 수치가 3개 이상인 행
df.dropna(thresh=3, axis=1) # NaN이 아닌 수치가 3개 이상인 열

"""se.dropna(inplace=False)
"""
se.dropna()

NaN 또는 NA에 관련된 또 다른 메서드는 isna, notna, fillna, isnull, notnull 등이 있는데 missing values에 관한 설명을 할 때 언급한다.

8-3. droplevel, reset_index

series 또는 dataframe의 hierarchical index/columns의 임의의 level을 삭제하고 싶을 때가 있다. 그때는 droplevel 메서드를 이용하면 된다. droplevel 메서드는 최소 하나의 level은 남겨둬야 한다. 모든 level을 삭제하려면 ValueError가 발생한다. reset_index(..., drop=True)를 사용하면 row index만 삭제할 수 있지만 모든 level을 삭제할 수 있다.

  • <df|se|ix>.droplevel(level, axis=0)
  • <se|df>.reset_index(level, drop=True)
# 예제 데이터로 hierarchical index, series, dataframe 준비
rix = pd.MultiIndex.from_arrays([list('abbc'), list('iijj')], names=['rk1', 'rk2'])
cix = pd.MultiIndex.from_arrays([list('xyz'), list('ghi')], names=['ck1', 'ck2'])
df = pd.DataFrame(np.arange(12).reshape(4, 3), index=rix, columns=cix)

# droplevel
df.droplevel(level=0)
df.droplevel(level='rk1')
df.droplevel(level=1, axis=1)
df.droplevel(level='ck2', axis=1)

# 모든 level을 삭제하려고 시도하면 ValueError
#df.droplevel(level=[0, 1]) 

# reset_index의 drop=True를 이용해서 index를 삭제할 수 있다.
df.reset_index(level=1, drop=True)
df.reset_index(level=[0, 1], drop=True)

# named index라면 indexname을 입력할 수 있다.
df.reset_index(level='rk2', drop=True)
df.reset_index(level=['rk1', 'rk2'], drop=True)

8-4. drop_duplicates

drip_duplicates 메서드는 dataframe, series, index의 중복을 제거하는데 사용된다.

  • <df|se|ix>.drop_duplicates
# 예제 데이터
ix = pd.Index(list('aabb'), name='key')
se = pd.Series([0, 1, 1, 1], index=ix)
data = {'x': ['i', 'i', 'i', 'j'],
        'y': ['m', 'k', 'k', 'n'],
        'z': [2, 6, 6, 6]}
df = pd.DataFrame(data, index=ix)

"""df.drop_duplicates(subset=None, keep='first', inplace=False, 
                      ignore_index=False)
    - 중복 행을 삭제한 dataframe 리턴
    - subset은 column label 또는 column label list 입력
    - keep는 first, last, False 지원. False는 중복 모두 삭제
    
   se.drop_duplicates(keep='first', inplace=False)
    - 중복 원소를 삭제한 series를 리턴
    
   ix.drop_duplicates(keep='frist')
    - 중복 원소를 삭제한 index를 리턴
"""

# series, index 중복원소 삭제
ix.drop_duplicates()
se.drop_duplicates()

# dataframe 중복 행 삭제
# 입력한 subset=label의 중복을 제외하고 keep='first'에 의해 처음 원소만 남김
df.drop_duplicates('x')
df.drop_duplicates('y')
df.drop_duplicates('z')
df.drop_duplicates(['x', 'z'])

# keep 인수 옵션에 따른 차이
df.drop_duplicates('z', keep='first')
df.drop_duplicates('z', keep='last')
df.drop_duplicates('z', keep=False)

# index 무시
df.drop_duplicates('x', ignore_index=True)

duplicated

삭제 관련 메서드는 아니지만, 원소가 중복인지 아닌지 boolean 값을 리턴하는 메서드 duplicated가 있다. 조심할 것은 keep 인수의 옵션에 따라 중복이라도 유지되는 원소는 False로 처리된다. 즉, 모든 중복 원소를 체크하려면 keep=False를 해야 한다.

  • <df|se|ix>.duplicated
df.duplicated('z', keep='first')
df.duplicated('z', keep=False)

se.duplicated(keep='last')
se.duplicated(keep=False)

ix.duplicated(keep='last')
ix.duplicated(keep=False)

9. NA 처리

여기서는 Not Available (NA) 관련 처리 메서드에 대해 정리한다. np.NaN은 NA로 취급된다. pandas 1.0 부터 pd.NA가 추가되어 역시 NA로 취급된다. 이미 앞 장에서 살펴본 dropna 메서드를 제외하고 여기서 살펴볼 NA 관련 메서드는 아래와 같은 것들이 있다. obj는 dataframe, series, index를 지칭한다.

  • obj.fillna
  • <df|se>.interpolate
  • obj.isna, obj.isnull
  • obj.notna, obj.notnull

9-1. fillna

fillna 메서드는 NA에 특정 값을 채우는데 사용한다.

# 예제 데이터
data = [[np.nan, 1,      2, np.nan],
        [np.nan, 0, np.nan,      1],
        [np.nan, 0, np.nan,      3],
        [     1, 2,      3,      4]]
df = pd.DataFrame(data, index=list('abcd'), columns=list('wxyz'))
se = pd.Series([np.nan, 1, 2, np.nan], index=list('abcd'))

"""<df|se>.fillna(value=None, method=None, axis=None, inplace=False,
                  limit=None, downcast=None)
    - value 인수는 NA대신 채워넣을 값을 scalar, dict, series, dataframe로 정의
    - method는 backfill, bfill, pad, ffill 지원
    - axis는 0, 1, index, columns
    - limit는 method로 채워지는 값의 개수를 지정
"""

# 모든 NA에 특정 값을 채워 넣는다.
df.fillna(0)
# dataframe의 경우 각 column에 따라 채울 값을 따로 지정할 수 있다.
df.fillna({'w':111, 'x':222, 'y':333, 'z':444})
# 아직 아래 같은 방식은 구현되지 않았다: NotImplementedError
#df.fillna({'a':111, 'b':222, 'c':333, 'd':444}, axis=1)
# dataframe은 채울 값에 대한 mapping 역할을 한다.
df.fillna(pd.DataFrame({'w': [111, 222, 333, 444]}, index=list('abcd')))

# method=backfill 또는 bfill 은 NA 값을 뒤 원소 값으로 채운다.
df.fillna(method='bfill')         # 열방향
df.fillna(method='bfill', axis=1) # 행방향
#  method=pad 또는 ffill 은 NA 값을 앞 원소 값으로 채운다.
df.fillna(method='ffill')         # 열방향
df.fillna(method='ffill', axis=1) # 행방향

# limit 인수는 method로 채워지는 값의 개수를 지정
# 예를 들어 NaN이 3개있는데 1을 지정하면 하나만 채우고 나머지는 NaN으로 유지
df.fillna(method='bfill', limit=1)

9-2. interpolate

interpolate 메서드는 fillna 메서드보다 더 기능이 많다. bfill이나 ffill을 할 수 있다. 특정 값으로 채우는 value를 지정하는 대신 1d interpolation을 사용할 수 있다.

"""<df|se>.interpolate(method='linear', axis=0, limit=None, inplace=False,
                       limit_direction='forward', limit_area=None,
                       downcast=None, **kwargs)
    - method 인수에 지정한 방법으로 NA를 내삽
    - method: 많은 내삽방법을 지원. 도움말 참고
    - axis: dataframe의 경우 내삽 방향 지정
    - limit: 채워질 NA의 개수. 0보다 커야함
    - limit_direction: NA가 채워지는 방향. 'forward', 'backward', 'both'
    - limit_area: NA를 채울 때 제한. inside (내삽만), outside (외삽만)
"""

# method 지정에 따른 내삽
# index가 numeric이여야만 간격계산이 가능
se = pd.Series([1, 2, np.nan, 4, 5])
se.interpolate() # method='linear'
se.interpolate('ffill')
se.interpolate('nearest')
se.interpolate('cubic')
se.interpolate('spline', order=2)
se.interpolate('polynomial', order=2)

# 아래 예제는 limit_direction 지정에 따라 외삽할 수 있음을 보여준다.
df = pd.DataFrame([(0.0, np.nan, -1.0, 1.0),
                   (np.nan, 2.0, np.nan, np.nan),
                   (2.0, 3.0, np.nan, 9.0),
                   (np.nan, 4.0, -4.0, 16.0)],
                  columns=list('abcd'))
df.interpolate(method='linear', limit_direction='forward')
df.interpolate(method='linear', limit_direction='backward')
df.interpolate(method='linear', limit_direction='both')

# 외삽만 하려면 limit_area를 이용
df.interpolate(method='linear', limit_direction='both', limit_area='outside')

9-3. isna, isnull, notna, notnull

isna 또는 isnull은 입력 객체의 원소가 NA인 자리가 True고 나머지가 False인 입력 객체 크기와 동일한 boolean 객체를 리턴한다. 즉, masking 역할의 객체를 생성한다. notna 또는 notnull은 isna와 반대 역할이다.

# 예제 데이터
data = [[np.nan, 1,      2, np.nan],
        [np.nan, 0, np.nan,      1],
        [np.nan, 0, np.nan,      3],
        [     1, 2,      3,      4]]
df = pd.DataFrame(data, index=list('abcd'), columns=list('wxyz'))
se = pd.Series([np.nan, 1, 2, np.nan], index=list('abcd'))


"""<df|se|ix>.isna()
    - isnull은 isna의 alias
    - 원소가 NA인 자리가 True인 boolean 객체를 리턴
"""
df.isna()
se.isna()


"""<df|se|ix>.notna()
    - notnull은 notna의 alias
    - 원소가 NA인 자리가 False인 boolean 객체를 리턴
"""
df.notna()
se.notna()

10. 유용한 메서드

여기서는 통계 및 여러 계산에 사용되는 유용한 메서드들을 정리한다. 사용 방법이 간단하기 때문에 dataframe, series, index 각각에 포함되는 메서드인지만 표시하였다.

  • 기술 통계 및 요약
method 설명 dataframe series index
min 최소값 v v v
max 최대값 v v v
sum 총합 v v  
prod 총합 v v  
cummin 누적 최소값 v v  
cummax 누적 최대값 v v  
cumsum 누적 총합 v v  
cumprod 누적 총곱 v v  
mad mean abs. dev. v v  
mean 평균 v v  
median 중위수 v v  
std 표준 편차 v v  
var 분산 v v  
argmin 최소값 정수 인덱스   v v
argmax 최대값 정수 인덱스   v v
idxmin 최소값 라벨 인덱스 v v  
idxmax 최대값 라벨 인덱스 v v  
count 원소 수 v v  
describe 기술통계 정보 v v  
diff 원소 간 차이값 v v  
  • 상관 및 공분산
method 설명 dataframe series index
corr correlation matrix v v  
corrwith pairwise correlation v    
autocorr pearson correlation   v  
cov covariance matrix v v  
kurt   v v  
pct_change percent change v v  
quantile 분위수 v v  
skew   v v  
  • 알아 둬야할 메서드
method 설명 pandas dataframe series index
all 행 또는 열의 모든 원소가 True면 True   v v v
any 행 또는 열의 모든 원소가 False면 False   v v v
value_counts nan이 아닌 값의 수 v   v v
isin 특정 값을 가지고 있으면 True   v v v
rename axis labels 바꾸기   v v v
rename_axis axis name 바꾸기   v v  
abs 절대값   v v  
nunique util_unique   v v v
unique unique 값 v   v v

11. 산술 연산

dataframe과 series의 산술 연산은 element-wise 연산이라기 보다는 label-wise 연산이다. 예를 들어 아래와 같이 index와 columns가 다른 두 series 또는 두 dataframe의 산술 연산을 해보면 label이 일치하는 부분만 계산되고 나머지는 NaN이 된다.

se1 = pd.Series([1, 2], index=['a', 'b'], name='x')
se2 = pd.Series([3, 4], index=['b', 'c'], name='y')
se1 + se2

df1 = pd.DataFrame([[1, 2], [3, 4]], index=['a', 'b'], columns=['x', 'y'])
df2 = pd.DataFrame([[5, 6], [7, 8]], index=['b', 'c'], columns=['y', 'z'])
df1 + df2

# dataframe과 series의 연산은 뭔가 의도하지 않은 결과가 나온다.
df1 + se1
# 아래와 같이 둘 다 dataframe으로 맞춰서 계산한다.
# 단, 열이 맞으려면 named series 여야 한다.
df1 + se1.to_frame()
df1 + se1.to_frame('y')

산술 연산자인 +, -, *, /, //, %, ** 모두 dataframe과 series의 연산에 사용할 수 있지만 label이 일치하지 않는 부분에 생기는 NaN을 처리할 수는 없다. 이러한 missing values를 처리하기 위해 dataframe과 series는 아래 표에 있는 산술 연산 관련 여러 메서드를 제공한다.

method reverse version 설명
add radd 더하기
sub rsub 빼기
mul rmul 곱하기
div rdiv 나누기
truediv rtruediv 소수 나누기
floordiv rfloordiv 정수 나누기
mod rmod 나누기의 나머지
pow rpow 지수곱
divmod rdivmod 몫과 나머지; series method
divide   truediv와 같음

사용법의 예는 아래와 같다.

"""df.add(other, axis='columns', level=None, fill_value=None)
"""
se1 = pd.Series([1, 2], index=['a', 'b'], name='x')
se2 = pd.Series([3, 4], index=['b', 'c'], name='y')
df1 = pd.DataFrame([[1, 2], [3, 4]], index=['a', 'b'], columns=['x', 'y'])
df2 = pd.DataFrame([[5, 6], [7, 8]], index=['b', 'c'], columns=['y', 'z'])

# fill_value=0는 어느 한쪽만 NaN인 원소를 0으로 채워서 계산한다.
# 따라서 df1과 df2에 모두 없는 원소면 NaN이 된다.
se1.add(se2, fill_value=0)
df1.add(df2, fill_value=0)

# axis 인수는 broadcasting과 관련된다.
df1.add([1, 2]) # default axis='columns'
df1.add([1, 2], axis='index')

12. 함수 적용

여기서는 pandas objects의 값들을 함수에 적용하는 메서드들에 대해 정리한다. 아래 표는 dataframe, series, index가 가진 함수 적용 관련 메서드이다.

method dataframe series index
apply v v  
transform v v  
aggregate (agg) v v  
applymap v    
map   v v
assign v    

아래는 dataframe의 apply, transform, aggregate 메서드의 signatures와 간단한 설명이다. 세 메서드 모두 지정한 axis 인수의 값에 따라 dataframe의 columns (axis=0인 경우; default) 또는 rows (axis=1인 경우)가 series 형태로 하나씩 func의 인수로 전달된다. 따라서 func에 scalar function을 지정할 수 없다. (예: 내장함수 int)

  • df.apply(func, axis=0, ...)
    • transform과 aggregate 메서드의 모든 기능을 포함한다.
  • df.transform(func, axis=0, ...)
    • output dataframe이 input dataframe와 크기가 같아야 한다. 따라서 func에 aggregation function을 지정할 수 없다.
  • df.aggregate(func, axis=0, ...)
    • 메서드의 이름대로 func에 aggregation function을 지정할 수 있다.

series 역시 apply, transform, aggregate 메소드를 가지지만, dataframe과는 달리 각각의 원소가 func에 전달된다. 따라서 func 인수에 scalar function을 지정할 수 있다. 각 원소가 func에 독립적으로 계산되기 때문에 aggregation이 안될 것 같지만 문제없이 aggregation이 된다. 자세한 예는 세부 내용을 다룰 때 볼 수 있다.

applymap 메서드는 dataframe의 각각의 원소가 지정한 함수에 전달되어 처리된다. 따라서 scalar function을 지정할 수 있다.

map 메서드는 series 또는 index의 각 원소가 지정한 함수에 전달된다는 점에서 series의 apply, transform, aggregate 메소드와 같지만 series의 값을 선택적으로 바꿀 수 있는 mapper 사용과 NA를 처리하는 기능이 있다는 점에서 차이가 있다.

assign 메서드는 dataframe에 새로운 column들을 추가하는데 사용한다. 이 때 새로운 열의 값은 지정한 함수에 의해 계산될 수 있고, series나 다른 형태의 산술 연산으로 계산 될 수 있다.

12-1. dataframe.apply

dataframe.apply 메서드는 지정한 axis에 따라 dataframe의 columns 또는 rows가 하나씩 func으로 전달된다. 아래 예제를 통해 func 인수에 지정하는 형태가 어떻게 되는지 주의 깊게 볼 필요가 있다.

"""df.apply(func, axis=0, raw=False, result_type=None, args=(), **kwds)

    - axis=0 일때, df 각각의 column이 df.index를 index로 하는 series로 전달
    - axis=1 일때, df 각각의 row가 df.columns를 index로 하는 series로 전달
    - 전달된 모든 series는 func(series) 평가
    - 리턴의 형태는 func으로 처리되는 결과에 따라 series 또는 dataframe
    - func: callable, numpy ufunc, df method string, list, dict
    - axis: 0, 1, index, columns
    - raw: False, True; True면 series대신 series.values가 전달
    - result_type: expand, reduce, broadcast
    - args, **kwds: func에 추가적으로 들어갈 인수들을 전달할 때 사용
"""
df = pd.DataFrame(np.arange(9).reshape(3, 3), 
                  index=list('abc'), columns=list('xyz'))

# func 인수
# -----------------------------------------------------------------------

# func=callable (series를 처리할 수 있어야 함)
dev = lambda s: s - s.mean()
df.apply(dev)
df.apply(dev).apply(abs)

# func=numpy.ufunc
df.apply(np.sqrt)
df.apply(np.sum) # reduction

# func=str 방식은 df의 메서드를 문자열로 지정
# 따라서 df 메서드로 있는 것만 지정할 수 있음
df.apply('sum')     # df.sum() 과 같음
df.apply('cumsum')  # df.cumsum() 과 같음

# func=list 방식으로 여러 func을 한꺼번에 지정할 수 있음
# 생성되는 index 또는 column labels은 지정한 func의 __name__ 이 됨
df.apply(['sum', 'prod', 'min', 'max'])
df.apply(['cumsum', 'cumprod'])
df.apply([np.sum, 'sum', lambda s: s.sum()])

# func=dict 방식은 column에 따라 다른 함수가 적용되도록 지정할 수 있음
df.apply({'x': 'cummin', 'y': 'cummax', 'z': 'cumsum'})
df.apply({'x': np.sqrt, 'y': dev})
df.apply({'a': 'min', 'b': 'max', 'c': 'sum'}, axis=1)
df.apply({'a': np.sum, 'b': dev}, axis=1)


# axis 인수
# -----------------------------------------------------------------------
# axis인수는 각각의 column이 func에 전달될 지, row가 전달될 지 지정
df.apply(np.sum) # default axis=0
df.apply(np.sum, axis=1)


# raw 인수
# -----------------------------------------------------------------------
# raw=True는 series.values (ndarray) 가 func에 전달
# func이 numpy reduction function인 경우 성능이 좋음
# 아래 두 코드를 ipython에서 %timeit 을 사용해 볼 것
df.apply(np.sum, raw=True)
df.apply(np.sum)


# result_type 인수
# -----------------------------------------------------------------------
# apply의 결과가 아래와 같이 tuple 형태인 경우
df.apply(lambda s: (s.sum(), s.mean()))
# result_type은 위와 같이 원소가 list-type 형태의 리턴을 바꿀 수 있음
df.apply(lambda s: (s.sum(), s.mean()), result_type='expand')

# 각 행에 대한 결과가 아래와 같이 열의 크기와 같은 list-like object 일때
df.apply(lambda s: [1, 2, 3], axis=1)
# result_type='expand'는 column labels을 보존하지 못한다.
df.apply(lambda s: [1, 2, 3], axis=1, result_type='expand')
# result_type='broadcast' 사용하면 column labels을 보존한다.
df.apply(lambda s: [1, 2, 3], axis=1, result_type='broadcast')


# args, **kwds 인수
# -----------------------------------------------------------------------
# 함수에 추가 인수를 입력할 때는 args=() 또는 **kwds 이용
f1 = lambda s, attr: getattr(s, attr)()
f2 = lambda s, i, j: s + i - j
df.apply(f1, args=('sum',)) # args=()
df.apply(f1, attr='sum')    # **kwds
df.apply(f2, args=(1, 2))   # args=()
df.apply(f2, i=1, j=2)      # **kwds

12-2. datafram.transform

dataframe.transform 메서드는 input dataframe과 output dataframe의 크기가 같도록 func을 지정해야 한다. func 인수와 axis 인수의 기능은 dataframe.apply와 동일하며 *args 인수와 **kwargs 인수는 func 인수에 지정한 함수의 추가적인 인수를 지정할 때 사용한다. (아마도 dataframe.apply 내부에서 dataframe.transform을 사용하는 것으로 보인다.)

"""df.transform(func, axis=0, *args, **kwargs)
    
    - output dataframe이 input dataframe과 동일한 크기가 되야한다는 
      조건을 제외하고 df.apply와 동일하다.
"""
df = pd.DataFrame(np.arange(9).reshape(3, 3), 
                  index=list('abc'), columns=list('xyz'))

# func 인수가 aggregation function이면 ValueError가 발생
# -----------------------------------------------------------------------
try:
    df.transform(lambda s: s.sum())
except ValueError:
    print('ValueError: s.sum')    


# 다른 모든 기능은 df.apply와 동일
# -----------------------------------------------------------------------
# output df 와 input df가 같도록 하는 func은 문제 없음
dev = lambda s: s - s.mean()
df.transform(dev)
df.transform(dev, axis=1)
df.transform(np.cumsum)  # numpy.ufunc
df.transform('cumsum')   # df method
df.transform([np.sqrt, 'cumsum', dev]) # list
df.transform({'x': np.sqrt, 'y': dev}) # dict

# func가 여러 인수의 입력을 받을 때는 args, kwargs를 이용한다.
f = lambda x, y, z: x + y + z
df.transform(f, y=1, z=2)

12-3. dataframe.aggregate

dataframe.aggregate 메서드는 transform 메서드 같이 input df와 output df가 크기가 일치해야 한다는 제한은 없다. (aggregate 메서드 역시 dataframe.apply 내부에서 사용하는 것으로 생각된다.)

"""df.aggregate(func, axis=0, *args, **kwargs)

    - df.transform과 마찬가지로 func 인수와 axis 인수의 기능은 df.apply와
      동일하며, *args, **kwargs 인수는 func 인수에 지정한 함수의 추가적인
      인수를 지정할 때 사용한다.
    - 메서드 이름이 aggregate이지만 df.aggregate의 경우 func 인수에 반드시
      aggregation function만 지정해야 하는 것은 아니다.
"""
df = pd.DataFrame(np.arange(9).reshape(3, 3), 
                  index=list('abc'), columns=list('xyz'))


# 다른 모든 기능은 df.apply와 동일
# -----------------------------------------------------------------------
dev = lambda s: s - s.mean()
df.agg(dev)
df.agg(dev, axis=1)
df.agg(np.cumsum)  # numpy.ufunc
df.agg('cumsum')   # df method
df.agg([np.sqrt, 'cumsum', dev]) # list
df.agg({'x': np.sqrt, 'y': dev}) # dict

# aggregation function
dev = lambda s: s.sum() - s.mean()
df.agg(dev)
df.agg(dev, axis=1)
df.agg(np.sum)  # numpy.ufunc
df.agg('sum')   # df method
df.agg([np.sum, 'sum', dev]) # list
df.agg({'a': np.sum, 'b': dev}, axis=1) # dict

# args, kwargs 인수
f = lambda s, y, z: s.sum() + y + z
df.transform(f, y=1, z=2)

12-4. series의 apply, transform, aggregate

series의 apply, transform, aggregate 메서드는 func에 series의 각 원소가 전달되지만, apply와 aggregate 메서드의 경우 func이 aggregation function이면 series 전체 원소의 reduction이 계산된다.

"""se.apply(func, convert_dtype=True, args=(), **kwds)
   se.transform(func, axis=0, *args, **kwargs)
   se.aggregate(func, axis=0, *args, **kwargs)
   
    - 각 원소가 func에 전달
    - transform은 input series와 output series의 크기가 같아야 함
    - aggregate 메서드는 func 인수에 지정한 함수가 rediction function이면
      series 전체 값이 aggregation 됨
    - apply 메서드도 aggregate 메서드와 마찬가지로 func에 지정된 함수에 
      따라 동작됨
"""
se = pd.Series([1, 2, 3], index=list('abc'), name='x')


# numpy.ufunc
se.apply(np.sqrt)
se.transform(np.sqrt)
se.agg(np.sqrt)

# aggregation function
se.apply('sum')
try:
    se.transform('sum')
except ValueError:
    print("ValueError: se.transform('sum')")    
se.agg('sum')    

# callable
se.apply(lambda x: x + 1)
se.transform(lambda x: x + 1)
se.agg(lambda x: x + 1)

# list
se.apply(['min', 'max', 'sum', 'prod'])
se.agg(['min', 'max', 'sum', 'prod'])

# 함수에 추가 인수 전달
f = lambda val, div: (val//div, val%div)
se.apply(f, args=(2,))
se.transform(f, div=2)
se.agg(f, div=2)

12-5. applymap

applymap은 dataframe의 element-wise apply라고 생각할 수 있다. 각각의 원소가 지정한 함수로 전달된다.

"""df.applymap(func) -> dataframe

    - df.applymap은 element-wise로 함수 적용
"""
df = pd.DataFrame([[1, -2.12], [3.356, -4.567]])
df.applymap(int)
df.applymap(abs)
df.applymap('{:0<+6.3}'.format)

12-6. map

map은 dataframe에는 없고, series, index의 메서드다. series.map 역시 series.apply와 마찬가지로 element-wise 연산이지만 apply 메서드와 구별되는 차이는 mapper를 사용해서 값을 변경할 수 있다는 것이다.

"""<se|ix>.map(arg, na_action=None)
   
    - arg는 func 또는 mapper; mapper는 dictionary 또는 series
    - na_action=None or 'ignore'
      na_action='ignore' 이면 se에 NaN이 있을때 arg를 적용하지 않고 NaN 처리
"""

# arg가 mapper인 경우
# ------------------------------------------------------------------------
# se.apply와 구분되는 se.map의 장점은 mapper로 각 원소값을 다른 값으로 변환
# 주의: index -> value mapping이 아닌 value -> value mapping 이라는 점이다. 
se = pd.Series(['i', 'j', np.nan, 'l'], index=list('abcd'))
# mapper가 dict인 경우 dict key가 원본 series의 value가 된다.
mapper = {'i':1, 'j':2, 'k':3, 'l':4, 'm':5, 'n':6}
se.map(mapper)
# mapper가 series인 경우 series index가 원본 series의 value가 된다.
mapper = pd.Series([1, 2, 3, 4, 5, 6], index=list('ijklmn'))
se.map(mapper)

# mapper에 원본 series의 value가 정의되어 있지 않으면 NaN으로 채워진다.
se.map({'i':1, 'j':2}) # se['d']가 l에서 NaN이 됨을 확인할 것
# 그러나 아래와 같이 __missing__ 메서드가 있는 mapper를 이용하면 디폴트값을
# 지정할 수 있다. 
class Mydict(dict):
    def __missing__(self, key):
        return 0
se.map(Mydict(i=1, j=2))    


# arg가 callable 인 경우
# ------------------------------------------------------------------------
# series.apply 보다 제한적이지만 비슷한 기능을 한다.
se.map(lambda x: 0 if pd.isnull(x) else x)


# na_action='ignore' 쓰임새
# ------------------------------------------------------------------------
# series의 각 원소를 두번 연속 연결해서 출력한다고 가정하면
se.map('{0}{0}'.format)
# 위 방식의 문제는 세 번째 원소인 NaN도 두번 연속 출력되는 것이다.
# 따라서 series 원소가 NaN인 경우는 무시할 필요가 있다.
# 이때 na_action='ignore'를 사용한다.
se.map('{0}{0}'.format, na_action='ignore')
# 또는 아래와 같이 함수가 nan을 처리 못하는 경우 bypass 시킬 수 있다.
se.map(str.upper, na_action='ignore')

12-7. assign

assign 메서드는 dataframe에 새로운 column들을 추가하는데 사용한다. df[k] = v 방식으로 column을 추가하는 대신 메서드로 column을 추가하기 때문에 임시 dataframe variable 만들지 않는 method chaining에 이용할 수 있다.

"""df.assign(**kwargs)

    - df에 새로운 열을 추가한 dataframe을 리턴
    - **kwargs에 label = callable or Series 형식으로 입력
    - **kwargs에 label = callable or Series 을 콤마로 분리해서
       한번에 여러 열 추가 
"""
# 예제 데이터
df = pd.DataFrame(np.arange(1, 7).reshape(3, 2), 
                  index=list('abc'), columns=list('xy'))

# series를 입력하면 left join 효과가 있다. 
se = pd.Series([1, 2, 3], index=['b', 'c', 'd'], name='xx')
df.assign(xx = se)
df.join(se, how='left')

# expression 방식으로 사용할 수 있다.
df.assign(xx = df['x'] + df['y'])

# callable 방식으로 사용하면 callable에 전달되는 인수는 self다.
# 즉, df가 전달된다.
df.assign(xx = lambda tb: tb['x'] + tb['y'])

# **kwargs 이기 때문에 여러 열을 한번에 추가할 수 있다.
df.assign(xx = lambda tb: tb['x']**2,
          yy = lambda tb: tb['y']**2,
          zz = se,
          ww = df['x'] + df['y'])

13. 그룹 연산

여기서는 groupby 메서드의 사용법 및 응용에 대해 정리한다.

13-1. groupby 기본 사용법

groupby 메서드의 기본 사용법과 동작을 정리한다.

"""df.groupby(by=None, axis=0, level=None, as_index=True, sort=True,
              group_keys=True, squeeze=False, observed=False)

    - 입력한 by에 따라 group정보가 저장된 groupby object 리턴
    - by: mapping (dict or series), function, label, list of labels, ndarray
"""

df = pd.DataFrame({'key1' : ['a', 'a', 'b', 'b', 'a'],
                   'key2' : ['one', 'two', 'one', 'two', 'one'],
                   'data1' : np.random.randn(5), 
                   'data2' : np.random.randn(5)})

# by=label
# ----------------------------------------------------------------------
# groupby 메서드는 groupby 객체를 리턴
grouped = df.groupby('key1')
type(grouped)

# groupby 객체는 by에 따른 묶음 정보
list(grouped) # list of tuples
dict(list(grouped))

# groupby는 주로 group으로 aggregation을 할 때 사용
# groupby는 dataframe이 가진 여러 메서드를 가지고 있음
grouped.mean()
grouped.sum()
grouped.size()

# groupby 객체의 aggregation 메서드 사용 예; 모두 같은 결과
grouped.sum() + 1       # dataframe.sum 메서드
grouped.agg(sum) + 1    # built-in sum
grouped.agg('sum') + 1  # dataframe.sum 메서드
grouped.agg(lambda s: s.sum() + 1) # callable

# syntactic sugar: data1 열에 대해서만 aggregation 하려면?
grouped['data1'].mean()
# 위 표현은 아래와 같다. 
df['data1'].groupby(df['key1']).mean()


# by=list of labels
# ----------------------------------------------------------------------
# 여러 기준으로 grouping을 할 수 있다.
grouped = df.groupby(['key1', 'key2'])
dict(list(grouped))
grouped.mean()
grouped.size()


# by=function
# ----------------------------------------------------------------------
# by가 function이면 self.index의 value가 각각 function으로 전달되어
# grouping key가 된다.
df.set_index('key2').groupby(lambda x: x.upper()).mean()


# by가 dict 또는 series 같은 mapping인 경우
# ----------------------------------------------------------------------
# self의 index에 mapping되는 dict나 series를 정의해 주면 된다.
# 따라서 아래와 같이 dict로 group을 정해줄 수 있다.
# mapping 이니까 당연히 mapper와 df.index의 크기는 달라도 된다. 
mapper = dict(zip(range(8), list('ABCABABC')))
df.groupby(mapper).mean()

# series도 같은 방식
mapper = pd.Series(list('ABCABABC'))
df.groupby(mapper).mean()


# by가 ndarray 또는 list-like 객체면 df.index와 크기가 일치해야 함
# ----------------------------------------------------------------------
df.groupby(list('ABABA')).mean()


# hierarchical index인 dataframe의 경우
# ----------------------------------------------------------------------
ix = pd.MultiIndex.from_arrays([['a', 'a', 'b', 'b', 'a'],
                                ['one', 'two', 'one', 'two', 'one']],
                               names=['key1', 'key2'])
df = pd.DataFrame({'data1' : np.random.randn(5), 
                   'data2' : np.random.randn(5)}, index=ix)

# level 인수로 group을 나눌 수 있음
df.groupby(level=0).mean()
df.groupby(level=1).mean()
df.groupby(level=[0, 1]).mean()

# axis=1은 column 기준으로 grouping
df.T.groupby(level=1, axis=1).mean()

# as_index=False 는 output의 index 무시
df.groupby(level=['key1', 'key2'], as_index=False).mean()
# record format이면 group 기준을 남길 수 있다.
df.reset_index().groupby(['key1', 'key2'], as_index=False).mean()


# groupby 객체는 iterable
# ----------------------------------------------------------------------
for name, group in df.groupby(level='key1'):
    print('group name: {}'.format(name))
    print(group, end='\n\n')

for names, group in df.groupby(level=['key1', 'key2']):
    print('group name: ({0[0]}, {0[1]})'.format(names))
    print(group, end='\n\n')

13-2. groupby.apply

groupby 객체는 dataframe과 series가 가진 여러 메서드를 사용할 수 있지만 동작이 약간씩 다르다. 예를 들어 dataframe.apply의 경우 지정한 axis에 따라 column-wise 또는 row-wise로 처리되고, series.apply의 경우 element-wise로 처리지만 groupby.apply는 group-wise로 처리된다. 예제를 통해 동작을 확인해 보자.

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]})


# dataframe, series, groupby 객체의 apply(func) 으로 전달되는 객체의 type 확인
# ---------------------------------------------------------------------------
# dataframe은 column-wise series 전달
df.apply(type)
# series는 element-wise 원소 전달
se = df['area']
se.apply(type)
# dataframe.groupby는 group-wise dataframe 전달
df.groupby('area').apply(type)
# series.groupby는 group-wise series 전달
se.groupby(df['cond']).apply(type)


# groupby.apply signature
# ---------------------------------------------------------------------------
grouped = df.groupby('area')

"""grouped.apply(func, *args, **kwargs)

    - df.apply의 func 인수가 callable, str method name, list, dict 같은 여러
      형태를 처리하는 것과 달리 groupby.apply의 func 인수는 하나의 callable만
      허용된다. 입력한 callable은 첫 번째 인수로 grouping된 객체를 전달받는데
      dataframe의 grouping은 dataframe을 전달 받고, series의 grouping series를
      전달 받는다.
    - *args, **kwargs: func에 입력한 callable의 추가 인수를 전달하는데 사용
"""
grouped.apply(lambda gr, colname: gr[colname].sum(), colname='expr')


# group_keys=False
# ---------------------------------------------------------------------------
# apply 메서드를 사용할 때 group을 나룰때 사용한 key를 나타나지 않게 한다.
df.groupby('area').apply(lambda gr: gr.describe())
df.groupby('area', group_keys=False).apply(lambda gr: gr.describe())


# groupby.apply는 split-apply-combine 전략으로 알려진 기법에 이용
# ---------------------------------------------------------------------------
# 'area' 열로 group을 나눈 후, 각 group을 expr로 정렬한 다음 combine
df.groupby('area').apply(lambda gr, by: gr.sort_values(by), by='expr')

# 문자열 column들을 index로 만들고, index 'cond'로 group을 나눈 후, 
# 각 group의 수치들을 지정한 func으로 처리하고 combine
grouped = df.set_index(['area', 'cond', 'is']).groupby(level='cond')
grouped.apply(lambda gr, ifunc: gr.apply(ifunc), ifunc=np.sum)

# 'is' 열로 group을 나눈 후, 각 group의 세부 기술 통계를 계산 및 combine
df.groupby(['is']).apply(lambda gr: gr.describe())

# 'area', 'is' 열로 group을 나눈 후, 각 group의 describe 및 combine
df.groupby(['area', 'is']).apply(lambda gr: gr.describe())


# split-apply-combine 또 다른 예제
# ---------------------------------------------------------------------------
# Python for Data Analysis - Wes McKinney의 10장을 참고할 것
# 참고: https://gist.github.com/ranfort77/de93365b9cbd35ba4241c57621b71fa2


# group name
# ---------------------------------------------------------------------------
# group name은 group.name 으로 접근할 수 있다.
df.groupby('area').apply(lambda gr: gr.name)

13-3. groupby.agg

groupby.apply와 마찬가지로 groupby.agg 역시 dataframe.agg와 약간 다르지만, aggregation을 위해 여러 기능이 준비되어 있다. 또 하나 주의해서 알아둘 점은 dataframe.groupby.apply는 group-wise dataframe이 전달되지만, dataframe.groupby.agg는 group-wise dataframe 안의 각각의 column이 series로 전달된다. (각각의 column 별로 aggregation을 위해) 예제를 통해 groupby.agg 의 동작을 확인해 보자.

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]})

# groupby.agg로 무엇이 전달되는지 groupby.apply와 비교
# ---------------------------------------------------------------------------
# dataframe.groupby.apply는 group-wise dataframe 전달
df.groupby('area').apply(type)
# dataframe.groupby.agg는 group-wise dataframe 각각의 column series가 전달
df.groupby('area').agg(type)
# series의 경우 groupby.apply와 groupby.agg가 동일하게 group-wise series 전달
se = df['area']
se.groupby(df['cond']).apply(type)
se.groupby(df['cond']).agg(type)


# groupby.agg signature
# ---------------------------------------------------------------------------
grouped = df.groupby('area')

"""grouped.agg(func, *args, **kwargs)

    - grouped.agg의 func 인수는 역시 df.agg와 동일하게 callable, 
      numpy unfunc, method string, list, dict 형태의 입력을 처리할 수 있음
"""
grouped.agg(lambda x: x.sum())
grouped.agg(np.sum)
grouped.agg('sum')
grouped.agg([lambda x: x.sum(), np.sum, 'sum'])
grouped.agg({'expr': 'sum', 'pred': np.sum, 'size': 'prod'})


# named aggregation
# ---------------------------------------------------------------------------
# groupby.agg는 named aggregation을 지원한다. 아래와 같이 grouped.agg 의
# 인수로 newlabel=(column_label, func), ... 형식으로 입력하면
# output column label을 변경할 수 있다.
grouped.agg(expr_sum=('expr', 'sum'), expr_prod=('expr', 'prod'),
            pred_sum=('pred', 'sum'), pred_prod=('pred', 'prod'))

# 위 방법을 더 명시적으로 표시하도록 namedtuple을 만드는 pd.NamedAgg를 
# 사용할 수 있다. 아래는 위 표현을 pd.NamedAgg를 사용하여 작성한 것이다.
grouped.agg(expr_sum=pd.NamedAgg(column='expr', aggfunc='sum'), 
            expr_prod=pd.NamedAgg(column='expr', aggfunc='prod'), 
            pred_sum=pd.NamedAgg(column='pred', aggfunc='sum'), 
            pred_prod=pd.NamedAgg(column='pred', aggfunc='prod'))

13-4. groupby.transform

groupby.transform 역시 df.transform과는 조금 차이가 있다. 예제를 통해 확인해 보자.

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]})

"""grouped.transform(func, *args, **kwargs)
    
    - 원본 group과 같은 index가 유지되도록 output의 index를 만들어 리턴
    - df.transform과는 달리 aggregation function 리턴을 broadcast하여
      원본 group들과 크기를 유지
"""
grouped = df.groupby('area')

# groupby.transform 은 groupby.agg와 마찬가지로 
# group-wise dataframe 각각의 column series를 전달
grouped.transform(type)

# df.transform에서 사용하는 func 지정 형식 중, callable, numpy unfunc
# method string 등은 지원하지만, list, dict는 지원하지 않는다.
dev = lambda s: s - s.mean()
grouped.transform(dev)
grouped.transform('cumsum')
grouped.transform(np.cumsum)
# grouped.transform([np.sqrt, 'cumsum', dev])
# grouped.transform({'expr': np.sqrt, 'pred': dev, 'size': 'cumsum'})

# df.transform과는 달리 aggregation function을 지원하는데,
# 결과는 원본 group과 크기가 같게되도록 broadcast된다.
grouped.transform(np.prod)  # broadcast

groupby.transform이 groupby.apply와 구분되는 점은 built-in aggregation function을 사용할 수 있다는 점과 unwrapped group operation이 가능하다는 점이다. Wes McKinney의 Python for Data Analysis 2nd ed.의 373 page 설명을 참고하자. 아래 예제를 통해 groupby.transform의 쓰임새를 확인한다.

# key 열에 있는 group 별로 normalize 하는 예제
df = pd.DataFrame({'key': ['a', 'b', 'c'] * 4,
                   'value': np.arange(12.)})
grouped = df.groupby('key')['value']
def normalize(x): return (x - x.mean()) / x.std()

# 위 grouped에 대해서 normalize 하는 방법은 아래와 같이 두 가지 방법이 있다.
grouped.transform(normalize)
grouped.apply(normalize)

# unwrapped group operation을 이용하면 위와 같이 callable을 거치지 않아도 된다.
# mean과 std는 pandas built-in aggregation function이기 때문에 
# grouped.transform 메서드를 이용하면 아래와 같은 방법도 가능하다.
# 좀 더 직관적이다.
(df['value'] - grouped.transform('mean')) / grouped.transform('std')

# groupby.transform에 aggregation function이 전달될 때, 각 group 별로
# broadcast 된다는 특징 때문에 아래와 같이 직관적인 표현이 가능하다.
demeaned = df['value'] - grouped.transform('mean')
# 아래와 같이 groupby.apply는 input의 크기가 유지되지 않는다는 점에서
# 위와 같은 연산을 할 수 없다.
grouped.apply(np.mean)

13-5. pivot_table

dataframe reshape에 대한 정리에서 이미 다루었지만 dataframe의 pivot_table 메서드는 group aggregation 기능이 있다. 예제를 통해 groupby.agg와 비교해 보자.

"""olddf.pivot_table(values=None, index=None, columns=None,
                     aggfunc='mean', fill_value=None, margins=False,
                     dropna=True, margins_name='All', observed=False) -> newdf
                     
    - values, index, columns에 여러 label list를 입력할 수 있다.
    - aggfunc은 index와 columns에 의해 aggregation되는 원소들이 적용되는 함수
    - fill_value는 missing data를 채울 값을 지정
"""

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]})

value_vars = ['expr', 'pred', 'size']

# pivot_table과 groupby.agg가 동일한 결과를 주도록 짝을지어 작성해 보았다.
df.pivot_table(values=value_vars, index='area', aggfunc='mean')
df.groupby('area')[value_vars].agg('mean')

# grouping key가 둘 이상인 경우
df.pivot_table(values=value_vars, index=['area', 'is'], aggfunc='mean')
df.groupby(['area', 'is'])[value_vars].agg('mean')

# 여러 aggregation function을 지정하는 경우
df.pivot_table(values=value_vars, index='area', aggfunc=['mean', 'sum'])
df.groupby(['area'])[value_vars].agg(['mean', 'sum'])
df.groupby(['area'])[value_vars].agg(['mean', 'sum']).swaplevel(axis=1).sort_index(axis=1)

# value variable에 따라 다른 aggregation function을 적용하는 경우
df.pivot_table(values=['expr', 'pred'], index='area', 
               aggfunc={'expr': 'mean', 'pred': np.std})
df.groupby('area')['expr', 'pred'].agg({'expr': 'mean', 'pred': np.std})


# pivot_table의 columns 인수는 variable을 저장한 column을 column labels로
# 만드는 기능이 있다. (pivot과 같이)
df.pivot_table(values=value_vars, index='area', columns='is', aggfunc='mean')
# 이러한 기능은 groupby.agg를 한 후, unstack을 이용하면 똑같이 구현할 수 있다.
df.groupby(['area', 'is'])[value_vars].agg('mean').unstack('is')

13-6. crosstab

여기서는 pandas top-level function인 crosstab 함수를 소개한다. 이 함수는 어떤 group들이 등장한 회수를 세는데 주로 사용한다. 예제를 통해 쓰임새를 확인하자.

"""pd.crosstab(index, columns, values=None, rownames=None, colnames=None,
                     aggfunc=None, margins=False, margins_name='All', 
                     dropna=True, normalize=False)

    - index와 columns에 지정한 두 group key의 빈도수를 저장한 dataframe을 리턴
    - index, columns 인수는 반드시 지정
"""

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]})

# 위 dataframe의 group keys가 area와 is 일 때 group에 따른 빈도수는?
pd.crosstab(df['area'], df['is'])

# rownames, colnames 인수는 group keys name 지정
pd.crosstab(df['area'], df['is'], rownames=['row'], colnames=['col'])

# values 인수에 value_vars를 지정할 수 있는데, aggfunc과 함께 지정해야 함
pd.crosstab(df['area'], df['is'], values=df['expr'], aggfunc='mean')
# 위 결과는 아래와 같다.
df.groupby(['area', 'is'])['expr'].agg('mean').unstack('is')

# aggfunc에 여러 목록을 지정
pd.crosstab(df['area'], df['is'], values=df['expr'], aggfunc=['mean', 'std'])

Leave a comment