Matplotlib 실전 기초: pyplot부터 subplot, subplots, twinx까지

Alchemist·2026년 9월 27일

KT AIVLE

목록 보기
22/130

데이터 분석을 하다 보면 단순히 숫자를 출력하는 것만으로는 데이터의 흐름을 파악하기 어렵다.
특히 시간에 따른 변화, 두 지표의 비교, 여러 그래프를 한 화면에 배치하는 작업은 시각화가 훨씬 직관적이다.

Python에서는 이런 정적 시각화를 만들 때 Matplotlib을 가장 기본적인 도구로 많이 사용한다.

이번 글에서는 Matplotlib의 pyplot을 중심으로 다음 내용을 정리한다.

  • 기본 그래프 그리기
  • Figure, Axes, Axis 구조
  • 제목·축 이름·범례 설정
  • 여러 선을 하나의 그래프에 표현하기
  • subplot()과 subplots()의 차이
  • twinx()로 서로 다른 y축을 가진 그래프 만들기
  • 그래프 저장과 레이아웃 정리

예제는 웹 서비스의 요청량, 오류율, 응답 시간 같은 개발자에게 익숙한 데이터로 구성했다.


Matplotlib이란?

Matplotlib은 Python에서 가장 널리 사용되는 시각화 라이브러리 중 하나다.

막대그래프, 선그래프, 산점도, 히스토그램 같은 2차원 그래프를 만들 수 있고, 그래프의 제목이나 축 범위, 범례, 크기 등을 세밀하게 조정할 수 있다.

보통 다음과 같이 pyplot 모듈을 plt라는 별칭으로 불러온다.

import matplotlib.pyplot as plt

데이터 생성을 위해 NumPy도 함께 사용하는 경우가 많다.

import numpy as np

Jupyter Notebook에서는 필요에 따라 다음 매직 명령을 사용할 수 있다.

%matplotlib inline

이 명령은 그래프를 Notebook 셀의 출력 영역에 표시할 때 사용한다.

최근 Jupyter 환경에서는 별도 설정 없이도 inline 출력이 기본 동작하는 경우가 많기 때문에, 반드시 필요한 코드는 아니다.


가장 기본적인 선 그래프 그리기

먼저 간단한 시계열 데이터를 만들어보자.

웹 서비스의 30분 동안 API 요청 수가 조금씩 변한다고 가정한다.

import matplotlib.pyplot as plt
import numpy as np

np.random.seed(0)

minutes = np.arange(30)
requests = 100 + np.random.randn(30) * 10

여기서 확인할 부분은 다음과 같다.

  • np.arange(30)은 0부터 29까지의 값을 만든다.
  • np.random.randn(30)은 정규분포에서 난수 30개를 생성한다.
  • np.random.seed(0)을 설정하면 같은 코드를 다시 실행했을 때 동일한 난수가 만들어진다.

이제 그래프를 그린다.

plt.figure()
plt.plot(minutes, requests)
plt.show()

기본 흐름은 다음처럼 이해하면 된다.

Figure 생성
    ↓
데이터를 그래프로 그림
    ↓
그래프 출력

즉,

plt.figure()
plt.plot(...)
plt.show()

가 Matplotlib의 가장 기본적인 흐름이다.


Figure, Axes, Axis 이해하기

Matplotlib을 제대로 사용하려면 Figure, Axes, Axis의 차이를 이해하는 것이 좋다.

Figure
└── Axes
    ├── x-axis
    └── y-axis

Figure

Figure는 전체 그래프를 담는 도화지다.

하나의 Figure 안에는 하나 또는 여러 개의 그래프가 들어갈 수 있다.

Axes

Axes는 실제 그래프가 그려지는 영역이다.

예를 들어 위쪽에 요청량 그래프, 아래쪽에 오류율 그래프를 그린다면 하나의 Figure 안에 Axes가 두 개 있는 구조다.

Axis

Axis는 x축과 y축 같은 개별 축을 의미한다.

이름이 비슷해서 처음에는 헷갈리기 쉽다.

용어의미
Figure그래프 전체 영역
Axes그래프가 실제로 그려지는 공간
Axisx축, y축 같은 개별 축

그래프 크기와 제목 설정하기

그래프의 크기는 figsize로 설정한다.

plt.figure(figsize=(10, 5))

plt.plot(minutes, requests)

