콘텐츠로 이동

3차시: 데이터 시각화 - Matplotlib/Seaborn으로 패턴 발견하기

⏰ 80분 · Matplotlib · Seaborn · 상관관계 · 인터랙티브 위젯 · 난이도 ●●●○○

학습목표: 데이터 유형에 맞는 시각화를 선택해 그리고, 상관관계와 분포를 해석하여 특성 간 관계를 설명할 수 있습니다.

오늘의 질문: “숫자 1,000개가 나열된 표와, 그 숫자로 그린 그림 한 장. 여러분은 어느 쪽에서 먼저 패턴을 발견하실 것 같습니까?”


분포는 히스토그램, 관계는 산점도, 범주는 박스플롯, 전체는 히트맵입니다.

숫자 하나(상관계수)와 그림 하나(산점도)를 짝지어 관계의 강도를 읽어냅니다.

ipywidgets로 x·y축을 바꿔가며 타이타닉 데이터의 생존 패턴을 찾습니다.

“생존과 가장 관련 깊은 특성 3가지”를 시각적 근거와 함께 발표합니다.


숫자만 본 표와 그림으로 본 분포를 비교합니다. Anscombe의 네 데이터셋을 통해 “평균·분산이 같아도 모양은 다르다”는 사실을 확인합니다.

히스토그램, 산점도, 박스플롯, 히트맵을 타이타닉 데이터로 하나씩 그립니다. 각 그림이 답하는 질문이 다르다는 것을 익힙니다.

ipywidgets Dropdown으로 변수 조합을 바꾸면서 산점도와 히트맵을 실시간 생성합니다. 상관계수가 높은 변수 쌍을 기록합니다.

타이타닉 데이터에서 생존과 관련된 특성 3가지를 그림과 함께 정리합니다. 모둠(4인)별로 1가지씩 발표하여 공유합니다.

핵심 개념 퀴즈와 자기점검으로 마무리합니다. 딥러닝 입력 데이터를 “보는 눈”을 정리합니다.


개념 1. 🖼️ 왜 그리는가 — 숫자만으로는 보이지 않는 것

섹션 제목: “개념 1. 🖼️ 왜 그리는가 — 숫자만으로는 보이지 않는 것”

평균, 분산, 상관계수가 완전히 동일한 네 개의 데이터가 있다고 생각해 보십시오. 숫자로만 요약하면 이 넷은 구별이 불가능합니다. 그러나 그림으로 그리면 하나는 직선, 하나는 곡선, 하나는 이상치 하나에 끌려간 직선, 하나는 한 점이 좌우를 흔드는 형태로 전혀 다른 모양이 됩니다. 이것이 Anscombe의 사중주입니다.

데이터 시각화의 역할은 장식이 아니라 탐색(exploration)입니다. 모델에 넣기 전에 데이터가 어떤 모양인지 눈으로 확인하지 않으면, 평균 뒤에 숨은 이상치·왜곡·패턴을 놓치게 됩니다.

# 실행 환경: Python 3.10+, 필요 패키지: matplotlib, seaborn, pandas
import seaborn as sns
import matplotlib.pyplot as plt
anscombe = sns.load_dataset("anscombe") # 4개 세트가 합쳐진 내장 데이터
# 4개 세트의 요약 통계를 비교
print(anscombe.groupby("dataset")[["x", "y"]].agg(["mean", "std"]).round(2))
x y
mean std mean std
dataset
I 9.0 3.32 7.50 2.03
II 9.0 3.32 7.50 2.03
III 9.0 3.32 7.50 2.03
IV 9.0 3.32 7.50 2.03

통계값이 사실상 동일합니다. 그러나 그림을 그리면 이야기가 달라집니다.

# 같은 통계, 다른 모양: FacetGrid로 네 세트를 나란히 비교
sns.lmplot(data=anscombe, x="x", y="y", col="dataset",
col_wrap=2, height=3, ci=None)
plt.show()

