본문 바로가기
PYTHON/빅데이터분석기사

ML_03 (classification s3-16~21 작업형 2 예시/고객 성별 예측)

by 쿠룽지 2023. 11. 14.
728x90
반응형
728x90

 

 

 

고객 3,500명에 대한 학습용 데이터(y_train.csv, X_train.csv)를 이용하여 성별예측 모형을 만든 후, 이를 평가용 데이터(X_test.csv)에 적용하여 얻은 2,482명 고객의 성별 예측값(남자일 확률)을 다음과 같은 형식의 csv 파일로 생성하시오. (제출한 모델의 성능은 ROC-AUC 평가지표에 따라 채점)

 

더보기

제공 데이터 목록

1) y_train.csv : 고객의 성별 데이터 (학습용), CSV 형식의 파일

2) X_train.csv, X_test.csv : 고객의 상품구매 속성 (학습용 및 평가용), CSV 형식의 파일

 

y_train.csv (3,500명 데이터) / cust_id, gender (0여자 1남자)

X_train.csv (3,500명 데이터), X_test.csv (2,482명 데이터)

---------------

유의 사항
성능이 우수한 예측 모형을 구축하기 위해서는 적절한 데이터 전처리, Feature Engineering, 분류 알고리즘 사용, 초매개변수 최적화, 모형 앙상블 등이 수반되어야 한다.

 

 

1) 기본 설정

# 사용 lib import
import pandas as pd

#데이터가 많은 경우 모두 출력이 되지 않고 ... 라고 나오기 때문에 수정해야함
pd.options.display.max_rows = 500 #출력할 max row 지정
pd.options.display.max_columns = 20 #출력할 max columns 지정

#출력 format 지정- 소수점 아래 4자리까지
pd.set_option('display.float_format', '{:,4f}'.format)

 

 

2) 데이터 불러오기

# 학습 데이터 X_train.csv 가져오기
X = pd.read_csv('bigdata/X_train.csv', encoding='cp949') #한글이 있어서 encoding
print(X.head(2))

# 학습 데이터 y_train.csv 가져오기
Y = pd.read_csv('bigdata/y_train.csv')
print(Y.head(2))

# 제출용 데이터 X_test.csv 가져오기
X_submission = pd.read_csv('bigdata/X_test.csv', encoding='cp949')
print(X_submission.head(2))

 

 

3) 데이터 전처리

3-1) xtrain xtest  결합

전처리 시 주의 사항 1. 결측치가 없도록 한다 2. dtype을 맞춘다 (문제 형식과도 비교해봐야함 / int, float만 가능)

# X, X_submission에 동일한 전처리를 위해 두 데이터를 결합해서 dfX로 이름 붙이기
dfX = pd.concat([X, X_submission], axis=0, ignore_index=True)
#concat()은 목록처리 해야하기 때문에 [] 넣었음
#axis=0 생략 가능
#ignore_index=True (인덱스 새로 정렬)

print(dfX.head(2))

 

 

3-2) 정보 확인

# 각 컬럼의 dtype 및 행, 열 개수 확인
dfX.info()

환불금액 2076 non-null -> 결측치 대체

주구매상품/ 주구매지점 object 변경 -> dtype 변환

 

 

3-3) dfX의 컬럼별 결측치 확인하기

print(dfX.isna().sum())

3906처럼 너무 많은 결측치 수를 가진 경우 1. 해당 컬럼을 제거하고 사용 2. 다른 값으로 채우기

2-a 범주형 변수일 경우 새로운 범주 생성

2-b 연속형 변수일 경우 평균이나 중앙값으로 대체

 

 

3-4) 결측치를 채우기 위한 값 선정을 위한 작업

temp = dfX.groupby('주구매상품')['환불금액'].mean()
print(temp)

#주구매상품별로 환불금액 평균이 다름을 확인

소형가전, 악기, 통신/컴퓨터 - NaN

 

 

