YOGYUI

빅데이터분석기사 실기 제2유형 문제 풀이 예시 (2) 본문

Study/자격증

빅데이터분석기사 실기 제2유형 문제 풀이 예시 (2)

요겨 2021. 6. 11. 17:16
반응형

4. 데이터 전처리

훈련용 데이터와 테스트 데이터의 범주형 속성의 레벨이 동일한지 확인해보자

levels(X_train$주구매상품)
>
 [1] "가공식품"    "가구"        "건강식품"    "골프"        "구두"        "기타"        "남성 캐주얼" "남성 트랜디" "남성정장"    "농산물"      "대형가전"    "디자이너"    "란제리/내의"
[14] "명품"        "모피/피혁"   "보석"        "생활잡화"    "섬유잡화"    "셔츠"        "소형가전"    "수산품"      "스포츠"      "시티웨어"    "식기"        "아동"        "악기"       
[27] "액세서리"    "육류"        "일용잡화"    "젓갈/반찬"   "주류"        "주방가전"    "주방용품"    "차/커피"     "축산가공"    "침구/수예"   "캐주얼"      "커리어"      "통신/컴퓨터"
[40] "트래디셔널"  "피혁잡화"    "화장품"     

levels(X_test$주구매상품)
>
 [1] "가공식품"    "가구"        "건강식품"    "골프"        "구두"        "기타"        "남성 캐주얼" "남성 트랜디" "남성정장"    "농산물"      "대형가전"    "디자이너"    "란제리/내의"
[14] "명품"        "모피/피혁"   "보석"        "생활잡화"    "섬유잡화"    "셔츠"        "수산품"      "스포츠"      "시티웨어"    "식기"        "아동"        "악기"        "액세서리"   
[27] "육류"        "일용잡화"    "젓갈/반찬"   "주류"        "주방가전"    "주방용품"    "차/커피"     "축산가공"    "침구/수예"   "캐주얼"      "커리어"      "통신/컴퓨터" "트래디셔널" 
[40] "피혁잡화"    "화장품"

주구매상품의 경우 훈련용 데이터에는 '소형가전'이 있는데 테스트 데이터에는 존재하지 않아 두 데이터간 범주 레벨 차이가 발생한다 (주구매지점은 동일)

범주 속성의 레벨이 상이하기 때문에 분석 모델의 결과가 테스트 데이터에서는 의도하지 않은 방향으로 나올 가능성이 크기 때문에 이를 미리 처리해줘야 한다

# library(dplyr)
X_train %>% filter(주구매상품=='소형가전')
>  cust_id 총구매액 최대구매액 환불금액 주구매상품 주구매지점 내점일수 내점당구매건수 주말방문비율 구매주기
1    1521   178000     178000        0   소형가전     본  점        1              1            1        0
2    2035   260000     260000        0   소형가전     잠실점        1              1            0        0

y_train %>% filter(cust_id %in% c(1521, 2035))
>  cust_id gender genfac
1    1521      0   여자
2    2035      1   남자

 

2건밖에 되지 않기 때문에 제외해주는 것이 제일 처리하기 쉬운 방법이다

# library(dplyr)
X_train <- read.csv('./data/X_train.csv')
X_train <- X_train %>% filter(!cust_id %in% c(1521, 2035))
X_train$주구매상품 <- as.factor(X_train$주구매상품)
X_train$주구매지점 <- as.factor(X_train$주구매지점)
X_train[is.na(X_train$환불금액),]$환불금액 <- 0

y_train <- read.csv('./data/y_train.csv')
y_train <- y_train %>% filter(!cust_id %in% c(1521, 2035))

nrow(X_train)
> [1] 3498
nrow(y_train)
> [1] 3498

 

훈련용 데이터의 수치형 데이터는 모두 제각각의 범위를 가지고 있다

('주말방문비율'만 0.0 ~ 1.0 사이의 범위를 가지고 있음)

