Python 데이터 시각화 기초: Matplotlib과 Seaborn으로 그래프 읽고 그리기

Alchemist·2026년 10월 6일

KT AIVLE

목록 보기
110/130

데이터 분석을 시작하면 숫자부터 잔뜩 보게 된다. 평균, 최댓값, 표준편차 같은 통계량도 중요하지만, 숫자만으로는 데이터가 어떤 방향으로 움직이는지 한눈에 파악하기 어렵다.

그래서 데이터 분석에서는 보통 수치화와 시각화를 함께 사용한다.

  • 수치화: 평균, 최솟값, 최댓값, 표준편차, 사분위수처럼 데이터를 숫자로 요약한다.
  • 시각화: 선 그래프, 히스토그램, 산점도, 박스 플롯처럼 데이터의 형태를 그래프로 확인한다.

Python에서 가장 기본이 되는 시각화 도구는 Matplotlib이고, 이를 기반으로 더 간결한 통계 그래프 API를 제공하는 라이브러리가 Seaborn이다.


Matplotlib과 Seaborn은 어떻게 다를까?

두 라이브러리는 경쟁 관계라기보다 함께 사용하는 도구에 가깝다.

구분MatplotlibSeaborn
특징세부 요소를 직접 제어하기 좋음적은 코드로 통계 그래프를 만들기 쉬움
장점축, 선, 범례, 글자 등을 세밀하게 조절 가능기본 디자인이 보기 좋고 hue 등 데이터 기반 구분이 편리
주 사용처범용 그래프, 세밀한 커스터마이징분포, 범주 비교, 통계 시각화
자주 쓰는 별칭pltsns

실제로는 Seaborn으로 그래프를 그리고 제목이나 크기 같은 부분을 Matplotlib으로 마무리하는 경우가 많다.

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

Jupyter Notebook이나 Google Colab에서 한글이 깨진다면 koreanize-matplotlib 같은 방법을 사용할 수 있다.

# 필요한 환경에서 한 번 설치
# !pip install koreanize-matplotlib -q

import koreanize_matplotlib

경고 메시지를 무조건 숨기는 warnings.filterwarnings("ignore")는 학습 중에는 권장하지 않는다. deprecated API 같은 중요한 경고까지 놓칠 수 있기 때문이다.


예제로 사용할 데이터

간단한 웹 서비스 지표를 만들어 보자.

metrics = pd.DataFrame({
    "day": ["01", "02", "03", "04", "05", "06", "07"],
    "active_users": [1200, 1310, 1280, 1420, 1510, 1470, 1600],
    "orders": [92, 101, 96, 116, 130, 124, 143]
})

events = pd.DataFrame({
    "latency_ms": [110, 130, 125, 180, 160, 145, 210, 190, 135, 155, 170, 240],
    "status": [
        "success", "success", "success", "fail",
        "success", "success", "fail", "success",
        "success", "success", "fail", "success"
    ],
    "endpoint": [
        "/login", "/login", "/search", "/search",
        "/order", "/order", "/order", "/login",
        "/search", "/search", "/login", "/order"
    ],
    "retry_count": [0, 0, 0, 1, 0, 0, 2, 1, 0, 0, 1, 3]
})

Matplotlib의 가장 기본: plt.plot()

선 그래프는 시간에 따라 값이 어떻게 변하는지 볼 때 특히 유용하다.

plt.plot(metrics["day"], metrics["active_users"])

plt.xlabel("날짜")
plt.ylabel("활성 사용자 수")
plt.title("일별 활성 사용자")
plt.show()

plot()에 하나의 값만 전달하면 그 값이 y축이 되고, 데이터의 인덱스가 x축으로 사용된다.

plt.plot(metrics["active_users"])
plt.show()

따라서 x축에 의미 있는 날짜나 순서가 필요하다면 직접 지정하는 편이 안전하다.


그래프를 읽기 쉽게 만드는 기본 요소

제목과 축 이름

plt.plot(metrics["day"], metrics["orders"])

