새소식

데이터 사이언스

Kaggle : Titanic (data preprocessing, feature engineering)

  • -

1. 데이터 프로파일링

 

2. 데이터 전처리 및 특성 엔지니어링

 

3. 모델링


저번 포스트를 끝으로 데이터 프로파일링을 마쳤다.

데이터 프로파일링 과정을 통해 각 특성마다 필요한 특성인지 아닌지에 대한 평가하고,

FamilySize라는 새로운 특성을 만들었다.

 

이번 포스트에서는 특성 엔지니어링에 대해 다룬다.

특성 엔지니어링에서 요구되는 작업은 결측치 처리, 이상치 제거, 원핫인코딩, 타입 변환 등이 있다.

모델에 학습시킬 최종 데이터셋을 만드는 과정이라고 생각하면 된다.

 

데이터셋도 있고, 특성도 전에 선택하지 않았나? 왜 특성 엔지니어링을 또 진행하는가?

이를 설명하려면 데이터 유형에 따른 성질(categorical, numerical), 원핫인코딩에 대한 이해가 필요하다.

 

 

1. 데이터 유형에 따른 성질

Categorical(범주형) Numerical(수치형)
Nominal (명목형) Ordinal (순서형) interval (구간형) ratio (비율형)
구별 순서(대소) 값 사이의 간격이 동일함 절대적 원점

* Categorical과 Qualitative(질적)은 같은 의미

* Numerical과 Quantitavive(양적)은 같은 의미

 

① Nominal (명목형)

성질: 구별

 ex) 도시 -> '서울', '부산', '대구'...

 

② Ordinal (순서형)

성질: 구별 + 순서

ex) 음식맛 -> '나쁨', '보통', '좋음'

 

③ Interval (구간형)

구별 + 순서 + 구간

ex) 섭씨 온도:12도, 20도 ... 

 

④ ratio (비율형)

구별 + 순서 + 구간 + 비율

ex) 13시부터의 대기시간-> '10분', '20분' ....

* 많이 헷갈리는 부분이 interval과 ratio의 차이인데
구분 기준은 '0의 값이 절대적 원점(0)을 뜻하는가?'이다.

예를 들어 interval의 예시인 섭씨 온도에서 0도가 '온도가 아예없다'(절대적 원점)를 의미하는가?
아니다. -> 구간형
이번에는 ratito의 예시인 13시부터의 대기시간에서 0이 '대기시간이 아예없다'(절대적 원점)를 의미하는가?
맞다 -> 비율형

* 위의 이유로 구간형 자료에서 기준값을 정하고 그 차이 값으로 값을 변환한다면 비율형자료로 바꿀 수 있다.

 

데이터 유형에 따른 값 매핑은 상황에 맞춰서 진행하면 된다.

(캐글은 정제된 데이터라서 값 매핑이 크게 필요한 경우는 많지 않다. 문자열을 정수값으로 변환하는 정도?)

(데이터 유형에 따른 값 매핑은 뒤에 포스팅할 공공 데이터 프로젝트에서 자세히 다뤄보자.)

 

 2. 원핫인코딩

위의 데이터 유형 중 Categorical, 그중에서도 Nominal(명목형)데이터에 필수적인 작업이다.

각 고유값을 하나의 특성으로 인식하게 하는 것이다.

예를 들어

도시이라는 특성값이 '서울', '부산', '대구' 세가지라고 하자.

만약

'서울' : 0

'부산' : 1

'대구' : 2 라고 값을 매핑한다면

도시라는 특성의 성질은 "서울, 부산, 대구는 각각 다르다''

라는 구별의 성질만 가지지만,

모델에서는 서울<부산<대구 라는 순서가 있다고 인식한다.

???????? 순서를 인식시키면 어떻게 되는거지...  더 크다? 가중치를 부여하는건가?

 

 

정수값으로 변환하면서 생길 수 있는 순서 or 구간 or 비율의 성질을 어떻게 없애는가?

