AI 개발 공부 공간

AI, 머신러닝, 딥러닝, Python & PyTorch, 실전 프로젝트

머신러닝/머신러닝: 실전 프로젝트 학습

인구 소득 예측 프로젝트 2 : 결측값 처리

qordnswnd123 2024. 12. 31. 18:57

1. train 데이터의 결측값 시각화

########## 데이터프레임의 결측값 분석 및 비율 계산##########
import numpy as np

train_miss_frame = train[train.columns[train.isnull().any(axis=0)]]

train_miss = pd.DataFrame({
    'missing': train_miss_frame.isnull().sum(),
    'ratio': np.round(train_miss_frame.isnull().sum() / train_miss_frame.shape[0], 4) * 100  
})

display(train_miss)

########## 시각화를 통한 결측값 분석 및 비율 시각화##########
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(5, 4))

width = 0.35

train_miss_col = ['native.country', 'occupation', 'workclass']

x = np.arange(len(train_miss_col))

y1 = train_miss['missing'].sort_index()  
y1_ = train_miss['ratio'].sort_index()  

bar1 = ax.bar(x, y1, label="train data", color="cornflowerblue")
ax.set_ylim(0, 600)
ax.set_xticks(x, train_miss_col)

ax.bar_label(bar1, padding=12, size=10)
ax.bar_label(bar1, labels=['(%.2f%%)' % y for y in y1_], padding=3, size=10)
ax.set_title("Missing Ratio", pad=10, size=15)
ax.legend(loc='best', fontsize=10)

plt.show()


2. 결측값 분포 시각화

import missingno as msno
msno.matrix(train)
plt.show()

 

※ 결과 해석

'workclass'와 'occupation' 칼럼에서는 비슷한 패턴이 나타나고 있습니다.
이것은 두 칼럼 모두에서 결측값이 비슷한 위치에 있음을 시각적으로 보여줍니다.
즉, 'workclass'와 'occupation'에서 결측값이 동시에 발생하는 경우가 많다는 것을 의미합니다.
이러한 패턴을 통해 두 변수가 어느 정도 상관관계가 있는 것으로 추측할 수 있으며, 이를 고려하여 결측값 처리를 해주는 것이 좋을 것으로 보입니다.


3. 직업(occupation) 결측값이 있는 행과 없는 행 분리

missing_occupation = train_origin[train_origin['occupation'].isnull()]
non_missing_occupation = train_origin[~train_origin['occupation'].isnull()]

display(missing_occupation.head(3))
display(non_missing_occupation.head(3))

display(missing_occupation.shape)
display(non_missing_occupation.shape)

 


4. 직업(occupation) 열을 카테고리로 변환 및 숫자 인코딩

missing_occupation = missing_occupation.copy()
non_missing_occupation = non_missing_occupation.copy()

# 'occupation' 열을 카테고리 타입으로 변환
non_missing_occupation['occupation'] = non_missing_occupation['occupation'].astype('category')

# 카테고리 목록 및 매핑 생성
occupation_categories = non_missing_occupation['occupation'].cat.categories
category_to_number = {category: number for number, category in enumerate(occupation_categories)}

# 'occupation' 열을 숫자로 인코딩
non_missing_occupation['occupation'] = non_missing_occupation['occupation'].map(category_to_number)
non_missing_occupation.head(5)

 


5. 랜덤 포레스트 모델을 사용한 직업(occupation) 결측값 대체

numeric_columns = non_missing_occupation.select_dtypes(include=['int', 'float']).columns.tolist() + ['occupation']

# 입력 피처(X)와 타겟(occupation)을 분리
X_train = non_missing_occupation[numeric_columns[:-1]]
y_train = non_missing_occupation[numeric_columns[-1]]

X_valid = missing_occupation[numeric_columns[:-1]]
y_valid = missing_occupation[numeric_columns[-1]]

# 랜덤 포레스트 모델 훈련
from sklearn.ensemble import RandomForestClassifier

model = RandomForestClassifier(random_state=24)
model.fit(X_train, y_train)
predicted_occupation = model.predict(X_valid)

# 결측값 대체
missing_occupation['occupation'] = predicted_occupation

missing_occupation.head(5)

 

※ int와 float 피처만 추출한 후 occupation을 키워주었는지에 대한 이유

범주형 변수를 모델에 입력으로 사용하기 위해서는 추가적인 전처리가 필요합니다.
예를 들어, 범주형 변수는 일반적으로 원-핫 인코딩 또는 레이블 인코딩과 같은 방법으로 숫자로 변환해야 합니다.

