Skip to content

11주차 · NumPy와 SciPy 기초

  1. 라이브러리와 import 개념
  2. listnumpy.ndarray의 핵심 차이
  3. shape/dtype를 점검하는 습관
  4. 반복문 대신 vectorization 기초
  5. scipy.signal의 스무딩/피크 탐색 기초
  6. 흔한 오류를 스스로 진단하는 체크리스트
  7. import numpy as np, np.array(...) 기본 문법을 자신 있게 읽기
  8. C 배열 연산과 NumPy 배열 연산의 차이를 설명하기
  9. np.dot, @ 연산자, np.linalg.solve로 행렬 연산 수행하기
  10. 브로드캐스팅 형태 호환 규칙을 표로 설명하고 EE 예제에 적용하기

NumPy 문법 quickstart: 처음 보는 학생은 이것부터

Section titled “NumPy 문법 quickstart: 처음 보는 학생은 이것부터”

NumPy를 쓸 때는 보통 아래 3단계를 반복합니다.

import numpy as np
arr = np.array([1, 2, 3], dtype=float)
print(arr.shape)
print(arr * 2)
  1. import numpy as np : numpynp라는 짧은 이름으로 가져온다.
  2. np.array([...]) : 리스트를 배열로 바꾼다.
  3. arr.shape, arr.dtype : 계산 전에 구조와 타입을 확인한다.
  4. arr * 2, arr + arr : 반복문 없이 원소별 연산이 된다.

Python 기본 문법만으로도 계산은 가능하지만, 반복적 수치 계산은 느리고 코드가 길어집니다.

  • 라이브러리: 재사용 가능한 기능 묶음
  • import로 가져와 사용
  • 검증된 함수를 쓰면 실수를 줄일 수 있음
import numpy as np
from scipy import signal
  • numpy as np: 가장 널리 쓰는 별칭
  • scipy.signal: 1차원 신호 처리 함수 집합

항목Python 리스트NumPy 배열(ndarray)
주 용도일반 컨테이너수치 계산
a + b이어붙이기원소별 덧셈
shape 정보없음있음 (arr.shape)
dtype 제어약함명시 가능

오해: “list도 숫자니까 계산 결과가 같겠지”

Section titled “오해: “list도 숫자니까 계산 결과가 같겠지””
a = [1, 2, 3]
b = [10, 20, 30]
print(a + b) # [1, 2, 3, 10, 20, 30]

의도했던 [11, 22, 33]이 아닙니다.

import numpy as np
arr_a = np.array([1, 2, 3])
arr_b = np.array([10, 20, 30])
print(arr_a + arr_b) # [11 22 33]

C와 비교: 배열 계산 감각이 이렇게 달라집니다

Section titled “C와 비교: 배열 계산 감각이 이렇게 달라집니다”
항목CPython + NumPy기억할 점
배열 생성타입/크기 선언np.array([...])Python은 런타임에 더 유연하다
원소별 덧셈보통 루프 직접 작성arr_a + arr_bNumPy가 브로드캐스트/원소연산 수행
평균 계산루프 + 누적 변수arr.mean()의도가 더 직접적이다
구조 점검수동 관리shape, dtype계산 전 검사 루틴이 쉽다
double a[3] = {1, 2, 3};
double b[3] = {10, 20, 30};
double out[3];
for (int i = 0; i < 3; i++) {
out[i] = a[i] + b[i];
}

핵심은 NumPy는 루프를 숨기고 수식 자체를 드러내므로, 수치 코드를 더 짧고 덜 실수하게 만든다는 점입니다.


3) shape와 dtype은 “계산 전 건강검진”

Section titled “3) shape와 dtype은 “계산 전 건강검진””
import numpy as np
arr = np.array([1, 2, 3, 4], dtype=float)
print(arr.shape) # (4,)
print(arr.dtype) # float64 (환경에 따라 다를 수 있음)
  • shape: 데이터 구조가 기대와 같은지 확인
  • dtype: 정밀도/연산 방식 확인

4) Vectorization: 반복문보다 간결하고 안전한 배열 연산

Section titled “4) Vectorization: 반복문보다 간결하고 안전한 배열 연산”
values = [1.0, 2.0, 3.0]
out = []
for v in values:
out.append(v * 2)
print(out)
import numpy as np
arr = np.array([1.0, 2.0, 3.0])
out = arr * 2
print(out)

초보자에게 vectorization이 좋은 이유:

  • 코드 줄 수 감소
  • 인덱스 실수 감소
  • 수식 의도가 더 잘 보임

5) SciPy signal 기초: 스무딩과 피크 탐색

Section titled “5) SciPy signal 기초: 스무딩과 피크 탐색”

SciPy는 NumPy 위에서 동작하는 과학 계산 라이브러리입니다. 이번 주에는 1차원 신호 처리에 자주 쓰이는 두 함수를 배웁니다.

함수역할주요 파라미터
signal.savgol_filter(y, window_length, polyorder)Savitzky-Golay 방식으로 신호를 스무딩(노이즈 완화)window_length: 창 크기(홀수), polyorder: 다항식 차수
signal.find_peaks(y, height, distance)극대값(봉우리) 인덱스 탐색height: 최소 높이, distance: 피크 간 최소 간격

