빅데이터분석기사
실기 분류

제2회 빅데이터 분석기사(실기) - 작업형:제2유형(파이썬) 2 분석

작성자 정보

  • ◆딥셀◆ 작성
  • 작성일

컨텐츠 정보

본문

이 번에는 작업현:제2유형 문제에 대해 약간의 분석을 해보겠습니다.
우선 데이터에 대하여 좀 더 자세하게 분석하고 적절한 피처 엔지니어링을 해 보겠습니다. 지난 번 글에서 설명한 베이스라인 보다 스코어를 조금 이라도 올리는 것이 목표입니다.

제공된 테스트 데이터에 대한 정답(label)이 없으므로 자체적으로 training data를 나누어서 모델의 성능을 측정해 보겠습니다.

주요 작업 내용
1) EDA, 남녀 데이터 비율 분석
2) 데이터 분리 : 트레이닝 데이터셋과 밸리드 데이터셋으로 분리
3) 밸리드 데이터셋을 이용하여 베이스라인의 스코어 측정
4) 특성공학(feature engineering) : 데이터 상관관계 분석을 통한 특징 추출 
5) 데이터 전처리
6) 모델 트레이닝 
7) 결과 평가

먼저 baseline 코드의 방식을 그대로 사용하고 training 데이터 중 500개를 밸리데이션으로 이용하여 측정한 ROC-AUC Score는 0.617이었습니다. 이 값이 어떤 의미가 있는지는 중간에 잠시 언급하겠습니다. 확인하기 위한 상세한 코드는 따로 적지 않겠습니다.(내용은 별 것이 없는데 글이 길어질 것 같아서요.)

1) EDA, 남녀 데이터 비율 분석
EDA(Exploring Data Analysis; 탐색적 테이터 분석)은 간단하세 해보겠습니다. 참고로 시험 환경에서는 GUI를 사용할 수 없습니다. 그래서 데이터 시각화를 이용한 분석은 할 수 없습니다. 아래 코드와 같이 데이터를 읽고 간단한 특징을 살펴 봅니다.
import os
import pandas as pd

data_dir = 'data'

X_train_file = os.path.join(data_dir, 'X_train.csv')
Y_train_file = os.path.join(data_dir, 'y_train.csv')
X_test_file = os.path.join(data_dir, 'X_test.csv')

x_train_df = pd.read_csv(X_train_file, encoding='euc-kr') # 다운로드한 데이터는 euc-kr 인코딩 필요
y_train_df = pd.read_csv(Y_train_file, encoding='euc-kr')

x_train_df = x_train_df.iloc[:, 1:] # 고객 번호 제거
x_train_df['gender'] = y_train_df['gender'] # label을 트레이닝 데이터에 합침, 데이터 분리를 위해서.
위와 같이 데이터프레임으로 데이터를 로드한 뒤 간단하게 특성을 살펴 봅니다.
먼저 궁금한 것이 남성과 여성의 비율입니다. 데이터 균형 문제는 다루기가 까다롭기도 하고 정확도가 의미가 있는지 비교하는 기준이 되기도 합니다.
female_no, male_no = y_train_df['gender'].value_counts()
female_ratio = female_no / (female_no + male_no)
print('female ratio : ', str(female_ratio)) # 0.624

 

위 코드를 실행하여 여성의 비율을 보면 0.624가 나옵니다. 트레이닝 데이터에 있는 전체의 62.4%가 여성입니다. 무조건 여성이라고 예측을 해도 62.4%의 정확도(Accuracy)가 나온다는 말이고 62.4%보다 작은 정확도를 나타내는 시스템은 쓸모가 없다는 의미이기도 합니다. 물론 ROC-AUC Score는 의미가 다르니까 계산하면 조금 다른 결과가 나올 것 같습니다. 대략 이런 감을 가지고 시작하면 될 것 같습니다.

아래와 같이 기본적인 방법으로 데이터의 특징을 살펴 봅니다.
x_train_df.info() 
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3500 entries, 0 to 3499
Data columns (total 10 columns):
 #   Column   Non-Null Count  Dtype  