네 개의 산점도는 서로 완전히 다른 형태로 나타납니다. 같은 평균 뒤에 다른 세계가 있습니다.


개념 2. 📊 시각화 4종 세트 — 질문에 맞는 그림 고르기

섹션 제목: “개념 2. 📊 시각화 4종 세트 — 질문에 맞는 그림 고르기”

각 그림은 서로 다른 질문에 답합니다. 질문이 “한 변수가 어떻게 퍼져 있는가?”라면 히스토그램, “두 변수가 함께 움직이는가?”라면 산점도입니다. 도구를 아는 것보다 질문에 맞는 도구를 고르는 것이 더 중요합니다.

그림답하는 질문쓰는 변수Seaborn 함수
히스토그램값이 어떻게 퍼져 있는가?수치형 1개sns.histplot
산점도두 변수가 함께 움직이는가?수치형 2개sns.scatterplot
박스플롯그룹별로 값이 다른가?범주형+수치형sns.boxplot
히트맵전체 변수들은 서로 어떤 관계인가?수치형 여러 개sns.heatmap
import seaborn as sns
import matplotlib.pyplot as plt
titanic = sns.load_dataset("titanic") # 타이타닉 승객 데이터 내장
# 나이 분포: 한 변수가 어떻게 퍼져 있는지가 궁금할 때
sns.histplot(data=titanic, x="age", bins=20)
plt.title("승객 나이 분포")
plt.show()
# 출력: 20~30대에 봉우리가 있는 히스토그램
# (실제로 실행하면 그래프 창이 뜹니다)

봉우리가 어디에 있는지, 꼬리가 어느 쪽으로 길게 뻗었는지가 한눈에 보입니다.

v2. 박스플롯 — 그룹 간 비교 추가

섹션 제목: “v2. 박스플롯 — 그룹 간 비교 추가”
# 생존 여부(0/1)에 따라 나이 분포가 다른지 궁금할 때
sns.boxplot(data=titanic, x="survived", y="age")
plt.title("생존 여부에 따른 나이 분포")
plt.xlabel("생존 (0=사망, 1=생존)")
plt.show()

박스플롯은 중앙값(박스 안 선), 사분위수(박스 위아래), 이상치(점)를 한번에 보여줍니다. 두 그룹의 박스 위치가 많이 겹치면 “나이로는 생존을 구분하기 어렵다”는 힌트가 됩니다.

# 나이와 운임(요금)은 관계가 있는가?
sns.scatterplot(data=titanic, x="age", y="fare", hue="survived")
plt.title("나이 vs 운임 (색: 생존 여부)")
plt.show()

위 코드의 hue="survived" 파라미터가 바로 세 번째 차원을 색으로 표현하는 기법입니다. 점 하나가 승객 한 명이고, 색이 곧 생존 여부입니다.

# 수치형 변수 간 상관계수를 한 번에
num_cols = ["survived", "age", "fare", "pclass", "sibsp", "parch"]
corr = titanic[num_cols].corr() # <- 여기가 [상관계수 행렬] 계산
sns.heatmap(corr, annot=True, cmap="coolwarm", center=0, fmt=".2f")
plt.title("수치형 변수 상관관계 히트맵")
plt.show()

위 코드의 3번째 줄 titanic[num_cols].corr()이 바로 상관계수 행렬입니다. 히트맵은 이 행렬을 색으로 칠한 것이며, 빨간색은 양의 상관, 파란색은 음의 상관을 나타냅니다.


개념 3. 🔗 상관관계 — 숫자 하나로 관계 요약하기

섹션 제목: “개념 3. 🔗 상관관계 — 숫자 하나로 관계 요약하기”

두 변수가 함께 움직이는 정도를 하나의 숫자로 표현한 것이 피어슨 상관계수입니다. 값은 -1에서 +1 사이에 있습니다.

