AI/ISLR

Chapter 04. 분류(Classification) - 실습

Dev_Kang 2024. 9. 2.

4.6 Lab: 로지스틱 회귀, LDA, QDA

4.6.1 주식시장 자료

ISLR 라이브러리 안에 Smarket이라는 자료가 있다. 이 자료는 2001 - 2005년까지 1, 250일에 걸친 S&P 500 주가지수의 수익률로 구성되며, 각 날짜에 그날 이전 5일의 각 거래일 Lag1에서 Lag5에 대한 수익률이 기록되어 있다. 또한, Volume(전날에 거래된 주식 수), Today(당일의 수익률), 그리고 Direction(당일 주가지수가 Up(상승) 또는 Down(하락)인지의 여부)이 기록되어 있다. 

library(ISLR)

names(Smarket) # 행이름 
[1] "Year"      "Lag1"      "Lag2"      "Lag3"      "Lag4"     
[6] "Lag5"      "Volume"    "Today"     "Direction"

dim(Smarket) # 행과 열의 개수
[1] 1250    9

summary(Smarket) # Smarket 요약
      Year           Lag1                Lag2          
 Min.   :2001   Min.   :-4.922000   Min.   :-4.922000  
 1st Qu.:2002   1st Qu.:-0.639500   1st Qu.:-0.639500  
 Median :2003   Median : 0.039000   Median : 0.039000  
 Mean   :2003   Mean   : 0.003834   Mean   : 0.003919  
 3rd Qu.:2004   3rd Qu.: 0.596750   3rd Qu.: 0.596750  
 Max.   :2005   Max.   : 5.733000   Max.   : 5.733000  
      Lag3                Lag4                Lag5         
 Min.   :-4.922000   Min.   :-4.922000   Min.   :-4.92200  
 1st Qu.:-0.640000   1st Qu.:-0.640000   1st Qu.:-0.64000  
 Median : 0.038500   Median : 0.038500   Median : 0.03850  
 Mean   : 0.001716   Mean   : 0.001636   Mean   : 0.00561  
 3rd Qu.: 0.596750   3rd Qu.: 0.596750   3rd Qu.: 0.59700  
 Max.   : 5.733000   Max.   : 5.733000   Max.   : 5.73300  
     Volume           Today           Direction 
 Min.   :0.3561   Min.   :-4.922000   Down:602  
 1st Qu.:1.2574   1st Qu.:-0.639500   Up  :648  
 Median :1.4229   Median : 0.038500             
 Mean   :1.4783   Mean   : 0.003138             
 3rd Qu.:1.6417   3rd Qu.: 0.596750             
 Max.   :3.1525   Max.   : 5.733000      

pairs(Smarket) # 산점도 행렬을 그리는 그래프이다.

그림 1: 산점도 행렬의 그림이다.

cor() 함수는 모든 설명 변수 쌍의 상관계수를 포함하는 행렬을 반환한다. 

cor(Smarket) # Error in cor(Smarket) : 'x' must be numeric
cor(Smarket[,-9]) # 상관계수
             Year         Lag1         Lag2         Lag3         Lag4         Lag5      Volume        Today
Year   1.00000000  0.029699649  0.030596422  0.033194581  0.035688718  0.029787995  0.53900647  0.030095229
Lag1   0.02969965  1.000000000 -0.026294328 -0.010803402 -0.002985911 -0.005674606  0.04090991 -0.026155045
Lag2   0.03059642 -0.026294328  1.000000000 -0.025896670 -0.010853533 -0.003557949 -0.04338321 -0.010250033
Lag3   0.03319458 -0.010803402 -0.025896670  1.000000000 -0.024051036 -0.018808338 -0.04182369 -0.002447647
Lag4   0.03568872 -0.002985911 -0.010853533 -0.024051036  1.000000000 -0.027083641 -0.04841425 -0.006899527
Lag5   0.02978799 -0.005674606 -0.003557949 -0.018808338 -0.027083641  1.000000000 -0.02200231 -0.034860083
Volume 0.53900647  0.040909908 -0.043383215 -0.041823686 -0.048414246 -0.022002315  1.00000000  0.014591823
Today  0.03009523 -0.026155045 -0.010250033 -0.002447647 -0.006899527 -0.034860083  0.01459182  1.000000000

