Skip to content

12주차 · 데이터 시각화

  1. 그래프를 그리기 전 질문, x/y 변수, 단위, 고정 조건을 문장으로 정의한다
  2. 제목 · 축 라벨 · 단위 · 축 범위를 갖춘 matplotlib 그래프를 작성한다
  3. NumPy 배열로 데이터를 생성하고 처리한 뒤 시각화 전 self-check를 수행한다
  4. 데이터 질문에 맞는 그래프 종류(선/산점/막대)를 선택하고, 부적절한 선택도 설명한다
  5. 축 절단, 라벨 누락, 전처리 은폐, 표본 수 누락 등 오해 유발 요소를 식별하고 수정한다
  6. 고차원 조건을 2D 그래프로 줄일 때 숨은 변수와 해석 한계를 밝힌다
  7. 결과를 관찰–원인 가설–시사점–한계 4문장으로 정리한다

그래프 설계 quickstart: 코드 전에 이 6가지를 먼저 답하기

Section titled “그래프 설계 quickstart: 코드 전에 이 6가지를 먼저 답하기”

그래프를 그리기 전에 아래 6가지 질문에 먼저 답하세요. 이 답이 없으면 코드가 맞아도 해석이 쉽게 흔들립니다.

  1. 이 그래프가 답하려는 질문은 무엇인가?
  2. 무엇이 변하는가? → x축 변수와 단위
  3. 무엇을 측정하거나 비교하는가? → y축 변수와 단위
  4. 비교를 공정하게 만들기 위해 어떤 조건을 고정했는가?
  5. 축 범위, log scale, 확대 표시가 결론을 과장하거나 숨기지 않는가?
  6. 이 그래프만으로 말할 수 없는 해석 한계는 무엇인가?

그래프를 보고서에 넣기 전, 먼저 아래 사양서를 채운다고 생각하세요. 사양서는 코드를 길게 만드는 문서가 아니라 그래프가 오해를 만들지 않도록 막는 안전장치입니다.

항목작성할 내용빠지면 생기는 위험
질문이 그림이 답하는 한 문장그림은 있는데 결론이 없음
x/y 변수독립 변수와 측정·비교할 값원인과 결과를 혼동함
단위s, V, dBm, Hz, Ω 등수치 크기 해석 불가
축 범위/스케일0 기준, 확대, log 여부변화가 과장되거나 묻힘
고정 조건온도, 거리, 부하, 샘플링 조건 등다른 조건을 같은 데이터처럼 비교함
전처리평균, 필터링, 이상치 제거 여부원본과 처리 결과를 혼동함
표본 수/불확실성n, 반복 수, 노이즈 범위우연한 차이를 설계 결론으로 오해함
해석 한계이 그래프로 말할 수 없는 것과잉 결론을 내림

2) 설계 사양을 matplotlib 코드로 옮기기

Section titled “2) 설계 사양을 matplotlib 코드로 옮기기”

아래 코드는 단순히 선을 그리는 예제가 아닙니다. title, xlabel, ylabel, ylim, legend는 질문, 단위, 축 범위, 계열 구분을 코드로 옮긴 결과입니다.

import matplotlib.pyplot as plt
import numpy as np
t = np.linspace(0, 1, 100) # 시간축 (s)
v = 3.3 * np.sin(2 * np.pi * 5 * t) # 5 Hz 정현파 (V)
title = '5 Hz Sine Wave — fixed 0-1 s interval'
x_label = 'Time (s)'
y_label = 'Voltage (V)'
axis_reason = 'y축은 ±3.3 V 신호가 잘리지 않도록 -4~4 V로 설정'
fixed_condition = '주파수 5 Hz, 관측 구간 0-1 s'
print('labels_ready:', all([title, x_label, y_label]))
print('same_length:', len(t) == len(v))
print('axis_reason:', axis_reason)
print('fixed_condition:', fixed_condition)
plt.figure(figsize=(7, 4))
plt.plot(t, v, linewidth=2, color='royalblue', label='V(t)')
plt.title(title)
plt.xlabel(x_label)
plt.ylabel(y_label)
plt.ylim(-4, 4)
plt.grid(alpha=0.3)
plt.legend()
plt.show()
  1. xy 길이가 같은지 먼저 확인한다 (len(x) == len(y))
  2. 제목은 그래프가 답하는 질문 또는 고정 조건을 드러내게 쓴다
  3. 축 라벨에는 단위를 반드시 포함한다
  4. 축 범위, log scale, 확대 표시를 썼다면 이유를 설명한다
  5. 원본/전처리 결과, 고정 조건, 비교 조건을 구분한다
  6. 그래프 뒤에 해석 문장과 한계 문장을 붙인다

