!pip install seaborn==0.11
# 라이브러리 업그레이드
# 업그레이드 후에는 세션 재시작해야 함
"""
# 1주차 실습 - Data EDA / 분석하기
"""
# data 라이브러리
import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)

# 시각화 라이브러리
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
print(sns.__version__)

# Input data files are available in the read-only "../input/" directory
import os
for dirname, _, filenames in os.walk('/kaggle/input'):
    for filename in filenames:
        print(os.path.join(dirname, filename))

train = pd.read_csv('/kaggle/input/kakr-4th-competition/train.csv')
train.head() # 앞쪽 데이터 출력
train.tail() # 뒤쪽 데이터 출력
train.info() # data frame출력
#non-null 데이터 개수, data type
import missingno as msno # null 데이터 확인 라이브러리
# https://github.com/ResidentMario/missingno
msno.matrix(train)
train['workclass'].value_counts() # feature 각 구성 요소의 개수 확인
sns.countplot(data = train, x = 'workclass') # data 분포 시각화
sns.countplot(data = train, x = 'race', hue = 'sex') # hue : 다른 feat 기준으로 분류
train.columns # feature 확인
train.describe() # 데이터 요약
"""
# ex.나이
- 중간값은 37세
- 최대 90세, 약 3~40대로 편향된 나이 데이터임을 알 수 있다.
"""
# 나이 분포를 히스토그램으로
fig, ax = plt.subplots(1,1,figsize = (12,5))
ax.hist(train['age'], width = 5, edgecolor = 'r')
ax.set_title('age histogram', fontweight = 'bold')
plt.show()
# 나이 분포를 kdeplot으로
sns.kdeplot(data = train, x = 'age')
# 출력값(income)을 text에서 숫자값(0, 1)로 바꾸기
train['income'] = (train['income'] == '>50K').astype('int') # astype : T/F를 0/1로 출력
print(train['income'])
"""
# Pivot table 만들기
"""
train.groupby(['race', 'sex'])['income'].mean()
"""
# pivot table분석
- 백인 남성의 31%가 50K 이상의 income을 보인다.
"""
# pandas로 pivot table
pd.pivot_table(train,
                      columns = 'sex',
                      index = 'race',
                      values = 'income',
                      aggfunc = 'sum')
"""
# Boxplot 그리기
"""
sns.boxplot(data = train, x = 'race', y = 'age', hue = 'sex') 
sns.violinplot(data = train, x = 'race', y = 'age', hue = 'sex') 
"""
# 상관계수 시각화하기
"""
corr = train.corr()
corr.style.background_gradient()
corr[['income']].style.background_gradient()
# 메소드 적용시 대괄호 두개로
sns.heatmap(corr, square = True, linecolor = 'white', linewidth = 0.3,
            cmap = 'coolwarm', vmax = 1.0, vmin = -1.0)
"""
# Tips
- 새로운 feature를 만들어보자
- 과편향된 feature라면 T/F로 분류해서 적용해보자
- 여러개의 모델을 만들 때 특정 feature 중심 / Outliar 예측 등으로 
"""
"""
# 2주차 - Feature Engineering
"""
PATH = '../input/kakr-4th-competition/'
train = pd.read_csv(PATH + 'train.csv')
test  = pd.read_csv(PATH + 'test.csv')

train.info() # data frame출력
"""
train_dtype = {
    'id' : np.int32,
    ...
}
- dictionary를 이용한 data type을 지정 가능.
    - 실제 데이터의 범위보다 read_csv는 최대 범위로 불러오기 때문에,
이러한 방식으로 dtype을 지정해주면 메모리 절약이 가능

- 단점 : overflow 문제 위험

"""
"""
* id
* age : 나이
* workclass : 고용 형태
* fnlwgt : 사람 대표성을 나타내는 가중치 (final weight의 약자)
* education : 교육 수준
* education_num : 교육 수준 수치
* marital_status: 결혼 상태
* occupation : 업종
* relationship : 가족 관계
* race : 인종
* sex : 성별
* capital_gain : 양도 소득
* capital_loss : 양도 손실
* hours_per_week : 주당 근무 시간
* native_country : 국적
* income : 수익 (예측해야 하는 값)
    * \>50K : 1
    * <=50K : 0
"""
print("\n########head########\n")
print(train.head())
print("\n########tail########\n")
print(train.tail())

