기본 콘텐츠로 건너뛰기

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

[data analysis] 일원분산분석(One-way ANOVA)

일원분산분석(One-way ANOVA)

관련된 내용

분산분석의 귀무가설은 다음과 같습니다.

H0 : µ1 = µ2 = · · · = µn

분산분석을 위해 다음을 가정합니다.

  • 각 모집단은 정규분포를 따릅니다.
  • 모든 모집단의 분산은 동일합니다.
  • 관측치들은 독립적이어야 합니다.

위의 정규성 가정은 각 그룹에 대응하는 모집단을 검정하는 것은 어렵기 때문에 모델의 잔차에 대한 검정으로 대신합니다. 또한 독립성은 자료의 수집단계의 정보에 의해 판단되는 것으로 분석 중에 그 검정은 쉽지 않습니다.

표 1에서 나타낸 것과 같이 one-way ANOVA는 각 factor에 포함되는 수준 즉 처리(treatment, 요인수준)가 없습니다. 그러므로 일원분산분석에서는 요인과 treatment가 같으며 각 요인에 포함된 값들(반응변수)을 그룹화합니다. 이 구조에서 각 그룹내의 변동과 각 요인들 사이의 변동을 비교합니다.

표 1 일원분산분석을 위한 자료구조
요인(처리) 1 2 t
반응(값) x11 x12 x1t
x21 x22 x2t
xn1 xn2 xnt
평균 X1 X2 X.t
총평균 X..

표 1로부터 각 값들은 식 1과 같이 일반화한 모형으로 나타낼 수 있습니다.

xij = μj + eij (식 1)
xij: 각 값
μj: 그룹 j의 평균
eij: xij에 대응하는 오차
i:1, 2,…, n(그룹내 값의 수)
j:1, 2, …, t(그룹의 수)

이 모형에서 각 변수는 독립적이고 정규분포에 부합한다고 가정했으므로 오차항(e) 역시 평균이 0이고 일정한 분산을 가진 정규분포를 따른다고 가정할 수 있습니다. 즉, 식 2와 같이 나타낼 수 있습니다.

e ∼ N(0, σ2) (식 2)

각 값의 오차는 식 3과 같이 그룹간 오차와 그룹내 오차의 합으로 계산됩니다.

$$x_{ij}-\bar{x}=(\bar{x_j}-\bar{x})+(x_{ij}-\bar{x_j})$$ (식 3)

식 3에서 $\bar{x}$는 전체 평균, $\bar{x_j}$는 그룹 j의 평균을 나타냅니다.

평균을 기준으로 각 값의 편차의 총합은 0 또는 0에 근접하므로 그것은 자료의 특성을 나타낼 수 없습니다. 그러므로 편차 대신에 편차의 제곱합(sum of squares, SS)이 분포의 특성을 위한 지표로 사용됩니다. 식 3의 각 오차항의 제곱합은 식 4와 같이 양변을 제곱하고 합하는 것으로 생성할 수 있습니다. 그 식의 좌항은 그룹(요인)의 구분없이 모든 데이터에 대한 편차의 제곱이 되며 우항은 그룹간과 그룹내의 편차의 제곱합을 나타냅니다. 여러 그룹이 관계된 모델에서는 공분산(covariance) 고려하기 위해 우항에 $\sum^t_{j=1}\text{Cov}_j$ 즉, 그룹간 공분산의 총합을 첨가 합니다.

\begin{align}\sum^t_{j=1}\sum^n_{i=1}(x_{ij}-\bar{x})^2&=\sum^t_{j=1}(\bar{x_j}-\bar{x})^2+\sum^t_{j=1}\sum^n_{i=1}(x_{ij}-\bar{x_j})^2+\sum^t_{j=1}\text{Cov}_j\\&\text{t: 그룹의 수}\\&\text{n: 각 그룹내의 값의 수} \end{align} (식 4)

그러나 분산분석은 각 그룹이 독립임을 가정하므로 식 4의 공분산은 0이 되므로 식 5과 같이 정리됩니다.

\begin{align}&\begin{aligned}\sum^t_{j=1}\sum^n_{i=1}(x_{ij}-\bar{x})^2&=\sum^t_{j=1}(\bar{x_j}-\bar{x})^2+\sum^t_{j=1}\sum^n_{i=1}(x_{ij}-\bar{x_j})^2 \\ \text{SST}&=\quad\text{SSTr} \qquad + \;\text{SSE} \end{aligned}\\ &\text{SST}:\text{ sum of squares total, 전체 제곱합} \\&\text{SSTr}:\text{ sum of squares treatment, 처리제곱합(between group)} \\&\text{SSE}:\text{ sum of squares error, 오차제곱합(within group)}\end{align} (식 5)

식 5의 각 제곱합을 대응되는 자유도로 나눈 값을 제곱평균(mean of squares)라고 하며 식 6과 같이 정의합니다.

