기본 콘텐츠로 건너뛰기

벡터와 행렬에 관련된 그림들

[data analysis] 다중회귀모델의 생성

회귀모델 생성

1개 이상의 설명변수들과 1개의 반응변수를 가진 선형모델 역시 statsmodels.api.OLS() 클래스와 sklearn.linear_model() 클래스를 적용하여 구축할 수 있습니다. 이 모델의 구축과정과 평가방법은 단순회귀분석(Simple regression)과 같습니다.

예 1)

코스피지수(kos), 코스탁지수(kq), kodex 레버리지(kl), kodex 인버스(ki), 그리고 원달러환율(WonDol)의 일일 종가들을 설명변수로 사용하여 삼성전자(sam)의 일일 종가를 추정하는 회귀모델을 구축해 봅니다. (이 자료에서 설명변수는 반응변수보다 1일 앞선 데이터 입니다.)

kos kq kl ki WonDol sam
Date
2023-01-10 2351.0 696.0 14440.0 4885.0 1239.0 60400.0
2023-01-11 2360.0 710.0 14525.0 4875.0 1240.0 60500.0
2023-01-12 2365.0 711.0 14580.0 4860.0 1242.0 60500.0

다음은 위 자료들을 호출하고 분석에 적합하게 조절하기 위한 코드들입니다.

import numpy as np 
import pandas as pd 
from sklearn import preprocessing
from scipy import stats
import matplotlib.pyplot as plt
import yfinance as yf
from sklearn.linear_model import LinearRegression
from statsmodels.regression.linear_model import OLS
from statsmodels.formula.api import ols
st=pd.Timestamp(2023,1, 10)
et=pd.Timestamp(2024, 5, 30)
code=["^KS11", "^KQ11", "122630.KS", "114800.KS","KRW=X","005930.KS"]
nme=["kos","kq","kl", "ki", "WonDol","sam" ]
da={}
for i, j in zip(nme,code):
    da[i]=yf.download(j,st, et)["Close"]
da2=pd.DataFrame(da.values(), index=da.keys()).T
da2=da2.ffill()
da2.head(3).round(0)

호출한 자료들의 시간들이 동일하지는 않습니다. 즉, WonDol을 제외한 자료는 거래가 중단되는 시간들이 존재합니다. 그러므로 이들 자료를 결합하는 과정에서 결측치가 존재합니다(결측치 조정 참조). 위 코드에서는 객체.ffill()를 적용하여 결측지 직전 값으로 대체하였습니다.

호출한 데이터들을 설명변수와 반응변수로 분리하고 마지막 행에 존재하는 자료들은 모델 생성에 참여하지 않은 설명변수로서 별도의 객체 final에 저장합니다.

ind=da2.values[:-1,:-1]
de=da2.values[1:,-1].reshape(-1,1)
final=da2.values[-1, :-1].reshape(1,-1)

각 변수들의 범위 즉, 스케일(규모)은 다양하므로 데이터 표준화를 실시합니다. 이것은 sklearn.preprocessing의 StandardScaler() 클래스를 적용합니다.

indScaler=preprocessing.StandardScaler().fit(ind)
deScaler=preprocessing.StandardScaler().fit(de)
indNor=indScaler.transform(ind)
finalNor=indScaler.transform(final)
deNor=deScaler.transform(de)
print(finalNor.round(3))
[[ 1.256 -0.133  1.488 -1.515  1.395]]

statsmodels.formula.api.ols() 클래스를 적용하여 회귀모델을 생성합니다. 이 모델의 세부적인 사항은 summay() 메서드로 확인할 수 있습니다. 이 메서드의 결과는 단순회귀분석에서 나타낸 것과 같이 3개의 표들로 구성됩니다. 이 표들은 이 메서드의 속성 tables로 별도로 나타낼 수 있습니다. 먼저 첫 번째 표를 나타내면 다음과 같습니다.

ols() 함수를 사용하기 위해서는 위의 indNor, deNor을 결합하여 DataFrame 구조로 생성하여야 합니다.

da3=pd.DataFrame(np.c_[indNor, deNor.reshape(-1,1)])
da3.columns=da2.columns
da3.head(3).round(2)
kos kq kl ki WonDol sam
0 -1.87 -3.01 -1.47 1.58 -2.36 -1.72
1 -1.79 -2.73 -1.42 1.53 -2.33 -1.72
2 -1.74 -2.71 -1.38 1.46 -2.29 -1.67
form='sam~kos+kq+kl+ki+WonDol'
reg=ols(form, data=da3).fit()
re=reg.summary()
re.tables[0]

위 코드의 결과는 표 1과 같습니다.

표 1 OLS Regression Results
Dep. Variable: sam R-squared: 0.863
Model: OLS Adj. R-squared: 0.861
Method: Least Squares F-statistic: 445.4
Date: Wed, 11 Sep 2024 Prob (F-statistic): 1.61e-150
Time: 09:52:12 Log-Likelihood: -154.10
No. Observations: 361 AIC: 320.2
Df Residuals: 355 BIC: 343.5
Df Model: 5
Covariance Type: nonrobust

