05_EDA_Titanic실습

2026. 7. 6. 08:57·SKN_AI_33/Data_Analysis

EDA 실습 — Titanic 생존 데이터

Titanic 데이터셋을 대상으로 EDA 10단계를 직접 적용한 실습 노트. 코드보다 각 단계에서 무엇을 판단했는지에 집중.

실습 파일: ~/Desktop/SKN/SKN_33/05_data_analysis/04_eda/02_eda_titanic.ipynb


핵심 분석 질문

EDA를 시작하기 전에 질문을 먼저 정해야 함.

  1. Titanic 승객의 생존 여부는 어떤 변수와 관련이 있어 보이는가?
  2. 결측치나 이상값 때문에 전처리가 필요한 컬럼은 무엇인가?
  3. 모델링을 한다면 어떤 파생변수를 만들 수 있는가?
  4. 최종적으로 어떤 인사이트를 보고서에 쓸 수 있는가?

01. 데이터 로드

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
import seaborn as sns
import platform

# 한글 폰트 설정 (Windows: Malgun Gothic)
font_candidates = {
    "Windows": ["Malgun Gothic", "NanumGothic"],
    "Darwin": ["AppleGothic", "NanumGothic"],
    "Linux": ["NanumGothic"],
}
available_fonts = {font.name for font in fm.fontManager.ttflist}
for font_name in font_candidates.get(platform.system(), []):
    if font_name in available_fonts:
        plt.rcParams["font.family"] = font_name
        break

plt.rcParams["axes.unicode_minus"] = False   # 마이너스 기호 깨짐 방지
sns.set_theme(style="whitegrid")

raw_df = pd.read_csv("data/titanic.csv")
df = raw_df.copy()   # 원본 유지, 복사본으로 작업

02. 데이터 구조

  • 크기: 891행 × 12열
  • 한 행 의미: Titanic 탑승 승객 1명
  • 타깃: Survived (0=사망, 1=생존)

컬럼 타입 의미

PassengerId int 승객 고유 번호
Survived int 생존 여부 (타깃)
Pclass int 객실 등급 (1·2·3, 숫자이지만 범주형)
Name str 이름
Sex str 성별
Age float 나이 (결측치 있음)
SibSp int 형제·배우자 수
Parch int 부모·자녀 수
Ticket str 티켓 번호
Fare float 요금
Cabin str 객실 번호 (결측치 많음)
Embarked str 탑승 항구 (S·C·Q)
print(df.shape)              # (891, 12)
print(df.columns.tolist())
df.info()

03. 기술 통계

수치형 요약 (describe().T)

컬럼 평균 중앙값 주목할 점

Survived 0.38 0 생존율 약 38%
Age 29.7 28.0 결측치로 714개만 집계
Fare 32.2 14.5 평균 > 중앙값 → 오른쪽 치우침

범주형 요약

컬럼 최빈값 고유값 수

Sex male (577) 2
Embarked S (644) 3
Name — 891 (모두 고유)
Cabin — 147
df.describe().T
df.describe(include=["object"]).T

# 타깃 클래스 비율
df["Survived"].value_counts(normalize=True) * 100
# → 사망 61.6%, 생존 38.4%

04. 결측치·중복·이상값

결측치 요약

컬럼 결측 수 결측률 처리 전략

Cabin 687 77.1% 원본 제거 → HasCabin 파생변수 생성
Age 177 19.9% 성별·등급별 중앙값으로 대체
Embarked 2 0.2% 최빈값(S)으로 대체
missing_summary = pd.DataFrame({
    "missing_count": df.isna().sum(),
    "missing_rate": (df.isna().sum() / df.shape[0]) * 100,
}).sort_values("missing_count", ascending=False)

중복

  • 전체 중복 행: 0개
  • PassengerId 중복: 0개 → 식별자로 사용 가능

이상값 (IQR 기준)

컬럼 이상값 후보 수 해석