---  ------   --------------  -----  
 0   총구매액     3500 non-null   int64  
 1   최대구매액    3500 non-null   int64  
 2   환불금액     1205 non-null   float64
 3   주구매상품    3500 non-null   object 
 4   주구매지점    3500 non-null   object 
 5   내점일수     3500 non-null   int64  
 6   내점당구매건수  3500 non-null   float64
 7   주말방문비율   3500 non-null   float64
 8   구매주기     3500 non-null   int64  
 9   gender   3500 non-null   int64  
dtypes: float64(3), int64(5), object(2)
memory usage: 273.6+ KB

위 결과에서 주목해야 할 내용은 전체 데이터의 수가 3500개인데 환불금액은 1205개 라는 것(즉 결측 데이터 존재)과 '주구매상품'과 '주구매지점'의 데이터가 숫자 타입이 아니라는 점입니다.

아래와 같은 코드로 첫 5개 개의 레코드를 볼 수 있습니다.

x_train_df.head() 
05c9d3b1b177336fdbcbd8222306a42d_1621328801_1487.PNG
 
위 결과를 보면 환불금액에 NaN(데이터 없으)이 보이고 '주구매상품'과 '주구매지점'이 문자열 데이터인 것을 볼 수 있습니다.
이 데이터가 처리의 대상이 됩니다.
그리고 아래 명령으로 수치 데이터들에 대한 통계값을 봅니다.
x_train_df.describe() 
05c9d3b1b177336fdbcbd8222306a42d_1621328992_127.PNG
이 결과에서는 '총구매액'과 '최대구매액'의 최소값이 마이너스인 것을 주목해야 합니다. 내용을 알 수 없으므로 이런 데이터는 제거하는 것이 좋을 것 같습니다.

2) 데이터 분리 : 트레이닝 데이터셋과 밸리드 데이터셋으로 분리 
데이터를 분리하기 전에 간단한 처리를 먼저 합니다. 다른 처리는 언제 해도 결과가 같지만 레코드를 제거하는 것은 먼저하는 것이 좋습니다. 
# 결측 데이터 처리, 환불금이 없으므로 0으로 대체
x_train_df = x_train_df.fillna(0)

# Pandas의 factorize : sklearn의 라벨 인코딩과 유사 ( 빈도 순으로 인코딩 )
x_train_df.loc[:, "주구매상품"] = pd.factorize(x_train_df["주구매상품"])[0].reshape(-1,1)
x_train_df.loc[:, "주구매지점"] = pd.factorize(x_train_df["주구매지점"])[0].reshape(-1,1)

# 총구매액이 음수인 레코드 제거
index_to_drop = x_train_df[x_train_df['총구매액'] <= 0].index
x_train_df = x_train_df.drop(index_to_drop)
결측 데이터를 처리하고 문자열 데이터를 숫자로 변환하고 이상데이터(총구매액이 음수인 데이터)를 제거하였습니다.
남은 데이터의 3,000개를 트레이닝 데이터로 사용하고 나머지를 밸리데이션 데이터로 사용하기 위해 분리합니다.

x_data_df = x_train_df.iloc[:, :-1]
y_data_df = x_train_df[['gender']]

from sklearn.model_selection import train_test_split

# train_test_split
x_train, x_valid, y_train, y_valid = train_test_split(x_data_df, y_data_df, test_size=490/3490, shuffle=True, stratify=y_data_df, random_state=34)
3) 밸리드 데이터셋을 이용하여 베이스라인의 스코어 측정 
이 상태에서 모델을 만들고 트레이닝을 하고 성능을 측정해 봅니다.
from sklearn.linear_model import LogisticRegression #Logistic(Regression)Classifier
model_lr = LogisticRegression()
model_lr.fit(x_train , y_train)
predict_proba_lr = model_lr.predict_proba(x_valid)

from sklearn.metrics import roc_auc_score
print("Logistic Classifier", "'s ROC AUC is ", roc_auc_score(y_valid, predict_proba_lr[:, 1])) 
결과가 0.6416 정도 나옵니다. 아무것도 하지 않은 베이스라인 코드의 결과(0.617)보다 좋아 졌습니다. '총구매액'이 음수 값을 갖는 이상데이터 10건을 제거했을 뿐인데도 성능이 향상되었습니다.

