기존 코드에 잘못 된 부분이 있어서 전체적으로 코드 수정을 해주었다.

1. Run_Preprocess.py

모델 학습을 위한 전처리 과정이 담겨있는 코드이다.

1-1) Import Library & Set wd

필요한 라이브러리와 상수를 선언해주는 파트이다.

# Data Handling
import pandas as pd
import os
import pickle

# Model Selection
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler


PROJECT_PATH = 'D:/projects/test'
ORIGIN_FILE_NAME = '2019_2022_매매데이터.csv'
POP_FILE_NAME = 'pop.csv'


1-2) Define Func

필요한 함수를 정의하였다. 아래는 함수에 대한 간단한 설명이다.

  • make_merge

    train과 test에 대해서 동시에 merge해주기 위한 코드이다. right_on에 값이 안들어오면, left_on으로 merge하고 값이 들어오면 left_on, right_on으로 merge한다. 최종적으로 merge된 데이터프레임 2개를 반환한다.

def make_merge(left1, left2, right, left_on, right_on =''):
    if right_on == '':
        left1 = pd.merge(left = left1, right = right, on = left_on)
        left2 = pd.merge(left = left2, right = right, on = left_on)
    else:
        left1 = pd.merge(left = left1, right = right, left_on = left_on, right_on = right_on)
        left2 = pd.merge(left = left2, right = right, left_on = left_on, right_on = right_on)
    return left1, left2
  • save_file

    저장 대상과 파일명을 받는다. 파일 확장자가 csv면 dataframe의 to_csv로 저장하고, pickle이면 pickle로 dump하는 함수이다.

def save_file(target, file_name):
    _, file_ext = os.path.splitext(file_name)

    if file_ext == '.csv':
        target.to_csv( os.path.join(PROJECT_PATH, file_name) )
    elif file_ext == '.pkl':
        with open(os.path.join(PROJECT_PATH, file_name), 'wb') as f:
            pickle.dump( target, f )             
    else:
        pass


1-3) Main process

  • Import Dataframe & Pre-processing

    필요한 데이터를 불러오고 결측치 처리 및 컬럼 전처리를 해주었다. 요약 통계량 생성을 위해 시군구에서 구만 따로 분리해주었으며, 계약년월을 계약년과 계약월로 나누었다. 인구 데이터도 불러와서 merge해주었따. 자세한 설명은 이전 글에 정리되어 있다.

if __name__ == '__main__':
    # import df
    df = pd.read_csv(os.path.join(PROJECT_PATH, ORIGIN_FILE_NAME), encoding='cp949')

    # fillna
    df['거래유형'][ df['거래유형'] == '-' ] = '중개거래'

    # pre-proceess
    df['구'] = df['시군구'].apply(lambda x: x.split(' ')[1])
    df['계약년'] = df['계약년월'].apply(lambda x: str(x)[:4])
    df['계약월'] = df['계약년월'].apply(lambda x: str(x)[4:])    

    # merge pop data
    pop = pd.read_csv(os.path.join(PROJECT_PATH, POP_FILE_NAME), encoding='cp949')
    pop['구별'] = pop['구별'].apply(lambda x: x.replace(' ', ''))
    pop['인구'] = pop['인구'].apply(lambda x: x.replace(',', '')).astype("int")
    df = pd.merge(df, pop, left_on='구', right_on='구별')    
  • Train Test Split

    test_size는 30%로 하고, 랜덤 추출하여 train과 test를 나누어주었다.

    train, test = train_test_split(df, test_size = 0.3, shuffle=True)
  • Feature Engineering

    동별 평균 거래가, 동평 총 거래가, 구별 평균 거래가, 구별 총 거래가를 요약통계량으로 추가해주었다. 이전 코드에서는 train test split을 하기 전에 요약통계량을 계산하는 바람에, test데이터가 학습에 개입하여 정확한 학습이 이루어지지 않았다. 따라서 train에 대해서 먼저 요약 통계량을 계산한 후, train, test에 merge 시켜주었다.

    dong_mean_money = pd.DataFrame(train.groupby(['시군구']).mean()['거래금액(만원)'].round().astype('int'))
    dong_sum_money = pd.DataFrame(train.groupby(['시군구']).sum()['거래금액(만원)'].round().astype('int'))
    gu_mean_money = pd.DataFrame(df.groupby(['구']).mean()['거래금액(만원)'].round().astype('int'))
    gu_sum_money = pd.DataFrame(df.groupby(['구']).sum()['거래금액(만원)'].round().astype('int'))

    dong_mean_money.columns = ['동별_평균_거래금액']
    dong_sum_money.columns = ['동별_총_거래금액']
    gu_mean_money.columns = ['구별_평균_거래금액']
    gu_sum_money.columns = ['구별_총_거래금액']

    train, test = make_merge(train, test, dong_mean_money, '시군구')
    train, test = make_merge(train, test, dong_sum_money, '시군구')
    train, test = make_merge(train, test, gu_mean_money, '구')
    train, test = make_merge(train, test, gu_sum_money, '구')
  • Select Columns

    필요한 컬럼만 추출해주었다. 학습에 쓰일 컬럼과, 스케일링에 쓰일 컬럼을 뽑아주었다.

    col = [
        '전용면적(㎡)', '계약년', '계약월',
        '층', '건축년도', '거래유형', '동별_평균_거래금액',
        '동별_총_거래금액', '구별_평균_거래금액', '구별_총_거래금액', '인구', '거래금액(만원)'        
    ]
    num_col = [
        '전용면적(㎡)', '동별_평균_거래금액',
        '동별_총_거래금액', '구별_평균_거래금액', '구별_총_거래금액', '인구'        
    ]

    train, test = train[col], test[col]
  • Standard Scaling

    numeric 데이터에 대해서 스케일링을 해주었다. 스케일러는 standard scaler를 사용하였다. 이전 코드에서는 스케일링 하는 과정에도 test 데이터가 참여하고 있어서 해당 부분 수정해주었다. train 데이터에 대해서만 fit시킨 scaler로 train, test 스케일링을 해주었다.

    # scaling
    scaler = StandardScaler()
    scaler.fit(train[num_col])

    train_res = scaler.transform(train[num_col])
    test_res = scaler.transform(test[num_col])
    train[num_col] = train_res
    test[num_col] = test_res
  • Save Result

    train과 test는 저장해주었고, 초기 실행시에는 scaler를 pickle파일로 dump 시키게끔 하였다.

    # save train/test file
    save_file(train, 'train.csv')
    save_file(test, 'test.csv')

    # save scaler
    if not os.path.isdir( os.path.join(PROJECT_PATH, 'scaler.pkl') ):
        save_file(scaler, 'scaler.pkl')


