앞에서 결측치 처리 방법으로 크게 3가지 방법을 학습하였다. 그래서 실제로 얼마나 유의미한 차이가 있을까? 앞에서의 3가지 방법을 다음 데이터셋에 적용해서 비교해보았다.
https://www.kaggle.com/c/spaceship-titanic
위 데이터셋은 승객들이 정상적으로 다른 차원의 공간으로 보내졌을지(보내졌다면 True, 아니라면 False) 예측하는 이진 분류 문제이다. 나는 train 데이터셋으로 각 방법에 따른 정확도를 평가해볼 것이다.
데이터는 특별한 전처리 없이 카테고리 변수들만 다시 인코딩해주었다. 사이킷런의 LabelEncoder()는 인코딩 시 결측치까지 인코딩하기 때문에 나중에 인코딩하였다.
train = pd.read_csv('/content/drive/MyDrive/ML_Data/kaggle/spaceship-titanic/train.csv')
train.shape
=>
(8693, 15)
train.info()
=>
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 8693 entries, 0 to 8692
Data columns (total 14 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 PassengerId 8693 non-null object
1 HomePlanet 8492 non-null object
2 CryoSleep 8476 non-null object
3 Cabin 8494 non-null object
4 Destination 8511 non-null object
5 Age 8514 non-null float64
6 VIP 8490 non-null object
7 RoomService 8512 non-null float64
8 FoodCourt 8510 non-null float64
9 ShoppingMall 8485 non-null float64
10 Spa 8510 non-null float64
11 VRDeck 8505 non-null float64
12 Name 8493 non-null object
13 Transported 8693 non-null bool
dtypes: bool(1), float64(6), object(7)
memory usage: 891.5+ KB
train.isnull().sum() # Null 데이터 확인
=>
PassengerId 0
HomePlanet 201
CryoSleep 217
Destination 182
Age 179
VIP 203
RoomService 181
FoodCourt 183
ShoppingMall 208
Spa 183
VRDeck 188
Name 200
Transported 0
Cabin_deck 199
Cabin_side 199
dtype: int64
train[['Cabin_deck', 'Cabin_num', 'Cabin_side']] = train.loc[:,'Cabin'].str.split('/', expand=True)
train = train.drop(['Cabin', 'Cabin_num'], axis=1) # Cabin_num 변수는 유의한 차이가 없어서 drop
train['Transported'] = train['Transported'].map({False:0, True:1})
train_cat = train.select_dtypes(exclude=['number'])
cols = list(train_cat.columns)
cols.remove('Name')
cols.append('Age')
cols.append('Transported')
cols
=>
['PassengerId',
'HomePlanet',
'CryoSleep',
'Destination',
'VIP',
'Cabin_deck',
'Cabin_side',
'Age',
'Transported']
# 카테고리 변수 확인
features = ['HomePlanet', 'CryoSleep', 'Destination', 'VIP', 'Cabin_deck', 'Cabin_side']
for feature in features:
print(feature)
print("------------------------------------------")
print(train[feature].value_counts())
=>
HomePlanet
------------------------------------------
Earth 4602
Europa 2131
Mars 1759
Name: HomePlanet, dtype: int64
CryoSleep
------------------------------------------
False 5439
True 3037
Name: CryoSleep, dtype: int64
Destination
------------------------------------------
TRAPPIST-1e 5915
55 Cancri e 1800
PSO J318.5-22 796
Name: Destination, dtype: int64
VIP
------------------------------------------
False 8291
True 199
Name: VIP, dtype: int64
Cabin_deck
------------------------------------------
F 2794
G 2559
E 876
B 779
C 747
D 478
A 256
T 5
Name: Cabin_deck, dtype: int64
Cabin_side
------------------------------------------
S 4288
P 4206
Name: Cabin_side, dtype: int64
train = train[cols]
train = train.drop('PassengerId', axis=1)
# Imputation 방법에 따른 복사 데이터셋 생성
train_imp1 = train.copy()
train_imp2 = train.copy()
train_imp3 = train.copy()
학습은 모두 동일하게 LogisticRegression(random_state=42)으로 학습하였고, 평가지표는 정확도(Accuracy)를 사용하였다.
1. Listwise Deletion
train_imp1 = train_imp1.dropna()
print(f"Original Data: {train.shape} => Listwise Deletion: {train_imp1.shape}")
=>
Original Data: (8693, 8) => Listwise Deletion: (7572, 8)
train_imp1 = train_imp1.reset_index()
train_imp1 = train_imp1.drop('index', axis=1)
X_train, y_train = train_imp1.drop(columns='Transported'), train_imp1['Transported']
features = ['HomePlanet', 'CryoSleep', 'Destination', 'VIP', 'Cabin_deck', 'Cabin_side']
for feature in features:
le = LabelEncoder()
le = le.fit(X_train[feature])
X_train[feature] = le.transform(X_train[feature])
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_validate
model1 = LogisticRegression(random_state=42)
cv1 = cross_validate(model1, X_train, y_train, cv=5, scoring='accuracy')
round(cv1['test_score'].mean(), 4) * 100
=>
71.87
Listwise Deletion의 경우 71.87%의 정확도를 보였다.
2. Mean / Median imputation
카테고리컬 데이터들은 최빈값으로 대체하였다.
features = ['HomePlanet', 'CryoSleep', 'Destination', 'VIP', 'Cabin_deck', 'Cabin_side']
for feature in features:
print(train_imp2[feature].mode())
print(round(train_imp2['Age'].mean()))
=>
0 Earth
Name: HomePlanet, dtype: object
0 False
Name: CryoSleep, dtype: object
0 TRAPPIST-1e
Name: Destination, dtype: object
0 False
Name: VIP, dtype: object
0 F
Name: Cabin_deck, dtype: object
0 S
Name: Cabin_side, dtype: object
29
for feature in features:
mode = train_imp2[feature].mode()[0]
null_idx = train_imp2[feature].isnull()
train_imp2.loc[null_idx, feature] = mode
mean = round(train_imp2['Age'].mean())
train_imp2['Age'] = train_imp2['Age'].fillna(mean)
train_imp2.isnull().sum()
=>
HomePlanet 0
CryoSleep 0
Destination 0
VIP 0
Cabin_deck 0
Cabin_side 0
Age 0
Transported 0
dtype: int64
train_imp2.shape
=>
(8693, 8)
model2 = LogisticRegression(random_state=42)
cv2 = cross_validate(model2, X_train, y_train, cv=5, scoring='accuracy')
round(cv2['test_score'].mean(), 4) * 100
=>
71.6
Mean / Median imputation의 경우 71.6%의 정확도를 보였다.
3. Model-based Imputation
수많은 imputation 방법 중, 나는 miceforest 패키지의 MICE를 활용하였다. MICE(Multiple Imputation by Chained Equations)란 예측 모델을 기반으로 반복적으로 결측치를 대체하여 수렴하는 알고리즘이다. miceforest 패키지에서는 LightGBM 모델을 기반으로 알고리즘이 돌아간다고 한다. 예측 모델을 기반으로 결측치 대체를 수행하기 때문에 시간이 오래 걸린다.
!pip install miceforest
cats = train_imp3.select_dtypes(exclude='number').columns
for feature in cats:
# miceforest를 사용하기 위해서는 변수타입이 category이거나 numeric이어야 한다.
train_imp3[feature] = train_imp3[feature].astype('category')
train_imp3.info()
import time
import miceforest as mf
start_time = time.time()
kds = mf.ImputationKernel(
train_imp3,
random_state=42
)
kds.mice(5)
train_imp3 = kds.complete_data()
end_time = time.time()
t = round(end_time - start_time, 2)
print(f"{t}sec")
=>
20.3sec # 앞의 두 예시들은 거의 즉각적으로 수행이 가능한 데에 반해 20초나 걸렸다.
X_train, y_train = train_imp3.drop(columns='Transported'), train_imp3['Transported']
features = ['HomePlanet', 'CryoSleep', 'Destination', 'VIP', 'Cabin_deck', 'Cabin_side']
for feature in features:
le = LabelEncoder()
le = le.fit(X_train[feature])
X_train[feature] = le.transform(X_train[feature])
model3 = LogisticRegression(random_state=42)
cv3 = cross_validate(model3, X_train, y_train, cv=5, scoring='accuracy')
round(cv3['test_score'].mean(), 4) * 100
=>
72.00999999999999
MICE의 경우 72.01%의 정확도를 보였다.
결측치 대체 방법만을 바꿔주는 것만으로도 모델의 학습에 작지 않은 영향을 미치는 것을 확인할 수 있다.
참고자료
'Data Science > Machine Learning' 카테고리의 다른 글
[Machine Learning] 데이터 전처리(2) - Imbalanced class(2) (0) | 2023.08.19 |
---|---|
[Machine Learning] 데이터 전처리(2) - Imbalanced class(1) (0) | 2023.08.18 |
[Machine Learning] 데이터 전처리(1) - 결측치 처리(1) (0) | 2023.06.22 |
[Machine Learning] 평가 지표 - 분류(Classification) (0) | 2023.02.07 |
[Machine Learning] 손실 함수 (loss function) (0) | 2023.01.31 |