기본 콘텐츠로 건너뛰기

[ML] 결정트리(Decision Tree) 모델

[data analysis] 이원분산분석(two-way ANOVA)

이원분산분석(two-way ANOVA)

관련된 내용

일원분산분석(one-way anova)의 경우 명목형인 설명변수(요인)의 각 효과와 연속형 반응변수의 관계를 추정하는 방법입니다. 요인이 2개 이상일 경우 고려해야 할 변동이 증가합니다. 이러한 다요인 분산분석에서 2개의 요인을 포함하는 자료의 분석을 이원분산분석(two-way anova)라고 하며 데이터 구조는 표 1과 같습니다.

표 1 이원분산분석의 데이터 구조
요인1(α) 요인2(β) 평균
처리1 처리2 처리k
1 y11 y12 y1k T1. y1.
2 y21 y22 y2k T2. y2.
n yn1 yn2 ynk Tn. yn.
T.1 T.2 T.k T
평균 y.1 y.2 y.k y

표 1은 다음과 같이 구성되어있습니다.

  • 요인(α, β, 설명변수): 2
  • 반응변수(y)
  • 수준(처리): 인자 1의 n(α), 인자 2의 k(β)

표 1의 각 값은 식 1과 같이 나타낼 수 있습니다.

yijl = μ + αi + βj + αi βj + eijl (식 1)
i: 요인 1의 인덱스, 1, 2, ..., n
j: 요인 2의 인덱스, 1, 2, ..., k
l: i, j에 대응하는 반응변수의 인덱스

위 모형은 n개 수준의 α와 k개 수준의 β에 의한 효과인 주효과(main effect)와 두 요인에 의한 교호 효과(interaction effect)는 αβ, 그리고 각 관찰치와의 오차항으로 구성됩니다. 식 1의 모형은 식 2와 같이 각 값의 변동으로 나타낼 수 있습니다.

\begin{align}\text{SST} &= \text{SSA}+\text{SSB}+\text{SSAB}+\text{SSE}\\ \sum(\text{y}_{ijl}-\bar{\text{y}}_{...})^2&= \sum(\text{y}_{i..}-\bar{\text{y}}_{...})^2\\&+ \sum(\text{y}_{.j.}-\bar{\text{y}}_{...})^2\\&+ \sum(\bar{\text{y}}_{ij.}-\bar{\text{y}}_{i..}-\bar{\text{y}}_{.j.}+\bar{\text{y}}_{...})^2\\&+ \sum(\text{y}_{ijl}-\bar{\text{y}}_{i..}-\bar{\text{y}}_{.j.}-\bar{\text{y}}_{ij.})^2\end{align}(식 2)
  SST: 총제곱합
  SSA: 요인1 제곱합
  SSB: 요인2 제곱합
  SSAB: 교호효과 제곱합
  SSE: 오차 제곱합

식 1의 모형을 근거로 다음의 가설을 검정합니다.

H0: 요인들에 대한 효과가 존재하지 않습니다.
H1: 요인들에 대한 효과가 존재합니다.

분산분석은 각 모집단이 독립이라는 가정을 합니다. 그러므로 요인간에 상호작용이 존재하지 않아야 합니다. 즉, 특정 요인들 사이에 결합된 결과가 각 값들의 합과 같아야 한다는 것입니다. 이 가정이 위배되는 경우 두 인자들 사이의 교호작용을 고려해 주어야 합니다. 일반적으로 데이터 중에서 최대 평균과 최소 평균의 비가 0.5 이상이 아닐 경우 각 변수들이 독립이라고 가정합니다.

이원분산분석의 결과는 표 2로 요약될 수 있습니다.

표 6.2.2 이원분산분석의 결과
Effect sample 크기 df SS MSS F
요인A 주효과 a a-1 SSA MSA MSA/MSE ~ F
요인B 주효과 b b-1 SSB MSB MSB/MSE ~ F
교호효과(AB) ab (a-1)(b-1) SSAB MSAB MSAB/MSE ~ F
오차변동 n 각 자유도의 차이 SSE MSE
총변동 n n-1 SST

