Deep Learning + AI

데이터분석응용 in R_로지스틱 회귀 pt2 실습260415

내인생왜이래 2026. 4. 19. 16:20
반응형

# 이전에 데이터부터 싹 깔끔히 날려주고

# 새로운 시작!

rm(list=ls())

 

 

# install.packages("caret")
# install.packages("glmnet")

# 혹시 안깔아뒀다면 스근하게 깔아주고~~

 

 

# 라이브러리 호출!

library(tidyverse)
library(caret) # 데이터 분할용
library(glmnet) # 로지스틱 회귀

 

 

# 여기에서 ID, ZIP Code 열을 제거한 후

# Personal.Loan 을 제외한 열들로 Personal.Loan을 예측해보겠다.

 

 

# 1. 데이터 로드 및 기본 전처리
bank_df <- read.csv("./datasets/UniversalBank.csv")

universal

 

 

# ID, ZIP Code 제거
bank_df <- bank_df %>% select(-ID, -'ZIP.Code')

 

# 참고로 ID, ZIP Code는 

# Personal.Loan 예측에 필요없을것으로 보이니 먼저 제거하려는 것이다.

 

 

# 열이름의 공백을 언더스코어로 변경
(names(bank_df) <- gsub("\\.", "_", names(bank_df)))

# .은 있는지도 없는지도 모르겠으니 일괄적으로 미리 변경해주는것이다.

# 괄호도 쳐서 결과값 출력도!

 [1] "Age"               
 [2] "Experience"        
 [3] "Income"            
 [4] "Family"            
 [5] "CCAvg"             
 [6] "Education"         
 [7] "Mortgage"          
 [8] "Personal_Loan"     
 [9] "Securities_Account"
[10] "CD_Account"        
[11] "Online"            
[12] "CreditCard"   

 

 

 

# bank_df의 구조 확인
str(bank_df)

'data.frame':	5000 obs. of  12 variables:
 $ Age               : int  25 45 39 35 35 37 53 50 35 34 ...
 $ Experience        : int  1 19 15 9 8 13 27 24 10 9 ...
 $ Income            : int  49 34 11 100 45 29 72 22 81 180 ...
 $ Family            : int  4 3 1 1 4 4 2 1 3 1 ...
 $ CCAvg             : num  1.6 1.5 1 2.7 1 0.4 1.5 0.3 0.6 8.9 ...
 $ Education         : int  1 1 1 2 2 2 2 3 2 3 ...
 $ Mortgage          : int  0 0 0 0 0 155 0 0 104 0 ...
 $ Personal_Loan     : int  0 0 0 0 0 0 0 0 0 1 ...
 $ Securities_Account: int  1 1 0 0 0 0 0 0 0 0 ...
 $ CD_Account        : int  0 0 0 0 0 0 0 0 0 0 ...
 $ Online            : int  0 0 0 0 0 1 1 0 1 0 ...
 $ CreditCard        : int  0 0 0 0 1 0 0 1 0 0 ...

 

 

 

# 특정 열을 범주형으로 변환하기

 

# Education에서 숫자들이 의미하는게 고졸, 대졸, 석박졸..(?)임을 인지한다면,

# 이것들은 범주형으로 취급되야한다고 생각할 수 있겠다.

# 현재 int로 되어 있으니 바꿔보자!

 

 

# 우선 Education이 가지는 값 종류부터 체크

table(bank_df$Education)

   1    2    3 
2096 1403 1501 

# 예상대로 고졸, 대졸, 석박졸로 분류된게 맞다. 

 

 

# 라벨을 붙여서 범주형으로 변환하자.

bank_df <- bank_df %>%
  mutate(Education = factor(Education,
                            levels = c(1, 2, 3), 
                            labels = c("Undergrad", "Graduate", "Advanced/Professional")))

 

 

# 잘 되었는지 확인!!

str(bank_df)