plt.xlabel('Minute')
plt.ylabel('Request Count')
plt.title('API Requests over Time')

plt.show()

각 함수의 역할은 다음과 같다.

함수역할
plt.figure(figsize=(w, h))Figure 크기 설정
plt.xlabel()x축 이름 설정
plt.ylabel()y축 이름 설정
plt.title()그래프 제목 설정
plt.show()그래프 출력

figsize=(10, 5)에서 숫자는 일반적인 픽셀 값이 아니라 인치 단위의 Figure 크기다.


하나의 그래프에 여러 데이터 표현하기

서로 관련 있는 두 데이터를 비교할 때는 하나의 Axes에 선을 여러 개 그릴 수 있다.

예를 들어 API A와 API B의 요청량을 비교해보자.

np.random.seed(1)

api_a = 100 + np.random.randn(30) * 10
api_b = 120 + np.random.randn(30) * 12

두 데이터를 같은 그래프에 그린다.

plt.figure(figsize=(10, 5))

plt.plot(minutes, api_a, label='API A')
plt.plot(minutes, api_b, label='API B')

plt.xlabel('Minute')
plt.ylabel('Request Count')
plt.title('API Request Comparison')

plt.legend()
plt.tight_layout()

plt.show()

label에 이름을 지정하고 plt.legend()를 호출하면 범례가 표시된다.

plt.plot(minutes, api_a, label='API A')
plt.plot(minutes, api_b, label='API B')
plt.legend()

그래프가 복잡해질수록 범례를 추가하는 습관이 중요하다.


tight_layout()은 왜 사용할까?

축 이름이나 제목이 길면 Figure 영역 밖으로 잘리는 경우가 있다.

plt.tight_layout()

을 사용하면 Matplotlib이 그래프 사이 간격과 여백을 자동으로 조정한다.

여러 그래프가 있는 Figure에서 특히 유용하다.


그래프 저장하기

화면에 출력하는 것뿐 아니라 이미지 파일로 저장할 수도 있다.

plt.savefig('api_requests.png')

일반적으로 plt.show() 이전에 호출한다.

plt.figure(figsize=(10, 5))
plt.plot(minutes, requests)

plt.title('API Requests over Time')
plt.tight_layout()

plt.savefig('api_requests.png')
plt.show()

분석 리포트나 문서에 그래프를 첨부해야 할 때 자주 사용한다.


여러 그래프 배치하기: subplot()

하나의 Figure에 여러 그래프를 배치하고 싶다면 subplot()을 사용할 수 있다.

기본 형태는 다음과 같다.

plt.subplot(행, 열, 인덱스)

예를 들어 2행 1열 구조라면 다음 위치를 의미한다.

(2, 1, 1) → 첫 번째 그래프

(2, 1, 2) → 두 번째 그래프

subplot() 예제

서비스 트래픽과 오류율을 각각 다른 그래프로 확인해보자.

time = np.linspace(0, 10, 100)

traffic = np.sin(time) + 5
error_rate = np.cos(time) * 2 + 3

그래프를 위아래로 배치한다.

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

plt.subplot(2, 1, 1)
plt.plot(time, traffic)
plt.title('Traffic')

plt.subplot(2, 1, 2)
plt.plot(time, error_rate)
plt.title('Error Rate')

plt.tight_layout()
plt.show()

subplot()을 호출할 때마다 현재 작업할 그래프 영역이 변경된다.

이 방식은 간단하지만 그래프 수가 많아지면 어떤 Axes를 수정하고 있는지 헷갈릴 수 있다.


subplot()과 subplots()의 차이

Matplotlib에는 이름이 비슷한 두 함수가 있다.

plt.subplot()
plt.subplots()

둘 다 여러 그래프를 만들 수 있지만 사용하는 방식이 다르다.

구분subplot()subplots()
방식현재 그래프 위치를 순서대로 변경Figure와 Axes 객체를 한번에 생성
스타일state-basedobject-oriented
작은 예제간단함조금 더 코드가 필요함
복잡한 그래프관리하기 어려울 수 있음구조적으로 관리하기 쉬움

개인적으로는 그래프 구성이 조금만 복잡해져도 subplots() 방식이 훨씬 읽기 쉽다.


subplots()로 여러 Axes 만들기

다음과 같이 Figure와 Axes를 동시에 만든다.

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