# 데이터 중 일부를 무작위 추출
print("\n########sample########\n")
print(train.sample(10))
# 통계적 요약
print("\n########describe########\n")
print(train.describe())
# data는 pandas의 dataframe이고, data.column, data['column']은 pandas의 series 자료형임을 유의
# data.column은 가끔 사전에 정의된 값이랑 겹칠 수 있음
# data['column']은 그 뒤의 메소드 힌트 등이 안나옴 - 취사선택!

# data.column : 해당 데이터의 column값 출력
print("\n#######native_country########\n")
print(train.native_country)
print("\n########native_country_groupby########\n")
print(train.native_country.value_counts()) #? : 값 없다
print("\n########workclass_groupby##########\n")
print(train.workclass.value_counts())
print("\n########capital_gain_99%###########\n")
print(train.capital_gain.describe(percentiles = [.99]))
# .describe[percentiles = float] : 특정 %분위 값 확인

print("\n#########capital_gain_loc############\n")
print(train.loc[train.capital_gain == 99999, :])
# loc : 데이터의 [조건, 범위]로 선택하여 출력

print("\n#########White race data############\n")
print(train.loc[train.race == 'White', :])
# income을 부등호 text에서 값으로 바꾸기
print("\n############## income : >50K, <=50K to 0, 1#################\n")
train.income = train.income.map(lambda x : int(x == '>50K') )
print(train.income.value_counts())

# 50K 넘는 사람 수가 19744 / 그렇지 않은 수가 6305명

# 복수개의 column같이 연산하기
print("\n##########apply method example###############\n")
print(train.apply(lambda x : x['age'] + x['hours_per_week'], axis=1))
# pandas의 plot 기능
print("\n##########pandas plot#############\n")
print(train.age.plot(kind = 'box'))
# ?가 있는 데이터 출력해보기
print("\n###########data having ? value#############\n")
print(train[train.apply(lambda x:"?" in list(x), axis = 1)])
print("\n###########both workclass is ? and occupation is ?###########\n")
print(train[(train.workclass == '?') & (train.occupation == '?')])
"""
# NULL check관련 팁
- .info method로 볼 때, dtype이 float가 아닐 것 같은데 float로 나타나면 대부분 NULL값을 갖는다.
"""
# Null Check하기
print(train.info)
"""
# 이상치 처리해보기
"""
print('\n########capital gain outliar###########\n')
print(train.loc[train.capital_gain == 99999, 'income'], sum(train.loc[train.capital_gain == 99999, 'income']))
# capital gain이 99999면 income이 >50K로 나타나는 것을 볼 수 있음

print('\n########capital gain outliar###########\n')
print(train[train.capital_gain < 99999].sort_values('capital_gain', ascending = False))
print("\n#######log capital gain###############\n")
print(train.capital_gain.map(lambda x:np.log(x, where = (x!=0))).describe())
"""
# Scaling
"""
from sklearn.preprocessing import MinMaxScaler, StandardScaler
mm_scaler = MinMaxScaler()
st_scaler = StandardScaler()

# fit_transform vs transform
train['MM_fnlwgt'] = mm_scaler.fit_transform(train['fnlwgt'].values.reshape(-1,1))
test['MM_fnlwgt'] = mm_scaler.transform(test['fnlwgt'].values.reshape(-1,1))

train['MM_age'] = mm_scaler.fit_transform(train['age'].values.reshape(-1,1))
test['MM_age'] = mm_scaler.transform(test['age'].values.reshape(-1,1))

