컴퓨터공학 300 주제 시리즈의 289번째 글이다. 전체 지도는 여기.

한 줄 요약

정보 시각화는 데이터의 값을 위치·길이·색 같은 시각 채널에 대응시켜 사람이 패턴을 빠르게 보게 하는 일이다. 어떤 채널을 쓰느냐에 따라 같은 데이터가 정확히 읽히기도, 잘못 읽히기도 한다.

왜 필요한가

평균, 분산, 상관계수 같은 요약 통계는 데이터를 몇 개의 숫자로 줄인다. 줄이는 과정에서 모양이 사라진다. 아래 “직접 해 보기”에서 보듯, 요약 통계가 소수점 둘째 자리까지 같은데 모양이 전혀 다른 데이터가 있다.

개발자는 시각화를 매일 본다. 모니터링 대시보드, 부하 테스트 결과, A/B 시험 보고서, 회고 자료. 잘못 그린 차트는 잘못된 결정을 부른다. 평균 응답 시간 그래프만 보고 “괜찮다”고 했다가, 꼬리 지연(p99)이 터진 것을 놓치는 식이다.

핵심 개념

데이터 유형

시각화는 데이터 유형에서 출발한다.

유형 예 의미 있는 연산
명목형(nominal) 노드 이름, 국가 같다/다르다
순서형(ordinal) 심각도 낮음·중간·높음 순서 비교
양적(quantitative) 응답 시간, 메모리 차이·비율
시간 타임스탬프 순서, 간격

명목형 데이터에 무지개 같은 연속 색을 쓰면 없는 순서를 암시한다. 양적 데이터에 서로 무관한 색 여러 개를 쓰면 크기 비교가 어렵다.

시각 채널과 정확도 순위

Cleveland 와 McGill(1984)은 사람에게 그래프에서 값의 비율을 판단하게 하는 실험을 했다. 결과는 대략 다음 순서였다. 위쪽일수록 정확하게 읽힌다.

1. 공통 축 위의 위치         (막대 끝, 점의 높이)
2. 정렬되지 않은 축 위의 위치  (여러 패널에 나뉜 차트)
3. 길이, 방향, 기울기
4. 각도
5. 면적
6. 부피, 곡률
7. 색의 농도·채도

실무 규칙이 여기서 나온다.

  • 정확한 비교가 목적이면 막대·점(위치)을 쓴다.
  • 파이 차트(각도·면적)는 비슷한 값의 비교에 약하다. 항목이 적고 “전체 대비 비중”이 핵심일 때만 쓴다.
  • 3D 막대는 원근 때문에 길이가 왜곡된다. 정보가 늘지 않는다.
  • 색 농도는 대략적 패턴(히트맵)에는 좋지만 정확한 값 비교에는 약하다.

정직한 축

  • 막대 그래프는 길이로 값을 표현하므로 y 축이 0 에서 시작해야 한다. 0 이 아닌 곳에서 자르면 작은 차이가 몇 배로 보인다.
  • 선 그래프는 변화를 보는 것이 목적이라 0 을 꼭 포함하지 않아도 된다. 대신 축 범위를 명시한다.
  • 두 개의 y 축(이중 축)은 축 범위를 마음대로 정해 아무 상관을 만들어 낼 수 있다. 가능하면 패널을 나눈다.
  • 값의 범위가 수십 배 이상 차이 나면 로그 축을 쓰고, 로그 축임을 표시한다.

색

  • 순차형(sequential): 작은 값에서 큰 값으로 한 방향. 메모리 사용률.
  • 발산형(diverging): 기준점에서 양쪽으로. 전주 대비 증감.
  • 범주형(categorical): 서로 구분되는 색. 노드별 선.

Matplotlib 문서는 viridis 같은 지각적으로 균일한 색 지도를 권장한다. 값이 커질 때 밝기도 일정하게 증가하므로 흑백 인쇄와 색각 이상 환경에서도 순서가 읽힌다. 무지개(jet) 색 지도는 밝기가 들쭉날쭉해 없는 경계를 만들어 낸다.

상호작용과 정보 탐색

Shneiderman 은 정보 탐색 인터페이스의 원칙을 “먼저 개요, 확대와 필터, 그다음 필요할 때 세부(Overview first, zoom and filter, then details-on-demand)”로 요약했다. Grafana 같은 대시보드가 전체 클러스터 요약 → 노드 선택 → 개별 파드 상세로 내려가는 구조가 이 원칙이다.

데이터-잉크 비율

Edward Tufte 는 차트의 잉크 중 데이터를 표현하는 비율을 높이라고 했다. 장식용 그림자, 진한 격자, 불필요한 3D 효과를 빼라는 뜻이다. 다만 축 레이블, 단위, 데이터 출처는 장식이 아니다. 지우면 안 된다.

직접 해 보기

Anscombe(1973)가 만든 네 데이터셋은 요약 통계가 거의 같다. Python 표준 라이브러리 statistics 로 확인하고, 두 개를 ASCII 산점도로 그린다(linear_regression, correlation 은 Python 3.10 이상).

import statistics as st