이 경우 구조는 다음과 같다.

fig
├── axes[0]
└── axes[1]

각 그래프를 명확하게 지정할 수 있다.

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

axes[0].plot(time, traffic)
axes[0].set_title('Traffic')

axes[1].plot(time, error_rate)
axes[1].set_title('Error Rate')

plt.tight_layout()
plt.show()

plt.title()이 아니라 axes[0].set_title()처럼 각 Axes 객체를 직접 조작한다는 점이 핵심이다.


subplot 방식과 객체지향 방식 비교

같은 결과를 두 방식으로 만들 수 있다.

pyplot 중심 방식

plt.subplot(2, 1, 1)
plt.plot(time, traffic)

plt.subplot(2, 1, 2)
plt.plot(time, error_rate)

plt.show()

객체지향 방식

fig, axes = plt.subplots(2, 1)

axes[0].plot(time, traffic)
axes[1].plot(time, error_rate)

plt.show()

작은 예제에서는 둘 다 괜찮다.

하지만 대시보드처럼 여러 그래프를 정교하게 제어해야 한다면 Axes 객체를 직접 다루는 방식이 유지보수하기 편하다.


선 스타일과 마커 설정하기

plot()에는 선 모양이나 마커를 간단한 문자열로 지정할 수도 있다.

예를 들어 다음처럼 사용할 수 있다.

plt.plot(time, traffic, 'o-')

'o-'는 다음 의미다.

o → 각 데이터 지점에 원형 마커 표시
- → 실선으로 연결

다른 예시는 다음과 같다.

plt.plot(time, traffic, '--')
plt.plot(time, traffic, 'o')
plt.plot(time, traffic, 'o--')

그래프의 목적에 따라 점과 선을 함께 표현하면 데이터 위치를 더 명확히 볼 수 있다.


x축을 공유하면서 y축을 다르게 사용하기: twinx()

서로 단위가 다른 두 지표를 한 그래프에서 비교해야 할 때가 있다.

예를 들어 서비스 운영 데이터를 생각해보자.

  • 요청 수: 수천 건
  • 평균 응답 시간: 수백 ms

두 값의 단위와 범위가 크게 다르기 때문에 같은 y축을 사용하면 한쪽 데이터가 거의 보이지 않을 수 있다.

이럴 때 twinx()를 사용할 수 있다.


예제 데이터 만들기

days = np.arange(1, 11)

request_count = np.array([
    1200, 1500, 1700, 1600, 1800,
    2100, 2400, 2600, 3000, 2800
])

avg_latency = np.array([
    180, 175, 190, 220, 205,
    210, 230, 250, 270, 240
])

서로 다른 y축 사용하기

fig, ax1 = plt.subplots(figsize=(9, 5))

ax1.plot(
    days,
    request_count,
    'o-',
    label='Request Count'
)

ax1.set_xlabel('Day')
ax1.set_ylabel('Requests')

이제 같은 x축을 공유하는 두 번째 Axes를 만든다.

ax2 = ax1.twinx()

그리고 두 번째 지표를 그린다.

ax2.bar(
    days,
    avg_latency,
    alpha=0.3,
    label='Average Latency'
)

ax2.set_ylabel('Latency (ms)')

전체 코드는 다음과 같다.

fig, ax1 = plt.subplots(figsize=(9, 5))

ax1.plot(
    days,
    request_count,
    'o-',
    label='Request Count'
)

ax1.set_xlabel('Day')
ax1.set_ylabel('Requests')

ax2 = ax1.twinx()

ax2.bar(
    days,
    avg_latency,
    alpha=0.3,
    label='Average Latency'
)

ax2.set_ylabel('Latency (ms)')

ax1.legend(loc='upper left')
ax2.legend(loc='upper right')

plt.title('Requests and Average Latency')
plt.tight_layout()
plt.show()

구조를 정리하면 다음과 같다.

하나의 Figure
    ↓
ax1 ─── x축 공유 ─── ax2
 │                   │
요청 수              응답 시간
y축 1                y축 2

즉 twinx()는 x축은 공유하지만 y축은 서로 다른 두 Axes를 만든다.


twinx()를 사용할 때 주의할 점

이중 y축 그래프는 서로 다른 단위의 데이터를 함께 표현할 수 있어서 편리하다.