Age 11 매우 나이 많은 승객. 오류보다 실제값 가능성 높음
Fare 116 고가 티켓. 1등급 특성. 로그 변환 고려
for col in ["Age", "Fare"]:
    q1 = df[col].quantile(0.25)
    q3 = df[col].quantile(0.75)
    iqr = q3 - q1
    lower, upper = q1 - 1.5 * iqr, q3 + 1.5 * iqr
    outlier_count = ((df[col] < lower) | (df[col] > upper)).sum()
    print(f"{col}: 이상값 후보 {outlier_count}개")

05 & 06. 분포 시각화 + 변수 간 관계

# 범주형 4개 분포 한 번에
fig, axes = plt.subplots(2, 2, figsize=(12, 8), constrained_layout=True)
axes = axes.ravel()   # 2D → 1D

for ax, (col, title) in zip(axes, [
    ("Survived", "생존 여부"), ("Pclass", "객실 등급"),
    ("Sex", "성별"), ("Embarked", "탑승 항구")
]):
    sns.countplot(data=df, x=col, ax=ax, hue=col, palette="Set2", legend=False)
    ax.set_title(title)

plt.show()

# 성별·등급·항구별 생존율
fig, axes = plt.subplots(1, 3, figsize=(15, 4), constrained_layout=True)
for ax, col in zip(axes, ["Sex", "Pclass", "Embarked"]):
    sns.barplot(data=df, x=col, y="Survived", errorbar=None, ax=ax)
    ax.set_ylim(0, 1)
    ax.set_title(f"{col}별 생존율")

plt.show()

07. 상관관계 및 교차분석

수치형 상관계수 (주목할 관계)

변수 조합 상관계수 해석

Pclass ↔ Fare -0.55 등급 낮을수록 요금 낮음
Survived ↔ Pclass -0.34 등급 높을수록 생존율 높음
Survived ↔ Fare 0.26 요금 높을수록 생존율 약간 높음
corr_cols = ["Survived", "Pclass", "Age", "SibSp", "Parch", "Fare"]
corr_mat = df[corr_cols].corr(numeric_only=True)
sns.heatmap(corr_mat, annot=True, fmt=".2f", cmap="coolwarm", center=0)
plt.show()

범주형 교차표

# normalize='index': 행 기준 비율 (각 그룹 내 생존율)
pd.crosstab(df["Sex"], df["Survived"], normalize="index").round(3)
pd.crosstab(df["Pclass"], df["Survived"], normalize="index").round(3)

성별 생존율

female 74.2%
male 18.9%

객실 등급 생존율

1등급 63.0%
2등급 47.3%
3등급 24.2%

08. 전처리 실습

eda_df = raw_df.copy()   # 원본 유지!

# Cabin: 결측 여부 파생변수
eda_df["HasCabin"] = eda_df["Cabin"].notna().astype(int)

# Age: 성별·등급별 중앙값으로 대체 (transform: 그룹 크기 유지)
age_median = eda_df.groupby(["Sex", "Pclass"])["Age"].transform("median")
eda_df["AgeFilled"] = eda_df["Age"].fillna(age_median)

# Embarked: 최빈값으로 대체
embarked_mode = eda_df["Embarked"].mode()[0]   # mode()[0]: 최빈값 첫 번째
eda_df["EmbarkedFilled"] = eda_df["Embarked"].fillna(embarked_mode)

groupby().transform(): 그룹별 계산 후 원본 DataFrame과 같은 인덱스로 반환. fillna()와 함께 쓰기 좋음.


09. Feature Engineering 실습

# 가족 규모
eda_df["FamilySize"] = eda_df["SibSp"] + eda_df["Parch"] + 1

# 단독 탑승 여부
eda_df["IsAlone"] = (eda_df["FamilySize"] == 1).astype(int)