Lag 변수와 당일의 수익률 사이에 상관관계는 0에 가깝다. 다시 말하면, 당일의 수익률와 그 전날의 수익률은 거의 관련이 없다. 유일하게 실질적으로 관련이 있는 것은 Year과 Volume이다. Volume은 시간이 지남에 따라 증가한다는 것을 알 수 있다. 즉, 2001년에서 2005년까지의 평균 일별 거래 주식 수가 증가한다. 

attach(Smarket) # attach을 사용하면 데이터 프레임의 변수들을 일일이 '$' 없이 접근할 수 있다. 
plot(Volume)

그림 2: Volume에 대한 그래프이다.

4.6.2 로지스틱 회귀

로지스틱 회귀를 적합하여 Lag1에서 Lag5와 Volume을 이용하여 Direction을 예측할 것이다. glm()함수는 로지스틱 회귀를 포함하는 일반화 선형 모델을 적합한다. lm()과 유사하며, 일반화 선형 모델의 다른 유형이 아니라 로지스틱 회귀를 실행하도록 family=binomial을 전달해야 한다. 

glm.fit = glm(Direction~Lag1+Lag2+Lag3+Lag4+Lag5+Volume, data = Smarket, family = binomial) # glm(formula(Output ~ Input), data, familiy)
summary(glm.fit)

Call:
glm(formula = Direction ~ Lag1 + Lag2 + Lag3 + Lag4 + Lag5 + 
    Volume, family = binomial, data = Smarket)

Coefficients:
             Estimate Std. Error z value Pr(>|z|)
(Intercept) -0.126000   0.240736  -0.523    0.601
Lag1        -0.073074   0.050167  -1.457    0.145
Lag2        -0.042301   0.050086  -0.845    0.398
Lag3         0.011085   0.049939   0.222    0.824
Lag4         0.009359   0.049974   0.187    0.851
Lag5         0.010313   0.049511   0.208    0.835
Volume       0.135441   0.158360   0.855    0.392

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 1731.2  on 1249  degrees of freedom
Residual deviance: 1727.6  on 1243  degrees of freedom
AIC: 1741.6

Number of Fisher Scoring iterations: 3

여기서 p-값이 가장 작은 것은 Lag 1이다. 이 설명 변수에 대한 계수가 음수인 것은, 어제의 수익률이 양수이면 오늘 주가지수가 상승할 가능성이 낮다는 것을 시사한다. 하지만 $0.15$의 p-값은 여전히 비교적 큰 값이므로 Lag1과 Direction 사이에 실질적인 상관성이 있다는 명백한 증거는 없다. 

 

coef() 함수를 사용하여 적합한 모델에 대한 계수들을 엑세스한다. 또한, summary() 함수를 사용하여 계수들의 p-값과 같은 적합된 모델의 특정 정보를 엑세스할 수 있다. 

coef(glm.fit) # 계수 출력
 (Intercept)         Lag1         Lag2         Lag3         Lag4         Lag5       Volume 
-0.126000257 -0.073073746 -0.042301344  0.011085108  0.009358938  0.010313068  0.135440659 

summary(glm.fit)$coef # 회귀 계수와 관련된 부분만 보여준다. 
                Estimate Std. Error    z value  Pr(>|z|)
(Intercept) -0.126000257 0.24073574 -0.5233966 0.6006983
Lag1        -0.073073746 0.05016739 -1.4565986 0.1452272
Lag2        -0.042301344 0.05008605 -0.8445733 0.3983491
Lag3         0.011085108 0.04993854  0.2219750 0.8243333
Lag4         0.009358938 0.04997413  0.1872757 0.8514445
Lag5         0.010313068 0.04951146  0.2082966 0.8349974
Volume       0.135440659 0.15835970  0.8552723 0.3924004

summary(glm.fit)$coef[,4] # 모든 행과 4열에 대해서만 보여준다.
(Intercept)        Lag1        Lag2        Lag3        Lag4        Lag5      Volume 
  0.6006983   0.1452272   0.3983491   0.8243333   0.8514445   0.8349974   0.3924004

 