train['ST_fnlwgt'] = st_scaler.fit_transform(train['fnlwgt'].values.reshape(-1,1))
test['ST_fnlwgt'] = st_scaler.transform(test['fnlwgt'].values.reshape(-1,1))

train['ST_age'] = st_scaler.fit_transform(train['age'].values.reshape(-1,1))
test['ST_age'] = st_scaler.transform(test['age'].values.reshape(-1,1))

print('\n############scaled train################\n')
print(train[['MM_fnlwgt','MM_age', 'ST_fnlwgt', 'ST_age']])

print('\n############scaled test################\n')
print(test[['MM_fnlwgt','MM_age', 'ST_fnlwgt', 'ST_age']])
"""
# Feature Engineering
"""
print(train)
from sklearn.preprocessing import OneHotEncoder, LabelEncoder
OHE = OneHotEncoder()
LE = LabelEncoder()

# onehot encoding
ohe_workclass = OHE.fit_transform(train['workclass'].values.reshape(-1,1))

OHE.get_feature_names(['workclass'])
ohe_workclass = pd.DataFrame(ohe_workclass.toarray(), 
             columns= OHE.get_feature_names(['workclass']))
print("\n########### onehot encoding - workclass##############\n")
print(ohe_workclass)

# 원래 dataset에 합치기
print("\n######### concat onehot encoded feature ans train data ##########\n")
train = pd.concat([train, ohe_workclass], axis = 1)
print(train)
# onehot encoding 일괄 적용하기
print(pd.get_dummies(train))
print("\n############Lable Encoder#############\n")
print(LE.fit_transform(train['workclass'].values.reshape(-1,1)))

# Label Encoding 순서 직접 지정하기
print("\n##########mapped label encoding############\n")
workclass_to_label = dict(zip(train['workclass'].unique(), list(range(10))))
print(train['workclass'].map(workclass_to_label))
"""
# PCA
"""
from sklearn.decomposition import PCA
pca = PCA(n_components = 60, svd_solver = 'full')

dummied = pd.get_dummies(train).drop(columns=['id', 'income'])
X_train_std = st_scaler.fit_transform(dummied)
X_train_pca = pca.fit_transform(X_train_std)

print("\n####### pca ##########\n")
print(X_train_pca)

X_train_res = np.dot(X_train_std - X_train_std.mean(axis=0), pca.components_.T)

print('\n#######feature domain before / after##########\n')
print(dummied.shape)
print(X_train_res.shape)
"""
# 3주차 - 모델과 앙상블
"""
# train, test data 불러오기
train = pd.read_csv("/kaggle/input/kakr-4th-competition/train.csv")
test = pd.read_csv("/kaggle/input/kakr-4th-competition/test.csv")
print('train head')
print(train.head())

# income 별도로 할당
label = train['income']
del train['income']
# label 인코딩 : map함수와 람다식으로 data 구성 요소 모두에 각 함수 연산 적용
label = label.map(lambda x:1 if x == '>50K' else 0)
print('\n\nlabel encoded')
print(label)
# data 중 id column은 index와 같은 의미
# 결과값에 영향을 주지 않을 것이므로 미리 제거
del train['id']
del test['id']

# 데이터를 안전하게 처리하기 위해 임시로 복사
tmp_train = train.copy()
tmp_test = test.copy()
print(train.info())
# 결측치 처리 - 최빈값(mode)으로
# workclass, occupation, native_country는 object 객체이면서 결측치가 존재
nan_column = ['workclass', 'occupation', 'native_country']
for c in nan_column:
    tmp_train.loc[train[c] == '?', c] = train[c].mode()[0]
    tmp_test.loc[test[c] == '?', c] = test[c].mode()[0]
tmp_train['capital_gain'].plot.hist()

# capital-gain, capital-loss의 값 편향을 로그함수로 보완
tmp_train['log_capital_gain'] = train['capital_gain'].map(lambda x:np.log(x) if x!= 0 else 0)
tmp_test['log_capital_gain'] = test['capital_gain'].map(lambda x:np.log(x) if x!= 0 else 0)
tmp_train['log_capital_gain'].plot.hist()