이에 대한 해답이 원핫인코딩이다.

'각 값마다 하나의 특성으로 만드는 것'

밑의 변환 과정을 보자.

 

기본 전제는

id가 1인 사람은 서울

id가 2인 사람은 부산

id가 3인 사람은 대구이다.

 

 

<원본>

id 도시
1 '서울'
2 '부산'
3 '대구'

<정수값으로 변환>

id 도시
1 0
2 1
3 2

<원핫인코딩 적용>

id 도시_서울 도시_부산 도시_대구
1 1 0 0
2 0 1 0
3 0 0 1

 

이렇게 되면 도시_서울이 1인 사람과 도시_부산이 1인 사람, 도시_대구가 1인 사람이 각각 다른 사람이라고

모델이 인식한다.

 

* 추가로 원핫인코딩에서 drop_first라는 파라미터를 True로 설정하면
첫 카테고리값은 사용하지 않고 인코딩한다.
id 도시_부산 도시_대구
1 0 0
2 1 0
3 0 1

이 표를 보면 '도시_서울'이라는 특성이 사라지고,

서울사람인 id 1이

도시_부산= 0

도시_대구= 0으로 표현됐다.

"부산 사람도 아니고, 대구사람도 아니면 서울 사람"이라는 것이다.

 

drop_first를 할 경우 특성값을 원핫인코딩하면서 생길 수 있는 다중 공선성을 방지하는 효과도 얻을 수 있다.

 


데이터 유형 및 원핫인코딩을 이해했으니

이제 왜 특성 엔지니어링을 해야하는지 알아보자.

 

1. 모델(라이브러리)의 요구사항

현재 사용되고 있는 모델 중 대다수는 정수 타입의 값을 가진 데이터셋을 학습하는 것으로 설계되어있다.

(사이킷런(sklearn)의 모델이 이에 해당한다.)

즉 문자열값의 경우 모델에서 인식하지 못하거나, 제대로된 결과를 띄우지 못하기 때문에 문자열 값을 정수값으로 변환하는 캐스팅 과정이 필요하다.

예를 들어 '도시'라는 특성에 '서울', '부산', '대구' 등의 값이 있다고 가정하자.

모델에서는 아예 이 값을 인식조차 할 수 없다.

하지만 이 값을 각각 1, 2, 3 으로 변환해서 모델에 학습시키는 것은 가능하다.

(이에 더해 '도시'의 경우 nominal 데이터이므로 원핫 인코딩또한 진행 해야 한다!)

 

2. 순서(order), 간격(interval) 등의 성질 부여

대소 관계 또는 배수 관계를 부여하고 싶을 때도 값을 변환한다.

단순히 문자열을 정수값으로 변환하는 것이 아니라 그 값에 성질을 부여한다.

예를 들어 '맛'이라는 특성에 '나쁨' '보통' '좋음'의 값이 있다고 가정하자.

이 값을 만약 4,8,12로 변환하면 컴퓨터에서는 '나쁨'이 '보통'보다 2배 나쁜 것으로 인식하게 된다.

??????????? 가중치를 주고자 일부러 값 변환을 저렇게 한게 아니라면 올바르지 않다.

순서의 성질만 부여하고 싶다면 0, 1, 2로 변환하는 것이 옳다.

 

import pandas as pd import numpy as np

데이터셋을 다루기 위해 pandas와 통계 계산을 위해 numpy 라이브러리를 각각 pd, np라는 이름으로 import했다.

 

train=pd.read_csv(r"C:\Users\USER\Desktop\프로그래밍 공부\파이썬으로 시작하는 캐글\train.csv") test=pd.read_csv(r"C:\Users\USER\Desktop\프로그래밍 공부\파이썬으로 시작하는 캐글\test.csv") gender_submission=pd.read_csv(r"C:\Users\USER\Desktop\프로그래밍 공부\파이썬으로 시작하는 캐글\gender_submission.csv")