이원분산분석은 일원분산분석과 같이 anova_lm() 함수를 사용합니다. 단지 식 1과 같이 모형에 각 요인효과와 두 요인간의 교호효과를 나타내는 항들이 존재해야 하는 복잡성에서 일원분산분석과의 차이가 있을 뿐입니다.

예 1)

다음은 5개의 그룹, 각각에 3개의 교육방법에 따른 결과입니다. 이 자료에 대해 그룹간, 교육방법간에 효과가 존재하는지를 검정합니다.

group Type
A B C
1 7,13,8,9,8,7 11,8,8,7,14,10 13,8,11,10,8,12
2 11,14,8,8,14,7 12,9,9,10,8,13 12,141,2,11,13,10
3 8,8,7,9,9,10 14,9, 8,13,8,12 9,7,11,12,13,8
4 14,11,11,9,10,7 14,11,14,11,13,13 9,9,8,7,13,9
5 8,12,12,12,11,7 10,7,8,13,14,11, 7,11,7,9,12,9

anova_lm() 함수를 적용하기 위해서는 전달하는 인수는 다음과 같이 구성된 모형입니다.

반응변수 ~ 설명변수

위 표에서 요인 group과 type이 설명변수, 그들에 대응하는 값들이 반응변수입니다. 위 모형을 작성하기 위해서는 자료는 group, type, 그리고 반응변수로 그룹화하여 서로 적합하게 대응되도록 조정해야 합니다. 즉, 분산분석을 위해 적합한 데이터 형태로 전환이 필요합니다. 변환된 자료은 객체의 요소를 반복하는 np.repeat() 함수와 객체 자체를 반복하는 np.tile() 함수를 사용하여 생성할 수 있습니다.

data=np.array([ 7, 13, 8, 9, 8, 7, 11, 14, 8, 8, 14, 7, 8, 8, 7, 9, 9,
10, 14, 11, 11, 9, 10, 7, 8, 12, 12, 12, 11, 7, 11, 8, 8, 7,
14, 10, 12, 9, 9, 10, 8, 13, 14, 9, 8, 13, 8, 12, 14, 11, 14,
11, 13, 13, 10, 7, 8, 13, 14, 11, 13, 8, 11, 10, 8, 12, 12, 14,
12, 11, 13, 10, 9, 7, 11, 12, 13, 8, 9, 9, 8, 7, 13, 9, 7,
11, 7, 9, 12, 9])
d={'type':np.repeat(['a','b','c'], 30),'group': np.tile(np.repeat([1,2,3,4, 5], 6),3), 'data':data}
df=pd.DataFrame.from_dict(d)
df.head(3)
type group data
0 a 1 7
1 a 1 13
2 a 1 8

anova_lm() 함수에 전달되는 모형은 ols() 클래스에 의해 생성됩니다. 모형에서 각 요인들 사이의 교호작용은 *으로 나타냅니다. 클래스 ols()에 전달되는 모델이 교호작용 항만으로 구성되는 경우 각 변수의 주효과는 암시적으로 입력된 형태입니다(식 6.2.3).

value ~ C(group) + C(type) + C(group) * C(type)(식 6.2.3)
  ⇔ value ~ C(group) * C(type)
mod=ols('data~C(group)*C(type)', data=df).fit()
sm.stats.anova_lm(mod)
df sum_sq mean_sq F PR(>F)
C(group) 4.0 24.333333 6.083333 1.239249 0.301614
C(type) 2.0 18.200000 9.100000 1.853780 0.163750
C(group):C(type) 8.0 61.800000 7.725000 1.573676 0.147128
Residual 75.0 368.166667 4.908889 NaN NaN

위 결과는 두 요인 group과 type에 대한 주효과에 대한 유의확률이 유의수준 0.05보다 크며 교호작용 역시 0.05보다 큰 유의확률을 보이므로 다음의 모든 귀무가설을 기각할 수 없습니다.