표 1에서 나타낸 F 검정의 유의확률(p-value)은 매우 작은 값으로 모델의 모든 회귀계수가 0이 된다는 귀무가설을 기각할 수 있습니다. 즉, 이 모델은 설명변수들에 의한 반응변수의 설명이 가능함을 나타냅니다. 이 결과는 작지않은 결정계수(R2)에 의해 부연됩니다.

F 검정의 결과는 각각 유의확률과 검정통계량을 반환하는 모델.f_pvalue, 모델.fvalue 속성, 결정계수는 모델.squared 속성으로 확인할 수 있습니다.

fst, fp, r2=reg.fvalue,reg.f_pvalue, reg.rsquared
print('F통계량:%.3f, p-value:%.3f, R2:%.3f' %(fst, fp, r2))
F통계량:445.378, p-value:0.000, R2:0.863

F 검정 결과는 이 모델 전체에 대한 검정으로 각 설명변수가 반응변수에 영향 정도를 확인할 수 없습니다. 모델에의해 발생되는 오차에 대해 각 설명변수들의 영향정도는 anova 분석으로 확인할 수 있습니다.

anovaRe=sm.stats.anova_lm(reg)
np.around(anovaRe, 2)
df sum_sq mean_sq F PR(>F)
kos 1.0 217.12 217.12 1552.84 0.0
kq 1.0 4.62 4.62 33.04 0.0
kl 1.0 82.49 82.49 589.98 0.0
ki 1.0 5.45 5.45 38.99 0.0
WonDol 1.0 1.68 1.68 12.04 0.0
Residual 355.0 49.64 0.14 NaN NaN

위 결과는 잔차의 분산 대비 각 설명변수의 분산의 비를 통계량으로 하는 것으로 유의수준 0.05를 기준으로 모든 변수들의 효과가 없다는 귀무가설을 기각할 수 있습니다. 이 결과는 객체 re의 두번째 표는 회귀계수에 대한 t 검정 결과를 나타냅니다(회귀계수의 평가 참조).

re.tables[1]
coef std err t P>|t| [0.025 0.975]
Intercept 8.29e-16 0.020 4.21e-14 1.000 -0.039 0.039
kos -2.1508 0.144 -14.942 0.000 -2.434 -1.868
kq 0.3980 0.041 9.772 0.000 0.318 0.478
kl 0.9641 0.280 3.445 0.001 0.414 1.514
ki -1.7233 0.269 -6.416 0.000 -2.251 -1.195
WonDol 0.0810 0.023 3.470 0.001 0.035 0.127

위 결과는 anova 분석과 같은 결과를 보여줍니다. 편차항을 제외한 모든 계수의 신뢰구간은 0을 포함하지 않으므로 모델에 영향을 준다고 할 수 있습니다. 이 회귀계수의 신뢰구간은 위 표외에 모델.conf_int(alpha) 메소드로 확인할 수 있습니다.

reg.conf_int(0.05).round(3)
0 1
Intercept -0.039 0.039
kos -2.434 -1.868
kq 0.318 0.478
kl 0.414 1.514
ki -2.251 -1.195
WonDol 0.035 0.127

각각의 설명변수와 반응변수 sam에 대한 단순회귀모형으로 위 분석결과를 확인할 수 있습니다. 예를 들어 WonDol-sam의 단순회귀모형으로 이 설명변수의 효과여부를 판단해 봅니다.

reg_wd=ols("sam~WonDol", data=da3).fit()
pd.DataFrame([reg_wd.fvalue, reg_wd.f_pvalue], index=['f-statics', 'p-value']).round(3)
0
f-statics 113.455
p-value 0.000

위와 같이 각 설명변수에 대한 단순회귀분석의 결과는 sklearn.feature_selection.f_regression(설명변수, 반응변수) 함수를 사용하여 모든 변수에 대한 결과를 확인할 수 있습니다. 이 함수는 하나의 설명변수과 반응변수로 생성되는 단순회귀모델에 대한 F 검정 결과(통계량과 유의확률)를 반환합니다. 예를 들어 다음 결과의 첫번째 통계량과 유의확률은 y=b0 + b1x[:,0]의 모델에 대한 F 검정의 결과입니다.

from sklearn import feature_selection 
Ftest=feature_selection.f_regression(indNor, deNor.ravel())
f2=pd.DataFrame([Ftest[0], Ftest[1]], index=["f-value", "p-value"], columns=da2.columns[:-1])
np.around(f2, 1)
kos kq kl ki WonDol
f-value 541.7 105.1 975.8 994.2 113.5
p-value 0.0 0.0 0.0 0.0 0.0

위 결과는 F 통계량과 p-value로서 각 설명변수에 의한 단순회귀모델이 유효함을 의미합니다.

생성된 모델의 회귀계수는 모델.params 속성으로 확인할 수 있습니다. 또한 추정값은 모델.predict() 메소드로 확인할 수 있습니다.