tmp_train['capital_loss'].plot.hist()

# capital-gain, capital-loss의 값 편향을 로그함수로 보완
tmp_train['log_capital_loss'] = train['capital_loss'].map(lambda x:np.log(x) if x!= 0 else 0)
tmp_test['log_capital_loss'] = test['capital_loss'].map(lambda x:np.log(x) if x!= 0 else 0)
tmp_train['log_capital_loss'].plot.hist()
# 기존 데이터 삭제
del tmp_train['capital_gain']
del tmp_train['capital_loss']
del tmp_test['capital_gain']
del tmp_test['capital_loss']
"""
# 데이터 쪼개기
- train data : 모델 학습을 위한 데이터
- valid data : 학습 모델의 성능 검증 (train data 중 일부를 분리)
- test data : 예측할 데이터

- train_test_split( data, label, test_size, random_state, shuffle, stratify )
    - test_size(float) : 비율 지정
    - random_state(int) : 랜덤 seed값 지정 (지정하지 않을 시 달라짐)
    - shuffle(bool) : 데이터 분리 시 섞을지 유무
    - stratify(array) : 쪼개기 전/후 클래스 비율을 유지하기 위해 설정
"""
from sklearn.model_selection import train_test_split

tmp_train, tmp_valid, y_train, y_valid = train_test_split(tmp_train, label,
                                                        test_size = 0.3,
                                                         random_state = 2020,
                                                         shuffle = True,
                                                         stratify = label
                                                         )
# 인덱스 초기화 : shuffle, random seed에 의해 섞인 인덱스를 정리
# concatenate 연산 시 인덱스가 정렬되어있지 않으면 연산이 꼬일 수 있어 정리해주는게 좋음

# reset_index(drop = ) : index 초기화 / drop = True - 이전 인덱스를 제거
tmp_train = tmp_train.reset_index(drop = True)
tmp_valid = tmp_valid.reset_index(drop = True)
tmp_test = tmp_test.reset_index(drop = True)
# 스케일링
cat_columns = [c for c, t in zip(tmp_train.dtypes.index, tmp_train.dtypes) if t == 'O'] 
num_columns = [c for c in tmp_train.columns if c not in cat_columns]

print('범주형 변수: \n{}\n\n 수치형 변수: \n{}\n'.format(cat_columns, num_columns))

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
tmp_train[num_columns] = scaler.fit_transform(tmp_train[num_columns])
tmp_valid[num_columns] = scaler.transform(tmp_valid[num_columns])
tmp_test[num_columns]  = scaler.transform(tmp_test[num_columns])
"""
## 범주형 변수 : one-hot encoding
|column|
|--|
|a|
|b|
|c|

대신

|a|b|c|
|--|--|--|
|1|0|0|
|0|1|0|
|0|0|1|

형태로 만든다.
"""
# 범주형 변수 : one-hot encoding
from sklearn.preprocessing import OneHotEncoder

tmp_data = pd.concat([tmp_train, tmp_valid, tmp_test])

ohe = OneHotEncoder(sparse = False)
ohe.fit(tmp_data[cat_columns])

# ohe 연산으로 생성된 column을 리스트로 변환
ohe_col = list()

for c in ohe.categories_:
    ohe_col += c.tolist()
new_train_category = pd.DataFrame(ohe.transform(tmp_train[cat_columns])
                                  , columns=ohe_col)

new_valid_category = pd.DataFrame(ohe.transform(tmp_valid[cat_columns])
                                  , columns=ohe_col)

new_test_category  = pd.DataFrame(ohe.transform(tmp_test[cat_columns])
                                  , columns=ohe_col)
# concat의 axis : 연결할 때 어느 축을 기준으로 할지를 결정
# data.shape = (a, b, ...)일 때
# axis = 0 : a를 기준 / 1 : b를 기준 / ...
tmp_train = pd.concat([tmp_train, new_train_category], axis = 1)
tmp_valid = pd.concat([tmp_valid, new_valid_category], axis = 1)
tmp_test = pd.concat([tmp_test, new_test_category], axis = 1)