3-5) '주구매상품'별 '환불금액' 평균으로 '환불금액'의 결측치를 채우기

temp = dfX.groupby('주구매상품')['환불금액'].transform('mean')
dfX['환불금액'] = dfX['환불금액'].mask(dfX['환불금액'].isna(), temp)

#dfX['환불금액'].mask(dfX['환불금액'].isna(), temp)
#dfX['환불금액'].isna()가 true일 경우 temp 값으로 dfX['환불금액']을 채움

#채우고 난 후 결측치 행 확인
dfX[dfX['환불금액'].isna()]

값을 채워도 아직 결측치가 남아있는 것을 확인할 수 있음

 

 

3-6) '주구매상품'별 평균을 구할 수 없는 경우 '환불금액'을 '환불금액'의 평균으로 채우기

dfX['환불금액'] = dfX['환불금액'].fillna(dfX['환불금액'].mean())
print(dfX.isna().sum().sum())

>> 0

결측치 채우기 성공

 

 

4) dfX에서 높은 상관계수 확인하기 (X Feature간)

상관관계가 높은 X1, X2가 있다면 제거하는 게 좋음 (0.98 0.99 등 -1 or 1에 가까운 것은 좋지 않음)

print(dfX.corr())

무난

 

 

df로 볼 수도 있음

(dfX.corr(numeric_only=True))

 

 

 

 

5) Y['gender'] 값의 분포 확인 (0 여성: 62.4% / 1 남성: 37.6%)

#값의 개수
temp = Y['gender'].value_counts()
print(temp)

#값의 비율
temp = Y['gender'].value_counts(normalize=True) # normalize=True / 비율로 볼 수 있음
print(temp)

 

 

6) dfX와 Y 병합하여 dfXY 만들기 (dfX 데이터는 3500개만)

#dfX.iloc[:3500, :].columns, Y.columns >
#dfX = ['cust_id', '총구매액', '최대구매액', '환불금액', '주구매상품', '주구매지점', '내점일수',
#'내점당구매건수','주말방문비율', '구매주기'],
#Y = ['cust_id', 'gender'] 


# cust_id 컬럼이 겹치기 때문에 concat 말고 merge로 병합
dfXY = pd.merge(dfX.iloc[:3500,:], Y)

dfXY.columns, dfXY.shape

 

 

 

7) dfXY를 사용, 성별별 평균 구하기 (gender에 따라 총구매액~ 구매주기에 값의 차이가 있는지 확인)

temp = dfXY.groupby('gender').mean()
print(temp)

 

 

8) 데이터 확인

# dfXY의 '주구매지점'별, 'gender'별 방문횟수를 확인한다
# 성별에 다른 분포가 있으므로 '주구매지점' 포함
temp = dfXY.groupby(['주구매지점', 'gender'])['주구매지점'].count()
print(temp) # 0 1의 주구매지점 별 방문횟수 값 출력

# dfXY의 '주구매상품'별, 'gender'별 방문횟수를 확인한다
# 성별에 다른 분포가 있으므로 '주구매상품' 포함
temp = dfXY.groupby(['주구매상품', 'gender'])['주구매상품'].count()
print(temp)

 

 

 

9) object 타입인 주구매지점, 주구매상품 type -> 숫자로 변경 후 최종 데이터 점검

#dfX에서 '주구매지점'에 대해 중복 제거 (고윳값 확인)
A = dfX['주구매지점'].unique()
print(A) # 순서가 없는 명목형

#dfX에서 '주구매상품'에 대해 중복 제거 (고윳값 확인)
B = dfX['주구매상품'].unique()
print(B) # 순서가 없는 명목형

#항목별 순서 개념이 없기 때문에 Label Encoding을 실행하여 숫자로 변환
#cat.codes = 고유번호 출력 
dfX['주구매지점'] = dfX['주구매지점'].astype('category').cat.codes
dfX['주구매상품'] = dfX['주구매상품'].astype('category').cat.codes