데이터를 전처리하고 모델을 학습시키는 것은 중요한 작업이지만, 이번 스터디에서는 주요 관점을 occupation 열의 결측값을 예측하는 것입니다.

따라서, 간단하게 하기 위해 숫자형 변수만을 사용하고 occupation 열을 타겟으로 설정했습니다.
이렇게 하면 추가적인 범주형 변수 전처리 작업을 생략할 수 있습니다.
결과적으로, 모델은 숫자로 표현된 입력 데이터로 occupation을 예측하는 데 집중할 수 있게 됩니다.

 


6. 직업(occupation) 열의 카테고리 역방향 매핑

# Restore the train DataFrame by combining non-missing and missing data
train_imputed_occupation = pd.concat([non_missing_occupation, missing_occupation])

# Reverse the category mapping (from number to category)
number_to_category = {num: cat for num, cat in enumerate(occupation_categories)}

# Restore the 'occupation' column to its original category values
train_imputed_occupation['occupation'] = train_imputed_occupation['occupation'].map(number_to_category)

# Display the first 3 rows of the restored DataFrame
train_imputed_occupation.head(3)

 

※ 원래의 범주형 분포로 데이터셋을 복원하는 이유

라벨 인코딩 또는 숫자 인코딩을 적용한 경우, 이 작업은 주로 모델 학습을 위한 train 데이터와 모델 성능 평가를 위한 test 데이터에 동일한 방식을 적용하기 위해 필요합니다.

예를 들어, 'occupation' 열을 라벨 인코딩하여 train 데이터로 모델을 학습했다고 가정해봅시다.
이제 모델을 사용하여 test 데이터에서 예측을 수행하려고 할 때, test 데이터의 'occupation' 열을 동일한 방식으로 라벨 인코딩해야 합니다. 그렇지 않으면 모델은 test 데이터를 이해하지 못하고 예측을 수행할 수 없습니다.

따라서 train 데이터와 test 데이터 모두에서 동일한 인코딩 방식을 적용하기 위해, train 데이터의 'occupation' 열을 다시 원래의 범주형 분포로 복원하여, 그 인코딩 방식을 test 데이터에도 동일하게 적용할 수 있게 합니다.
결과적으로 이렇게 하면 모델은 train 데이터와 test 데이터 모두에서 동일한 범주형 분포의 데이터를 다루며 일관성 있게 예측을 수행할 수 있습니다.


7. 학습 및 테스트 데이터의 결측값 대체

지금까지는 결측치 피처 중 하나인 'occupation' 열의 결측치를 랜덤 포레스트(RF) 모델을 사용하여 예측하고 대체하였습니다.

그러나 train_imputed_occupation 데이터프레임의 'native.country'와 'workclass' 피처에서도 결측치가 존재하며, 또한 test 데이터셋에도 결측치가 존재합니다.

이들은 더 간단한 방식으로 결측치를 대체하기 위해 각 피처의 최빈값(가장 빈번하게 나타나는 값) 을 사용하려고 합니다.

test_origin = pd.read_csv('test.csv')

# Import SimpleImputer from sklearn
from sklearn.impute import SimpleImputer

# Create SimpleImputer instance with 'most_frequent' strategy
imputer = SimpleImputer(strategy='most_frequent')

# Apply imputer to fill missing values in 'occupation', 'workclass', and 'native.country' columns in the train data
train_imputed_occupation[['occupation', 'workclass', 'native.country']] = imputer.fit_transform(train_imputed_occupation[['occupation', 'workclass', 'native.country']])

# Apply imputer to fill missing values in the same columns in the test data
test_origin[['occupation', 'workclass', 'native.country']] = imputer.transform(test_origin[['occupation', 'workclass', 'native.country']])

8. 학습 및 테스트 데이터의 결측값 확인

# Check missing values in the training data
missing_train = train_imputed_occupation[['occupation', 'workclass', 'native.country']].isnull().sum()

# Check missing values in the test data
missing_test = test_origin[['occupation', 'workclass', 'native.country']].isnull().sum()

# Print missing values for both datasets
print("학습 데이터 결측값 확인:\n", missing_train)
print("\n테스트 데이터 결측값 확인:\n", missing_test)

# Display the first 5 rows of the train DataFrame
display(train_imputed_occupation.head(5))

# Display the first 5 rows of the test DataFrame
display(test_origin.head(5))