# 기존 범주형 column 제거
tmp_train = tmp_train.drop(columns=cat_columns)
tmp_valid = tmp_valid.drop(columns=cat_columns)
tmp_test = tmp_test.drop(columns=cat_columns)
"""
# 학습 모델 만들기
"""
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC # Support Vector Machine
from sklearn.ensemble import RandomForestClassifier

from xgboost import XGBClassifier
from lightgbm import LGBMClassifier

# 이번 대회의 valuation : f1_score방식
from sklearn.metrics import f1_score
tmp_y_train = y_train
tmp_y_valid = y_valid
lr = LogisticRegression()
lr.fit(tmp_train, tmp_y_train)

y_pred = lr.predict(tmp_valid)
print(f"Logistic Regression f1 score : {f1_score(tmp_y_valid, y_pred, average = 'micro')}")
svc = SVC()
svc.fit(tmp_train, tmp_y_train)

y_pred = svc.predict(tmp_valid)
print(f"SVC f1 score : {f1_score(tmp_y_valid, y_pred, average = 'micro')}")
rf = RandomForestClassifier()
rf.fit(tmp_train, tmp_y_train)

y_pred = rf.predict(tmp_valid)
print(f"RandomForest F1 Score: {f1_score(tmp_y_valid, y_pred, average='micro')}")
xgb = XGBClassifier(tree_method='gpu_hist')
xgb.fit(tmp_train, tmp_y_train)

y_pred = xgb.predict(tmp_valid)
print(f"XGBoost F1 Score: {f1_score(tmp_y_valid, y_pred, average='micro')}")
lgb = LGBMClassifier(tree_method='gpu_hist')
lgb.fit(tmp_train, tmp_y_train)

