Created
March 22, 2020 12:03
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# 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