plt.title("일별 주문 수", fontsize=15, pad=10)
plt.xlabel("날짜")
plt.ylabel("주문 수")
plt.show()

그래프를 만들 때 제목과 축 레이블은 장식이 아니라 해석을 위한 정보다.


선 스타일과 마커

Matplotlib은 선 색상, 마커, 선 스타일을 조합할 수 있다.

plt.plot(
    metrics["day"],
    metrics["orders"],
    marker="o",
    linestyle="--",
    linewidth=1.5
)

plt.show()

간단한 축약 표현도 가능하다.

plt.plot(metrics["day"], metrics["orders"], "go--")
plt.show()

다만 실무 코드에서는 color=, marker=, linestyle=처럼 이름을 명시하는 편이 읽기 쉽다.


여러 선을 한 그래프에 겹쳐 보기

plt.show()를 호출하기 전에 plot()을 여러 번 호출하면 같은 영역에 그래프가 겹쳐진다.

plt.plot(
    metrics["day"],
    metrics["active_users"],
    marker="o",
    label="활성 사용자"
)

plt.plot(
    metrics["day"],
    metrics["orders"],
    marker="s",
    label="주문"
)

plt.legend()
plt.grid(axis="y", alpha=0.3)
plt.show()

단, 두 값의 단위나 크기가 지나치게 다르면 같은 y축에서 비교하는 것이 오히려 오해를 만들 수 있다. 그런 경우에는 그래프를 분리하는 편이 낫다.


축 범위와 그래프 크기 조절

축 범위

plt.plot(metrics["day"], metrics["orders"], marker="o")
plt.ylim(80, 150)
plt.show()

xlim()과 ylim()을 사용하면 화면에 보여줄 범위를 조절할 수 있다.

그래프 크기

plt.figure(figsize=(8, 4))
plt.plot(metrics["day"], metrics["orders"], marker="o")
plt.show()

Matplotlib의 기본 크기는 설정값인 rcParams를 따른다.

print(plt.rcParams["figure.figsize"])

현재 Matplotlib 기본 설정은 일반적으로 [6.4, 4.8]인 만큼, 환경에 따라 직접 확인하는 습관이 안전하다.


기준선을 넣으면 해석이 쉬워진다

특정 목표값이나 평균을 그래프에 표시할 때 axhline()과 axvline()이 유용하다.

mean_orders = metrics["orders"].mean()

plt.plot(metrics["day"], metrics["orders"], marker="o")

plt.axhline(
    mean_orders,
    linestyle="--",
    linewidth=1,
    label=f"평균 {mean_orders:.1f}"
)

plt.legend()
plt.show()

평균선처럼 의미 있는 기준이 있을 때만 추가해야 한다. 선이 너무 많아지면 오히려 데이터가 보이지 않는다.


subplot()으로 여러 그래프 나누기

서로 다른 지표를 억지로 한 그래프에 겹치기보다 각각의 영역에 나누어 표현할 수 있다.

plt.figure(figsize=(8, 6))

plt.subplot(2, 1, 1)
plt.plot(metrics["day"], metrics["active_users"], marker="o")
plt.title("활성 사용자")

plt.subplot(2, 1, 2)
plt.plot(metrics["day"], metrics["orders"], marker="o")
plt.title("주문")

plt.tight_layout()
plt.show()

subplot(행, 열, 위치)라고 기억하면 된다.

새 코드를 작성할 때는 객체 지향 방식의 plt.subplots()도 많이 사용한다.

fig, axes = plt.subplots(2, 1, figsize=(8, 6))

axes[0].plot(metrics["day"], metrics["active_users"], marker="o")
axes[0].set_title("활성 사용자")

axes[1].plot(metrics["day"], metrics["orders"], marker="o")
axes[1].set_title("주문")

plt.tight_layout()
plt.show()

Seaborn으로 분포를 빠르게 확인하기

Seaborn의 큰 장점 중 하나는 데이터프레임의 열 이름을 직접 지정할 수 있다는 점이다.