데이터 질문권장 그래프이유
”시간에 따라 값이 어떻게 변하나?”선 그래프(Line)순서, 추세, 기울기 확인 용이
”두 변수의 관계는?”산점도(Scatter)상관, 분산, 이상점 파악 용이
”범주별 크기 비교는?”막대 그래프(Bar)카테고리 비교 명확
”값 분포가 어떻게 생겼나?”히스토그램(Histogram)빈도 분포 확인 가능
”조건이 여러 개인가?”작은 여러 그래프 또는 색/마커 1개 추가숨은 변수를 분리해 비교 가능

4) 차원의 저주: 2D 그래프가 숨기는 변수

Section titled “4) 차원의 저주: 2D 그래프가 숨기는 변수”

실제 공학 데이터는 한두 개 숫자로 끝나지 않습니다. RSSI를 예로 들면 거리뿐 아니라 안테나 종류, 장애물, 측정 시간, 주변 전파 환경, 송신 출력, 측정 위치가 함께 영향을 줍니다. 하지만 2D 그래프는 보통 x축과 y축 두 변수만 보여 줍니다. 이때 나머지 조건이 섞이면 그럴듯하지만 틀린 상관관계가 생길 수 있습니다.

여기서는 차원의 저주를 많은 조건을 2D 그림 하나로 줄일 때 정보가 사라지는 문제라는 실무적 의미로 사용합니다.

고차원 데이터를 2D 그래프로 줄일 때의 원칙

Section titled “고차원 데이터를 2D 그래프로 줄일 때의 원칙”
  1. 한 그래프는 한 질문만 답하게 한다.
  2. 비교하지 않는 조건은 제목, 캡션, 출력 문장에 고정 조건으로 적는다.
  3. 세 번째 변수는 색 또는 마커 하나까지만 조심해서 쓴다.
  4. 조건이 많으면 한 그림에 몰아넣지 말고 작은 여러 그래프나 표와 그래프로 나눈다.
  5. 결론 문장에는 “이 조건에서”라는 범위를 붙인다.
  6. 원인 확정이 아니라 관찰이면 “~때문으로 보인다”처럼 가설로 표현한다.

# 예시 코드
import matplotlib.pyplot as plt
x = [1, 2, 3, 4]
y = [51, 52, 53, 54]
plt.plot(x, y, color='red')
plt.ylim(50.8, 54.2) # y축을 과도하게 잘라 변화가 과장됨
plt.title('Result') # 제목이 모호함
# 축 라벨·단위 없음
plt.show()

문제점: 축 라벨이 없고, 제목이 모호하며, y축 절단 때문에 작은 변화가 크게 보입니다. 이 그래프만 보면 51V에서 54V로 변한 차이가 심각한 설계 실패처럼 보일 수 있습니다.

아래 이미지는 위 코드처럼 y축을 좁게 잘라 저장한 결과입니다. 실제 변화량보다 변화가 크게 보이는지 확인하세요.

Y-axis cropped matplotlib example

# 예시 코드
import matplotlib.pyplot as plt
x = [1, 2, 3, 4]
y = [51, 52, 53, 54]
plt.figure(figsize=(6, 4))
plt.plot(x, y, marker='o', linewidth=2, color='royalblue', label='측정값')
plt.title('출력 전압 vs 테스트 단계')
plt.xlabel('테스트 단계 (-)')
plt.ylabel('출력 전압 (V)')
plt.ylim(0, 60) # 맥락을 보존하는 축 범위
plt.grid(alpha=0.3)
plt.legend()
plt.show()

