본문 바로가기

문제풀이

머신러닝: 개인 과제 풀이 및 해설 정리

시나리오
- 여러분은 S모 금융회사에 입사하였습니다. 올해 프로젝트로 회사의 주 수입원인 이자 매출 증대를 위해서 대출 권유 텔레마케팅을 진행할 예정입니다. 하지만 회사 고객이 1000만명이 넘는 대기업이기 때문에 모든 고객에게 텔레마케팅을 진행하는 것은 비용(돈, 시간)적인 방면에서 효율적이지 못합니다.
- 데이터 분석그룹 소속인 여러분은 마케팅을 효율적으로 수행하기 위한 방안으로  “대출 할 것 같은” 고객을 사전에 선별하라는 지시를 받았습니다. 입사 후 첫 프로젝트이기 때문에 직접 회사 내 데이터로 예측모델링을 수행하기 전에 앞서 포르투칼 은행 데이터 기반으로 파일럿 프로젝트를 수행하여 고객 정보를 바탕으로 대출 실행 예측을 하려 합니다.

 

데이터 설명 및 출처
- 해당 데이터는 포르투칼 은행이 진행한 전화마케팅 콜을 시행하고 대출 유무 여부를 기록한 데이터 입니다. 총 16개의 변수와 45,211의 관측치를 가지고 있습니다.
  · UCI 데이터 저장소: https://archive.ics.uci.edu/dataset/222/bank+marketing
  · Github: https://github.com/uci-ml-repo/ucimlrepo

 

문제1: 라이브러리를 통해 데이터 불러오기


- 다음코드는 데이터를 불러오고 df 변수명에 저장하는 코드입니다.
- github 문서를 읽고 데이터를 요청하여 로컬환경에 저장해보세요.

# 데이터 불러오기
# pip install ucimlrepo
from ucimlrepo import fetch_ucirepo 
bank_marketing = fetch_ucirepo(id=222) 
  
# df 변수명에 저장  
# df = bank_marketing.data.original #통합 파일만 불러와도 ok

X = bank_marketing.data.features 
y = bank_marketing.data.targets 

df = pd.concat([X, y], axis=1)   #2개 파일 병합할 경우 concat 활용

# csv 파일로 로컬환경에 저장 
df.to_csv('bank_marketing.csv', index=False)  #불러오기 시 iindex 중복을 막고자 제외 설정 

df.head(3)

 

문제 2: Y 변수 인코딩 적용하기 


- Y 라벨을 no,yes를 사용자 정의함수와 apply를 이용하여 0,1로 인코딩 하세요.
- 함수명은 get_binary로 설정하세요.
- Pandas docs: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.apply.html

 

def get_binary(x):
    """
    주어진 문자열 x가 'no'일 경우 0을 반환하고, 그렇지 않을 경우 1을 반환합니다.
    이 함수는 일반적으로 두 가지 범주(예: 'yes'와 'no')를 가진 데이터를 이진 형식(0과 1)으로 변환하는 데 사용됩니다.

    Args:
        x (str): 변환할 문자열. 'no' 또는 그 외의 값을 가질 수 있습니다.
    Returns:
        int: 문자열 x가 'no'일 경우 0, 그렇지 않을 경우 1을 반환합니다.
    """
    if x == 'no':
        return 0
    else:
        return 1

# y_train 데이터 인코딩 코드
y_train['y'] = y_train['y'].apply(get_binary)

# y_test 데이터 인코딩코드
y_test['y'] = y_test['y'].apply(get_binary)

#잘 적용되었는지 확인
display(y_train[:10])

 

(추가) 라벨인코더로y값 변환하기 

from sklearn.preprocessing import LabelEncoder

le = LabelEncoder()
le.fit(df_target['y'])
df_target['y_le'] = le.transform(df_target['y']) #숫자는 자동으로 적용됨

 

문제3: 간단한 모델링 & 평가함수 생성하기

 

- 평가하기 위한 다음 함수를 완성하세요.
- 필요한 모듈율 불러오고, 학습시키고, 결과를 저장하세요.
    - y_pred_train : 훈련데이터 예측결과 변수
    - y_pred_test: 테스트데이터 예측결과 변수