#최종 데이터 점검
#결측치 확인/ dtype 확인
dfX.info()

 

 

 

10) 데이터 분리, 모델 생성 및 학습

#1. 사용할 도구 import

#scaler
from sklearn.preprocessing import StandardScaler
#데이터 분리
from sklearn.model_selection import train_test_split
#모델
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from xgboost import XGBClassifier
#성능평가
from sklearn.metrics import roc_auc_score




#2. 함수 작성 (get_scores, make_models, get_data)

#train score, test score, roc_auc_score를 반환하는 함수 작성
def get_scores(model, xtrain, xtest, ytrain, ytest):
	A = model.score(xtrain, ytrain) #훈련
    B = model.score(xtest, ytest) #테스트
    ypred = model.predict_proba(xtest)[:, 1] #남자값이라 1
    C = roc_auc_score(ytest, ypred)
    return '{:.4f} {:.4f} {:.4f}'.format(A,B,C) # 소수점 네자리 수 형식으로 return

#다양한 모델을 만들고 성능을 출력하는 함수 작성
def make_models(xtrain, xtest, ytrain, ytest):
    model1 = LogisticRegression(max_iter=5000).fit(xtrain, ytrain)
    print('model1', get_scores(model1, xtrain, xtest, ytrain, ytest))

    for k in range(1, 10):
        model2 = KNeighborsClassifier(k).fit(xtrain, ytrain)
        print('model2', k, get_scores(model2, xtrain, xtest, ytrain, ytest))

    # overfitting 가능성 有 
    							#선생님이랑 결과값 동일하게 나오기 위해 random_state=0
    model3 = DecisionTreeClassifier(random_state=0).fit(xtrain, ytrain)
    print('model3', get_scores(model3, xtrain, xtest, ytrain, ytest))
    for d in range(3, 8):
        model3 = DecisionTreeClassifier(max_depth=d, random_state=0).fit(xtrain, ytrain)
        print('model3', d, get_scores(model3, xtrain, xtest, ytrain, ytest))

    # overfitting 가능성 有
    model4 = RandomForestClassifier(random_state=0).fit(xtrain, ytrain)
    print('model4', get_scores(model4, xtrain, xtest, ytrain, ytest))
    for d in range(3, 8):
        model4 = RandomForestClassifier(500, max_depth=d,random_state=0).fit(xtrain, ytrain)
        print('model4', d, get_scores(model4, xtrain, xtest, ytrain, ytest))

    model5 = XGBClassifier().fit(xtrain, ytrain)
    print('model5', get_scores(model5, xtrain, xtest, ytrain, ytest))




#X를 train용도, submission용도로 나누고, Y를 1차원으로 바꿈 (StandardScaler 적용)

def get_data(dfX, Y):
    X = dfX.drop(columns=['cust_id'])
    X_use = X.iloc[:3500, :]
    X_submission = X.iloc[3500:, :]
    Y1 = Y['gender']
    
    scaler = StandardScaler()
    X1_use = scaler.fit_transform(X_use)
    X1_submission = scaler.transform(X_submission)
    print(X1_use.shape, X1_submission.shape, Y1.shape)
    return X1_use, X1_submission, Y1

train data = fit_transform
test data = transform
test data는 모델이 학습된 후 평가할 때만 사용되어야 하는데

fit_transform을 하게 된다면 모델의 성능을 평가할 수 없기 때문에 transform을 쓴다고 한다.

 

참고 블로그

[scikit-learn] transform()과 fit_transform()의 차이는 무엇일까? (tistory.com)

 

[scikit-learn] transform()과 fit_transform()의 차이는 무엇일까?

왜 scikit-learn에서 모델을 학습할 때, train dataset에서만 .fit_transform()메서드를 사용하는 건가요? TL;DR 안녕하세요 steve-lee입니다. 실용 머신러닝 A to Z 첫번 째 시간은 scikit-learn에서 자주 사용하는 tra