H01: group의 효과는 존재하지 않습니다.
H02: type의 효과는 존재하지 않습니다.
H03: type과 group의 교호작용에 의한 효과는 존재하지 않습니다.

예 2)

코스피주가지수(kos), 환율(wu), kodex 반도체(ksemi)의 일간 변화량사이에 유의 한 차이가 존재하는지를 파악합니다.

kos uk ksemi
0 2 2 3.016632
1 1 2 2.667315
2 2 1 2.722964

위 자료를 호출하고 작성하기 위한 코드는 다음과 같습니다. 위 자료에서 kos와 uk는 명목변수입니다. 즉, 호출된 연속변수를 명목변수로 변환한 것으로 함수 pd.cut()을 사용합니다.

st=pd.Timestamp(2020, 5, 1)
et=pd.Timestamp(2024, 5, 30)
code=['KS11', 'USD/KRW', "091160"]
nme=['kos','uk', 'ksemi']
da={}
for i, j in zip(code, nme):
    x=fdr.DataReader(i,st, et)['Close']
    da[j]=((x.pct_change())*100).dropna()
mn, mx=da['kos'].min(), da['kos'].max()
kos1=pd.cut(da["kos"], bins=np.linspace(mn, mx+0.1, 5), labels=range(4))
mn, mx=da['uk'].min(), da['uk'].max()
uk1=pd.cut(da["uk"], bins=np.linspace(mn, mx+0.1, 5), labels=range(4))
data=pd.concat([kos1, uk1, da['ksemi']], join="inner", axis=1)
data.index=range(len(data))
data.columns=['kos','uk', 'ksemi']
da=data.dropna()
da.head(3)

이원분산분석을 위한 모델은 두 요인인 kos와 uk를 설명변수로하여 반응변수인 ksemi에 미치는 효과를 결정하기 위한 것입니다.

mod=ols('ksemi~C(kos)*C(uk)', data=data).fit()
np.around(sm.stats.anova_lm(mod), 4)
df sum_sq mean_sq F PR(>F)
C(kos) 3.0 1096.42 365.47 184.03 0.00
C(uk) 3.0 9.75 3.25 1.64 0.18
C(kos):C(uk) 9.0 11.51 1.28 0.64 0.76
Residual 988.0 1962.13 1.99 NaN NaN

위 결과에 의하면 반응변수 ksemi에 대한 변수 kos에 의한 주효과만이 존재합니다. 이 결과의 신뢰성은 분석의 전제조건의 충족 여부에 좌우 됩니다. 먼저 정규성 부합 여부를 확인하기 위해 생성된 모델의 잔차를 대상으로 분석을 진행합니다.

res=mod.resid
nor=stats.shapiro(res)
print("통계량: %.3f, p-value: %.3f" %(nor[0], nor[1]))
통계량: 0.991, p-value: 0.000

위 결과는 정규성에 부합한다는 귀무가설을 기각할 수 있습니다. 이 결과를 시각화하면 그림 1과 같습니다.

그림 1. anova 모형 잔차에 대한 Q-Q plot.
plt.figure(figsize=(5,3))
stats.probplot(res, plot=plt)
plt.show()

그림 1에 의하면 두 개 이상의 가능한 이상치가 발견되고 양쪽 꼬리부분에서 정규성의 이탈이 발견되었습니다. 정규성 가정에 대한 개선을 위해 각 요인들의 표본분포를 적용합니다. 위 데이터는 시간에 따른 것으로 모든 변수의 데이터들의 표집은 동 시간대에 이루어져야 합니다. 이 점을 고려하여 다음과 같이 표본들의 평균으로 구성된 데이터를 생성하였습니다.

st=pd.Timestamp(2020, 5, 1)
et=pd.Timestamp(2024, 5, 30)
code=['KS11', 'USD/KRW', "091160"]
nme=['kos','uk', 'ksemi']
da={}
for i, j in zip(code, nme):
    x=fdr.DataReader(i,st, et)['Close']
    da[j]=((x.pct_change())*100).dropna()