스무딩이 필요한 이유: 센서나 측정 데이터에는 항상 잡음이 섞입니다. 잡음을 그대로 두면 find_peaks가 작은 흔들림을 피크로 잘못 감지합니다. 스무딩 후 피크를 찾으면 더 안정적입니다.

from scipy import signal
# window_length는 반드시 홀수 (짝수이면 오류 발생)
y_smooth = signal.savgol_filter(y, window_length=11, polyorder=2)
# height=0.5: 높이 0.5 이상인 봉우리만 탐색
# distance=10: 두 피크 사이 최소 간격 10 인덱스
peaks, props = signal.find_peaks(y_smooth, height=0.5, distance=10)

예제 1 · NumPy 기초(shape/dtype/vectorization) Runs in-browser with Pyodide
Ready
  1. list를 곧바로 계산에 쓰지 않고 ndarray로 변환해 연산 의미를 고정합니다.
  2. dtype=float를 명시해 정밀도 관련 혼란을 줄입니다.
  3. shapedtype를 먼저 출력해 입력 상태를 진단합니다.
  4. 정규화 수식은 배열 전체에 한 번에 적용됩니다(vectorization).
  5. + 1e-12는 0으로 나누는 위험을 완화하는 안전장치입니다.
  6. 동일 기호 +가 list와 ndarray에서 다르게 동작함을 직접 비교합니다.
  7. assert로 구조와 dtype을 마지막에 확인해 실수를 빠르게 잡습니다.

예제 2를 읽기 전에 아래 함수들의 역할을 먼저 파악하세요.

함수역할예시
np.linspace(start, stop, num)start부터 stop까지 num개 등간격 숫자 배열 생성np.linspace(0, 1, 5)[0, 0.25, 0.5, 0.75, 1]
np.random.default_rng(seed)재현 가능한 난수 생성기 객체 생성. seed를 고정하면 매번 같은 난수rng = np.random.default_rng(42)
rng.normal(size=n)평균 0, 표준편차 1의 정규분포 난수 n개 생성rng.normal(size=200)
np.diff(arr)배열에서 인접 원소 간 차이(차분) 계산. 길이가 1 줄어듦np.diff([1,3,6])[2,3]
np.round(arr, decimals)배열 원소를 지정 소수점 자리로 반올림np.round([1.2345], 2)[1.23]

예제 2 · SciPy signal 기초(스무딩과 피크 탐색) Runs in-browser with Pyodide
Ready

raw vs smoothed vs peaks를 눈으로 읽는 미니 차트

Section titled “raw vs smoothed vs peaks를 눈으로 읽는 미니 차트”
indexrawsmoothedpeaksquick read
00.0460.008시작점은 낮음
1-0.0920.073노이즈 때문에 잠깐 내려감
20.2380.133전체 흐름은 다시 상승
30.3300.189완만하게 올라가는 중
4-0.0420.241raw는 흔들려도 smooth는 상승 유지
50.1190.290작은 흔들림이 줄어듦
60.3920.379첫 큰 봉우리 쪽으로 접근
70.3810.454아직 검출 조건 전
index 0 1 2 3 4 5 6 7
raw ▂ ▁ ▄ ▅ ▁ ▃ ▆ ▆
smoothed ▁ ▂ ▂ ▃ ▃ ▄ ▅ ▆
peaks · · · · · · · ·
  • raw: 노이즈 때문에 위아래 흔들림이 큽니다.
  • smoothed: 작은 흔들림을 줄여서 큰 흐름을 읽기 쉽습니다.
  • peaks: 이 초반 8개 샘플에서는 아직 검출되지 않고, 실제 첫 피크는 인덱스 14, 25, 35에서 잡힙니다.
  1. 난수 시드를 고정하면 다른 수강생의 결과와도 동일한 값을 재현해 비교할 수 있습니다.
  2. np.linspace(0, 4*np.pi, 200)으로 등간격 x축을 만듭니다. 200개 점이 생깁니다.
  3. 원 신호 y는 일부러 잡음을 포함해 현실성을 높였습니다.
  4. np.diff(y)는 인접 값의 차이를 보여주어 신호가 얼마나 빠르게 변하는지 확인합니다.
  5. sample rawsample smooth를 함께 보면, 작은 흔들림이 줄어드는 방향을 숫자로도 확인할 수 있습니다.
  6. savgol_filter로 급격한 흔들림을 완화해 피크 탐색 안정성을 높입니다.
  7. find_peaksheight는 최소 높이, distance는 피크 간 최소 간격입니다.
  8. 인덱스 ipeaks[i]를 직접 꺼내 실제 좌표값을 출력합니다.
  9. assert len(peaks) > 0로 파라미터가 완전히 실패하지 않았는지 확인합니다.


6) 행렬 연산: np.dot, @ 연산자, np.linalg.solve

Section titled “6) 행렬 연산: np.dot, @ 연산자, np.linalg.solve”

공학 문제에서는 단순 원소별 연산 외에도 행렬 곱셈연립방정식 풀기가 자주 필요합니다.