데이터셋 3가지 로드(train, test, gender_submission)

 

pd.read_csv() 함수로 csv파일 내용을 데이터프레임으로 가져올 수 있다.

 

print(test.columns) test.head()

test.columns로 전체 특성이름을 리스트형태로 확인할 수 있다.

test.head()로 test 데이터프레임의 상위 5개 레코드를 데이터프레임으로 확인 할 수 있다.

 

-> 전체적인 데이터 개요가 어떻게 되는지 눈으로 확인 할 수 있다.

 

data=pd.concat([train, test], sort=False) data.isnull().sum()

train set과 test셋을 pd.concat() 함수로 합쳤다.

sort 파라미터는 False로 합친 후 정렬은 하지 않았다. (정렬하게 될 경우 알파벳 순서대로 정렬됨)

 

합쳐진 data라는 데이터프레임에 isnull().sum()을 적용해서 각 특성마다 결측치의 개수를 확인했다.

여기서 Survived는 생존여부로, 예측해야하는 값이다. 결측치가 있는 이유는 생존 여부를 예측해야하는 test set과 생존여부를 알고 있는 train set이 합쳐졌기 때문이다.

따라서 survived의 결측치는 특성 엔지니어링할 필요가 없다. (최종 예측값으로 채워넣어야하는 것)

 

남은 특성중 결측치가 있는 특성은 Age, Fare, Cabin, Embarked이다.

 

여기서 결측치를 처리하는 방법에 대해서 공부해보자.


결측치를 처리하는 방법은 무궁무진하다.

대표적인 방법은 평균값, 최빈값, 중앙값 등이 있으며 상황에 맞춰서 유동적으로 결측치를 처리한다.

예를 들어 전체 레코드가 1000개인데 결측치가 900개인 특성이 있다면 결측치를 임의로 처리하는 것보다는 없애는 것이 올바를 것이다. (임의로 결측치를 처리하게 될 경우 실제 데이터를 왜곡시킬 가능성이 크기 때문이다 "데이터 조작")

이 책에서 쓴 방법은 총 4가지이다.

 

1. 'Embarked' : 최빈값 대체

2. 'Fare' : 평균값 대체

3. 'Age' : 평균, 표준편차를 기반으로 랜덤한 값으로 대체

4.'Cabin' : 특성 삭제

 

여기서 생기는 질문이 있다.

 

왜 저 방법을 선택했는가?

이것도 앞의 포스트에서 말한 것과 마찬가지로 분석가 판단이다.

 

그렇다면 그 판단의 근거는?

이게 메인이다. 뭘보고 그렇게 판단했는가?

힌트가 있다면 세번째 방법인 평균, 표준편차를 기반으로 랜덤한 값으로 대체했다는 것이다.

 

평균, 표준편차로 랜덤하게 값을 뽑는다는 것은 분포를 보고 판단했다는 것이다.

그럼 분포를 어떻게 알았을까... 데이터를 하나하나 손으로 그래프에 점찍어서?

물론 그럴 수도 있지만 그러기엔 시대가 많이 흘렀다.

 

pandas_profiling 라이브러리를 사용하면 데이터 분포를 시각자료로 확인할 수 있을 뿐만 아니라 결측치 개수, 상관성 등등 여러 정보를 한눈에 확인할 수 있다.

import pandas_profiling data[['Age', 'Cabin','Fare','Embarked']].profile_report()

data 데이터프레임중 궁금한 특성 4가지에 대해서만 profiling했다.

 

이제 각 profile 결과를 보면서 결측치 대체 방법을 결정해보자.

 

 

1. 'Embarked' : 승선한 항구      :     "최빈값으로 결측치 대체"

데이터를 확인해보면 S,C,Q의 3가지의 항구가 있고 결측치는 3개이다. (missing)

방법을 생각해보자.

여기서 평균값은 사용할 수 없다. S,C,Q의 평균값은 무엇??