'data.frame':	5000 obs. of  12 variables:
 $ Age               : int  25 45 39 35 35 37 53 50 35 34 ...
 $ Experience        : int  1 19 15 9 8 13 27 24 10 9 ...
 $ Income            : int  49 34 11 100 45 29 72 22 81 180 ...
 $ Family            : int  4 3 1 1 4 4 2 1 3 1 ...
 $ CCAvg             : num  1.6 1.5 1 2.7 1 0.4 1.5 0.3 0.6 8.9 ...
 $ Education         : Factor w/ 3 levels "Undergrad","Graduate",..: 1 1 1 2 2 2 2 3 2 3 ...
 $ Mortgage          : int  0 0 0 0 0 155 0 0 104 0 ...
 $ Personal_Loan     : int  0 0 0 0 0 0 0 0 0 1 ...
 $ Securities_Account: int  1 1 0 0 0 0 0 0 0 0 ...
 $ CD_Account        : int  0 0 0 0 0 0 0 0 0 0 ...
 $ Online            : int  0 0 0 0 0 1 1 0 1 0 ...
 $ CreditCard        : int  0 0 0 0 1 0 0 1 0 0 ...

 

 

 

# 아참. 훈련돌리기전에 필수적인 사항.

# Personal_Loan도 범주형으로 변환해야한다.

# 로지스틱 회귀를 돌리기 위한 전제조건이기 때문이다!

 

# 우선 값의 종류를 한번 보자.

table(bank_df$Personal_Loan)


   0    1 
4520  480 

# 이항분류하기 적합!

 

 

# 범주화한 후 구조 재출력해보자

bank_df$Personal_Loan <- factor(bank_df$Personal_Loan)
str(bank_df)

'data.frame':	5000 obs. of  12 variables:
 $ Age               : int  25 45 39 35 35 37 53 50 35 34 ...
 $ Experience        : int  1 19 15 9 8 13 27 24 10 9 ...
 $ Income            : int  49 34 11 100 45 29 72 22 81 180 ...
 $ Family            : int  4 3 1 1 4 4 2 1 3 1 ...
 $ CCAvg             : num  1.6 1.5 1 2.7 1 0.4 1.5 0.3 0.6 8.9 ...
 $ Education         : Factor w/ 3 levels "Undergrad","Graduate",..: 1 1 1 2 2 2 2 3 2 3 ...
 $ Mortgage          : int  0 0 0 0 0 155 0 0 104 0 ...
 $ Personal_Loan     : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 2 ...
 $ Securities_Account: int  1 1 0 0 0 0 0 0 0 0 ...
 $ CD_Account        : int  0 0 0 0 0 0 0 0 0 0 ...
 $ Online            : int  0 0 0 0 0 1 1 0 1 0 ...
 $ CreditCard        : int  0 0 0 0 1 0 0 1 0 0 ...

 

 

# 기본 세팅 끝

 

 

 

# 종속변수(y)와 독립변수(X) 분리해보자.

 

y <- bank_df$Personal_Loan
X <- bank_df %>% select(-Personal_Loan)

 

 

 

# 훈련/검증 데이터 분할하자. (훈련:검증 = 60:40)

 

set.seed(1)

# set.seed(1)는 실험할적마다 결과 안바뀌게 할려고

 

train_index <- createDataPartition(y, p = 0.6, list = FALSE)


# createDataPartition 함수는 단순히 데이터를 나누는게 아니라
# y의 클래스 비율을 유지하면서 데이터를 나눠줌
# list = false를 하면 나중에 index를 벡터 형태로 반환해줌

 

 

# 뽑은걸 슬쩍 봐보자.

head(train_index)

     Resample1
[1,]         1
[2,]         3
[3,]         4
[4,]         5
[5,]         6
[6,]         7

 

 

# 잘 나온것 같으니,,

# 6:4로 분할한 index 차례로 df에 넣어주자.

# train, test, 그리고 그들의 y값에도 넣어줘야 6:4 완료다!

 

train_df <- bank_df[train_index, ]
valid_df <- bank_df[-train_index, ]

 

train_y <- bank_df$Personal_Loan[train_index]
valid_y <- bank_df$Personal_Loan[-train_index]

 

 

 

# 로지스틱 회귀 모델 적합해보자


logit_reg <- glm(Personal_Loan ~ ., 
                 data = train_df, 
                 family = binomial(link = "logit"))

# family = binomial(link = "logit")는 
# 종속변수가 이항분포를 가지며
# 로짓변환을 이용한다는 뜻. 
# 즉, 로지스틱 회귀

 

 

# 결과 출력해보자

summary(logit_reg)


Call:
glm(formula = Personal_Loan ~ ., family = binomial(link = "logit"), 
    data = train_df)

Coefficients:
                                 Estimate Std. Error z value Pr(>|z|)    
(Intercept)                    -1.307e+01  2.314e+00  -5.650 1.61e-08 ***
Age                            -1.021e-02  8.477e-02  -0.120 0.904150    
Experience                      2.014e-02  8.448e-02   0.238 0.811601    
Income                          6.068e-02  3.901e-03  15.554  < 2e-16 ***
Family                          6.713e-01  9.958e-02   6.741 1.57e-11 ***
CCAvg                           1.569e-01  5.868e-02   2.673 0.007517 ** 
EducationGraduate               3.906e+00  3.414e-01  11.443  < 2e-16 ***
EducationAdvanced/Professional  3.968e+00  3.392e-01  11.700  < 2e-16 ***
Mortgage                        9.171e-04  7.688e-04   1.193 0.232914    
Securities_Account             -1.326e+00  4.161e-01  -3.188 0.001432 ** 
CD_Account                      3.872e+00  4.295e-01   9.015  < 2e-16 ***
Online                         -6.895e-01  2.098e-01  -3.287 0.001012 ** 
CreditCard                     -8.785e-01  2.608e-01  -3.368 0.000757 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 1897.2  on 2999  degrees of freedom
Residual deviance:  720.4  on 2987  degrees of freedom
AIC: 746.4

Number of Fisher Scoring iterations: 8

# Income, Family, CCAvg,,,CreditCard 등 유의미한 변수가 많다~~~

 

 

# 회귀 계수들만 따로 추출해보기. 절편 제외

coef(logit_reg)[-1] 

                           Age                     Experience 
                 -0.0102081878                   0.0201372747 
                        Income                         Family 
                  0.0606773777                   0.6712530553 
                         CCAvg              EducationGraduate 
                  0.1568570150                   3.9064261503 
EducationAdvanced/Professional                       Mortgage 
                  3.9681371558                   0.0009170549 
            Securities_Account                     CD_Account 
                 -1.3264514304                   3.8720628033 
                        Online                     CreditCard 
                 -0.6895199295                  -0.8785267112 

 

 

 

# 만들어진 모델로 확률값 추출해보자


predictions <- predict(logit_reg, newdata = valid_df, type = "response")

# 검증 데이터를 모델에 넣어 예측 확률을 계산
# type = "response"는 예측값을 확률로 반환하라는 뜻


head(predictions, 20)

           2            8           10           13           17           19 
3.845908e-05 3.493688e-04 9.762645e-01 1.674083e-01 9.020834e-01 9.943503e-01 
          21           23           24           27           28           29 
4.677389e-03 1.116869e-04 3.441551e-05 1.915253e-01 1.671022e-02 1.213129e-03 
          34           35           36           37           40           43 
2.364308e-03 1.779875e-02 2.368770e-03 4.574626e-03 1.179604e-01 7.687487e-01 
          49           50 
4.436226e-02 4.056307e-05 

 

 

# 이제 컷오프로 분류하기!

predictions_binary <- ifelse(predictions >0.5, 1, 0)

 

# 이제 실제답과 예측한 값 비교하기!
data.frame(valid_y, predictions_binary)

     valid_y predictions_binary
2          0                  0
8          0                  0
10         1                  1
13         0                  0
17         1                  1
19         1                  1
21         0                  0
23         0                  0
24         0                  0
27         0                  0
28         0                  0
29         0                  0
34         0                  0
35         0                  0
36         0                  0
37         0                  0
40         0                  0
43         1                  1
49         0                  0
50         0                  0
52         0                  0
54         1                  1
55         0                  0
57         0                  0
58         1                  1
61         0                  0
66         0                  0
71         0                  0
74         0                  0
77         0                  0
82         0                  0
84         0                  0
85         0                  0
87         0                  0
89         0                  0
90         0                  0
100        0                  0

# 등등 밑 부분 생략
# 제일 좋은 상황은 p_b가 v_y와 완전히 일치하는 것

 

 

 

# 성능평가

 

# 훈련데이터 성능평가

logit_reg_pred_train <- predict(logit_reg, newdata = train_df, type = "response")
logit_reg_pred_train_class <- ifelse(logit_reg_pred_train > 0.5, 1, 0)

confusionMatrix(factor(logit_reg_pred_train_class), 
                 factor(train_y), positive = "1")

 

# 입력값이 반드시 범주형이여야함을 유의하자!!!!
# 우리는 1을 찾는데 관심이 있으므로, 1을 positve으로 설정한것.

Confusion Matrix and Statistics

          Reference
Prediction    0    1
         0 2678   92
         1   34  196
                                          
               Accuracy : 0.958           
                 95% CI : (0.9502, 0.9649)
    No Information Rate : 0.904           
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.7341          
                                          
 Mcnemar's Test P-Value : 3.815e-07       
                                          
            Sensitivity : 0.68056         
            Specificity : 0.98746         
         Pos Pred Value : 0.85217         
         Neg Pred Value : 0.96679         
             Prevalence : 0.09600         
         Detection Rate : 0.06533         
   Detection Prevalence : 0.07667         
      Balanced Accuracy : 0.83401         
                                          
       'Positive' Class : 1               
                                          

 

 

# 검증 데이터 성능 평가
logit_reg_pred_valid <- predict(logit_reg, newdata = valid_df, type = "response")
logit_reg_pred_valid_class <- ifelse(logit_reg_pred_valid > 0.5, 1, 0)
confusionMatrix(factor(logit_reg_pred_valid_class), 
                 factor(valid_y), positive = "1")

Confusion Matrix and Statistics

          Reference
Prediction    0    1
         0 1787   58
         1   21  134
                                         
               Accuracy : 0.9605         
                 95% CI : (0.951, 0.9686)
    No Information Rate : 0.904          
    P-Value [Acc > NIR] : < 2.2e-16      
                                         
                  Kappa : 0.751          
                                         
 Mcnemar's Test P-Value : 5.115e-05      
                                         
            Sensitivity : 0.6979         
            Specificity : 0.9884         
         Pos Pred Value : 0.8645         
         Neg Pred Value : 0.9686         
             Prevalence : 0.0960         
         Detection Rate : 0.0670         
   Detection Prevalence : 0.0775         
      Balanced Accuracy : 0.8432         
                                         
       'Positive' Class : 1              
                                         

 

 

# train 성능 > valid 성능인 경우 과적합의 가능성이 있다
# train 성능과 valid 성능이 비슷하다면 일반화 성공했다고 볼 수 있다.

# 성능의 지표라 볼 수 있는 Accuracy가 0.958 vs 0.9605 라서

# 후자다. 굿!

 

 

# confusion Matrix에 쓰인 성능 지표들,,

# 이해하기 쉽게 예시와 곁들였다.

 

# 1. accuracy : (TP + TN) / (TP + TN + FP + FN)

# 2. sensitivity (recall) : TP / (TP + FN)
# 민감도/재현율

# P인것들 중 실제 P인 비율이니, 1-sensitivity는 잠재고객

# 3. specificity : TN / (TN + FP)
# 특이도

# N인것들 중 실제 N 비율이니, 대출 안할 고객을 맞힌 비율.

# 4. Precision : TP / (TP + FP)
# Pos Pred Value(정밀도)
# 예상 잠재(전체 P) 중 실제 고객 비율(T인 P)

# 5. Balanced Accuracy : (Sensitivity + Specificity) / 2
# 불균형 데이터에서 가장 믿을만한 지표
# 민감도와 특이도의 평균치
# 모델의 전반적인 균형 감각

 

 

 

# 추가적인 Insight

# 1. 검증 데이터를 보아하니,,

 

#           Reference
# Prediction    0    1
#           0 1787   58
#           1   21  134

 

# 확실하지 않으면
# 안할거라고 예측하는 경향이 있다.

 

 

# 2. 수익 극대화가 목적이면
# 컷오프 임계값을 조정해보자.

반응형