4) 특성공학(feature engineering) : 데이터 상관관계 분석을 통한 특징 추출
이제 데이터의 변수(feature)들 중 성별과 상관관계가 높은 변수를 찾아 봅니다. 간단하게 pandas의 .corr() 메써드를 사용하면 변수들 간의 상관계수를 구할 수 있습니다.
x_train_df.corr() 
05c9d3b1b177336fdbcbd8222306a42d_1621342531_3267.PNG 
결과는 각 변수별 상관계수를 보여줍니다. 필요한 것은 성별(gender)과의 상관계수이므로 맨 마지막 줄에서 상관계수가 큰 변수(feature)를 선택하면 됩니다.
결과를 보면 상관계수가 큰 변수가 없습니다. 절대값이 0.1보다 큰 변수들을 선택하면 다음과 같습니다. 
'총구매액', '최대구매액', '환불금액', '내점일수', '주구매상품'
이 변수들만으로 트레이닝을 합니다. 여기서 주의할 점이 있습니다. '주구매상품'은 숫자로 인코딩을 하였지만 범주형 변수입니다. 따라서 크기나 비율의 개념이 없기 때문에 다른 변수처럼 취급하면 안됩니다. 이럴 때 더미 변수를 사용합니다.
다음 코드는 성별과 상관계수가 상대적으로 큰 변수를 선택하고 범주형 데이터인 '주구매상품'을 더미 변수로 변환한 후 트레이닝 데이터와 밸리데이션 데이터로 분할하는 코드입니다.
x_data_df = x_train_df[['총구매액', '최대구매액', '환불금액', '내점일수', '주구매상품']]
y_data_df = x_train_df[['gender']]

x_data_df = pd.concat([x_data_df, pd.get_dummies(x_train_df['주구매상품'], prefix='g', drop_first=True)], axis=1)
x_data_df.drop(['주구매상품'], axis=1)

from sklearn.model_selection import train_test_split

# train_test_split
x_train, x_valid, y_train, y_valid = train_test_split(x_data_df, y_data_df, test_size=490/3490, shuffle=True, stratify=y_data_df, random_state=34)
5) 데이터 전처리 
다음과 같이 Min-Max 스케일러로 데이터를 변환합니다.
# 데이터 스케일링 : Min-Max 스케일 변환
from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
scaler.fit(x_train)

x_train_scaled = scaler.transform(x_train)
x_train_scaled

x_valid_scaled = scaler.transform(x_valid)
x_valid_scaled
6) 모델 트레이닝 
from sklearn.linear_model import LogisticRegression #Logistic(Regression)Classifier

model_lr = LogisticRegression()
model_lr.fit(x_train_scaled , y_train) 
7) 결과 평가 
predict_proba_lr = model_lr.predict_proba(x_valid_scaled)

from sklearn.metrics import roc_auc_score
print("Logistic Classifier", "'s ROC AUC is ", roc_auc_score(y_valid, predict_proba_lr[:, 1])) 
이 코드의 실행 결과로 ROC-AUC Score가 0.65 나옵니다. 
성능이 약간 향상되었습니다. 제한된 시간과 시험 환경을 생각하면 나쁘지 않은 결과인 것 같습니다.

8) RandomForest 
위에서 준비된 데이터를 가지고 RandomForest를 모델로하여 트레이닝하고 예측해보겠습니다.
from sklearn.ensemble import RandomForestClassifier #Random Forest
model_rf = RandomForestClassifier(max_leaf_nodes=32)
model_rf.fit(x_train_scaled , y_train)

predict_proba_rf = model_rf.predict_proba(x_valid_scaled)
print("Random Forest", "'s ROC AUC is ", roc_auc_score(y_valid, predict_proba_rf[:, 1]))
이렇게 RandomForest 분류기를 모델로하여 예측한 결과의 스코어는 대략 0.674 정도가 나옵니다. 성능이 조금 더 향상되었습니다.
다른 모델과 다른 하이퍼파라미터로 시도해 보시고 더 좋은 성능이 나온 것이 있으면 댓글로 알려 주시면 고맙겠습니다.

다음에는 이 코드를 정리하여 실전용 파이썬 파일을 만드는 것으로 마무리 하겠습니다.

관련자료

댓글 7

JUNEYOUNGCHEUN님의 댓글

  • JUNEYOUNGCHEUN
  • 작성일