def get_score(train:pd.DataFrame,  test:pd.DataFrame, x_var_list:list):
    """ train과 test 데이터와 X변수 컬럼을 받아 평가지표를 내는 함수입니다.

    Args:
        train (pd.DataFrame): train 데이터프레임
        test (pd.DataFrame): test 데이터프레임
        x_var_list (list): 모델링에 사용할 변수 리스트
    """
    #외부 전달인자를 내부변수에 할당
    X_train = train 
    X_test = test

    #일부 컬럼만 가져오기
    X_train = X_train[x_var_list]
    X_test = X_test[x_var_list]
    
    #모듈불러오기
    from sklearn.ensemble import RandomForestClassifier
    from sklearn.metrics import accuracy_score, f1_score

    #모델가져오기 & 학습하기
    model_rf = RandomForestClassifier(random_state=42)
    model_rf.fit(X_train, y_train)
        
    # 학습하여 결과 저장
    y_pred_train = model_rf.predict(X_train)
    y_pred_test = model_rf.predict(X_test)
    
    #평가표 생성
    result = pd.DataFrame({'acc' : [accuracy_score(y_train, y_pred_train), accuracy_score(y_test, y_pred_test)],
                            'f1_score' : [f1_score(y_train, y_pred_train), f1_score(y_test, y_pred_test)]},
                            index = ['train','test'])
    
    display(result.round(2))

# duration 변수만 사용하여 결과내기
get_score(X_train, X_test, ['duration'])

 

> 문제 풀이 과정에서 로지스틱회귀 모델만 적용하여 문제에서 제시한 결과 값과 계속 다르게 나옴 

 : 데이터 분석의 목적은 좋은 모델을 찾아서 예측값을 높이는 것임을 간과하고 하나의 모델 결과값만 수차례 확인

> 추후 각 데이터 특성에 맞는 여러 모델을 한 번씩 돌려보는 연습 필요 

 

(추가) 랜덤포레스트 모델에서 각 변수별 중요도 보는 법

# features 이름과 중요도에 대한 함수 입력
rf.feature_names_in_
rf.feature_importances_

 

문제4: 모델링 수행하기


- 전체 변수를 가공하여 예측모델링을 수행하는 함수 get_numeric_sc를 완성해보세요.

def get_numeric_sc(X_train:pd.DataFrame, X_test:pd.DataFrame):
    """데이터를 전달받아 수치형 변수 스케일링하는 함수

    Args:
        X_train (pd.DataFrame): train 데이터프레임
        X_test (pd.DataFrame): test 데이터프레임

    Returns:
        pd.DataFrame, pd.DataFrame: train, test 데이터프레임
    """
    # 수치형변수
    # age, balance, day_of_week, duration, campaign, pdays,previous
    
    #StandardScaler 적용할 변수 리스트
    sc_col = ['pdays','previous']
    #MinMaxScaler 적용할 변수 리스트
    mm_col = ['age','duration','day_of_week','balance','campaign']
    
    #모듈 불러오기
    from sklearn.preprocessing import StandardScaler, MinMaxScaler
    
    #모델 가져오기
    #정답은
    ## 모델은 한 번만 불러와도 됨 
    ## sd_sc = StandardScaler()
    ## mm_sc_train = MinMaxScaler()

    sd_sc_train = StandardScaler()
    sd_sc_test = StandardScaler()
    mm_sc_train = MinMaxScaler()
    mm_sc_test = MinMaxScaler()
    
    #정답은
    ## 학습과 변환은 동시에! 
    ## X_train[sc_col] = sc.fit_transform(X_train[sc_col])
    ## X_test[sc_col] = sc.transform(X_test[sc_col])
    ## X_train[mm_col] = mm.fit_transform(X_train[mm_col])
    ## X_test[mm_col] = mm.transform(X_test[mm_col])

    #train, test 데이터변환(Standard Scaler이용)
    sd_sc_train.fit(X_train[sc_col])
    X_train[sc_col] = sd_sc_train.transform(X_train[sc_col])

    sd_sc_test.fit(X_test[sc_col])
    X_test[sc_col] = sd_sc_test.transform(X_test[sc_col])
    
    #train, test 데이터변환(MinMax Scaler이용)
    mm_sc_train.fit(X_train[mm_col])
    X_train[mm_col] = mm_sc_train.transform(X_train[mm_col])

    mm_sc_test.fit(X_test[mm_col])
    X_test[mm_col] = mm_sc_test.transform(X_test[mm_col])
    
    return X_train, X_test

X_train, X_test = get_numeric_sc(X_train, X_test)

 

 

 

기타: 문제 코드 복습  

 

1) 데이터 분리 

더보기
# 제공받은 데이터를 train, test로 분리
X = df.drop(columns = ['y'])
y = df[['y']]

# 학습과 평가를 위해 데이터 셋 분리
# 순서 매우매우 중요
# 기본 test_size = 0.3
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify = y, random_state= 42)

#분리된 데이터 차원 확인
print(X_train.shape, X_test.shape, y_train.shape, y_test.shape)

