Plot on Image

Contributor

현재웅님

  • matplotlib은 데이터를 그림으로 보여주지만 그림을 읽어 보여주기도 합니다.
  • 이 성질을 이용하면 다양한 연출을 할 수 있습니다.
  • 대용량 데이터의 시각화 결과물을 그림파일로 저장하고 재활용해봅시다.

1. 대용량 데이터 다루기

  • Matplotlib은 큰 데이터를 다루기에 적합한 도구는 아닐 지도 모릅니다.
  • 커다란 데이터를 읽으라면 DOS 공격으로 오인하기도 합니다.
  • 이럴 때 택할 수 있는 방법 하나는 데이터를 조금씩 읽어서 그리는 것입니다.

1.1. 대용량 데이터 만들기

  • skew가 있는 정규분포 데이터 1억개 X 10 쌍을 만듭니다.
  • skew가 있으면 정규분포의 중심축이 가운데서 옆으로 밀린 모양이 됩니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import matplotlib.pyplot as plt
    import seaborn as sns
    import numpy as np
    from scipy.stats import skewnorm
    sns.set_style("whitegrid")
    sns.set_context("talk")

    for i in range(10):
    d = skewnorm.rvs(np.random.randint(20), size=100000000)
    np.save(f"./data_{i}.npy", d, allow_pickle=True)

1.2. 대용량 데이터 그리기

  • 대용량 데이터를 다루는 방법 중 하나는 한번에 한 덩어리씩 그리는 것입니다.
  • 1억개 데이터를 한 세트씩 읽고, 그리고, 버립니다.
  • 본 예제에서는 boxplot을 그려봅니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    fig, axs = plt.subplots(ncols=10, sharey=True, figsize=(10,5))

    for i, ax in enumerate(axs):
    # 데이터 한 세트 읽기
    data_small = np.load(f"./data_{i}.npy", allow_pickle=True)

    # 데이터 한 세트 그리기
    sns.boxplot(y=data_small, ax=axs[i], orient="v", color="orange")
    ax.set_xlabel(f"X{i}")
    ax.grid(b=None)
    if i != 0:
    ax.spines["left"].set_visible(False)
    if i != 9:
    ax.spines["right"].set_visible(False)

    # 그린 데이터 세트 버리기
    del data_small

    fig.tight_layout()
    fig.savefig("plot0.png")

  • 여러 subplots에 나누어 그렸지만 마치 하나에 있는 것처럼 보이게 하고자 합니다.
  • spines[].set_visible(False)를 이용해서 subplots 사이의 spines를 모두 보이지 않게 처리했습니다.
  • 언뜻 봐서는 한 axes에 있는 것 같지만 위 아래 spine이 끊겨 있습니다.
  • 이 틈을 메워봅시다.

2. 끊긴 spine 이어주기

2.1. 그리자마자 이어주기

  • 가장 좋은 방법은 그림을 그릴 때 메우는 것입니다.
  • 그림을 그린 직후, 커다란 axes를 그리고 spine만 남기고 모두 지웁니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    fig, axs = plt.subplots(ncols=10, sharey=True, figsize=(10,5))

    for i, ax in enumerate(axs):
    # 데이터 한 세트 읽기
    data_small = np.load(f"./data_{i}.npy", allow_pickle=True)

    # 데이터 한 세트 그리기
    sns.boxplot(y=data_small, ax=axs[i], orient="v", color="orange")
    ax.set_xlabel(f"X{i}")
    ax.grid(b=None)
    if i != 0:
    ax.spines["left"].set_visible(False)
    if i != 9:
    ax.spines["right"].set_visible(False)

    # 그린 데이터 세트 버리기
    del data_small

    # 커다란 테두리 만들어 주기
    ax_big = fig.add_subplot(111)
    ax_big.grid(b=None)
    ax_big.set_facecolor("None")
    ax_big.set_xticks([])
    ax_big.set_yticks([])

    fig.tight_layout()

  • 그런데 아차, 깜빡하고 테두리를 잇지 않고 저장했다면 어떻게 해야 할까요?
  • 제 예제의 경우 그림을 그리는 데만 약 4분이 걸립니다. 데이터가 많을수록 더 오래 걸릴 것입니다.
  • 다행히 테두리가 끊긴 그림파일을 저장해 두었습니다. 이 것을 이용해 봅시다.

