2차시: Pandas로 데이터와 대화하기 - DataFrame 탐색과 전처리
🎯 이 차시의 핵심 주제
섹션 제목: “🎯 이 차시의 핵심 주제”🧠 DataFrame의 구조
섹션 제목: “🧠 DataFrame의 구조”엑셀 시트처럼 생긴 2차원 자료구조를 파이썬에서 자유자재로 다루는 법
🔧 데이터 탐색 & 전처리
섹션 제목: “🔧 데이터 탐색 & 전처리”head, info, describe로 첫인상 파악하고 결측치·중복을 정리
💬 조건 필터링과 groupby
섹션 제목: “💬 조건 필터링과 groupby”“여성 1등석 승객의 생존율”처럼 질문을 코드로 바꾸는 훈련
📝 미니 챌린지
섹션 제목: “📝 미니 챌린지”생존율이 가장 높은 집단을 직접 찾아내는 개인 과제
⏱️ 수업 흐름
섹션 제목: “⏱️ 수업 흐름”1단계: 도입 & DataFrame 첫 만남 (15분)
섹션 제목: “1단계: 도입 & DataFrame 첫 만남 (15분)”Pandas가 왜 필요한지 공감하고, Series와 DataFrame의 구조를 직접 만들어 봅니다. NumPy와 무엇이 다른지 비교합니다.
2단계: 타이타닉 데이터 탐색 (20분)
섹션 제목: “2단계: 타이타닉 데이터 탐색 (20분)”실제 타이타닉 CSV를 불러와 head(), info(), describe()로 데이터의 첫인상을 파악합니다.
3단계: 결측치 처리 & 필터링 (20분)
섹션 제목: “3단계: 결측치 처리 & 필터링 (20분)”결측치를 탐지하고 fillna, dropna로 처리합니다. 불리언 인덱싱으로 “여성 승객만” 같은 조건 추출을 연습합니다.
4단계: groupby로 집단 분석 (15분)
섹션 제목: “4단계: groupby로 집단 분석 (15분)”groupby를 사용해 성별·객실등급별 생존율을 계산합니다.
5단계: 미니 챌린지 & 정리 (10분)
섹션 제목: “5단계: 미니 챌린지 & 정리 (10분)”“생존율이 가장 높은 집단은?”을 개인별로 해결하고, 결과를 공유한 뒤 형성 평가로 마무리합니다.
📚 핵심 개념
섹션 제목: “📚 핵심 개념”1. 🧠 DataFrame, 파이썬 속의 엑셀 시트
섹션 제목: “1. 🧠 DataFrame, 파이썬 속의 엑셀 시트”엑셀을 써본 경험을 떠올려 보세요. 행에는 사람이, 열에는 이름·나이·점수가 있고, 원하는 열만 뽑거나 조건에 맞는 행만 필터링합니다. Pandas의 DataFrame은 바로 이 엑셀 시트를 파이썬 코드로 조작할 수 있게 만든 자료구조입니다.
DataFrame은 2차원 표 형태의 자료구조이고, 그 한 열을 떼어내면 Series라는 1차원 자료구조가 됩니다. 둘 모두 인덱스(index)라는 행 이름표를 갖는다는 점이 리스트나 NumPy 배열과 다른 점입니다.
flowchart LR A[Series<br/>1차원 + 인덱스] --> C[DataFrame<br/>2차원 표] B[여러 Series를<br/>열로 결합] --> C C --> D[행: 하나의 관측치<br/>열: 하나의 변수]
NumPy vs Pandas, 무엇이 다른가
섹션 제목: “NumPy vs Pandas, 무엇이 다른가”| 항목 | NumPy ndarray | Pandas DataFrame |
|---|---|---|
| 차원 | N차원 | 2차원 (주로) |
| 데이터 타입 | 한 종류만 | 열마다 다른 타입 가능 |
| 이름표 | 없음 (정수 인덱스만) | 행/열 이름 가능 |
| 주 용도 | 수치 계산 | 표 형태 데이터 분석 |
숫자 계산만 빠르게 한다면 NumPy가 유리하지만, “이름이 Kim인 사람의 점수”처럼 의미 있는 이름표로 데이터를 다룰 때는 Pandas가 훨씬 편합니다.
직접 만들어 보기 (v1)
섹션 제목: “직접 만들어 보기 (v1)”실행 환경: Python 3.10+,
pip install pandas필요
import pandas as pd # 관례적으로 pd로 줄여 씁니다
# 딕셔너리로 간단히 DataFrame 만들기 (키=열 이름, 값=열 데이터)data = { "이름": ["지민", "하윤", "서준"], "나이": [15, 16, 15], "점수": [88, 92, 75]}df = pd.DataFrame(data) # <- 여기가 DataFrame 생성print(df) 이름 나이 점수0 지민 15 881 하윤 16 922 서준 15 75출력 왼쪽의 0, 1, 2는 자동으로 붙은 행 인덱스입니다. 위 코드 10번째 줄 pd.DataFrame(data)가 바로 딕셔너리를 표로 변환하는 과정입니다.
열 하나 뽑으면 Series (v2)
섹션 제목: “열 하나 뽑으면 Series (v2)”점수_시리즈 = df["점수"] # <- 대괄호로 열 이름을 지정하면 Series 반환print(점수_시리즈)print("타입:", type(점수_시리즈).__name__)0 881 922 75Name: 점수, dtype: int64타입: SeriesDataFrame에서 df["열이름"]으로 꺼낸 한 열은 Series가 됩니다. 값(88, 92, 75)에 인덱스(0, 1, 2)가 따라붙은 형태입니다.
2. 🔍 데이터의 첫인상 파악하기 — head, info, describe
섹션 제목: “2. 🔍 데이터의 첫인상 파악하기 — head, info, describe”낯선 데이터를 받으면 먼저 어떤 데이터인지 훑어보는 작업이 필요합니다. 처음 보는 요리를 먹기 전에 색깔과 냄새를 맡아보는 것과 같습니다. Pandas는 이 “첫인상 파악”을 위한 세 가지 도구를 제공합니다.
head(n): 상위 n개 행을 보여줍니다 (기본 5개). 데이터 생김새 확인용info(): 행·열 개수, 각 열의 데이터 타입, 결측치 개수를 요약describe(): 숫자 열의 평균·표준편차·최소·최대 등 기초 통계
타이타닉 데이터 불러오기
섹션 제목: “타이타닉 데이터 불러오기”타이타닉 데이터셋은 1912년 침몰한 타이타닉호 승객 정보를 담고 있습니다. 실습용으로 seaborn 라이브러리에 내장되어 있어 인터넷 연결만 되면 바로 쓸 수 있습니다.
import pandas as pdimport seaborn as sns # 실습용 데이터셋 제공 라이브러리
# 타이타닉 데이터 불러오기titanic = sns.load_dataset("titanic")
# 상위 5개 행만 확인 (전체를 다 출력하면 너무 기니까)print(titanic.head()) survived pclass sex age sibsp parch fare embarked class ...0 0 3 male 22.0 1 0 7.2500 S Third ...1 1 1 female 38.0 1 0 71.2833 C First ...2 1 3 female 26.0 0 0 7.9250 S Third ...3 0 3 male 35.0 1 0 53.1000 S First ...4 0 3 male 35.0 0 0 8.0500 S Third ...주요 열의 의미는 다음과 같습니다.
| 열 이름 | 의미 |
|---|---|
| survived | 생존 여부 (0=사망, 1=생존) |
| pclass | 객실 등급 (1=1등석, 2=2등석, 3=3등석) |
| sex | 성별 |
| age | 나이 |
| fare | 요금 |
| embarked | 탑승 항구 (S=사우샘프턴, C=셰르부르, Q=퀸스타운) |
info로 전체 구조 훑기
섹션 제목: “info로 전체 구조 훑기”titanic.info()<class 'pandas.core.frame.DataFrame'>RangeIndex: 891 entries, 0 to 890Data columns (total 15 columns): # Column Non-Null Count Dtype--- ------ -------------- ----- 0 survived 891 non-null int64 1 pclass 891 non-null int64 2 sex 891 non-null object 3 age 714 non-null float64 <- 결측치 존재! ... 7 embarked 889 non-null object <- 결측치 존재!info() 출력에서 중요한 정보는 Non-Null Count입니다. 전체 891행인데 age는 714개만 값이 있으므로, 891 - 714 = 177개의 결측치가 있다는 뜻입니다.
describe로 통계 요약
섹션 제목: “describe로 통계 요약”# 숫자 열만 요약 통계를 보여줍니다print(titanic.describe()) survived pclass age sibsp parch farecount 891.000000 891.000000 714.000000 891.000000 891.000000 891.000000mean 0.383838 2.308642 29.699118 0.523008 0.381594 32.204208std 0.486592 0.836071 14.526497 1.102743 0.806057 49.693429min 0.000000 1.000000 0.420000 0.000000 0.000000 0.00000025% 0.000000 2.000000 20.125000 0.000000 0.000000 7.91040050% 0.000000 3.000000 28.000000 0.000000 0.000000 14.45420075% 1.000000 3.000000 38.000000 1.000000 0.000000 31.000000max 1.000000 3.000000 80.000000 8.000000 6.000000 512.329200survived의 평균(mean)이 0.38이라는 건 전체 생존율이 약 38%였다는 의미입니다. age의 최솟값이 0.42(약 5개월)이고 최댓값이 80이라는 정보도 한눈에 보입니다.
3. 🧹 결측치 처리와 조건 필터링
섹션 제목: “3. 🧹 결측치 처리와 조건 필터링”데이터에 빈칸(결측치)이 있으면 평균을 계산할 때 오류가 나거나, 모델이 학습을 못 하는 일이 생깁니다. 빠진 퍼즐 조각을 어떻게 처리할지 결정해야 합니다. 선택지는 두 가지입니다.
dropna(): 결측치가 있는 행/열을 버린다fillna(값): 결측치를 특정 값으로 채운다 (평균, 중앙값, 0 등)
어느 쪽이 정답인지는 상황에 따라 다릅니다. 데이터가 많고 결측치가 적으면 버려도 되지만, 소중한 데이터라면 평균값으로 채우는 게 낫습니다.
결측치 개수 세기
섹션 제목: “결측치 개수 세기”# 열별 결측치 개수 (True는 1로 계산되므로 sum으로 카운트)print(titanic.isnull().sum())survived 0pclass 0sex 0age 177 <- 177개 결측...embarked 2deck 688 <- 대부분이 결측...v1: 결측치 있는 행 다 버리기 (위험한 방법)
섹션 제목: “v1: 결측치 있는 행 다 버리기 (위험한 방법)”df_dropped = titanic.dropna() # 결측치 있는 행을 모두 제거print("원본:", len(titanic), "행")print("제거 후:", len(df_dropped), "행")원본: 891 행제거 후: 182 행891행 중 182행만 남았습니다. deck 열이 대부분 결측이라 무턱대고 버리면 79%의 데이터를 잃습니다. 좋은 전략이 아닙니다.
v2: 필요한 열만 골라서 처리
섹션 제목: “v2: 필요한 열만 골라서 처리”# 분석에 쓸 열만 선별 (deck처럼 결측이 많은 열은 제외)cols = ["survived", "pclass", "sex", "age", "fare", "embarked"]df = titanic[cols].copy() # 원본 보존을 위해 복사본 생성
# age는 중앙값으로 채우기 (평균보다 이상치에 덜 민감)df["age"] = df["age"].fillna(df["age"].median())
# embarked는 결측이 2개뿐 → 최빈값으로 채우기df["embarked"] = df["embarked"].fillna(df["embarked"].mode()[0])
print(df.isnull().sum())survived 0pclass 0sex 0age 0fare 0embarked 0결측치가 모두 사라졌습니다. 위 코드 7번째 줄 df["age"].fillna(df["age"].median())가 평균 대신 중앙값으로 채우는 전략입니다. 극단적으로 어린 승객이나 노인이 평균을 왜곡하는 것을 막을 수 있습니다.
조건 필터링 — 불리언 인덱싱
섹션 제목: “조건 필터링 — 불리언 인덱싱”“여성 승객만 보고 싶다”를 코드로 옮기면 이렇게 됩니다.
# 조건식은 True/False 시리즈를 반환is_female = df["sex"] == "female"print(is_female.head())0 False1 True2 True3 True4 FalseName: sex, dtype: bool# True인 행만 추출 (대괄호 안에 조건식을 넣음)females = df[df["sex"] == "female"]print(f"여성 승객 수: {len(females)}")print(f"여성 생존율: {females['survived'].mean():.2%}")여성 승객 수: 314여성 생존율: 74.20%위 코드 2번째 줄 df[df["sex"] == "female"]가 바로 불리언 인덱싱입니다. 대괄호 안에 True/False 시리즈를 넣으면, True인 행만 남습니다.
여러 조건 결합하기
섹션 제목: “여러 조건 결합하기”# '&'는 and, '|'는 or (파이썬의 and/or은 안 됨!) 각 조건은 반드시 괄호로 감쌉니다# 1등석 여성 승객만 추출first_class_female = df[(df["pclass"] == 1) & (df["sex"] == "female")]print(f"1등석 여성: {len(first_class_female)}명, 생존율: {first_class_female['survived'].mean():.2%}")1등석 여성: 94명, 생존율: 96.81%1등석 여성의 생존율은 96.81%입니다. 전체 평균 38%와 비교하면 엄청난 차이입니다.
⚠️ 흔한 에러: 괄호를 빼먹으면?
섹션 제목: “⚠️ 흔한 에러: 괄호를 빼먹으면?”# 이 코드를 실행하면 어떤 에러가 날까요?first_class_female = df[df["pclass"] == 1 & df["sex"] == "female"]TypeError: Cannot perform 'rand_' with a dtyped [object] array and scalar of type [bool]원인: & 연산자가 ==보다 우선순위가 높아서 1 & df["sex"]가 먼저 계산됩니다. 파이썬이 “1과 문자열을 어떻게 AND 하나요?”라며 혼란에 빠집니다.
수정: 각 조건을 반드시 소괄호로 감싸세요.
first_class_female = df[(df["pclass"] == 1) & (df["sex"] == "female")] # <- 괄호 필수4. 👥 groupby — 집단별 비교의 핵심 무기
섹션 제목: “4. 👥 groupby — 집단별 비교의 핵심 무기”“남자와 여자의 생존율을 각각 비교하고 싶다”면 조건 필터링을 두 번 해도 됩니다. 하지만 “모든 객실등급별, 모든 성별별, 모든 항구별 생존율”을 알고 싶다면 일일이 필터링하는 건 비효율적입니다. 이때 groupby를 씁니다.
groupby는 “같은 값을 가진 행끼리 묶어서 → 각 그룹에 통계 함수를 적용”하는 분할-적용-결합(split-apply-combine) 패턴입니다.
flowchart LR A[전체 데이터] --> B[성별로 분할<br/>split] B --> C[각 그룹별<br/>평균 계산<br/>apply] C --> D[결과 합치기<br/>combine]
v1: 성별 생존율
섹션 제목: “v1: 성별 생존율”# sex 열로 묶고, survived 열의 평균을 계산result = df.groupby("sex")["survived"].mean()print(result)sexfemale 0.742038male 0.188908Name: survived, dtype: float64여성 74.2%, 남성 18.9%. 극명한 차이입니다.
v2: 두 개의 기준으로 묶기
섹션 제목: “v2: 두 개의 기준으로 묶기”# 성별 + 객실등급 두 기준으로 묶기result = df.groupby(["sex", "pclass"])["survived"].mean()print(result)sex pclassfemale 1 0.968085 2 0.921053 3 0.500000male 1 0.368852 2 0.157407 3 0.135447Name: survived, dtype: float64위 코드 2번째 줄 groupby(["sex", "pclass"])가 두 개의 기준으로 동시에 집단을 나누는 코드입니다. 결과에서 1등석 여성은 96.8%, 3등석 남성은 13.5% 생존이라는 사실을 바로 읽을 수 있습니다.
v3: 여러 통계를 한 번에
섹션 제목: “v3: 여러 통계를 한 번에”# agg로 여러 통계 함수를 동시에 적용result = df.groupby("pclass")["fare"].agg(["mean", "min", "max", "count"])print(result) mean min max countpclass1 84.154687 0.0000 512.3292 2162 20.662183 0.0000 73.5000 1843 13.675550 0.0000 69.5500 4911등석 평균 요금은 $84, 3등석은 $13로 6배 이상 차이가 납니다.
🔧 실습 활동: 타이타닉 데이터 완전 정복앞서 배운 네 가지 개념(DataFrame 구조 · 첫인상 파악 · 결측치/필터링 · groupby)을 하나의 스크립트로 연결해 보는 실습입니다. Jupyter Notebook이나 Google Colab에서 셀 단위로 복사해 실행하세요.
섹션 제목: “🔧 실습 활동: 타이타닉 데이터 완전 정복앞서 배운 네 가지 개념(DataFrame 구조 · 첫인상 파악 · 결측치/필터링 · groupby)을 하나의 스크립트로 연결해 보는 실습입니다. Jupyter Notebook이나 Google Colab에서 셀 단위로 복사해 실행하세요.”Step 1: 환경 준비와 데이터 로드
섹션 제목: “Step 1: 환경 준비와 데이터 로드”import pandas as pdimport seaborn as sns
titanic = sns.load_dataset("titanic")print(f"데이터 크기: {titanic.shape}") # (행 수, 열 수)print(f"열 목록: {list(titanic.columns)}")데이터 크기: (891, 15)열 목록: ['survived', 'pclass', 'sex', 'age', 'sibsp', 'parch', 'fare', 'embarked', 'class', 'who', 'adult_male', 'deck', 'embark_town', 'alive', 'alone']Step 2: 분석용 데이터 정리
섹션 제목: “Step 2: 분석용 데이터 정리”# 분석에 필요한 열만 선택 + 원본 보호를 위해 copycols = ["survived", "pclass", "sex", "age", "fare", "embarked"]df = titanic[cols].copy()
# 결측치 처리 (age는 중앙값, embarked는 최빈값)df["age"] = df["age"].fillna(df["age"].median())df["embarked"] = df["embarked"].fillna(df["embarked"].mode()[0])
print("결측치 처리 후:", df.isnull().sum().sum(), "개 남음")결측치 처리 후: 0 개 남음Step 3: 나이대(연령 그룹) 열 추가하기
섹션 제목: “Step 3: 나이대(연령 그룹) 열 추가하기”숫자 나이를 그대로 쓰는 대신 “어린이/청년/중년/노년”으로 묶으면 집단 비교가 쉬워집니다. Pandas의 pd.cut이 이 일을 해줍니다.
# 구간 나누기: 0-12(어린이), 13-19(청소년), 20-39(청년), 40-59(중년), 60+(노년)bins = [0, 12, 19, 39, 59, 100]labels = ["어린이", "청소년", "청년", "중년", "노년"]df["age_group"] = pd.cut(df["age"], bins=bins, labels=labels)
print(df[["age", "age_group"]].head(6)) age age_group0 22.0 청년1 38.0 청년2 26.0 청년3 35.0 청년4 35.0 청년5 28.0 청년위 코드 4번째 줄 pd.cut이 연속 숫자를 범주형 데이터로 변환하는 도구입니다.
Step 4: 슬라이더로 조건 바꿔가며 탐색하기 (ipywidgets)
섹션 제목: “Step 4: 슬라이더로 조건 바꿔가며 탐색하기 (ipywidgets)”Jupyter 환경에서 슬라이더를 움직여 “몇 살 이상 승객의 생존율이 몇 %인가”를 실시간으로 확인해 봅니다.
# Colab/Jupyter 전용. 일반 파이썬 스크립트에서는 동작하지 않습니다from ipywidgets import interact, IntSlider
@interact(min_age=IntSlider(min=0, max=80, step=5, value=20))def survival_by_age(min_age): subset = df[df["age"] >= min_age] rate = subset["survived"].mean() print(f"{min_age}세 이상 승객 {len(subset)}명, 생존율 {rate:.2%}")슬라이더를 움직이면 아래처럼 결과가 실시간으로 바뀝니다.
20세 이상 승객 693명, 생존율 36.22%40세 이상 승객 230명, 생존율 38.26%60세 이상 승객 26명, 생존율 26.92%Step 5: 종합 — 모든 집단의 생존율 표
섹션 제목: “Step 5: 종합 — 모든 집단의 생존율 표”# 성별, 객실등급, 연령대를 모두 묶어 생존율 집계summary = df.groupby(["sex", "pclass", "age_group"], observed=True)["survived"].agg(["mean", "count"])summary.columns = ["생존율", "인원수"]# 인원수 5명 이상인 집단만 신뢰성 있게 확인summary = summary[summary["인원수"] >= 5].sort_values("생존율", ascending=False)print(summary.head(10)) 생존율 인원수sex pclass age_groupfemale 2 어린이 1.000000 10 1 청년 0.978261 46 1 중년 0.967742 31 2 청년 0.918919 37 1 노년 1.000000 5 3 어린이 0.542857 35 2 중년 0.888889 18...위 결과만 봐도 “1-2등석에 탑승한 여성 어린이는 거의 100% 생존”이라는 패턴이 드러납니다.
🎯 미니 챌린지: 생존율이 가장 높은 집단 찾기
섹션 제목: “🎯 미니 챌린지: 생존율이 가장 높은 집단 찾기”개인 과제 · 10분
타이타닉 승객을 성별, 객실등급, 연령대 세 가지 기준으로 묶었을 때, 생존율이 가장 높은 집단과 가장 낮은 집단을 찾아내고, 그 차이가 의미하는 바를 한 문장으로 적어 봅니다.
미션 조건
섹션 제목: “미션 조건”- 인원수가 5명 이상인 집단만 대상으로 한다 (인원이 적으면 우연의 영향이 큼)
- 생존율을 소수점 둘째 자리까지 표시
- 결과를 해석하는 문장을 1-2줄 작성
🔎 힌트
groupby(["sex", "pclass", "age_group"], observed=True)["survived"].agg(["mean", "count"])로 요약 테이블 생성summary[summary["인원수"] >= 5]로 필터링sort_values("mean", ascending=False)정렬 후.head(1),.tail(1)
✅ 정답 코드
summary = df.groupby(["sex", "pclass", "age_group"], observed=True)["survived"].agg(["mean", "count"])summary = summary[summary["count"] >= 5].sort_values("mean", ascending=False)
print("🏆 생존율 최고 집단")print(summary.head(1))print("\n💀 생존율 최저 집단")print(summary.tail(1))해석 예시: “1-2등석 여성은 거의 모두 살아남은 반면, 3등석 남성 중년 이상은 대부분 사망했다. 1912년 당시 ‘여성과 어린이 먼저’라는 구조 원칙과 객실 위치(1등석은 갑판 근처, 3등석은 배 아래쪽)의 영향이 함께 작용한 결과로 보인다.”
🧩 연습 문제
섹션 제목: “🧩 연습 문제”기초 — 예제 변형
섹션 제목: “기초 — 예제 변형”타이타닉 데이터에서 남성 승객의 평균 요금과 여성 승객의 평균 요금을 각각 출력하세요.
힌트
groupby("sex")로 묶고 ["fare"].mean()을 호출합니다.
정답
print(df.groupby("sex")["fare"].mean())sexfemale 44.479818male 25.523893Name: fare, dtype: float64여성 승객의 평균 요금이 거의 2배 높습니다. 이는 고급 객실에 여성이 많이 탑승했음을 시사합니다.
응용 — 새 상황 적용
섹션 제목: “응용 — 새 상황 적용”탑승 항구(embarked)별로 생존율을 계산하고, 어느 항구에서 탑승한 승객의 생존율이 가장 높은지 찾으세요.
힌트
groupby("embarked")["survived"].mean() 후 sort_values(ascending=False).
정답
rate = df.groupby("embarked")["survived"].mean().sort_values(ascending=False)print(rate)embarkedC 0.553571Q 0.389610S 0.339009Name: survived, dtype: float64셰르부르(C)에서 탑승한 승객 생존율이 55%로 가장 높습니다. 이 항구에서 1등석 승객이 많이 탑승했기 때문입니다.
도전 — 결합/설계
섹션 제목: “도전 — 결합/설계”“혼자 탑승한 승객”과 “가족과 함께 탑승한 승객”의 생존율을 비교하세요. (힌트: sibsp는 형제/배우자 수, parch는 부모/자녀 수입니다.)
힌트
sibsp + parch == 0이면 혼자 탑승입니다. 새 열 alone_flag를 만들고 groupby를 적용하세요.
정답
titanic_full = sns.load_dataset("titanic")# 가족 수 = 형제/배우자 + 부모/자녀titanic_full["family_size"] = titanic_full["sibsp"] + titanic_full["parch"]titanic_full["alone_flag"] = titanic_full["family_size"] == 0
result = titanic_full.groupby("alone_flag")["survived"].agg(["mean", "count"])result.index = ["가족 동반", "혼자 탑승"]print(result) mean count가족 동반 0.505650 354혼자 탑승 0.303538 537가족과 함께 탑승한 승객 생존율(50.6%)이 혼자 탑승(30.4%)보다 20%p 높습니다. 위기 상황에서 가족 단위로 구조된 경향을 보여줍니다.
🤔 토론 / 탐구 활동
섹션 제목: “🤔 토론 / 탐구 활동”모둠(4인) · 10분 · 미니 챌린지 결과를 공유한 후 아래 질문에 대해 의견을 나누세요.
- 1등석과 3등석의 생존율 차이가 이렇게 큰 이유는 단순히 “요금 차이”로 설명할 수 있을까요? 다른 요인은 무엇이 있을까요?
age열의 결측치 177개를 중앙값으로 채웠습니다. 만약 평균값으로 채웠다면 결과가 어떻게 달라졌을까요? 둘 중 무엇이 더 “공정한” 선택일까요?- 데이터 분석가가 결측치를 무작정 버리면(
dropna) 왜 위험한 결정일까요? 실제로 이번 실습에서 무작정 버렸다면 어떤 결론이 나왔을지 추측해 보세요.
💡 실무에서는 이렇게 씁니다
섹션 제목: “💡 실무에서는 이렇게 씁니다”오늘 배운 Pandas 기능은 AI/머신러닝 프로젝트의 데이터 전처리 단계에서 80% 이상을 차지합니다.
| 배운 기능 | 실무 활용 예시 |
|---|---|
read_csv, head, info | 고객 이탈 예측 프로젝트에서 수십만 건의 로그 데이터 첫 검토 |
fillna, dropna | 의료 데이터에서 빠진 검사 항목을 중앙값으로 채워 모델 학습 가능하게 만들기 |
| 불리언 인덱싱 | ”최근 7일 이내 구매 고객”만 추출해 타겟 마케팅 목록 생성 |
groupby + agg | 쇼핑몰에서 카테고리별·지역별 매출 요약 대시보드 생성 |
pd.cut (구간 분할) | 신용 점수를 “저/중/고” 등급으로 변환 후 모델 입력 피처로 사용 |
특히 groupby는 SQL의 GROUP BY와 동일한 개념이라 데이터베이스 경험이 있는 사람에게는 익숙합니다. 머신러닝 엔지니어가 모델을 학습하기 전에 수행하는 EDA(Exploratory Data Analysis, 탐색적 데이터 분석)의 핵심 도구입니다.
🔗 참고 자료
섹션 제목: “🔗 참고 자료”📎 Pandas 공식 10분 튜토리얼
섹션 제목: “📎 Pandas 공식 10분 튜토리얼”핵심 기능을 한 번에 훑는 공식 가이드. 오늘 배운 내용의 확장판 →
📎 Kaggle 타이타닉 경진대회
섹션 제목: “📎 Kaggle 타이타닉 경진대회”같은 데이터로 생존 예측 모델을 만들어 점수를 겨루는 입문자 경진대회 →
📎 Seaborn 데이터셋 리스트
섹션 제목: “📎 Seaborn 데이터셋 리스트”타이타닉 외에도 iris, tips, penguins 등 실습용 데이터셋 다수 제공 →
📝 형성 평가
섹션 제목: “📝 형성 평가”객관식 1. DataFrame의 한 열만 뽑았을 때 반환되는 자료구조는 무엇입니까?
① list ② numpy.ndarray ③ Series ④ dict
정답 확인
정답: ③
df["열이름"]으로 꺼낸 한 열은 값과 인덱스를 함께 가진 1차원 자료구조인 Series입니다. DataFrame은 여러 Series가 열로 결합된 형태입니다.
객관식 2. 다음 중 df[(df["age"] >= 20) & (df["sex"] == "female")]의 실행 결과로 올바른 것은?
① 20세 이상 OR 여성인 행만 추출
② 20세 이상 AND 여성인 행만 추출
③ 에러 발생 — and 키워드를 써야 함
④ 20세 이상인 행의 sex 열만 추출
정답 확인
정답: ②
&는 두 조건의 AND를 의미하며, 각 조건은 괄호로 감싸야 합니다. 파이썬의 and 키워드는 Series 연산에 사용할 수 없어 &(비트 연산자)를 씁니다.
객관식 3. 결측치 처리 전략으로 가장 적절하지 않은 것은?
① 결측이 2-3개뿐이면 dropna로 그 행만 제거
② 나이 같은 숫자 열은 중앙값으로 fillna
③ 범주형 열(항구명 등)은 최빈값으로 fillna
④ 결측치가 많아도 dropna()로 전체 제거해 깔끔하게 만든다
정답 확인
정답: ④
실습에서 본 것처럼 deck 열 때문에 dropna()를 그냥 호출하면 891행이 182행으로 줄어 80%의 데이터가 사라집니다. 결측이 많은 열을 제외하거나 열별로 다른 전략을 쓰는 것이 안전합니다.
서술형 1. 타이타닉 데이터에서 “1등석 여성의 생존율 96.8%” vs “3등석 남성의 생존율 13.5%“라는 결과가 나왔습니다. 이 차이를 단순히 “운이 좋았다”로 설명할 수 있을까요? 데이터 분석가의 관점에서 어떤 요인들을 더 조사해야 할지 3가지 이상 제시하세요.
예시 답안
단순한 운의 차이로 보기 어렵습니다. 추가로 조사해야 할 요인은 다음과 같습니다.
- 객실 위치와 구조: 1등석은 주로 상갑판에, 3등석은 배 하부에 위치했습니다. 물이 차오른 순서와 구명보트까지의 동선이 달랐습니다.
- 구조 우선순위 규칙: “여성과 어린이 먼저” 원칙이 실제 어떻게 적용되었는지, 등급별로 다르게 집행되었는지 확인이 필요합니다.
- 정보 접근성: 경고 방송이 영어로 이루어졌기 때문에 이민자가 많았던 3등석은 정보 전달이 늦었을 가능성이 있습니다.
embarked와 교차 분석할 수 있습니다. - 표본 크기: 1등석 여성은 94명이고 3등석 남성은 347명입니다. 두 집단의 인원수 차이도 통계 해석에 영향을 줍니다.
자기점검 체크리스트
- DataFrame에서 열을 선택하고, 불리언 인덱싱으로 조건에 맞는 행을 추출할 수 있다
-
isnull().sum()으로 결측치를 탐지하고,fillna/dropna를 상황에 맞게 선택할 수 있다 - 타이타닉 데이터를 불러와
head,info,describe로 첫인상을 파악할 수 있다 -
groupby로 두 개 이상의 기준을 묶어 집단별 통계를 계산할 수 있다 - “데이터로 답을 찾을 수 있는가?”라는 오늘의 질문에 내 말로 답할 수 있다
💭 성찰
섹션 제목: “💭 성찰”아래 질문에 자신만의 답을 적어 보세요. 정답이 아니라 자기 언어로 정리하는 과정이 중요합니다.
- 이번 차시에서 가장 “아하!” 했던 순간은 언제였나요? (예: 괄호 빼먹고 에러가 났을 때, groupby 결과가 한눈에 보였을 때)
- 결측치를 중앙값으로 채우는 것과 평균으로 채우는 것 중 어느 쪽이 더 적절한지, 오늘 이후 여러분의 기준은 무엇인가요?
- “데이터가 곧 증거”라는 말에 대한 여러분의 생각은 어떻게 바뀌었나요? 데이터 분석에도 해석의 여지가 있다는 점을 어떻게 받아들이고 있습니까?
🔗 다음 차시 미리보기
섹션 제목: “🔗 다음 차시 미리보기”3차시에서는 오늘 정리한 데이터를 그림으로 바꾸는 작업을 시작합니다. Matplotlib과 Seaborn으로 막대그래프·히스토그램·상관관계 히트맵을 그리며, “표로는 안 보이던 패턴이 그래프로는 한눈에 보인다”는 경험을 하게 됩니다. 타이타닉 생존율을 그래프로 표현하면 어떤 이야기가 새로 보일까요?