summary(X_train[,c(2,3,4,7,8,9,10)])
>    총구매액            최대구매액           환불금액            내점일수      내점당구매건수    주말방문비율        구매주기     
 Min.   : -52421520   Min.   : -2992000   Min.   :        0   Min.   :  1.00   Min.   : 1.000   Min.   :0.00000   Min.   :  0.00  
 1st Qu.:   4747050   1st Qu.:  2875000   1st Qu.:        0   1st Qu.:  2.00   1st Qu.: 1.667   1st Qu.:0.02729   1st Qu.:  4.00  
 Median :  28222700   Median :  9837000   Median :        0   Median :  8.00   Median : 2.333   Median :0.25641   Median : 13.00  
 Mean   :  91919252   Mean   : 19664242   Mean   :  8289786   Mean   : 19.25   Mean   : 2.835   Mean   :0.30725   Mean   : 20.96  
 3rd Qu.: 106507930   3rd Qu.: 22962500   3rd Qu.:  2642250   3rd Qu.: 25.00   3rd Qu.: 3.375   3rd Qu.:0.44898   3rd Qu.: 28.00  
 Max.   :2323180070   Max.   :706629000   Max.   :563753000   Max.   :285.00   Max.   :22.083   Max.   :1.00000   Max.   :166.00 

훈련용 데이터의 수치형 데이터들을 모두 유사한 범위 (0~1)를 갖게 만들기 위해 '정규화(normalization)'을 적용하자

func_norm <- function(x) {
    return (x - min(x)) / (max(x) - min(x))
}

 

함수로 구현하는게 일반적이지만, 훈련용 데이터 계산 시 사용한 각 속성별 최소값, 최대값을 모두 저장하고 동일하게 테스트용 데이터 정규화 시 동일하게 사용해야 하므로 코드가 길어지고 실수가 발생할 가능성이 존재한다

 

시험장 환경에서는 caret 패키지가 제공되니 이를 활용하도록 하자

(caret 패키지의 preProcess 함수를 사용)

library(caret)
model_proc <- preProcess(X_train[,c(-1)], method='range')
X_train_proc <- predict(model_proc, X_train)
X_test_proc <- predict(model_proc, X_test)

preProcess함수 인자 method='range'는 데이터를 정규화하는 것을 말한다

(method=c("center", "scale") 사용 시 표준화 - standarization)

고객 아이디 (cust_id)는 정규화할 필요 없으니 인덱싱으로 제외해준다

model_proc
> Created from 3500 samples and 9 variables

Pre-processing:
  - ignored (2)
  - re-scaling to [0, 1] (7)

두 개의 범주형 속성 (주구매상품, 주구매지점)을 제외한 7개의 속성이 정규화되었다 (주말방문비율은 굳이 할 필요는 없다만...)

summary(X_train_proc)
>    cust_id          총구매액         최대구매액          환불금액           주구매상품      주구매지점      내점일수        내점당구매건수     주말방문비율        구매주기      
 Min.   :   0.0   Min.   :0.00000   Min.   :0.000000   Min.   :0.000000   기타    : 595   본  점  :1077   Min.   :0.000000   Min.   :0.00000   Min.   :0.00000   Min.   :0.00000  
 1st Qu.: 874.8   1st Qu.:0.02406   1st Qu.:0.008268   1st Qu.:0.000000   가공식품: 546   잠실점  : 474   1st Qu.:0.003521   1st Qu.:0.03162   1st Qu.:0.02729   1st Qu.:0.02410  
 Median :1749.5   Median :0.03395   Median :0.018079   Median :0.000000   농산물  : 339   분당점  : 436   Median :0.024648   Median :0.06324   Median :0.25641   Median :0.07831  
 Mean   :1749.5   Mean   :0.06076   Mean   :0.031927   Mean   :0.014705   화장품  : 264   부산본점: 245   Mean   :0.064274   Mean   :0.08703   Mean   :0.30725   Mean   :0.12625  
 3rd Qu.:2624.2   3rd Qu.:0.06690   3rd Qu.:0.036575   3rd Qu.:0.004687   시티웨어: 213   영등포점: 241   3rd Qu.:0.084507   3rd Qu.:0.11265   3rd Qu.:0.44898   3rd Qu.:0.16867  
 Max.   :3499.0   Max.   :1.00000   Max.   :1.000000   Max.   :1.000000   디자이너: 193   일산점  : 198   Max.   :1.000000   Max.   :1.00000   Max.   :1.00000   Max.   :1.00000  
                                                                          (Other) :1350   (Other) : 829                                                                           