2.2. 그린 다음에 이어주기

matplotlib.pyplot.imread
matplotlib.pyplot.imshow

  • matplotlib의 이미지 처리 기능을 이용합니다.
  • 그림을 imread()로 읽어와서 imshow()로 띄운 뒤 끊어진 선 위에 새로운 선을 올려놓으면 됩니다.

2.1. ax.imread()

  • 반사적으로 figure와 axes를 만들고 읽어봅니다.
  • 손가락이 알아서 fig, ax = plt.subplots()를 입력하고 있습니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    fig, ax = plt.subplots(figsize=(10,5))

    # 이미지 읽어오기
    im = plt.imread("./plot0.png")

    # axes에 이미지 출력
    ax.imshow(im)

    # 이미지 빼고 나머지 요소들 지우기
    ax.grid(b=None)
    ax.set_facecolor("None")
    ax.set_xticks([])
    ax.set_yticks([])

    fig.tight_layout()

  • 뭔가 이상합니다.
  • axes안에 이미지가 들어가 버려서 좋지 않습니다.
    1. figure와 axes 사이 여백때문에 그림의 여백이 추가됩니다.
    2. figure size를 원래 그림과 맞춰주어도 살짝 그림이 눌리면서 해상도가 나빠집니다.

2.2. fig.figimage()

  • axes가 아니라 figure에 이미지를 담는 것으로 해결할 수 있습니다.
  • fig.figimage()명령을 사용합니다.
  • 그리고 아까처럼 테두리 제작용으로 커다란 axes를 만들어 줍니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    fig, ax = plt.subplots(figsize=(10, 5))

    # 이미지 읽어오기
    im = plt.imread("./43_toomany_1.png")

    # figure에 이미지 출력
    fig.figimage(im, resize=False)

    # axes 안보이게 하기
    ax.set_xticks([])
    ax.set_yticks([])
    ax.axis("off")

    # 테두리 제작용 커다란 axes 만들기
    ax_big = fig.add_subplot(111)
    ax_big.set_xticks([])
    ax_big.set_yticks([])

    fig.tight_layout()
    fig.savefig("plot1.png")
  • 주피터 노트북에 보이는 도형들이 줄을 제대로 맞추지 않고 있습니다.
  • 그러나 걱정하지 않아도 됩니다. 저장된 그림 파일을 열어보면 정상적으로 줄이 맞아 있습니다.
  • axes 바깥쪽을 건드릴 때 종종 주피터 노트북에 보이는 결과와 파일로 저장되는 경우가 다릅니다.
  • 파일로 저장한 결과를 사용한다면, 가끔 저장된 파일을 열어볼 필요가 있습니다.

2.3. 위에 다른 그림 그리기

  • 끊긴 테두리를 이어주기 위해서 ax_big을 만들었습니다.
  • 그런데 생각해보면 이것도 하나의 axes입니다.
  • 이 위에 다른 데이터를 그림으로 그리거나, 다양한 도형을 patches로 추가할 수 있습니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    fig, ax = plt.subplots(figsize=(10, 5))

    im = plt.imread("./43_toomany_1.png")
    fig.figimage(im, resize=False)

    ax.set_xticks([])
    ax.set_yticks([])
    ax.axis("off")

    ax_big = fig.add_subplot(111, zorder=2)
    ax_big.set_xticks([])
    ax_big.set_yticks([])
    ax_big.set_facecolor("none")

    ax_big.spines["left"].set_visible(False)
    ax_big.spines["right"].set_visible(False)
    ax_big.spines["top"].set_bounds(0.1, 1)
    ax_big.spines["bottom"].set_bounds(0.1, 1)
    ax_big.spines["bottom"].set_position(("outward", -34))

    X = np.linspace(0, 1, 51)
    c = np.random.random(size=51)

    ax_big.scatter(X, np.sin(10*X), c=c, s=300*c**2, cmap="jet", alpha=0.3)
    ax_big.set_ylim(-1.5, 1.5)

    fig.tight_layout()

  • ax_big 공간에 사인 그래프로 장난을 쳐 봤습니다.
  • 본인의 용도에 따라 마음껏 활용하시면 됩니다.


도움이 되셨나요? 카페인을 투입하시면 다음 포스팅으로 변환됩니다

Share