데이터 분석가
Published 2023. 10. 22. 20:07
전세가 예측 모델 프로젝트(5) Project
  • 저번 글에서 정규성과 등분산성의 불만족으로 통계분석 파트에서는 종속변수를 범주형 데이터로 분할하여 로지스틱 회귀분석을 진행하기로 결론을 내렸다. 이번 글에서는 로지스틱 회귀 분석을 진행하며 겪었던 고찰과정을 적어보려고 한다.

Data leakage

정의 및 방지책

  • 학습 데이터와 테스트 데이터를 나누어 모델을 학습시킬 때 데이터가 유출되어 학습 데이터 외의 정보로 모델이 학습되는 것을 Data leakage라고 합니다. 이로 인해 모델의 성능지표가 왜곡되어 나타나고 모델의 일반화가 저하되어 실제 현장에서 사용될 때 문제가 생길 수 있습니다.
  • 이러한 오류를 방지하기 위해서는 학습 데이터에 모델을 활용하여 예측할 시점에 해당 정보를 사용할 수 있는지 점검한 상태에서 1번과 2번 방법을 통해 Data leakage를 방지할 수 있다.
    • 1번 : 파이프라인을 활용한다.
    • 2번 : 적절한 데이터 분할 및 데이터 전처리 과정이 이루어진다.
  • 아래 코드를 통해 프로젝트에서 각각의 방법이 어떻게 적용되었는지 알아보자.

데이터 분할 및 데이터 전처리

데이터 분할

  • 먼저 데이터를 분할합니다.
# 데이터 분할: 학습용(train), 테스트용(test)
train_data, test_data = train_test_split(df, test_size=0.2, random_state=42)

# 학습 데이터에서 검증용(validation) 데이터를 다시 분할
train_data, val_data = train_test_split(train_data, test_size=0.2, random_state=42)

타겟 인코딩

  • 학습 데이터에 테스트 데이터의 정보가 흘러 들어가는 것을 막기 위해 학습 데이터의 타겟인코딩 정보를 저장한 뒤 이 매핑정보를 테스트 데이터와 검증 데이터에 적용해 줍니다.
# 범주형 변수 리스트 지정
categorical_columns = ['Building_Use_y', 'Region_Name_y', 'YearMonth']
target_column = 'JS_Price'

# 각 범주형 변수에 대한 인코딩 매핑을 저장할 딕셔너리 초기화
encoding_maps = {}

# 학습 데이터에서 각 범주의 평균 타겟 값 계산 및 매핑 저장
for categorical_column in categorical_columns:
    encoding_map = train_data.groupby(categorical_column)[target_column].mean().to_dict()
    encoding_maps[categorical_column] = encoding_map

    # 데이터에 타겟 인코딩 적용
    train_data[categorical_column + '_encoded'] = train_data[categorical_column].map(encoding_map)
    val_data[categorical_column + '_encoded'] = val_data[categorical_column].map(encoding_map)
    test_data[categorical_column + '_encoded'] = test_data[categorical_column].map(encoding_map)

    # 기존 범주형 변수 삭제
    train_data.drop(columns=[categorical_column], inplace=True)
    val_data.drop(columns=[categorical_column], inplace=True)
    test_data.drop(columns=[categorical_column], inplace=True)

파이프라인

  • 스케일링과 오버샘플링, 인코딩 등의 데이터 전처리를 파이프라인을 이용해 처리합니다. 파이프라인 코드는 프로젝트가 마감된 이후에 작성하여 Github에 추가하였습니다. 따라서 ppt나 pdf자료에는 고려되어 있지 않습니다.
# 범주형 변수 리스트 지정
categorical_columns = ['Building_Use_y', 'Region_Name_y', 'YearMonth']
target_column = 'JS_Price'

# JS_Price를 4개의 범주로 나누고 기존 변수 삭제
df['JS_Price_Category'] = pd.cut(df['JS_Price'], bins=5, labels=False)
df.drop('JS_Price', axis=1, inplace=True)

# 선택한 독립변수만 추출
selected_features = [
'Population',
'Crime_Rates',
'YearMonth',
'HSP_index',
'TC_index',
'CA_index',
'SDT_index',
'IR',
'UR',
'Floor'
]

# 범주형 열 중에서 선택한 독립 변수에 포함된 열 확인
categorical_columns_exist = list(set(categorical_columns).intersection(set(selected_features)))

# 데이터 분할
X = df[selected_features]
y = df['JS_Price_Category']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 파이프라인 설정 (타겟 인코딩 포함 여부에 따라 다르게 설정)
if categorical_columns_exist:
    pipeline = [
        ('target_encoder', TargetEncoder(cols=categorical_columns_exist)),  # 타겟 인코딩
        ('scaler', StandardScaler()),  # 수치형 변수 스케일링
        ('oversampler', RandomOverSampler(sampling_strategy='auto', random_state=42)),  # 오버샘플링
        ('classifier', LogisticRegression(multi_class='multinomial', solver='lbfgs'))  # 로지스틱 회귀 모델
    ]
else:
    pipeline = [
        ('scaler', StandardScaler()),  # 수치형 변수 스케일링
        ('oversampler', RandomOverSampler(sampling_strategy='auto', random_state=42)),  # 오버샘플링
        ('classifier', LogisticRegression(multi_class='multinomial', solver='lbfgs'))  # 로지스틱 회귀 모델
    ]

결론

  • 나는 파이프라인의 존재를 잘 몰랐기 때문에 2번의 방법을 선택하여 로지스틱 회귀분석을 진행하였다. 하지만 이러한 과정을 거치면 파이프라인을 활용할 때에 비해 1) 코드가 지나치게 복잡해져 코드의 가독성이 떨어지고 모델의 모듈화가 진행되지 않는다. 2) Data leakage의 위험을 완전히 배제할 수 없는 등의 단점이 있다. 따라서 중간에 파이프라인으로 통계분석을 다시 진행하고자 했지만 마감기한이 얼마 남지 않았기 때문에 기존의 2번 방법으로 프로젝트를 끝마치고 다음부터는 반드시 파이프라인을 적용하여 데이터 전처리를 진행해야겠다는 개선점을 얻게 되었다.
profile

데이터 분석가

@이꾹꾹

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!