📊 데이터공학

NumPy

Numerical Python

Python의 수치 연산 라이브러리. 다차원 배열(ndarray)과 벡터 연산 제공. 데이터 과학의 기초.

📖 상세 설명

NumPy(Numerical Python)는 2005년 Travis Oliphant가 Numeric과 Numarray를 통합해 만든 Python 수치 컴퓨팅 라이브러리입니다. C로 구현된 ndarray(N-dimensional array)는 Python 리스트보다 50-100배 빠른 벡터 연산을 제공하며, 데이터 과학 생태계 전체의 기반입니다.

브로드캐스팅(Broadcasting)은 NumPy의 핵심 기능으로, 서로 다른 형상의 배열 간 연산을 자동으로 처리합니다. (3, 4) 배열과 (4,) 배열을 더하면 자동으로 행 방향으로 확장해 연산합니다. 명시적 반복문 없이 벡터화된 코드를 작성할 수 있어 성능과 가독성 모두 향상됩니다.

주요 기능으로 선형대수(np.linalg), 푸리에 변환(np.fft), 난수 생성(np.random), 통계 함수(mean, std, percentile)를 제공합니다. dtype으로 float32, float64, int64 등 메모리 효율적인 데이터 타입을 지정하고, reshape, transpose, concatenate로 배열 형상을 조작합니다.

Pandas DataFrame, TensorFlow/PyTorch 텐서, scikit-learn 입력은 모두 NumPy 배열과 호환됩니다. GPU 가속이 필요하면 CuPy(NVIDIA CUDA), JAX(Google TPU)가 NumPy API를 그대로 사용합니다.

💻 코드 예제

import numpy as np

# 배열 생성
arr = np.array([1, 2, 3, 4, 5])
matrix = np.arange(12).reshape(3, 4)  # 0~11을 3x4 행렬로
zeros = np.zeros((2, 3), dtype=np.float32)
ones = np.ones((3, 3))
identity = np.eye(4)  # 4x4 단위행렬

print(f"Shape: {matrix.shape}, Dtype: {matrix.dtype}")
# Shape: (3, 4), Dtype: int64

# 브로드캐스팅 예제
a = np.array([[1], [2], [3]])  # (3, 1)
b = np.array([10, 20, 30, 40])  # (4,)
result = a + b  # (3, 4) - 자동 확장
print(result)
# [[11 21 31 41]
#  [12 22 32 42]
#  [13 23 33 43]]

# 벡터화 연산 (반복문 대신)
data = np.random.randn(1000000)
# 느림: [x**2 for x in data]
squared = data ** 2  # 빠름: 벡터화 연산

# 인덱싱과 슬라이싱
arr = np.arange(10)
print(arr[2:7:2])      # [2 4 6] - start:stop:step
print(arr[arr > 5])    # [6 7 8 9] - 불리언 인덱싱

# 고급 인덱싱 (Fancy Indexing)
indices = np.array([1, 3, 5])
print(arr[indices])    # [1 3 5]

# 선형대수
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])

print(A @ B)              # 행렬 곱셈
print(np.linalg.det(A))   # 행렬식: -2.0
print(np.linalg.inv(A))   # 역행렬
eigenvalues, eigenvectors = np.linalg.eig(A)

# 통계 함수
data = np.random.normal(100, 15, size=1000)
print(f"평균: {np.mean(data):.2f}")
print(f"표준편차: {np.std(data):.2f}")
print(f"중앙값: {np.median(data):.2f}")
print(f"95 퍼센타일: {np.percentile(data, 95):.2f}")

# 축(axis) 기반 연산
matrix = np.random.randint(0, 10, size=(3, 4))
print(f"열별 합계: {matrix.sum(axis=0)}")  # (4,)
print(f"행별 합계: {matrix.sum(axis=1)}")  # (3,)

# 배열 결합
arr1 = np.array([[1, 2], [3, 4]])
arr2 = np.array([[5, 6]])
vertical = np.vstack([arr1, arr2])    # (3, 2)
horizontal = np.hstack([arr1, arr1])  # (2, 4)

🗣️ 실무 대화 예시

시니어: "피처 전처리 로직인데, for문으로 100만 행 돌리니까 5분 걸려요. NumPy 벡터화하면 1초 이내로 줄일 수 있어요."

주니어: "브로드캐스팅이 뭔가요?"

시니어: "shape이 다른 배열 연산 시 자동으로 차원을 맞춰주는 거예요. (1000, 10) 배열에서 각 열의 평균을 빼려면 그냥 data - data.mean(axis=0) 하면 돼요."

면접관: "NumPy 성능 최적화 경험이 있나요?"

지원자: "반복문을 벡터화 연산으로 대체하고, dtype을 float64에서 float32로 줄여 메모리를 절반으로 줄였습니다. 큰 배열은 np.memmap으로 디스크 매핑해서 메모리 부족 문제를 해결했고, 연산 집약적인 부분은 Numba의 @jit 데코레이터로 JIT 컴파일했습니다."

리뷰어: "arr.copy() 빼먹으면 view 반환되어 원본 수정될 수 있어요. 슬라이싱 결과를 수정할 거면 명시적으로 copy() 호출하세요."

개발자: "아, 그래서 원본 데이터가 바뀌었군요. 바로 수정하겠습니다."

⚠️ 주의사항

🔥 실제 장애 사례

ML 스타트업 2022

float64→float32 변환 시 정밀도 손실로 모델 성능 급락

원인: 메모리 최적화 위해 무분별하게 dtype을 float32로 변경, 금융 모델에서 정밀도 손실 발생

영향: 거래 예측 모델 정확도 15% 하락, 실제 손실 발생

해결: 중요 컬럼은 float64 유지, 덜 중요한 피처만 float32 사용

교훈: dtype 변경 전 도메인별 정밀도 요구사항 검토, 검증 데이터셋으로 영향 테스트

일반 사례 공통

슬라이싱 view 반환으로 원본 데이터 오염

원인: arr[::2] 슬라이싱이 view를 반환하는 것을 몰라 수정 시 원본도 변경됨

영향: 훈련 데이터가 변형되어 모델 재학습 필요, 디버깅에 이틀 소요

해결: .copy()로 명시적 복사 후 수정

교훈: NumPy 슬라이싱은 view 반환, 수정 전 항상 .copy() 고려

📝 이해도 퀴즈

Q1. NumPy의 브로드캐스팅(Broadcasting)이란?

Q2. arr = np.arange(10); b = arr[2:5] 후 b[0] = 99를 하면?

Q3. NumPy 성능 최적화 방법으로 올바른 것은?

📚 더 배우기

💬 질문 & 기여

이 페이지에 오류가 있거나 추가하고 싶은 내용이 있다면 알려주세요!