(9주차) 11월1일
groupby, tidydata, barplot, 해들리위컴의 그래프레이어, 심슨의역설
- (1/12) group by (1)
- (2/12) group by (2)
- (3/12) pd.cut (1)
- (4/12) pd.cut (2)
- (5/12) tidydata (1)
- (6/12) tidydata (2)
- (7/12) tidydata (3)
- (8/12) Barplot + 해들리위컴 그래프레이어 (1)
- (9/12) Barplot + 해들리위컴 그래프레이어 (2)
- (10/12) Barplot + 해들리위컴 그래프레이어 (2)
- (11/12) 심슨의 역설 (1)
- (12/12) 심슨의 역설 (2)
import pandas as pd
import numpy as np
df=pd.read_csv('https://raw.githubusercontent.com/PacktPublishing/Pandas-Cookbook/master/data/flights.csv')
df
df.columns
- 데이터프레임을 여러개의 서브데이터프레임으로 나누는 기능
- 단독으로 쓸 이유는 별로 없다. $\to$ 그룹을 나누고 어떠한 "연산"을 하기 위함
df.groupby(by='AIRLINE')
- 데이터프레임을 각 항공사 별로 나눔
- 확인
grouped_df = df.groupby(by='AIRLINE')
grouped_df.groups
- 너무 보기 힘듬
- 보기좋은 형태로 확인
list(grouped_df.groups)
grouped_df.get_group('AA')
for g in grouped_df.groups:
print(g)
display(grouped_df.get_group(g))
- 방법1
df.groupby(by='AIRLINE').agg({'ARR_DELAY':'mean'})
- 방법2 ($\star\star\star$)
df.groupby(by='AIRLINE').agg({'ARR_DELAY':np.mean})
- 방법3
df.groupby(by='AIRLINE')['ARR_DELAY'].agg('mean')
- 방법4 ($\star$)
df.groupby(by='AIRLINE')['ARR_DELAY'].agg(np.mean)
- 방법5
df.groupby(by='AIRLINE')['ARR_DELAY'].mean()
- 방법2와 방법4는 사용자정의 함수를 쓸 수 있다는 장점이 있음
def f(x): return -np.mean(x)
df.groupby(by='AIRLINE').agg({'ARR_DELAY':f})
df.groupby(by='AIRLINE').agg({'ARR_DELAY':lambda x: -np.mean(x)})
df.groupby(by='AIRLINE')['ARR_DELAY'].agg(lambda x: -np.mean(x))
- 입력이 여러개인 사용자 정의 함수도 사용가능함
def f(x,y): return np.mean(x)**y
df.groupby(by='AIRLINE')['ARR_DELAY'].agg(f,2)
df.groupby(by='AIRLINE').agg({'ARR_DELAY': lambda x: f(x,2)})
- 방법1~5
df.groupby(by=['AIRLINE','WEEKDAY']).agg({'CANCELLED':'sum'})
df.groupby(by=['AIRLINE','WEEKDAY']).agg({'CANCELLED':np.sum})
df.groupby(by=['AIRLINE','WEEKDAY'])['CANCELLED'].agg('sum')
df.groupby(by=['AIRLINE','WEEKDAY'])['CANCELLED'].agg(np.sum)
df.groupby(by=['AIRLINE','WEEKDAY'])['CANCELLED'].sum()
- 방법 1~4 (5번은 쓸 수 없다)
df.groupby(by=['AIRLINE','WEEKDAY']).agg({'CANCELLED':['sum','mean'],'DIVERTED':['sum','mean']})
df.groupby(by=['AIRLINE','WEEKDAY']).agg({'CANCELLED':[np.sum,np.mean],'DIVERTED':[np.sum,np.mean]})
df.groupby(by=['AIRLINE','WEEKDAY'])[['CANCELLED','DIVERTED']].agg(['sum','mean'])
df.groupby(by=['AIRLINE','WEEKDAY'])[['CANCELLED','DIVERTED']].agg([np.sum,np.mean])
df.groupby(by=['AIRLINE','WEEKDAY']).agg({'CANCELLED':['sum','mean','size'],'AIR_TIME':['mean','var']})
df.groupby(by=['AIRLINE','WEEKDAY'])\
.agg({'CANCELLED':[np.sum,np.mean,len],'AIR_TIME':[np.mean,lambda x: np.std(x,ddof=1)**2]})
df
- 목표: DIST를 적당한 구간으로 나누어 카테고리화 하고 그것을 바탕으로 groupby를 수행하자.
df.DIST.hist()
df.DIST.describe()
- 구간을 아래와 같이 설정한다.
bins=[-np.inf, 400, 700, 1200, np.inf]
- pd.cut()을 이용하여 각 구간의 obs를 카테고리화 하자.
cuts=pd.cut(df.DIST,bins=bins)
cuts
- cuts, AIRLINE $\to$ {DIVERTED: sum}
df.groupby([cuts,'AIRLINE']).agg({'DIVERTED':sum})
- 아래와 비교해보자.
df.groupby(['AIRLINE']).agg({'AIRLINE':len})
- cuts을 이용하여 추가그룹핑을 하면 조금 다른 특징들을 데이터에서 발견할 수 있다.
- AA항공사와 DL항공사는 모두 비슷한 우회횟수를 가지고 있음.
- AA항공사는 700회이상의 구간에서 우회를 많이하고 DL항공사는 400~700사이에서 우회를 많이 한다. (패턴이 다름)
- 구간이름에 label을 붙이는 방법
bins
cuts2=pd.cut(df.DIST,bins=bins,labels=['Q1','Q2','Q3','Q4'])
cuts2
df.groupby(by=[cuts2,'AIRLINE']).agg({'DIVERTED':sum})
df.groupby(cuts2).agg({'DIVERTED':len})
import pandas as pd
import numpy as np
from plotnine import *
import matplotlib.pyplot as plt
- 느낌: ggplot으로 그림 그리기 좋은 데이터 + pandas로 query, group by 등을 쓰기 좋은 자료
- 정의: https://r4ds.had.co.nz/tidy-data.html
- Each variable must have its own column.
- Each observation must have its own row.
- Each value must have its own cell.
예시1
| obs | x | y | shape | color |
|---|---|---|---|---|
| 0 | 0 | 0 | 'star' | 'F' |
| 1 | 0 | 1 | 'circ' | 'F' |
| 2 | 1 | 0 | 'star' | 'M' |
| 3 | 1 | 1 | 'circ' | 'M' |
예시2
| shape=star | shape=circ | |
|---|---|---|
| color=F | (0,0) | (0,1) |
| color=M | (1,0) | (1,1) |
- 문제의 깃헙주소로 들어가서 데이터를 관찰 $\to$ 좌측상단이 비워져있음 $\to$ index_col=0 옵션을 사용
url = 'https://raw.githubusercontent.com/PacktPublishing/Pandas-Cookbook/master/data/state_fruit.csv'
df=pd.read_csv(url,index_col=0)
df
- 데이터변형
df.stack()
df.stack().reset_index()
df.stack().reset_index().rename(columns={'level_0':'group1','level_1':'group2',0:'X'})
- index_col=0 옵션을 사용하지않음
url = 'https://raw.githubusercontent.com/PacktPublishing/Pandas-Cookbook/master/data/state_fruit.csv'
df2=pd.read_csv(url)
df2
df2.rename(columns={'Unnamed: 0':'group1'})
df2.rename(columns={'Unnamed: 0':'group1'}).melt(id_vars='group1')
df2.rename(columns={'Unnamed: 0':'group1'}).melt(id_vars='group1')\
.rename(columns={'variable':'group2','value':'X'})
df
df.melt()
df2
df2.stack()
df
df.reset_index()
df.reset_index().melt(id_vars='index')
df.reset_index().melt(id_vars='index')\
.rename(columns={'index':'group1','variable':'group2','value':'X'})
df2.set_index('Unnamed: 0')
df2.set_index('Unnamed: 0').stack()
df2.set_index('Unnamed: 0').stack().reset_index()
df2.set_index('Unnamed: 0').stack().reset_index()\
.rename(columns={'Unnamed: 0':'group1','level_1':'group2',0:'X'})
g=['A']*100+['B']*200
y=list(np.random.randn(100)*2+2)+list(np.random.randn(200)+3)
df=pd.DataFrame({'g':g,'y':y})
df
ggplot(df)+geom_bar(aes(x='g',fill='g')) ## 디폴트로 카운트를 수행해줌
- 이것은 아래의 코드와 같다.
df.groupby(by='g').count()
fig=ggplot(df.groupby(by='g').count().reset_index())
fig+geom_bar(aes(x='g',y='y',fill='g'),stat='identity')
- barplot은 기본적으로 groupby+count()가 내장되어 있다. 따라서 아래의 코드
ggplot(df)+geom_bar(aes(x='g',fill='g')) ## 디폴트로 카운트를 수행해줌
를 좀더 엄밀하게 쓰면
ggplot(df)+geom_bar(aes(x='g',fill='g'),stat='count')
- 이것은 때때로 불편하다. 왜냐하면 데이터프레임을 변환하는 것은 판다스를 이용하는게 더 쉽고 자유로움
td=df.groupby(by='g').count().reset_index()
td
- 그냥 'x=g, y=y'를 맵핑하여 그리면 안되나?
plt.bar(td.g,td.y)
td.plot(kind='bar',x='g',y='y')
- 그런데 ggplot을 쓰려고 하면?
ggplot(td)+geom_bar(aes(x='g',y='y',fill='g'))
- 너무 불편해요.. stat='identity' 를 항상 써야하는것이!
- groupby 를 자동으로 해주므로 익숙해지면 ggplot2 방식이 더 편하지 않을까? $\to$ groupby 하는게 더 편해요..
df.groupby('g').agg({'y':[np.mean,np.median,np.std,lambda x: np.max(x)-np.min(x)]})
df.groupby('g')\
.agg({'y':[np.mean,np.median,np.std,lambda x: np.max(x)-np.min(x)]})\
.rename(columns={'<lambda_0>':'range'}).stack().reset_index()
td=df.groupby('g')\
.agg({'y':[np.mean,np.median,np.std,lambda x: np.max(x)-np.min(x)]})\
.rename(columns={'<lambda_0>':'range'}).stack().reset_index()
ggplot(td)+geom_bar(aes(x='level_1',y='y',fill='g'),stat='identity')
- 쌓인상태로 보이는것이 불편함. $\to$ position='dodge' 로!
ggplot(td)+geom_bar(aes(x='level_1',y='y',fill='g'),stat='identity',position='dodge')
- 때때로 아래와 같이 보는 것이 더 좋은 경우도 있음
ggplot(td)\
+geom_bar(aes(x='level_1',y='y',fill='g'),stat='identity',position='dodge')\
+coord_flip()
ggplot(td)\
+geom_bar(aes(x='level_1',y='y',fill='g'),stat='identity',position='dodge')\
+coord_flip()+facet_wrap('level_1')
ggplot(td)\
+geom_bar(aes(x='g',y='y',fill='g'),stat='identity',position='dodge')\
+coord_flip()+facet_wrap('level_1')
ggplot(td)+facet_grid('level_1~g')\
+geom_bar(aes(x='g',y='y',fill='g'),stat='identity',position='dodge')+coord_flip()
- 데이터셋 + 맵핑 + 지옴 + 포지션 + 스탯 + 축 + 면분할
- 데이터셋: 판다스
- 맵핑: x축, y축, 색깔, 크기, 투명도
- 지옴: 포인트지옴, 바지옴, 라인지옴, 스무스지옴
- 포지션: jitter, dodge, intentity
- 스탯: identity, count
- 축: coord_flip()
- 면분할: facet_wrap(), facet_grid()
DEP=(['A1']*2+['A2']*2+['B1']*2+['B2']*2)*2
GEN=['M']*8+['F']*8
STATE=['PASS','FAIL']*8
COUNT=[1,9,2,8,80,20,85,15,5,5,5,5,9,1,9,1]
df=pd.DataFrame({'DEP':DEP,'STATE':STATE,'GEN':GEN,'COUNT':COUNT})
df
df.groupby(['GEN','STATE']).agg({'COUNT':np.sum})
df.groupby(['GEN','STATE']).agg({'COUNT':np.sum})
df.groupby(['GEN','STATE']).agg({'COUNT':np.sum}).reset_index()
df.groupby(['GEN']).agg({'COUNT':np.sum}).reset_index()
- 두개의 데이터프레임을 합쳐야 한다.
_df1=df.groupby(['GEN','STATE']).agg({'COUNT':np.sum}).reset_index()
_df2=df.groupby(['GEN']).agg({'COUNT':np.sum}).reset_index().rename(columns={'COUNT':'SUM'})
display(_df1)
display(_df2)
- 단순한 방법
def f(x):
if x=='F':
return 40
if x=='M':
return 220
_df1['SUM']=list(map(f,_df1.GEN))
_df1
- 좀 더 좋은 방법
_df1=df.groupby(['GEN','STATE']).agg({'COUNT':np.sum}).reset_index()
- _df1를 다시 롤백
def f(_df2):
return lambda x: _df2.query('GEN == @x').SUM.item()
_df1.GEN
_df1['SUM']=list(map(f(_df2),_df1.GEN))
_df1
- 더 좋은 방법
_df1=df.groupby(['GEN','STATE']).agg({'COUNT':np.sum}).reset_index()
- _df1을 다시 롤백
_df1
_df2
pd.merge(_df1,_df2)
_df1.merge(_df2)
_df2.merge(_df1)
td=_df2.merge(_df1)
td
td['PROP']=td.COUNT/td.SUM
td
ggplot(td.query('STATE=="PASS"'))+geom_bar(aes(x='GEN',y='PROP',fill='GEN'),stat='identity')
- 남자의 합격률이 더 높다. $\to$ 성차별이 있어보인다(?)
- 학과별 합격률
df
td=df.groupby(['DEP','GEN']).agg({'COUNT':sum}).reset_index()\
.rename(columns={'COUNT':'SUM'}).merge(df)
td['PROP']=td.COUNT/td.SUM
td
td.query('STATE=="PASS"')
ggplot(td.query('STATE=="PASS"'))\
+geom_bar(aes(x='GEN',y='PROP',fill='GEN'),stat='identity')\
+facet_wrap('DEP')