\begin{align}&\begin{aligned}\text{MST}&=\frac{\sum^t_{j=1}\sum^n_{i=1}(x_{ij}-\bar{x})^2}{n\cdot t - 1}\\&=\frac{\text{SST}}{n\cdot t - 1}\\ \text{MSTr}&=\frac{\sum^t_{j=1}(\bar{x_j}-\bar{x})^2}{t-1}\\&=\frac{\text{SSTr}}{t-1}\\ \text{MSE}&=\frac{\sum^t_{j=1}\sum^n_{i=1}(x_{ij}-\bar{x_j})^2 }{n\cdot t-t}\\&=\frac{\text{SSE}}{n\cdot t-t}\end{aligned}\\ &\text{MST: mean of squares total, 전체 제곱평균}\\ &\text{MSTr: mean of squares treatment, 처리제곱평균}\\ &\text{MSE: mean of squares error, 오차제곱평균}\end{align} (식 6)

예 1)

다음 코드로 생성한 자료(da)에 대해 sstr, sse, mstr, mse를 계산해 봅니다.

np.random.seed(1)
da=np.random.randint(1, 11, size=(10, 4))
print(da[:3,:])
[[ 6  9 10  6]
 [ 1  1  2  8]
 [ 7 10  3  5]]

자료 da 에 대한 각 통계량을 계산하면 다음과 같습니다.

다음 코드의 .shape 속성은 객체의 모양 즉, 행과 열의 수를 나타냅니다.

n, t=shape(da)
N=n*t
print(f'Total #:{N}, factor #:{t}, # per factor:{n}')
Total #:40, factor #:4, # per factor:10
da.shape
(10, 4)
Mu=np.mean(da)
mu=np.mean(da, axis=0)
print(f'전체평균: {Mu}, 각 그룹 평균: {mu}')
전체평균: 6.1, 각 그룹 평균: [6.5 6.8 4.6 6.5]
sst=np.sum((np.ravel(da)-Mu)**2)
mst=sst/(N-1)
print(f'SST: {round(sst,3)}, MST: {round(mst, 3)}')
SST: 377.6, MST: 9.682
sstr=np.sum((Mu-mu)**2*np.repeat(10, 4))
mstr=sstr/(t-1)
print(f'SSTr: {round(sstr,3)}, MSTr: {round(mstr, 3)}')
SSTr: 30.6, MSTr: 10.2
sse=np.sum((da-mu)**2)
mse=sse/(N-t)
print(f'SSE: {round(sse,3)}, MSE: {round(mse, 3)}')
SSE: 347.0, MSE: 9.639

위 통계량은 파이썬의 패키지인 statsmodels.api의 anova_lm(model) 함수를 사용하여 계산할 수 있습니다. 이 함수에 전달하는 인수는 statsmodels.api.ols()에 의해 생성되는 회귀모델입니다. 회귀모델은 설명변수와 반응변수 사이에 관계를 수식화하는 모델을 생성합니다. 이 함수를 사용하기 위해서는 다음 코드와 같이 패키지를 임포트 해야 합니다.

import statsmodels.api as sm
from statsmodels.formula.api import ols

ols() 함수에 전달하는 인수는 설명변수와 반응변수로 구분하여야 합니다. 그러므로 자료 da의 구조를 각 그룹과 값으로 표 2와 같이 변형합니다.

표 2 자료 구조의 변화
설명변수반응변수
group 1 group 2group 1x11
x11x12group 1x21
x21x21group 2x12
group 2x22

위 변환을 위해 다음 코드에서는 np.repeat()np.ravel()함수를 적용하였습니다.

da1=np.c_[np.repeat(range(1, 5), 10), np.ravel(da, order='F')]
da1=pd.DataFrame(da1, columns=["ind",'de'])
da1.head()
ind de
0 1 6
1 1 1
2 1 7
3 1 6
4 1 5
model=ols('de~C(ind)', data=da1).fit()
sm.stats.anova_lm(model)
df sum_sq mean_sq F PR(>F)
C(ind) 3.0 30.6 10.200000 1.058213 0.378876
Residual 36.0 347.0 9.638889 NaN NaN

위 결과에서 설명변수들 사이의 오차는 1행, 그룹내 오차는 2행에 대응하는 값입니다. 즉, 1행과 2행은 각각 그룹간, 그룹내의 통계량을 나타낸 것입니다.

정규성과 독립성에 부합하는 자료의 선형변환 역시 정규분포를 따릅니다. 각 자료들의 편차는 원시자료의 선형변환 결과이므로 변환된 자료와 그들의 제곱은 각각 정규분포와 χ2분포에 부합할 것입니다. 또한 χ2에 부합하는 자료들의 분산비는 F-분포를 형성합니다. 분산분석은 이 가정들을 전제조건으로 시행하는 것으로 그 분석의 통계량인 MSTr과 MSE의 비(식 7)에 대한 검정은 F분포를 기반으로 시행합니다.

\begin{align}F&=\frac{\text{MSTr}}{\text{MSE}}\\&=\frac{\frac{SSTr}{t-1}}{\frac{SSE}{n-t}}\end{align} (식 7)

다음은 자료에 대한 정규성 검정 결과입니다.

pval={}
for i in range(0, da.shape[1]):
    pval[i]=round(stats.shapiro(da[:,i])[1],3)
pval
{0: 0.262, 1: 0.015, 2: 0.324, 3: 0.535}

