Matplotlib 기초 (1) - Figure와 Axes, 그리고 첫 그래프
matplotlib이 그림을 그리는 구조인 Figure와 Axes에서 출발해, 초보자가 반드시 헷갈리는 두 가지 인터페이스(pyplot과 객체지향)를 구분하고, 선·산점도·막대 그래프를 그리고 축과 제목을 붙이며 한글 폰트 문제까지 정리한 Matplotlib 기초 첫 편입니다.
표로 확인한 데이터를 그림으로 보면 숫자만으로는 안 보이던 것이 드러납니다. matplotlib은 파이썬에서 그래프를 그리는 표준 도구입니다. 이 글은 matplotlib이 그림을 그리는 구조에서 출발해, 초보자가 반드시 한 번은 헷갈리는 두 인터페이스를 정리하고 첫 그래프를 그립니다. 시리즈 소개는 0편을 참고하세요. 앞선 NumPy 기초와 Pandas 기초에서 쓰던 성적 데이터를 그대로 씁니다.
왜 그림인가
Pandas의 describe()는 평균과 사분위수를 숫자로 보여줍니다. 하지만 평균이 같아도 분포 모양이 전혀 다른 두 데이터가 있을 수 있고, 그 차이는 표로는 잘 안 보입니다. 히스토그램 하나를 그리면 한눈에 드러납니다. 시각화는 분석의 장식이 아니라 점검 도구입니다. 데이터를 받자마자 그려보는 습관이 Pandas 기초 1편의 “첫 점검 다섯 줄”과 같은 역할을 합니다.
matplotlib의 구조: Figure와 Axes
matplotlib을 처음 배울 때 가장 먼저 잡아야 할 것은 문법이 아니라 구조입니다. 그림은 두 층으로 이루어집니다.
- Figure — 그림 전체를 담는 도화지입니다. 크기, 배경, 저장 단위가 여기에 걸립니다.
- Axes — 도화지 위에 놓인 하나의 그래프입니다. x축, y축, 제목, 그려지는 선이 전부 이 Axes에 속합니다.
한 Figure 안에 Axes가 여러 개 있을 수 있습니다. 2행 2열로 그래프 네 개를 배치하면, Figure 하나에 Axes 네 개가 놓인 것입니다. “축”이라는 이름 때문에 x축이나 y축으로 오해하기 쉬운데, Axes는 그래프 하나 전체를 가리킵니다.
1
2
3
import matplotlib.pyplot as plt
fig, ax = plt.subplots() # Figure 하나 + Axes 하나를 함께 만든다
plt.subplots()는 도화지(fig)와 그 위의 그래프(ax)를 한 번에 만들어 돌려줍니다. 앞으로 그림은 거의 항상 이 한 줄로 시작합니다.
두 인터페이스: 초보자가 반드시 헷갈리는 지점
matplotlib 예제를 검색하면 두 가지 스타일이 뒤섞여 나옵니다. 이걸 구분하지 못하면 코드가 왜 되는지 안 되는지 알 수 없습니다.
1
2
3
4
5
6
7
8
# 방식 A: pyplot 인터페이스 — plt에 직접 명령
plt.plot([1, 2, 3], [4, 5, 6])
plt.title("A")
# 방식 B: 객체지향 인터페이스 — Axes 객체에 명령
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [4, 5, 6])
ax.set_title("B")
방식 A는 “지금 활성화된 그래프”에 암묵적으로 그립니다. 짧지만, 그래프가 여러 개가 되면 어디에 그려지는지 모호해집니다. 방식 B는 ax라는 그래프 객체를 명시적으로 지목해 그립니다. 길어 보여도 어디에 무엇이 그려지는지 분명합니다.
처음부터 객체지향 방식(
fig, ax = plt.subplots()뒤에ax.___)을 쓰는 습관을 들이세요. 그래프가 하나일 때는 두 방식의 차이가 없지만, 여러 개를 배치하는 순간 pyplot 방식은 무너집니다. 이 글도 이후 전부 객체지향으로 씁니다.
메서드 이름에 규칙이 하나 있습니다. pyplot의 plt.title(), plt.xlabel()은 객체지향에서 ax.set_title(), ax.set_xlabel()처럼 set_ 접두사가 붙습니다. 값을 읽는 게 아니라 설정하는 것이라 set_이 붙는다고 기억하면 됩니다.
첫 그래프 세 가지
성적 데이터를 불러와 실제로 그려봅니다. 데이터 준비는 NumPy 기초 0편과 같습니다.
1
2
3
4
import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_csv("https://blog.hoseunggg.com/assets/data/grades.csv")
선 그래프 는 순서가 있는 값의 흐름을 봅니다. 학기별 평균 평점의 추이 같은 것입니다.
1
2
3
4
term_avg = df.groupby("수강학기")["성적"].mean()
fig, ax = plt.subplots()
ax.plot(term_avg.index, term_avg.values, marker="o")
산점도 는 두 값의 관계를 봅니다. 학점(수업 부담)과 성적 사이에 관계가 있는지 점으로 찍어 봅니다.
1
2
fig, ax = plt.subplots()
ax.scatter(df["학점"], df["성적"], alpha=0.3) # alpha: 점이 겹칠 때 투명도
막대 그래프 는 범주별 크기를 비교합니다. 이수구분별 평균 성적 같은 것입니다.
1
2
3
4
group_avg = df.groupby("이수구분")["성적"].mean()
fig, ax = plt.subplots()
ax.bar(group_avg.index, group_avg.values)
ax.plot은 흐름, ax.scatter는 관계, ax.bar는 비교입니다. 데이터가 무엇을 말하게 하고 싶은지에 따라 고릅니다.
축과 제목, 범례 붙이기
그래프는 축 라벨과 제목이 없으면 절반만 완성된 것입니다. 무엇을 그린 것인지 6개월 뒤의 자신은 기억하지 못합니다.
1
2
3
4
5
6
7
fig, ax = plt.subplots()
ax.plot(term_avg.index, term_avg.values, marker="o", label="평균 성적")
ax.set_xlabel("수강학기")
ax.set_ylabel("성적")
ax.set_title("학기별 평균 성적 추이")
ax.legend() # label로 지정한 이름이 범례로 표시된다
label=로 이름을 달아둔 요소는 ax.legend()를 부르면 범례로 모입니다. 한 그래프에 선을 여러 개 겹칠 때 어느 선이 무엇인지 구분하는 표준 방법입니다.
한글이 깨질 때
matplotlib은 기본 폰트에 한글이 없어서, 위 코드의 “수강학기” 같은 한글 라벨이 네모(□)로 깨져 나옵니다. Google Colab에서는 나눔 폰트를 설치하고 지정하면 해결됩니다.
1
2
3
4
5
6
# Colab 셀에서 한 번 실행 후 런타임 재시작
!apt-get install -y fonts-nanum > /dev/null
import matplotlib.pyplot as plt
plt.rcParams["font.family"] = "NanumGothic"
plt.rcParams["axes.unicode_minus"] = False # 마이너스 부호 깨짐 방지
axes.unicode_minus 설정도 함께 해두는 것이 좋습니다. 이걸 빼면 음수 눈금의 마이너스 기호가 깨지는 별개의 문제가 생깁니다.
저장하기
화면에 보이는 것과 파일로 남기는 것은 다릅니다. 그림을 파일로 저장할 때는 Figure에 savefig를 부릅니다.
1
fig.savefig("term_avg.png", dpi=150, bbox_inches="tight")
dpi는 해상도이고, bbox_inches="tight"는 그림 주변의 불필요한 여백을 잘라줍니다. 이 두 옵션은 저장할 때 거의 항상 함께 씁니다. 저장은 Axes(ax)가 아니라 도화지 전체인 Figure(fig)의 일이라는 점만 기억하면 됩니다.
정리
| 개념 | 한 줄 요약 |
|---|---|
| Figure / Axes | 도화지 / 그 위의 그래프 하나. 저장은 Figure, 그리기는 Axes |
| 두 인터페이스 | plt.___(암묵적)와 ax.___(명시적). 객체지향(ax)을 기본으로 |
| set_ 접두사 | 객체지향에서는 ax.set_title, ax.set_xlabel처럼 설정 메서드 |
| 그래프 종류 | 흐름은 plot, 관계는 scatter, 비교는 bar |
| 축과 범례 | set_xlabel/set_ylabel/set_title, label + legend() |
| 한글 폰트 | 나눔 폰트 설치 후 font.family 지정, unicode_minus도 함께 |
| 저장 | fig.savefig(..., dpi=150, bbox_inches="tight") |
matplotlib의 어려움은 문법이 아니라 구조와 두 인터페이스의 혼란에서 옵니다. Figure와 Axes를 구분하고 객체지향 방식으로 통일하면 나머지는 메서드 이름을 찾아 쓰는 일입니다. 다음 글에서는 실제로 자주 그리는 그래프인 히스토그램, 박스플롯, 산점도를 다루며 “언제 무엇을 그릴지”를 정리합니다.