predict() 함수는 주어진 설명 변수 값에 대해 주가지수가 상승할 확률을 예측하는 데 사용될 수 있다. type="response" 옵션을 사용하면 $P(Y=1|X)$ 형태의 확률을 출력한다. predict() 함수에 어떠한 자료도 주어지지 않으면 로지스틱 모델을 적합하는 데 사용되었던 훈련자료에 대해 확률이 계산된다. 첫 10개의 확률만 출력했다. 이 값들은 주가지수가 하락하는 것이 아니라 상승할 확률이다. constrants() 함수로 확인해보면 가변수는 Up일 때 $1$이기 때문이다. 

glm.probs = predict(glm.fit, type="response") #predict 함수: 적합된 모델을 사용하여 새로운 데이터에 대한 예측을 생성한다. 
glm.probs[1:10]
        1         2         3         4         5         6         7         8         9        10 
0.5070841 0.4814679 0.4811388 0.5152224 0.5107812 0.5069565 0.4926509 0.5092292 0.5176135 0.4888378 

contrasts(Direction) # contrasts 함수: 범주형 변수의 레벨을 확인한다. 
     Up
Down  0
Up    1

 

특정한 날의 주가지수가 상승할지 혹은 하락할지 예측하기 위해서는 예측된 확률들을 클래스 라벨 Up 또는 Down으로 변환해야 한다. 다음 두 명령어는 주가지수의 상승 예측 확률이 0.5보다 큰지 또는 작은지에 따라 클래스 예측 벡터를 생성한다. table() 함수를 사용하여 혼동행렬을 제공할 수 있으며, 얼마나 많은 관측치가 올바르게 또는 잘못 분류되었는지 결정할 수 있다. 

glm.pred = rep("Down", 1250) # 1250개의 Down 원소로 구성된 벡터를 생성한다. 
glm.pred[glm.probs > .5] = "Up" # 주가지수의 확률이 0.5보다 큰 경우 Up으로 바꾼다.

table(glm.pred,Direction) # 혼동행렬을 제공한다.
        Direction
glm.pred Down  Up
    Down  145 141
    Up    457 507

 

혼동행렬에서 대각원소들은 올바른 에측을 나타내고 비대각원소들은 잘못된 예측을 나타낸다. 따라서, 모델은 $652$일$(507+145)$을 정확하게 예측하였다. mean() 함수는 예측이 맞았던 날의 비율을 계산하는 데 사용할 수 있다. 로지스틱 회귀는 주가지수의 움직임 방향을 $52.2$% 올바르게 예측하였다.

(507+145) / 1250
[1] 0.5216
mean(glm.pred==Direction)
[1] 0.5216

 

언뜻 보기에 로지스틱 회귀모델은 임의 추측(random guess)보다 조금 더 나은 것 처럼 보인다. 하지만, 1,250개 관측치의 동일한 셋에 대해 모델을 훈련하고 검정했기 때문에 이 결과는 오해의 소지가 있다. 다시 말하면 $47.8$%는 훈련오차율이다. 훈련오차율은 검정 오차율을 과소평가하는 경향이 있다. 로지스틱 회귀모델의 정확도를 좀 더 잘 평가하려면 데이터의 일부를 이용하여 모델을 적합하고, 그 다음에 모델이 나머지 데이터를 얼마나 잘 예측하는지 조사할 수 있다. 이렇게 하면 좀 더 현실적인 오류율을 얻을 것이다. 

 

먼저 2001년에서 2004년까지의 관측치들에 대응하는 벡터를 생성해보자. 그다음에 이 벡터를 사용하여 2005년 관측치들로 구성된 데이터셋을 생성한다. 객체 train은 자료의 관측치들에 대응하는 $1,250$개의 원소를 가지는 벡터이다. 2005년 이전의 관측치에 대응하는 벡터 원소들은 TRUE로 설정, 2005년의 관측치에 대응하는 벡터 원소들은 FALSE로 설정된다. train은 boolean 벡터이다. 

 

명령어 Smarket[train, ]은 2005년 이전 날짜에 해당하는 주식시장 자료의 부분행렬을 선택할 것이다. 2005년 이전의 관측치에 대응하는 train 원소들만 TRUE이기 때문이다. 기호 !은 모든 원소들을 반대로 하는 데 사용될 수 있다. 

train = (Year < 2005) # 2005년 이전이면 TRUE, 2005년이면 FALSE
Smarket.2005 = Smarket[!train, ] # train 벡터에서 FALSE 값을 가진 행만 선택
dim(Smarket.2005)
[1] 252   9
Direction.2005 = Direction[!train]

 