# 나이 구간화
eda_df["AgeGroup"] = pd.cut(
    eda_df["AgeFilled"],
    bins=[0, 12, 18, 35, 60, eda_df["AgeFilled"].max()],
    labels=["Child", "Teen", "YoungAdult", "Adult", "Senior"],
    include_lowest=True   # 가장 왼쪽 경계값 포함
)

# 로그 변환 (Fare 치우침 완화)
eda_df["LogFare"] = np.log1p(eda_df["Fare"])   # log(1+x)

# 원-핫 인코딩
model_ready_df = pd.get_dummies(
    eda_df[["Survived", "Pclass", "AgeFilled", "FamilySize",
            "IsAlone", "LogFare", "HasCabin", "Sex", "EmbarkedFilled"]],
    columns=["Sex", "EmbarkedFilled"],
    dtype=int
)
# 결과: Sex_female, Sex_male, EmbarkedFilled_C, EmbarkedFilled_Q, EmbarkedFilled_S 생성

10. 최종 인사이트

  1. 전체 생존율은 약 38.4%로 사망자 비율이 더 높음
  2. 여성 생존율(74.2%)이 남성(18.9%)보다 훨씬 높음 → Sex는 가장 관련 큰 변수
  3. 객실 등급이 높을수록 생존율이 높음 (1등급 63%, 3등급 24%)
  4. Cabin 결측률이 77%로 원본 컬럼 사용 어려움 → HasCabin 파생변수 활용
  5. Age 결측률 20% → 성별·등급별 중앙값으로 대체 전략 선택
  6. Fare는 오른쪽 치우침 + 이상값 후보 116개 → LogFare 변환 고려

다음 단계: 결측치 처리 → 인코딩 → Feature/Target 분리 → 머신러닝 모델 학습


자주 쓴 코드 패턴

# 결측치 요약표
pd.DataFrame({
    "missing_count": df.isna().sum(),
    "missing_rate": (df.isna().sum() / len(df)) * 100,
}).sort_values("missing_count", ascending=False)

# 그룹별 교차표 (비율)
pd.crosstab(df["Sex"], df["Survived"], normalize="index").round(3)

# 여러 subplot + ravel
fig, axes = plt.subplots(2, 2, figsize=(12, 8), constrained_layout=True)
axes = axes.ravel()
for ax, col in zip(axes, col_list):
    sns.countplot(data=df, x=col, ax=ax)

# 그룹별 변환 후 fillna
age_fill = df.groupby(["Sex", "Pclass"])["Age"].transform("median")
df["AgeFilled"] = df["Age"].fillna(age_fill)

'SKN_AI_33 > Data_Analysis' 카테고리의 다른 글

04_EDA  (0) 2026.07.06
03_Seaborn  (0) 2026.07.06
02_Pandas  (0) 2026.07.06
01. NumPy  (0) 2026.07.06
'SKN_AI_33/Data_Analysis' 카테고리의 다른 글
  • 04_EDA
  • 03_Seaborn
  • 02_Pandas
  • 01. NumPy
sjh4253
sjh4253
  • sjh4253
    지노의 빈노트
    sjh4253
  • 전체
    오늘
    어제
    • 분류 전체보기 (42)
      • SKN_AI_33 (42)
        • Git (3)
        • Python (8)
        • DB (6)
        • Web_Scraping (5)
        • Data_Analysis (5)
        • Machine_learning (0)
        • 회고 (15)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    자료형
    BeautifulSoup
    크롤링
    Join
    Selenium
    except
    DB연동
    DDL
    네이버api
    Python
    데이터수집
    트랜잭션
    파이썬
    개발공부
    DB입문
    rollback
    SQL
    MySQL
    python입문
    데이터베이스설계
    웹스크래핑
    데이터베이스
    데이터타입
    조건문
    requests
    데이터조회
    예외처리
    반복문
    데이터파이프라인
    동적크롤링
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
sjh4253
05_EDA_Titanic실습
상단으로

티스토리툴바