캐글 필사

[캐글 필사(1)-1] 타이타닉 EDA & Feature Engineering

조조링 2020. 10. 11. 02:42
728x90
반응형
더보기
  • 다음 코드는 www.kaggle.com/ash316/eda-to-prediction-dietanic을 필사한 내용입니다.
  • - 다음은 코드 설명
  • >> 다음은 분석 결과 해석
  • 중간에 링크걸려 있는 것은 글 쓰면서 참고한 사이트 입니다
  • 코드 및 결과해석 부분은 최대한 정확하게 쓰려고 했지만 틀린 부분이 있을 수도 있습니다. 

 


< 준비 단계 >

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
plt.style.use('fivethirtyeight')
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline

from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"

- 분석을 위해 필요한 패키지 설치 :: pandas / numpy 기본 & matplotlib / seaborn 시각화 

- print(plt.style.available) 하면 변경 가능한 테마 목록들이 나온다 > 폰트까지 몽땅 바꿀 수 있다!

- %matplotlib inline :: notebook을 실행한 브라우저에서 바로 그림을 볼 수 있게 해주는 것 출처: https://korbillgates.tistory.com/85
- 한 셀에 print(a) print(b)를 같이 쓰면 하나만 출력된다 > 이를 방지하기 위해 from~ 'all' 부분을 써준다


< 데이터 불러오기 >

 

data = pd.read_csv('C:/Users/82108/Desktop/kaggle/train.csv')
data.head()

타이타닉 데이터 


< 결측치 확인 >

data.isnull().sum()   

결측치 개수

 - isnull() 하면 결측치는 T , 결측치가 아닌 것은 F로 출력된다. 

 - isnull().sum()을 해줌으로써 T는 1로, F는 0으로 취급되어서 결측치의 개수를 확인할 수 있다. 

 - Age 177개  / Cabin 687개 / Embarked 2개 결측치 존재

 


< 종속변수에 대해 알아보자 >

data['Survived'].value_counts()

- value_counts() 도수분포표 확인 가능 >> R에서 table 같은 역할  table(data$Survived) 

>> 1이 생존자 / 0이 생존하지 못한 사람 

>> 생존자가 더 적음을 알 수 있다.

 

f, ax = plt.subplots(1,2,figsize = (15,8))   # ggplot 느낌 >> 그래프 공간 만들기  / 1행 2열 / 크기는 (18,8) (가로,세로)
data['Survived'].value_counts().plot.pie(explode = [0,0.1], autopct = '%1.1f%%', ax = ax[0] , shadow = True)
ax[0].set_title('Survived')  # 제목 ## ggtitle
ax[0].set_ylabel('') # y축이름  #ylab
sns.countplot('Survived', data = data, ax = ax[1])
ax[1].set_title('Survived')
plt.show()

- plt.subplots 로 그래프 공간과 사이즈 확보 >> 1행2열, 사이즈는 가로 세로 각각 15,8로 확보

- value_counts 한 결과를 plot.pie를 통해 파이차트로 시각화 > 파이차트는 천제를 이루는 각 구성요소가 전체 중 얼마만큼의 ㅣ중으로 되어 있는지 파악하기 좋다. 

- plot.pie 옵션 :: explode --> 파이조각이 돌출되는 크기 -- > [0,0.1] 은 2번째 파이 조각만 0.1만큼 돌출되게 함 

                            :: autopct --> 파이 조각의 전체 대비 백분율을 소수점 1자리 까지 %로 표기하겠다. 

                            :: shadow --> 파이 차트의 그림자 효과 유무 

                            :: labels --> 파이 조각의 라벨 

- ax[n]을 통해 어느 위치에 그릴지 정해준다. 

- set_title 은 제목 

- set_ylabel 은 y축 이름 

- sns.countplot :: 막대그래프 / 알고자 하는 컬럼을 X축에 적으면 된다. 

 