Histogram: 구간별 빈도 확인

sns.histplot(
    data=events,
    x="latency_ms",
    bins=6,
    edgecolor="black"
)

plt.show()

히스토그램은 수치형 데이터가 어떤 구간에 많이 몰려 있는지 보여준다.

다만 bins를 어떻게 설정하느냐에 따라 모양이 달라질 수 있으므로 하나의 설정만 보고 분포를 단정하면 안 된다.


hue로 그룹을 나눠 보기

Seaborn에서는 hue에 범주형 변수를 지정하면 색상으로 그룹을 나눌 수 있다.

sns.histplot(
    data=events,
    x="latency_ms",
    hue="status",
    bins=6
)

plt.show()

hue는 Seaborn을 사용할 때 매우 자주 접하게 되는 매개변수다.


Density Plot: 부드러운 분포 곡선

sns.kdeplot(
    data=events,
    x="latency_ms",
    fill=True
)

plt.show()

KDE(Kernel Density Estimation)는 히스토그램보다 부드러운 곡선으로 분포를 추정한다.

중요한 점은 곡선의 높이 자체가 해당 값이 나올 확률을 뜻하는 것은 아니라는 것이다. 연속형 확률 밀도에서는 특정 구간의 확률을 곡선 아래 면적으로 해석한다.

그룹별 KDE도 가능하다.

sns.kdeplot(
    data=events,
    x="latency_ms",
    hue="status",
    common_norm=False
)

plt.show()

common_norm=False를 사용하면 각 그룹의 밀도 곡선을 독립적으로 정규화해 형태를 비교하기 편하다.


Histogram과 KDE를 한 번에

예전에는 sns.distplot()을 사용한 코드가 많이 보였지만, 이 함수는 Seaborn 공식 문서에서 deprecated 상태다.

현재는 다음처럼 작성하는 편이 좋다.

sns.histplot(
    data=events,
    x="latency_ms",
    kde=True
)

plt.show()

개별 데이터 위치까지 보고 싶다면 rugplot()을 추가할 수 있다.

sns.histplot(data=events, x="latency_ms", kde=True)
sns.rugplot(data=events, x="latency_ms")
plt.show()

Box Plot: 중앙값, 사분위수, 이상치 후보 확인

sns.boxplot(
    data=events,
    x="latency_ms"
)

plt.show()

박스 플롯은 작은 공간 안에 분포 정보를 많이 담고 있다.

  • 박스의 왼쪽 또는 아래쪽 경계: Q1, 25%
  • 박스 내부 선: Q2, 중앙값 50%
  • 박스의 오른쪽 또는 위쪽 경계: Q3, 75%
  • 박스 길이: IQR = Q3 - Q1
  • 기본적인 이상치 후보: Q1 - 1.5 × IQR보다 작거나 Q3 + 1.5 × IQR보다 큰 값

여기서 박스 안의 선은 평균이 아니라 중앙값이라는 점이 중요하다.


Count Plot: 범주별 개수 확인

sns.countplot(
    data=events,
    x="endpoint"
)

plt.show()

countplot()은 범주별 개수를 직접 집계해서 막대 그래프로 보여준다.

따라서 다음 Pandas 코드와 비슷한 질문에 답한다.

events["endpoint"].value_counts()

Bar Plot: 범주별 수치 요약 비교

countplot()이 개수를 비교한다면 barplot()은 범주별 수치형 값의 통계량을 비교할 때 사용한다.

sns.barplot(
    data=events,
    x="status",
    y="latency_ms",
    estimator="mean",
    errorbar=("ci", 95)
)

plt.show()

기본적으로 평균을 비교하며, 현재 Seaborn에서는 errorbar 매개변수로 신뢰구간 등의 표시 방법을 제어할 수 있다.

오차 막대가 필요 없다면 다음처럼 끌 수 있다.

sns.barplot(
    data=events,
    x="status",
    y="latency_ms",
    errorbar=None
)

plt.show()