st=pd.Timestamp(2020, 5, 1)
et=pd.Timestamp(2024, 5, 30)
code=['KS11', 'USD/KRW', "091160"]
nme=['kos','uk', 'ksemi']
da={}
for i, j in zip(code, nme):
    x=fdr.DataReader(i,st, et)['Close']
    da[j]=((x.pct_change())*100).dropna()
data=(pd.DataFrame.from_dict(da)).dropna()
data_sam=pd.DataFrame()
for i in range(1000):
    tst=pd.DataFrame(data.sample(10, random_state=i).mean(axis=0)).T
    data_sam=pd.concat([data_sam, tst])
data_sam.index=range(1,1001)
data_sam.head(3).round(2)
kos uk ksemi
10.05 0.16 0.32
2 -0.290.05 0.51
3 0.01 0.100.15

변수 kos, uk를 목록화합니다.

da_sam=data_sam.copy()
da_sam["kos"]=pd.qcut(data_sam["kos"], q=2, labels=range(1, 3))
da_sam["uk"]=pd.qcut(data_sam["uk"], q=2,  labels=range(1, 3))
da_sam.head(3).round(2)
kos uk ksemi
11 2 0.32
2 1 2 0.51
3 1 2 0.15

분산분석을 시행합니다.

mod2=ols("ksemi~C(kos)*C(uk)", data=data_sam).fit()
sm.stats.anova_lm(mod2, type=2).round(2)
df sum_sq mean_sq F PR(>F)
C(kos) 1.0 138.02 138.02 588.58 0.00
C(uk) 1.0 0.62 0.62 2.65 0.10
C(kos):C(uk) 1.0 0.61 0.61 2.59 0.11
Residual 996.0 233.55 0.23 NaN NaN

분산분석 결과는 이전 결과와 유사합니다. 즉, 요인 kos에 의한 효과만 인정됩니다. 분석을 위한 자료의 정규성과 등분산성을 검정합니다. 다음 코드는 정규성 검정 결과입니다.

res2=mod2.resid
nor2=stats.shapiro(res2)
print("통계량: %.3f, p-value: %.3f" %(nor2[0], nor2[1]))
통계량: 0.998, p-value: 0.228

위 결과는 정규분포에 부합한다는 귀무가설을 기각 할 수 없습니다. 등분산성을 테스트하기 위해 함수 scipy.stats.levene()을 적용합니다. 이 함수의 인수는 클래스로 구성된 각 그룹이 되므로 먼저 각 그룹의 자료수를 파악하기 위해 pandas.pivot_table() 함수를 사용하여 피봇테이블을 작성합니다.

pivot=pd.pivot_table(da_sam, values="ksemi", index="kos", columns="uk", aggfunc=lambda x: len(x))
pivot
uk 1 2
kos
1 222 278
2 278 222

위 결과는 각 그룹에 포함된 샘플의 규모를 나타내지만 실질적으로 필요한 부분은 각 포함된 샘플의 값입니다. 이것은 함수 pandas.groupby()를 사용하여 확인할 수 있습니다.

