Skip to content

Instantly share code, notes, and snippets.

@ranfort77
Created March 22, 2020 12:03
Show Gist options
  • Save ranfort77/de93365b9cbd35ba4241c57621b71fa2 to your computer and use it in GitHub Desktop.
Save ranfort77/de93365b9cbd35ba4241c57621b71fa2 to your computer and use it in GitHub Desktop.
[Python for Data Analysis: 10. Data Aggregation and Group Operations] #book chapter summary: GroupBy, pivot_table, crosstab #pandas
#
# referencies
# [1] Python for Data Analysis - Wes McKinney
import numpy as np
import pandas as pd
#----------------------------------------------------------------------
# GroupBy Mechanics (Ref.[1] p.288)
#----------------------------------------------------------------------
#-- key1의 label을 사용하여 data1의 평균 계산
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)})
# 아래 Groupby object는 각각의 group에 대한 어떤 연산을 적용할 준비 상태
grouped = df['data1'].groupby(df['key1']) # GroupBy object
grouped.mean()
# 다중 index에 대해서도 쉽게 적용
means = df['data1'].groupby([df['key1'], df['key2']]).mean()
#-- group key가 list여도 된다.
states = np.array(['Ohio', 'California', 'California', 'Ohio', 'Ohio'])
years = np.array([2005, 2005, 2006, 2005, 2006])
df['data1'].groupby([states, years]).mean()
#-- DataFrame에 대해서 key를 DataFrame의 column name을 입력해도 된다.
df.groupby('key1').mean() # 주의: key2는 nuisance column (p.291 참고)
df.groupby(['key1', 'key2']).mean()
df.groupby(['key1', 'key2']).size()
# Iterating Over Groups
#----------------------------------------------------------
#-- GroupBy object는 iterable
for name, group in df.groupby('key1'):
print(name)
print(group)
# multiple keys
for (k1, k2), group in df.groupby(['key1', 'key2']):
print((k1, k2))
print(group)
# dict 형태
pieces = dict(list(df.groupby('key1')))
pieces['a']
pieces['b']
# 아래는 열방향 grouping을 할 수 있음을 보여주는데, df.dtypes가 작동하는 방식이
# 엄밀히 이해는 가지 않는다.
# df.dtypes의 values를 기반으로 grouping을 하기 때문인것 같기도 하다.
for dtype, group in df.groupby(df.dtypes, axis=1):
print(dtype)
print(group)
# Selecting a Column or Subset of Columns
#----------------------------------------------------------
#-- syntactic sugar
df.groupby('key1')['data1'] # df['data1'].groupby(df['key1'])
df.groupby('key1')[['data2']] # df[['data2']].groupby(df['key1'])
# data2에 대한 평균만 계산하려면?
df.groupby(['key1', 'key2'])[['data2']].mean() # DataFrame 리턴
df.groupby(['key1', 'key2'])['data2'].mean() # Series 리턴
# Grouping with Dicts and Series
#----------------------------------------------------------
#-- df.dtypes를 사용한 예제처럼 series로 groupby되는 경우를 살펴본다.
people = pd.DataFrame(np.random.randn(5, 5),
columns=['a', 'b', 'c', 'd', 'e'],
index=['Joe', 'Steve', 'Wes', 'Jim', 'Travis'])
people.iloc[2:3, [1, 2]] = np.nan
# dict가 group key로 사용되면 values로 group이 나눠지며 dict key는 DataFrame index
# 에 대응하는 key로 사용된다.
mapping = {'a': 'red', 'b': 'red', 'c': 'blue',
'd': 'blue', 'e': 'red', 'f' : 'orange'}
by_column = people.groupby(mapping, axis=1)
by_column.sum()
# dict는 쉽게 series로 변환되며, dict key가 index가 된다.
map_series = pd.Series(mapping)
people.groupby(map_series, axis=1).sum()
people.groupby(map_series, axis=1).count()
# map_series의 index f가 people에 없기 때문에 f에 대한 것은 group되지 않는다.
# Grouping with Functions
#----------------------------------------------------------
# groupby(function)을 적용하면 function(index)의 값이 group key가 된다.
people.groupby(len).sum()
# function과 index에 mapping되는 list를 같이 입력하면 function(index)와 list를
# 함께 고려한 결과가 나온다.
key_list = ['one', 'one', 'one', 'two', 'two']
people.groupby([len, key_list]).min()
people.groupby([len, key_list]).count()
# Grouping by Index Levels
#----------------------------------------------------------
# 이번 예제는 hierarchical index에 대한 level을 groupby key로 사용하는 것이다.
columns = pd.MultiIndex.from_arrays([['US', 'US', 'US', 'JP', 'JP'],
[1, 3, 5, 1, 3]], names=['cty', 'tenor'])
hier_df = pd.DataFrame(np.random.randn(4, 5), columns=columns)
hier_df.groupby(level='cty', axis=1).count()
#----------------------------------------------------------------------
# Data Aggregation (Ref.[1] p.296)
#----------------------------------------------------------------------
# 표 참고: Optimized groupby methods (Ref.[1] p.296)
#-- groupby methods에 없는 aggregation function은 series methods를 사용
grouped = df.groupby('key1')
grouped['data1'].quantile(0.9)
#-- groupby.agg 메소드: custom aggregation funtion 사용법
def peak_to_peak(arr):
return arr.max() - arr.min()
grouped.agg(peak_to_peak)
grouped['data1'].agg(peak_to_peak)
#-- groupby.describe
grouped.describe()
# Column-Wise and Multiple Function Application
#----------------------------------------------------------
# agg 메소드는 string, list, dict, tuple 같이 여러가지 방식의 입력을 받는다.
# 그에 따라 결과가 어떻게 바뀌는지 확인
#-- agg 메소드에 문자열로 함수명을 전달
tips = pd.read_csv('examples/tips.csv')
tips['tip_pct'] = tips['tip'] / tips['total_bill'] # tip percentage
grouped = tips.groupby(['day', 'smoker'])
grouped_pct = grouped['tip_pct']
grouped_pct.agg('mean')
# 함수들을 리스트로 한꺼번에 전달
grouped_pct.agg(['mean', 'std', peak_to_peak])
#-- (name, function) 형태로 함수를 전달하면 column label이 name이 된다.
# 이것은 __name__이 '<lambda>' 인 lambda 함수에 유용
temp = lambda arr: arr.max() - arr.min()
grouped_pct.agg([('foo', 'mean'), ('bar', np.std), ('lambda', temp)])
#-- column을 여러가지 입력할 수 있고, 각 column에 대해 여러 functions 적용할 수 있다.
functions = ['count', 'mean', 'max'] # function list
result = grouped['tip_pct', 'total_bill'].agg(functions)
#-- 입력 column 당, 서로 다른 functions을 적용할 수 있다.
grouped.agg({'tip' : np.max, 'size' : 'sum'}) # function dict
grouped.agg({'tip_pct' : ['min', 'max', 'mean', 'std'], 'size' : 'sum'})
# Returning Aggregated Data Without Row Indexes
#----------------------------------------------------------
#-- as_index=False는 groupby key가 column으로 보존된다.
tips.groupby(['day', 'smoker'], as_index=False).mean()
# 위 동작은 아래 동작과 같다.
result = tips.groupby(['day', 'smoker']).mean()
result.reset_index()
#----------------------------------------------------------------------
# Apply: General split-apply-combine (Ref.[1] p.302)
#----------------------------------------------------------------------
# 여기서는 groupby.apply 메소드를 사용하는 여러 예제를 보여준다.
#-- groupby.apply(function)이 어떻게 동작하는지 확인할 것
# 아래 함수는 groupby.apply에 적용되는 함수를 정의한다. 리턴값은
# pandas object이거나 scalar여야 한다. (p.303 참고)
def top(df, n=5, column='tip_pct'):
return df.sort_values(by=column)[-n:]
# 단순히 아래처럼 함수 호출을 할 의도로 사용할 수 있다.
top(tips, n=6)
# 아래처럼 group들이 top에 적용된 결과를 확인하라.
tips.groupby('smoker').apply(top)
# top의 인수는 list로 입력할 수도 있다. 굉장히 유연하다.
tips.groupby(['smoker', 'day']).apply(top, n=1, column='total_bill')
#-- groupby shortcut
# 아래 동작을 groupby.apply로 적용하면
result = tips.groupby('smoker')['tip_pct'].describe()
result.unstack('smoker')
# 아래와 같다. 결과 index가 좀 다르다. 두 동작이 정확히 어떻게 작동하는지
# 좀 더 엄밀하게 판단할 필요가 있다.
f = lambda x: x.describe()
tips.groupby('smoker')['tip_pct'].apply(f)
# Suppressing the Group Keys
#----------------------------------------------------------
#-- group_keys=False: 아래 세 코드라인의 미세한 차이를 확인할 것
tips.groupby('smoker').apply(top)
tips.groupby('smoker', group_keys=False).apply(top)
tips.groupby('smoker', as_index=False).apply(top)
# Quantile and Bucket Analysis
#----------------------------------------------------------
#-- cut, groupby의 응용: equal-length buckets
frame = pd.DataFrame({'data1': np.random.randn(1000),
'data2': np.random.randn(1000)})
quartiles = pd.cut(frame.data1, 4)
def get_stats(group):
return {'min': group.min(), 'max': group.max(),
'count': group.count(), 'mean': group.mean()}
grouped = frame.data2.groupby(quartiles)
grouped.apply(get_stats).unstack()
#-- qcut, groupby의 응용: equal-size buckets
grouping = pd.qcut(frame.data1, 10, labels=False)
grouped = frame.data2.groupby(grouping)
grouped.apply(get_stats).unstack()
# Example: Filling Missing Values with Group-Specific Values
#----------------------------------------------------------
#-- group에 따라 NA 값을 채우는 예제
states = ['Ohio', 'New York', 'Vermont', 'Florida',
'Oregon', 'Nevada', 'California', 'Idaho']
group_key = ['East'] * 4 + ['West'] * 4
# 일부러 NA값을 만든다.
data = pd.Series(np.random.randn(8), index=states)
data[['Vermont', 'Nevada', 'Idaho']] = np.nan
fill_mean = lambda g: g.fillna(g.mean())
# group에 따라 평균으로 채워진다.
data.groupby(group_key).apply(fill_mean)
#-- group에 따라 특정값으로 채워기
fill_values = {'East': 0.5, 'West': -1}
fill_func = lambda g: g.fillna(fill_values[g.name])
data.groupby(group_key).apply(fill_func)
# Example: Random Sampling and Permutation
#----------------------------------------------------------
#-- 본 예제는 트럼프 카드를 만들고 랜덤 선택하는 것을 다룬다.
suits = ['H', 'S', 'C', 'D']
card_val = (list(range(1, 11)) + [10] * 3) * 4
base_names = ['A'] + list(range(2, 11)) + ['J', 'K', 'Q']
cards = []
for suit in ['H', 'S', 'C', 'D']:
cards.extend(str(num) + suit for num in base_names)
# 카드 52장을 deck으로 만들었다.
deck = pd.Series(card_val, index=cards)
# 아래 함수는 입력된 deck에서 랜덤하게 n개를 선택한다.
def draw(deck, n=5):
return deck.sample(n)
# 가지고 있는 카드 52장을 모양별로 groupping해서 그 중 랜덤하게 2장을 뽑는다.
get_suit = lambda card: card[-1] # 카드모양은 카드이름의 마지막 문자
deck.groupby(get_suit).apply(draw, n=2)
# Example: Group Weighted Average and Correlation
#----------------------------------------------------------
#-- 본 예제는 groupping을 한 후, DataFrame의 두 columns 사이의 연산 예제이다.
df = pd.DataFrame({'category': ['a', 'a', 'a', 'a', 'b', 'b', 'b', 'b'],
'data': np.random.randn(8), 'weights': np.random.rand(8)})
grouped = df.groupby('category')
get_wavg = lambda g: np.average(g['data'], weights=g['weights'])
grouped.apply(get_wavg)
#-- 본 예제 역시 grupping을 한 후, 두 columns의 연산 예제이다.
# 예제 데이터는 index가 date인 주식정보이고, columns은 AAPL, MSFT, XOM, SPX이다.
close_px = pd.read_csv('examples/stock_px_2.csv', parse_dates=True, index_col=0)
close_px.info() # NA 정보, dtype 정보, memory 정보를 볼 수 있다.
# time series 분석에서 등장하는 퍼센트 변화를 계산한 후, NA를 없앤다.
rets = close_px.pct_change().dropna()
# 보고 싶은 것은 년도별 퍼센트 변화이다. 그러기 위해 연도별 groupping을 한다.
get_year = lambda x: x.year # date object는 year attribute가 있다.
by_year = rets.groupby(get_year)
# 이때 각 group columns은 SPX column과 pairwise correlation을 계산한다.
spx_corr = lambda x: x.corrwith(x['SPX'])
by_year.apply(spx_corr)
# inter-column correlation 도 계산할 수 있다.
by_year.apply(lambda g: g['AAPL'].corr(g['MSFT']))
# Example: Group-Wise Linear Regression
#----------------------------------------------------------
#-- 본 예제는 group 별 linear regression을 수행한다.
# statsmodels ordinary least squares
import statsmodels.api as sm
def regress(data, yvar, xvars):
Y = data[yvar]
X = data[xvars]
X['intercept'] = 1.
result = sm.OLS(Y, X).fit()
return result.params
by_year.apply(regress, 'AAPL', ['SPX'])
#----------------------------------------------------------------------
# Pivot Tables and Cross-Tabulation (Ref.[1] p.313)
#----------------------------------------------------------------------
# 표 참고: pivot_table options
#-- 본 예제에서는 pivot_table에 대한 여러 기능을 설명하는데
# 본문에서 의도한 내용을 정확히 이해하지 못했다. pivot_table의 기능을 좀 더
# 확실히 이해할 필요가 있다.
tips.pivot_table(index=['day', 'smoker'])
tips.pivot_table(['tip_pct', 'size'], index=['time', 'day'], columns='smoker')
# margins=True
tips.pivot_table(['tip_pct', 'size'], index=['time', 'day'], columns='smoker',
margins=True)
# aggfunc=len
tips.pivot_table('tip_pct', index=['time', 'smoker'], columns='day',
aggfunc=len, margins=True)
# fill_value
tips.pivot_table('tip_pct', index=['time', 'size', 'smoker'], columns='day',
aggfunc='mean', fill_value=0)
# Cross-Tabulations: Crosstab
#----------------------------------------------------------
# crosstab은 group frequencies 계산을 위한 pivot table의 special case이다.
s = np.arange(1, 11)
n = pd.Series([0, 1, 0, 1, 1, 1, 0, 0, 1, 0]).map({0:'USA', 1:'Japan'})
h = pd.Series([0, 1, 0, 0, 1, 0, 0, 1, 0, 0]).map({0:'Right-handed', 1:'Left-handed'})
data = pd.DataFrame(dict(Sample=s, Nationality=n, Handedness=h))
pd.crosstab(data.Nationality, data.Handedness, margins=True)
pd.crosstab([tips.time, tips.day], tips.smoker, margins=True)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment