본문 바로가기
IT/캐글

[캐글] 처음 시작하는 캐글 - 타이타닉(3)

by 작지만 중요한 것들을 발견하는 블로그. 2024. 5. 29.

이 글은 이유한님의 글을 참고하며 캐글 필사를 해보면서

코드를 한줄 한줄 읽어가며 가능한한 자세하게 분석해보려고 합니다.

유한님의 정리가 깔끔해서 그것들을 붙여넣기는 하지만! 학습한 내용 정리에 초점을 맞추었습니다.

 

(출처: 캐글 코리아 홈페이지의 이유한 님의 글)

 

코드 속에 설명을 덧붙이니, 시간도 더 줄어들고 깔끔해지는 것 같네요. 

한번 따라 쳐봤지만, 잘 감이 안잡혀서 코드를 하나하나 뜯어보는데 사실 이렇게 하면서 느끼는 점은

  • matplotlib와 seaborn pandas로 여러 그래프를 그릴 수 있고, 그래프에는 어떤 옵션이 들어가는지 
  • EDA(Exploratory Data Analysis) 탐색적 데이터 분석을 통해 어떤 데이터를 머신러닝에 사용할 것인지 판단하며
  • 데이터를 시각화 해보면서 어떤식으로 데이터를 활용할 것인지 결정하는 단계인 것 같네요.
  • 파이썬을 잘 다루고, 데이터에 대한 감각도 있어야 머신러닝을 잘 할 수 있을 것 같습니다.

시작합니당 ㅎㅎ!

2.5 Pclass, Sex, Age - 티켓등급, 성별, 나이 컬럼

코드
# 먼저, 플롯차트 그리드를 1행  2열, 크기는 가로 18, 세로 8 inch로 생성합니다.
f,ax=plt.subplots(1,2,figsize=(18,8))

# seaborn의 violinplot 차트에서 "Pclass", "Age"를 hue (생존 여부, Survived)에 따라 바이올린 플롯을 색깔로 구분합니다.
# scale='count': 각 바이올린의 너비를 해당 그룹의 데이터 개수에 비례하도록 조정합니다.
# split=True: 생존 여부에 따라 바이올린을 좌우로 분리하여 표시합니다.
# ax=ax[0]: 그림을 그릴 Axes 객체를 지정합니다. 여러 개의 그림을 그릴 때 사용합니다. 1행 1열 (1번째) 그래프에 넣기
sns.violinplot("Pclass","Age", hue="Survived", data=df_train, scale='count', split=True,ax=ax[0])

ax[0].set_title('Pclass and Age vs Survived')	# title 명명
ax[0].set_yticks(range(0,110,10)) # yticks는 0부터 100까지 10씩 증가하면서 그래프를 나타냅니다.

# 이번엔 "Sex", "Age"를 hue (Survived)에 따라 구분합니다. 이번에는 1행 2열 (2번째)에 할당합니다.
sns.violinplot("Sex","Age", hue="Survived", data=df_train, scale='count', split=True,ax=ax[1])
ax[1].set_title('Sex and Age vs Survived')		# title 명명
ax[1].set_yticks(range(0,110,10))		# 위와 같음 
plt.show()		# 그리기

 