>> 앞으로 Survived 값이 0,1에 따라 다른 변수들의 분포가 어떻게 분포되어 있는지 확인할 것이다.

         즉, 0과 1로 예측해주는 중요한 변수를 찾을 것이다. 

 


< 독립변수들에 대해 알아보자 >

 

<  1. Sex :: 범주형 변수  >

data.groupby(['Sex','Survived'])['Survived'].count()

f, ax = plt.subplots(1,2,figsize = (18,8))
data[['Sex','Survived']].groupby(['Sex']).mean().plot.bar(ax = ax[0])
ax[0].set_title('Survived vs Sex')
sns.countplot('Sex', hue = 'Survived', data = data , ax = ax[1])
ax[1].set_title('Sex:Survived vs Dead')
plt.show()

- data를 Sex와 Survived로 그룹화를 한 후, Survived의 빈도수를 가져옴 

- ax[0] :: data의 Sex와 Survived만 뽑아 낸 후, Sex로 그룹화를 시키고 평균을 구함 > plot.bar로 시각화

         :: 남성 안에서 생존자의 비율 / 여성 안에서 생존자의 비율을 확인하기 위해서 

         :: 즉, P(생존 | 남성) , P(생존 | 여성) 을 의미한다고 생각한다. 

- ax[1] :: sns.countplot 의 X축은 Sex 이고 / hue = 'Survived' --> Survived를 기준으로 데이터를 쪼갠다.

         :: 남성안에서도 Survived가 0,1 로 쪼개고 여성안에서도 쪼개진다. 

>> 여성은 70%이상 , 남성은 20% 이하의 생존율을 가진다. >> 여성을 우선 구조했음을 짐작할 수 있다. 

>> 성별(Sex)이 Survived를 분류하는데 중요한 영향을 주는 변수라고 생각 

 


 

< 2. Pclass :: 순서형 변수 >

pd.crosstab(data.Pclass, data.Survived, margins = True).style.background_gradient(cmap = 'summer_r') 

f,ax = plt.subplots(1,2, figsize = (18,8))
data['Pclass'].value_counts().plot.bar(color = ['#CD7F32','#FFDF00','#D3D3D3'], ax = ax[0])
ax[0].set_title('Number Of Passengers By Pclass')
ax[0].set_ylabel('Count')
sns.countplot('Pclass', hue = 'Survived', data = data , ax = ax[1])
ax[1].set_title('Pclass : Survived vs Dead')
plt.show()