summary(X_test_proc)
>    cust_id        총구매액          최대구매액           환불금액           주구매상품     주구매지점     내점일수        내점당구매건수     주말방문비율        구매주기      
 Min.   :3500   Min.   :0.006306   Min.   :-0.048544   Min.   :0.000000   기타    :465   본  점  :726   Min.   :0.000000   Min.   :0.00000   Min.   :0.00000   Min.   :0.00000  
 1st Qu.:4120   1st Qu.:0.024204   1st Qu.: 0.008281   1st Qu.:0.000000   가공식품:395   잠실점  :352   1st Qu.:0.003521   1st Qu.:0.03557   1st Qu.:0.02346   1st Qu.:0.02410  
 Median :4740   Median :0.034913   Median : 0.019369   Median :0.000000   농산물  :235   분당점  :328   Median :0.028169   Median :0.06787   Median :0.25000   Median :0.07831  
 Mean   :4740   Mean   :0.064594   Mean   : 0.034895   Mean   :0.015903   화장품  :177   부산본점:168   Mean   :0.065200   Mean   :0.08630   Mean   :0.29381   Mean   :0.12221  
 3rd Qu.:5361   3rd Qu.:0.075285   3rd Qu.: 0.041246   3rd Qu.:0.005276   시티웨어:168   일산점  :158   3rd Qu.:0.090669   3rd Qu.:0.11265   3rd Qu.:0.42357   3rd Qu.:0.16265  
 Max.   :5981   Max.   :1.226493   Max.   : 0.840191   Max.   :1.545915   디자이너:123   영등포점:150   Max.   :0.778169   Max.   :0.70553   Max.   :1.00000   Max.   :1.06627  
                                                                          (Other) :919   (Other) :600     

정규화된 훈련용 데이터 X_train_proc은 모든 cust_id를 제외한 모든 수치형 데이터가 0 ~ 1 사이로 정규화되었으며, 정규화에 사용된 각 속성별 최소/최대값을 동일하게 테스트용 데이터에 적용한 X_test_proc을 보면 값의 범위가 0 ~ 1 사이가 아닌 것을 알 수 있다

 

5. 데이터 클래스 불균형 해소

훈련용 데이터의 클래스는 남자가 1315개, 여자가 2183개로 1:1.66 비율로 불균형이 존재한다

DMwR 패키지의 SMOTE(Synthetic Minority Oversampling TEchnique)를 보통 사용하는데, 시험장에서는 제공되지 않으니 간단히 caret 패키지의 upSample 함수를 사용하기로 한다

(수가 적은 클래스의 데이터를 추출 후 중복시켜 수가 많은 클래스와 비율을 1:1로 맞추는 작업)

# library(caret)
set.seed(210612)

temp <- X_train_proc
temp$genfac <- y_train$genfac
temp2 <- upSample(subset(temp, select=-genfac), temp$genfac)
table(temp2$Class)
> 
남자 여자 
2183 2183

X_train_proc <- subset(temp2, select=-Class)
y_train <- subset(temp2, select=c(cust_id, Class))
names(y_train) <- c('cust_id', 'genfac')
y_train$gender <- ifelse(y_train$genfac == '남자', 1, 0)

gender 속성을 포함하는 데이터프레임 temp를 임시로 생성한 뒤, temp$genfac속성을 기준으로 적인 클래스(남자)의 데이터를 중복해 클래스 여자와 수를 맞춘 데이터프레임 temp2를 생성했다 (기준 속성의 데이터형은 factor여야 한다!) -> 남자와 여자의 클래스 수가 2184로 동일해진 것을 확인

합쳐진 데이터프레임을 다시 원래대로 나눠준다 (X_train_proc, y_train으로)

 

수치 데이터 정규화 및 클래스 불균형이 해소된 데이터로 로지스틱 회귀모델을 다시 구동해보자

model_glm <- glm(y_train$gender ~ .-cust_id, data=X_train_proc, family='binomial')
y_pred_glm <- predict(model_glm, newdata=X_train_proc, type='response')
pred_glm <- prediction(y_pred_glm, y_train$gender)
performance(pred_glm, "auc")@y.values[[1]]
> [1] 0.6985133

