1. NumPy 배열(ndarray) 기본 구조
NumPy 배열은 ndarray라고 불리는 다차원 배열 객체입니다. 다음과 같은 두 부분으로 구성됩니다:
- 실제 데이터
- 데이터를 설명하는 메타데이터 (차원, 모양, 데이터 타입 등)
2. 배열의 주요 속성
import numpy as np
# 3차원 배열 생성
data = np.array([
[[1, 2, 3, 4, 5, 6, 7], [1, 2, 3, 4, 5, 6, 7], [1, 2, 3, 4, 5, 6, 7]],
[[1, 2, 3, 4, 5, 6, 7], [1, 2, 3, 4, 5, 6, 7], [1, 2, 3, 4, 5, 6, 7]]
])
print(data) # 배열 출력 (리스트와 달리 쉼표 없이 출력)
print(data.ndim) # 배열 차원의 개수 (축 개수, rank라고도 함)
print(data.shape) # 배열의 모양 (n행 m열인 경우 (n, m))
print(data.size) # 배열의 전체 요소 개수 (n * m)
print(data.dtype) # 배열 요소의 데이터 타입 (type() 함수와 유사하지만 속성)
print(data.itemsize) # 배열 각 요소의 바이트 크기 (int32는 4, float64는 8)
print(data.data) # 실제 배열 데이터가 저장된 버퍼 (일반적으로 인덱스로 접근)
3. 배열 생성 방법
3.1. array() 함수 사용
arr1 = np.array(range(10)) # 정수형 배열
arr2 = np.array([1, 2, 3.14, 4, 5]) # 실수형 배열 (정수와 실수 혼합 시 실수형으로 변환)
arr3 = np.array([[1, 2, 3], ('a', 'b', 'c')]) # 2차원 배열: 중첩된 시퀀스 (리스트, 튜플 모두 가능)
print(arr1, type(arr1), arr1.dtype)
print(arr2, type(arr2), arr2.dtype)
print(arr3, arr3.shape, arr3.ndim, arr3.size) # 2차원 배열, 총 6개 요소
3.2. arange() 함수 사용
print(np.arange(10)) # 0부터 9까지 정수형 배열
print(np.arange(10.0)) # 0.0부터 9.0까지 실수형 배열
print(np.arange(5, 12)) # 5부터 11까지 정수형 배열
print(np.arange(5.0, 12, 2)) # 5.0부터 12.0까지, 간격 2인 실수형 배열
print(np.arange(10000)) # 배열이 너무 크면 중간 부분을 생략하고 모서리만 출력
3.3. linspace() 함수 사용
# numpy.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)
res1 = np.linspace(2.0, 3.0, num=5) # 2.0 ~ 3.0 사이를 5등분
res2 = np.linspace(2.0, 3.0, num=5, endpoint=False) # 마지막 값(3.0) 미포함
res3 = np.linspace(2.0, 3.0, num=5, retstep=True) # (배열, 간격) 튜플 반환
print(res1, type(res1))
print(res2)
print(res3, type(res3))
3.4. zeros(), ones() 및 *_like() 함수
# zeros(): 모든 요소를 0으로 채운 배열 생성
zero_arr1 = np.zeros(5) # 1차원, 5개 요소
zero_arr2 = np.zeros((2, 2)) # 2x2 배열
print(zero_arr1, zero_arr1.dtype)
print(zero_arr2, zero_arr2.dtype)
# zeros_like(): 주어진 배열과 동일한 shape와 dtype을 가진 0으로 채워진 배열 생성
ref_arr = np.array([list(range(5)), list(range(5, 10))])
zeros_like_arr = np.zeros_like(ref_arr)
print(ref_arr)
print(zeros_like_arr)
# ones() / ones_like(): 모든 요소를 1로 채움 (zeros와 동일한 방식)
ones_arr1 = np.ones(9)
ones_arr2 = np.ones((2, 3, 4))
ones_like_arr = np.ones_like(ref_arr)
print(ones_arr1)
print(ones_arr2)
print(ones_like_arr)
3.5. eye() 함수 (단위 행렬)
# N x N 단위 행렬 생성 (대각선은 1, 나머지는 0)
identity_matrix = np.eye(5)
print(identity_matrix)
4. NumPy 일반 함수 (배열 조작)
4.1. 배열 모양 변경 (reshape, resize, T)
arr1 = np.arange(10)
arr2 = np.ones((5, 2))
# .T 속성: 전치(transpose)
# 1차원 배열은 전치해도 변하지 않음
print(arr1, '\n', arr1.T)
print(arr2, '\n', arr2.T)
# reshape(): 데이터는 유지하면서 배열 모양만 변경 (요소 개수 일치 필요)
reshaped1 = arr1.reshape(2, 5) # 10개 요소를 2x5로 변경
reshaped2 = np.zeros((4, 6)).reshape(3, 8) # 24개 요소를 3x8로 변경
reshaped3 = np.reshape(np.arange(12), (3, 4)) # 12개 요소를 3x4로 변경
print(arr1, '\n', reshaped1)
print(reshaped2)
print(reshaped3)
# resize(): 지정된 shape에 맞게 요소를 복사하여 채움 (부족하면 반복, 초과하면 잘라냄)
resized_arr = np.resize(np.arange(5), (3, 4)) # 5개 요소를 3x4(12개)로 복사하여 채움
print(resized_arr)
4.2. 배열 복사 (copy)
original = np.arange(10)
shallow_copy = original # 참조 복사 (같은 메모리 공간을 가리킴)
print(shallow_copy is original) # True
original[2] = 9
print(original, shallow_copy) # 둘 다 변경됨
deep_copy = original.copy() # 깊은 복사 (새로운 메모리 공간)
print(deep_copy is original) # False
original[0] = 99
print(original, deep_copy) # original만 변경됨
4.3. 데이터 타입 변환 (astype)
float_arr = np.arange(10, dtype=float)
print(float_arr, float_arr.dtype)
int_arr = float_arr.astype(np.int32) # 실수형 배열을 32비트 정수형으로 변환
print(int_arr, int_arr.dtype)
print(float_arr, float_arr.dtype) # 원본은 변경되지 않음
4.4. 배열 결합 (hstack, vstack, stack)
# hstack(horizontal stack): 수평으로 배열 결합 (열 방향)
a = np.arange(5) # 1차원, 5개
b = np.arange(5, 9) # 1차원, 4개
h_combined = np.hstack((a, b)) # shape가 달라도 가능
print(a, a.shape)
print(b, b.shape)
print(h_combined, h_combined.shape)
# 2차원 배열의 hstack (행 수가 같아야 함)
a_2d = np.array([[1], [2], [3]]) # 3행 1열
b_2d = np.array([['a'], ['b'], ['c']]) # 3행 1열
h_combined_2d = np.hstack((a_2d, b_2d))
print(a_2d, a_2d.shape)
print(b_2d, b_2d.shape)
print(h_combined_2d, h_combined_2d.shape)
# vstack(vertical stack): 수직으로 배열 결합 (행 방향)
v_combined = np.vstack((a, b)) # a, b는 1차원 배열
print(a, a.shape)
print(b, b.shape)
print(v_combined, v_combined.shape)
# stack(): 새로운 축을 따라 배열 결합 (shape가 같아야 함)
stacked_axis0 = np.stack((a, b)) # axis=0, shape (2, 5)
stacked_axis1 = np.stack((a, b), axis=1) # axis=1, shape (5, 2)
print(a, a.shape)
print(b, b.shape)
print(stacked_axis0, stacked_axis0.shape)
print(stacked_axis1, stacked_axis1.shape)
4.5. 배열 분할 (hsplit, vsplit)
arr = np.arange(16).reshape(4, 4)
# hsplit(): 수평으로 분할 (열 방향)
h_split = np.hsplit(arr, 2) # 4열을 2개로 분할 (2열씩)
print(arr)
print(h_split, type(h_split)) # 리스트로 반환
# vsplit(): 수직으로 분할 (행 방향)
v_split = np.vsplit(arr, 4) # 4행을 4개로 분할 (1행씩)
print(v_split, type(v_split))
4.6. 기본 연산 및 통계 함수
arr = np.arange(6).reshape(2, 3)
# 스칼라 연산
print(arr + 10) # 덧셈
print(arr * 2) # 곱셈
print(1 / (arr + 1)) # 나눗셈
print(arr ** 0.5) # 제곱근
# 통계 함수
print(arr.mean()) # 평균
print(arr.max()) # 최대값
print(arr.min()) # 최소값
print(arr.std()) # 표준 편차
print(arr.var()) # 분산
print(arr.sum(), np.sum(arr, axis=0)) # 총합, 열 기준 합계
print(np.sort(np.array([1, 4, 3, 2, 5, 6]))) # 정렬
5. 인덱싱과 슬라이싱
5.1. 기본 인덱싱 및 슬라이싱
# 1차원 배열
one_d = np.arange(20)
print(one_d)
print(one_d[4]) # 5번째 요소 (인덱스 4)
print(one_d[3:6]) # 인덱스 3부터 5까지
# 2차원 배열
two_d = np.arange(16).reshape(4, 4)
print(two_d, '차원:', two_d.ndim)
print(two_d[2], '차원:', two_d[2].ndim) # 3번째 행 (1차원 배열)
print(two_d[2][1]) # 3행 2열 요소
print(two_d[1:3], '차원:', two_d[1:3].ndim) # 2~3행 (2차원 배열)
print(two_d[2, 2]) # 3행 3열 요소
print(two_d[:2, 1:]) # 1~2행, 2~4열
# 3차원 배열
three_d = np.arange(8).reshape(2, 2, 2)
print(three_d, '차원:', three_d.ndim)
print(three_d[0], '차원:', three_d[0].ndim) # 첫 번째 2D 레이어
print(three_d[0][0], '차원:', three_d[0][0].ndim) # 첫 번째 레이어의 첫 번째 행
print(three_d[0][0][1]) # 첫 번째 레이어의 첫 번째 행, 두 번째 열
5.2. 인덱싱/슬라이싱을 통한 값 변경 및 복사
arr = np.arange(10)
print(arr)
arr[5] = 100 # 특정 요소 값 변경
arr[7:9] = 200 # 슬라이스 범위 값 변경
print(arr) # 원본 배열이 변경됨
# 깊은 복사를 통해 원본 보호
original = np.arange(10)
copied = original.copy()
copied[7:9] = 200
print(original) # 원본은 변경되지 않음
print(copied)
6. 난수 생성
6.1. 정규 분포 (normal)
# 표준 정규 분포 (평균=0, 표준편차=1)에서 4x4 샘플 생성
samples = np.random.normal(size=(4, 4))
print(samples)
6.2. 균일 분포 (rand)
scalar = np.random.rand() # 0~1 사이 난수 1개
vector = np.random.rand(4) # 0~1 사이 난수 4개 (1차원 배열)
matrix = np.random.rand(2, 3) # 0~1 사이 난수 6개 (2x3 배열)
print(scalar, type(scalar))
print(vector, type(vector))
print(matrix, type(matrix))
samples1 = np.random.rand(1000) # 1000개의 난수
samples2 = np.random.rand(1000)
6.3. 표준 정규 분포 (randn)
samples1 = np.random.randn(1000) # 평균 0, 표준편차 1
samples2 = np.random.randn(1000)
6.4. 정수 난수 (randint)
# numpy.random.randint(low, high=None, size=None, dtype='l')
# high가 None이면 [0, low) 범위, 아니면 [low, high) 범위
print(np.random.randint(2)) # [0, 2) 범위 정수 1개
print(np.random.randint(2, size=5)) # [0, 2) 범위 정수 5개
print(np.random.randint(2, 6, size=5)) # [2, 6) 범위 정수 5개
print(np.random.randint(2, size=(2, 3))) # [0, 2) 범위 정수 2x3 배열
print(np.random.randint(2, 6, (2, 3))) # [2, 6) 범위 정수 2x3 배열
7. 데이터 입출력
7.1. 배열 저장 (.npy 파일)
import os
os.chdir('./') # 저장할 디렉토리로 변경 (예: 현재 디렉토리)
data = np.random.rand(5, 5)
np.save('array_data.npy', data)
# 또는 절대 경로 사용: np.save('/path/to/array_data.npy', data)
7.2. 배열 불러오기 (.npy 파일)
loaded_data = np.load('array_data.npy')
print(loaded_data)
# 또는 절대 경로 사용: np.load('/path/to/array_data.npy')
7.3. 텍스트 파일 저장/불러오기
data = np.random.rand(5, 5)
# 텍스트 파일로 저장 (쉼표 구분)
np.savetxt('array_data.txt', data, delimiter=',')
# 텍스트 파일 불러오기
loaded_txt = np.loadtxt('array_data.txt', delimiter=',')
print(loaded_txt)
# 또는 절대 경로 사용: np.loadtxt('/path/to/array_data.txt')