- pd.crosstab(index, # values to group by in the rows

                           columns, # values to group by in the columns

                           rownames, colnames ,

                          margins, # adding row / column margins,

                          normalize # Normalizing by dividing all values by the sum of values )  # 교차표 

- margins = True로 해주면 열과 행의 All 합계를 표시해준다. 

- .style.background_gradient(cmap = ' ' ) :: 값을 그라데이션으로 표현해줌 (기능 보다는 시각능력?)

>> Pclass1~3 등급 비율을 보면 1~2가 약 200명, 3등급이 500명으로 3등급이 압도적으로 많다.

>> 하지만 생존자의 비율을 보면 1~2등급은 탑승객 대비 각각 70% , 50% 가 살아남았지만, 

               3등급의 탑승객은 20% 이하의 생존율을 가지고 있다. 

>> 등급이 높을수록 (1에 가까울수록) 구조하는데 우선순위가 부여된 것으로 짐작할 수 있다. 

 

 


 

< 3. Sex & Pclass >

pd.crosstab([data.Sex, data.Survived], data.Pclass , margins = True).style.background_gradient(cmap = 'summer_r')

sns.factorplot('Pclass','Survived', hue = 'Sex', data=data)
plt.show()

- sns.factorplot & hue옵션을 통해 3개의 차원으로 그래프를 그릴 수 있다. 

>> Pclass가 1~2인 여성의 생존율은 거의 1에 가깝지만 Pclass 3인 여성의 생존율은 50%임을 확인할 수 있다.

>> 즉, 여성의 생존율이 높지만 그 중에서도 Plcass가 높을 수록 더 높음을 알 수 있다. >> 자본주의...?

>> 남성 또한 Pclass가 떨어질 수록 생존율도 떨어진다. 

>> Pclass가 Survived를 분류하는데 중요한 변수라고 생각할 수 있다.  

 


 

 

< 4. Age :: 연속형 변수 >

data.Age.max()  # 80
data.Age.min()  # 0.42
data.Age.mean()  # 29.7

f,ax = plt.subplots(1,2, figsize = (18,8))
sns.violinplot('Pclass', "Age", hue = 'Survived', data = data , split = True, ax = ax[0])
ax[0].set_title('Pclass and Age vs SUrvived')
ax[0].set_yticks(range(0,110,10))
sns.violinplot('Sex','Age',hue = "Survived", data = data , split = True, ax = ax[1])
ax[1].set_title('Sex and Age vs Survived')
ax[1].set_yticks(range(0,110,10))
plt.show()

- sns.violinplot :: 데이터의 커널 확률 밀도를 Plotting하고 중앙의 boxplot을 그려줌으로써 각 데이터의 특성을 잘 표현해준다. 그룹 간의 비교를 시각적으로 잘 표현해 준다. 

- set_yticks :: y축 범위 

>> 아이들의 숫자는 Pclass가 3으로 갈수록 증가한다. 

>> 등급과 무관하게 아이들의 생존비율은 높은 편이다.

>> 나이가 많을수록 생존율이 낮아진다. 특히 남성이 더 낮은 편이다. 

 

 


 

<  4-1 Age 결측치 처리  >

- 앞에서 결측치 확인했을 때, Age의 결측치는 177개 존재한다고 했다.

- 결측치를 평균 나이인 29살로 채우면 어떨까??

  >> 문제점 :: 나이는 연속형 변수이다. 너무 많은 사람들이 존재하고 그에 따라 다양한 값들이 존재한다.

                  만약, 실제고 4살짜리 아이인데 평균 나이 29살로 대체하게 된다면 ? 

  >> 해결책 :: Name 변수를 살펴보면 . 앞에 Mr / Mrs 같은 수식어가 붙는다. 이러한 수식을 통해 나이대를 어느정도 짐작할 수 있다. 

  >> 내가 해야할 일 :: Name 에 어떤 수식어가 있는지를 나타내는 새로운 변수를 만들기 >> By 정규표현식

data['Initial'] = 0
for i in data:
    data['Initial'] = data.Name.str.extract('([A-Za-z]+)\.') 
    
data.head()  

Initial 변수 추가

 

pd.crosstab(data.Initial, data.Sex).T.style.background_gradient(cmap = 'summer_r')

- pd.crosstab을 통해 Initial분포 확인해보기 >> 너무 다양한 Initial을 가지고 있음 >> 어느 정도 통합해주자.

 

 

data['Initial'].replace(['Mlle','Mme','Ms','Dr','Major','Lady','Countess','Jonkheer',
                        'Col','Rev','Capt','Sir','Don'],
                       ['Miss','Miss','Miss','Mr','Mr','Mrs','Mrs','Other','Other','other',
                       'Mr','Mr','Mr'], inplace = True)
                       
pd.crosstab(data.Initial, data.Sex).T.style.background_gradient(cmap = 'summer_r')

- data의 Initial 변수의 값을 바꾸기 위해 replace함수 사용 >> replace(Alist , Blist) :: A list 값들을 B list 값들로 바꾼다. 

>> 그럼 이제 이걸 이용해서 어떻게 결측치를 채울 것인가?

>> Name은 결측치가 없었기 때문에 Initial도 결측치가 없다. 즉, 나이는 결측치여도 Initial은 채워져 있다. 

>> Initial로 그룹화 한 후, 평균을 구해서 그 평균으로 대체한다! 

 

data.groupby('Initial')['Age'].mean()

# Initial로 그룹화 해서 나온 평균을 기준으로 Nan 대체 
data.loc[(data.Age.isnull())&(data.Initial == "Mr"),'Age'] = 33
data.loc[(data.Age.isnull())&(data.Initial == "Mrs"),'Age'] = 36
data.loc[(data.Age.isnull())&(data.Initial == "Master"),'Age'] = 5
data.loc[(data.Age.isnull())&(data.Initial == "Miss"),'Age'] = 22
data.loc[(data.Age.isnull())&(data.Initial == "Other"),'Age'] = 46

data.Age.isnull().any()  # --> False :: 결측치 이제 없음!

그룹화 평균 나이

f,ax = plt.subplots(1,2, figsize = (20,10))
data[data['Survived'] == 0].Age.plot.hist(ax = ax[0], bins = 20, edgecolor = 'black', color = 'red')  # only dead age 
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], color = 'green', bins = 20 , edgecolor = 'black')
ax[1].set_title('Survived = 1')
x2 = list(range(0,85,5))
ax[1].set_xticks(x2)
plt.show()

