Titanic 생존자 예측
② Feature Engineering & 모델링
3. Feature Engineering
Feature Engineering을 시작하기에 앞서 상관계수, 결측치를 확인해보자
train.corr()
train.isnull().sum()
Train과 Test data를 한 번에 변환하기 위해 List에 담아주었다.
data_list = [train, test]
3-1. Sex
Sex는 male(남성), female(여성)으로 나누어져 있다. 이를 0과 1로 변경해주자
# int type의 숫자 0과 1로 매핑
for data in data_list:
data['Sex'] = data['Sex'].astype('category').cat.codes
train['Sex'].head()
3-2. Age
위에서 Age 컬럼에 177개의 결측치가 존재하였다.
이 결측치에 대해 여성에겐 여성 나이의 평균, 남성에겐 남성 나이의 평균으로 채워줄 것이다.
sex_mean = train.groupby('Sex')['Age'].mean()
sex_mean
- 여성의 평균 나이는 28세, 남성의 평균 나이는 31세이다.
# Age 결측치 처리
for data in data_list:
data.loc[(data['Sex'] == 0) & (data['Age'].isnull()), 'Age'] = sex_mean[0]
data.loc[(data['Sex'] == 1) & (data['Age'].isnull()), 'Age'] = sex_mean[1]
train.isnull().sum()
이제 Age를 카테고리화 할 것이다.
- 남자와 여자는 0, 1로 차이가 극명하게 나뉘는 값이지만 요금이나 나이의 경우 20과 30의 차이가 10이라고 해서 극명하게 나뉘는 값이 아니다.
- 이를 전체적인 범주에 맞게 낮춰주기 위해 카테고리화 할 것이다
먼저, Age를 5개 구간으로 나누어 AgeRange 컬럼에 저장해주자
# 카테고리화
train['AgeRange'] = pd.cut(train['Age'], 5)
train[['AgeRange', 'Survived']].groupby(['AgeRange']).mean()
- 16, 32, 48, 64 값들을 기준으로 카테고리화를 하면 되겠다
for data in data_list:
data.loc[ data['Age'] <= 16, 'Age'] = 0
data.loc[(data['Age'] > 16) & (data['Age'] <= 32), 'Age'] = 1
data.loc[(data['Age'] > 32) & (data['Age'] <= 48), 'Age'] = 2
data.loc[(data['Age'] > 48) & (data['Age'] <= 64), 'Age'] = 3
data.loc[ data['Age'] > 64, 'Age'] = 4
train.drop('AgeRange', inplace=True, axis=1)
train.head()
카테고리화가 잘 되었고 'AgeRange' 컬럼은 이제 필요 없으므로 drop 해주었다.
3-3. Name
탑승객들의 이름 앞을 추출해보면 Mr, Mrs, Miss 등의 명칭등을 뽑아낼 수 있다.
이름 전체를 사용하는 것은 생존자 예측에 큰 도움이 되지 않을 것으로 예상되므로 이름을 간소화해보자
train['Title'] = train['Name'].str.extract(' ([A-Za-z]+)\.', expand=False)
train['Title'].value_counts()
- Mr, Miss, Mrs, Master가 가장 많이 존재하고 나머지는 적게 존재하므로 복잡도를 증가시키지 않기 위해 모두 'Other'로 통일하면 좋을 것 같다.
for data in data_list:
data['Title'] = data['Name'].str.extract(' ([A-Za-z]+)\.', expand=False)
data['Title'] = data['Title'].replace(['Dr', 'Rev', 'Mlle', 'Major', 'Col', 'Countess',
'Capt', 'Ms', 'Sir', 'Lady', 'Mme', 'Don', 'Jonkheer'], 'Other')
# -> 범주형 -> 수치형
data['Title_name'] = data['Title'].astype('category').cat.codes
- title_name이라는 컬럼에 title을 카테고리화 -> 수치화 한 값을 넣어주었다.
이름의 Title별로 생존율을 확인해보면 다음과 같다.
train[['Title', 'Survived']].groupby(['Title']).mean()
Title_name 컬럼을 만들어줬으니 이제 필요 없는 'Name', 'Title' 컬럼을 지우고 확인해보자
# 불필요한 데이터 지우기
train.drop(['Name', 'Title'], axis=1, inplace = True)
test.drop(['Name', 'Title'], axis=1, inplace = True)
train.head()
3-4. SibSp + Parch
SibSp는 함께 탑승한 형제자매, 배우자의 총합 / Parch는 함께 탑승한 부모, 자녀의 총합 이었다.
혼자 탑승한 탑승객과 그 외의 탑승객의 사망률에 차이가 있었기 때문에 가족이라는 새로운 컬럼을 만들어주자
train['FamilySize'] = train['SibSp'] + train['Parch'] + 1
test['FamilySize'] = train['SibSp'] + train['Parch'] + 1
train.head()
피봇 테이블을 이용하여 가족수별 생존율을 확인해보고 시각화해보자
pd.pivot_table(train, index='FamilySize', values='Survived')
sns.countplot(data=train, x='FamilySize', hue='Survived')
FamilySize라는 컬럼을 만들어주었으므로 이제 필요 없는 'SibSp', 'Parch' 컬럼을 제거해주자
# 컬럼 제거
drop_list = ['SibSp', 'Parch']
for data in data_list:
data.drop(drop_list, inplace=True, axis=1)
3-5. Embarked
위에서 Embarked 컬럼에 2개의 결측치가 존재했다.
결측치가 2개뿐이니 가장 많은 비율을 차지하는 'S'로 채워주었다.
train['Embarked'].value_counts()
# 결측치 처리
for data in data_list:
data['Embarked'] = data['Embarked'].fillna('S')
sns.countplot(data=train, x='Embarked', hue='Survived')
- Cherbourg에서 탑승한 사람들이 상대적으로 많이 살아남은 것으로 보인다
이제 S, C, Q를 각각 0, 1, 2로 매핑해주자
em_mapping = {'S':0, 'C':1, 'Q':2}
for data in data_list:
data['Embarked'] = data['Embarked'].map(em_mapping)
3-6. Cabin
먼저 Cabin 컬럼에 대해 살펴보면 다음과 같이 선실을 대표하는 알파벳이 맨 처음에 나온다
train['Cabin'].value_counts()
for data in data_list:
data['Cabin'] = data['Cabin'].fillna('N')
data['Cabin'] = data['Cabin'].apply(lambda x:x[0])
data['Cabin'] = data['Cabin'].astype('category').cat.codes
train.head()
- 결측치에는 'N'으로 저장하고 각각 맨 앞자리의 알파벳을 불러왔다.
- 그 다음 Cabin 컬럼을 카테고리화 한 뒤 수치형으로 바꿔주었다.
3-7. Fare
Fare도 위의 Age에서 한 것과 같이 이미 숫자이긴 하지만 단순화하기 위하여 4개 구간으로 나누어줄 것이다
# 카테고리화
for data in data_list:
data['Farerange'] = pd.cut(data['Fare'], 4)
train[['Farerange', 'Survived']].groupby(['Farerange']).mean()
- 128, 256, 384를 기준으로 나눠주면 될 것 같다
for data in data_list:
data.loc[data['Fare'] <= 128, 'Fare'] = 0
data.loc[(data['Fare'] > 128) & (data['Fare'] <= 256), 'Fare'] = 1
data.loc[(data['Fare'] > 256) & (data['Fare'] <= 384), 'Fare'] = 2
data.loc[data['Fare'] > 384, 'Fare'] = 3
train.head()
이제 Farerange 컬럼은 필요 없으므로 제거해주었다
# 컬럼 지우기
for data in data_list:
data.drop('Farerange', axis=1, inplace=True)
3-8. Drop Data
train 데이터의 컬럼들을 살펴보면 다음과 같다
- PassengerId, Ticket 컬럼들은 필요 없으므로 drop해 줄 것이다
drop_list = ['PassengerId', 'Ticket']
for data in data_list:
data.drop(drop_list, inplace=True, axis=1)
for data in data_list:
data.drop('Cabin', inplace=True, axis=1)
- Cabin 컬럼도 제거하는 것이 조금 더 성능이 좋아서 뒤늦게 제거에 추가했다.
3-9. test data 결측치
마지막으로 test data에 있는 Fare 결측치 1개를 Fare의 평균으로 채워주었다.
test[test['Fare'].isnull()] = test['Fare'].mean()
피쳐 엔지니어링이 끝났으므로 train, test 데이터를 확인해보자
train.head()
test.head()
상관계수도 확인해보면
train.corr()
- FE 전후 상관계수를 확인해보면 원래는 없었거나 상관관계가 거의 없었던 Pclass, Sex, Fare, Embarked 등이 이제는 Survived에 영향을 끼치는 것으로 보인다.
4. 모델링
여러 알고리즘을 돌려보고 점수가 가장 좋은 것을 최종 모델로 선정하려고 한다.
먼저 train_input, train_target을 만들어주고 라이브러리들을 불러오자
train_input = train.drop('Survived', axis=1).values
train_target = train['Survived'].values
print(train_input.shape, train_target.shape) #(891, 7) (891,)
# 라이브러리
from sklearn.model_selection import KFold
from sklearn.model_selection import cross_validate
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import StratifiedKFold
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.experimental import enable_hist_gradient_boosting
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.svm import SVC
4-1. KNN
model = KNeighborsClassifier()
score = cross_validate(model, train_input, train_target,
return_train_score=True, n_jobs=-1,
cv = StratifiedKFold())
print(np.mean(score['train_score']), np.mean(score['test_score']))
# 0.8369821296310886 0.8013495700207143
- train data의 정확도는 약 84%, 검증 데이터의 정확도는 약 80%
4-2. Decision Tree
model = DecisionTreeClassifier()
score = cross_validate(model, train_input, train_target,
return_train_score=True, n_jobs=-1,
cv = StratifiedKFold())
print(np.mean(score['train_score']), np.mean(score['test_score']))
# 0.872335203366059 0.8024982738057874
- train data의 정확도는 약 87%, 검증 데이터의 정확도는 약 80% -> 과대적합으로 보임
4-3. Random Forest
model = RandomForestClassifier()
score = cross_validate(model, train_input, train_target,
return_train_score=True, n_jobs=-1,
cv = StratifiedKFold())
print(np.mean(score['train_score']), np.mean(score['test_score']))
# 0.872335203366059 0.7980164459230431
- train data의 정확도는 약 87%, 검증 데이터의 정확도는 약 80% -> 과대적합으로 보임
4-4. GradientBoosting
model = GradientBoostingClassifier()
score = cross_validate(model, train_input, train_target,
return_train_score=True, n_jobs=-1,
cv = StratifiedKFold())
print(np.mean(score['train_score']), np.mean(score['test_score']))
# 0.8555009691602187 0.8204632477559475
- train data의 정확도는 약 86%, 검증 데이터의 정확도는 약 82%
4-5. HistGradientBoosting
model = HistGradientBoostingClassifier()
score = cross_validate(model, train_input, train_target,
return_train_score=True, n_jobs=-1,
cv = StratifiedKFold())
print(np.mean(score['train_score']), np.mean(score['test_score']))
# 0.8597093307278945 0.8126043562864854
- train data의 정확도는 약 86%, 검증 데이터의 정확도는 약 81% -> 과대적합으로 보임
4-6. Naive Bayes
model = GaussianNB()
score = cross_validate(model, train_input, train_target,
return_train_score=True, n_jobs=-1,
cv = StratifiedKFold())
print(np.mean(score['train_score']), np.mean(score['test_score']))
# 0.8117284145169169 0.812560416797439
- train data의 정확도는 약 81%, 검증 데이터의 정확도는 약 81%
4-7. Support Vector Machine
model = SVC()
score = cross_validate(model, train_input, train_target,
return_train_score=True, n_jobs=-1,
cv = StratifiedKFold())
print(np.mean(score['train_score']), np.mean(score['test_score']))
# 0.8366992609168413 0.8349946644906158
- train data의 정확도는 약 84%, 검증 데이터의 정확도는 약 80%
4-8. 최종 모델
KNN, Gradient Boosting, SVM의 성능이 가장 좋았고 최종 모델로는 KNN으로 정하였다.
train 데이터 정확도 | 검증 데이터 정확도 | |
KNN | 0.83698 | 0.80135 |
Gradient Boosting | 0.8555 | 0.82046 |
SVM | 0.836699 | 0.834995 |
model = KNeighborsClassifier()
model.fit(train_input, train_target)
test_input = test.values
pred = model.predict(test_input)
submission['Survived'] = pred
submission
submission.to_csv("C:/data/titanic/submission_result.csv", index=False)
예측 Model의 성능을 올리기 위해 개선할 점을 생각해보면
- 더 자세한 EDA
- 결측값을 다른 방법으로 처리하기
- 사용하지 않은 Feature을 사용하거나, 사용한 Feature들 중 필요 없다고 생각하는 것들은 제거해보기
- Modeling 단계에서 파라미터 값을 튜닝해보며 최적화 값 찾기
- AutoML 이라는 것을 알게 되었는데 아직 시도는 못 해봤다. 다음에 시도해봐야지!
train 데이터 정확도 | 검증 데이터 정확도 | |
KNN | 0.83698 | 0.80135 |
Gradient Boosting | 0.8555 | 0.82046 |
SVM | 0.836699 | 0.834995 |
사실 나는 KNN보다 Gradient Boosting이나 SVM가 더 적합한 모델일 것이라고 생각했는데
캐글의 점수는 그렇지 않았다.
캐글에서 이 대회의 평가 지표는 Metric여서 그런게 아닐까 싶다!
'Python > 데이터분석 실습' 카테고리의 다른 글
[Kaggle] Titanic 생존자 예측 ① 데이터 확인 & EDA (0) | 2022.05.09 |
---|---|
[Kaggle] 자전거 수요 예측 ② Feature Engineering & 모델링 (0) | 2022.05.04 |
[Kaggle] 자전거 수요 예측 ① 데이터 확인 & EDA (0) | 2022.05.03 |
[Kaggle ] Pima Indians Diabetes 예측 ② 데이터 전처리 후 모델 학습/예측 (0) | 2022.03.01 |
[Kaggle] Pima Indians Diabetes 예측 ① EDA, 시각화 탐색 (0) | 2022.02.28 |