2005년 이전의 관측치셋만 사용하여 로지스틱 회귀모델을 적합한다. 그 다음에 2005년 이전의 각 날짜에 대해 주가지수가 상승할 예측확률을 얻는다. 

glm.fit = glm(Direction~Lag1+Lag2+Lag3+Lag4+Lag5+Volume, data = Smarket, family = binomial, subset = train) # subset은 특정 행들만 사용하여 모델을 적합할 수 있다. 
glm.probs = predict(glm.fit, Smarket.2005, type="response") # Smarket.2005는 예측할 데이터이다.

glm.pred = rep("Down", 252) # 252개의 Down 원소로 구성된 벡터를 생성한다. 
glm.pred[glm.probs > .5] ="Up"
table(glm.pred, Direction.2005)
        Direction.2005
glm.pred Down Up
    Down   77 97
    Up     34 44
    
mean(glm.pred == Direction.2005)
[1] 0.4801587
mean(glm.pred != Direction.2005)
[1] 0.5198413

검정오차율이 $52$%로, 임의 추측보다 더 못하다. 로지스틱 회귀모델은 모든 설명변수에 대해 매우 실망스러운 p-값을 가지며, Lag 1에 대한 가장 작은 p-값도 그렇게 작지 않다. Direction을 예측하는 데 도움이 되지 않아 보이는 변수들을 제외함으로써 더 효율적인 모델을 얻을 수 있다. 결국, 반응변수와 상관관계가 없는 설명변수들을 사용하는 것은 검정 오차율을 악화시키는 경향이 있다. 따라서 이러한 설명변수들을 제외하는 것이 모델 개선을 이끌 수 있다. 

glm.fit  = glm(Direction~Lag1+Lag2, data = Smarket, family = binomial, subset = train) # 설명 변수들은 Lag1, Lag2만 사용한다. 
glm.probs = predict(glm.fit, Smarket.2005, type="response")
glm.preb = rep("Down", 252)
glm.preb[glm.probs > .5] = "Up"
table(glm.preb, Direction.2005)
        Direction.2005
glm.preb Down  Up
    Down   35  35
    Up     76 106
mean(glm.preb==Direction.2005)
[1] 0.5595238
106/(106+76)
[1] 0.5824176

결과가 약간 나아져 일별 주가지수 움직임의 $56$%를 올바르게 예측하였다. 전체 오류율 측면에서 로지스틱 회귀방법은 단순한 기법보다 나은 것이 없다. 하지만 혼동행렬을 보면 로지스틱 회귀가 주가지수 상승을 예측한 날짜에 대한 적중률은 58%이다. 이 결과를 보면 주가지수 상승을 예측한 날에는 주식을 사고 주가지수 하락이 예측된 날에는 거래를 피하는 것이 유효해 보인다.

 

Lag1과 Lag2의 특정값에 연관된 수익률을 예측한다고 해보자. 특히, Lag1 = 1.2, Lag2 = 1.1인 날짜와 Lag1 = 1.5, Lag2 = -0.8인 날짜의 Direction을 예측하고자 한다. 

predict(glm.fit, newdata=data.frame(Lag1 = c(1.2, 1.5), Lag2 = c(1.1, -0.8)), type = "response")
        1         2 
0.4791462 0.4960939

4.6.3 선형 판별 분석

Smarket 자료에 대해 LDA를 수행한다. LDA 모델을 적합하는 데는 MASS 라이브러리의 lda() 함수를 사용한다. lda() 함수의 문법은 lm() 과 동일하다. 

library(MASS)
lda.fit = lda(Direction~Lag1+Lag2, data = Smarket, subset = train)
lda.fit
Call:
lda(Direction ~ Lag1 + Lag2, data = Smarket, subset = train)

Prior probabilities of groups:
    Down       Up 
0.491984 0.508016 

Group means:
            Lag1        Lag2
Down  0.04279022  0.03389409
Up   -0.03954635 -0.03132544

Coefficients of linear discriminants:
            LD1
Lag1 -0.6420190
Lag2 -0.5135293
plot(lda.fit)

그림 3: plot(lda.fit) 결과