>> 5세 이하 구간을 보면 생존자의 비율이 많음을 알 수 있다. 이를 통해 아동과 여성을 우선 생존했음을 짐작할 수 있다.

 

 


 

< 5. Embarked :: 범주형 변수 >

pd.crosstab([data.Embarked, data.Pclass],[data.Sex, data.Survived],
           margins = True).style.background_gradient(cmap = 'summer_r')

sns.factorplot('Embarked','Survived', data = data)
fig = plt.gcf()
fig.set_size_inches(5,3)
plt.show()

- Embarked에 따른 생존비율 시각화 

- plt.gcf :: Figure 객체(그림이 그려지는 캔버스), Axes객체(하나의 플롯), Axis 객체(하나의 축) 등으로 구성 plt.subplot(행,열,위치)를 통해 한 Figure내에 여러 Axes 를 표현 가능 // plt.gcf()를 통해 Figure 객체를 얻을 수 있음. plt.gca()를 통해 Axes 객체를 얻을 수 있음.

>> C port에 탑승한 사람들의 생존률이 55%로 가장 높고, S가 가장 낮다. 

 

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.subplots_adjust(wspace = 0.2 , hspace = 0.5)
plt.show()

- plt.subplots_adjust() :: 서브 플롯 간 간격을 변경할 수 있다. / wsapce , hspace 는 각각 축 너비와 높이의 비율을 의미

 

>> ax[0,0] :: S탑승객이 압도적으로 많으며 Q는 100명 이하가 탑승해있다. 

>> ax[0,1] :: 성별에 따라서도 남성 & 여성 모두 S > C > Q 순으로 많다.

>> ax[1,1] :: S 탑승객 중 Pclass3이 가장 많다

                       C 탑승객의 생존율이 좋을 것이다  > 이유 >> Pclass 1~2는 거의 생존했기 때문에 

 

sns.factorplot('Pclass','Survived', hue = 'Sex', col = 'Embarked', data = data)
plt.show()

>> Embarked의 종류와는 상관없이 Pclass1~2인 여성의 생존율은 거의 1에 가깝다.

>> Embarked S & Pclass 3 탑승객이 가장 안타깝다 > 남여 모두 생존율이 낮다 

 


 

< 5-1. Embarked 결측치 채우기 >

- 앞에서 Embarked의 결측치는 2개 밖에 없었다.

- Embarked의 분포는 S가 압도적으로 많으니 S로 대체해도 상관없을 거 같다. 

data['Embarked'].fillna('S', inplace = True)

data.Embarked.isnull().any() # False

- data['Embarked'].fillna를 통해 결측치를 추출했고 그 값들을 S로 대체한다. 

 


 

< 6. SibSp:: 이산형 변수 >

> 변수 설명 :: 함께 탑승한 형제 또는 배우자 수

> Sibling = brother , sister , stepbrother, stepsister

> Spouse = husband , wife 

pd.crosstab([data.SibSp], data.Survived).style.background_gradient(cmap = 'summer_r')

