Picking and Modifying Colors

  • 데이터 시각화를 언어라고 한다면 색상은 단어입니다.
  • “여기는 무엇입니다”를 보여주기도 하고 “얼마나 큽니다”를 보여주기도 합니다.
  • 좋은 단어는 상황을 정확하게 전달할 뿐 아니라 글을 아름답게 합니다.
  • 좋은 색상은 데이터를 효과적으로 전달할 뿐 아니라 보는 사람의 마음을 즐겁고 편안하게 합니다.

1. 좋은 색상 가져오기

Finding the right color paletts for data visualizations
Claus Wilke, “데이터 시각화 교과서”, 영문판(Free)
Colin Ware “데이터 시각화, 인지과학을 만나다”
An alternative to pink & blue: Colors for gender data

  • 많은 전문가들이 적절한 색상 선택의 중요성을 강조합니다.
  • 파워포인트라면 색상 팔레트를 열고 색상을 눈으로 보며 하나를 고를텐데, 코딩을 하려면 이 점이 어렵습니다.
  • 제가 일을 할 때 matplotlib의 List of names colors이 한 켠에 떠있는 이유입니다.
  • 같은 데이터를 남들은 어떻게 표시했는지 궁금할 때는 구글링을 합니다.

  • 남녀 성별을 파랑과 빨강 외에 다른 색으로 표시하고 싶어 찾다가 이런 글을 찾았습니다.


  • python으로 그림에서 색상을 가져오는 방법이전 글에서 설명했습니다.

  • 이 방법의 단점은 색상의 좌표를 정확히 알기 어렵다는 것입니다.

  • 정확한 좌표를 찾아 시행 착오를 반복해야 합니다.

  • 이번 글에서는 파워포인트와의 협업으로 효율성을 높이는 방법을 알아봅니다.

1.1. powerpoint

  • 여기서는 파워포인트를 예시로 들고 있습니다.
  • 그러나 RGB 색상을 뽑을 수 있다면 무엇이든 관계없습니다.
  • (1) 파워포인트에 그림을 붙여 넣습니다.


  • (2) 그림을 선택한 후, 칠하기 또는 윤곽선 색에서 스포이드를 고릅니다.


  • (3) 그림 위에서 마우스를 잡고 움직이면서 내가 원하는 색이 맞는지 찾습니다.


  • (4) 2초쯤 기다리면 색이 RGB 값으로 바뀝니다. 기억합니다.


1.2. python

matplotlib: Specifying Colors
matplotlib.axes.Axes.plot
Pega Devlog: matplotlib plot()
Pega Devlog: Matplotlib colors

  • Matplotlib은 여러 가지의 색상 입력을 지원합니다.
    1. float array : [0, 1] 범위의 RGB, RGBA 입력을 지원합니다 - (0.1, 0.2, 0.5) or (0.1, 0.2, 0.5, 0.3)
    2. hex code : 대소문자 무관하게 RGB, RGBA 입력을 지원합니다 - “#0f0f0f” or “#0f0f0f80”
    3. string gray level : [0, 1] 사이 값으로 gray level을 표현합니다 - “0.5”
    4. 그 외에도 named colors, cycle number를 지원합니다만 여기서는 다루지 않겠습니다.
  • 파워포인트에서 읽은 색상을 그대로 사용하거나, hex code로 바꾸어 사용합니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import numpy as np
    from matplotlib import colors

    # 파워포인트에서 읽은 값
    c_ppt = [25, 160, 170]

    # [0, 1] array로 변경
    c_array = np.array(c_ppt)/255
    print(f"# c_array= {c_array}")

    # hex code로 변경
    c_hex = colors.to_hex(np.array([25, 160, 170])/255)
    print(f"# c_hex= ''{c_hex}'")
  • 실행 결과:
    1
    2
    # c_array= [0.09803922 0.62745098 0.66666667]
    # c_hex= ''#19a0aa'
  • 제대로 선택됐는지 그려봅니다.

  • 평소와는 조금 다른 spiral 예제를 사용합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import matplotlib.pyplot as plt

    t = np.arange(0.1, 10*np.pi, 0.1)

    r = 10/np.log1p(t)
    X = r * np.cos(t)
    Y = r * np.sin(t)

    fig, axs = plt.subplots(ncols=2, figsize=(8, 4), sharex=True, sharey=True)
    axs[0].plot(X, Y, "o-", c=[0.09803922, 0.62745098, 0.66666667])
    axs[0].set_title("c_array")
    axs[1].plot(X, Y, "o-", c="#19a0aa")
    axs[1].set_title("c_hex")
    axs[0].set_xlim(-5, 5)
    axs[0].set_ylim(-5, 5)


  • 원하는 색을 잘 가져왔습니다.

  • 표현 방식이 달라도 동일한 색상이 표현됩니다.