y_pred = lgb.predict(tmp_valid)
print(f"LightGBM F1 Score: {f1_score(tmp_y_valid, y_pred, average='micro')}")
"""
# K-Fold Cross Validation
"""
# 기존 모델 학습에서는 전처리를 미리 해도 되나,
# K-fold의 경우 매번 전처리가 필요하므로 이를 함수로 만들게 됨
def preprocess(x_train, x_valid, x_test):
    tmp_x_train = x_train.copy()
    tmp_x_valid = x_valid.copy()
    tmp_x_test  = x_test.copy()
    
    tmp_x_train = tmp_x_train.reset_index(drop=True)
    tmp_x_valid = tmp_x_valid.reset_index(drop=True)
    tmp_x_test  = tmp_x_test.reset_index(drop=True)
    
    nan_column = ['workclass', 'occupation', 'native_country']
    for c in nan_column:
        tmp_x_train.loc[tmp_x_train[c] == '?', c] = tmp_x_train[c].mode()[0]
        tmp_x_valid.loc[tmp_x_valid[c] == '?', c] = tmp_x_valid[c].mode()[0]
        tmp_x_test.loc[tmp_x_test[c]   == '?', c] = tmp_x_test[c].mode()[0]
    
    tmp_x_train['log_capital_loss'] = tmp_x_train['capital_loss'].map(lambda x : np.log(x) if x != 0 else 0)
    tmp_x_valid['log_capital_loss'] = tmp_x_valid['capital_loss'].map(lambda x : np.log(x) if x != 0 else 0)
    tmp_x_test['log_capital_loss'] = tmp_x_test['capital_loss'].map(lambda x : np.log(x) if x != 0 else 0)
    
    tmp_x_train['log_capital_gain'] = tmp_x_train['capital_gain'].map(lambda x : np.log(x) if x != 0 else 0)
    tmp_x_valid['log_capital_gain'] = tmp_x_valid['capital_gain'].map(lambda x : np.log(x) if x != 0 else 0)
    tmp_x_test['log_capital_gain'] = tmp_x_test['capital_gain'].map(lambda x : np.log(x) if x != 0 else 0)
    
    tmp_x_train = tmp_x_train.drop(columns=['capital_loss', 'capital_gain'])
    tmp_x_valid = tmp_x_valid.drop(columns=['capital_loss', 'capital_gain'])
    tmp_x_test  = tmp_x_test.drop(columns=['capital_loss', 'capital_gain'])
    
    scaler = StandardScaler()
    tmp_x_train[num_columns] = scaler.fit_transform(tmp_x_train[num_columns])
    tmp_x_valid[num_columns] = scaler.transform(tmp_x_valid[num_columns])
    tmp_x_test[num_columns]  = scaler.transform(tmp_x_test[num_columns])
    
    tmp_all = pd.concat([tmp_x_train, tmp_x_valid, tmp_x_test])

    ohe = OneHotEncoder(sparse=False)
    ohe.fit(tmp_all[cat_columns])
    
    ohe_columns = list()
    for lst in ohe.categories_:
        ohe_columns += lst.tolist()
    
    tmp_train_cat = pd.DataFrame(ohe.transform(tmp_x_train[cat_columns]), columns=ohe_columns)
    tmp_valid_cat = pd.DataFrame(ohe.transform(tmp_x_valid[cat_columns]), columns=ohe_columns)
    tmp_test_cat  = pd.DataFrame(ohe.transform(tmp_x_test[cat_columns]), columns=ohe_columns)
    
    tmp_x_train = pd.concat([tmp_x_train, tmp_train_cat], axis=1)
    tmp_x_valid = pd.concat([tmp_x_valid, tmp_valid_cat], axis=1)
    tmp_x_test = pd.concat([tmp_x_test, tmp_test_cat], axis=1)

    tmp_x_train = tmp_x_train.drop(columns=cat_columns)
    tmp_x_valid = tmp_x_valid.drop(columns=cat_columns)
    tmp_x_test = tmp_x_test.drop(columns=cat_columns)
    
    return tmp_x_train.values, tmp_x_valid.values, tmp_x_test.values
def xgb_f1(y, t, threshold=0.5):
    t = t.get_label()
    y_bin = (y > threshold).astype(int) 
    return 'f1',f1_score(t, y_bin, average='micro')
from sklearn.model_selection import KFold, StratifiedKFold
# KFold : 데이터를 동일 비율로 나눔
# StratifiedKFold : 데이터를 클래스 비율에 맞추어 나눔

n_split = 5
skf = StratifiedKFold(n_splits = n_split, shuffle = True)
score = list()
oof_pred = np.zeros((test.shape[0], )) # data 행 개수

for i, (train_idx, val_idx) in enumerate(skf.split(train, label)): # dataset을 Stratified K-Fold 분할
    # i : iteration
    # train_idx, val_idx : skf.split으로 반환된 fold data index
    x_train, y_train = train.iloc[train_idx, :], label[train_idx]
    x_valid, y_valid = train.iloc[val_idx, :], label[val_idx]
    
    # 전처리
    x_train, x_valid, x_test = preprocess(x_train, x_valid, test)
    
    # 모델 정의
    clf = XGBClassifier(tree_method='gpu_hist')
    
    # 모델 학습
    clf.fit(x_train, y_train,
            eval_set = [[x_valid, y_valid]], 
            eval_metric = xgb_f1,        
            early_stopping_rounds = 100,
            verbose = 100,  )

    # 훈련, 검증 데이터 Log Loss 확인
    train_f1_score = f1_score(y_train, clf.predict(x_train), average='micro')
    val_f1_score = f1_score(y_valid, clf.predict(x_valid), average='micro')
    print('{} Fold, train f1_score : {:.4f}4, validation f1_score : {:.4f}\n'.format(i, train_f1_score, val_f1_score))
    
    score.append(val_f1_score)
    
    # out of fold 앙상블 구현
    # 전체 확률의 mean값 계산을 위해 split으로 나눈 후 합산
    # predict_proba에는 0 혹은 1이 저장 - 1번 column값으로 '참'인 경우를 추출해냄
    oof_pred += clf.predict_proba(x_test)[:, 1] / n_split
     