2) 다수의 변수 한 번에 시각화 하는 함수 

더보기
#수치형 변수 시각화
def get_3_hist(data:pd.DataFrame, columns:list):
    """ 데이터와 컬럼을 전달하면 히스토그램을 3개씩 출력해주는 함수

    Args:
        data (pd.DataFrame): 데이터프레임
        columns (list): 컬럼 리스트
    """
    
    # 3열씩 출력되도록 len(columns) 활용 
    plt.figure(figsize=(15, 5 * (len(columns) // 3)))

    for i, col in enumerate(columns):
        # 서브플롯 위치 설정(행수, 열수, 위치)
        plt.subplot(len(columns) // 3 + (len(columns) % 3 > 0), 3, i + 1)
        # 히스토그램 그리기
        sns.histplot(data[col])
        plt.title(col)

    # 전체 그림 표시
    plt.tight_layout()
    plt.show()
get_3_hist(X_train, numeric_col)

 

3) 로그스케일 적용

- 로그함수 y = \log_\a x (a>0, a≠1) 기본적으로 지수함수 y = a^x (a > 0, a ≠ 1)의 역함수 

① x값은 항상 양수(x > 0)

② y값은 실수가 되며, 무조건 (0, 1)을 지남

③ log 밑 값에 따라 그래프가 달라짐

- numpy에서 np.log1p(x) 함수

: 로그 함수는 x=0이면 y는 −∞의 값을 가지므로, x+1을 넣어 x의 0을 1로, y의 −∞값을 0으로 바꿔주는 함수

: np.log(x+1)도 같은 결과값을 보임 

더보기
# 이상치가 많은 컬럼에 대해서 로그스케일 적용
# 로그스케일 적용을 위해서는 음수가 있으면 안됨 
# balance의 음수 값 보정
balance_min = abs(min(X_train['balance'].min(), X_test['balance'].min()))
X_train['balance'] = X_train['balance'] + balance_min
X_test['balance'] = X_test['balance'] + balance_min


# 로그스케일 적용
for col in ['duration','balance','previous']:
    X_train[col] = np.log1p(X_train[col])
    X_test[col] = np.log1p(X_test[col])

4) 범주형 칼럼 더미화 하는 함수 

더보기
def get_category(X_train:pd.DataFrame, X_test:pd.DataFrame):
    """ 데이터를 전달받아 범주형 변수 더미화하는 함수

    Args:
        X_train (pd.DataFrame): train 데이터프레임
        X_test (pd.DataFrame):  test 데이터프레임

    Returns:
        pd.DataFrame, pd.DataFrmae, list: train, test 데이터프레임, 더미화된 컬럼
    """
    
    #범주형변수
    # 'job','marital','education','default','housing','loan','contact','month','poutcome'
    
    #범주형 컬럼 더미화 하기
    X_train_dummies = pd.get_dummies(X_train[category_col])
    X_test_dummies = pd.get_dummies(X_test[category_col])
    
    # 더미화한 변수를 기존 데이터셋에 합치기
    X_train = pd.concat([X_train, X_train_dummies], axis = 1)
    X_test = pd.concat([X_test, X_test_dummies], axis = 1)
    
    return X_train, X_test, X_train_dummies.columns.to_list()
    

X_train, X_test, col_dummies = get_category(X_train,X_test)

5) test 데이터에 대한 분포 시각화 

더보기
#train, test 데이터의 라벨 수 세기 
y_train_counts = y_train.value_counts()
y_test_counts = y_test.value_counts()

datasets = [(y_train, 'Class Distribution in y_train'),
            (y_test, 'Class Distribution in y_test')]

# subplot 생성
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(6, 3))

# 반복문을 사용하여 각 데이터셋의 클래스 분포 시각화
for i, (dataset, title) in enumerate(datasets):
    value_counts = dataset.value_counts()
    axes[i].pie(value_counts, labels=value_counts.index, autopct='%1.1f%%', startangle=140)
    axes[i].set_title(title)

# 그래프 보여주기
plt.tight_layout()
plt.show()

 

6) SMOTE 알고리즘을 활용한 oversampling 적용 

- y결과값에서 yes(1)의 비중이 적어서 제대로 된 학습이 어려움 > 오버샘플링을 통해 모델 재학습

더보기
#최초 1회 실행 후 주석처리
# !pip install imbalanced-learn

from imblearn.over_sampling import SMOTE
sm = SMOTE(random_state=42)
X_train, y_train = sm.fit_resample(X_train, y_train)
X_test, y_test = sm.fit_resample(X_test, y_test)