Heatmap: 숫자 행렬을 색으로 읽기

먼저 집계된 표를 만든 뒤 Heatmap으로 표현할 수 있다.

summary = (
    events
    .groupby(["endpoint", "status"], as_index=False)["latency_ms"]
    .mean()
    .pivot(
        index="endpoint",
        columns="status",
        values="latency_ms"
    )
)

sns.heatmap(
    summary,
    annot=True,
    fmt=".1f",
    cmap="Blues"
)

plt.show()

Heatmap에서 진한 색이 항상 큰 값이라는 법칙은 없다.

어떤 색상표를 사용했는지에 따라 방향이 달라질 수 있으므로 반드시 옆의 colorbar와 실제 숫자를 함께 봐야 한다.


Joint Plot과 Pair Plot은 언제 사용할까?

두 함수는 여러 정보를 한 화면에서 빠르게 탐색하고 싶을 때 유용하다.

sns.jointplot(
    data=events,
    x="latency_ms",
    y="retry_count"
)

jointplot()은 두 변수의 관계와 주변 분포를 함께 볼 수 있다.

pairplot()은 여러 수치형 변수의 조합을 한꺼번에 그린다.

sns.pairplot(data=some_dataframe)

하지만 열이 많아질수록 그래프 수가 급격히 증가하고 실행 시간도 길어진다. 실무에서는 무엇을 확인하려는지 정한 뒤 필요한 변수만 선택해서 그리는 편이 효율적이다.


어떤 그래프를 선택하면 될까?

알고 싶은 것대표적인 그래프
시간에 따른 변화Line Plot
수치형 변수의 분포Histogram, KDE
중앙값·사분위수·이상치 후보Box Plot
두 수치형 변수 관계Scatter Plot
범주별 개수Count Plot
범주별 평균 등 통계량Bar Plot
행렬 형태의 값 비교Heatmap
여러 변수 관계 빠르게 탐색Pair Plot

중요한 것은 예쁜 그래프를 많이 만드는 것이 아니다.

그래프를 왜 그렸는지, 그리고 그 그래프에서 어떤 사실을 읽을 수 있는지 설명할 수 있어야 한다.


자주 헷갈리는 부분

plt.show()는 마지막에

그래프 제목, 축 이름, 범례, 기준선 등은 일반적으로 show() 전에 설정한다.

plt.plot(metrics["day"], metrics["orders"])
plt.title("일별 주문")
plt.xlabel("날짜")
plt.ylabel("주문 수")
plt.show()

x축을 생략하면 인덱스가 사용될 수 있다

특히 선 그래프에서 인덱스의 순서가 이상하면 그래프도 예상하지 못한 형태가 될 수 있다.

그래프를 과하게 꾸미지 않는다

색, 마커, 그리드, 텍스트를 모두 넣는다고 좋은 시각화가 되는 것은 아니다. 데이터 해석에 필요한 요소만 남기는 것이 더 중요하다.


핵심 정리

  • 데이터 분석에서는 수치화와 시각화를 함께 사용하면 데이터를 더 빠르게 이해할 수 있다.
  • Matplotlib은 세밀한 그래프 제어에 강하고, Seaborn은 통계 그래프를 간단하게 만들기 좋다.
  • plt.plot()은 선 그래프의 기본이며 제목, 레이블, 범례, 축 범위 등을 조합해 사용한다.
  • 여러 그래프는 겹쳐 그릴 수도 있고 subplot() 또는 subplots()로 분리할 수도 있다.
  • Seaborn의 hue는 범주형 기준으로 데이터를 나눠 비교할 때 유용하다.
  • 수치형 분포는 Histogram, KDE, Box Plot을 함께 보면 이해하기 쉽다.
  • sns.distplot()은 deprecated이므로 새 코드에서는 histplot(), displot(), kdeplot() 등을 사용한다.
  • 시각화의 목적은 그래프 자체가 아니라 데이터에서 의미를 읽고 설명하는 것이다.
profile
html_programming_language

0개의 댓글