$ r = \dfrac{\sum_{i=1}^{n}(x_i - \bar{x})(y_i - \bar{y})}{\sqrt{\sum(x_i-\bar{x})^2}\sqrt{\sum(y_i-\bar{y})^2}} $

여기서 $x_i, y_i$는 개별 데이터, $\bar{x}, \bar{y}$는 평균입니다. 수식을 외울 필요는 없습니다. 다음 해석 기준만 기억하면 충분합니다.

| $|r|$ 값 | 관계 강도 | 산점도 모양 | |---|---|---| | 0.0 - 0.2 | 거의 없음 | 구름처럼 흩어짐 | | 0.2 - 0.5 | 약함 | 희미한 경향 | | 0.5 - 0.8 | 중간 | 뚜렷한 경향 | | 0.8 - 1.0 | 강함 | 선에 가까움 |

부호가 +이면 한쪽이 커질 때 다른 쪽도 커지고, -이면 반대로 움직입니다.

flowchart LR
  A[데이터<br/>두 변수] --> B[산점도로<br/>모양 확인]
  A --> C[상관계수<br/>숫자 계산]
  B --> D[관계 해석]
  C --> D
  D --> E[특성 선택<br/>모델링 힌트]

중요한 주의사항: 상관관계는 선형 관계만 잡아냅니다. U자 형태의 관계는 상관계수가 0에 가까워도 실제로는 강한 관계일 수 있습니다. 그래서 숫자(상관계수)와 그림(산점도)을 항상 함께 봅니다.


개념 4. 🎛️ 인터랙티브 위젯 — 변수를 바꿔가며 탐색하기

섹션 제목: “개념 4. 🎛️ 인터랙티브 위젯 — 변수를 바꿔가며 탐색하기”

데이터에 변수가 10개라면, 변수 쌍의 조합은 45가지가 됩니다. 이를 하나씩 코드를 고쳐가며 보는 것은 비효율적입니다. ipywidgets는 Jupyter Notebook에서 Dropdown, Slider 같은 UI를 붙여, 변수를 바꿀 때마다 그림이 다시 그려지게 해 줍니다.

핵심 아이디어는 “함수 하나를 만들고, 함수의 인자를 위젯으로 연결”하는 것입니다.

# 실행 환경: Jupyter Notebook, 패키지: ipywidgets
from ipywidgets import interact
import seaborn as sns
import matplotlib.pyplot as plt
titanic = sns.load_dataset("titanic").dropna(subset=["age", "fare"])
num_cols = ["age", "fare", "pclass", "sibsp", "parch"]
def plot_scatter(x_col, y_col): # <- 여기가 [그림 그리는 함수]
sns.scatterplot(data=titanic, x=x_col, y=y_col, hue="survived")
plt.title(f"{x_col} vs {y_col}")
plt.show()
# interact가 Dropdown을 자동 생성
interact(plot_scatter, x_col=num_cols, y_col=num_cols)
# Jupyter 셀 아래에 두 개의 Dropdown이 나타남
# x_col: [age ▼] y_col: [fare ▼]
# 선택을 바꿀 때마다 산점도가 즉시 갱신됨

위 코드의 마지막 줄 interact(plot_scatter, x_col=num_cols, y_col=num_cols)이 핵심입니다. interact는 함수의 인자 이름(x_col, y_col)을 읽어 각각에 대한 위젯을 자동으로 만들어 붙입니다.


Jupyter Notebook을 열고 다음 셀을 가장 먼저 실행합니다.

# 실행 환경: Python 3.10+, Jupyter Notebook
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from ipywidgets import interact
# 한글 폰트 (Windows 기준, Mac은 'AppleGothic')
plt.rcParams['font.family'] = 'Malgun Gothic'
plt.rcParams['axes.unicode_minus'] = False
titanic = sns.load_dataset("titanic")
print(titanic.shape)
print(titanic.columns.tolist())
(891, 15)
['survived', 'pclass', 'sex', 'age', 'sibsp', 'parch', 'fare', 'embarked',
'class', 'who', 'adult_male', 'deck', 'embark_town', 'alive', 'alone']

승객 891명, 컬럼 15개입니다.

실습 1. 범주형 변수로 생존 탐색

섹션 제목: “실습 1. 범주형 변수로 생존 탐색”
# 성별(sex)에 따른 생존율 — countplot은 범주별 개수를 센 그림
sns.countplot(data=titanic, x="sex", hue="survived")
plt.title("성별 × 생존")
plt.show()

질문: 남성과 여성 중 어느 쪽의 생존율이 더 높습니까? 막대 높이 비율을 보고 답해 보십시오.

실습 2. 수치형 × 범주형 — 박스플롯

섹션 제목: “실습 2. 수치형 × 범주형 — 박스플롯”
# 객실 등급별 운임 분포
sns.boxplot(data=titanic, x="pclass", y="fare")
plt.ylim(0, 300) # 이상치 일부 잘라 가독성 확보
plt.title("객실 등급별 운임")
plt.show()

실습 3. 인터랙티브 산점도 + 상관계수

섹션 제목: “실습 3. 인터랙티브 산점도 + 상관계수”
titanic_num = titanic.dropna(subset=["age", "fare"])
num_cols = ["age", "fare", "pclass", "sibsp", "parch", "survived"]
def explore(x_col, y_col):
r = titanic_num[x_col].corr(titanic_num[y_col]) # <- 여기가 [상관계수]
sns.scatterplot(data=titanic_num, x=x_col, y=y_col,
hue="survived", alpha=0.6)
plt.title(f"{x_col} vs {y_col} (r = {r:.2f})")
plt.show()
interact(explore, x_col=num_cols, y_col=num_cols)

Dropdown을 바꿔가며 |r|이 0.3 이상인 변수 쌍을 찾아 아래 표에 기록합니다.

변수 X변수 Y상관계수 r관계 방향(+/-)

다음 코드를 실행하면 어떤 에러가 날 것 같습니까?

# 일부러 틀린 코드
corr = titanic.corr()
sns.heatmap(corr, annot=True)

에러 메시지:

ValueError: could not convert string to float: 'male'

원인 분석: titanic에는 sex, embarked 같은 문자열 컬럼이 있습니다. corr()는 숫자 컬럼에만 작동합니다. 최신 pandas는 numeric_only 파라미터로 해결합니다.

수정 코드:

corr = titanic.corr(numeric_only=True) # <- 숫자 컬럼만 골라서 상관
sns.heatmap(corr, annot=True, cmap="coolwarm", center=0, fmt=".2f")
plt.show()

활동 유형: 모둠(4인) 활동 시간: 20분 (탐색 12분 + 발표 준비 5분 + 발표 3분)

지시문: 타이타닉 데이터에서 생존(survived)과 가장 관련이 깊은 특성 3가지를 찾아 시각적 근거와 함께 발표하십시오. 각 모둠원은 특성 하나씩 담당합니다.

탐색 절차:

  1. 수치형 변수 후보(age, fare, pclass, sibsp, parch)에 대해 survived와의 상관계수를 확인합니다.
  2. 범주형 변수 후보(sex, class, embarked, who)는 countplot 또는 박스플롯으로 비교합니다.
  3. 가장 설득력 있는 3개를 고르고, 각각 그림 1장 + 한 문장 설명을 준비합니다.

발표 양식:

선택한 특성그림 종류관찰된 패턴 (한 문장)

생각할 거리:

  • 상관계수가 0에 가까운데도 생존과 관계 있는 변수가 있을 수 있습니까? 이유는 무엇입니까?
  • pclass(객실 등급)와 fare(운임)는서로 어떤 관계일 것 같습니까? 둘 다 모델에 넣으면 어떤 문제가 생길 수 있습니까?
  • 어린아이(who = "child")의 생존율이 유난히 높다면, 이는 어떤 시대적 맥락을 반영한 것입니까?