LDA 결과를 보면 $\hat\pi_1=0.492$이고 $\hat\pi_2=0.508$이다. 즉, 훈련 관측치들의 49.2%는 주가지수가 하락했던 날에 해당한다. 그룹 평균은 각 클래스 내 각 설명 변수의 평균이며, LDA는 이것을 $\mu_k$의 추정치로 사용한다. 그룹 평균들은 주가지수가 상승한 날 이전 이틀의 수익률은 음수이고 하락한 날 이전 이틀의 수익률은 양수인 경향이 있다는 것을 시사한다.

 

선형판별계수들은 LDA 결정규칙을 형성하는 데 사용되는 Lag1과 Lag2의 선형결합을 제공한다. 다시 말해, 선형판별계수들은 $X=x$에 곱해지는 승수(multiplier)들이다. 만약 $-0.642*Lag1 - 0.514*Lag2$가 크면 LDA 분류기는 주가지수 상승을 예측할 것이고, 그렇지 않다면 주가지수 하락을 예측할 것이다. plot() 함수는 훈련 관측치 각각에 대해 $-0.642*Lag1 - 0.514*Lag2$를 계산하여 얻은 선형판별 그래프를 제공한다. 

 

predict() 함수는 3개의 원소를 가진 리스트를 반환한다. 첫 번째 원소 class는 주가지수의 움직임에 대한 LDA의 예측을 포함한다. 두 번째 원소 posterior는 사후확률을 포함하는 행렬로, k번째 열은 대응하는 관측치가 K번째 클래스에 속하는 사후확률이다. 마지막으로, x는 앞에서 기술한 선형판별을 포함한다. 

lda.pred = predict(lda.fit, Smarket.2005)
names(lda.pred)
[1] "class"     "posterior" "x"  

lda.class = lda.pred$class
table(lda.class, Direction.2005)
         Direction.2005
lda.class Down  Up
     Down   35  35
     Up     76 106
     
mean(lda.class == Direction.2005)
[1] 0.5595238

사후확률에 50% 임계치를 적용하여 lda.pred$class에 포함된 예측을 다시 해본다.

sum(lda.pred$posterior[,1] >= .5)
[1] 70
sum(lda.pred$posterior[,1] < .5)
[1] 182
sum(lda.pred$posterior[,1] > .9) # 사후확률이 90%인 경우이다.
[1] 0

모델에 의해 출력된 사후확률은 주가지수가 하락할 확률에 대응한다는 것을 유념한다. 

lda.pred$posterior[1:20, 1]
      999      1000      1001      1002      1003      1004      1005      1006      1007      1008      1009      1010      1011 
0.4901792 0.4792185 0.4668185 0.4740011 0.4927877 0.4938562 0.4951016 0.4872861 0.4907013 0.4844026 0.4906963 0.5119988 0.4895152 
     1012      1013      1014      1015      1016      1017      1018 
0.4706761 0.4744593 0.4799583 0.4935775 0.5030894 0.4978806 0.4886331 
lda.class[1:20]
 [1] Up   Up   Up   Up   Up   Up   Up   Up   Up   Up   Up   Down Up   Up   Up   Up   Up   Down Up   Up  
Levels: Down Up

4.6.4 이차 판별 분석

Smarket 자료에 QDA 모델을 적합한다. QDA 적합은 MASS 라이브러리의 qda() 함수를 사용한다. 이 함수의 문법은 lda()와 동일하다.

qda.fit = qda(Direction~Lag1+Lag2, data = Smarket, subset = train)
qda.fit
Call:
qda(Direction ~ Lag1 + Lag2, data = Smarket, subset = train)

Prior probabilities of groups:
    Down       Up 
0.491984 0.508016 

Group means:
            Lag1        Lag2
Down  0.04279022  0.03389409
Up   -0.03954635 -0.03132544

적합에 대한 출력 결과는 그룹 평균들은 포함하지만 선형 판별 계수는 포함하지 않는다. QDA 분류기는 설명변수들의 1차 함수가 아니라 2차 함수에 관련되기 때문이다. predict() 함수는 LDA와 정확하게 동일한 방식으로 동작한다. 

qda.class = predict(qda.fit, Smarket.2005)$class
table(qda.class, Direction.2005)
         Direction.2005
qda.class Down  Up
     Down   30  20
     Up     81 121
mean(qda.class == Direction.2005)
[1] 0.5992063