gr=da_sam.groupby(["kos","uk"])
gr.groups
{(1, 1): [5, 7, 9, 10, 11, 13, 20, 21, 22, 24, 27, 37, 39, 42, 43, 44,  …

위 결과는 변수 "kos"와 "uk"의 각 클래스로 분류된 "ksemi"의 인덱스를 사전형식(dictionary)으로 나타냅니다. 즉, 위 결과의 key는 두 설명변수의 클래스입니다.

gr.groups.keys()
dict_keys([(1, 1), (1, 2), (2, 1), (2, 2)])

예를 들어 위 결과를 사용하여 "kos"와 "uk"의 클래스 (1,1)에 해당하는 ksemi 자료는 다음과 같습니다.

da_sam.ksemi[gr.groups[(1,1)]].head(3)
5   -0.824925
7   -0.490587
9   -0.439029
Name: ksemi, dtype: float64

위와 같은 형식으로 각 그룹에 포함된 ksemi 자료를 호출하여 levene 검정을 실시하면 다음과 같습니다.

leveneRe=stats.levene(da_sam.ksemi[gr.groups[(1,1)]],da_sam.ksemi[gr.groups[(1,2)]],da_sam.ksemi[gr.groups[(2,1)]], da_sam.ksemi[gr.groups[(2,2)]])
print("통계량: %.3f, p-value: %.3f" %(leveneRe[0], leveneRe[1]))
통계량: 1.674, p-value: 0.171

그룹별로 호출된 자료에 대해 lenvene 검정을 실시합니다.

leveneRe=stats.levene(new[0], new[1],new[2],new[3])
print("통계량: %.3f, p-value: %.3f" %(leveneRe[0], leveneRe[1]))
통계량: 2.190, p-value: 0.088

위 결과는 자료는 등분산성을 만족한다는 귀무가설을 기각 할 수 없습니다.

분산분석에 대한 사후분석 tukeyHSD를 실시합니다. 요인변수(factor)가 두개 이상일 경우 statsmodels.stats.multicomp.pairwise_tukeyhsd(x, group, alpha=0.05) 함수를 적용할 수 없습니다. 대신에 쌍(pair)분석을 위한 대상에 대해 t-검정을 실시합니다. 두 변수의 클래스로 분류된 총 4 그룹들에서 분석을 위한 두 개의 그룹의 추출은 조합으로 계산할 수 있으며 itertools.combinations() 함수를 사용합니다.

import itertools
idx=list(itertools.combinations(gr.groups.keys(), 2))
idx
[((1, 1), (1, 2)),
 ((1, 1), (2, 1)),
 ((1, 1), (2, 2)),
 ((1, 2), (2, 1)),
 ((1, 2), (2, 2)),
 ((2, 1), (2, 2))]

t-test는 stats.ttest_ind() 함수를 적용합니다. 또한 t-test는 등분산의 성립 여부에 따라 합동 표준편차의 계산방식이 다르므로 비교 대상의 등분산 여부를 먼저 결정합니다. stats.bartlett() 함수를 적용합니다.

["pValue: %.3f" %stats.bartlett(da_sam.ksemi[gr.groups[i]], da_sam.ksemi[gr.groups[j]])[1] for i, j in idx]
['pValue: 0.380',
 'pValue: 0.048',
 'pValue: 0.728',
 'pValue: 0.241',
 'pValue: 0.214',
 'pValue: 0.020']
["%s, %s, pValue: %.3f" %(i, j, stats.ttest_ind(da_sam.ksemi[gr.groups[i]], da_sam.ksemi[gr.groups[j]])[1])  for i, j in idx]
['(1, 1), (1, 2), pValue: 0.069',
 '(1, 1), (2, 1), pValue: 0.000',
 '(1, 1), (2, 2), pValue: 0.000',
 '(1, 2), (2, 1), pValue: 0.000',
 '(1, 2), (2, 2), pValue: 0.000',
 '(2, 1), (2, 2), pValue: 0.036']

위 결과는 (1, 1):(1, 2)와 (2, 1):(2, 2)의 분석에서만 유의한 차이는 보이지 않습니다. 이 두 룹의 경우 uk 클래스만 다른 경우입니다. 그러므로 ksemi에 효과를 나타내는 변수는 kos라고 할 수 있으며 이 결과는 위의 모델 mod2에 대한 분산분석 결과와 같습니다.

댓글

이 블로그의 인기 게시물

[Linear Algebra] 유사변환(Similarity transformation)

유사변환(Similarity transformation) n×n 차원의 정방 행렬 A, B 그리고 가역 행렬 P 사이에 식 1의 관계가 성립하면 행렬 A와 B는 유사행렬(similarity matrix)이 되며 행렬 A를 가역행렬 P와 B로 분해하는 것을 유사 변환(similarity transformation) 이라고 합니다. $$\tag{1} A = PBP^{-1} \Leftrightarrow P^{-1}AP = B $$ 식 2는 식 1의 양변에 B의 고유값을 고려한 것입니다. \begin{align}\tag{식 2} B - \lambda I &= P^{-1}AP – \lambda P^{-1}P\\ &= P^{-1}(AP – \lambda P)\\ &= P^{-1}(A - \lambda I)P \end{align} 식 2의 행렬식은 식 3과 같이 정리됩니다. \begin{align} &\begin{aligned}\textsf{det}(B - \lambda I ) & = \textsf{det}(P^{-1}(AP – \lambda P))\\ &= \textsf{det}(P^{-1}) \textsf{det}((A – \lambda I)) \textsf{det}(P)\\ &= \textsf{det}(P^{-1}) \textsf{det}(P) \textsf{det}((A – \lambda I))\\ &= \textsf{det}(A – \lambda I)\end{aligned}\\ &\begin{aligned}\because \; \textsf{det}(P^{-1}) \textsf{det}(P) &= \textsf{det}(P^{-1}P)\\ &= \textsf{det}(I)\end{aligned}\end{align} 유사행렬의 특성 유사행렬인 두 정방행렬 A와 B는 'A ~ B' 와 같

[matplotlib] 히스토그램(Histogram)

히스토그램(Histogram) 히스토그램은 확률분포의 그래픽적인 표현이며 막대그래프의 종류입니다. 이 그래프가 확률분포와 관계가 있으므로 통계적 요소를 나타내기 위해 많이 사용됩니다. plt.hist(X, bins=10)함수를 사용합니다. x=np.random.randn(1000) plt.hist(x, 10) plt.show() 위 그래프의 y축은 각 구간에 해당하는 갯수이다. 빈도수 대신 확률밀도를 나타내기 위해서는 위 함수의 매개변수 normed=True로 조정하여 나타낼 수 있다. 또한 매개변수 bins의 인수를 숫자로 전달할 수 있지만 리스트 객체로 지정할 수 있다. 막대그래프의 경우와 마찬가지로 각 막대의 폭은 매개변수 width에 의해 조정된다. y=np.linspace(min(x)-1, max(x)+1, 10) y array([-4.48810153, -3.54351935, -2.59893717, -1.65435499, -0.70977282, 0.23480936, 1.17939154, 2.12397372, 3.0685559 , 4.01313807]) plt.hist(x, y, normed=True) plt.show()

R 미분과 적분

내용 expression 미분 2차 미분 mosaic를 사용한 미분 적분 미분과 적분 R에서의 미분과 적분 함수는 expression()함수에 의해 생성된 표현식을 대상으로 합니다. expression expression(문자, 또는 식) 이 표현식의 평가는 eval() 함수에 의해 실행됩니다. > ex1<-expression(1+0:9) > ex1 expression(1 + 0:9) > eval(ex1) [1] 1 2 3 4 5 6 7 8 9 10 > ex2<-expression(u, 2, u+0:9) > ex2 expression(u, 2, u + 0:9) > ex2[1] expression(u) > ex2[2] expression(2) > ex2[3] expression(u + 0:9) > u<-0.9 > eval(ex2[3]) [1] 0.9 1.9 2.9 3.9 4.9 5.9 6.9 7.9 8.9 9.9 미분 D(표현식, 미분 변수) 함수로 미분을 실행합니다. 이 함수의 표현식은 expression() 함수로 생성된 객체이며 미분 변수는 다음 식의 분모의 변수를 의미합니다. $$\frac{d}{d \text{변수}}\text{표현식}$$ 이 함수는 어떤 함수의 미분의 결과를 표현식으로 반환합니다. > D(expression(2*x^3), "x") 2 * (3 * x^2) > eq<-expression(log(x)) > eq expression(log(x)) > D(eq, "x") 1/x > eq2<-expression(a/(1+b*exp(-d*x))); eq2 expression(a/(1 + b * exp(-d * x))) > D(eq2, "x") a * (b * (exp(-d * x) * d))/(1 + b