2. 색 조정하기

  • 우리 뇌에서 색은 상대적으로 인식됩니다.
  • 원본에서는 아주 좋아보이던 색도 막상 가져오면 주변의 영향으로 색감이 달라보입니다.
  • 나에게 맞게 색상(hue), 채도(saturation), 명도(brightness, value)를 바꿔줄 필요가 있습니다.

2.1. hsv 공간 활용

  • RGB 공간에서 수정할 수도 있지만 이럴 때는 HSV 공간이 더 효과적입니다.
  • 변경된 색상을 다시 표현하려면 RGB로 재변환해야 합니다.
  • RGB → HSV → HSV(수정) → RGB(수정)은 이렇게 진행됩니다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 입력 색상 (RGB)
    print(f"# c_array (old)= {c_array}")

    # 입력 색상 (HSV)
    c_hsv = colors.rgb_to_hsv(c_array)
    print(f"# c_hsv (old)= {c_hsv}")

    # 출력 색상 (HSV)
    c_hsv_new = c_hsv + np.array([0.1, -0.5, 0.2])
    print(f"# c_hsv (new)= {c_hsv_new}")

    # 출력 색상 (RGB)
    c_array_new = colors.hsv_to_rgb(c_hsv_new)
    print(f"# c_array (new)= {c_array_new}")
  • 변경 전/후 색상을 나란히 그려봅니다.
    1
    2
    3
    4
    5
    6
    7
    fig, axs = plt.subplots(ncols=2, figsize=(8, 4), sharex=True, sharey=True)
    axs[0].plot(X, Y, "o-", c=c_array)
    axs[0].set_title("c_array (old)")
    axs[1].plot(X, Y, "o-", c=c_array_new)
    axs[1].set_title("c_array (new)")
    axs[0].set_xlim(-5, 5)
    axs[0].set_ylim(-5, 5)

  • hue + 0.1, saturation - 0.5, value + 0.2 효과가 반영되었습니다.