함수/연산자역할예시
np.dot(A, B)행렬 곱셈 또는 벡터 내적np.dot(A, x) → 행렬-벡터 곱
A @ Bnp.dot와 동일 (Python 3.5+, 가독성 좋음)A @ x
np.linalg.solve(A, b)연립방정식 Ax = b 풀기x = np.linalg.solve(Z, V)

EE 예제 A: 임피던스 행렬과 전압 계산

Section titled “EE 예제 A: 임피던스 행렬과 전압 계산”

2포트 회로에서 임피던스 행렬 Z와 전류 벡터 I가 주어지면, 전압 V = Z @ I 로 계산합니다.

import numpy as np
# 임피던스 행렬 Z (단위: Ω)
Z = np.array([[50, 10],
[10, 75]], dtype=float)
# 전류 벡터 I (단위: A)
I = np.array([2.0, 1.0])
# 전압 벡터 V = Z @ I
V = Z @ I
print("V (Volt):", V) # [110. 95.]

EE 예제 B: 연립방정식으로 분압 계산

Section titled “EE 예제 B: 연립방정식으로 분압 계산”

저항 회로에서 KVL/KCL을 행렬 방정식 Ax = b 로 세우면 np.linalg.solve로 한 번에 풀 수 있습니다.

import numpy as np
# 예: 두 절점의 전압 V1, V2 를 구하는 방정식
# 방정식 1: 3*V1 - V2 = 12
# 방정식 2: -V1 + 4*V2 = 8
A = np.array([[3, -1],
[-1, 4]], dtype=float)
b = np.array([12.0, 8.0])
x = np.linalg.solve(A, b)
print("V1, V2 (Volt):", np.round(x, 3))
# 검증: A @ x 가 b 와 일치하는지
print("residual:", np.round(A @ x - b, 10))

7) 브로드캐스팅: 형태가 다른 배열 간 연산

Section titled “7) 브로드캐스팅: 형태가 다른 배열 간 연산”

브로드캐스팅은 형태(shape)가 다른 두 배열을 자동으로 맞춰서 연산하는 NumPy 규칙입니다. 반복문 없이 배열-스칼라, 2D-1D 연산이 가능합니다.

두 배열의 shape을 오른쪽부터 비교합니다. 각 자리에서:

  • 두 값이 같으면: 그대로 연산
  • 한쪽이 1이면: 1인 쪽을 상대 크기로 늘려서 연산
  • 둘 다 1보다 크고 다르면: 오류
A shapeB shape결과 shape가능 여부
(4,)() (스칼라)(4,)가능
(3, 4)(4,)(3, 4)가능 (행 방향 복제)
(3, 1)(4,)(3, 4)가능 (양방향 복제)
(3, 4)(3,)오류불가 (오른쪽 정렬 후 불일치)
(3, 4)(1, 4)(3, 4)가능

동일 저항망에서 여러 입력 전압을 한 번에 계산합니다.

import numpy as np
# 분배비 배열 (각 채널의 분압 비율)
ratios = np.array([0.5, 0.33, 0.25, 0.20]) # shape (4,)
# 여러 공급 전압 (shape (3, 1) 로 세로 배열)
supply_V = np.array([[5.0],
[9.0],
[12.0]]) # shape (3, 1)
# 브로드캐스팅: (3,1) x (4,) → (3, 4)
output_V = supply_V * ratios
print("shape:", output_V.shape) # (3, 4)
print(np.round(output_V, 3))
# 각 행 = 공급 전압, 각 열 = 분배비 채널

두 센서 신호의 정규화된 내적(유사도)을 브로드캐스팅으로 계산합니다.

import numpy as np
rng = np.random.default_rng(0)
sig_a = rng.normal(size=100)
sig_b = sig_a + 0.3 * rng.normal(size=100) # sig_a 에 노이즈 추가
# 정규화
sig_a_n = (sig_a - sig_a.mean()) / sig_a.std()
sig_b_n = (sig_b - sig_b.mean()) / sig_b.std()
# 내적 = 상관계수(표준화 시)
corr = np.dot(sig_a_n, sig_b_n) / len(sig_a_n)
print(f"신호 유사도(상관계수): {corr:.3f}")

안티패턴증상원인개선 방법
import numpynp.array 사용NameError별칭 불일치import numpy as np 고정
list 상태로 수식 적용이어붙이기/타입 혼란list와 ndarray 의미 차이 미이해계산 전 np.array(...) 변환
shape 확인 생략브로드캐스팅 오류입력 구조 미검증계산 직전 print(arr.shape)
dtype 무시예상과 다른 정밀도기본 dtype 착각dtype=float 명시
find_peaks 파라미터 감으로 설정피크 과다/누락기준값 의미 미이해height/distance를 단계 조정
짝수 window_length 사용함수 오류필터 조건 미확인Savitzky-Golay 창 길이는 홀수 사용

[기초] 배열 생성과 기본 연산: np.linspace로 배열을 만들고 원소별 연산을 적용하세요.

실습 문제 1 · 배열 생성과 원소별 연산 Runs in-browser with Pyodide
Ready