결과
  • 지금까지 본 Sex, Pclass, Age, Survived 모두를 보기위해 사용하는 차트는 seaborn 의 violinplot를 이용합니다.
    ( violinplot : 바이올린플롯은 하나 이상의 변수로 그룹화한 후 데이터 포인트의 분포를 보여줍니다. 기본 분포의 커널 밀도 추정치(kde)를 사용하여 그려집니다. Seaborn - https://seaborn.pydata.org/generated/seaborn.violinplot.html)
  • x 축은 Pclass, Sex 를 나타내고, y 축은 distribution(Age) 입니다.
  • 아마 생존과 가장 관련이 깊다고 생각되는 Sex, Pclass, Age 과 Survived를 시각화 하여 나타낸 것 같습니다.

 

2.6 Embarked - 탑승항구 컬럼

코드
# subplots로 1행 1열의 1개짜리 그래프를 7 x 7 인치로 그리드를 생성합니다.
f, ax = plt.subplots(1, 1, figsize=(7, 7)) 

# Embarked(탑승항구), urvived(생존) 컬럼을 선택하고 'Embarked'값을 기준으로 그룹화합니다. 
# as_index=True는 결과에 'Embarked'를 인덱스가 아닌 일반 열로 포함시킵니다.
# .mean(): 각 그룹에서 'Survived' 열의 평균값을 계산합니다. 이는 각 출신 항구별 생존률을 나타냅니다.
# 계산된 생존율을 기준으로 내림차순 정렬합니다. 생존율이 가장 높은 탑승 항구부터 가장 낮은 탑승 항구 순으로 정렬합니다.
df_train[['Embarked', 'Survived']].groupby(['Embarked'], as_index=True).mean().sort_values(by='Survived', ascending=False).plot.bar(ax=ax)

 

결과
  • 생존율 차이가 있습니다. 모델을 만들고 나면 우리가 사용한 feature 들이 얼마나 중요한 역할을 했는지 확인해볼 수 있습니다. 이는 추후에 모델을 만들고 난 다음에 살펴볼 것입니다.
  • 다른 feature 로 split 하여 한번 살펴보겠습니다

 

코드
# f, ax 객체에 sub plot으로 2행 2열의 빈 그리드 플롯 생성, 전체 크기는 20,15 인치
f,ax=plt.subplots(2, 2, figsize=(20,15))

sns.countplot('Embarked', data=df_train, ax=ax[0,0])					# countplot, 탑승항구 컬럼 그래프 (1행, 1열)
sns.countplot('Embarked', hue='Sex', data=df_train, ax=ax[0,1])			# countplot, 탑승항구 컬럼과 성별 그래프 생성. 성별을 y축으로 (1행, 2열)에 위치
sns.countplot('Embarked', hue='Survived', data=df_train, ax=ax[1,0])	# countplot, 탑승항구 컬럼과 생존 그래프 생성. 생존을 y축으로 (2행, 1열)에 위치
sns.countplot('Embarked', hue='Pclass', data=df_train, ax=ax[1,1])		# countplot, 탑승항구 컬럼과 티켓등급 그래프 생성, (2행 2열) 위치

ax[0,0].set_title('(1) No. Of Passengers Boarded')		# 1행 1열 그래프 set_title() 함수로 제목 명명
ax[0,1].set_title('(2) Male-Female Split for Embarked') # 1행 2열 제목 명명
ax[1,0].set_title('(3) Embarked vs Survived')		# 제목 명명
ax[1,1].set_title('(4) Embarked vs Pclass') 		# 제목 명명

# wspace: subplot 사이 가로 간격을 설정. 0부터 1 사이의 값을 가지며, 값이 클수록 간격이 넓어집니다. 0.2는 subplot 너비의 20%만큼 가로 간격을 설정합니다.
# hspace: subplot 사이 세로 간격을 설정. 0부터 1 사이의 값을 가지며, 값이 클수록 간격이 넓어집니다. 0.5는 subplot 높이의 50%만큼 세로 간격을 설정합니다.
plt.subplots_adjust(wspace=0.2, hspace=0.5) 
plt.show()		# 그래프 생성

 

결과

S가 생존율이 높은 이유는 탑승객이 많이 타서 인것 같습니다.

2.7 Family - SibSp(형제 자매) + Parch(부모, 자녀)

코드
# SibSp: 함께 탑승한 형제자매(Siblings)와 배우자(Spouse)의 수
# Parch: 함께 탑승한 부모(Parents)와 자녀(Children)의 수
# 두 항목을 더해서 가족 컬럼을 만듭니다.
df_train['FamilySize'] = df_train['SibSp'] + df_train['Parch'] + 1 # 자신을 포함해야하니 1을 더합니다
df_test['FamilySize'] = df_test['SibSp'] + df_test['Parch'] + 1 # 자신을 포함해야하니 1을 더합니다

# 최대 최소값 확인
print("Maximum size of Family: ", df_train['FamilySize'].max())
print("Minimum size of Family: ", df_train['FamilySize'].min())

 

코드
f,ax = plt.subplots(1,3, figsize=(40,10))	# 빈 그래프 생성

sns.countplot(x='FamilySize', data=df_train, ax=ax[0])
ax[0].set_title('(1) No. Of Passengers Boarded', y=1.02) #

# hue='Survived': 색상으로 표현할 변수를 설정합니다. 이 경우 'Survived' 변수가 사용됩니다. 즉, 생존 여부에 따라 색상이 다르게 표시됩니다
sns.countplot(x='FamilySize', hue='Survived', data=df_train, ax=ax[1])
ax[1].set_title('(2) Survived countplot depending on FamilySize', y=1.02)

# groupby(['FamilySize'], as_index=True).mean()
# 'FamilySize' 열을 기준으로 데이터를 그룹화합니다.
# as_index=True 옵션을 사용하여 기존 인덱스 대신 'FamilySize'를 새로운 인덱스로 설정합니다.
# 각 그룹에 대해 'Survived' 열의 평균을 계산합니다.
# .sort_values(by='Survived', ascending=False):
# 'Survived' 열의 평균값을 기준으로 데이터를 내림차순 정렬합니다. 즉, 생존율이 높은 가족 크기 순으로 정렬합니다.
df_train[['FamilySize', 'Survived']].groupby(['FamilySize'], as_index=True).mean().sort_values(by='Survived', ascending=False).plot.bar(ax=ax[2])
ax[2].set_title('(3) Survived rate depending on FamilySize', y=1.02)

plt.subplots_adjust(wspace=0.2, hspace=0.5)	# wspace= 가로 20%, hspace=0.5 세로 50% 간격 조정
plt.show()

 

결과

그대로 복붙하여 정리를 해봅니다.

  • Figure (1) - 가족크기가 1~11까지 있음을 볼 수 있습니다. 대부분 1명이고 그 다음으로 2, 3, 4명입니다.
  • Figure (2), (3) - 가족 크기에 따른 생존비교입니다. 가족이 4명인 경우가 가장 생존확률이 높습니다. 가족수가 많아질수록, (5, 6, 7, 8, 11) 생존확률이 낮아지네요. 가족수가 너무 작아도(1), 너무 커도(5, 6, 8, 11) 생존 확률이 작네요. 3~4명 선에서 생존확률이 높은 걸 확인할 수 있습니다.

 

8. Fare - 탑승요금

  • Fare 는 탑승요금이며, contious feature 입니다.
코드
f, ax = plt.subplots(1,1, figsize=(8,4)) # 그래프와 축 생성, 크기 조절

# sns.distplot: 이는 seaborn 라이브러리의 distplot 함수를 호출하며, 이는 데이터의 분포 그래프을 만드는 데 사용합니다.
# label='Skewness : {:.2f}'.format(df_train['Fare'].skew()): 이는 플롯에 "Fare" 데이터의 왜곡도를 표시하는 레이블을 만듭니다.
# .skew(): 이는 데이터의 왜곡도를 계산합니다 (비대칭성을 측정하는 지표).
# '{:.2f}'.format(): 이는 왜곡도 값을 소수점 두 자리까지 표시하도록 형식 지정합니다.
g = sns.distplot(df_train['Fare'], color='b', label='Skewness : {:.2f}'.format(df_train['Fare'].skew()), ax=ax)

# g.legend(): 이는 g 객체(분포 플롯을 포함)에서 legend 메서드를 호출하여 플롯에 범례를 만듭니다.
# loc='best': 이는 Matplotlib에게 플롯에서 가장 적절한 위치에 범례를 자동으로 배치하도록 지시합니다.
g = g.legend(loc='best')

 

결과

그대로 복붙하여 정리를 해봅니다.

  • 보시다시피, distribution이 매우 비대칭인 것을 알 수 있습니다.(high skewness). 만약 이대로 모델에 넣어준다면 자칫 모델이 잘못 학습할 수도 있습니다. 몇개 없는 outlier 에 대해서 너무 민감하게 반응한다면, 실제 예측 시에 좋지 못한 결과를 부를 수 있습니다.
  • outlier의 영향을 줄이기 위해 Fare 에 log 를 취하겠습니다.
  • 여기서 우리는 pandas 의 유용한 기능을 사용할 겁니다. dataFrame 의 특정 columns 에 공통된 작업(함수)를 적용하고 싶으면 아래의 map, 또는 apply 를 사용하면 매우 손쉽게 적용할 수 있습니다.
  • 우리가 지금 원하는 것은 Fare columns 의 데이터 모두를 log 값 취하는 것인데, 파이썬의 간단한 lambda 함수를 이용해 간단한 로그를 적용하는 함수를 map 에 인수로 넣어주면, Fare columns 데이터에 그대로 적용이 됩니다. 매우 유용한 기능이니 꼭 숙지하세요!

 

코드

이 코드는 map()함수와 람다 함수를 이용해서 데이터에 로그를 씌워줍니다.

map(a, b) 함수는 b요소를 a지정된 함수로 처리해주는 함수 입니다.

# 아래 줄은 뒤늦게 발견하였습니다. 13번째 강의에 언급되니, 일단 따라치시고 넘어가면 됩니다.
df_test.loc[df_test.Fare.isnull(), 'Fare'] = df_test['Fare'].mean() # testset 에 있는 nan value 를 평균값으로 치환합니다.

df_train['Fare'] = df_train['Fare'].map(lambda i: np.log(i) if i > 0 else 0)
df_test['Fare'] = df_test['Fare'].map(lambda i: np.log(i) if i > 0 else 0)
코드
fig, ax = plt.subplots(1, 1, figsize=(8, 8))
g = sns.distplot(df_train['Fare'], color='b', label='Skewness : {:.2f}'.format(df_train['Fare'].skew()), ax=ax)
g = g.legend(loc='best')

 

결과
  • log 를 취하니, 이제 비대칭성이 많이 사라진 것을 볼 수 있습니다.
  • 우리는 이런 작업을 사용해 모델이 좀 더 좋은 성능을 내도록 할 수 있습니다.
  • 사실 방금한 것은 feature engineering 에 들어가는 부분인데, 여기서 작업했습니다.
  • 모델을 학습시키기 위해, 그리고 그 모델의 성능을 높이기 위해 feature 들에 여러 조작을 가하거나, 새로운 feature를 추가하는 것을 feature engineering 이라고 하는데, 우리는 이제 그것을 살펴볼 것입니다.

 

2.9 Cabin

  • 이 feature 는 NaN 이 대략 80% 이므로, 생존에 영향을 미칠 중요한 정보를 얻어내기가 쉽지는 않습니다.
  • 그러므로 우리가 세우려는 모델에 포함시키지 않도록 하겠습니다. - 이런 판단을 할 수 있어야 하나보다.
코드
df_train['Ticket'].value_counts()

 

2.10 Ticket

  • 이 feature 는 NaN 은 없습니다. 일단 string data 이므로 우리가 어떤 작업들을 해주어야 실제 모델에 사용할 수 있는데, 이를 위해선 사실 아이디어가 필요합니다.

 

EDA를 마칩니다. 내용이 길고, 할 것들이 많아서 익숙해지는데 시간이 많이 필요하지만, 익숙해지고 데이터도 잘 다루고 모델도 만들어 캐글에 참가해서 결과물을 내볼 날을 고대해봅니당..ㅎㅎ!