흥미롭게도 QDA 설명변수들은 모델을 적합하는 데 2005년 데이터를 사용하지 않았음에도 거의 60%는 정확하다. 정확한 모델링이 매우 어려운 것으로 알려진 주식시장 데이터에 대해 이 정도의 수준의 정확도는 상당히 인상적이다. 주식시장에 베팅하기 전에 이 기법의 성능을 훨씬 더 큰 검정셋에 대해 평가해볼 것을 권한다. 

4.6.5 K-최근접 이웃

class 라이브러리의 knn() 함수를 사용하여 KNN을 수행할 것이다. knn()은 모델을 적합하고 그 다음에 모델을 사용하여 단일 명령어를 사용하여 예측한다. 이 함수는 4개의 입력이 필요하다. 

1. 훈련 데이터와 연관된 설명변수들을 포함하는 행렬. train.X로 표시

2. 예측하고자 하는 데이터와 연관된 설명변수들을 포함하는 행렬. test.X로 표시

3. 훈련 관측치들에 대한 클래스 라벨을 포함하는 벡터. train.Direction으로 표시

4. 분류기가 사용할 최근접 이웃의 수를 나타내는 K 값

 

cbind() 함수를 사용하여 Lag1과 Lag2 변수를 두 개의 행렬로 결합한다. 하나는 훈련셋, 다른 하나는 검정셋에 대한 것이다. 

library(class)
train.X = cbind(Lag1, Lag2)[train, ]
test.X = cbind(Lag1, Lag2)[!train, ]
train.Driection = Direction[train]

이제, 2005년 데이터에 대한 주가지수 움직임을 에측하는 데 knn() 함수를 사용할 수 있다. 적용하기 전에 랜덤 시드를 설정한다. 만약 여러 개의 관측치들이 동일하게 가장 가까운 이웃으로 판단되면 R은 임의로 하나를 선택할 것이다. 그러므로 동일한 결과를 재생산하려면 랜덤 시드를 설정해야 한다. 

set.seed(1)
knn.pred = knn(train.X, test.X, train.Driection, k=1)
table(knn.pred, Direction.2005)
        Direction.2005
knn.pred Down Up
    Down   43 58
    Up     68 83
(83+43)/252
[1] 0.5

knn.pred = knn(train.X, test.X, train.Driection, k=3)
table(knn.pred, Direction.2005)
        Direction.2005
knn.pred Down Up
    Down   48 54
    Up     63 87
(87+48)/252
[1] 0.5357143

K=1일 때에 비해 K=3을 이용하면 결과는 약간 나아졌다. 그러나, K를 더 높여도 결과가 더 개선되지는 않는다. 이 자료의 경우 QDA가 지금까지 조사한 방법들 중 가장 좋은 결과를 제공한다. 

4.6.6 Caravan 보험 자료에 적용

KNN기법을 ISLR 라이브러리에 포함되어 있는 Caravan 자료에 적용한다. 이 자료는 5,822명에 대한 인구 통계적 특징을 측정하는 85개의 설명변수를 포함한다. 반응변수는 Purchase로, 개인이 이동식 주택 보험을 구매했는지의 여부를 나타낸다. 이 자료에서는 단지 6%만이 보험을 구매하였다. 

dim(Caravan)
[1] 5822   86
attach(Caravan)
summary(Purchase)
  No  Yes 
5474  348 
348/5822
[1] 0.05977327

KNN 분류기에서는 변수들의 크기, 스케일(scale)이 문제가 된다. 스케일이 큰 변수들은 관측치들 간의 거리에 미치는 영향이 스케일이 작은 변수들보다 더 크므로 KNN 분류기에 미치는 영향이 더 크다. 두 변수 salary(1달러 단위)와 age(1년 단위)를 포함하는 자료를 생각한다. KNN에서 1,000달러의 급여차이는 50년의 나이 차이에 비해 엄청나게 큰 것이다. salary가 분류 결과를 결정하고 age는 거의 영향을 주지 못한다. 이것은 1천 달러의 급여 차이는 50년의 나이 차이에 비해 사소한 것이라는 직관가도 배치된다. 

 

salary를 일본 엔화로 측정하고 age를 분 단위로 측정하면 두 변수를 달러와 년 단위로 측정한 경우 얻은 것과는 상당히 다른 분류 결과를 얻을 것이다. 좋은 방법은 데이터를 표준화하여 모든 변수들이 평균이 0이고 표준편차가 1이 되게 하는 것이다. 그러면 모든 변수들이 비교 가능한 스케일이 될 것이다. scale() 함수가 이러한 역할을 한다. 데이터를 표준화할 때, 질적변수는 제외한다. 