타이타닉 데이터에서 운임(fare) 의 분포를 히스토그램으로 그리되, 생존 여부에 따라 색을 다르게 표시하십시오.

힌트

sns.histplothue 파라미터를 쓰면 범주별로 색이 나뉩니다.

정답 코드
import seaborn as sns
import matplotlib.pyplot as plt
titanic = sns.load_dataset("titanic")
sns.histplot(data=titanic, x="fare", hue="survived", bins=30, kde=True)
plt.title("운임 분포 (생존 여부별)")
plt.xlim(0, 300)
plt.show()

운임이 낮은 구간에 사망자가 더 많이 몰려 있는 형태가 관찰됩니다.

🟡 응용: 붓꽃(iris) 데이터로 전이 적용

섹션 제목: “🟡 응용: 붓꽃(iris) 데이터로 전이 적용”

Seaborn 내장 iris 데이터에서 품종(species)별 꽃잎 길이(petal_length) 분포를 박스플롯으로 그리고, 어느 품종이 가장 구분하기 쉬운지 한 문장으로 적으십시오.

힌트

sns.load_dataset("iris")로 데이터를 불러오고, x="species", y="petal_length"로 설정합니다.

정답 코드
import seaborn as sns
import matplotlib.pyplot as plt
iris = sns.load_dataset("iris")
sns.boxplot(data=iris, x="species", y="petal_length")
plt.title("품종별 꽃잎 길이")
plt.show()

setosa의 박스가 다른 두 품종과 겹치지 않고 아래쪽에 완전히 분리되어 있어, 꽃잎 길이만으로도 setosa는 쉽게 구분할 수 있습니다.

🔴 도전: pairplot과 상관 히트맵 결합

섹션 제목: “🔴 도전: pairplot과 상관 히트맵 결합”

iris 데이터에 대해 pairplotheatmap한 번에 출력하고, 가장 강한 상관관계를 가진 특성 쌍을 찾으십시오.

힌트

sns.pairplot(iris, hue="species")은 모든 수치형 변수 쌍의 산점도를 자동 생성합니다. 히트맵은 iris.corr(numeric_only=True)로 계산 후 그립니다.

정답 코드
import seaborn as sns
import matplotlib.pyplot as plt
iris = sns.load_dataset("iris")
# 1. 모든 변수 쌍을 산점도로
sns.pairplot(iris, hue="species")
plt.show()
# 2. 상관계수 히트맵
corr = iris.corr(numeric_only=True)
sns.heatmap(corr, annot=True, cmap="coolwarm", center=0, fmt=".2f")
plt.title("iris 상관관계")
plt.show()

petal_lengthpetal_width의 상관계수가 약 0.96으로 가장 강합니다. 꽃잎은 길이가 길면 너비도 넓어지는 관계입니다.


오늘 배운 시각화는 실제 AI/데이터 분석 현장에서 거의 모든 프로젝트 초반에 등장합니다.

상황쓰는 시각화목적
신규 데이터셋 받은 첫날히스토그램 + 박스플롯이상치·결측치·분포 모양 확인
모델 입력 특성 고르기상관 히트맵타겟과 관련 높은 특성 선별, 중복 특성 제거
A/B 테스트 결과 비교박스플롯 + 바이올린플롯두 그룹의 분포 차이 확인
딥러닝 학습 로그 분석꺾은선 그래프loss·accuracy 곡선으로 과적합 감지

특히 탐색적 데이터 분석(EDA) 단계에서 “숫자를 읽지 말고 모양을 읽어라”는 원칙이 널리 쓰입니다. Kaggle 상위 입상자들의 노트북을 보면, 모델 코드보다 시각화 코드가 훨씬 많은 경우가 대부분입니다.



객관식 1. 다음 중 두 수치형 변수의 관계를 살펴볼 때 가장 적합한 그림은 무엇입니까?

① 히스토그램 ② 산점도 ③ 박스플롯 ④ 막대그래프

정답 확인

