www.kaggle.com/ash316/eda-to-prediction-dietanic
(※ 오늘은 이 캐글러의 공유 코드를 활용해 공부하는 내용입니다.)
1장. EDA
1) feature 분석
2) 여러 feature들간의 관계, 경향 찾기
2장. Feature Engineering and Data Cleaning
1) 새로운 Feature 추가
2) 반복되는 feature 제거
3) 모델링에 적합한 형태로 feature 변환
3장. Predictive Modeling
1) 기본적인 알고리즘 실행
2) CrossValidation(교차 검증)
3) Ensembling(앙상블 기법)
4) 중요한 특성 추출(Important Feature Extraction)
오늘도 지난번에 이어서 EDA를 진행한다..!
1장. EDA
1. Feature 분석
3-1) Age 특성 결측치 대체
먼저 data에 'Initial'이라는 새로운 칼럼을 추가할 것이다.
data['Initial'] = 0
for i in data:
data['Initial'] = data.Name.str.extract('([A-Za-z]+)\.')
data['Initial'] = 0
-> data에 'Initial'이라는 새로운 칼럼을 추가하고 그 값은 전부 0으로 입력한다.
for i in data:
-> 반복문이다. data에 해당하는 행의 수만큼 반복된다.
data['Initial'] = data.Name.str.extract('([A-Za-z]+)\.')
->data의 'Initial'칼럼을 새로운 값으로 채워넣을 것이다. (data['Initial'] = )
Name칼럼으로 채워넣을 것인데 Name칼럼의 str형식만 추출해서 채워넣어라. (data['Initial'] = data.Name.str.extract())
'([A-Za-z]+)\.' 이 말은 A에서 Z로 끝나거나 소문자 a에서 z로 끝나는 모든 str형식을 찾아서 추출하는 것이고,
.(dot) 앞에 붙은 str형식의 모든 문자를 따로 추출하라는 뜻이다.
즉, 사람 이름 앞에 붙는 호칭을 Initial 칼럼에 새로 저장하는 것이다.
----------> Name 특성을 분류하는 방법을 배웠다. 다음번에도 적용할 수 있을지는 모르겠다...
pd.crosstab(data.Initial, data.Sex).T.style.background_gradient(cmap = 'summer_r')
pd.crosstab(data.Initial, data.Sex).T.style.background_gradient(cmap = 'summer_r')
-> 이니셜과 성별 특성으로 교차표를 그려라. (pd.crosstab(data.Initial, data.Sex))
.T : 전치(Transpose)를 의미한다. 저걸 지우면 세로로 긴 교차표가 그려진다. (사실, .T 옵션을 사용해도 편하긴하지만 애초에 x, y를 바꿔적으면 된다.)
'summer_r'테마 적용 .style.background_gradient(cmap = 'summer_r')
결과로 나온 단어 중 Mlle, Mme는 프랑스어로 마드모아젤과 마담이라고 읽고 Mlle는 Miss, Mme는 Mrs를 의미한다.
이처럼 비슷한 의미의 단어들은 의미에 맞게 합쳐주도록 하자.
data['Initial'].replace(['Mlle', 'Mme', 'Ms', 'Dr', 'Major', 'Lady', 'Countess', 'Jonkheer', 'Col',
'Rev', 'Capt', 'Sir', 'Don'], ['Miss', 'Mrs', 'Miss', 'Mr', 'Mr', 'Mrs',
'Mrs', 'Other', 'Other', 'Other', 'Mr', 'Mr',
'Mr'], inplace = True)
data['Initial'].replace()
-> Initial칼럼의 값들을 바꿔주자.
Mlle, Ms는 Miss로, Mme, Lady, Countess는 Mrs,로, Dr, Major, Capt, Sir, Don은 Mr로, Jonkheer, Col, Rev는 Other로 변경!
(캐글러는 Mme를 Miss로 분류했는데 나는 기혼여성인 Mrs로 분류했다.)
data.groupby('Initial')['Age'].mean()
Inital별로 평균 나이를 구했다.
Master는 왜 평균연령이 4.57세인가.. 궁금하다.
Master 행들을 조회해보니 0.42세에서 11살까지 연령의 남자아이들로 아마도 mater는 도련님 정도의 의미로 쓰인 듯하다.
pd.crosstab(data.Initial, data.Sex).T.style.background_gradient(cmap = 'summer_r')
그리고 교차표를 다시 한 번 확인했다.
Mr로 바꾼 사람 중에 female이 들어가있었다.
위의 교차표로 돌아가서 확인해보니, Dr 중에 여성이 한 분 있었다.
이를 바로 잡아주기 위해서 그 분을 찾아 헤매기 시작했다.
data.loc[(data['Initial'] == 'Mr') & (data['Sex'] == 'female'), :]
앨리스 선생님은 49세의 여성으로 생존하셨고 1등석을 이용하셨으며 가족과 탑승하지는 않으셨다.
아무래도 Mrs일 확률이 높긴 하지만, 과연 이 분을 Mrs로 넣어야할지 Miss로 넣어야할지 고민하던 와중 이름을 검색해보았다.
www.encyclopedia-titanica.org/titanic-survivor/alice-leader.html
데이터를 통해 유추를 해야 마땅하지만.. 여튼 Mrs가 맞았다.
알맞게 데이터를 수정해주었다.
data.loc[(data['Initial'] == 'Mr') & (data['Sex'] == 'female'), 'Initial'] = 'Mrs'
그리고 잘 반영되었는지 확인
data.loc[data['PassengerId'] == 797, :]
다시 이니셜별 나이 평균을 조회했다.
data.groupby('Initial')['Age'].mean()
그런데, Pclass와 Initial을 둘 다 고려해서 연령대 분포를 보고싶었다.
등석별로 나이 편차가 있지 않을까 싶었기 때문이다.
data.groupby(['Initial', 'Pclass'])['Age'].describe()
Miss나 Mr에서 평균의 차이가 있는 것으로 보였다. Mrs는 2, 3등석에서 별 차이가 없었지만, 1등석과는 분명한 차이가 있어보였다.
그래서 고민 끝에 이 15가지 그룹 중 Mater를 제외하고 나머지에는 Initial에 Pclass를 모두 반영해서 평균치를 입력하기로 했다..
data.loc[(data.Age.isnull()) & (data.Initial == 'Master'), 'Age'] = 5
data.loc[(data.Age.isnull()) & (data.Initial == 'Miss') & (data.Pclass == 1), 'Age'] = 30
data.loc[(data.Age.isnull()) & (data.Initial == 'Miss') & (data.Pclass == 2), 'Age'] = 23
data.loc[(data.Age.isnull()) & (data.Initial == 'Miss') & (data.Pclass == 3), 'Age'] = 16
data.loc[(data.Age.isnull()) & (data.Initial == 'Mr') & (data.Pclass == 1), 'Age'] = 42
data.loc[(data.Age.isnull()) & (data.Initial == 'Mr') & (data.Pclass == 2), 'Age'] = 33
data.loc[(data.Age.isnull()) & (data.Initial == 'Mr') & (data.Pclass == 3), 'Age'] = 29
data.loc[(data.Age.isnull()) & (data.Initial == 'Mrs') & (data.Pclass == 1), 'Age'] = 41
data.loc[(data.Age.isnull()) & (data.Initial == 'Mrs') & (data.Pclass == 2), 'Age'] = 34
data.loc[(data.Age.isnull()) & (data.Initial == 'Mrs') & (data.Pclass == 3), 'Age'] = 34
data.loc[(data.Age.isnull()) & (data.Initial == 'Other') & (data.Pclass == 1), 'Age'] = 51
data.loc[(data.Age.isnull()) & (data.Initial == 'Other') & (data.Pclass == 2), 'Age'] = 43
data.Age.isnull().sum()
나이 특성의 결측치 처리는 완료된 것 같다.
f, ax = plt.subplots(1, 2, figsize = (18, 8))
data[data['Survived'] == 0].Age.plot.hist(ax = ax[0], bins = 20, edgecolor = 'black', color = 'red')
ax[0].set_title('Survived = 0')
x1 = list(range(0, 85, 5))
ax[0].set_xticks(x1)
data[data['Survived'] == 1].Age.plot.hist(ax = ax[1], bins = 20, edgecolor = 'black', color = 'green')
ax[1].set_title('Survived = 1')
x2 = list(range(0, 85, 5))
ax[1].set_xticks(x2)
plt.show()
f, ax = plt.subplots(1, 2, figsize = (18, 8))
-> 두 개의 그래프를 그릴 평면을 18*8 사이즈로 지정
data[data['Survived'] == 0].Age.plot.hist(ax = ax[0], bins = 20, edgecolor = 'black', color = 'red')
-> 사망자의 Age 특성을 히스토그램으로 그려라. ( data[data['Survived'] == 0].Age.plot.hist() )
히스토그램 옵션으로
첫번째 그래프에 그리고 ( ax = ax[0] )
히스토그램 범주는 20개로 설정 ( bins = 20)
모서리 색은 검정 (edgecolor = 'black')
막대 색은 빨강 (color = 'red')
x1 = list(range(0, 85, 5))
-> 0부터 85까지 5씩 건너뛴 숫자를 x1이라는 이름의 list에 저장
ax[0].set_xticks(x1)
-> 그 x1 리스트를 히스토그램 x축에 적용
data[data['Survived'] == 1].Age.plot.hist(ax = ax[1], bins =20, edgecolor = 'black', color = 'red')
-> 이번엔 생존자의 나이 특성을 히스토그램으로 그리기
아래는 위와 동일
히스토그램 관찰 결과,
1) 5세 이하의 생존률이 매우 높았음 (여자와 아이를 먼저 구한 덕분)
2) 캐글러의 데이터와는 다르게 25에서 30세의 사망이 가장 많았다. (캐글러는 30-35세가 가장 많이 사망)
3) 최고령자는 살았다.
캐글러와 결과 히스토그램 모양이 좀 많이 달랐다.
Pclass까지 분류해서 나이대의 평균을 적용한 것이 과연 더 좋은 결과를 가져다줄지 조금 걱정되기 시작했다.
캐글러가 더 전문성 있는 분석을 진행했겠지만 그래도 그의 코딩과 약간은 다른 면이 있어도 좋지 않을까 싶어 이대로 분석을 진행하기로 마음 먹었다.
이니셜 별 등급에 따른 생존률을 비교해 보자.
sns.factorplot('Pclass', 'Survived', col = 'Initial', data = data)
plt.show()
sns.factorplot('Pclass', 'Survived', col = 'Initial', data = data)
-> x축을 Pclass로, 생존률에 대한 factorplot을 그려라. ( sns.factorplot('Pclass', 'Survived', data = data) )
근데 Initial별로 그래프를 나누어 보여라. ( col = 'Initial' )
------------------------> factorplot을 특성별로 그래프를 그리려면 옵션으로 col = 'feature'로 쓰는듯 했다.
이 플롯으로 확인할 수 있는 인사이트는 캐글러와 크게 다르지 않았다.
4) 승선장(Embarked) 특성 : 범주형 변수
승선장 변수는 어떤 인사이트를 가져다줄까
pd.crosstab([data.Embarked, data.Pclass], [data.Sex, data.Survived],
margins = True).style.background_gradient(cmap = 'summer_r')
pd.crosstab([data.Embarked, data.Pclass], [data.Sex, data.Survived], margins = True).style.background_gradient(cmap = 'summer_r')
-> 행은 Embarked, Pclass로, 열은 Sex, Survived로 구성된 교차표를 그려라
(pd.crosstab([data.Embarked, data.Pclass], [data.Sex, data.Survived])
총합을 표시해라. (margins = True)
테마는 summer_r로 지정 (.style.background_gradient(cmap = 'summer_r')
S가 의미하는 Southampton 승선장에서 가장 많은 승객이 탑승했다.
그래서인지 사망자의 비율도 가장 많은 편이었다.
sns.factorplot('Embarked', 'Survived', data = data)
fig = plt.gcf()
fig.set_size_inches(5, 3)
plt.show()
sns.factorplot('Embarked', 'Survived', data = data)
-> 승선장별 생존률을 나타내는 factorplot을 그려라
fig = plt.gcf()
-> 그림 객체를 구현한다
fig.set_size_inches(5, 3)
-> 그 객체의 사이즈는 5*3 인치로 구성
만약에 이 객체 옵션을 굳이 설정하지 않으면 알아서 그래프 크기를 설정한다.
sns.factorplot('Embarked', 'Survived', data = data)
plt.gcf() 함수를 굳이 사용하지 않더라도 알아서 그래프를 그려주기때문에 굳이 저 함수를 적용할 필요는 없다.
하지만 그래프 객체를 지정해서 자신이 원하는 형태로 그래프를 그릴 수 있도록 해주니 참고하자.
plt.show()
-> 그래프를 그려라.
C 승선장에서 55%로 가장 높은 생존률을 보였고, S승선장은 35%가 안되는 낮은 생존률을 보였다.
f, ax = plt.subplots(2, 2, figsize = (20, 15))
sns.countplot('Embarked', data = data, ax = ax[0, 0])
ax[0, 0].set_title('No. Of Passengers Boarded')
sns.countplot('Embarked', hue = 'Sex', data = data, ax = ax[0, 1])
ax[0, 1].set_title('Male-Female Split for Embarked')
sns.countplot('Embarked', hue = 'Survived', data = data, ax = ax[1, 0])
ax[1, 0].set_title('Embarked vs Survived')
sns.countplot('Embarked', hue = 'Pclass', data = data, ax = ax[1, 1])
ax[1, 1].set_title('Embarked vs Pclass')
plt.show()
f, ax = plt.subplots(2, 2, figsize = (20, 15))
-> 4개의 그래프를 2행 2열로 보여줄 평면을 20*15 사이즈로 구성
첫번째 그래프
sns.countplot('Embarked', data = data, ax = ax[0, 0])
-> 첫번째 막대그래프는 승선장 특성에 대한 걸로
두번째 그래프
sns.countplot('Embarked', hue = 'Sex', data = data, ax = ax[0, 1])
-> 두번째 막대그래프는 승선장에 대해서 성별로 그룹화해서
세번째 그래프
sns.countplot('Embarked', hue = 'Survived', data = data, ax = ax[1, 0])
-> 세번째는 승선장 그래프를 생존여부별로
네번째 그래프
sns.countplot('Embarked', hue = 'Pclass', data = data, ax = ax[1, 1])
-> 네번째는 승선장 그래프를 Pclass 별로 그려라.
그래프 관찰 결과,
1) 대부분의 탑승객들은 S 승선장에서 탑승했으며, 3등석 승객 비율이 가장 높았다.
2) C승선장 탑승객은 생존률이 가장 높았다. 아마도 1, 2등석 승객이 C 승선장에서 많이 탑승했기 때문에 발생한 비율적 혜택으로 보인다.
3) S승선장은 가장 많은 1등석 승객이 탑승한 승강장이다. 1등석 승객의 생존률이 높지만 S에서 승선했다면 생존률이 매우 낮을 것으로 보인다.
4) Q승선장은 95%의 승객이 3등석 이용자다.
sns.factorplot('Pclass', 'Survived', hue = 'Sex', col = 'Embarked', data = data)
plt.show()
sns.factorplot('Pclass', 'Survived', hue = 'Sex', col = 'Embarked', data = data)
-> x축을 Pclass로 해서 생존률에 대한 factorplot을 그려라. ( sns.factorplot('Pclass', 'Survived')
성별로 그룹화하고 (hue = 'Sex')
승선장별로 그래프를 그려라. (col = 'Embarked')
그래프 관찰 결과,
1) Q승선장과 C승선장을 이용한 1, 2등석 여성의 생존률은 거의 100%에 가까웠다. S승선장 여성의 1등석 여성도 마찬가지
2) S승선장의 3등석 승객은 성별에 관계없이 생존률이 매우 낮아졌다.
3) Q승선장 남자 승객의 생존률은 등석에 상관없이 거의 0에 가까웠다.
앞선 포스팅에서 결측치를 확인했을 때, Embarked 특성에서도 결측치 2개가 있었다.
이를 채워주기 위해 캐글러는 최빈값인 S를 입력하여 대체했다.
이전에 나도 승선장이나 나이대를 비교해 같은 선택을 했는데 그대로 진행한 것이 조금 아쉬웠다.
그래서 티켓을 비교하면 승선장 위치를 알 수 있지 않을까 싶어서 확인해보았다. (참고로, Ticket 특성은 결측이 없었다.)
그런데 해당 티켓을 가진 인원은 그 두 승객 뿐이었고 Cabin도 같은 칸을 쓴 사람이 그 두 승객 뿐이었다.
별다른 아이디어가 더 없어서 그냥 S로 결측을 대체했다.
data['Embarked'].fillna('S', inplace = True)
data.Embarked.isnull().sum()
5) 형제, 자매, 배우자 수 총합(SibSp) : 이산형 변수
SibSp는 Sibling과 Spouse의 줄임말이었다.
pd.crosstab(data.SibSp, data.Survived).style.background_gradient(cmap = 'summer_r')
f, ax = plt.subplots(1, 2, figsize = (20, 8))
sns.barplot('SibSp', 'Survived', data = data, ax = ax[0])
ax[0].set_title('SibSp vs Survived')
sns.factorplot('SibSp', 'Survived', data = data, ax = ax[1])
ax[1].set_title('SibSp vs Survived')
plt.show()
sns.barplot('SibSp', 'Survived', data = data, ax = ax[0])
-> seaborn 패키지로 막대그래프를 그린다. x축은 SibSp, y축은 생존률
------------------------------------>seaborn 패키지로 그래프를 그리는 이유를 알아보았다.
1) 색감이 좀 더 예쁘다
2) xtick, ytick, legends를 알아서 결정해주고, hue 옵션이 간편
3) seaborn에서만 제공되는 통계 플롯이 있다.
f, ax = plt.subplots(1, 2, figsize = (20, 8))
sns.barplot('SibSp', 'Survived', data = data, ax = ax[0])
ax[0].set_title('SibSp vs Survived')
sns.factorplot('SibSp', 'Survived', data = data, ax = ax[1])
ax[1].set_title('SibSp vs Survived')
plt.close(2)
plt.show()
갑자기 어째서 이런 불상사가 발생하는 것인지 모르겠는데..
저 plt.close(2) 쓰면 아래처럼 나오고
plt.close(2)를 지우면 결과창에 factorplot이 나오긴 하는데 다음 열로 밀려난채 출력된다.
형제, 자매, 배우자의 수가 1-2명인 경우 생존률이 약 50%에 가까워서 가장 유리한 것으로 보였다.
혼자인 경우 생존률은 34.5%로 낮은 편이었다.
형제, 자매, 배우자의 수가 5, 8인 경우 생존률이 극히 낮았다.
왜 그 수가 많을수록 불리했는지 생각했을 때, Pclass의 영향이 있는 것으로 보여 SibSp, Pclass 교차표를 구성했다.
pd.crosstab(data.SibSp, data.Pclass).style.background_gradient(cmap = 'summer_r')
SibSp가 4명 이상인 사람들은 전부 3등석 승객이었다.
6) 부모, 자식의 수 총합(Parch) : 이산형 변수
Parch 변수는 승객의 함께 탑승한 부모와 자식의 총합을 의미한다.
pd.crosstab(data.Parch, data.Pclass).style.background_gradient(cmap = 'summer_r')
f, ax = plt.subplots(1, 2, figsize = (20, 8))
sns.barplot('Parch', 'Survived', data = data, ax = ax[0])
ax[0].set_title('Parch vs Survived')
sns.factorplot('Parch', 'Survived', data = data, ax = ax[1])
ax[1].set_title('Parch vs Survived')
plt.show()
SibSp 특성의 그래프를 그렸을 때랑 같은 현상이 발생했다..
왜 자꾸 factorplot이 레이아웃에 출력되지 않고 넘어가는지 모르겠다...
캐글러처럼 plt.close(2) 옵션을 써도 SibSp랑 똑같이 아예 factorplot이 사라져버린다...
Parch 특성 관찰 결과,
1) 부모 자식의 총합이 4, 6인 승객의 생존률은 0이었다.
2) 부모 자식의 총합이 1-3인 승객의 생존률이 50%를 넘었다.
3) 부모 자식의 총합이 0인 승객의 생존률은 40%에 못 미쳤다.
pd.crosstab(data.Parch, data.Pclass).style.background_gradient(cmap = 'summer_r')
Parch가 4-6인 승객 중 단 한 명의 승객만이 1등석이었고 나머지는 전부 3등석 승객이었다.
SibSp처럼 Parch가 많은 승객이 생존률이 낮은 이유도 Pclass가 높지 않기 때문인 것으로 보인다.
7) 요금(Fare) : 연속형 변수
요금(Fare)는 Pclass와 연관이 깊은 특성이다.
1등석을 이용했을수록 요금이 더 많이 나왔을 것이므로, 요금에 따라 생존률도 차이가 있을 것으로 예상된다.
data.Fare.describe()
이번에는 연속형 변수의 분포를 보는 seaborn 패키지의 distplot을 그리는 법을 보자.
f, ax = plt.subplots(1, 3, figsize = (20, 8))
sns.distplot(data[data['Pclass'] == 1].Fare, ax = ax[0])
ax[0].set_title('Fares in Pclass1')
sns.distplot(data[data['Pclass'] == 2].Fare, ax = ax[1])
ax[1].set_title('Fares in Pclass2')
sns.distplot(data[data['Pclass'] == 3].Fare, ax = ax[2])
ax[2].set_title('Fares in Pclass3')
plt.show()
sns.distplot(data[data['Pclass'] == 1].Fare, ax = ax[0])
-> seaborn 패키지의 distplot을 그려라 (sns.distplot())
Pclass가 1등급인 승객들의 Fare 특성에 대한 distplot을 그려라. ( sns.distplot(data[data['Pclass'] == 1].'Fare') )
모든 특성에 대한 대략적인 분석이 끝났다.
이제 특성들끼리의 상관관계를 확인할 차례다!
8) 전체 특성의 상관관계 알아보기
히트맵은 어떻게 그릴까?
sns.heatmap(data.corr(), annot = True, cmap = 'RdYlGn', linewidths = 0.2)
fig = plt.gcf()
fig.set_size_inches(10, 8)
plt.show()
sns.heatmap(data.corr(), annot = True, cmap = 'RdYlGn', linewidths = 0.2)
-> seaborn 패키지의 heatmap 플롯을 그려라. (sns.heatmap())
히트맵 플롯 위에 상관계수를 적어라. (annot = True 옵션) 만약에 annot = False라면 색깔만 나온다..
'RdYlGn' 테마로 색깔을 입혀라. (cmap = 'RdYlGn') (디폴트값은 보라보라한 컬러로 색칠한다.)
각각의 간격을 0.2만큼 띄워라 (linewidths = 0.2)
히트맵 해석하기
이 히트맵으로 상관계수를 알 수 있고, 만약 연속형 변수끼리 높은 상관성을 보인다면 해당 변수들은 다중공선성이 있을 수도 있다고 판단하여 삭제하거나 변환해줘야 한다.
다중공선성을 파악하기 위한 일환으로 상관계수를 살펴보자.
Object이거나 String형식인 피쳐들을 제외하고, 7개의 Feature가 남아서 총 49개의 사각형이 생겼다.
하지만 이 중에서도 중복되는 절반을 제거하고, 상관계수는 1인 대각선을 제외하면 21가지가 남는다.
그 21가지 중에서도 순서형 범주 Pclass와 범주형 변수인 Survived, PassengerId를 제외하면 의미있는 비교는 Age, SibSp, Parch, Fare사이에서 이루어진다.
이를 고려했을 때, 가장 높은 상관계수는 SibSp와 Parch의 상관계수 0.41이다.
나머지는 Pclass와 Fare, Pclass와 Age에서 상관계수가 약간 있는데, 인과관계는 분명하지만 Pclass는 순서형 변수이기때문에 다중공선성을 의심하여 삭제할 필요가 없다.
결론은 모든 피쳐 간의 상관성이 그리 크지 않다고 생각되므로 모든 특성을 활용하기로 한다.
이렇게 EDA를 마무리하자!
이번 포스팅까지 모든 Feature에 대해 EDA를 진행했다.
다음 포스팅에서는 Part2 Feature Engineering과 Data Cleaning을 진행하자.