Nan이라는 새로운 항구를 만드는 것 (경영과학에서의 더미와 같은 맥락)

또는 최빈값으로 결측치 대체

또는 특성 삭제

 

결측치 2개 때문에 특성을 삭제하는 것은 빈대잡자고 초가삼간 다태우는 것이나 다름없고 평균값은 사용할 수 없다.

그럼 Nan값 채우기 또는 최빈값으로 채우기인데,

nan이라는 생판 다른 항구를 만드는 것보다는 최빈값으로 채우는게 현실을 덜 왜곡시킨다고 볼 수 있다.

 

=> 최빈값 대체

 

data['Embarked'].fillna('S', inplace=True) # 결손값을 최빈값인 'S'로 채움

 

 

2. 'Fare'' : 요금      :     "평균값 대체"

이번 특성은 요금이고 결측치는 1개이다.

사실 결측치가 너무 적어서 엄청난 이상치 ex) 10000 정도를 넣지 않는다면 큰 영향은 없을 것으로 판단된다.

하지만 이 영향을 가장 적게 주려면? 평균값 또는 최빈값이 가장 나을 것이다.

 

=> 평균값 대체 (사실 내가 보기에는 512.3292라는 최대값과 다른 값들과의 차이가 커서 최빈값이 더 나을 것 같긴하다.)

 

data['Fare'].fillna(np.mean(data['Fare']), inplace=True)

fillna() : 결측치 행을 채움

np.mean() : 평균값 계산

inplace=True : 실제 데이터셋을 수정

 

3. 'Age' : 나이      :     "평균, 표준편차를 기반으로 랜덤한 값으로 대체"

위에서 언급했던 평균, 표준편차로 결측치를 채운 Age이다.

분포를 보면 살짝 이해가 될 것이다.

"현실 세계의 대부분의 현상은 정규분포를 따른다"는 말이 있다.

(나는 이 말이 지나친 일반화라고 느꼈었는데 이제 슬슬 이해가 된다.)

데이터의 분포를 보면 정규분포와 유사하다.

이 때문에 평균, 표준편차를 이용하여 랜덤하게 값을 채운 것이다.

 

age_avg=data['Age'].mean() age_std=data['Age'].std() data['Age'].fillna(np.random.randint(age_avg-age_std, age_avg+age_avg), inplace=True) # 평균값-표준편차 ~ 평균값 + 표준편차 사이의 랜덤한 정수 값

randint(a,b): a와 b사이의 랜덤한 정수값

 

 

4. 'Cabin' : 객실 번호      :     "특성 삭제"

Cabin은 결측치를 대체하지 않고, 특성 자체를 삭제했다.

위에서 언급했듯 값의 대부분이 결측치일 경우 임의로 값을 대체하는 것보다는 특성 삭제가 올바르다.

좀더 확실하게 판단하기 위해 자세히 살펴 보자.

train['Cabin'].unique()를 통해 고유값을 리스트로 확인할 수 있다.

 

우선 총 레코드 수가 891개인데 687개가 결측값이다. 2/3 정도가 결측값인 것이다.

이 값들을 고유값들 중 하나의 값을 랜덤으로 뽑아서 대체할 수 있겠지만 단순히 생각해도 현실을 너무 많이 왜곡하게 된다.

예를 들어 3층 객실인 사람을 1층에 있었다고  훈련데이터를 잘못만든다면 예측값이 달라질 수 있다.

"3층이라 갑판과 가까워서 생존했다"는 유의미한 현상을 왜곡시킬 수 있다는 것이다.

따라서 특성을 삭제하는 것이 올바르다.

 

=>  특성 삭제

 

 


 

이번 포스트에서는 특성 엔지니어링과 결측치 처리에 대해 알아보았다.

중점:

1. 데이터 유형에 따른 값변환 (원핫인코딩 등)

2. 데이터 프로파일링을 통한 결측치 처리

 

다음 포스트에서는 마지막으로 모델링에 대해 알아보자!

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.