reg.params.round(3)
Intercept    0.000
kos         -2.151
kq           0.398
kl           0.964
ki          -1.723
WonDol       0.081
dtype: float64

위 ols() 함수 대신에 sklearn.linear_model.LinearRegression() 클래스를 사용하여 회귀모델을 작성할 수 있습니다. 다음은 이 클래스를 적용한 결과입니다.

mod=LinearRegression().fit(indNor, deNor)
b0=mod.intercept_
b=mod.coef_
print(f"편차:{np.around(b0, 3)}, 계수: {np.around(b, 3)}")
편차:[-0.], 계수: [[-2.151  0.398  0.964 -1.723  0.081]]
r2=mod.score(indNor, deNor)
print(f"R2: {round(r2, 3)}")
R2: 0.863

그림 1은 생성한 모델을 통해 반응변수와 그에 대응하는 추정된 값들을 시각화한 것입니다.

그림 1. 중회귀모형에 의한 예측.
plt.figure(figsize=(4, 3))
plt.plot(deNor, c="g", ls="--", label="data")
plt.plot(mod.predict(indNor), c="b", label="regression")
plt.legend(loc="best", frameon=False)
plt.show()

댓글

이 블로그의 인기 게시물

[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' 와 같...

[sympy] Sympy객체의 표현을 위한 함수들

Sympy객체의 표현을 위한 함수들 General simplify(x): 식 x(sympy 객체)를 간단히 정리 합니다. import numpy as np from sympy import * x=symbols("x") a=sin(x)**2+cos(x)**2 a $\sin^{2}{\left(x \right)} + \cos^{2}{\left(x \right)}$ simplify(a) 1 simplify(b) $\frac{x^{3} + x^{2} - x - 1}{x^{2} + 2 x + 1}$ simplify(b) x - 1 c=gamma(x)/gamma(x-2) c $\frac{\Gamma\left(x\right)}{\Gamma\left(x - 2\right)}$ simplify(c) $\displaystyle \left(x - 2\right) \left(x - 1\right)$ 위의 예들 중 객체 c의 감마함수(gamma(x))는 확률분포 등 여러 부분에서 사용되는 표현식으로 다음과 같이 정의 됩니다. 감마함수는 음이 아닌 정수를 제외한 모든 수에서 정의됩니다. 식 1과 같이 자연수에서 감마함수는 factorial(!), 부동소수(양의 실수)인 경우 적분을 적용하여 계산합니다. $$\tag{식 1}\Gamma(n) =\begin{cases}(n-1)!& n:\text{자연수}\\\int^\infty_0x^{n-1}e^{-x}\,dx& n:\text{부동소수}\end{cases}$$ x=symbols('x') gamma(x).subs(x,4) $\displaystyle 6$ factorial 계산은 math.factorial() 함수를 사용할 수 있습니다. import math math.factorial(3) 6 a=gamma(x).subs(x,4.5) a.evalf(3) 11.6 simpilfy() 함수의 알고리즘은 식에서 공통사항을 찾아 정리하...

sympy.solvers로 방정식해 구하기

sympy.solvers로 방정식해 구하기 대수 방정식을 해를 계산하기 위해 다음 함수를 사용합니다. sympy.solvers.solve(f, *symbols, **flags) f=0, 즉 동차방정식에 대해 지정한 변수의 해를 계산 f : 식 또는 함수 symbols: 식의 해를 계산하기 위한 변수, 변수가 하나인 경우는 생략가능(자동으로 인식) flags: 계산 또는 결과의 방식을 지정하기 위한 인수들 dict=True: {x:3, y:1}같이 사전형식, 기본값 = False set=True :{(x,3),(y,1)}같이 집합형식, 기본값 = False ratioal=True : 실수를 유리수로 반환, 기본값 = False positive=True: 해들 중에 양수만을 반환, 기본값 = False 예 $x^2=1$의 해를 결정합니다. solve() 함수에 적용하기 위해서는 다음과 같이 식의 한쪽이 0이 되는 형태인 동차식으로 구성되어야 합니다. $$x^2-1=0$$ import numpy as np from sympy import * x = symbols('x') solve(x**2-1, x) [-1, 1] 위 식은 계산 과정은 다음과 같습니다. $$\begin{aligned}x^2-1=0 \rightarrow (x+1)(x-1)=0 \\ x=1 \; \text{or}\; -1\end{aligned}$$ 예 $x^4=1$의 해를 결정합니다. solve() 함수의 인수 set=True를 지정하였으므로 결과는 집합(set)형으로 반환됩니다. eq=x**4-1 solve(eq, set=True) ([x], {(-1,), (-I,), (1,), (I,)}) 위의 경우 I는 복소수입니다.즉 위 결과의 과정은 다음과 같습니다. $$x^4-1=(x^2+1)(x+1)(x-1)=0 \rightarrow x=\pm \sqrt{-1}, \; \pm 1=\pm i,\; \pm1$$ 실수...