NumPy의 모든 것은 ndarray(N-dimensional array) 라는 단일 자료구조 위에 세워집니다.
ndarray는 같은 자료형(dtype)의 값들을 연속된 메모리 블록에 저장하고, 그 위에서 반복문 없는 연산(벡터화)을 제공합니다.
이 챕터에서는 "왜 list 대신 ndarray인가", "ndarray를 읽는 법(속성)", "view와 copy의 차이"를 잡습니다. 이후 모든 챕터가 이 세 가지를 전제합니다.
import numpy as nplist 대신 NumPy ndarray인가파이썬 list는 서로 다른 타입의 객체를 담을 수 있는 유연한 컨테이너입니다. 그 유연성의 대가로, list는 내부적으로 "값"이 아니라 "값을 가리키는 포인터"를 나열해서 저장합니다. 정수 하나를 더하려 해도 파이썬은 매번 객체를 해석하고(type check), 박싱/언박싱을 거칩니다.
NumPy는 반대 선택을 합니다. 배열 전체를 하나의 dtype으로 고정하는 대가로 유연성을 포기하고, 값 자체를 C 배열처럼 연속으로 쌓습니다. 그 결과는 다음과 같습니다.
for 문 없이 a + b, a > 0, a.mean(axis=0) 처럼 배열 전체를 한 줄로 다룹니다.정리: list는 "여러 종류의 물건을 넣는 서랍", ndarray는 "같은 규격 부품을 채운 트레이"입니다. 데이터 전처리는 후자입니다.
디버깅의 90%는 "이 배열의 shape가 무엇인가"를 확인하는 것에서 시작합니다. 아래 속성은 반드시 눈에 익혀 둡니다.
a = np.array([[1, 2, 3], [4, 5, 6]])
a.dtype # dtype('int64') - 원소 자료형 (Windows + NumPy 1.x는 int32)
a.shape # (2, 3) - 차원별 크기 (행 2, 열 3)
a.ndim # 2 - 차원 수 = 축(axis) 개수 = len(a.shape)
a.size # 6 - 전체 원소 수 = shape의 곱
a.itemsize # 8 - 원소 1개의 바이트 크기 (int32면 4)
a.nbytes # 48 - 전체 데이터 바이트 = size * itemsizeshape: 가장 자주 봅니다. (2, 3)은 "2행 3열"입니다. 1차원은 (3,)처럼 콤마가 붙습니다 ((3)은 그냥 정수 3).ndim: 스칼라 0, 벡터 1, 행렬 2, 그 이상은 텐서입니다. axis 인자를 쓸 때 이 값이 상한입니다.dtype: 3장에서 자세히 다룹니다. 기본 정수 dtype은 플랫폼·버전에 따라 다르므로, 재현이 중요하면 np.array([...], dtype=np.int64)로 못 박습니다.두 용어는 앞으로 계속 나오므로 여기서 정의만 잡습니다.
for를 도는" 대신 "배열 단위 연산 한 방"으로 표현하는 것입니다. 루프가 사라지는 게 아니라, 루프가 C 계층으로 내려가서 빨라집니다.# 벡터화 이전
result = []
for x in data:
result.append(x * 2 + 1)
# 벡터화 이후
result = data * 2 + 1(2, 3) 배열 + 스칼라, (2, 3) 배열 + (3,) 배열. → 자세한 규칙은 Python - NumPy 06. 브로드캐스팅.NumPy 연산 결과는 두 종류입니다.
a[1:4]), 전치(a.T), reshape(대개)이 view를 만듭니다.a[a > 0]), 팬시 인덱싱(a[[0, 2]]), astype, flatten이 copy를 만듭니다.
view를 수정하면 원본도 함께 바뀝니다.a = np.arange(5) # array([0, 1, 2, 3, 4])
b = a[1:4] # view (메모리 공유)
b[0] = 99
a # array([ 0, 99, 2, 3, 4]) ← 원본이 변했다!
c = a[1:4].copy() # 명시적 복사본
c[0] = -1
a # array([ 0, 99, 2, 3, 4]) ← 원본 그대로메모리 공유 여부 확인:
np.shares_memory(a, b) # True - 정확한 검사
np.shares_memory(a, c) # False
# a.base 는 공개 속성이지만 메모리 공유 판정에는 부정확 → shares_memory를 쓸 것실무 규칙: 함수 인자로 받은 배열을 수정할 때는 먼저
.copy()합니다. 그렇지 않으면 호출자의 데이터를 조용히 오염시킵니다.
ndarray는 1차원 메모리 블록 + "그 블록을 어떻게 다차원으로 해석할지"를 나타내는 stride 정보로 이루어집니다.
[[1,2,3],[4,5,6]] → 메모리에 1 2 3 4 5 6.order='F', column-major): 열을 먼저 채웁니다. 메모리에 1 4 2 5 3 6. Fortran·MATLAB 호환.
전치 a.T는 데이터를 한 바이트도 복사하지 않습니다. 단지 stride를 뒤집어 "읽는 순서"만 바꾼 view입니다. 그래서 매우 쌉니다.a = np.arange(6).reshape(2, 3)
a.T.shape # (3, 2)
np.shares_memory(a, a.T) # True대부분의 전처리에서는 order를 신경 쓸 필요가 없지만, "전치 후 reshape가 갑자기 느려지거나 copy가 발생"하는 현상의 배경이 이것입니다.
댓글
GitHub 계정으로 의견이나 질문을 남길 수 있습니다.