파이썬 과학 컴퓨팅의 기초와 핵심, Numerical Python 정복하기
파이썬을 활용한 과학 컴퓨팅의 여정에 오신 것을 환영합니다. 이 문서는 그 첫걸음이자 핵심 도구인 NumPy 라이브러리를 심도 있게 이해하고 효과적으로 활용할 수 있도록 돕기 위해 마련되었습니다.
NumPy는 대규모 다차원 배열과 행렬 연산에 필요한 다양한 기능을 제공하며, 이는 데이터 분석, 머신러닝, 공학 시뮬레이션 등 수많은 분야에서 고성능 컴퓨팅의 기반이 됩니다. 파이썬의 편리함과 C언어 수준의 실행 속도를 결합하여, 복잡한 수치 계산을 간결하고 효율적으로 처리할 수 있게 해줍니다.
본 가이드는 NumPy의 기본 개념인 ndarray부터 시작하여 배열 생성, 인덱싱, 조작, 유니버설 함수, 통계 및 선형 대수 기능에 이르기까지 핵심적인 내용을 체계적으로 다룹니다. 각 장의 상세한 설명과 실용적인 코드 예제, 그리고 연습 문제를 통해 독자 여러분이 NumPy의 강력함을 직접 경험하고 응용 능력을 키울 수 있도록 구성했습니다.
이 가이드가 여러분의 데이터 처리 및 분석 능력을 한 단계 끌어올리는 데 기여할 수 있기를 바랍니다. NumPy와 함께라면 복잡한 문제도 명쾌하게 해결해 나갈 수 있을 것입니다.
- ~ : ) (with Gemini)
이 문서는 NumPy의 다양한 기능들을 체계적으로 학습할 수 있도록 구성되었습니다. 각 장의 주요 내용과 학습 목표는 다음과 같습니다.
NumPy의 정의, 파이썬 과학 컴퓨팅에서의 중요성, 그리고 NumPy를 사용해야 하는 이유(성능, 다차원 배열, 벡터화 연산 등)에 대해 알아봅니다.
NumPy의 핵심 데이터 구조인 ndarray를 생성하는 다양한 방법들(np.array, np.zeros, np.ones, np.arange, np.linspace, 난수 생성 등)을 학습하고 실습합니다.
생성된 NumPy 배열의 차원(ndim), 형태(shape), 크기(size), 데이터 타입(dtype) 등 중요한 속성들을 확인하고 이해합니다.
배열의 특정 요소나 부분 집합에 접근하는 기본 인덱싱, 슬라이싱 방법과 더불어, 조건에 맞는 데이터를 추출하는 불리언 인덱싱, 특정 위치의 요소들을 선택하는 팬시 인덱싱을 학습합니다. 뷰(View)와 복사(Copy)의 개념도 중요하게 다룹니다.
배열의 형태를 바꾸거나(reshape, transpose), 여러 배열을 합치거나(concatenate, vstack, hstack), 나누는(split) 등 배열의 구조를 변경하는 다양한 기법을 익힙니다.
배열의 모든 요소에 대해 반복문 없이 빠르게 함수를 적용하는 유니버설 함수(UFuncs)에 대해 배웁니다. 산술, 삼각, 지수/로그, 비교, 논리 연산 등 다양한 UFuncs의 사용법을 익힙니다.
평균, 중앙값, 표준편차, 분산, 합계, 최댓값/최솟값, 백분위수, 상관계수 등 데이터 분석에 필수적인 기본 통계량을 계산하는 NumPy 함수들을 학습합니다.
np.linalg)행렬 곱, 역행렬, 행렬식, 선형 방정식 해법, 고유값 및 고유벡터 계산, 특이값 분해(SVD) 등 NumPy의 강력한 선형 대수 기능들을 살펴봅니다.
배열의 요소를 오름차순 또는 내림차순으로 정렬하는 방법(np.sort, ndarray.sort)과 정렬된 인덱스를 반환하는 np.argsort에 대해 학습합니다.
모양이 다른 배열 간의 연산을 가능하게 하는 NumPy의 핵심 기능인 브로드캐스팅의 규칙과 다양한 활용 예를 이해합니다.
NumPy 배열을 바이너리 파일(.npy, .npz)이나 텍스트 파일(.csv, .txt)로 저장하고 불러오는 방법을 익힙니다.
구조화된 배열, 마스크된 배열, 메모리 매핑, 고급 UFuncs 등 NumPy의 더 깊이 있는 주제들을 간략하게 소개하여 추가 학습의 방향을 제시합니다.
앞서 학습한 NumPy의 기능들을 종합적으로 활용해보는 연습 문제들을 통해 실력을 점검하고 응용력을 향상시킵니다.
NumPy는 "Numerical Python"의 약자로, 파이썬에서 과학적 계산과 수치 연산을 위한 핵심 라이브러리입니다. NumPy의 가장 중요한 기능은 강력한 N차원 배열(ndarray) 객체를 제공하는 것이며, 이는 동일한 데이터 타입의 항목들로 구성된 다차원 그리드입니다. 이 ndarray를 통해 벡터 및 행렬 연산을 비롯한 다양한 고급 수학 및 수치 연산을 효율적으로 수행할 수 있습니다.
NumPy는 C와 포트란으로 작성된 부분이 많아 실행 속도가 매우 빠르며, 메모리 관리도 효율적입니다. 데이터 과학, 기계 학습, 딥러닝, 공학, 금융 등 다양한 분야에서 광범위하게 활용되며, Pandas, SciPy, Matplotlib, Scikit-learn과 같은 다른 주요 파이썬 라이브러리들의 핵심적인 기반이 됩니다.
ndarray): C로 구현되어 파이썬 리스트보다 훨씬 빠르고 메모리 효율적입니다. 대량의 데이터를 다룰 때 성능 차이가 두드러집니다.NumPy 배열은 다양한 방법으로 생성할 수 있습니다. 데이터 타입(dtype)을 명시적으로 지정하여 메모리 사용량을 최적화하거나 특정 타입의 연산을 보장할 수 있습니다.
np.array(object, dtype=None, ...)파이썬 리스트, 튜플 등 시퀀스(sequence) 데이터로부터 NumPy 배열을 생성합니다. dtype 매개변수로 데이터 타입을 지정할 수 있습니다.
import numpy as np
# 1차원 배열 생성
list1 = [1, 2, 3, 4, 5]
arr1 = np.array(list1)
print("1D Array from list:\n", arr1)
print("dtype:", arr1.dtype)
# 2차원 배열 생성 (정수형)
list2 = [[1.0, 2.5, 3.3], [4.0, 5.8, 6.1]]
arr2 = np.array(list2, dtype=np.int32) # float 리스트를 int32 타입 배열로
print("\n2D Array (int32) from list of floats:\n", arr2)
print("dtype:", arr2.dtype)1D Array from list:
[1 2 3 4 5]
dtype: int64 (또는 시스템에 따라 int32)
2D Array (int32) from list of floats:
[[1 2 3]
[4 5 6]]
dtype: int32np.zeros(shape, dtype=float), np.ones(shape, dtype=float), np.full(shape, fill_value, dtype=None), np.empty(shape, dtype=float)특정 크기의 배열을 각각 0, 1, 지정된 값(fill_value), 또는 초기화되지 않은 (임의의) 값으로 채워서 생성합니다. empty는 가장 빠르지만, 사용 전 반드시 값을 할당해야 합니다.
import numpy as np
zeros_arr = np.zeros((2, 3), dtype=np.float32)
print("Zeros Array (float32):\n", zeros_arr)
ones_arr = np.ones((3, 2), dtype=int)
print("\nOnes Array (int):\n", ones_arr)
full_arr = np.full((2, 4), np.pi) # pi 값으로 채움
print("\nFull Array with pi:\n", full_arr)
empty_arr = np.empty((2,2)) # 값은 예측 불가
print("\nEmpty Array (uninitialized values):\n", empty_arr)Zeros Array (float32):
[[0. 0. 0.]
[0. 0. 0.]]
Ones Array (int):
[[1 1]
[1 1]
[1 1]]
Full Array with pi:
[[3.14159265 3.14159265 3.14159265 3.14159265]
[3.14159265 3.14159265 3.14159265 3.14159265]]
Empty Array (uninitialized values):
[[... ...]] (실행 시마다 다른 임의의 값)np.arange([start,] stop[, step,], dtype=None)파이썬의 range() 함수와 유사하게, 주어진 시작점(start, 기본값 0)부터 끝점(stop, 미포함) 전까지 지정된 간격(step, 기본값 1)에 따라 연속된 값을 가지는 배열을 생성합니다.
import numpy as np
arr_0_to_9 = np.arange(10)
print("0 to 9:", arr_0_to_9)
arr_2_to_8_step_2 = np.arange(2, 9, 2) # 2, 4, 6, 8
print("2 to 8 (step 2):", arr_2_to_8_step_2)
arr_float = np.arange(0, 1.0, 0.2, dtype=float)
print("0 to 0.8 (step 0.2, float):", arr_float)0 to 9: [0 1 2 3 4 5 6 7 8 9]
2 to 8 (step 2): [2 4 6 8]
0 to 0.8 (step 0.2, float): [0. 0.2 0.4 0.6 0.8]np.linspace(start, stop, num=50, endpoint=True, dtype=None)주어진 시작점(start)과 끝점(stop) 사이를 지정된 개수(num, 기본값 50)만큼 균일하게 나눈 값을 가지는 배열을 생성합니다. endpoint=True (기본값)이면 stop 값을 포함합니다.
import numpy as np
arr_0_to_1_5pts = np.linspace(0, 1, 5)
print("0 to 1 (5 points, endpoint included):", arr_0_to_1_5pts)
arr_0_to_10_no_endpoint = np.linspace(0, 10, 5, endpoint=False)
print("0 to 10 (5 points, endpoint excluded):", arr_0_to_10_no_endpoint)0 to 1 (5 points, endpoint included): [0. 0.25 0.5 0.75 1. ]
0 to 10 (5 points, endpoint excluded): [0. 2. 4. 6. 8.]np.eye(N, M=None, k=0, dtype=float) 와 np.identity(n, dtype=float)np.eye는 NxM 크기의 배열을 생성하며, k번째 대각선(k=0은 주대각선)이 1이고 나머지는 0인 배열을 만듭니다. np.identity(n)은 n x n 크기의 단위 행렬(주대각선이 1)을 생성합니다 (np.eye(n)과 동일).
import numpy as np
identity_3x3 = np.identity(3)
print("3x3 Identity matrix:\n", identity_3x3)
eye_3x4_k1 = np.eye(3, 4, k=1) # 3x4, 1번 대각선이 1
print("\n3x4 eye matrix (k=1):\n", eye_3x4_k1)3x3 Identity matrix:
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]
3x4 eye matrix (k=1):
[[0. 1. 0. 0.]
[0. 0. 1. 0.]
[0. 0. 0. 1.]]np.random 모듈 (주요 함수)다양한 분포의 난수 배열을 생성합니다. 시드(seed)를 설정하여 재현 가능한 난수를 생성할 수 있습니다.
import numpy as np
np.random.seed(42) # 재현성을 위한 시드 설정
# 0과 1 사이의 균일 분포 (균등 분포)
rand_arr = np.random.rand(2, 3) # shape 지정
print("Random Array (uniform, 0 to 1):\n", rand_arr)
# 표준 정규 분포 (평균 0, 표준편차 1)
randn_arr = np.random.randn(3, 2) # shape 지정
print("\nRandom Array (standard normal):\n", randn_arr)
# [low, high) 범위의 정수 난수
randint_arr = np.random.randint(1, 100, size=(2,4)) # 1부터 99까지
print("\nRandom Integer Array [1, 100):\n", randint_arr)
# 특정 분포를 따르는 난수 (예: 정규분포)
mu, sigma = 0, 0.1 # 평균과 표준편차
normal_dist_arr = np.random.normal(mu, sigma, size=(2,2))
print("\nNormal distribution (mu=0, sigma=0.1):\n", normal_dist_arr)
# 시퀀스에서 무작위 선택
choices = np.array(['a', 'b', 'c', 'd'])
random_choice = np.random.choice(choices, size=5, replace=True) # 복원 추출
print("\nRandom choices from sequence:", random_choice)
# 순서 섞기 (in-place)
arr_to_shuffle = np.arange(5)
np.random.shuffle(arr_to_shuffle)
print("\nShuffled array:", arr_to_shuffle)Random Array (uniform, 0 to 1):
[[0.37454012 0.95071431 0.73199394]
[0.59865848 0.15601864 0.15599452]]
Random Array (standard normal):
[[ 0.05808361 -0.81530992]
[-0.62747958 -0.30887855]
[-0.33032019 1.33733369]]
Random Integer Array [1, 100):
[[19 50 97 20]
[77 80 90 28]]
Normal distribution (mu=0, sigma=0.1):
[[ 0.03209849 -0.06919855]
[ 0.0786633 -0.04744836]]
Random choices from sequence: ['a' 'd' 'b' 'c' 'b']
Shuffled array: [1 0 3 2 4]NumPy 배열은 배열의 구조와 특성을 나타내는 다양한 속성을 가지고 있습니다. 이 속성들은 함수 호출 없이 직접 접근하여 사용합니다.
import numpy as np
arr = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]], dtype=np.float64)
# shape: (2, 2, 2)
print("arr.ndim (차원 수):", arr.ndim)
print("arr.shape (배열의 형태, 각 차원의 크기):", arr.shape)
print("arr.size (총 요소 수):", arr.size)
print("arr.dtype (데이터 타입):", arr.dtype)
print("arr.itemsize (각 요소의 바이트 크기):", arr.itemsize)
print("arr.nbytes (배열 전체의 바이트 크기, arr.size * arr.itemsize):", arr.nbytes)
print("arr.flags (메모리 레이아웃 정보 등):", arr.flags)arr.ndim (차원 수): 3
arr.shape (배열의 형태, 각 차원의 크기): (2, 2, 2)
arr.size (총 요소 수): 8
arr.dtype (데이터 타입): float64
arr.itemsize (각 요소의 바이트 크기): 8
arr.nbytes (배열 전체의 바이트 크기, arr.size * arr.itemsize): 64
arr.flags (메모리 레이아웃 정보 등):
C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : True
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : Falseflags 속성은 배열이 메모리에 어떻게 저장되어 있는지(C-style row-major, Fortran-style column-major), 쓰기 가능한지 등의 정보를 담고 있어 고급 최적화나 디버깅에 유용할 수 있습니다.
NumPy 배열의 특정 요소나 부분 배열에 효과적으로 접근하고 수정하는 방법입니다. NumPy의 슬라이싱은 파이썬 리스트와 달리 원본 배열의 **뷰(view)**를 반환하는 경우가 많으므로, 슬라이싱된 뷰를 수정하면 원본 배열도 변경될 수 있음에 유의해야 합니다.
파이썬 리스트와 유사하게 개별 요소(인덱싱)나 범위(슬라이싱)에 접근합니다. 2차원 이상 배열에서는 쉼표(,)로 각 축의 인덱스/슬라이스를 구분합니다.
import numpy as np
arr = np.arange(10, 20) # [10 11 12 13 14 15 16 17 18 19]
print("Original array:", arr)
print("arr[3]:", arr[3])
slice_arr = arr[2:6] # 인덱스 2부터 5까지
print("Slice arr[2:6]:", slice_arr)
# 슬라이스는 뷰(View)이므로, 수정 시 원본 변경
slice_arr[0] = 99 # arr[2]가 99로 변경됨
print("Modified slice_arr[0] = 99")
print("Original arr after slice modification:", arr)
# 복사본을 원하면 .copy() 사용
slice_copy = arr[2:6].copy()
slice_copy[0] = 1000
print("\nOriginal arr after slice_copy modification:", arr) # 원본 변경 없음
arr2d = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]])
print("\n2D Original array:\n", arr2d)
print("arr2d[1, 2]:", arr2d[1, 2]) # 2번째 행, 3번째 열 (값: 7)
print("arr2d[0]:", arr2d[0]) # 첫 번째 행 전체
print("arr2d[:, 2]:", arr2d[:, 2]) # 모든 행의 3번째 열
print("arr2d[1:, :2]:\n", arr2d[1:, :2]) # 2번째 행부터, 첫 2개 열
# [[ 5 6]
# [ 9 10]]Original array: [10 11 12 13 14 15 16 17 18 19]
arr[3]: 13
Slice arr[2:6]: [12 13 14 15]
Modified slice_arr[0] = 99
Original arr after slice modification: [10 11 99 13 14 15 16 17 18 19]
Original arr after slice_copy modification: [10 11 99 13 14 15 16 17 18 19]
2D Original array:
[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]]
arr2d[1, 2]: 7
arr2d[0]: [1 2 3 4]
arr2d[:, 2]: [ 3 7 11]
arr2d[1:, :2]:
[[ 5 6]
[ 9 10]]조건 연산자를 사용하여 생성된 불리언 배열을 인덱스로 사용하여, 조건이 True인 위치의 요소들만 선택합니다. 불리언 인덱싱은 항상 **복사본(copy)**을 반환합니다.
import numpy as np
names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
data = np.random.randn(7, 4) # 7x4 난수 배열
print("Names:", names)
print("Data (first 2 rows for brevity):\n", data[:2])
# 'Bob'에 해당하는 행만 선택
bob_rows_bool = (names == 'Bob')
print("\nBoolean index for 'Bob':", bob_rows_bool)
print("Data rows for 'Bob':\n", data[bob_rows_bool])
# 조건 조합: 'Bob'이 아니고, data의 첫 번째 열이 0보다 큰 행
complex_condition = (names != 'Bob') & (data[:, 0] > 0)
print("\nRows where name is not 'Bob' AND data[:,0] > 0:\n", data[complex_condition])
# 조건에 맞는 요소에 값 할당
data[names == 'Joe'] = 0 # 'Joe'에 해당하는 모든 행을 0으로 설정
print("\nData after setting 'Joe' rows to 0 (first 4 rows):\n", data[:4])Names: ['Bob' 'Joe' 'Will' 'Bob' 'Will' 'Joe' 'Joe']
Data (first 2 rows for brevity):
[[ 1.76405235 0.40015721 0.97873798 2.2408932 ]
[ 1.86755799 -0.97727788 0.95008842 -0.15135721]]
Boolean index for 'Bob': [ True False False True False False False]
Data rows for 'Bob':
[[ 1.76405235 0.40015721 0.97873798 2.2408932 ]
[-0.10321885 0.4105985 0.14404357 1.45427351]]
Rows where name is not 'Bob' AND data[:,0] > 0:
[[ 0.76103773 0.12167502 0.44386323 0.33367433]] (예시, 실제 값은 다름)
Data after setting 'Joe' rows to 0 (first 4 rows):
[[ 1.76405235 0.40015721 0.97873798 2.2408932 ]
[ 0. 0. 0. 0. ]
[ 0.76103773 0.12167502 0.44386323 0.33367433]
[-0.10321885 0.4105985 0.14404357 1.45427351]]정수 배열(또는 리스트)을 인덱스로 사용하여 배열의 특정 요소들을 원하는 순서대로 선택합니다. 팬시 인덱싱은 항상 **복사본(copy)**을 반환합니다.
import numpy as np
arr = np.empty((8, 4)) # 8x4 배열 생성 (값은 미초기화)
for i in range(8):
arr[i] = i # 각 행을 0, 1, ..., 7로 채움
print("Original array (arr):\n", arr)
# 특정 순서로 행 선택
selected_rows = arr[[4, 3, 0, 6]]
print("\nSelected rows [4, 3, 0, 6]:\n", selected_rows)
# 음수 인덱스 사용 가능 (끝에서부터)
selected_neg_idx = arr[[-1, -3, -5]]
print("\nSelected rows using negative indices [-1, -3, -5]:\n", selected_neg_idx)
# 다차원 팬시 인덱싱: 각 요소의 (행, 열) 인덱스 쌍 지정
arr2d = np.arange(12).reshape((3,4)) # [[0,1,2,3],[4,5,6,7],[8,9,10,11]]
print("\nOriginal 2D array for multi-fancy indexing:\n", arr2d)
rows = np.array([0, 1, 2])
cols = np.array([0, 2, 1])
selected_elements = arr2d[rows, cols] # arr2d[0,0], arr2d[1,2], arr2d[2,1]
print("Selected elements at (0,0), (1,2), (2,1):", selected_elements) # [0 6 9]Original array (arr):
[[0. 0. 0. 0.]
[1. 1. 1. 1.]
[2. 2. 2. 2.]
[3. 3. 3. 3.]
[4. 4. 4. 4.]
[5. 5. 5. 5.]
[6. 6. 6. 6.]
[7. 7. 7. 7.]]
Selected rows [4, 3, 0, 6]:
[[4. 4. 4. 4.]
[3. 3. 3. 3.]
[0. 0. 0. 0.]
[6. 6. 6. 6.]]
Selected rows using negative indices [-1, -3, -5]:
[[7. 7. 7. 7.]
[5. 5. 5. 5.]
[3. 3. 3. 3.]]
Original 2D array for multi-fancy indexing:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
Selected elements at (0,0), (1,2), (2,1): [0 6 9]배열의 형태(shape)를 변경하거나, 여러 배열을 결합하거나 분할하는 등 배열의 구조를 변경하는 다양한 함수를 제공합니다.
reshape(newshape) 또는 arr.reshape(newshape)배열의 형태(shape)를 변경합니다. 전체 요소 수는 동일하게 유지되어야 합니다. reshape은 가능한 경우 원본 데이터의 뷰(view)를 반환하지만, 메모리 레이아웃이 호환되지 않으면 복사본을 반환할 수도 있습니다.
import numpy as np
arr = np.arange(1, 13) # 1부터 12까지
print("Original array (1D):", arr)
reshaped_3x4 = arr.reshape((3, 4))
print("\nReshaped to (3, 4):\n", reshaped_3x4)
# -1 사용: 해당 차원의 크기는 자동으로 계산됨
reshaped_2x_auto = arr.reshape((2, -1)) # (2, 6)이 됨
print("\nReshaped to (2, -1) -> (2,6):\n", reshaped_2x_auto)
# View vs Copy 확인
reshaped_view_check = arr.reshape(3,4)
reshaped_view_check[0,0] = 100
print("\nModified reshaped_view_check[0,0] = 100")
print("Original arr after modifying view:", arr) # 원본 arr[0]도 변경됨Original array (1D): [ 1 2 3 4 5 6 7 8 9 10 11 12]
Reshaped to (3, 4):
[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]]
Reshaped to (2, -1) -> (2,6):
[[ 1 2 3 4 5 6]
[ 7 8 9 10 11 12]]
Modified reshaped_view_check[0,0] = 100
Original arr after modifying view: [100 2 3 4 5 6 7 8 9 10 11 12]transpose(), arr.T, np.swapaxes(arr, axis1, axis2)배열의 축을 바꿉니다. arr.T는 arr.transpose()의 축약형으로, 2차원 배열의 경우 전치 행렬을 만듭니다. swapaxes는 지정된 두 축을 서로 바꿉니다. 이 연산들은 뷰를 반환합니다.
import numpy as np
arr2d = np.array([[1, 2, 3], [4, 5, 6]])
print("Original 2D array (2x3):\n", arr2d)
print("arr2d.T (Transpose, 3x2):\n", arr2d.T)
arr3d = np.arange(24).reshape((2,3,4))
print("\nOriginal 3D array (2x3x4) shape:", arr3d.shape)
# (0,1,2) -> (0,2,1)
transposed_3d_021 = arr3d.transpose((0, 2, 1))
print("Transposed 3D array to (0,2,1) shape:", transposed_3d_021.shape)
# swapaxes: 축 0과 축 2를 교환
swapped_axes_3d = np.swapaxes(arr3d, 0, 2) # shape (4,3,2)
print("Swapped axes (0 and 2) shape:", swapped_axes_3d.shape)Original 2D array (2x3):
[[1 2 3]
[4 5 6]]
arr2d.T (Transpose, 3x2):
[[1 4]
[2 5]
[3 6]]
Original 3D array (2x3x4) shape: (2, 3, 4)
Transposed 3D array to (0,2,1) shape: (2, 4, 3)
Swapped axes (0 and 2) shape: (4, 3, 2)np.concatenate((a1, a2, ...), axis=0), np.vstack(tup), np.hstack(tup), np.dstack(tup)여러 배열을 특정 축(axis)을 따라 결합합니다.
vstack (vertical stack)은 행 방향(axis 0)으로 쌓고,
hstack (horizontal stack)은 열 방향(axis 1)으로 쌓습니다.
dstack (depth stack)은 세 번째 축(axis 2)을 따라 쌓습니다.
결합될 배열들은 결합 축을 제외한 나머지 축들의 크기가 동일해야 합니다.
import numpy as np
arr1 = np.array([[1, 2], [3, 4]])
arr2 = np.array([[5, 6], [7, 8]])
arr3 = np.array([9, 10]) # shape (2,) -> reshape to (1,2) for vstack/concatenate with 2D
arr3_2d = arr3.reshape(1,2)
print("arr1:\n", arr1)
print("arr2:\n", arr2)
print("arr3_2d:\n", arr3_2d)
# axis=0 (행 방향, 수직)
concat_axis0 = np.concatenate((arr1, arr2, arr3_2d), axis=0)
print("\nConcatenated (axis=0):\n", concat_axis0)
# axis=1 (열 방향, 수평)
concat_axis1 = np.concatenate((arr1, arr2), axis=1)
print("\nConcatenated (axis=1):\n", concat_axis1)
v_stacked = np.vstack((arr1, arr2, arr3_2d))
print("\nVstacked:\n", v_stacked)
# hstack은 1D 배열도 그대로 붙일 수 있음
arr_h1 = np.array([1,2,3])
arr_h2 = np.array([4,5,6])
h_stacked = np.hstack((arr_h1, arr_h2))
print("\nHstacked (1D arrays):", h_stacked)
arr_h_2d1 = np.array([[1],[2],[3]])
arr_h_2d2 = np.array([[4],[5],[6]])
h_stacked_2d = np.hstack((arr_h_2d1, arr_h_2d2))
print("\nHstacked (2D column vectors):\n", h_stacked_2d)arr1:
[[1 2]
[3 4]]
arr2:
[[5 6]
[7 8]]
arr3_2d:
[[ 9 10]]
Concatenated (axis=0):
[[ 1 2]
[ 3 4]
[ 5 6]
[ 7 8]
[ 9 10]]
Concatenated (axis=1):
[[1 2 5 6]
[3 4 7 8]]
Vstacked:
[[ 1 2]
[ 3 4]
[ 5 6]
[ 7 8]
[ 9 10]]
Hstacked (1D arrays): [1 2 3 4 5 6]
Hstacked (2D column vectors):
[[1 4]
[2 5]
[3 6]]np.split(ary, indices_or_sections, axis=0), np.hsplit(ary, indices_or_sections), np.vsplit(ary, indices_or_sections)배열을 여러 개의 하위 배열로 분할합니다. indices_or_sections가 정수 N이면 N개의 동일한 크기로 분할하고, 1차원 배열이면 해당 인덱스 지점에서 분할합니다.
hsplit은 열 방향(axis 1)으로, vsplit은 행 방향(axis 0)으로 분할합니다.
import numpy as np
arr = np.arange(1, 13).reshape((3, 4))
print("Original array (3x4):\n", arr)
# 행 방향으로 3개로 분할 (vsplit)
v_split_arrs = np.vsplit(arr, 3)
print("\nVsplit into 3 (list of arrays):")
for sub_arr in v_split_arrs: print(sub_arr)
# 열 방향으로 인덱스 1, 3에서 분할 (hsplit)
# 결과는 arr[:, :1], arr[:, 1:3], arr[:, 3:]
h_split_arrs_idx = np.hsplit(arr, [1, 3])
print("\nHsplit at indices 1, 3 (list of arrays):")
for sub_arr in h_split_arrs_idx: print(sub_arr)Original array (3x4):
[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]]
Vsplit into 3 (list of arrays):
[[1 2 3 4]]
[[5 6 7 8]]
[[ 9 10 11 12]]
Hsplit at indices 1, 3 (list of arrays):
[[1]
[5]
[9]]
[[ 2 3]
[ 6 7]
[10 11]]
[[ 4]
[ 8]
[12]]flatten(order='C') vs ravel(order='C')다차원 배열을 1차원 배열로 펼칩니다.
flatten()은 항상 **복사본(copy)**을 반환합니다.
ravel()은 가능한 경우 원본 배열의 **뷰(view)**를 반환하며 (메모리가 연속적인 경우), 그렇지 않으면 복사본을 반환합니다. 뷰를 수정하면 원본도 변경될 수 있으므로 주의해야 합니다.
order='C'는 행 우선(C 스타일), order='F'는 열 우선(Fortran 스타일)으로 펼칩니다.
import numpy as np
arr2d = np.array([[1, 2, 3], [4, 5, 6]])
print("Original 2D Array:\n", arr2d)
flattened_arr = arr2d.flatten() # 기본값 order='C'
raveled_arr = arr2d.ravel() # 기본값 order='C'
print("Flattened Array:", flattened_arr)
print("Raveled Array:", raveled_arr)
# flatten은 항상 복사본
flattened_arr[0] = 100
print("\nAfter modifying flattened_arr, original arr2d (unchanged):\n", arr2d)
# ravel은 뷰일 수 있음 (대부분의 경우)
raveled_view = arr2d.ravel()
raveled_view[0] = 200
print("After modifying raveled_view, original arr2d (changed):\n", arr2d)Original 2D Array:
[[1 2 3]
[4 5 6]]
Flattened Array: [1 2 3 4 5 6]
Raveled Array: [1 2 3 4 5 6]
After modifying flattened_arr, original arr2d (unchanged):
[[1 2 3]
[4 5 6]]
After modifying raveled_view, original arr2d (changed):
[[200 2 3]
[ 4 5 6]]UFuncs(Universal Functions)는 NumPy 배열의 각 요소에 대해 연산을 수행하는 함수입니다. 이 함수들은 내부적으로 C로 구현되어 있어 매우 빠르며, 명시적인 반복문 없이 벡터화된 연산을 가능하게 합니다. UFuncs는 단항(unary, 입력 인자 1개) 또는 이항(binary, 입력 인자 2개) 연산을 지원하며, 결과를 새 배열로 반환하거나 지정된 출력 배열에 저장할 수 있습니다.
배열 간 또는 배열과 스칼라 간의 기본적인 산술 연산을 요소별로 수행합니다. (예: np.add, np.subtract, np.multiply, np.divide, np.floor_divide, np.power, np.mod 등)
import numpy as np
arr1 = np.array([1, 2, 3, 4])
arr2 = np.array([4, 3, 2, 1])
print("arr1:", arr1)
print("arr2:", arr2)
print("np.add(arr1, arr2) or arr1 + arr2:", np.add(arr1, arr2))
print("np.multiply(arr1, 5) or arr1 * 5:", arr1 * 5)
print("np.power(arr1, arr2) or arr1 ** arr2:", np.power(arr1, arr2)) # [1^4, 2^3, 3^2, 4^1]
print("np.maximum(arr1, np.array([2,2,2,2])):", np.maximum(arr1, np.array([2,2,2,2]))) # 요소별 최댓값arr1: [1 2 3 4]
arr2: [4 3 2 1]
np.add(arr1, arr2) or arr1 + arr2: [5 5 5 5]
np.multiply(arr1, 5) or arr1 * 5: [ 5 10 15 20]
np.power(arr1, arr2) or arr1 ** arr2: [1 8 9 4]
np.maximum(arr1, np.array([2,2,2,2])): [2 2 3 4]삼각함수(np.sin, np.cos), 지수/로그 함수(np.exp, np.log, np.sqrt), 올림/내림/반올림(np.ceil, np.floor, np.round) 등 다양한 수학 함수를 요소별로 적용합니다.
import numpy as np
arr = np.array([0.25, 1.5, 2.7, -0.5])
print("Original array:", arr)
print("np.sqrt(np.abs(arr)):", np.sqrt(np.abs(arr))) # 음수 때문에 절대값 후 제곱근
print("np.exp(arr):", np.exp(arr)) # e^x
print("np.round(arr, decimals=1):", np.round(arr, decimals=1)) # 소수점 첫째자리까지 반올림
angles = np.array([0, np.pi/6, np.pi/4, np.pi/3, np.pi/2])
print("\nCos of angles:", np.cos(angles))Original array: [ 0.25 1.5 2.7 -0.5 ]
np.sqrt(np.abs(arr)): [0.5 1.22474487 1.64316767 0.70710678]
np.exp(arr): [ 1.28402542 4.48168907 14.87973172 0.60653066]
np.round(arr, decimals=1): [ 0.2 1.5 2.7 -0.5]
Cos of angles: [1.00000000e+00 8.66025404e-01 7.07106781e-01 5.00000000e-01
6.12323400e-17]요소별 비교(>, <, ==, np.greater 등)와 논리 연산(np.logical_and, np.logical_or, np.logical_not)을 수행하여 불리언 배열을 반환합니다.
import numpy as np
arr1 = np.array([1, 5, 3, 7])
arr2 = np.array([2, 4, 3, 8])
print("arr1 > arr2 :", arr1 > arr2) # [False True False False]
print("np.equal(arr1, arr2) :", np.equal(arr1, arr2)) # [False False True False]
bool_arr1 = np.array([True, False, True, False])
bool_arr2 = np.array([True, True, False, False])
print("\nnp.logical_and(bool_arr1, bool_arr2) :", np.logical_and(bool_arr1, bool_arr2)) # [ True False False False]
print("np.logical_not(bool_arr1) :", np.logical_not(bool_arr1)) # [False True False True]arr1 > arr2 : [False True False False]
np.equal(arr1, arr2) : [False False True False]
np.logical_and(bool_arr1, bool_arr2) : [ True False False False]
np.logical_not(bool_arr1) : [False True False True]NumPy는 배열 데이터의 기본적인 통계량을 계산하는 다양한 함수를 제공합니다. 대부분의 통계 함수는 axis 매개변수를 사용하여 특정 축을 따라 연산을 수행할 수 있습니다.
np.sum(), np.prod()배열 요소들의 합(sum) 또는 곱(prod)을 계산합니다.
import numpy as np
arr = np.array([[1,2,3], [4,5,6]])
print("Array:\n", arr)
print("Sum of all elements:", np.sum(arr)) # 21
print("Sum along axis 0 (columns):", np.sum(arr, axis=0)) # [5 7 9]
print("Sum along axis 1 (rows):", np.sum(arr, axis=1)) # [ 6 15]
print("\nProduct of all elements:", np.prod(arr)) # 720
print("Product along axis 0 (columns):", np.prod(arr, axis=0)) # [ 4 10 18]Array:
[[1 2 3]
[4 5 6]]
Sum of all elements: 21
Sum along axis 0 (columns): [5 7 9]
Sum along axis 1 (rows): [ 6 15]
Product of all elements: 720
Product along axis 0 (columns): [ 4 10 18]np.mean(), np.median(), np.std(), np.var(), np.percentile()데이터의 평균(mean), 중앙값(median), 표준편차(std), 분산(var), 백분위수(percentile)를 계산합니다. 기본적으로 NumPy의 var와 std는 N으로 나누는 모집단 통계량을 계산합니다 (ddof=0). 표본 통계량은 ddof=1로 설정합니다.
import numpy as np
data = np.array([1, 2, 5, 2, 6, 2, 7, 3, 8, 4]) # 약간 불균형한 데이터
print("Data:", data)
print("Mean:", np.mean(data))
print("Median:", np.median(data)) # 중앙값, 정렬 후 가운데 값
print("Standard Deviation (population, ddof=0):", np.std(data))
print("Standard Deviation (sample, ddof=1):", np.std(data, ddof=1))
print("Variance (population, ddof=0):", np.var(data))
print("25th percentile (Q1):", np.percentile(data, 25))
print("75th percentile (Q3):", np.percentile(data, 75))Data: [1 2 5 2 6 2 7 3 8 4]
Mean: 4.0
Median: 3.5
Standard Deviation (population, ddof=0): 2.280350850198276
Standard Deviation (sample, ddof=1): 2.403700850981097
Variance (population, ddof=0): 5.2
25th percentile (Q1): 2.0
75th percentile (Q3): 5.75np.min(), np.max(), np.argmin(), np.argmax(), np.ptp()배열의 최솟값(min), 최댓값(max), 최솟값의 첫 번째 인덱스(argmin), 최댓값의 첫 번째 인덱스(argmax), 그리고 최댓값과 최솟값의 차이(range, ptp - peak to peak)를 찾습니다.
import numpy as np
data = np.array([[3, 7, 1], [9, 2, 5]])
print("Data:\n", data)
print("Min of all:", np.min(data)) # 1
print("Max along axis 1 (rows):", np.max(data, axis=1)) # [7 9]
print("Argmin of all (flattened index):", np.argmin(data)) # 2 (1의 위치)
print("Argmax along axis 0 (columns):", np.argmax(data, axis=0)) # [1 0 1]
print("Peak-to-peak (range) of all:", np.ptp(data)) # 9 - 1 = 8
print("Peak-to-peak along axis 1 (rows):", np.ptp(data, axis=1)) # [7-1, 9-2] -> [6 7]Data:
[[3 7 1]
[9 2 5]]
Min of all: 1
Max along axis 1 (rows): [7 9]
Argmin of all (flattened index): 2
Argmax along axis 0 (columns): [1 0 1]
Peak-to-peak (range) of all: 8
Peak-to-peak along axis 1 (rows): [6 7]np.corrcoef(x, y=None, rowvar=True)피어슨 곱상관계수를 계산합니다. rowvar=True(기본값)이면 각 행이 변수, 각 열이 관측값을 나타냅니다. False면 그 반대입니다. 자기 자신과의 상관계수는 대각선에 1로 나타납니다.
import numpy as np
x = np.array([1, 2, 3, 4, 5])
y = np.array([2, 4, 5, 4, 6]) # x와 양의 상관관계를 가질 것으로 예상
z = np.array([5, 4, 3, 2, 1]) # x와 음의 상관관계를 가질 것으로 예상
# x와 y의 상관계수 행렬
corr_xy = np.corrcoef(x, y)
print("Correlation matrix (x, y):\n", corr_xy)
print("Correlation coefficient between x and y:", corr_xy[0, 1])
# 세 변수 간의 상관계수 행렬
corr_xyz = np.corrcoef([x, y, z]) # 각 행이 변수
print("\nCorrelation matrix (x, y, z):\n", corr_xyz)Correlation matrix (x, y):
[[1. 0.82020013]
[0.82020013 1. ]]
Correlation coefficient between x and y: 0.8202001301960043
Correlation matrix (x, y, z):
[[ 1. 0.82020013 -1. ]
[ 0.82020013 1. -0.82020013]
[-1. -0.82020013 1. ]]NumPy는 np.linalg 서브모듈을 통해 행렬 곱셈, 행렬식, 역행렬, 고유값 분해 등 다양한 선형 대수 연산을 지원합니다. 이러한 연산은 과학 및 공학 분야에서 매우 중요합니다.
np.dot(a, b) 또는 a @ b (행렬 곱셈)두 배열의 점곱(dot product)을 계산합니다. 1차원 배열(벡터) 간에는 내적, 2차원 배열(행렬) 간에는 행렬 곱셈, 다차원 배열의 경우 복잡한 규칙에 따라 계산됩니다. Python 3.5+에서는 @ 연산자로도 행렬 곱셈을 수행할 수 있습니다.
import numpy as np
A = np.array([[1, 2], [3, 4]]) # (2,2)
B = np.array([[5, 6], [7, 8]]) # (2,2)
C = np.array([[1,0],[0,1],[1,1]]) # (3,2)
# 행렬 곱셈 (2x2) * (2x2) -> (2x2)
matrix_mult_AB = np.dot(A, B)
print("Matrix A:\n", A)
print("Matrix B:\n", B)
print("A dot B:\n", matrix_mult_AB)
print("A @ B:\n", A @ B)
# 행렬 곱셈 (3x2) * (2x2) -> (3x2)
matrix_mult_CB = C @ B
print("\nMatrix C:\n", C)
print("C @ B:\n", matrix_mult_CB)Matrix A:
[[1 2]
[3 4]]
Matrix B:
[[5 6]
[7 8]]
A dot B:
[[19 22]
[43 50]]
A @ B:
[[19 22]
[43 50]]
Matrix C:
[[1 0]
[0 1]
[1 1]]
C @ B:
[[ 5 6]
[ 7 8]
[12 14]]np.linalg.det(a), np.linalg.inv(a), np.linalg.solve(a, b)
det: 정방 행렬의 행렬식(determinant)을 계산합니다.
inv: 정방 행렬의 역행렬(inverse)을 계산합니다. 행렬식이 0이면 (특이 행렬, singular matrix) 역행렬이 존재하지 않아 오류가 발생합니다.
solve: 선형 방정식 시스템 $Ax = b$의 해 $x$를 구합니다. inv(A) @ b 보다 수치적으로 안정적이고 빠릅니다.
import numpy as np
A = np.array([[4, 7], [2, 6]])
print("Matrix A:\n", A)
# 행렬식
det_A = np.linalg.det(A) # 4*6 - 7*2 = 24 - 14 = 10
print("\nDeterminant of A:", det_A)
# 역행렬
if det_A != 0:
A_inv = np.linalg.inv(A)
print("\nInverse of A:\n", A_inv)
# A @ A_inv는 단위 행렬에 가까워야 함
print("A @ A_inv (should be Identity approx.):\n", np.round(A @ A_inv, decimals=5))
else:
print("\nInverse does not exist for A (singular matrix)")
# 선형 방정식 해: 4x + 7y = 1, 2x + 6y = -2
# A는 위와 동일, b = [1, -2]
b = np.array([1, -2])
try:
x_solution = np.linalg.solve(A, b)
print("\nSolution x for Ax=b:", x_solution) # [ 2. -1.]
# 검증: np.allclose(A @ x_solution, b) -> True
print("Verification (A @ x_solution == b):", np.allclose(A @ x_solution, b))
except np.linalg.LinAlgError:
print("\nSystem Ax=b cannot be solved (e.g., singular matrix A)")
Matrix A:
[[4 7]
[2 6]]
Determinant of A: 10.000000000000002
Inverse of A:
[[ 0.6 -0.7]
[-0.2 0.4]]
A @ A_inv (should be Identity approx.):
[[1. 0.]
[0. 1.]]
Solution x for Ax=b: [ 2. -1.]
Verification (A @ x_solution == b): Truenp.linalg.eig(a), np.linalg.svd(a)
eig: 정방 행렬의 고유값(eigenvalues)과 고유벡터(eigenvectors)를 계산합니다. $Av = \lambda v$에서 $\lambda$가 고유값, $v$가 고유벡터입니다.
svd: 행렬의 특이값 분해(Singular Value Decomposition)를 수행합니다. $A = U \Sigma V^T$ 형태로 분해하며, 차원 축소, 잡음 제거 등 다양한 응용에 사용됩니다.
import numpy as np
matrix = np.array([[1, -1, 0], [-1, 2, -1], [0, -1, 1]]) # 대칭행렬
print("Matrix:\n", matrix)
# 고유값, 고유벡터
eigenvalues, eigenvectors = np.linalg.eig(matrix)
print("\nEigenvalues:\n", eigenvalues) # 고유값은 실수
print("Eigenvectors (each column is an eigenvector):\n", eigenvectors)
# 검증: matrix @ eigenvectors[:,i] == eigenvalues[i] * eigenvectors[:,i]
for i in range(len(eigenvalues)):
print(f"Check for eigenvalue {eigenvalues[i]:.2f}: {np.allclose(matrix @ eigenvectors[:,i], eigenvalues[i] * eigenvectors[:,i])}")
# 특이값 분해 (SVD)
A_svd = np.array([[1,2,3],[4,5,6],[7,8,9]])
U, s, Vt = np.linalg.svd(A_svd) # s는 1차원 배열 (특이값)
print("\nSVD of A_svd:")
print("U (left singular vectors):\n", U)
print("s (singular values):\n", s)
print("Vt (V transpose, right singular vectors transposed):\n", Vt)
# 원본 행렬 복원 (s를 대각행렬로 만들어야 함)
Sigma = np.zeros(A_svd.shape)
Sigma[:A_svd.shape[1], :A_svd.shape[1]] = np.diag(s) # s는 대각행렬의 대각성분
A_reconstructed = U @ Sigma @ Vt
print("\nReconstructed A from SVD (should be close to original):\n", A_reconstructed)
print("Reconstruction close to original:", np.allclose(A_svd, A_reconstructed))Matrix:
[[ 1 -1 0]
[-1 2 -1]
[ 0 -1 1]]
Eigenvalues:
[3.00000000e+00 1.00000000e+00 2.22044605e-16]
Eigenvectors (each column is an eigenvector):
[[ 0.57735027 -0.70710678 0.40824829]
[-0.57735027 -0. -0.81649658]
[ 0.57735027 0.70710678 0.40824829]]
Check for eigenvalue 3.00: True
Check for eigenvalue 1.00: True
Check for eigenvalue 0.00: True
SVD of A_svd:
U (left singular vectors):
[[-0.21483724 0.88723069 0.40824829]
[-0.52058739 0.24964395 -0.81649658]
[-0.82633754 -0.38794278 0.40824829]]
s (singular values):
[1.68481034e+01 1.06836951e+00 4.41842475e-16]
Vt (V transpose, right singular vectors transposed):
[[-0.47967118 -0.57236779 -0.66506441]
[ 0.77669099 0.07568647 -0.62531805]
[ 0.40824829 -0.81649658 0.40824829]]
Reconstructed A from SVD (should be close to original):
[[1. 2. 3.]
[4. 5. 6.]
[7. 8. 9.]]
Reconstruction close to original: TrueNumPy는 배열의 요소를 정렬하기 위한 효율적인 함수들을 제공합니다. 축(axis)을 지정하여 특정 축을 따라 정렬할 수 있습니다.
np.sort(a, axis=-1, kind=None, order=None) vs ndarray.sort(axis=-1, kind=None, order=None)
np.sort()는 정렬된 배열의 **복사본**을 반환합니다 (원본 배열 변경 없음).
ndarray.sort()는 배열 객체의 메서드로, 원본 배열을 **직접 정렬(in-place)**하며 None을 반환합니다.
axis: 정렬할 축. 기본값 -1은 마지막 축을 의미. None이면 배열을 평탄화한 후 정렬.
kind: 정렬 알고리즘 ('quicksort', 'mergesort', 'heapsort', 'stable'). 기본값은 'quicksort'. 'stable'은 안정 정렬을 보장.
order: 구조화된 배열(structured array)의 경우, 정렬 기준으로 사용할 필드 이름을 지정.
import numpy as np
arr1d = np.array([3, 1, 4, 1, 5, 9, 2, 6])
print("Original 1D array:", arr1d)
# np.sort() - 복사본 반환
sorted_copy = np.sort(arr1d)
print("Sorted copy (np.sort):", sorted_copy)
print("Original arr1d after np.sort:", arr1d) # 원본 유지
# ndarray.sort() - 원본 직접 정렬
arr_to_sort_inplace = arr1d.copy() # 원본 유지를 위해 복사본 사용
arr_to_sort_inplace.sort() # 반환값 None
print("\nArray after inplace sort (arr.sort()):", arr_to_sort_inplace)
# 내림차순 정렬 (np.sort 후 슬라이싱)
desc_sorted = np.sort(arr1d)[::-1]
print("\nDescending sorted array:", desc_sorted)
arr2d = np.array([[9, 1, 7], [2, 8, 3], [5, 6, 4]])
print("\nOriginal 2D array:\n", arr2d)
# 각 행을 따라 정렬 (axis=1)
print("Sorted along rows (axis=1):\n", np.sort(arr2d, axis=1))
# 각 열을 따라 정렬 (axis=0)
print("Sorted along columns (axis=0):\n", np.sort(arr2d, axis=0))Original 1D array: [3 1 4 1 5 9 2 6]
Sorted copy (np.sort): [1 1 2 3 4 5 6 9]
Original arr1d after np.sort: [3 1 4 1 5 9 2 6]
Array after inplace sort (arr.sort()): [1 1 2 3 4 5 6 9]
Descending sorted array: [9 6 5 4 3 2 1 1]
Original 2D array:
[[9 1 7]
[2 8 3]
[5 6 4]]
Sorted along rows (axis=1):
[[1 7 9]
[2 3 8]
[4 5 6]]
Sorted along columns (axis=0):
[[2 1 3]
[5 6 4]
[9 8 7]]np.argsort(a, axis=-1, kind=None, order=None)배열을 정렬했을 때의 **원래 인덱스**를 반환합니다. 즉, 이 함수가 반환한 인덱스를 사용하여 원본 배열을 슬라이싱하면 정렬된 배열을 얻을 수 있습니다. 데이터 자체를 정렬하는 것이 아니라, 정렬된 순서에 해당하는 인덱스가 필요할 때 유용합니다 (예: 다른 배열을 동일한 순서로 정렬할 때).
import numpy as np
arr = np.array([30, 10, 40, 20, 50])
print("Original array:", arr)
sorted_indices = np.argsort(arr) # 오름차순 정렬 인덱스
print("Indices that would sort arr (ascending):", sorted_indices)
# arr[sorted_indices] 를 하면 [10 20 30 40 50] 이 됨
print("Array sorted using argsort indices:", arr[sorted_indices])
# 내림차순 정렬 인덱스
# 1. 오름차순 인덱스를 뒤집거나
desc_indices_method1 = sorted_indices[::-1]
# 2. 배열 값에 음수를 취해 argsort (숫자형 데이터에만 가능)
desc_indices_method2 = np.argsort(-arr)
print("\nIndices for descending sort (method 1):", desc_indices_method1)
print("Array sorted descending (using method 1 indices):", arr[desc_indices_method1])
print("Indices for descending sort (method 2):", desc_indices_method2)
print("Array sorted descending (using method 2 indices):", arr[desc_indices_method2])
# 2D 배열에서 사용 예시 (각 행의 값들을 정렬하는 인덱스)
arr2d_ages = np.array([[25, 30, 22], [40, 18, 35]]) # 나이 데이터
arr2d_names = np.array([['Alice', 'Bob', 'Charlie'], ['David', 'Eve', 'Frank']])
print("\nAges array:\n", arr2d_ages)
print("Names array:\n", arr2d_names)
# 각 행의 나이를 오름차순으로 정렬하는 인덱스
age_sort_indices_rows = np.argsort(arr2d_ages, axis=1)
print("\nIndices to sort ages within each row:\n", age_sort_indices_rows)
# 이 인덱스를 사용하여 이름 배열도 동일하게 정렬 (팬시 인덱싱 활용 필요)
# np.take_along_axis 또는 반복문/리스트 컴프리헨션 사용
sorted_names_by_age = np.array([names_row[indices_row] for names_row, indices_row in zip(arr2d_names, age_sort_indices_rows)])
print("Names sorted by age within each row:\n", sorted_names_by_age)
print("Ages sorted within each row (for verification):\n", np.take_along_axis(arr2d_ages, age_sort_indices_rows, axis=1))
Original array: [30 10 40 20 50]
Indices that would sort arr (ascending): [1 3 0 2 4]
Array sorted using argsort indices: [10 20 30 40 50]
Indices for descending sort (method 1): [4 2 0 3 1]
Array sorted descending (using method 1 indices): [50 40 30 20 10]
Indices for descending sort (method 2): [4 2 0 3 1]
Array sorted descending (using method 2 indices): [50 40 30 20 10]
Ages array:
[[25 30 22]
[40 18 35]]
Names array:
[['Alice' 'Bob' 'Charlie']
['David' 'Eve' 'Frank']]
Indices to sort ages within each row:
[[2 0 1]
[1 2 0]]
Names sorted by age within each row:
[['Charlie' 'Alice' 'Bob']
['Eve' 'Frank' 'David']]
Ages sorted within each row (for verification):
[[22 25 30]
[18 35 40]]브로드캐스팅은 NumPy가 산술 연산 중에 서로 다른 모양(shape)의 배열을 처리하는 방법을 설명하는 강력한 메커니즘입니다. 명시적으로 배열을 복제하지 않고도, 작은 배열이 큰 배열의 모양에 맞게 "확장"되어 요소별 연산이 가능해집니다. 이는 메모리를 효율적으로 사용하고 코드를 간결하게 만듭니다.
브로드캐스팅 규칙 (두 배열 A, B에 대해):
ValueError가 발생합니다.브로드캐스팅된 결과 배열의 각 차원 크기는 입력 배열들의 해당 차원 크기 중 더 큰 값(또는 유일한 값)이 됩니다.
import numpy as np
# 예시 1: 스칼라와 배열
arr = np.array([[1, 2, 3], [4, 5, 6]]) # shape (2, 3)
scalar_val = 10
result1 = arr + scalar_val # 스칼라가 (2,3)으로 브로드캐스팅
print("Array (2,3):\n", arr)
print("Array + Scalar:\n", result1)
# 예시 2: 1D 배열과 2D 배열
matrix = np.arange(1, 7).reshape((2, 3)) # shape (2, 3) -> [[1,2,3],[4,5,6]]
row_vector = np.array([10, 20, 30]) # shape (3,)
# row_vector의 shape이 (1,3)으로 간주된 후, 첫 번째 차원이 2로 확장되어 (2,3)이 됨
result2 = matrix + row_vector
print("\nMatrix (2,3):\n", matrix)
print("Row Vector (3,):", row_vector)
print("Matrix + Row Vector:\n", result2)
# 예시 3: 열 벡터와 2D 배열
# matrix는 위와 동일 (2,3)
col_vector = np.array([[100], [200]]) # shape (2,1)
# col_vector의 두 번째 차원이 3으로 확장되어 (2,3)이 됨
result3 = matrix + col_vector
print("\nColumn Vector (2,1):\n", col_vector)
print("Matrix + Column Vector:\n", result3)
# 예시 4: 열 벡터와 행 벡터
v_col = np.array([[0], [10], [20]]) # (3,1)
v_row = np.array([1, 2, 3, 4]) # (4,) -> (1,4)
# v_col은 (3,4)로, v_row는 (3,4)로 브로드캐스팅
result4 = v_col + v_row
print("\nColumn Vector (3,1):\n", v_col)
print("Row Vector (4,):\n", v_row)
print("Column Vector + Row Vector (3x4 result):\n", result4)Array (2,3):
[[1 2 3]
[4 5 6]]
Array + Scalar:
[[11 12 13]
[14 15 16]]
Matrix (2,3):
[[1 2 3]
[4 5 6]]
Row Vector (3,): [10 20 30]
Matrix + Row Vector:
[[11 22 33]
[14 25 36]]
Column Vector (2,1):
[[100]
[200]]
Matrix + Column Vector:
[[101 102 103]
[204 205 206]]
Column Vector (3,1):
[[ 0]
[10]
[20]]
Row Vector (4,):
[1 2 3 4]
Column Vector + Row Vector (3x4 result):
[[ 1 2 3 4]
[11 12 13 14]
[21 22 23 24]]NumPy 배열을 파일 시스템에 저장하고 다시 불러와서 사용할 수 있습니다. NumPy는 자체 바이너리 형식(.npy, .npz)과 텍스트 파일 형식을 모두 지원합니다.
np.save(file, arr), np.load(file)단일 NumPy 배열을 .npy 확장자를 가지는 압축되지 않은 원시 바이너리 파일 형식으로 저장하고 불러옵니다. 이 형식은 효율적이며 배열의 모양, 데이터 타입 등의 메타데이터를 포함합니다.
import numpy as np
arr_to_save = np.arange(0, 100, 10).reshape(2,5)
filename = 'my_single_array.npy'
np.save(filename, arr_to_save)
print(f"Array saved to {filename}")
loaded_arr = np.load(filename)
print("\nLoaded array:\n", loaded_arr)
print("Is loaded array same as original?", np.array_equal(arr_to_save, loaded_arr))Array saved to my_single_array.npy
Loaded array:
[[ 0 10 20 30 40]
[50 60 70 80 90]]
Is loaded array same as original? True
(실행 후 'my_single_array.npy' 파일이 생성됩니다.)np.savez(file, *args, **kwds), np.savez_compressed(file, *args, **kwds)여러 개의 NumPy 배열을 .npz 확장자를 가지는 단일 파일에 아카이브 형태로 저장합니다. savez는 압축하지 않고, savez_compressed는 각 배열을 압축하여 저장합니다. 불러올 때는 딕셔너리 유사 객체가 반환되며, 저장 시 사용한 키워드 인자 이름 (또는 기본 이름 'arr_0', 'arr_1' 등)으로 각 배열에 접근합니다.
import numpy as np
arr_a = np.array([1,2,3,4,5])
arr_b = np.array([[10,20],[30,40]])
npz_filename = 'multiple_arrays_compressed.npz'
# 여러 배열 저장 (키워드 인자로 이름 지정)
np.savez_compressed(npz_filename, first_array=arr_a, second_matrix=arr_b)
print(f"Multiple arrays saved to {npz_filename}")
# 저장된 npz 파일 불러오기
data_archive = np.load(npz_filename)
print("\nKeys in loaded archive:", list(data_archive.keys()))
print("Loaded first_array:", data_archive['first_array'])
print("Loaded second_matrix:\n", data_archive['second_matrix'])
data_archive.close() # 파일 객체 닫기 (필수)Multiple arrays saved to multiple_arrays_compressed.npz
Keys in loaded archive: ['first_array', 'second_matrix']
Loaded first_array: [1 2 3 4 5]
Loaded second_matrix:
[[10 20]
[30 40]]
(실행 후 'multiple_arrays_compressed.npz' 파일이 생성됩니다.)np.loadtxt(fname, dtype=float, comments='#', delimiter=None, skiprows=0, usecols=None), np.savetxt(fname, X, fmt='%.18e', delimiter=' ', newline='\n', header='', footer='', comments='# ')텍스트 파일 (예: CSV, TSV)에서 데이터를 불러오거나(loadtxt) 저장합니다(savetxt). 주로 1차원 또는 2차원 배열에 사용되며, 다양한 옵션을 통해 파일 형식을 지정할 수 있습니다. loadtxt는 genfromtxt보다 간단하지만 기능이 적습니다 (예: 누락된 값 처리 불가).
import numpy as np
data_to_txt = np.array([[1.1, 2.22, 3.333], [4.0, 5.5, 6.66]])
txt_filename = 'my_data_formatted.csv'
# 텍스트 파일로 저장 (소수점 2자리, 쉼표 구분, 헤더 포함)
header_str = 'Feature1,Feature2,Feature3'
np.savetxt(txt_filename, data_to_txt, fmt='%.2f', delimiter=',', header=header_str, comments='')
print(f"Data saved to {txt_filename} with header.")
# 텍스트 파일에서 불러오기 (헤더 건너뛰기)
loaded_from_txt = np.loadtxt(txt_filename, delimiter=',', skiprows=1)
print("\nData loaded from text file (header skipped):\n", loaded_from_txt)
# 특정 열만 선택하여 불러오기
selected_cols_data = np.loadtxt(txt_filename, delimiter=',', skiprows=1, usecols=(0, 2)) # 첫 번째와 세 번째 열
print("\nLoaded only columns 0 and 2:\n", selected_cols_data)Data saved to my_data_formatted.csv with header.
Data loaded from text file (header skipped):
[[1.1 2.22 3.33]
[4. 5.5 6.66]]
Loaded only columns 0 and 2:
[[1.1 3.33]
[4. 6.66]]
(실행 후 'my_data_formatted.csv' 파일이 생성됩니다. 내용은 다음과 같습니다:
Feature1,Feature2,Feature3
1.10,2.22,3.33
4.00,5.50,6.66
)NumPy에는 이 문서에서 다룬 내용 외에도 다양한 고급 기능들이 있습니다. 여기서는 몇 가지 주제만 간략히 언급합니다.
np.array([('Alice', 25, 160.5), ('Bob', 30, 175.0)], dtype=[('name', 'U10'), ('age', 'i4'), ('height', 'f8')]))np.ma 모듈): 배열의 특정 요소들을 유효하지 않거나 누락된 것으로 표시(마스크)하고, 연산 시 이 값들을 무시할 수 있게 합니다. 결측치 처리에 유용합니다.np.memmap): 매우 큰 배열을 다룰 때, 배열 전체를 메모리에 로드하지 않고 파일의 일부만 메모리에 매핑하여 사용할 수 있게 합니다. 디스크 상의 배열을 마치 메모리 내 배열처럼 다룰 수 있습니다.np.newaxis, np.einsum (아인슈타인 합 표기법) 등을 활용하여 복잡한 배열 연산을 효율적이고 간결하게 표현할 수 있습니다.np.fft 모듈): 신호 처리, 이미지 분석 등에서 사용되는 고속 푸리에 변환(FFT) 및 관련 함수들을 제공합니다.이러한 고급 주제들은 특정 응용 분야에서 NumPy의 강력함을 더욱 확장시켜줍니다. 필요에 따라 공식 문서를 통해 더 깊이 학습할 수 있습니다.
NumPy 학습 내용을 복습하고 응용해볼 수 있는 연습 문제입니다. 각 문제 아래의 '해답 보기'를 클릭하면 정답 코드를 확인할 수 있습니다.
0부터 23까지의 정수를 요소로 가지는 1차원 배열을 생성한 후, 이 배열을 (2, 3, 4) 형태로 바꾸고, 바뀐 배열의 차원 수, 모양, 전체 요소 수, 데이터 타입을 출력하세요.
import numpy as np
# 1단계: 0부터 23까지의 정수를 가지는 1차원 배열 생성
arr = np.arange(24)
print("원본 1차원 배열:", arr)
# 2단계: (2, 3, 4) 형태로 변경
reshaped_arr = arr.reshape((2, 3, 4))
print("\n(2,3,4) 형태로 변경된 배열:\n", reshaped_arr)
# 3단계: 속성 출력
print("\n차원 수 (ndim):", reshaped_arr.ndim)
print("모양 (shape):", reshaped_arr.shape)
print("전체 요소 수 (size):", reshaped_arr.size)
print("데이터 타입 (dtype):", reshaped_arr.dtype)10에서 20까지 (20 포함) 2씩 증가하는 정수 배열을 만들고, 이 배열을 부동소수점(float64) 타입으로 변환한 후 출력하세요.
import numpy as np
int_arr = np.arange(10, 21, 2) # 10, 12, ..., 20
print("생성된 정수 배열:", int_arr)
float_arr = int_arr.astype(np.float64)
# 또는 float_arr = np.array(int_arr, dtype=float)
print("부동소수점 타입으로 변환된 배열:", float_arr)
print("변환된 배열의 데이터 타입:", float_arr.dtype)다음과 같은 2차원 배열 matrix가 주어졌을 때:
matrix = np.arange(1, 17).reshape(4, 4)
# [[ 1 2 3 4]
# [ 5 6 7 8]
# [ 9 10 11 12]
# [13 14 15 16]]a) 세 번째 행의 두 번째, 세 번째 요소를 추출하세요 (예: [10, 11]).
b) 모든 행의 마지막 두 개 열을 추출하세요.
c) 짝수 행(0번, 2번 행)만 선택하여 출력하세요.
d) 배열의 모서리 4개 요소 (1, 4, 13, 16)를 팬시 인덱싱으로 추출하세요.
import numpy as np
matrix = np.arange(1, 17).reshape(4, 4)
print("원본 배열 matrix:\n", matrix)
# a) 세 번째 행(인덱스 2)의 두 번째(인덱스 1), 세 번째(인덱스 2) 요소
result_a = matrix[2, 1:3]
print("\na) [10, 11]:", result_a)
# b) 모든 행의 마지막 두 개 열
result_b = matrix[:, -2:]
print("\nb) 마지막 두 열:\n", result_b)
# c) 짝수 행 (0번, 2번 행)
result_c = matrix[[0, 2]] # 또는 matrix[::2, :]
print("\nc) 짝수 행:\n", result_c)
# d) 모서리 4개 요소 (1, 4, 13, 16)
# (0,0), (0,3), (3,0), (3,3)
rows = np.array([0, 0, 3, 3])
cols = np.array([0, 3, 0, 3])
result_d = matrix[rows, cols]
print("\nd) 모서리 요소 [1, 4, 13, 16]:", result_d)1부터 10까지의 정수를 랜덤하게 섞은 배열을 만드세요 (크기 10). 그 다음, 이 배열에서 5보다 큰 요소들은 모두 0으로 변경하고, 5 이하인 요소들은 원래 값에 100을 더한 값으로 변경하세요. 최종 배열을 출력하세요.
import numpy as np
np.random.seed(0) # 재현성을 위해 시드 설정
arr = np.arange(1, 11)
np.random.shuffle(arr)
print("랜덤하게 섞인 배열:", arr)
# 5보다 큰 요소들의 인덱스
gt_5_condition = arr > 5
# 5 이하인 요소들의 인덱스
lte_5_condition = arr <= 5
arr[gt_5_condition] = 0
arr[lte_5_condition] = arr[lte_5_condition] + 100
print("조건에 따라 변경된 배열:", arr)두 개의 (2,3) 형태의 정수 난수 배열 A와 B를 생성하세요 (값의 범위는 1~10).
a) A와 B의 요소별 합, 곱을 계산하세요.
b) A 배열의 각 요소에 대해 $sin^2(x) + cos^2(x)$ 를 계산하고, 결과가 1에 가까운지 확인하세요.
c) 두 배열 A와 B를 수직으로 합친 후(vstack), 합쳐진 배열의 각 열(column)의 평균과 표준편차를 계산하세요.
import numpy as np
np.random.seed(1) # 재현성
A = np.random.randint(1, 11, size=(2,3))
B = np.random.randint(1, 11, size=(2,3))
print("배열 A:\n", A)
print("배열 B:\n", B)
# a) 요소별 합, 곱
sum_AB = A + B
prod_AB = A * B
print("\na) A + B:\n", sum_AB)
print(" A * B:\n", prod_AB)
# b) sin^2(x) + cos^2(x) 계산
A_float = A.astype(float)
trig_result = np.sin(A_float)**2 + np.cos(A_float)**2
print("\nb) sin^2(A) + cos^2(A):\n", trig_result)
print(" 결과가 1에 가까운지 (np.allclose 사용):", np.allclose(trig_result, 1))
# c) 수직으로 합치고 통계 계산
stacked_AB = np.vstack((A, B))
print("\nc) 수직으로 합쳐진 배열 (A 위, B 아래):\n", stacked_AB)
col_means = np.mean(stacked_AB, axis=0)
col_stds = np.std(stacked_AB, axis=0)
print(" 각 열의 평균:", col_means)
print(" 각 열의 표준편차:", col_stds)1차원 배열 `temperatures_celsius = np.array([0, 10, 20, 30, 40])`가 주어졌을 때, 이 섭씨 온도를 화씨 온도로 변환하는 공식을 사용하여 화씨 온도 배열을 만드세요. 공식: $F = C \times \frac{9}{5} + 32$. 브로드캐스팅을 활용하세요.
import numpy as np
temperatures_celsius = np.array([0, 10, 20, 30, 40])
print("섭씨 온도:", temperatures_celsius)
temperatures_fahrenheit = temperatures_celsius * (9/5) + 32
print("화씨 온도:", temperatures_fahrenheit)다음 선형 연립 방정식을 NumPy를 사용하여 푸세요:
$2x + y - z = 8$
$-3x - y + 2z = -11$
$-2x + y + 2z = -3$
import numpy as np
A = np.array([[2, 1, -1],
[-3, -1, 2],
[-2, 1, 2]])
b = np.array([8, -11, -3])
print("계수 행렬 A:\n", A)
print("상수 벡터 b:", b)
try:
x_solution = np.linalg.solve(A, b)
print("\n해 (x, y, z):", x_solution)
print("A @ x_solution (결과가 b와 같아야 함):", A @ x_solution)
print("검증 성공 여부 (allclose):", np.allclose(A @ x_solution, b))
except np.linalg.LinAlgError as e:
print("\n오류 발생:", e)