deepinsight.tistory.com

 

 

11) 수행

# X, Y 분리하기
X1_use, X1_submission, Y1 = get_data(dfX, Y)

# train, test 7:3 분할, stratify 적용, random_state=0 적용
xtrain, xtest, ytrain, ytest = train_test_split(X1_use, Y1,
                                                test_size=0.3,
                                                stratify=Y1,
                                                random_state=0)

#다양한 모델 만들어보기
#(make_models 함수 호출 부분은 실제 시험에서 제출 전 꼭 주석 해달라고 하셨당 / 시간 초과)
make_models(xtrain, xtest, ytrain, ytest)

모델이 1부터 5까지 주르륵 나오고 그 중 가장 성능이 좋은 값인

model4 6 0.7314 0.6590 0.6989 주석 메모

 

 

#roc_auc 가장 성능이 좋은 모델 꺼내서 실행
#model4 6 0.7314 0.6590 0.6989 
model = RandomForestClassifier(500, max_depth=6, random_state=0).fit(xtrain, ytrain)
print('final model', get_scores(model, xtrain, xtest, ytrain, ytest))

#depth = 5 도 좋음 : train, test 점수 차이가 많지 않고, roc_auc 점수 좋음
#train, test, roc_auc를 모두 고려해서 앞으로 평가에 좋은 결과를 보일 것으로 예상됨
model = RandomForestClassifier(500, max_depth=5,random_state=0).fit(xtrain, ytrain)
print('final model', get_scores(model, xtrain, xtest, ytrain, ytest))


#위와 같은 방법 아니면 GridSearchCV를 통해서 파라미터 값을 찾아낼 수도 있음
 

 

 

12) 제출할 데이터 생성

X_submission.columns 
#Index(['cust_id', '총구매액', '최대구매액', '환불금액', '주구매상품', '주구매지점',
#'내점일수', '내점당구매건수','주말방문비율', '구매주기'], dtype='object')
#X1은 ndarray타입이라 columns가 없음

#X1_submission 으로 cust_id와 예측한 남자일 확률을 파일로 저장하기
pred = model.predict_proba(X1_submission)[:,1]
submission = pd.DataFrame({'cust_id':X_submission['cust_id'], 
							'gender':pred})
submission.to_csv('수험번호.csv', index=False)

#파일 읽어 출력해보기
temp = pd.read_csv('수험번호.csv')
temp.head(2)

 

 


 

정리

 

1) 시험환경에 제출하는 코드

# 데이터 파일 읽기 예제
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from xgboost import XGBClassifier
from sklearn.metrics import roc_auc_score

# 시험환경에서는 아래와 같이 해야해서 수정했습니다 ^^*
pd.options.display.max_rows = 500     #출력할 max row를 지정
pd.options.display.max_columns = 20   #출력할 max columns를 지정

def get_scores(model, xtrain, xtest, ytrain, ytest):
    A = model.score(xtrain, ytrain)
    B = model.score(xtest, ytest)
    ypred = model.predict_proba(xtest)[:, 1]
    C = roc_auc_score(ytest, ypred)
    return '{:.4f} {:.4f} {:.4f}'.format(A, B, C)