하지만 두 그래프의 스케일을 어떻게 설정하느냐에 따라 실제보다 강한 관계가 있는 것처럼 보일 수 있다.

그래서 다음을 주의하는 것이 좋다.

  • 각 y축 단위를 명확하게 표시한다.
  • 범례를 각각 표시한다.
  • 두 변수의 인과관계를 그래프만 보고 단정하지 않는다.
  • 단순 비교가 목적이라면 그래프를 분리하는 것도 고려한다.

시각화는 정보를 명확하게 전달하는 것이 목적이지, 관계를 과장하는 것이 목적이 아니다.


개발자 관점에서 활용하기

Matplotlib은 데이터 분석뿐만 아니라 개발 환경의 로그나 운영 데이터를 빠르게 확인할 때도 유용하다.

예를 들어 다음과 같은 데이터를 그려볼 수 있다.

시간별 API 요청 수
시간별 평균 응답 시간
HTTP 5xx 오류율
페이지별 클릭 수
배치 처리량
모델 추론 시간
CPU / 메모리 사용량

하나의 서비스 상황을 분석한다고 하면 다음처럼 구성할 수 있다.

Figure
├── Axes 1: API 요청 수
├── Axes 2: 평균 응답 시간
└── Axes 3: 오류율

이런 식으로 지표를 함께 보면 장애 발생 시점이나 패턴을 파악하기 쉬워진다.


자주 헷갈리는 부분

plt.figure()는 항상 필요한가?

아니다.

다음처럼 바로 plot()을 실행해도 Matplotlib이 내부적으로 Figure를 생성한다.

plt.plot([1, 2, 3], [10, 20, 30])
plt.show()

하지만 Figure 크기를 지정하거나 복잡한 그래프를 구성해야 한다면 명시적으로 생성하는 편이 좋다.


subplot()과 subplots() 중 무엇을 쓰는 게 좋을까?

작은 예제에서는 subplot()도 충분하다.

하지만 여러 그래프를 만들고 각각의 축을 직접 제어해야 한다면 subplots()가 더 명확하다.

fig, axes = plt.subplots(2, 2)

처럼 생성한 뒤,

axes[0, 0]
axes[0, 1]
axes[1, 0]
axes[1, 1]

형태로 원하는 그래프를 정확히 지정할 수 있다.


Axes와 Axis는 같은 것인가?

아니다.

Axes → 실제 그래프 영역
Axis → x축 또는 y축

이 차이를 알고 있으면 Matplotlib 공식 문서를 읽을 때 훨씬 덜 헷갈린다.


핵심 정리

  • Matplotlib은 Python의 대표적인 정적 데이터 시각화 라이브러리다.
  • 일반적으로 matplotlib.pyplot을 plt라는 이름으로 사용한다.
  • 가장 기본적인 흐름은 figure → plot → show다.
  • figsize로 그래프 크기를 설정할 수 있다.
  • xlabel, ylabel, title, legend로 그래프 정보를 명확하게 전달할 수 있다.
  • Figure는 전체 도화지, Axes는 그래프가 그려지는 공간, Axis는 개별 축이다.
  • subplot()은 위치를 순서대로 지정하는 방식이고, subplots()는 Figure와 Axes 객체를 직접 다루는 방식이다.
  • 복잡한 시각화에서는 subplots() 기반의 객체지향 방식이 관리하기 편하다.
  • twinx()를 이용하면 x축을 공유하면서 서로 다른 두 개의 y축을 사용할 수 있다.
  • tight_layout()은 그래프 간격과 여백을 자동으로 정리하는 데 유용하다.
  • savefig()를 사용하면 그래프를 이미지 파일로 저장할 수 있다.

정리하며

Matplotlib을 처음 사용할 때는 plot() 함수만 익히는 것보다 Figure와 Axes의 구조를 먼저 이해하는 것이 중요하다.

단순한 선 그래프는 몇 줄이면 그릴 수 있지만, 그래프가 많아지거나 서로 다른 축을 사용해야 하는 순간부터 구조를 이해하지 못하면 코드가 금방 복잡해진다.

기본 그래프 → 여러 선 비교 → subplot → subplots → twinx 순서로 익혀두면 이후 히스토그램, 산점도, 막대그래프, Seaborn 같은 시각화 도구를 사용할 때도 훨씬 수월하다.

profile
html_programming_language

0개의 댓글