일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 현대통신
- RS-485
- esp32
- Python
- 공모주
- 미국주식
- 코스피
- Bestin
- 티스토리챌린지
- 애플
- 파이썬
- matter
- 국내주식
- raspberry pi
- 나스닥
- 월패드
- Espressif
- cluster
- 홈네트워크
- Apple
- 배당
- SK텔레콤
- 오블완
- Home Assistant
- homebridge
- 해외주식
- MQTT
- 매터
- 힐스테이트 광교산
- ConnectedHomeIP
- Today
- Total
YOGYUI
빅데이터분석기사 실기 제2유형 문제 풀이 예시 (2) 본문
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유형 문제 풀이 예시 (2)
'Study > 자격증' 카테고리의 다른 글
빅데이터분석기사 실기 제2유형 문제 풀이 예시 Final (7) | 2021.06.13 |
---|---|
빅데이터분석기사 실기 제2유형 문제 풀이 예시 (3) (8) | 2021.06.12 |
빅데이터분석기사 실기 제2유형 문제 풀이 예시 (1) (7) | 2021.06.11 |
빅데이터분석기사 실기 예시문제 유형 분석 (0) | 2021.05.12 |
빅데이터분석기사 응시자격 심사 서류제출 (0) | 2021.05.11 |