def make_models(xtrain, xtest, ytrain, ytest):
    model1 = LogisticRegression(max_iter=5000).fit(xtrain, ytrain)
    print('model1', get_scores(model1, xtrain, xtest, ytrain, ytest))

    for k in range(1, 10):
        model2 = KNeighborsClassifier(k).fit(xtrain, ytrain)
        print('model2', k, get_scores(model2, xtrain, xtest, ytrain, ytest))

    # overfitting ??
    model3 = DecisionTreeClassifier(random_state=0).fit(xtrain, ytrain)
    print('model3', get_scores(model3, xtrain, xtest, ytrain, ytest))
    for d in range(3, 8):
        model3 = DecisionTreeClassifier(max_depth=d, random_state=0).fit(xtrain, ytrain)
        print('model3', d, get_scores(model3, xtrain, xtest, ytrain, ytest))

    # overfitting ??
    model4 = RandomForestClassifier(random_state=0).fit(xtrain, ytrain)
    print('model4', get_scores(model4, xtrain, xtest, ytrain, ytest))
    for d in range(3, 8):
        model4 = RandomForestClassifier(500, max_depth=d,random_state=0).fit(xtrain, ytrain)
        print('model4', d, get_scores(model4, xtrain, xtest, ytrain, ytest))

    model5 = XGBClassifier(eval_metric='logloss', use_label_encoder=False).fit(xtrain, ytrain)
    print('model5', get_scores(model5, xtrain, xtest, ytrain, ytest))

def get_data(dfX, Y):
    X = dfX.drop(columns=['cust_id'])
    X_use = X.iloc[:3500, :]
    X_submission = X.iloc[3500:, :]
    Y1 = Y['gender']
    scaler = StandardScaler()
    X1_use = scaler.fit_transform(X_use)
    X1_submission = scaler.transform(X_submission)
    print(X1_use.shape, X1_submission.shape, Y1.shape)
    return X1_use, X1_submission, Y1

# 이 부분은 colab 경로와 다르므로 실행되지 않습니다.
X_submission = pd.read_csv("data/X_test.csv")
X = pd.read_csv("data/X_train.csv")
Y = pd.read_csv("data/y_train.csv")

# 사용자 코딩
dfX = pd.concat([X, X_submission], axis=0, ignore_index=True)
#print(dfX.isna().sum())
temp = dfX.groupby('주구매상품')['환불금액'].mean()
#print(temp)
temp = dfX.groupby('주구매상품')['환불금액'].transform('mean')
# dfX['환불금액']의 결측치를 temp로 채우기
dfX['환불금액'] = dfX['환불금액'].mask(dfX['환불금액'].isna(), temp)
dfX['환불금액'] = dfX['환불금액'].fillna(dfX['환불금액'].mean())
#print(dfX.isna().sum().sum())
#print(dfX.corr())
A = dfX['주구매지점'].unique()
B = dfX['주구매상품'].unique()
dfX['주구매지점'] = dfX['주구매지점'].astype('category').cat.codes
dfX['주구매상품'] = dfX['주구매상품'].astype('category').cat.codes
#dfX.info()

# X, Y 분리하기
X1_use, X1_submission, Y1 = get_data(dfX, Y)
# train, test  7:3 분할, stratify 적용, random_state=0 적용
xtrain, xtest, ytrain, ytest = train_test_split(X1_use, Y1,
                                                test_size=0.3,
                                                stratify=Y1,
                                                random_state=0)
# 다양한 모델 만들어 보기
#make_models(xtrain, xtest, ytrain, ytest)

model = RandomForestClassifier(500, max_depth=6,random_state=0).fit(xtrain, ytrain)
print('final model', get_scores(model, xtrain, xtest, ytrain, ytest))
pred = model.predict_proba(X1_submission)[:, 1]
submission = pd.DataFrame({'cust_id': X_submission['cust_id'],
                           'gender': pred})
submission.to_csv('003000000.csv', index=False)
#print(submission.head(5))
# 답안 제출 참고
# 아래 코드 예측변수와 수험번호를 개인별로 변경하여 활용
# pd.DataFrame({'cust_id': X_test.cust_id, 'gender': pred}).to_csv('003000000.csv', index=False)

 

 

 

+ 2) 환불금액을 제거하는 방법으로 해결한 코드

# 데이터 파일 읽기 예제
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from xgboost import XGBClassifier
from sklearn.metrics import roc_auc_score

pd.set_option('max_rows',500)    #출력할 max row를 지정
pd.set_option('max_columns',20)  #출력할 max columns를 지정