standardized.X = scale(Caravan[,-86])
var(Caravan[,1])
[1] 165.0378
var(Caravan[,2])
[1] 0.1647078
var(standardized.X[,1])
[1] 1
var(standardized.X[,2])
[1] 1

관측치들을 분할하여 첫 1,000개의 검정셋에, 그리고 나머지는 훈련셋에 포함한다. K=1을 이용하여 훈련 데이터에 대해 KNN 모델을 적합하고, 성능은 검정 데이터를 사용하여 평가한다. 

test = 1:1000
train.X = standardized.X[-test,]
test.X = standardized.X[test,]
train.Y = Purchase[-test]
test.Y = Purchase[test]
set.seed(1)
knn.pred = knn(train.X, test.X, train.Y, k = 1)
mean(test.Y != knn.pred)
[1] 0.118
mean(test.Y != "No")
[1] 0.059

벡터 test는 1에서 1,000까지의 값을 가진 수치형이다. 1,000개의 검정 관측치에 대한 KNN 오류율은 12%가 조금 안된다. 언뜻보기에 이것은 꽤 좋은 결과인 것 같다, 하지만 고객 중 단지 6%만 보험을 구매하였으므로 설명변수 값에 상관없이 항상 No라고 예측하면 오류율은 6%로 내려갈 것이다. 

 

보험을 개인에게 판매하는 데는 적지 않은 어떤 비용이 있다고 가정한다. 보험 회사는 구매를 할 것 같은 고객에게만 보험 판매를 시도하고 싶을 것이며, 따라서 전체 오류율에는 관심이 없다. 대신에, 보험을 구입한다고 올바르게 에측되는 고객 비율에 관심이 있다. K=1인 KNN은 보험을 구입한다고 예측된 고객들 중에서는 임의 추측보다 훨씬 낫다. 보험 구입이 예측된 77명의 고객 중에서 9명, 즉 11.7%가 실제로 보험을 구입하였다. 이 비율은 임의 추측으로 얻을 수 잇는 것보다 2배나 높다. 

table(knn.pred, test.Y)
        test.Y
knn.pred  No Yes
     No  873  50
     Yes  68   9
9/(68+9)
[1] 0.1168831

K=3이면 성공율을 19%로 올라가고 K=5이면 26.7%가 된다. 이것은 임의 추측보다 4배 이상 높은 비율이다. 

knn.pred = knn(train.X, test.X, train.Y, k = 3)
table(knn.pred, test.Y)
        test.Y
knn.pred  No Yes
     No  920  54
     Yes  21   5
5/26
[1] 0.1923077
knn.pred = knn(train.X, test.X, train.Y, k = 5)
table(knn.pred, test.Y)
        test.Y
knn.pred  No Yes
     No  930  55
     Yes  11   4
4/15
[1] 0.2666667

비교를 위해 로지스틱 회귀모델을 이 데이터에 적합해 볼 수 있다. 분류기에 대한 예측확률 컷오프로 0.5를 사용한다면 문제가 발생한다. 즉, 검정 관측치 중 7명만 보험으 ㄹ구매한다고 예측된다. 에측확률이 0.25를 초과할 때 구매를 예측한다면 훨씬 더 나은 결과를 얻을 것이다. 즉, 33명이 구매한다고 에측하게 될 것이고, 이 중 약 33%는 옳다. 이것은 임의 추측보다 5배 이상 낫다.

glm.fit = glm(Purchase~., data = Caravan, family = binomial, subset = -test)
glm.probs = predict(glm.fit, Caravan[test, ], type="response")
glm.preb = rep("No", 100)
glm.preb[glm.probs > .5] = "Yes"
table(glm.preb, test.Y)
        test.Y
glm.preb No Yes
     No  95   5
     Yes  7   0

glm.preb = rep("No", 1000)
glm.preb[glm.probs > .25] = "Yes"
table(glm.preb, test.Y)
        test.Y
glm.preb  No Yes
     No  919  48
     Yes  22  11
11/(22+11)
[1] 0.3333333

Chapter04.R
0.00MB

댓글