자료의 두 번째 그룹을 제외하고 모두 정규분포에 부합한다고 할 수 있습니다. 두번쨰 그룹은 정규성 부합에 위배됩니다. 이러한 경우 자료의 조정이나 분산분석 대상에서 제외를 고려해야 하지만 이 예제에서 그대로 분석을 진행합니다.

독립성 검정을 위해 scipy.stats.chi2_contingency() 함수를 적용합니다. 이 함수의 경우 필요한 데이터 형태는 교차표이므로 위의 da1을 사용하여 교차표를 생성합니다.

ta=pd.crosstab(da1.iloc[:,0], da1.iloc[:,1])
indeRe=stats.chi2_contingency(ta)
p_val=indeRe[1]; p_val.round(3)
0.441

위 두 검정의 결과는 분산분석의 전제조건에 부합함을 나타냅니다. 그러므로 anova_lm()의 결과를 적용할 수 있습니다. 이 결과의 F값은 검정통계량되며 그에 대응하는 유의확률 (p-value)은 다음의 귀무가설을 기각할 수 없음을 의미합니다. 즉, 4개 그룹의 평균은 같다고 할 수 있습니다.

H0: μ1 = μ2 = … =μn

다음 예에서 ols() 클래스 적용 없이 분산분석을 실행합니다.

예 2)

위 예제의 자료 da에 대해 분산분석을 실시합니다.

Fratio=mstr/mse
print(f"F 비: {round(Fratio, 3)}")
F 비: 1.058
ci=stats.f.interval(0.95, t-1, N-t)
print(f"하한: %.3f, 상한: %.3f" %(ci[0], ci[1]))
하한: 0.071, 상한: 3.505
pV=stats.f.sf(Fratio, t-1, N-t)
print(f'p-value: {round(pV, 3)}')
p-value: 0.379

위 결과에 의하면 F 비(F ratio)는 신뢰구간 내에 존재하며 이 통계량의 유의확률(p value) 역시 유의수준(0.05)에 비해 크므로 귀무가설을 기각할 수 없습니다. 즉, 각 그룹의 모평균을 같다고 할 수 있습니다. 이 결과는 위 statsmodels.stats.anoval_lm() 함수를 적용한 결과와 같습니다.

예 3)

일정기간의 코스피(kos), 코스탁(kq), 다우존스 주가지수(dj) 그리고 원-달러(WonDol)의 일일 변화량에 대한 분산분석을 실행해 봅니다.

이 자료는 다음의 코드에 의해 호출할 수 있으며 각 자료의 Open과 Close 사이의 일일 변화율을 계산하여 다음의 자료를 생성합니다.

st=pd.Timestamp(2024,1, 1)
et=pd.Timestamp(2024, 5, 30)
code=["KS11", "KQ11", "DJI", "USD/KRW"]
nme=['kos','kq','dj','WonDol']
da=pd.DataFrame()
for i in code:
    x=fdr.DataReader(i,st, et)['Close']
    x1=x.pct_change()
    da=pd.concat([da, x1], axis=1)
da.columns=nme
da.index=range(len(da))
da1=da.dropna()
da1.head(2).round(3)
kos kq dj WonDol
1 -0.023 -0.008 -0.008 0.011
2 -0.008 -0.006 0.000 0.001

위 데이터에 대한 분산분석 모형을 생성하기 위해서는 표 2와 같이 데이터를 변수이름과 각 이름에 대응하는 값들의 형태로 재구조화 해야 합니다. 이러한 변형은 pd.melt() 함수를 사용합니다.

da2=pd.melt(da1, value_vars=['kos', 'kq', 'dj', 'WonDol'], var_name="idx", value_name="val")
da2.head(3).round(3)
idx val
0 kos -0.023
1 kos -0.008
2 kos -0.003

위 자료에 대한 anova 분석 결과는 다음과 같습니다.

model=ols("val~C(idx)", data=da2).fit()
sm.stats.anova_lm(model).round(3)
df sum_sq mean_sq F PR(>F)
C(idx) 3.0 0.000 0.0 0.222 0.881
Residual 376.0 0.027 0.0 NaN NaN

위 결과는 비교적 높은 유의확률로 귀무가설을 채택할 수 있습니다. 위 생성된 객체인 model은 다양한 속성을 가지고 있습니다. 이 속성을 사용하여 위 표의 각 값들을 호출할 수 있습니다.

print("f value: %.4f\np-value:%.4f\nmsetr:%.4f\nmse: %.4f"%(model.fvalue,model.f_pvalue, model.mse_model, model.mse_resid))
f value: 0.2223
p-value:0.8809
msetr:0.0000
mse: 0.0001

scipy.stats.f_oneway(x1, x2,...) 함수를 적용하여 같은 결과를 확인할 수 있습니다. 이 함수는 검정통계량과 p value 만을 반환합니다.

static, pV=stats.f_oneway(da1["kos"],da1["kq"],da1["dj"],da1["WonDol"])
print("static: %.3f, p-value: %.3f" %(static, pV))
static: 0.222, p-value: 0.881

댓글

이 블로그의 인기 게시물

[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