def get_scores(model, xtrain, xtest, ytrain, ytest):
    A = model.score(xtrain, ytrain)
    B = model.score(xtest, ytest)
    ypred = model.predict_proba(xtest)[:, 1]
    C = roc_auc_score(ytest, ypred)
    return '{:.4f} {:.4f} {:.4f}'.format(A, B, C)

def make_models(xtrain, xtest, ytrain, ytest):
    model1 = LogisticRegression(max_iter=5000).fit(xtrain, ytrain)
    print('model1', get_scores(model1, xtrain, xtest, ytrain, ytest))

    for k in range(1, 10):
        model2 = KNeighborsClassifier(k).fit(xtrain, ytrain)
        print('model2', k, get_scores(model2, xtrain, xtest, ytrain, ytest))

    # overfitting ??
    model3 = DecisionTreeClassifier(random_state=0).fit(xtrain, ytrain)
    print('model3', get_scores(model3, xtrain, xtest, ytrain, ytest))
    for d in range(3, 8):
        model3 = DecisionTreeClassifier(max_depth=d, random_state=0).fit(xtrain, ytrain)
        print('model3', d, get_scores(model3, xtrain, xtest, ytrain, ytest))

    # overfitting ??
    model4 = RandomForestClassifier(random_state=0).fit(xtrain, ytrain)
    print('model4', get_scores(model4, xtrain, xtest, ytrain, ytest))
    for d in range(3, 8):
        model4 = RandomForestClassifier(500, max_depth=d,random_state=0).fit(xtrain, ytrain)
        print('model4', d, get_scores(model4, xtrain, xtest, ytrain, ytest))

    model5 = XGBClassifier(eval_metric='logloss', use_label_encoder=False).fit(xtrain, ytrain)
    print('model5', get_scores(model5, xtrain, xtest, ytrain, ytest))

def get_data(dfX, Y):
    X = dfX.drop(columns=['cust_id'])
    X_use = X.iloc[:3500, :]
    X_submission = X.iloc[3500:, :]
    Y1 = Y['gender']
    scaler = StandardScaler()
    X1_use = scaler.fit_transform(X_use)
    X1_submission = scaler.transform(X_submission)
    print(X1_use.shape, X1_submission.shape, Y1.shape)
    return X1_use, X1_submission, Y1


X_submission = pd.read_csv("bigdata/X_test.csv", encoding='cp949')
X = pd.read_csv("bigdata/X_train.csv", encoding='cp949')
Y = pd.read_csv("bigdata/y_train.csv")

dfX = pd.concat([X, X_submission], axis=0, ignore_index=True)

# 결측치가 많은 환불금액을 제거해 봄  =>  수정내용
dfX = dfX.drop(columns='환불금액')

A = dfX['주구매지점'].unique()
B = dfX['주구매상품'].unique()
dfX['주구매지점'] = dfX['주구매지점'].astype('category').cat.codes
dfX['주구매상품'] = dfX['주구매상품'].astype('category').cat.codes

# X, Y 분리하기
X1_use, X1_submission, Y1 = get_data(dfX, Y)
# train, test  7:3 분할, stratify 적용, random_state=0 적용
xtrain, xtest, ytrain, ytest = train_test_split(X1_use, Y1,
                                                test_size=0.3,
                                                stratify=Y1,
                                                random_state=0)
# 다양한 모델 만들어 보기
make_models(xtrain, xtest, ytrain, ytest)

model = RandomForestClassifier(500, max_depth=6,random_state=0).fit(xtrain, ytrain)
print('final model', get_scores(model, xtrain, xtest, ytrain, ytest))
pred = model.predict_proba(X1_submission)[:, 1]
submission = pd.DataFrame({'cust_id': X_submission['cust_id'],
                           'gender': pred})
submission.to_csv('003000000.csv', index=False)
728x90
반응형