x123 = [10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5]
data = {
    "I":   (x123, [8.04, 6.95, 7.58, 8.81, 8.33, 9.96, 7.24, 4.26, 10.84, 4.82, 5.68]),
    "II":  (x123, [9.14, 8.14, 8.74, 8.77, 9.26, 8.10, 6.13, 3.10, 9.13, 7.26, 4.74]),
    "III": (x123, [7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 6.08, 5.39, 8.15, 6.42, 5.73]),
    "IV":  ([8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8],
            [6.58, 5.76, 7.71, 8.84, 8.47, 7.04, 5.25, 12.50, 5.56, 7.91, 6.89]),
}

for name, (x, y) in data.items():
    slope, intercept = st.linear_regression(x, y)
    print(f"{name:3s} 평균x={st.mean(x):.2f} 평균y={st.mean(y):.2f} "
          f"분산y={st.variance(y):.2f} r={st.correlation(x, y):.3f} "
          f"회귀 y={intercept:.2f}+{slope:.3f}x")

def scatter(x, y, w=30, h=10):
    grid = [[" "] * w for _ in range(h)]
    for xi, yi in zip(x, y):
        c = int((xi - 3) / (20 - 3) * (w - 1))
        r = h - 1 - int((yi - 2) / (13 - 2) * (h - 1))
        grid[r][c] = "o"
    return ["|" + "".join(row) for row in grid] + ["+" + "-" * w]

for name in ("II", "IV"):
    print(f"\n데이터셋 {name}")
    print("\n".join(scatter(*data[name])))

실행 결과다(산점도의 빈 줄 일부는 생략).

I   평균x=9.00 평균y=7.50 분산y=4.13 r=0.816 회귀 y=3.00+0.500x
II  평균x=9.00 평균y=7.50 분산y=4.13 r=0.816 회귀 y=3.00+0.500x
III 평균x=9.00 평균y=7.50 분산y=4.12 r=0.816 회귀 y=3.00+0.500x
IV  평균x=9.00 평균y=7.50 분산y=4.12 r=0.817 회귀 y=3.00+0.500x

데이터셋 II
|        o oo o o o
|      o           o
|     o
|   o
|
| o
+------------------------------

데이터셋 IV
|                           o
|
|
|        o
|        o
|        o
|        o
+------------------------------

네 줄의 숫자는 사실상 같다. 하지만 II 는 직선이 아니라 곡선이고, IV 는 x 가 거의 전부 8 인데 점 하나가 회귀선을 혼자 만든다. 상관계수 0.816 이라는 같은 숫자가 전혀 다른 현실을 가리킨다. 그려 보기 전에는 모른다.

현업에서는

  • 응답 시간 대시보드에 평균만 두지 않는다. p50·p95·p99 를 함께 그리거나 히트맵(시간 x 지연 구간 x 빈도)을 쓴다. 평균은 소수의 매우 느린 요청을 가린다.
  • 노드별 CPU 사용률을 선 여러 개로 그릴 때 노드가 많으면 “스파게티”가 된다. 상위 몇 개만 강조하고 나머지는 회색으로 두거나, 작은 패널 여러 개(small multiples)로 나눈다. 홈랩 k3s 처럼 노드가 몇 개뿐이면 선 그래프로 충분하다.
  • 장애 회고 자료의 그래프에는 축 단위, 시간대(UTC 인지 KST 인지), 데이터 출처를 반드시 적는다. 시간대 표기가 빠진 그래프는 사건 순서를 오해하게 만든다.
  • 상태 표시를 빨강·초록 색만으로 하면 색각 이상 사용자가 구분하지 못할 수 있다. 아이콘이나 글자를 함께 쓴다.

확인 문제

  1. 막대 그래프의 y 축을 0 에서 시작해야 하는 이유는? 선 그래프는 왜 덜 엄격한가?
  2. 여섯 개 서비스의 월별 비용 비교에 파이 차트보다 막대 그래프가 나은 이유를 Cleveland-McGill 결과로 설명하라.
  3. 명목형 데이터에 순차형 색 지도를 쓰면 생기는 문제는?
  4. Anscombe 데이터셋 IV 가 보여 주는 위험은?

풀이

  1. 막대는 길이로 값을 표현하므로 축을 자르면 길이 비율이 값의 비율과 달라진다. 선 그래프는 위치와 기울기로 변화를 보여 주므로 축 범위만 명시하면 된다.
  2. 막대는 공통 축 위의 위치·길이로 비교하고 파이는 각도·면적으로 비교한다. 앞쪽 채널이 더 정확하게 판단된다.
  3. 실제로 없는 순서나 크기 관계가 있는 것처럼 보인다.
  4. 극단값 하나가 회귀선과 상관계수를 지배할 수 있다는 것. 요약 통계만 보면 강한 상관이 있는 것처럼 오해한다.

더 읽을거리 (References)

  • Matplotlib, Choosing Colormaps in Matplotlib
  • W. S. Cleveland, R. McGill, “Graphical Perception: Theory, Experimentation, and Application to the Development of Graphical Methods,” Journal of the American Statistical Association, 79(387), 531–554, 1984.
  • F. J. Anscombe, “Graphs in Statistical Analysis,” The American Statistician, 27(1), 17–21, 1973.
  • E. R. Tufte, The Visual Display of Quantitative Information, 2nd ed., Graphics Press, 2001.