HOWTO · Matplotlib

Matplotlib 에서 모든 서브 플로트에 대한 단일 범례를 만드는 방법

Matplotlib에서 `fig.legend()`는 Figure 전체에 범례를 추가하고, `ax.legend()`는 특정 Axes에 범례를 추가합니다. 모든 서브플롯에서 선과 레이블이 같다면 특히 편리합니다.

이 페이지의 내용

fig.legend()를 사용하면 Matplotlib Figure 전체에 범례 하나를 추가할 수 있습니다. 모든 서브플롯에 같은 레이블이 붙은 계열이 표시된다면 Axes 하나에서 핸들과 레이블을 가져와 재사용하면 됩니다. 서브플롯마다 표시하는 계열이 다르면 각 Axes에서 핸들과 레이블을 모아 두 목록을 모두 Figure의 범례에 전달합니다. ax.legend()는 특정 Axes에 범례를 만드는 메서드라는 점에서 다릅니다.

아래 예제에서는 Matplotlib으로 Figure를 만듭니다. 첫 번째 예제는 공통 범례에 모은 레이블도 출력합니다.

서브플롯의 레이블이 같을 때 Figure 전체에 공통 범례 만들기

각 서브플롯에 같은 계열이 같은 레이블로 그려져 있다면, 서브플롯 하나에서 공통 범례에 필요한 항목을 가져올 수 있습니다. 아래 예제에서는 크기가 서로 다른 두 측정값에 모두 measurement라는 레이블을 붙입니다. 레이블과 의미가 같으므로 첫 번째 Axes에서 핸들을 가져오는 것으로 충분합니다. Figure 전체에 범례를 한 번만 표시하려면 각 Axes에서 legend()를 호출하는 대신 fig에 범례를 추가합니다.

다음 예제를 실행해 보세요.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

import matplotlib

matplotlib.use("Agg")
import logging

# Ignore only Matplotlib's benign cold-start cache notice; other diagnostics
# remain visible on stderr.
class _FontCacheNoticeFilter(logging.Filter):
    def filter(self, record):
        return "Matplotlib is building the font cache; this may take a moment." not in record.getMessage()


logging.getLogger("matplotlib.font_manager").addFilter(_FontCacheNoticeFilter())
import matplotlib.pyplot as plt

fig, axes = plt.subplots(2, 1, figsize=(6, 4))
x = [0, 1, 2]

for ax, scale in zip(axes, [1, 2]):
    ax.plot(x, [scale * value for value in x], label="measurement")

handles, labels = axes[0].get_legend_handles_labels()
fig.legend(handles, labels, loc="upper center")
fig.tight_layout(rect=(0, 0, 1, 0.9))
print("Legend entries:", labels)
plt.show()
Legend entries: ['measurement']

get_legend_handles_labels()는 해당 Axes에 있는 레이블 지정 아티스트의 핸들과 레이블을 짝이 맞는 두 목록으로 반환합니다. 공통 레이블 방식은 간결하지만 선택한 Axes에 있는 계열만 나타냅니다. 다른 서브플롯에 별도의 레이블이나 계열이 있다면 다음 절의 전체 Axes 방식으로 처리하세요.

서브플롯의 내용이 다를 때 모든 Axes에서 핸들과 레이블 모으기

서브플롯마다 서로 다른 레이블이 붙은 선이 있다면 하나의 반복문에서 각 Axes의 핸들과 레이블을 함께 모읍니다. 두 목록은 위치에 따라 짝을 이룹니다. 즉, 인덱스 i의 핸들은 인덱스 i의 레이블에 해당합니다. 아래 코드는 선마다 하나씩, 총 두 개의 범례 항목을 만듭니다.

다음 예제를 실행해 보세요.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt

fig, axes = plt.subplots(1, 2, figsize=(7, 3))
axes[0].plot([0, 1, 2], [0, 1, 4], label="quadratic")
axes[1].plot([0, 1, 2], [0, 1, 2], label="linear")

handles, labels = [], []
for ax in axes:
    ax_handles, ax_labels = ax.get_legend_handles_labels()
    handles.extend(ax_handles)
    labels.extend(ax_labels)

fig.legend(handles, labels, loc="upper center", ncol=2)
fig.tight_layout(rect=(0, 0, 1, 0.88))
plt.show()

이 방식은 모든 Axes에서 레이블이 지정된 아티스트를 모으므로 같은 레이블이 여러 번 나타날 수 있습니다. 중복 항목이 같은 개념을 가리킨다면 fig.legend()를 호출하기 전에 원하는 레이블마다 핸들을 하나씩 선택하세요. 반대로 독자가 계열을 구분해야 한다면 서로 다른 레이블을 유지합니다. 일부 항목만 표시하려면 해당 핸들과 레이블만 전달하면 됩니다. 범례에 그려진 모든 선을 포함할 필요는 없습니다.

Figure 전체 범례의 위치와 서식 지정하기

loc 인수는 범례의 어느 지점을 기준점에 맞출지 정합니다. 예를 들어 fig.legend(..., loc="upper center")는 범례를 Figure 위쪽 가운데에 배치합니다. 기준점을 직접 지정하려면 bbox_to_anchor를 사용하세요. Figure 범례에서는 좌표가 Figure를 기준으로 합니다. 예를 들어 fig.legend(handles, labels, loc="upper center", bbox_to_anchor=(0.5, 1.02))는 범례를 Figure 위쪽 경계 바로 위에 둡니다. 기준점 값을 조정해 위치를 옮기고, 범례가 서브플롯 내용을 가리지 않도록 fig.tight_layout(rect=(0, 0, 1, 0.9)) 또는 fig.subplots_adjust(top=0.82)로 공간을 확보하세요.

범례 서식에는 다른 Matplotlib 범례와 같은 익숙한 옵션을 사용할 수 있습니다. fig.legend()에 fontsize=9, frameon=False, facecolor="white" 같은 값을 전달하면 글자 크기, 테두리 표시 여부, 배경색을 조정할 수 있습니다. 범례가 Figure 경계 밖에 있을 때 fig.savefig("plot.png", bbox_inches="tight")로 저장하면 저장된 이미지에 바깥쪽 범례까지 포함할 수 있습니다.

Figure 전체 범례에 관한 자주 묻는 질문

ax.legend()와 fig.legend()는 어떻게 다른가요? ax.legend()는 특정 Axes에 속합니다. fig.legend()는 Figure에 속하며, 하나 이상의 Axes에서 가져온 항목을 합칠 수 있습니다.

모든 서브플롯의 레이블이 같을 때 범례 하나를 만들려면 어떻게 하나요? 대표 Axes 하나에서 핸들과 레이블을 가져와 fig.legend()에 전달하세요. 이 방법은 해당 Axes에 다른 서브플롯과 같은 레이블의 계열이 있을 때 사용할 수 있습니다.

일부 선만 범례에 넣으려면 어떻게 하나요? 원하는 핸들과 레이블만 짝이 맞는 목록에 담아 fig.legend()에 전달하세요. 레이블이 반복되면 중복을 제거하기 전에 각 항목이 같은 계열을 가리키는지 확인하세요.

범례가 그래프를 가리지 않게 하려면 어떻게 하나요? loc와 bbox_to_anchor로 범례를 Figure 여백에 배치한 다음 서브플롯 레이아웃에서 공간을 확보하세요. 범례가 캔버스 밖에 있다면 저장할 때 tight bounding box를 사용해 잘리지 않게 하세요.