728x90
넘파이(Numpy)선형대수학 벡터와 다차원 배열
머신러닝엔지니어의 홈그라운드
행렬이나 대규모 다차원 배열을 쉽게 처리할 수 있도록 지원하는 파이썬의 라이브러리
import numpy as np
넘파이 배열의 구조
- 넘파이 모듈의 기본 배열은 ndarray(n-dimension array, 다차원 배열)
다차원배열 변환
- np.array() : 다차원 배열을 만드는 함수
li = [1, 2, 3, 4]
li_arr = np.array(li)
li_arr
array([1, 2, 3, 4])
type(li_arr)
numpy.ndarray
# 튜플을 다차원배열로 변환
tu = (1, 2, 3, 4)
tu_arr = np.array(tu)
tu_arr
array([1, 2, 3, 4])
type(tu_arr)
numpy.ndarray
- 배열도 mutable 의 특징을 가짐
- 변수를 다른 변수에 복사한 뒤 데이터를 수정하면 모든 변수의 데이터가 수정됨
# mutable데이터이므로 co_arr를 수정해도 li_arr값이 바뀜
co_arr = li_arr
id(co_arr), id(li_arr)
(2199139204912, 2199139204912)
co_arr
array([1, 2, 3, 4])
co_arr[0] = 100
co_arr
array([100, 2, 3, 4])
li_arr
array([100, 2, 3, 4])
# 다차원 배열의 값을 복사해서 새로운 배열을 만들면
# 동일한 값을 가진 새로운 배열이 만들어지기 때문에 서로 다른 데이터가 됨
new_arr = np.array(li_arr)
# 따라서 서로 다른 id값을 가짐
id(new_arr), id(li_arr)
(2199139205008, 2199139204912)
id(new_arr), id(li_arr)
(2199139205008, 2199139204912)
new_arr[0] = 99
new_arr
array([99, 2, 3, 4])
li_arr
array([100, 2, 3, 4])
- 파이썬 리스트는 다양한 자료형을 사용할 수 있지만 넘파이 배열은 같은 자료형만 넣을 수 있음
- 다차원 배열을 생성할 때 자료형을 지정하지 않으면 내부의 원소를 보고 자동으로 추론해서 만듦
# float 자료형을 지정해서 다차원배열 만들기
fl_arr = np.array(li, dtype = float)
fl_arr
array([1., 2., 3., 4.])
# 판다스가 내부적으로 넘파이를 쓰기 때문에 명령어가 비슷한 경우가 많음
fl_arr.dtype
dtype('float64')
- 2차원배열이란
- 2개의 축을 가지는 배열(행, 열)
- 두 개의 1차원 배열이 쌓여서 하나의 2차원 배열이 만들어짐
- 2개의 축을 가지는 배열(행, 열)
arr2d = np.array([[1, 2, 3], [4, 5, 6]])
# 각 축의 원소의 개수
arr2d.shape
(2, 3)
# 축의 개수
arr2d.ndim
2
arr2d.dtype
dtype('int32')
arr2d
array([[1, 2, 3],
[4, 5, 6]])
# 다차원 배열을 1차원 배열로 조회
arr2d.flatten()
array([1, 2, 3, 4, 5, 6])
배열에서 원소 조회하기
# 1차원 배열
li_arr
array([100, 2, 3, 4])
li
[1, 2, 3, 4]
li[0]
1
# 다차원 배열에서 원소 조회
list2d = arr2d.tolist() # 배열을 리스트로 변환
list2d
[[1, 2, 3], [4, 5, 6]]
arr2d
array([[1, 2, 3],
[4, 5, 6]])
# 리스트는 정수만 이용해서 원소 조회 가능
# 따라서 리스트 내 리스트의 원소를 조회할 때는 인덱싱으르 두 번 사용해야함
list2d[0][1]
2
list2d[0, 1]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[81], line 1
----> 1 list2d[0, 1]
TypeError: list indices must be integers or slices, not tuple
# 다차원 배열은 행과 열의 인덱스를 튜플로 조회할 수 있음
arr2d[0, 1]
2
arr2d[0]
array([1, 2, 3])
arr2d[0][1]
2
슬라이싱
arr1 = np.array([[0, 1, 2, 3], [4, 5, 6, 7]])
arr1
array([[0, 1, 2, 3],
[4, 5, 6, 7]])
arr1.shape
(2, 4)
# 첫 번째 행 전체
arr1[0, :]
array([0, 1, 2, 3])
# 두 번째 열 전체
arr1[:, 1]
array([1, 5])
# 두 번째 행의 두 번째 열부터 끝 열까지
arr1[1, 1:]
array([5, 6, 7])
# 각 행의 두 번째 열까지
arr1[:, :2]
array([[0, 1],
[4, 5]])
연습문제
- 아래의 행렬에서 값 7을 인덱싱
- 아래의 행렬에서 값 14를 인덱싱
- 아래의 행렬에서 배열 [6, 7] 을 슬라이싱
- 아래의 행렬에서 배열 [7, 12] 를 슬라이싱
- 아래의 행렬에서 배열 [[3, 4], [8, 9]]를 슬라이싱
m = np.arange(15).reshape(3, -1)
m
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
# 1. 값 7을 인덱싱
m[1, 2]
7
# 2. 값 14를 인덱싱
m[2, 4]
14
# 3. 배열 [6, 7] 을 슬라이싱
m[1, 1:3]
m[1, (1,2)]
array([6, 7])
# 4. 배열 [7, 12] 를 슬라이싱
m[1:3, 2]
array([ 7, 12])
# 5. 배열 [[3, 4], [8, 9]]를 슬라이싱
m[:2, 3:]
array([[3, 4],
[8, 9]])
- 아래의 행렬에서 배열 [8, 14]를 슬라이싱
- 아래의 행렬에서 배열 [[11, 12], [17, 18]]을 슬라이싱
- 아래의 행렬에서 배열[3, 9, 15, 21]을 슬라이싱
m = np.arange(1, 25).reshape(4, 2, 3)
# 4, 2, 3배열
m
array([[[ 1, 2, 3],
[ 4, 5, 6]],
[[ 7, 8, 9],
[10, 11, 12]],
[[13, 14, 15],
[16, 17, 18]],
[[19, 20, 21],
[22, 23, 24]]])
#1 배열 [8, 14]를 슬라이싱
m[1:3, 0, 1]
array([ 8, 14])
#2 배열 [[11, 12], [17, 18]]을 슬라이싱
m[1:3, 1, 1:3]
array([[11, 12],
[17, 18]])
#3 배열[3, 9, 15, 21]을 슬라이싱
m[:, 0, 2]
array([ 3, 9, 15, 21])
파이썬 리스트와 넘파이 배열의 차이
- 넘파이 배열은 배열끼리 연산이 가능하지만 파이썬 리스트는 값의 추가만 가능
- 넘파이 배열은 숫자와의 연산도 가능하지만 파이썬 리스트는 불가능
- 파이썬 리스트 : 리스트 요소를 반복하는 것은 가능
- 사용 용도
- 파이썬 리스트는 값을 추가하거나 제거하는 일에 사용
- 넘파이 배열은 수치 계산이 많고 복잡하거나 다차원배열이 필요할 때 사용
전치 연산(transpose)
- 2차원 배열의 행과 열을 바꾸는 연산
arr = np.array([[1, 2, 3], [4, 5, 6]])
arr
array([[1, 2, 3],
[4, 5, 6]])
arr.shape
(2, 3)
arr.T
array([[1, 4],
[2, 5],
[3, 6]])
arr.T.shape
(3, 2)
배열 크기 변형
# arange: 파이썬의 range와 같으나, 넘파이의 다차원 배열을 만들어줌
arr1 = np.arange(12)
arr1
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
# reshape(3, 4): 3, 4의 배열로 만들어줌(12개의 요소가 있으므로 빠지는 값이 있으면 안됨 3X4)
arr2 = arr1.reshape(3, 4)
arr2
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
- 사용하는 원소의 수가 정해져 있기 때문에 reshape의 형태 원소 중 하나는 -1로 대체할 수 있음
arr1.reshape(3, -1)
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
arr1.reshape(2, 2, -1)
array([[[ 0, 1, 2],
[ 3, 4, 5]],
[[ 6, 7, 8],
[ 9, 10, 11]]])
arr1.reshape(2, -1, 2)
array([[[ 0, 1],
[ 2, 3],
[ 4, 5]],
[[ 6, 7],
[ 8, 9],
[10, 11]]])
넘파이 함수의 특징
- 벡터화 연산
- 벡터와 행렬의 연산은 구성하는 원소별로 처리하는 것이 보통
- 동일한 인덱스의 원소끼리 계산을 처리하는 것을 벡터화 연산이라고 함
- 유니버설 함수(universal function)
- 벡터화 연산을 지원하는 특별한 함수를 유니버설 함수라고 함
# 일반 파이썬 함수의 타입 확인
def test():
pass
type(test)
function
# 유니버설 함수 확인
type(np.add)
numpy.ufunc
# 넘파이 모듈에는 유니버설 함수도 있고 일반 함수도 있음
type(np.sort)
numpy._ArrayFunctionDispatcher
list2d + list2d
[[1, 2, 3], [4, 5, 6], [1, 2, 3], [4, 5, 6]]
np.add(list2d, list2d)
array([[ 2, 4, 6],
[ 8, 10, 12]])
벡터화 연산
벡터화 연산이 필요한 이유
- data 리스트 내의 각 값에 2를 곱해야 하는 경우
# 리스트를 통한 풀이
data = list(range(10))
data
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
data * 2
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
ans = []
for i in data:
ans.append(i * 2)
ans
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
# 넘파이를 통한 풀이
data = np.array(data)
data * 2
array([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18])
넘파이 벡터화 연산
- 벡터화 연산은 비교 연산과 논리 연산을 포함한 모든 종류의 수학 연산에 대해 적용됨
a = np.array([1, 2, 3])
b = np.array([10, 20, 30])
2 * a + b
array([12, 24, 36])
# 넘파이 비교연산: 넘파이 연산에서는 각 요소가 2와 같은지 비교한다
a == 2
array([False, True, False])
# 파이썬에서는 False만 반환
[1, 2, 3] == 2
False
b > 10
array([False, True, True])
(a == 2) & (b > 10)
array([False, True, False])
a[a == 2]
array([2])
b[b > 10]
array([20, 30])
- 배열끼리의 연산
arr1 = np.arange(6, 10)
arr1
array([6, 7, 8, 9])
arr2 = np.arange(10, 14)
arr2
array([10, 11, 12, 13])
# 두 개의 다차원 배열을 곱하면 동일한 인덱스의 원소끼리 곱셈한 결과인 배열이 반환됨
arr1 * arr2
array([ 60, 77, 96, 117])
# arr1에 차원 추가
new_arr1 = arr1.reshape(-1, 1)
new_arr1
array([[6],
[7],
[8],
[9]])
new_arr1.shape
(4, 1)
# arr2에 차원 추가
new_arr2 = arr2.reshape(1, -1)
new_arr2
array([[10, 11, 12, 13]])
new_arr2.shape
(1, 4)
# 행렬 요소별 곱셈 연산
new_arr1 * new_arr2
array([[ 60, 66, 72, 78],
[ 70, 77, 84, 91],
[ 80, 88, 96, 104],
[ 90, 99, 108, 117]])
# 행렬곱 연산
new_arr1 @ new_arr2
array([[ 60, 66, 72, 78],
[ 70, 77, 84, 91],
[ 80, 88, 96, 104],
[ 90, 99, 108, 117]])
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
# * 연산자는 요소별 곱셈을 수행
result = a * b
result
array([[ 5, 12],
[21, 32]])
# @ 연산자는 행렬곱을 수행
a @ b
array([[19, 22],
[43, 50]])
브로드캐스팅(broadcasting)
- 배열끼리 연산을 하기 위해서는 두 배열의 크기가 같아야 함
- 서로 다른 크기를 가진 배열의 사칙 연산을 하는 경우 넘파이에서는 크기가 작은 배열을 자동으로 반복 확장하여 크기가 큰 배열에 맞춤
new_arr2
array([[10, 11, 12, 13]])
new_arr1 + new_arr2
array([[16, 17, 18, 19],
[17, 18, 19, 20],
[18, 19, 20, 21],
[19, 20, 21, 22]])
문제
- 아래의 배열에서 3의 배수를 찾기
- 아래의 배열에서 4로 나누면 1이 남는 수를 찾기
- 아래의 배열에서 3으로 나누면 나누어지고 4로 나누면 1이 남는 수를 찾기
x= np.arange(1, 21)
x
array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20])
# 3의 배수를 찾기
x[x%3==0]
array([ 3, 6, 9, 12, 15, 18])
# 4로 나누면 1이 남는 수를 찾기
x[x%4==1]
array([ 1, 5, 9, 13, 17])
# 3으로 나누면 나누어지고 4로 나누면 1이 남는 수를 찾기
x[(x%3==0) & (x%4==1)]
array([9])
축에 따른 연산
- 동일한 축에 있는 원소들을 계산하기 위해서 필요함
1차원배열
arr1 = np.array([1, 2, 3, 4])
arr1
array([1, 2, 3, 4])
# 합계
np.sum(arr1)
10
arr1.sum()
10
# 최댓값
arr1.max()
4
np.max(arr1)
4
# 최솟값
arr1.min()
1
np.min(arr1)
1
# 최솟값의 위치
arr1.argmin()
0
np.argmin(arr1)
0
# 최댓값의 위치
arr1.argmax()
3
np.argmax(arr1)
3
#memo 어느 컬럼의 값이 최댓값으로 관측이 되는지 (최댓값의 위치)가 중요. 이럴 때 argmax를 사용
# 평균
arr1.mean()
2.5
np.mean(arr1)
2.5
# 중앙값 arr1.median()는 안됨
np.median(arr1)
2.5
2차원 배열
arr2 = arr1.reshape(2, 2)
arr2
array([[1, 2],
[3, 4]])
# 모든 원소를 더한 합
np.sum(arr2)
10
# 수직방향으로 같은 열에 속한 원소만 합산
np.sum(arr2, axis = 0)
array([4, 6])
# 수평방향으로 같은 행에 속한 원소만 합산
np.sum(arr2, axis = 1)
array([3, 7])
arr3 = np.random.randint(0, 3, size = (2, 3))
arr3
array([[1, 0, 1],
[2, 1, 2]])
np.sum(arr3, axis = 0)
array([3, 1, 3])
arr3.sum(axis = 0)
array([3, 1, 3])
np.sum(arr3, axis = 1)
array([2, 5])
3차원 배열
arr4 = np.arange(1, 9).reshape(2, 2, 2)
arr4
array([[[1, 2],
[3, 4]],
[[5, 6],
[7, 8]]])
# 모든 원소를 더한 합
np.sum(arr4)
36
# 두 개의 행렬의 동일한 인덱스의 원소를 합한 값
np.sum(arr4, axis = 0)
array([[ 6, 8],
[10, 12]])
# 각 2차원 배열의 열의 합
np.sum(arr4, axis = 1)
array([[ 4, 6],
[12, 14]])
# 각 2차원 배열의 행의 합
np.sum(arr4, axis = 2)
array([[ 3, 7],
[11, 15]])
np.sum(arr4, axis = (0, 1))
array([16, 20])
np.sum(arr4, axis = (0, 2))
array([14, 22])
np.sum(arr4, axis = (1, 2))
array([10, 26])
arr5 = np.random.randint(0, 3, size = (2, 3, 4))
arr5
array([[[1, 2, 0, 0],
[0, 0, 1, 2],
[0, 1, 2, 2]],
[[0, 1, 1, 1],
[0, 1, 0, 0],
[2, 1, 1, 1]]])
np.sum(arr5, axis = 0)
array([[1, 3, 1, 1],
[0, 1, 1, 2],
[2, 2, 3, 3]])
np.sum(arr5, axis = 1)
# 메모: axis에 지정한 인덱스가 없어진다고 생각하면 됨
array([[1, 3, 3, 4],
[2, 3, 2, 2]])
np.sum(arr5, axis = 2)
array([[3, 3, 5],
[3, 1, 5]])
np.sum(arr5, axis = (0, 1))
array([3, 6, 5, 6])
np.sum(arr5, axis = (0, 2))
array([ 6, 4, 10])
np.sum(arr5, axis = (1, 2))
array([11, 9])
연습문제
- 전체의 최댓값
- 각 행의 합
- 각 행의 최댓값
- 각 열의 평균
- 각 열의 최솟값
x = np.random.random((5, 6))
x
array([[0.20038014, 0.33761304, 0.58716728, 0.80374347, 0.04548007,
0.84502681],
[0.60990788, 0.23293953, 0.38783746, 0.39304561, 0.40699699,
0.2521242 ],
[0.96355814, 0.74149859, 0.90786554, 0.99431617, 0.29592062,
0.08274319],
[0.00170032, 0.19394994, 0.7575594 , 0.71126109, 0.84484799,
0.25433904],
[0.97162765, 0.25708811, 0.94897902, 0.07004172, 0.09798152,
0.31262803]])
# 전체의 최댓값
x.max()
0.9943161733732123
# 각 행의 합
np.sum(x, axis = 1)
x.sum(axis = 1)
array([2.8194108 , 2.28285167, 3.98590225, 2.76365778, 2.65834605])
# 각 행의 최댓값
np.max(x, axis = 1)
x.max(axis = 1)
array([0.84502681, 0.60990788, 0.99431617, 0.84484799, 0.97162765])
# 각 열의 평균
np.mean(x, axis = 0)
x.mean(axis = 0)
array([0.54943483, 0.35261784, 0.71788174, 0.59448161, 0.33824544,
0.34937225])
# 각 열의 최솟값
np.min(x, axis = 0)
x.min(axis = 0)
array([0.00170032, 0.19394994, 0.38783746, 0.07004172, 0.04548007,
0.08274319])
- 각 2차원 배열에서의 최댓값
- 각 2차원 배열에서의 행의 합
- 각 2차원 배열에서의 각 행의 최댓값
- 전체 열의 평균
- 각 2차원 배열에서의 열의 평균
- 각 2차원 배열에서의 각 열의 최솟값
data_3d = np.random.random((3, 5, 6))
data_3d
array([[[0.59962204, 0.75798068, 0.15908062, 0.09949244, 0.60570942,
0.77421978],
[0.76711999, 0.78082797, 0.65943262, 0.9854782 , 0.09253896,
0.14591833],
[0.94912589, 0.95910646, 0.71513689, 0.13548017, 0.06884772,
0.87266148],
[0.5842159 , 0.35157257, 0.91342156, 0.16606104, 0.38627379,
0.92404421],
[0.94783183, 0.27094533, 0.7573572 , 0.62587953, 0.14000422,
0.91606323]],
[[0.66863074, 0.94329112, 0.50910555, 0.11549423, 0.73525703,
0.51919633],
[0.1159518 , 0.04522405, 0.23273843, 0.50271341, 0.33305661,
0.72748196],
[0.35930582, 0.60336696, 0.56833567, 0.11338679, 0.74080759,
0.56089899],
[0.86964779, 0.64711402, 0.27213468, 0.59641798, 0.08601652,
0.65814645],
[0.36824712, 0.57657667, 0.36958634, 0.75077496, 0.45472241,
0.04145205]],
[[0.86177556, 0.45171653, 0.56784964, 0.23810024, 0.52237345,
0.97351832],
[0.6508929 , 0.67501394, 0.45822788, 0.04328172, 0.56626508,
0.25245257],
[0.65974149, 0.69265882, 0.17731165, 0.16655758, 0.91152876,
0.54121063],
[0.31211153, 0.92303648, 0.99591766, 0.7289974 , 0.77809139,
0.73822501],
[0.48305115, 0.56173461, 0.51174974, 0.66493751, 0.40835996,
0.05475567]]])
# 각 2차원 배열에서의 최댓값
data_3d.max(axis = (1, 2))
array([0.9854782 , 0.94329112, 0.99591766])
# 각 2차원 배열에서의 행의 합
data_3d.sum(axis = 2)
array([[2.99610498, 3.43131607, 3.7003586 , 3.32558907, 3.65808135],
[3.490975 , 1.95716625, 2.94610181, 3.12947744, 2.56135955],
[3.61533374, 2.64613409, 3.14900893, 4.47637947, 2.68458864]])
# 각 2차원 배열에서의 각 행의 최댓값
data_3d.max(axis = 2)
array([[0.77421978, 0.9854782 , 0.95910646, 0.92404421, 0.94783183],
[0.94329112, 0.72748196, 0.74080759, 0.86964779, 0.75077496],
[0.97351832, 0.67501394, 0.91152876, 0.99591766, 0.66493751]])
# 전체 열의 평균
data_3d.mean(axis = (0, 1))
array([0.61315144, 0.61601108, 0.52449241, 0.39553688, 0.45532353,
0.58001633])
# 각 2차원 배열에서의 열의 평균
data_3d.mean(axis = 1)
array([[0.76958313, 0.6240866 , 0.64088578, 0.40247827, 0.25867482,
0.72658141],
[0.47635665, 0.56311456, 0.39038013, 0.41575747, 0.46997203,
0.50143515],
[0.59351453, 0.66083208, 0.54221131, 0.36837489, 0.63732373,
0.51203244]])
# 각 2차원 배열에서의 각 열의 최솟값
data_3d.min(axis = 1)
array([[0.5842159 , 0.27094533, 0.15908062, 0.09949244, 0.06884772,
0.14591833],
[0.1159518 , 0.04522405, 0.23273843, 0.11338679, 0.08601652,
0.04145205],
[0.31211153, 0.45171653, 0.17731165, 0.04328172, 0.40835996,
0.05475567]])
Inf 와 nan
- 넘파이에서는 무한대를 표현하기 위한 np.inf 와 정의할 수 없는 숫자를 나타내는 np.nan 이 있음
np.array([0, 1, -1, 0]) / np.array([1, 0, 0, 0])
C:\Users\ITSC\AppData\Local\Temp\ipykernel_6908\2068585620.py:1: RuntimeWarning: divide by zero encountered in divide
np.array([0, 1, -1, 0]) / np.array([1, 0, 0, 0])
C:\Users\ITSC\AppData\Local\Temp\ipykernel_6908\2068585620.py:1: RuntimeWarning: invalid value encountered in divide
np.array([0, 1, -1, 0]) / np.array([1, 0, 0, 0])
[560]:
array([ 0., inf, -inf, nan])
728x90
'06_Numpy' 카테고리의 다른 글
Numpy(넘파이)_연습문제 (0) | 2025.03.11 |
---|---|
02_선형대수 기본 (0) | 2025.03.10 |
넘파이(NumPy) documentation 확인 방법 (0) | 2025.03.10 |