1-4) Else process

해당 프로세스를 import해서 사용할 경우에는 scaler를 넘겨주게끔 하였다.

else:
    with open(os.path.join(PROJECT_PATH, 'scaler.pkl'), 'rb') as f:
        scaler = pickle.load( f )


1-5) 전체 Script

# Data Handling
import pandas as pd
import os
import pickle

# Model Selection
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler


PROJECT_PATH = 'D:/projects/test'
ORIGIN_FILE_NAME = '2019_2022_매매데이터.csv'
POP_FILE_NAME = 'pop.csv'


def make_merge(left1, left2, right, left_on, right_on =''):
    if right_on == '':
        left1 = pd.merge(left = left1, right = right, on = left_on)
        left2 = pd.merge(left = left2, right = right, on = left_on)
    else:
        left1 = pd.merge(left = left1, right = right, left_on = left_on, right_on = right_on)
        left2 = pd.merge(left = left2, right = right, left_on = left_on, right_on = right_on)
    return left1, left2

def save_file(target, file_name):
    _, file_ext = os.path.splitext(file_name)

    if file_ext == '.csv':
        target.to_csv( os.path.join(PROJECT_PATH, file_name) )
    elif file_ext == '.pkl':
        with open(os.path.join(PROJECT_PATH, file_name), 'wb') as f:
            pickle.dump( target, f )             
    else:
        pass


if __name__ == '__main__':
    # import df
    df = pd.read_csv(os.path.join(PROJECT_PATH, ORIGIN_FILE_NAME), encoding='cp949')

    # fillna
    df['거래유형'][ df['거래유형'] == '-' ] = '중개거래'

    # pre-proceess
    df['구'] = df['시군구'].apply(lambda x: x.split(' ')[1])
    df['계약년'] = df['계약년월'].apply(lambda x: str(x)[:4])
    df['계약월'] = df['계약년월'].apply(lambda x: str(x)[4:])    

    # merge pop data
    pop = pd.read_csv(os.path.join(PROJECT_PATH, POP_FILE_NAME), encoding='cp949')
    pop['구별'] = pop['구별'].apply(lambda x: x.replace(' ', ''))
    pop['인구'] = pop['인구'].apply(lambda x: x.replace(',', '')).astype("int")
    df = pd.merge(df, pop, left_on='구', right_on='구별')

    # train/test split
    train, test = train_test_split(df, test_size = 0.3, shuffle=True)

    # Featrue engineering
    dong_mean_money = pd.DataFrame(train.groupby(['시군구']).mean()['거래금액(만원)'].round().astype('int'))
    dong_sum_money = pd.DataFrame(train.groupby(['시군구']).sum()['거래금액(만원)'].round().astype('int'))
    gu_mean_money = pd.DataFrame(df.groupby(['구']).mean()['거래금액(만원)'].round().astype('int'))
    gu_sum_money = pd.DataFrame(df.groupby(['구']).sum()['거래금액(만원)'].round().astype('int'))

    dong_mean_money.columns = ['동별_평균_거래금액']
    dong_sum_money.columns = ['동별_총_거래금액']
    gu_mean_money.columns = ['구별_평균_거래금액']
    gu_sum_money.columns = ['구별_총_거래금액']

    train, test = make_merge(train, test, dong_mean_money, '시군구')
    train, test = make_merge(train, test, dong_sum_money, '시군구')
    train, test = make_merge(train, test, gu_mean_money, '구')
    train, test = make_merge(train, test, gu_sum_money, '구')

    # select columns
    col = [
        '전용면적(㎡)', '계약년', '계약월',
        '층', '건축년도', '거래유형', '동별_평균_거래금액',
        '동별_총_거래금액', '구별_평균_거래금액', '구별_총_거래금액', '인구', '거래금액(만원)'        
    ]
    num_col = [
        '전용면적(㎡)', '동별_평균_거래금액',
        '동별_총_거래금액', '구별_평균_거래금액', '구별_총_거래금액', '인구'        
    ]

    train, test = train[col], test[col]

    # scaling
    scaler = StandardScaler()
    scaler.fit(train[num_col])

    train_res = scaler.transform(train[num_col])
    test_res = scaler.transform(test[num_col])
    train[num_col] = train_res
    test[num_col] = test_res

    # save train/test file
    save_file(train, 'train.csv')
    save_file(test, 'test.csv')

    # save scaler
    if not os.path.isdir( os.path.join(PROJECT_PATH, 'scaler.pkl') ):
        save_file(scaler, 'scaler.pkl')

