Matplotlib 서브플롯: plt.subplots()로 다중 패널 그림 만들기
Updated on
단일 패널 그림은 실제 분석에 충분한 경우가 거의 없습니다. 분포를 나란히 비교하거나, 산점도를 잔차와 함께 보여주거나, 4개의 지표를 대시보드 레이아웃으로 표시해야 합니다. 서브플롯 없이는 함께 제시할 때 시각적 관계가 사라지는 개별 그림을 만들게 됩니다.
Matplotlib의 plt.subplots() 함수는 공유 축, 일관된 크기, 유연한 레이아웃을 가진 다중 패널 그림을 만듭니다. 이 가이드는 기본 그리드부터 고급 비대칭 레이아웃까지 모든 것을 다룹니다.
기본 서브플롯
단일 행
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
axes[0].plot(x, np.sin(x))
axes[0].set_title('사인')
axes[1].plot(x, np.cos(x), color='orange')
axes[1].set_title('코사인')
axes[2].plot(x, np.tan(x), color='green')
axes[2].set_ylim(-5, 5)
axes[2].set_title('탄젠트')
plt.tight_layout()
plt.show()그리드 레이아웃
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
x = np.linspace(0, 10, 100)
axes[0, 0].plot(x, x, 'b-')
axes[0, 0].set_title('선형')
axes[0, 1].plot(x, x**2, 'r-')
axes[0, 1].set_title('이차')
axes[1, 0].plot(x, np.sqrt(x), 'g-')
axes[1, 0].set_title('제곱근')
axes[1, 1].plot(x, np.log(x + 1), 'm-')
axes[1, 1].set_title('로그')
plt.tight_layout()
plt.show()공유 축
축을 공유하면 비교를 위한 일관된 스케일이 보장됩니다:
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(42)
data1 = np.random.normal(0, 1, 1000)
data2 = np.random.normal(2, 1.5, 1000)
# X축 공유
fig, axes = plt.subplots(2, 1, figsize=(8, 6), sharex=True)
axes[0].hist(data1, bins=30, color='steelblue', alpha=0.7)
axes[0].set_ylabel('카운트')
axes[0].set_title('분포 A')
axes[1].hist(data2, bins=30, color='coral', alpha=0.7)
axes[1].set_ylabel('카운트')
axes[1].set_xlabel('값')
axes[1].set_title('분포 B')
plt.tight_layout()
plt.show()import matplotlib.pyplot as plt
import numpy as np
# X축과 Y축 모두 공유
fig, axes = plt.subplots(2, 3, figsize=(12, 8), sharex=True, sharey=True)
for i, ax in enumerate(axes.flat):
data = np.random.randn(100)
ax.hist(data, bins=20, color=f'C{i}', alpha=0.7)
ax.set_title(f'샘플 {i+1}')
plt.tight_layout()
plt.show()간격 및 레이아웃 제어
tight_layout()
겹침을 방지하기 위해 간격을 자동 조정:
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
# ... 플롯 추가 ...
plt.tight_layout() # 간격 자동 수정
plt.show()subplots_adjust()
간격 수동 제어:
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
plt.subplots_adjust(
left=0.1, # 왼쪽 여백
right=0.95, # 오른쪽 여백
top=0.92, # 상단 여백
bottom=0.08, # 하단 여백
wspace=0.3, # 서브플롯 간 가로 간격
hspace=0.4, # 서브플롯 간 세로 간격
)
plt.show()슈퍼 타이틀 추가
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
fig.suptitle('삼각함수', fontsize=16, fontweight='bold')
x = np.linspace(0, 2 * np.pi, 100)
for ax, func, name in zip(axes, [np.sin, np.cos, np.tan], ['sin', 'cos', 'tan']):
ax.plot(x, func(x))
ax.set_title(name)
plt.tight_layout()
plt.show()비대칭 레이아웃을 위한 GridSpec
다른 크기의 패널이 필요할 때 GridSpec을 사용합니다:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
fig = plt.figure(figsize=(12, 8))
gs = gridspec.GridSpec(2, 3, figure=fig)
# 큰 왼쪽 패널 (2행에 걸침)
ax1 = fig.add_subplot(gs[:, 0])
ax1.plot(np.random.randn(100).cumsum())
ax1.set_title('시계열 (2행)')
# 오른쪽 상단 패널
ax2 = fig.add_subplot(gs[0, 1])
ax2.bar(['A', 'B', 'C'], [3, 7, 5])
ax2.set_title('막대 그래프')
ax3 = fig.add_subplot(gs[0, 2])
ax3.scatter(np.random.randn(50), np.random.randn(50))
ax3.set_title('산점도')
# 하단 넓은 패널 (2열에 걸침)
ax4 = fig.add_subplot(gs[1, 1:])
ax4.hist(np.random.randn(500), bins=30, color='coral')
ax4.set_title('히스토그램 (2열)')
plt.tight_layout()
plt.show()위치 기반 레이아웃을 위한 subplot2grid
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(12, 8))
# (행, 열), (행_시작, 열_시작), rowspan, colspan
ax1 = plt.subplot2grid((3, 3), (0, 0), colspan=2)
ax2 = plt.subplot2grid((3, 3), (0, 2), rowspan=2)
ax3 = plt.subplot2grid((3, 3), (1, 0))
ax4 = plt.subplot2grid((3, 3), (1, 1))
ax5 = plt.subplot2grid((3, 3), (2, 0), colspan=3)
ax1.set_title('상단 넓음')
ax2.set_title('오른쪽 높음')
ax3.set_title('중간 왼쪽')
ax4.set_title('중간 가운데')
ax5.set_title('하단 전체 너비')
plt.tight_layout()
plt.show()서브플롯 반복 처리
import matplotlib.pyplot as plt
import numpy as np
# 쉬운 반복을 위해 axes 배열 평탄화
fig, axes = plt.subplots(2, 3, figsize=(12, 8))
datasets = [np.random.randn(100) for _ in range(6)]
colors = ['#e74c3c', '#3498db', '#2ecc71', '#f39c12', '#9b59b6', '#1abc9c']
for ax, data, color, i in zip(axes.flat, datasets, colors, range(6)):
ax.hist(data, bins=20, color=color, alpha=0.7)
ax.set_title(f'데이터셋 {i+1}')
ax.set_xlabel('값')
ax.set_ylabel('카운트')
plt.tight_layout()
plt.show()빈 서브플롯 제거
그리드 위치보다 플롯이 적을 때:
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(2, 3, figsize=(12, 8))
data_count = 5 # 6칸에 5개 데이터셋만
for i, ax in enumerate(axes.flat):
if i < data_count:
ax.plot(np.random.randn(50).cumsum())
ax.set_title(f'플롯 {i+1}')
else:
ax.set_visible(False) # 빈 서브플롯 숨기기
plt.tight_layout()
plt.show()인터랙티브 다중 패널 탐색
같은 데이터셋의 다른 뷰를 인터랙티브하게 비교하고 싶은 빠른 시각적 탐색을 위해, PyGWalker (opens in a new tab)는 Jupyter에서 열을 드래그 앤 드롭하여 대시보드를 만들 수 있게 합니다 -- 서브플롯 코드 불필요:
import pandas as pd
import pygwalker as pyg
df = pd.read_csv('your_data.csv')
walker = pyg.walk(df)FAQ
Matplotlib에서 서브플롯을 어떻게 만드나요?
fig, axes = plt.subplots(행, 열)을 사용하여 서브플롯 그리드가 있는 그림을 만듭니다. 그리드의 경우 axes[행, 열], 단일 행/열의 경우 axes[i]로 개별 축에 접근합니다. 레이블 겹침을 방지하기 위해 항상 마지막에 plt.tight_layout()을 호출하세요.
서브플롯 간에 축을 어떻게 공유하나요?
plt.subplots()에 sharex=True 또는 sharey=True를 전달합니다. 예를 들어, fig, axes = plt.subplots(2, 1, sharex=True)는 두 서브플롯이 같은 x축 스케일을 공유하게 합니다. 분포나 시계열 비교에 유용합니다.
다른 크기의 서브플롯을 어떻게 만드나요?
비대칭 레이아웃에는 matplotlib.gridspec.GridSpec을 사용합니다. 그리드를 만들고 슬라이스 표기법으로 여러 셀에 걸치게 합니다: ax = fig.add_subplot(gs[0, :2])는 첫 번째 행의 처음 두 열에 걸치는 서브플롯을 만듭니다.
서브플롯 간의 간격을 어떻게 조정하나요?
자동 간격에는 plt.tight_layout()을 호출합니다. 수동 제어에는 plt.subplots_adjust(wspace=0.3, hspace=0.4)를 사용합니다. wspace는 가로 간격, hspace는 세로 간격입니다.
모든 서브플롯 위에 제목을 어떻게 추가하나요?
fig.suptitle('내 제목', fontsize=16)을 사용하여 모든 서브플롯 위에 슈퍼 타이틀을 추가합니다. plt.subplots_adjust(top=0.92)로 상단 여백을 조정하거나 plt.tight_layout()을 호출하여 겹침을 방지해야 할 수 있습니다.
결론
Matplotlib의 plt.subplots()는 다중 패널 그림의 기반입니다. 일반 그리드에는 (행, 열)로, 일관된 스케일에는 sharex/sharey로, 자동 간격에는 tight_layout()으로, 비대칭 레이아웃에는 GridSpec으로 사용합니다. 빠른 반복을 위해 axes.flat으로 축 배열을 평탄화하고 루프합니다. 이러한 패턴이 다중 패널 시각화 요구의 대부분을 충족합니다.