(7주차) 10월25일
Partial correlation, 행을선택하는 방법, fifa22자료 시각화
- (1/11) Partial Correlation: 아이스크림을 많이 먹으면 걸리는 병
- (2/11) Partial Correlation: 기상청기온자료를 활용하여 가짜자료 구축 및 해석 (1)
- (3/11) Partial Correlation: 기상청기온자료를 활용하여 가짜자료 구축 및 해석 (2)
- (4/11) Partial Correlation: plotnine을 활용한 시각화
- (5/11) 행을선택하는 방법 (기본)
- (6/11) 행을선택하는 방법 (람다)
- (7/11) 행을선택하는 방법 (쿼리)
- (8/11) fifa22자료 설명
- (9/11) fifa22자료 데이터변형 및 시각화 (1)
- (10/11) fifa22자료 데이터변형 및 시각화 (2)
- (11/11) 과제설명
- 내용요약
- 여름 $\to$ 수영장 $\to$ 소아마비
- 여름 $\to$ 아이스크림
- 아이스크림과 소아마비는 상관관계가 높다: 아이스크림 성분중에서 소아마비를 유발하는 유해물질이 있을 것이다 (?)
- 아래와 같이 모형을 간단하게 하자.
- 온도 $\to$ 소아마비
- 온도 $\to$ 아이스크림
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
- 교재의 예제상황은 예를들면 아래와 같다.
- 아이스크림 판매량 = 20 + 온도 $\times$ 2 + $\epsilon$
np.random.seed(1)
temp= np.array([-10.2, -5.2, 0.1, 10.1, 12.2, 14.7,
25.4, 26.8, 28.9, 35.1, 32.2, 34.6])
ϵ1= np.random.normal(size=12,scale=5)
icecream= 20 + temp * 2 + ϵ1
plt.plot(temp,icecream,'.')
- 온도와 아이스크림 판매량의 산점도
- 소아마비 = 30 + 온도 $\times$ 0.5 + $\epsilon^*$
np.random.seed(2)
ϵ2= np.random.normal(size=12,scale=5)
disease = 30+ temp* 0.5 + ϵ2
plt.plot(temp,disease,'.')
- 온도와 소아마비의 산점도
- 아이스크림과 질병의 산점도를 그려보자.
plt.plot(icecream,disease,'.')
- 양의 상관관계에 있다.
- 아이스크림 중 어떠한 물질이 소아마비를 일으키는것이 분명하므로 (인과성이 분명해보이니까) 아래와 같은 모형을 세우자. <-- 여기서부터 틀렸음
$${\tt disease}_i =\beta_0 +\beta_1 {\tt icecream}_i +\epsilon_i,\quad \textbf{for} ~~ i=1,2,\dots, 12$$
- 적절한 $\beta_0$와 $\beta_1$을 추정하면 우리는 아이스크림과 소아마비의 관계를 알 수 있다. <-- 틀린주장
- 틀린 모형
- 도데체 우리가 뭘 잘못했는가?
- 두 변수 사이에 상관관계가 있어도 실제 원인은 다른 변수에 숨겨져 있는 경우가 많다.
- 예제의상황: 아이스크림과 익사자도 양의 상관관계에 있을것이다.
- 아이스크림을 먹이면 물에 빠져 죽는다 $\to$ 틀린주장
- 사실 기온이 숨겨진 원인이다. 기온이 증가하면 아이스크림 판매량도 증가하고 폭염때문에 익사사고율도 높아지는 구조이다.
- 아래와 같은 예제를 생각하자.
- 인구수 $\to$ 교회
- 인구수 $\to$ 범죄건수
- 지역별 교회와 범죄건수를 살펴보면 상관관계가 높게 나올것임
- 교회를 많이 지으면 범죄건수도 증가한다?
- 사실 그렇지 않다.
- 인구수가 비슷한 도시끼리 묶어서 비교해보면 교회와 범죄건수는 양의 상관관계에 있지 않을것임
- 올바른 분석: 온도가 비슷한 그룹끼리 묶어서 그려보자. $\to$ 상관계수가 줄어들 것이다.
plt.plot(icecream[:6],disease[:6],'.')
plt.plot(icecream[6:],disease[6:],'.')
- 진짜로 선형관계가 약해졌다..
- 위의 toy example은 데이터가 너무 작아서 억지스러움
df=pd.read_csv('https://raw.githubusercontent.com/guebin/2021DV/master/_notebooks/extremum.csv')
df
- 평균기온만 선택하여 뽑자.
pd.Series(df.columns)
temp=np.array(df.iloc[:,3])
len(temp)
- 아이스크림 판매량
np.random.seed(1)
ϵ1=np.random.normal(size=656, scale=10)
icecream=temp*2 + 30 + ϵ1
plt.plot(temp,icecream,'.')
- 소아마비
np.random.seed(2)
ϵ2=np.random.normal(size=656,scale=1)
disease=temp*0.5 + 40 +ϵ2
plt.plot(temp,disease,'.')
- 아이스크림과 소아마비
plt.plot(icecream,disease,'.')
np.corrcoef(icecream,disease)
- 0.86정도..
- 여름만 뽑아서 그러보면?
plt.plot(icecream[temp>25],disease[temp>25], '.') ## 평균기온이 25도가 넘어가면 여름
- 산점도
fig , ((ax1,ax2), (ax3,ax4)) = plt.subplots(2,2)
ax1.plot(temp,icecream,'.')
ax2.plot(temp,disease,'.')
ax3.plot(icecream,disease,'.')
ax4.plot(icecream[temp>25],disease[temp>25],'.')
fig , ((ax1,ax2), (ax3,ax4)) = plt.subplots(2,2)
ax1.plot(temp,icecream,'.')
ax2.plot(temp,disease,'.')
ax3.plot(icecream,disease,'.')
ax4.plot(icecream,disease,'.')
ax4.plot(icecream[temp>25],disease[temp>25],'.')
- 목표: 모든 온도구간에 대하여 각각 색을 다르게 하여 그려보자.
- 사실 지금 변수는 온도, 아이스크림판매량, 소아마비
- 지금까지는 기본산점도만 사용하였기에 2차원플랏만 그렸음 $\to$ 그래서 각각의 산점도를 정신없이 그려왔음
- 온도가 유사한 지역을 색으로 묶으면 3차원 플랏이 가능함
- 일단 데이터 프레임을 정리하자.
df1=pd.DataFrame({'temp':temp, 'icecream':icecream, 'disease':disease})
df1
- 온도를 카테고리화 하자 $\to$ 적당한 구긴을 설정하기 위해서 히스토그램을 그려보자.
df1.temp.hist()
plt.hist(df1.temp)
- 구간은 5정도로 하면 적당할것 같다.
def f(x):
if x<0:
y='group0'
elif x<5:
y='group5'
elif x<10:
y='group10'
elif x<15:
y='group15'
elif x<20:
y='group20'
elif x<25:
y='group25'
else:
y='group30'
return y
df1['temp2']=list(map(f,df1.temp))
df1
from plotnine import *
ggplot(data=df1)+geom_point(aes(x='icecream',y='disease',colour='temp2'),alpha=0.5)
ggplot(data=df1)+geom_point(aes(x='icecream',y='disease',colour='temp2'),alpha=0.2)+geom_smooth(aes(x='icecream',y='disease',colour='temp2'),size=2,linetype='dashed')
- 온도를 통제하니까 아이스크림과 질병은 관련이 없어보인다.
np.random.seed(1)
ϵ1=np.random.normal(size=656, scale=10)
icecream=temp*2 + 30 + ϵ1
np.random.seed(2)
ϵ2=np.random.normal(size=656,scale=1)
disease= 30+ temp*0.0 + icecream*0.15 +ϵ2*2
df2=pd.DataFrame({'temp':temp,'icecream':icecream,'disease':disease})
df2['temp2']=list(map(f,df2.temp))
df2
ggplot(data=df2)+geom_point(aes(x='icecream',y='disease',colour='temp2'),alpha=0.2)+geom_smooth(aes(x='icecream',y='disease',colour='temp2'),size=2,linetype='dashed')
df1.corr()
df2.corr()
- 온도구간을 10으로 변경하고 df1, df2에서 아이스크림과 소아마비의 산점도를 시각화한뒤 스크린샷 제출
np.random.seed(1)
dic= {'X1':np.random.normal(0,1,5),
'X2':np.random.normal(0,1,5),
'X3':np.random.normal(0,1,5),
'X4':np.random.normal(0,1,5),
'X5':np.random.normal(0,1,5),
'X6':np.random.normal(0,1,5)}
df1=pd.DataFrame(dic)
df1
- 방법1
df1.iloc[0] # 이상해
- 방법2
df1.iloc[[0]]
- 방법3
df1.iloc[0,:]
- 방법4
df1.iloc[[0],:]
- 방법5
df1.loc[0] # 이상해
- 방법6
df1.loc[[0]]
- 방법7
df1.loc[0,:]
- 방법8
df1.loc[[0],:]
- 방법9
df1.iloc[[True,False,False,False,False]]
- 방법10
df1.iloc[[True,False,False,False,False],:]
- 방법11
df1.loc[[True,False,False,False,False]]
- 방법12
df1.loc[[True,False,False,False,False],:]
df1
- 방법1
df1.iloc[[0,2],:]
- 방법2
df1.loc[[0,2],:]
- 그외에 여러행을 뽑는 방법이 있음; 슬라이싱, 불인덱싱
- 인덱스가 정수가 아닌경우
_df= pd.DataFrame({'A':[1,2,3,4],'B':[4,5,6,7]},index=list('abcd'))
_df
_df.loc['a':'c',:]
_df.iloc[0:3,:]
_df.loc[['a','b','c'],:]
_df.iloc[[0,1,2],:]
- 대부분의 경우 observation에 특정한 이름이 있는 경우는 없으므로 loc이 그다지 쓸모 없음
- 그렇지만 특정경우에는 쓸모가 있음
np.random.normal(size=(20,4))
np.random.seed(1)
_df= pd.DataFrame(np.random.normal(size=(20,4)), columns=list('ABCD'), index=pd.date_range('20201225',periods=20))
_df
- 1월5일부터 1월8일까지의 자료만 보고싶다.
_df.loc['20210105':'20210108']
- iloc으로 하려면 힘들다.
_df.index
pd.Series(_df.index)
_df.iloc[11:15]
- 저는 아래와 같은 실수를 자주해요
_df.loc['A']
- 올바른 방법
_df['A']
- 아래의 사실을 기억하자.
-
기본적으로는 iloc, loc은
[2], [2:]처럼 1차원으로 원소를 인덱싱할수도 있고,[2,3], [:,2]와 같이 2차원으로 인덱싱할 수도 있다. -
1차원으로 인덱싱하는 경우는 기본적으로 행을 인덱싱한다 $\to$ iloc, loc은 행과 더 친하고 열과 친하지 않다.
-
따라서 열을 선택하는 방법에 있어서 loc, iloc이 그렇제 좋은 방법은 아니다.
-
그렇지만 열을 선택하는 방법은 iloc이나 loc이 제일 편리하다. (이외의 다른 방법이 마땅하게 없음) 그래서 열을 선택할때도 iloc이나 loc을 선호한다.
_df.iloc[::2]
- 이 방법은 칼럼에도 적용가능
_df.iloc[:,::2]
- 칼럼에는 잘 쓰지 않는이유?
-
row는 특정간격으로 뽑는 일이 빈번함. (예를들어 일별데이터를 주별데이터로 바꾸고싶을때, 바꾸고 싶을 경우?)
-
col을 특정간격으로 뽑아야 하는 일은 없음
np.random.seed(1)
df2= pd.DataFrame(np.random.normal(size=(10,4)),columns=list('ABCD'))
df2
- 방법1
df2.loc[map(lambda x: x>0,df2['A']),:]
- 방법2
df2.loc[lambda df: df['A']>0,:]
- ??
- map의 기능은 (1) 리스트를 원소별로 분해하여 (2) 어떠한 함수를 적용하여 아웃풋을 구한뒤 (3) 각각의 아웃풋을 다시 하나의 리스트로 묶음
- 우리는 이중에서 (1),(3)에만 집중했음
- 하지만 생각해보면 일단 (2) 일단 함수를 적용하는 기능이 있었음
- 그런데 위의 코든느 함수를 적용한 결과가 아니라 함수 오브젝트 자체를 전달하여도 동작함
요약!!
- True, False로 이루어진 벡터를 리스트의 형태로 전달하여 인덱상했음 (원래 우리가 알고 있는 개념)
- True, False로 이루어진 벡터를 리턴할 수 있는 함수오브젝트 자체를 전달해도 인덱싱이 가능
- 방법3
df2.iloc[map(lambda x: x>0,df2['A']),:]
- 방법4: 실패
df2.iloc[lambda df: df['A']>0,:]
- 위에서 iloc을 loc으로 바꾸면 되는데..
- iloc입장에서는 조금 서운함
df2
- 방법1
df2.loc[map(lambda x,y: x>0 and y<0, df2['A'],df2['C']),:]
- 방법2
df2.loc[map(lambda x,y: x>0 & y<0, df2['A'],df2['C']),:]
- ??? 아래를 관찰
보충학습
0<3.2 & 0<2.2
(0<3.2) & (0<2.2)
보충학습끝
위의코드도 괄호로 묶어주면 잘 동작한다.
df2.loc[map(lambda x,y: (x>0) & (y<0), df2['A'],df2['C']),:]
- 방법3
df2.loc[lambda df: (df['A'] >0) & (df['C']<0)]
아래는 실행되지 않는다
df2.loc[lambda df: (df['A'] >0) and (df['C']<0)]
실행되지 않는이유
np.array([True, False]) & np.array([True, True])
np.array([True, False]) and np.array([True, True])
- iloc을 이용한 방법은 생략
import numpy as np
import pandas as pd
np.random.seed(1)
df=pd.DataFrame(np.random.normal(size=(15,4)),columns=list('ABCD'))
df
- 방법1
df.query('A>0 & B<0')
- 방법2
df.query('A>0 and B<0')
df.query('A<B<C')
- 방법1
df.A.mean()
df.query('A>-0.018839420539994597')
- 방법2
meanA=df.A.mean()
meanA
df.query('A> @meanA')
- 방법1
df.query(' A> @meanA and A<0.8')
- 방법2
df.query(' A> @meanA'
' and A<0.8')
- 참고사항: 아래는 에러가 발생한다.
df.query('A> @meanA'
'and A<0.8')
df
- 0, 3:5, 9:11 에 해당하는 row를 뽑고싶다. $\to$ 칼럼이름을 index로 받아서 사용한다.
df.query('index==0 or 3<=index <=5 or 9<=index <=11')
- 응용사례1
df.query('index==0 or index ==[8,9,10]')
- 응용사례2
i1= np.arange(3)
i1
df.query('index in @i1 or index==5')
- 시계열자료에서 특히 유용함
df2=pd.DataFrame(np.random.normal(size=(10,4)), columns=list('ABCD'), index=pd.date_range('20201226',periods=10))
df2
df2.query(
' "2020-12-27"<= index <= "2021-01-03" ')
df2.query(
' "2020-12-27"<= index <= "2021-01-03" '
' and A+B < C')
- FIFA22라는 축구게임이 있음 (굉장히 인기있음)
- 게임에 실제 선수들이 나오면서 선수들의 능력치가 세밀하게 구현되어 있음
- 이 능력치에 대한 데이터셋은 캐글에 공개되어 있음
fifa22=pd.read_csv('https://raw.githubusercontent.com/guebin/2021DV/master/_notebooks/2021-10-25-FIFA22_official_data.csv')
- Overall을 기준으로 정렬하여 보자.
fifa22=fifa22.sort_values(by='Overall',ascending=False).reset_index().rename(columns={'index':'index_old'})
fifa22.head()
from plotnine import *
ggplot(data=fifa22)+geom_point(aes(x='Overall', y='Potential'))
- 뭔가 Potential > Overall 인 관계가 성립하는것 같다. $\to$ Potetial2= Potential - Overall 인 변수를 새로 만들고 시각화해보자.
- 판다스: 새로운열 추가
fifa22['Potential2'] = fifa22['Potential'] - fifa22['Overall']
ggplot(data=fifa22)+geom_point(aes(x='Overall', y='Potential2'),alpha=0.1)
ggplot(data=fifa22)+geom_point(aes(x='Overall', y='Potential2'),alpha=0.1,position='jitter')
- 포텐셜2가 너무 0근처인 선수들이 있다. (아마 은퇴한 선수가 아닐까?) $\to$ 제외하고 그리자.
ggplot(data=fifa22.query('Potential2>0.1'))+geom_point(aes(x='Overall', y='Potential2'),alpha=0.1,position='jitter')
- 해석
- 음의 상관관계가 있다.
- 오버올이 클수록 포텐셜2의 분산이 작아진다. (오버올이 클수록 더 성장할 부분이 없으니까)
- Overall을 구간별로 나누자: 어느정도가 적당한 구간일까?
fifa22.Overall.describe()
import matplotlib.pyplot as plt
fifa22.Overall.hist()
def f(x):
if x>72: y='Q1'
elif x>68: y='Q2'
elif x>63: y='Q3'
else: y='Q4'
return y
fifa22['Q']=list(map(f,fifa22.Overall))
fifa22[['Q','Overall']]
ggplot(data=fifa22.query('Potential2>0.1'))\
+geom_boxplot(aes(x='Q',y='Potential2'))
- Q1으로 갈수록 분산이 작아짐! $\to$ 헷갈린다...
- 산점도와 박스플랏을 겹쳐서 그린다면 좀더 이해가 쉬울것 같다.
- x축의 위치를 조정하면 될것 같다 $\to$ Q1, Q2, Q3, Q4 각 그룹별로 x축의 위치를 구하자.
fifa22.query('Q=="Q1"').Overall.mean()
- 이런식으로 해도 되지만
fifa22.groupby(by='Q').mean().Overall
l=fifa22.groupby(by='Q').mean().Overall.to_list()
l
- 이제 박스플랏이 들어갈 x축의 위치를 저장할 컬럼을 추가하고 그 이름을 Qx 라고 하자.
def g(x):
if x=='Q1': y=l[0]
elif x=='Q2': y=l[1]
elif x=='Q3': y=l[2]
else: y=l[3]
return y
fifa22['Qx']=list(map(g,fifa22.Q))
fifa22
ggplot(data=fifa22.query('Potential2>0.1'))\
+geom_point(aes(x='Overall', y='Potential2',color='Q'),alpha=0.1,size=0.1,position='jitter')\
+geom_boxplot(aes(x='Qx', y='Potential2',color='Q'))
fifa22 데이터셋에서 Q==Q1이고, Potentail2>20 인 선수들의 이름을 출력하라.