정답: ② 산점도는 두 수치형 변수를 x축·y축에 배치하여 함께 움직이는 경향(상관관계)을 시각적으로 확인하기에 가장 적합합니다. 히스토그램은 한 변수의 분포, 박스플롯은 범주별 수치 비교, 막대그래프는 범주별 개수/합계에 씁니다.

객관식 2. 상관계수 $r = -0.82$ 라는 값을 해석한 것으로 가장 올바른 것은 무엇입니까?

① 두 변수는 관계가 거의 없다 ② 두 변수는 약한 양의 관계이다 ③ 두 변수는 강한 음의 관계이다 ④ 두 변수는 인과관계가 있다

정답 확인

정답: ③ $|r| = 0.82$는 강한 관계이며, 부호가 음수이므로 한쪽이 커질 때 다른 쪽은 작아지는 음의 상관입니다. 또한 상관관계는 인과관계를 의미하지 않으므로 ④는 언제나 부적절한 해석입니다.

객관식 3. titanic.corr()을 실행했더니 ValueError: could not convert string to float: 'male' 에러가 발생했습니다. 가장 적절한 해결 방법은 무엇입니까?

① 데이터를 다시 다운로드한다 ② titanic.corr(numeric_only=True)로 수정한다 ③ pandas를 삭제하고 재설치한다 ④ Jupyter를 재시작한다

정답 확인

정답: ② corr()는 수치형 컬럼에만 작동합니다. numeric_only=True 파라미터를 추가하면 문자열 컬럼(sex, embarked 등)을 자동으로 제외하고 계산합니다.

서술형 1. 평균과 분산이 완전히 같은 두 데이터셋이라도 “시각화해야 한다”고 말하는 이유를, Anscombe의 사중주를 근거로 3문장 이내로 설명하십시오.

예시 답안

Anscombe의 사중주는 평균·분산·상관계수가 모두 같은 네 개의 데이터셋이지만, 그림으로 그리면 직선·곡선·이상치가 끌어당긴 선 등 전혀 다른 모양이 나타납니다. 요약 통계만으로는 데이터의 실제 구조(비선형성, 이상치)를 감지할 수 없습니다. 따라서 분석과 모델링 전에 반드시 시각화로 데이터의 모양을 확인해야 잘못된 해석을 피할 수 있습니다.

자기점검 체크리스트

  • 분포·관계·범주·전체 비교 상황에 맞는 그림 4종(히스토그램·산점도·박스플롯·히트맵)을 선택할 수 있다
  • 상관계수의 부호와 절댓값을 보고 두 변수의 관계 강도와 방향을 설명할 수 있다
  • ipywidgets interact로 변수를 바꿔가며 패턴을 탐색할 수 있다
  • “숫자 1,000개의 표와 그림 한 장 중 어느 쪽이 먼저 패턴을 드러내는가”에 자신의 답을 말할 수 있다

아래 질문에 자신의 말로 답을 적어 보십시오.

  • 오늘 그린 그림 중 “숫자만 봤을 때는 몰랐는데 그림으로 보니 보였다” 는 순간이 있었습니까? 어떤 변수였습니까?
  • 상관계수가 높지 않은데도 생존과 관련이 있어 보였던 변수가 있었습니까? 그 이유를 어떻게 설명할 수 있습니까?
  • 미니 챌린지에서 다른 모둠이 발표한 특성 중, 여러분이 미처 생각하지 못한 것은 무엇이었습니까?

4차시에서는 오늘 눈으로 찾은 패턴을 기계가 스스로 학습하게 만드는 첫걸음을 시작합니다. scikit-learn으로 타이타닉 생존자 예측 모델을 만들어 보며, “사람이 고른 특성 3개”와 “기계가 찾은 중요 특성”이 얼마나 일치하는지 비교합니다.

다음 질문: “오늘 여러분이 고른 특성 3개로 만든 모델과, 모든 특성을 다 넣은 모델. 어느 쪽이 더 정확할 것 같습니까?”