[ADP] R로 하는 의사결정나무(Decision Tree) 모형

2020. 7. 24. 20:22ADP | ADsP with R/Knowledge

1. Decision Tree model 요약

 의사결정 나무는 간단하게 말해서 if~else와 같이 특정 조건을 기준으로 O/X로 나누어 분류/회귀를 진행하는 tree구조의 분류/회귀 데이터마이닝 기법이다.

 이해도가 매우 높고 직관적이라는 장점이 있다. 그렇기에 많이 사용되며, 의사결정나무도 많은 머신러닝 기법과 동일하게 종속변수의 형태에 따라 분류와 회귀 문제로 나뉜다.

 종속변수가 범주형일 경우 Decision Tree Classification으로 분류를 진행하고, 종속변수가 연속형일 경우 Decision Tree Regression으로 회귀를 진행한다. 

 자세한 원리와 과정은 아래 링크를 통해 학습을 실시하기 바란다. 이번 게시글에서는 R을 통하여 구현하는 과정을 진행하겠다. 

https://todayisbetterthanyesterday.tistory.com/39

 

[Data Analysis 개념] Decision Tree(의사결정나무) 모형 - Classification/Regression Tree의 직관적/수학적 이해

 이 게시글에서는 Decision Tree의 개념만 다룰 것이다. Python으로 구현하고자 한다면 아래 실습링크를 통해서 학습하길 바란다. https://todayisbetterthanyesterday.tistory.com/38 [Python] 의사결정나무(Dec..

todayisbetterthanyesterday.tistory.com

 

2. R로 하는 의사결정나무(Decision Tree model) 실습

 R에서 의사결정나무(Decision Tree)를 구현하는 패키지는 rpart와 party 패키지가 존재한다. 각각을 실습을 통해서 사용하는 방법을 알아보자.

1) rpart 패키지, rpart() 함수 활용 - 의사결정 분류나무(Decision Tree Classifier)

## 라이브러리 load
library(rpart)

# Decision Tree 모델 생성
c <- rpart(Species~., data=iris)
c

 위에서 간단하게 의사결정 나무를 생성하였다. 

# 시각화
plot(c, compress=T, margin=0.3)
text(c, cex=1.5)

# 예측
head(predict(c, newdata = iris, type = "class"))
tail(predict(c, newdata = iris, type = "class"))

 위의 newdata에는 기존의 iris데이터를 동일하게 넣었다. 실습의 편의상 이렇게 진행한다. 만약 데이터를 train/test로 분리를 시킨 상태에서 진행한다면 newdata 부분에 test데이터를 넣으면 될 것이다. 

# 시각화2
library(rpart.plot)
prp(c, type = 4, extra = 2)

 위의 경우는 rpart.plot이란 패키지를 설치하고 이 패키지를 통해서 다른 방식으로 시각화 작업을 진행한 것이다. 이를 해석해 보자면, Petal.Lenght >= 2.5이면서 Petal.Width <1.8인 노드가 해당한 개체는 54개인데, 이 중에서 versicolor의 개수가 49개임을 나타낸다. 그렇기에 추가적인 자료가 이 조건에 맞게 들어오게 된다면, (실제로는 다른 범주일지라도) versicolor로 범주를 분류할 것이다.

 

# rpart가 제공하는 정보
ls(c)

rpart를 통해서는 위와 같은 정보를 확인할 수 있다. 여기서 cptable은 트리의 크기(depth)에 따른 비용-복잡도 모수 (cost-complexity parameter / CP)를 제공하며, 교차타당성 오차(cross-validation error / xerror)를 함께 제공한다. 이 값들은 prune()또는 rpart.control()을 통한 가지치기(pruning)와 트리의 최대 크기를 제한하기 위한 옵션으로 사용된다. 

# cptable
c$cptable

 

 

# 교차타당성 오차를 최소화하는 Tree로 가지치기(pruning)
opt <- which.min(c$cptable[, "xerror"])
cp <- c$cptable[opt,"CP"]
prune.c <- prune(c, cp=cp)
plot(prune.c)
text(prune.c)

 위의 결과를 보면 이전과 다른 결과가 출력되지는 않았다. 데이터셋이 단순한 특성이 있기에 그럴 것이다. 

# CP(cost-complexity parameter) 그래프
plotcp(c)

 위의 경우는 cross validation error에 따른 cp값을 나타낸 것이다. 유의수준 아래로 내려온 cp는 size=3일때이기에 size=3의 tree가 가장 적당하다. 

 

2) party 패키지, ctree() 함수 활용 - 의사결정 분류나무(Decision Tree Classifier)

# 패키지 install 및 라이브러리 load
install.packages("party")
library(party)

# datasets
data(stagec)                  ## rpart 패키지에서 제공하는 데이터
str(stagec)

 위 데이터는 146명의 전립선 암 환자의 자료이다. 7개의 설명변수를 이용하여 범주형의 반응변수를 분류(예측)한다. 

# 데이터셋의 결측값 제거
stagec1 <- subset(stagec, !is.na(g2))
stagec2 <- subset(stagec1, !is.na(gleason))
stagec3 <- subset(stagec2, !is.na(eet))
str(stagec3)

 위의 과정을 통해 3가지 컬럼에 결측값이 존재하는 데이터를 모두 제거했다. 총 134개의 결측값이 제거된 데이터가 남았으며 이를 이용하여 모형에 적용할 것이다. 