AUC는 0.6985133으로 측정되었으며, 앞서 전처리 전 데이터로 모델링했을 때의 AUC가 0.6952498였으니 약 0.47%의 분류 성능 개선이 이루어졌다... 거의 효과가 없다고 볼수 있지만, 시험에서는 부분점수를 획득할 수 있는 부분이니 준비해가도록 하자 (downSample은 왠만하면 쓰지 않을 예정)

(개인적인 경험상 불균형 비율이 5배 이상 벌어졌을 때 드라마틱한 효과가 나왔던 사례가 몇번 있다)

 

6. 랜덤포레스트

랜덤포레스트용 패키지인 randomForest 패키지 사용법을 숙지해가도록 하자

(시험 문제에 나와있는 '앙상블' 활용은 랜덤포레스트만 사용해도 충분할 듯?)

library(randomForest)
set.seed(210612)

model_rf <- randomForest(y_train$genfac ~ .-cust_id, X_train_proc)
model_rf
>
Call:
 randomForest(formula = y_train$genfac ~ . - cust_id, data = X_train_proc) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 3

        OOB estimate of  error rate: 19.88%
Confusion matrix:
     남자 여자 class.error
남자 1722  461   0.2111773
여자  407 1776   0.1864407

randomForest 함수의 학습 대상은 범주형 데이터 (y_train$genfac)을 넣고, 로지스틱 회귀분석과 마찬가지로 cust_id는 분석 속성에서 제외해준다

model_rf를 프린트해보면 혼동행렬(confusion matrix)이 표시되므로 모델의 분류 정확도를 파악할 수 있다

(Accuracy = (1722 + 1776) / (2183 + 2183) = 0.801)

트리 개수는 500개 default값이 사용되었다

 

plot(model_rf)

트리 개수가 50개 이상부터는 out-of-bag(OOB) 오차 감소의 효과가 그리 크지 않은 것을 알 수 있다

 

varImpPlot(model_rf)

varImpPlot 함수로 변수중요도를 시각화해서 볼 수 있다

주구매상품이 성별 판별에 가장 주요한 변수로 작용하는 것을 알 수 있다 (ex: 남성정장 = 남성)

그런데 주구매지점이 4번째 주요 변수로 작용하는 것은 아무래도 찝찝하다 (어떤 지역은 특정 성별이 유난히 많이 방문하나?, 이게 내점일수나 구매주기보다 유의하다고??)

 

평균지니지수감소량을 정량적으로 파악하기 위해서는 importance 함수를 사용하면 된다

(caret 패키지의 varImp 함수를 사용해도 된다)

importance(model_rf)
> 
               MeanDecreaseGini
총구매액               292.6481
최대구매액             290.7912
환불금액               132.0358
주구매상품             395.0070
주구매지점             287.0893
내점일수               184.0269
내점당구매건수         211.4887
주말방문비율           193.1024
구매주기               173.2738

 

ROC 커브는 로지스틱 회귀모델과 같은 방식으로 구하면 된다

y_pred_rf <- predict(model_rf, newdata=X_train_proc, type='prob')
y_pred_rf_man <- y_pred_rf[,'남자']
pred_rf <- prediction(y_pred_rf_man, y_train$gender)
perf_rf <- performance(pred_rf, measure='tpr', x.measure='fpr')
plot(perf_rf)
abline(0, 1)

ROC 커브를 그려보면 거의 1에 가까운 AUC를 갖는 것을 볼 수 있다

(사실 학습용 데이터로 학습한 뒤에 학습용 데이터를 대상으로 ROC 커브 그리면 위와 같이 나오는게 정상이긴 하다..)

performance(pred_rf, "auc")@y.values[[1]]
> [1] 0.9999996

 

 

랜덤포레스트 모델로 테스트 데이터 분류 결과를 추출하는 건 로지스틱 회귀모델과 동일한 방식으로 구현하면 된다 (분류 결과가 '남자'일 확률에 대한 벡터만 추출)

y_pred_test_rf <- predict(model_rf, newdata=X_test_proc, type='prob')[,'남자']

 

[시리즈]

빅데이터분석기사 실기 제2유형 문제 풀이 예시 (1)

빅데이터분석기사 실기 제2유형 문제 풀이 예시 (2)

빅데이터분석기사 실기 제2유형 문제 풀이 예시 (3)

빅데이터분석기사 실기 제2유형 문제 풀이 예시 Final

반응형