개선점: 제목이 질문을 드러내고, 축 라벨에 단위가 있으며, y축 범위가 데이터 맥락을 보존합니다. 확대가 필요하다면 전체 범위 그림과 확대 그림을 함께 제시하거나, 축을 잘랐다는 사실을 명시해야 합니다.

아래 이미지는 위 코드처럼 제목, 축 라벨, 단위, y축 범위를 갖춰 저장한 결과입니다. 같은 데이터라도 축 범위와 라벨이 해석을 어떻게 바꾸는지 비교하세요.

Properly labeled matplotlib example


6) 엔드-투-엔드 파이프라인: 설계 사양 → 데이터 → 처리 → 그래프 작성

Section titled “6) 엔드-투-엔드 파이프라인: 설계 사양 → 데이터 → 처리 → 그래프 작성”

공학 시각화는 보통 다음 4단계로 진행합니다.

  1. 설계 사양: 질문, 단위, 고정 조건, 해석 한계를 먼저 쓴다
  2. 데이터 생성/로드: NumPy로 배열 생성 또는 측정값 로드
  3. 처리와 검증: 정규화, 통계, 필터링을 수행하고 길이·범위를 점검
  4. 그래프 작성과 해석: plt.plot, plt.title, plt.xlabel, plt.ylabel로 그림을 완성하고 해석 문장을 붙인다
코드역할확인할 점
plt.figure(figsize=(...))새 그림 영역 만들기너무 작거나 길쭉하지 않은가
plt.plot(x, y, ...)선 그래프 그리기len(x) == len(y)인가
plt.scatter(x, y, ...)산점도 그리기점의 흩어짐을 봐야 하는 질문인가
plt.title(...)그래프 제목질문 또는 고정 조건이 드러나는가
plt.xlabel(...), plt.ylabel(...)축 라벨과 단위단위가 포함되어 있는가
plt.ylim(...), plt.xscale(...)축 범위와 스케일확대·log scale 이유를 설명할 수 있는가
plt.grid(...), plt.legend()읽기 보조 요소여러 선이 있을 때 색 외에도 구분되는가
plt.show()그림 표시그림 뒤에 해석과 한계 문장을 붙였는가
import numpy as np
import matplotlib.pyplot as plt
# 1) 설계 사양
title = '10 Hz Sine Signal — Raw vs Smoothed'
x_label = 'Time (s)'
y_label = 'Voltage (V)'
fixed_condition = '관측 구간 0-1 s, 같은 난수 seed=42'
preprocess_note = '이동 평균 window=10으로 스무딩'
interpretation_limit = '시뮬레이션 데이터이므로 실제 센서 잡음 원인을 확정할 수 없음'
# 2) 데이터 생성 (NumPy)
rng = np.random.default_rng(42)
t = np.linspace(0, 1, 200) # 시간 (s)
v = 5.0 * np.sin(2 * np.pi * 10 * t) # 10 Hz 신호 (V)
v_noisy = v + 0.4 * rng.normal(size=t.size) # 잡음 추가
# 3) 처리 — 이동 평균 스무딩
window = 10
v_smooth = np.convolve(v_noisy, np.ones(window) / window, mode='same')
# 4) self-check
print('labels_ready:', all([title, x_label, y_label]))
print('same_length:', len(t) == len(v_noisy) == len(v_smooth))
print('fixed_condition:', fixed_condition)
print('preprocess_note:', preprocess_note)
# 5) 시각화
plt.figure(figsize=(8, 4))
plt.plot(t, v_noisy, alpha=0.4, color='gray', linestyle='--', label='Raw noisy')
plt.plot(t, v_smooth, linewidth=2, color='royalblue', label='Smoothed')
plt.title(title)
plt.xlabel(x_label)
plt.ylabel(y_label)
plt.legend()
plt.grid(alpha=0.3)
plt.show()
print('한계:', interpretation_limit)