>> 함께 탑승한 형제 또는 배우자 수가 5명 이상인 탑승객 중 생존자는 없음

>> 혼자온 경우는 자기만 챙기면 되지만, 가족이 많을 수록 나보다 남을 챙기려고 하는 이유때문은 아닐까 짐작 

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('SisSp vs Survived')
plt.close(2)
plt.show()

** 오류는 아닌데 그래프 할당이 안됨 ㅠㅠ 이유 찾아보기 **

 

pd.crosstab(data.SibSp, data.Pclass).style.background_gradient(cmap = 'summer_r')

>> Pclass에 따른 SibSp분포 확인해보자

>> SibSp가 클 수록 Pclass 3을 이용하는 경향이 있다 >> 가족이 많으니 가격 부담 때문일거라고 생각 

 

 


 

< 7. Parch:: 이산형 변수 >

- 변수 설명 :: 함께 탑승한 부모 또는 자녀 수 

pd.crosstab(data.Parch, data.Pclass).style.background_gradient(cmap = 'summer_r')

>> Parch 역시 SibSp 와 같은 패턴을 띄고 있다 :: 가족 구성원이 많을수록 생존율이 낮았던 Pclass3에 탑승했다. 

 


 

< 8. Fare:: 연속형 변수 >

 

data.Fare.max()  # 512
data.Fare.min()  # 0
data.Fare.mean()  # 32

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 Pclass 1')
sns.distplot(data[data['Pclass']==2].Fare, ax = ax[1])
ax[1].set_title('Fares in Pclass 2')
sns.distplot(data[data['Pclass']==3].Fare, ax = ax[2])
ax[2].set_title('Fares in Pclass 3')
plt.show()

>> Pclass1의 Fare가 높음을 알 수 있다. 

>> 연속형 변수는 범주화를 시킬 수 있다. By using binning

 


 

< 변수들 간의 상관계수 확인하기 >

sns.heatmap(data.corr(), annot = True, cmap = 'RdYlGn', linewidths = 0.2)
# data.corr() ---->> correlation matrix
# annot = True 옵션 ---->> 상관계수 숫자 값을 같이 표시해줌 
fig = plt.gcf()
fig.set_size_inches(10,8)
plt.show()

>> heatmap 해석

>> 우선, 수치형 변수들 끼리의 상관계수만 비교하자

>> 문자형 및 범주형 등등은 상관계수를 비교할 수 없다.

>> 상관계수가 엄청 높다는 것(1에 가까움)은 두 변수가 거의 같은 정보를 공유하고 있다는 것이고 이를 '다중공선성이       강하다' 라고 한다.

>> 다중공선성이 강하면 둘 중 한 변수는 쓸모 없기 때문에 하나만 사용하는 것이 좋다.

>> 타이타닉 데이터에서는 그렇게 강한 다중공선성을 가지는 변수들이 안보이기 때문에 그대로 간다.


< Feature Engineering and Data Cleaning > 

>> 모든 변수들을 분석에 사용하는 것은 아니다.

>> 우리는 모델링 하기 전에 새로운 변수를 추가하거나 혹은 제거할 필요가 있다. 

 

< 1. New_feature :: Age_band >

>> 연속형 변수로써 값이 정말 다양했다 >> 이는 범주화 해줄 필요가 있다. 

>> 그렇다면 무슨 기준으로 나이를 자를것인가??

>> max age가 80 / min age 가 0 이었기 때문에 0~80을 5개의 구간으로 나누는 방법을 선택!

>> 80 / 5 = 16 이니 한 구간의 크기는 16이 될 것이다. 

" Contiunous values into categorical values By Binning or Normalisation "

data['Age_band'] = 0
data.loc[data['Age'] <=16, 'Age_band'] = 0
data.loc[(data['Age'] > 16) & (data['Age'] <= 32), 'Age_band'] = 1
data.loc[(data['Age'] > 32) & (data['Age'] <= 48), 'Age_band'] = 2
data.loc[(data['Age'] > 48) & (data['Age'] <= 64), 'Age_band'] = 3
data.loc[data['Age'] > 64, 'Age_band'] = 4
data.head(2)