# train/test split
set.seed(1)
ind <- sample(2, nrow(stagec3), replace = TRUE, prob = c(0.7,0.3))

trainData<-stagec3[ind==1,]
testData<-stagec3[ind==2,]

 train/test set으로 데이터를 나누는데 결과의 재현성을 고정시키기위해 seed 고정을 했다. 그리고 train/test 를 7:3으로 랜덤 복원추출하여 나누었다. 

# tree 적합
tree <- ctree(ploidy ~., data=trainData)
tree

# tree 도식화
plot(tree)

 위의 결과는 ctree()로 만들어낸 tree의 도식화이다. ctree를 적용하면 최종노드의 막대그래프를 통해서 반응변수(ploidy)의 각 범주별 비율을 알아낼 수 있다. 

 위의 문제를 보면 데이터에 대한 문제가 보인다. node4에서 diploid가 aneuploid보다 높은 비율을 가졌다. 만약 이 아래 child node가 존재하면 더 상세한 분류작업이 진행되기에 문제가 안될테지만, aneuploid는 다른 범주에도 속하지 못한다. 이때는 랜덤 복원추출이 아니라 train/test의 분포가 고루고루 섞이도록 층화추출법을 사용해서 데이터셋을 고르게 나눠주거나 tree의 정밀도를 높이거나 또는 다른 분류기법을 사용해야할 것으로 보인다. 

# 결과 예측과 confusion matrix를 통한 오분류표 확인
testPred = predict(tree, newdata = testData)
table(testPred, testData$ploidy)

 위의 경우는 test데이터에 aneuploid가 하나도 존재하지 않아서 정확도가 높은 것처럼 보이지만, 앞의 트리의 범주별 비율을 감안하면, 만약 test데이터에 aneuploid가 존재해도 제대로 분류를 못했을 것이다. 

 

3) party 패키지, ctree() 함수 활용 - 의사결정 회귀나무(Decision Tree Regressor)

 여태 종속변수가 범주형일때 의사결정나무에 대해서 알아보았다. 여태 한 의사결정나무는 정확히 말하면 의사결정 분류나무였다. 이번에는 종속변수가 연속형인 경우의 의사결정 나무에 대해서 알아보겠다. 

# 연속형 범주를 갖는 dataset
airq <- subset(airquality, !is.na(Ozone))
head(airq)

위의 코드는 반응변수(종속변수)인 Ozone에 결측치가 포함되어 있는 자료를 제외하고 저장한 데이터이다. 

# Decision Regression Tree 
airct <- ctree(Ozone ~., data = airq)
airct

 위의 결과를 보면 *가 존재하는 5개의 row를 확인할 수 있다. 이 row들이 최종노드를 나타낸다. 즉 leaf node가 5개인 트리모형을 생성한 것이다. 이를 도식화 하면 아래와 같다. 

# Decision Regression Tree plot
plot(airct)

 의사결정 분류나무와 도식화의 차이가 보이는가? 위의 의사결정 회귀나무를 보면 각 범주에 속한 비율이 나타난 것이 아니라, 개별 Node에서 boxplot(4분위 수/평균을 보여주는 plot)을 나타낸다. 이는 예측을 직접해보면 확실하게 느낄 수 있다. 

# predict
head(predict(airct, data=airq))

 이는 기존 학습데이터를 예측한 것이다. 이상한 숫자가 나온다고 생각했을 수도 있다. 이는 연속형 반응변수(종속변수)에 대한 예측값을 최종 마디에 속한 자료들의 평균값으로 예측을 하기 때문이다. 자료가 속한 해당 최종마디의 번호를 출력하고 싶다면, type = "node"를 사용해서 어떤 node에 속한지 확인할 수 있다. 

# 소속 노드를 확인
predict(airct, data=airq, type = "node")

 여기를 보면 3,5,6,8,9로 나누어진 것을 볼 수 있다. 3,5,6,8,9번째 노드들이 위의 그래프에서 확인해보면 leaf node였다. 연속형 변수의 경우는 분류문제가 아니기 때문에 confusion_matrix를 통해서 모델의 성능을 확인할 수 없다. 대신에 mse를 구한다. 

# MSE
mean((airq$Ozone - predict(airct))^2)

 

 이상 의사결정 분류/회귀 나무를 모두 알아보았다. 의사결정 나무모형은 구조가 단순하여 해석이 용이하다는 가장 큰 장점이 있다. 그렇기에 유용한 입력변수와 예측변수간의 상호작용을 잘 확인할 수 있으며, 동시에 선형성/정규성/등분산성 등 수학적 가정이 불필요한 비모수적 모형이기에 편리함이 존재한다. 

 하지만, 분류기준값의 경계선 근방의 자료값에 대해서는 오차가 크게 발생할 수 있으며(비연속성), 로지스틱회귀와 같이 각 예측변수의 효과를 파악하기 힘들고 새로운 자료에 대해 예측이 불안정할 수 있다. 

 하지만 직관적인 이해도가 가장 좋은 머신러닝 모델 중 하나이기에 산업에서 많이 쓰이고 있다.