결과를 정리할 때 아래 4문장 구조를 사용하세요. 관찰과 원인 가설을 분리하면, 그래프를 보고 바로 단정하는 실수를 줄일 수 있습니다.

  1. 관찰: “A에서 B로 변할 때, C가 증가/감소했다.”
  2. 원인 가설: “이는 D 특성 때문으로 보인다.”
  3. 공학적 시사점: “따라서 E 조건에서 운용/설계를 권장한다.”
  4. 한계: “다만 F 조건은 확인하지 않았으므로, 이 그래프만으로는 G를 확정할 수 없다.”

예시:

  1. 같은 안테나와 같은 측정 환경에서 거리가 1m에서 6m로 증가할 때, RSSI가 -38 dBm에서 -60 dBm으로 감소했다.
  2. 이는 자유공간 경로손실이 거리의 로그에 비례해 증가하기 때문으로 보인다.
  3. 따라서 안정적인 수신을 위해 이 조건에서는 송수신 거리를 3m 이내로 유지하는 설계를 권장한다.
  4. 다만 장애물, 안테나 방향, 주변 전파 환경을 바꾸어 반복 측정하지 않았으므로 거리 외 요인의 영향은 확정할 수 없다.

예제 1 · NumPy 데이터 → matplotlib 그래프 작성 실행하면 matplotlib 그래프가 아래에 표시됩니다
Ready

위 예제의 데이터를 matplotlib 코드로 저장하면 아래와 같습니다. 이 이미지는 예제 1과 같은 50개 점, 같은 전압값을 사용합니다.

5 Hz sine wave rendered with matplotlib

  1. NumPy로 등간격 시간축과 정현파를 생성합니다. W11에서 배운 np.linspace와 vectorization을 그대로 활용합니다.
  2. 제목·축 라벨·데이터 길이를 먼저 점검한 뒤 plt.plot으로 그리면 실수를 줄일 수 있습니다.
  3. all([…])로 라벨이 모두 비어 있지 않은지 한 번에 검사합니다.
  4. 플로팅 전에 min/max/mean으로 데이터 범위를 확인하면 축 범위 설정에 도움이 됩니다.
  5. axis_reasonfixed_condition을 출력해 축 범위와 실험 조건을 그래프 밖에서도 확인합니다.
  6. assert로 x/y 길이와 라벨 존재 여부를 자동 확인하고, 마지막에 해석 한계를 적습니다.

개념역할예시
np.diff(v) / np.diff(t)구간 기울기(변화율) 계산RSSI 감소율 (dB/m)
f-string 출력해석 문장을 코드로 자동 생성f"평균 기울기: {slope:.2f}"
zip(a, b)두 배열을 같은 순서의 쌍으로 순회for d, r in zip(distance_m, rssi_dbm):

zip() 이해하기: 인덱스 루프와 비교

Section titled “zip() 이해하기: 인덱스 루프와 비교”

두 리스트나 배열을 함께 순회할 때, 인덱스로 접근하는 방식과 zip을 사용하는 방식을 비교해 봅시다.

names = ["sensor_A", "sensor_B", "sensor_C"]
values = [22.5, 23.1, 21.8]
# 방법 1: 인덱스로 접근 (이미 알고 있는 방식)
for i in range(len(names)):
print(names[i], values[i])
# 방법 2: zip 으로 쌍을 꺼내기 (같은 결과, 더 간결)
for name, val in zip(names, values):
print(name, val)

zip(a, b)ab에서 같은 순서의 원소를 묶어 줍니다. 인덱스 변수 i가 필요 없어 실수가 줄어듭니다.


예제 2 · 거리-RSSI 파이프라인 (데이터 → 처리 → 그래프 → 해석) 실행하면 matplotlib 그래프가 아래에 표시됩니다
Ready

아래 정적 그림은 예제 2의 기준 결과입니다. 콘솔에서는 거리·RSSI 표, labels_ready, same_length, fixed_condition, hidden_variable_note, 해석 문장이 서로 맞는지 점검하세요.