>> 나이를 5개의 값 (0,1,2,3,4) 로 범주화 진행 

data['Age_band'].value_counts().to_frame().style.background_gradient(cmap = 'summer')

sns.factorplot('Age_band', 'Survived', data = data, col = 'Pclass')
plt.show()

>> Pclass와 상관없이 나이가 증가할수록 생존율이 감소하고 있다. 

 


< 2. New feature :: Family_size >

:: Parch와 SibSp의 합을 가족구성원 수로 사용할 수 있다.

:: Parch와 SibSp는 한 가족에서 나눠진 변수라고 볼 수 있기 때문에 하나로 합친 변수로 사용하는게 좋을 거 같다. 

:: 가족 사이즈와 혼자의 생존율 차이를 확인하기 위해서 필요한 변수

data['Family_size'] = 0
data['Family_size'] = data['Parch'] + data['SibSp']
data['Alone'] = 0
data.loc[data.Family_size == 0, 'Alone'] = 1 # Alone

sns.factorplot('Alone','Survived', data = data , hue = 'Sex', col = 'Pclass')
plt.show()

>> 남성 기준으로는 Pclass 상관없이 혼자온 사람들의 생존율이 낮은 편이다.

>> 여성 기준으로는 Pclass 1~2 탑승객은 거의 생존하며, Pclass3은 남성과 반대로 생존율이 혼자온 사람이 더 높다.

 


 

< 3. New feature :: Fare_Range >

:: Age와 마찬가지고 Fare 변수도 연속형 변수로 값이 정말 다양하다 >> 범주화 시켜줄 필요가 있다.

:: 이번에는 pandas.qcut을 사용하여 범주화를 해줄 것 이다.

:: pandas.qcut은 bin size를 설정해주면 동등하게 값이 들어가도록 자동적으로 잘라준다. 

data['Fare_range'] = pd.qcut(data['Fare'],4)
data.groupby(['Fare_range'])['Survived'].mean().to_frame().style.background_gradient(cmap = 'summer_r')

>> 역시 Fare의 값이 높을수록 생존율이 높아짐을 확인할 수 있다

data['Fare_cat'] = 0
data.loc[data['Fare'] <= 7.91, 'Fare_cat']=0
data.loc[(data['Fare'] > 7.91) & (data['Fare'] <= 14.454), 'Fare_cat'] = 1
data.loc[(data['Fare'] > 14.454) & (data['Fare'] <= 31), 'Fare_cat'] = 2
data.loc[(data['Fare'] > 31) & (data['Fare'] <= 513), 'Fare_cat'] = 3

sns.factorplot('Fare_cat','Survived',data = data , hue = 'Sex')
plt.show()

 


 

< 4. 문자형 변수를 숫자값을 가지는 범주형으로 변환해주기 >

data['Sex'].replace(['male','female'],[0,1], inplace = True)
data['Embarked'].replace(['S','C','Q'], [0,1,2], inplace = True)
data['Initial'].replace(['Mr','Mrs','Miss','Master','Other'],
                       [0,1,2,3,4], inplace = True)

 

 

< 5. 필요없는 변수 버리기 & 최종 데이터 완성 >

# 필요없는 변수 버리기
# Name : categorical로 변환 불가
# Age : Age_band로 변환
# Ticket : 랜덤된 번호이고 categorical 변환 불가
# Fare : Fare_cat로 변환
# Cabin : NaN이 너무 많고 대다수 사람들이 multiple cabins을 가지고 있음 
# Fare_range : fare_cat 만들기 위해 필요했었음
# PassengerId : categorical 변환 불가

data.drop(['Name','Age','Ticket','Fare','Cabin','Fare_range','PassengerId'],
         axis = 1, inplace = True)
         
data.head()

728x90
반응형