# 교차 검증 F1 Score 평균 계산하기
print('Cross Validation Score : {:.4f}'.format(np.mean(score)))
score = list()
oof_pred = np.zeros((test.shape[0], )) # data 행 개수

for i, (train_idx, val_idx) in enumerate(skf.split(train, label)): # dataset을 Stratified K-Fold 분할
    # i : iteration
    # train_idx, val_idx : skf.split으로 반환된 fold data index
    x_train, y_train = train.iloc[train_idx, :], label[train_idx]
    x_valid, y_valid = train.iloc[val_idx, :], label[val_idx]
    
    # 전처리
    x_train, x_valid, x_test = preprocess(x_train, x_valid, test)
    
    # 모델 정의
    clf = LogisticRegression(solver='liblinear', max_iter=1000)
    
    # 모델 학습
    clf.fit(x_train, y_train)

    # 훈련, 검증 데이터 Log Loss 확인
    train_f1_score = f1_score(y_train, clf.predict(x_train), average='micro')
    val_f1_score = f1_score(y_valid, clf.predict(x_valid), average='micro')
    print('{} Fold, train f1_score : {:.4f}4, validation f1_score : {:.4f}\n'.format(i, train_f1_score, val_f1_score))
    
    score.append(val_f1_score)
    
    # out of fold 앙상블 구현
    # 전체 확률의 mean값 계산을 위해 split으로 나눈 후 합산
    # predict_proba에는 0 혹은 1이 저장 - 1번 column값으로 '참'인 경우를 추출해냄
    oof_pred += clf.predict_proba(x_test)[:, 1] / n_split
     
# 교차 검증 F1 Score 평균 계산하기
print('Cross Validation Score : {:.4f}'.format(np.mean(score)))
score = list()
oof_pred = np.zeros((test.shape[0], )) # data 행 개수

for i, (train_idx, val_idx) in enumerate(skf.split(train, label)): # dataset을 Stratified K-Fold 분할
    # i : iteration
    # train_idx, val_idx : skf.split으로 반환된 fold data index
    x_train, y_train = train.iloc[train_idx, :], label[train_idx]
    x_valid, y_valid = train.iloc[val_idx, :], label[val_idx]
    
    # 전처리
    x_train, x_valid, x_test = preprocess(x_train, x_valid, test)
    
    # 모델 정의
    clf = LGBMClassifier(tree_method='gpu_hist')
    
    # 모델 학습
    clf.fit(x_train, y_train,
            eval_set = [[x_valid, y_valid]], 
            eval_metric = 'logloss',        
            early_stopping_rounds = 100,
            verbose = 100, )

    # 훈련, 검증 데이터 Log Loss 확인
    train_f1_score = f1_score(y_train, clf.predict(x_train), average='micro')
    val_f1_score = f1_score(y_valid, clf.predict(x_valid), average='micro')
    print('{} Fold, train f1_score : {:.4f}4, validation f1_score : {:.4f}\n'.format(i, train_f1_score, val_f1_score))
    
    score.append(val_f1_score)
    
    # out of fold 앙상블 구현
    # 전체 확률의 mean값 계산을 위해 split으로 나눈 후 합산
    # predict_proba에는 0 혹은 1이 저장 - 1번 column값으로 '참'인 경우를 추출해냄
    oof_pred += clf.predict_proba(x_test)[:, 1] / n_split
     
# 교차 검증 F1 Score 평균 계산하기
print('Cross Validation Score : {:.4f}'.format(np.mean(score)))
submit = pd.read_csv("/kaggle/input/kakr-4th-competition/sample_submission.csv")
submit.loc[:, 'prediction'] = (oof_pred > 0.5).astype(int)
submit.head()
submit.to_csv('submission.csv', index=False)