RSSI versus distance rendered with matplotlib

  1. 측정 데이터를 NumPy 배열로 정의합니다. dtype=float을 명시해 이후 나누기 연산이 정확하게 됩니다.
  2. np.diff로 인접 구간의 변화량을 구하고 거리 차이로 나눠 기울기(dB/m)를 얻습니다.
  3. plt.plot에 거리 배열과 RSSI 배열을 넣어 처리 결과를 그래프로 확인합니다.
  4. zip(distance_m, rssi_dbm)으로 인덱스 없이 두 배열을 동시에 순회합니다.
  5. 관찰–원인 가설–시사점–한계 4문장 해석 템플릿을 f-string과 일반 출력으로 완성합니다.

실수겉으로 보이는 문제왜 위험한가수정 방법
라벨·단위 누락x, y만 보임물리적 의미 해석 불가Time (s), Voltage (V)처럼 명시
y축 절단 미표기작은 차이가 크게 보임결론 왜곡0 기준 또는 확대 표시 명시
x/y 길이 불일치런타임 오류 또는 이상한 그래프데이터 오정렬assert len(x) == len(y)
원본 없이 스무딩 결과만 표시그래프가 깔끔해 보임노이즈와 이상치가 숨겨짐원본과 처리 결과를 함께 표시
서로 다른 조건을 한 선으로 연결추세가 있는 것처럼 보임존재하지 않는 인과관계 생성조건별로 나누거나 고정 조건 명시
색만으로 계열 구분화면에서는 보이지만 인쇄나 색각 차이에서는 불명확비교 대상 혼동marker, linestyle, legend를 함께 사용
표본 수/반복 수 누락평균값만 보임우연한 차이를 일반화n, 반복 수, 불확실성 범위 표시
해석 문장 없음그림만 있음의사결정 근거 없음관찰–원인 가설–시사점–한계 작성
라벨 빈 문자열"" 가 그대로 출력됨독자에게 정보 없음all([title, x_label, y_label]) 점검

로컬에서도 같은 그림을 저장하고 싶다면

Section titled “로컬에서도 같은 그림을 저장하고 싶다면”

보고서나 발표 자료에 그래프를 넣을 때는 파일로 저장합니다. 로컬 Python, Jupyter, Colab에서는 아래 패턴을 사용하세요.

import matplotlib.pyplot as plt
plt.figure(figsize=(7, 4))
plt.plot(x, y, marker="o")
plt.title(title)
plt.xlabel(x_label)
plt.ylabel(y_label)
plt.grid(alpha=0.3)
plt.tight_layout()
plt.savefig("my_plot.png", dpi=160)
plt.show()

목표: 시간에 따라 증가하는 전압 데이터를 공학 그래프 설계 기준에 맞게 표현합니다.

해야 할 일: 그래프 질문, 제목, 축 라벨, 단위, 축 범위 이유를 정한 뒤 plt.plot으로 그리세요.

완료 조건: labels_ready, same_length, axis_reason, fixed_condition이 출력되고 그래프가 표시되어야 합니다.

실습 문제 1 · 그래프 사양과 matplotlib 작성 실행하면 matplotlib 그래프가 아래에 표시됩니다
Ready

  • 질문: 이 그래프가 답하는 질문이 제목이나 캡션에 드러나는가?
  • 단위: x축과 y축 라벨에 단위가 있는가?
  • 스케일: 축 범위, 확대, log scale이 결론을 과장하지 않는가?
  • 데이터 정렬: len(x) == len(y)와 조건별 데이터 매칭을 확인했는가?
  • 조건: 비교 조건과 고정 조건을 구분했는가?
  • 전처리: 원본, 스무딩, 필터링, 이상치 제거 여부를 표시했는가?
  • 차원: 2D 그래프에서 숨은 변수나 빠진 조건을 설명했는가?
  • 해석: 관찰–원인 가설–시사점–한계를 모두 작성했는가?