2.2. h, s, v 제어 함수

  • 함수로 저장해두면 앞으로 종종 사용하기 좋습니다.
  • 색상, 채도, 명도를 제어하는 함수를 각각 따로 만듭니다.
  • hex code를 넣었을 때도 동작하게 합니다.
  • 1. 색상(hue) 제어 함수

  • 색상에 특정 값을 더하거나 빼서 제어합니다.

  • HSV 공간에서 hue는 시작과 끝이 없이 순환합니다. [0, 1]을 벗어나도 거기에 해당하는 결과를 출력하게 합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    def mod_hue(rgb, d_hue):
    """
    rgb : (np.array, list or hex code) RGB color
    d_hue : (float) Hue change of the rgb
    """
    if isinstance(rgb, str):
    rgb = colors.to_rgb(rgb)
    if isinstance(d_hue, str):
    d_hue = colors.to_rgb(d_due)

    hsv = colors.rgb_to_hsv(rgb)
    hsv += np.array([d_hue, 0, 0])
    hsv[0] = hsv[0]%1

    return colors.hsv_to_rgb(hsv)
  • spiral 예제에 적용합니다.

  • 색상 순환이 구현되었습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    fig, axes = plt.subplots(ncols=4, nrows=3, figsize=(12, 9))
    axs = axes.ravel()

    for ax, dh in zip(axs, np.linspace(0, 1, 12)):
    c = mod_hue(c_array, dh)
    ax.plot(X, Y, "o-", c=c)
    ax.set_xlim(-5, 5)
    ax.set_ylim(-5, 5)
    ax.axis(False)


  • 2. 채도(saturation) 제어 함수

  • 채도는 정의에 따라 [0, 1] 범위로 제한되어 있습니다.

  • 이 값을 벗어나면 안되는데, matplotlib의 rgb_to_hsv()명령에 자동 제어 기능이 없으니 넣어줍니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    def mod_sat(rgb, d_sat):
    """
    rgb : (np.array, list or hex code) RGB color
    d_sat : (float) saturation change of the rgb
    """
    if isinstance(rgb, str):
    rgb = colors.to_rgb(rgb)
    if isinstance(d_sat, str):
    d_sat = colors.to_rgb(d_sat)

    hsv = colors.rgb_to_hsv(rgb)
    hsv += np.array([0, d_sat, 0])
    if hsv[1] > 1:
    hsv[1] = 1
    elif hsv[1] < 0:
    hsv[1] = 0

    return colors.hsv_to_rgb(hsv)
  • spiral 예제에 적용합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    fig, axes = plt.subplots(ncols=4, nrows=3, figsize=(12, 9))
    axs = axes.ravel()

    for ax, ds in zip(axs, np.linspace(-0.8, 0.1, 12)):
    c = mod_sat(c_array, ds)
    ax.plot(X, Y, "o-", c=c)
    ax.set_xlim(-5, 5)
    ax.set_ylim(-5, 5)
    ax.axis(False)


  • 3. 명도(value) 제어 함수

  • 명도도 채도와 마찬가지로 [0, 1] 범위로 제한되어 있습니다.

  • 자동 제어 기능을 넣어줍니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    def mod_val(rgb, d_val):
    """
    rgb : (np.array, list or hex code) RGB color
    d_sat : (float) value change of the rgb
    """
    if isinstance(rgb, str):
    rgb = colors.to_rgb(rgb)
    if isinstance(d_val, str):
    d_val = colors.to_rgb(d_val)

    hsv = colors.rgb_to_hsv(rgb)
    hsv += np.array([0, 0, d_val])
    if hsv[2] > 1:
    hsv[2] = 1
    elif hsv[2] < 0:
    hsv[2] = 0
    return colors.hsv_to_rgb(hsv)
  • spiral 예제에 적용합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    fig, axes = plt.subplots(ncols=4, nrows=3, figsize=(12, 9))
    axs = axes.ravel()

    for ax, dv in zip(axs, np.linspace(-0.6, 0.3, 12)):
    c = mod_val(c_array, dv)
    ax.plot(X, Y, "o-", c=c)
    ax.set_xlim(-5, 5)
    ax.set_ylim(-5, 5)
    ax.axis(False)


2.3. [h, s, v] 동시 제어 함수

  • h, s, v를 list 형태로 넣어서 동시에 제어합니다.

    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
    28
    29
    30
    31
    def mod_color(rgb, d_hsv):
    """
    rgb : (np.array, list or hex code) RGB color
    d_hsv : (np.array or list) value change in hsv space
    """
    if isinstance(rgb, str):
    rgb = colors.to_rgb(rgb)
    if isinstance(d_hsv, str):
    d_hhsv = colors.to_rgb(d_hsv)

    hsv = colors.rgb_to_hsv(rgb)
    d_hue, d_sat, d_val = d_hsv

    hsv += np.array([d_hue, d_sat, d_val])

    # hue circulation
    hsv[0] = hsv[0]%1

    # saturation limited in [0, 1]
    if hsv[1] > 1:
    hsv[1] = 1
    elif hsv[1] < 0:
    hsv[1] = 0

    # value limited in [0, 1]
    if hsv[2] > 1:
    hsv[2] = 1
    elif hsv[2] < 0:
    hsv[2] = 0

    return colors.hsv_to_rgb(hsv)
  • spiral 예제에 적용합니다.

  • 2차원 공간에서 hue와 saturation을 바꿔봅니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    fig, axes = plt.subplots(ncols=12, nrows=12, figsize=(12, 12))

    for i in range(len(axes)):
    for j in range(len(axes[i])):
    c = mod_color(c_array, np.array([i*0.05, j*-0.08, 0]))
    axes[i,j].plot(X, Y, "o-", c=c, ms=2)
    axes[i,j].set_xlim(-5, 5)
    axes[i,j].set_ylim(-5, 5)
    axes[i,j].axis(False)

3. 결론

  • 파워포인트와 파이썬은 모두 편리하고 강력합니다.
  • 따로 사용하기보다 각각의 장점을 살려서 시너지를 얻을 수 있습니다.
  • 적극적으로 아이디어를 섞을 필요가 있습니다.


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

Share