else:
    with open(os.path.join(PROJECT_PATH, 'scaler.pkl'), 'rb') as f:
        scaler = pickle.load( f )



2. Run_Scaler.py

위 코드에서 저장된 scaler를 불러와서 numeric 데이터를 스케일링 해주는 코드이다.

2-1) Import Library

위에서 작성한 new_script에서 scaler를 import 해주었다.

from new_script import scaler

필요한 라이브러리를 import해주고, 깔끔한 실행파일 제작을 위해 경고 log출력은 꺼주었다.

import numpy as np
import os
import warnings
warnings.filterwarnings('ignore')


2-2) Script

numeric 관측치를 받고 스케일링 해준 뒤, 출력해주었다. ‘x’를 input값으로 받을 때 까지 while문을 돌렸다.

GO = None

while True:
    col_list = ['전용면적(㎡)', '동별_평균_거래금액', '동별_총_거래금액', '구별_평균_거래금액', '구별_총_거래금액', '인구']

    data = input(','.join(col_list) + " 리스트를 입력하시오 : ")

    data = data.replace(' ', '').split(',')
    data = np.array(data).astype('int').reshape(1, -1)

    output = scaler.transform(data)[0]

    print('{:^12} | {:^12} | {:^12} | {:^12} | {:^12} | {:^12}'.format(col_list[0], col_list[1], col_list[2], col_list[3], col_list[4], col_list[5]))
    print('-'*150)
    print('{:^20}, {:^20}, {:^20}, {:^20}, {:^20}, {:^20}'.format(output[0], output[1], output[2], output[3], output[4], output[5]))

    GO = input('멈추려면 x 입력: ')
    if GO == 'x':
        break


2-3) Make .exe file

  • Install Pyinstaller
>>> pip install pyinstaller
  • Make .exe file

    배포해야하는 파일이 많으면 번거롭기 때문에, –onefile 옵션을 줘서 실행 파일에 배포 파일을 전부 담았다.

>>> pyinstaller --onefile Run_Scaler.py

성공적으로 변환하였다.


2-4) 전체 Script

from new_script import scaler

import numpy as np
import os
import warnings
warnings.filterwarnings('ignore')

GO = None

while True:
    col_list = ['전용면적(㎡)', '동별_평균_거래금액', '동별_총_거래금액', '구별_평균_거래금액', '구별_총_거래금액', '인구']

    data = input(','.join(col_list) + " 리스트를 입력하시오 : ")

    data = data.replace(' ', '').split(',')
    data = np.array(data).astype('int').reshape(1, -1)

    output = scaler.transform(data)[0]

    print('{:^12} | {:^12} | {:^12} | {:^12} | {:^12} | {:^12}'.format(col_list[0], col_list[1], col_list[2], col_list[3], col_list[4], col_list[5]))
    print('-'*150)
    print('{:^20}, {:^20}, {:^20}, {:^20}, {:^20}, {:^20}'.format(output[0], output[1], output[2], output[3], output[4], output[5]))

    GO = input('멈추려면 x 입력: ')
    if GO == 'x':
        break



3. 개선점

  • 지금 Run_Script는 Run_Preprocess에 종속되어있기 때문에 해당부분 코드를 수정해줘야겠다.
  • 다른 환경에 배포해봐서 잘 실행되는지 확인하기
  • 대략적인 score 확인이 끝났으니 Boosting 계열 알고리즘에 넣어서 score 확인해보기

댓글남기기