질문 있습니다~
# Pandas의 factorize : sklearn의 라벨 인코딩과 유사 ( 빈도 순으로 인코딩 )이라면
value_counts()로 출력했을 때 가장 많은 빈도를 차지하는 데이터가 0또는 가장 큰 숫자로 나와야 하는 거 아닌가요?
대입해서 풀어봤는데 빈도 순대로 숫자가 인코딩되지 않아서요... 제가 입문자라서... 설명해주시면 감사하겠습니다~

◆딥셀◆님의 댓글의 댓글

  • ◆딥셀◆
  • 작성일
위 문장이 조금 부정확한 것 같습니다. tactorize가 빈도순 이라는 의미입니다. sklearn의 라벨인코딩은 빈도순이 아닌 것 같습니다. 유사하다고 한 것은 범주 하나에 숫자 하나를 대응 시켰다는 의미입니다. 좀 더 디테일하게 설명하지 못해서 죄송합니다.
그리고 좀 더 생각해 보니 범주형 데이터를 라벨 인코딩하는 것은 적절하지 않는 것 같습니다. 빨리 할 수 있고 스코에에 크게 영향을 주지 않기 때문에 급하게 써먹을 수 있는 방법이라고 생각하고 쓴 것 입니다.
적절한 방법에 대해 다시 정리해 올리겠습니다.

이원응님의 댓글

  • 이원응
  • 작성일

◆딥셀◆님의 댓글의 댓글

  • ◆딥셀◆
  • 작성일
급하게 머신러닝을 돌릴려고 모든 데이터를 쉽게 숫자로 바꿀려고 라벨인코딩을 하였습니다만 사실 범주형 데이터를 라벨인코딩(factorize 포함)하는 것은 적절하지 않은 것 같습니다.
예제의 경우 어떻게 해도 스코어가 잘 나오지 않아서 라벨인코딩을 한 것과 더미 변수를 사용한 것의 차이가 나지 않는데 제대로 할려면 더미 변수를 사용하는 것이 맞는 것 같습니다.(One-Hot-Encoding)
테스트 좀 해보고 전체적으로 FM 대로 풀이하는 코드를 올리도록 하겠습니다.
생각할 것들이 많고 복잡해서 시간이 좀 걸리네요.
감사합니다.

김민수님의 댓글

  • 김민수
  • 작성일
저 궁금한게 총구매액은 알겠는데 최대구매액이 의미하는 바가 무엇일까요?
최대 구매액이 한도라면 환불금액이 0원일 때 총구매액이 최대구매액을 넘을 수가 없을텐데 총 구매액이 항상 큽니다.
금액이 높을수록 단순 상관계수는 높아지는데 이 컬럼을 넣어도 되는건지 헷갈리네요

◆딥셀◆님의 댓글의 댓글

  • ◆딥셀◆
  • 작성일
데이터에 대한 자세한 설명이 없어서 제 나름대로 추측해 보았습니다.

최대구매액 : 여러 번의 구매 중 가장 큰 값으로 한 번 구매한 값
총구매액 : 여러 번의 구매액을 모두 합한 값(환불 금액은 뺌)

1) 최대금액과 총구매액이 같은 경우 : 한 번 구매
2) 최대금액이 총구매액 보다 작은 경우 : 2번 이상 구매한 대부분의 경우
3) 최대금액이 총구매액 보다 큰 경우 : 환불을 한 경우(총 69건)

이 예제에서는 상관 관계가 거의 없는 것 같습니다.

장석윤님의 댓글

  • 장석윤
  • 작성일
안녕하세요?
초보질문드립니다.
print("Logistic Classifier", "'s ROC AUC is ", roc_auc_score(y_valid, predict_proba_lr[:, 1]))
이 코드에서 [:, 1] 이 남일 확률이라는 여기는 이유가 무엇인지요?  그럼, [:, 0] 은 여성일 확률을 말하는지요?
그렇다면 그 이유는 뭘까요?
선생님의 고견 부탁드립니다. 초보질문여서 죄송합니다.

(시험 문제에서는 gender 컬럼값이 0 이면 여자, 1이면 남자  이렇게 했을뿐인데....  )

최근글


새댓글


알림 0