선형대수 기반 프로그램을 가능하게 하는 대표적인 파이썬 라이브러리로, 빠른 계산과 배열 연산 능력을 보장한다.
선형대수는 머신러닝의 주요 알고리즘 중 하나이며 많은 머신러닝 알고리즘은 넘파이를 기반으로 하기에 넘파이에 익숙해지는 것이 매우 중요하다.
(1) 다차원 배열
(2) 고정된 데이터 타입
(3) 효율적인 연산
(4) 브로드캐스팅
(5) 인덱싱, 슬라이싱
(6) 선형 대수 연산
리스트와 같은 다양한 인자를 입력 받아서 ndarray로 변환하는 기능을 수행한다.
shape로 배열의 모양을 확인할 수 있으며 ndarray의 행과 열의 수는 튜플 형태이다.
a = np.array([[[1, 2, 3], [4, 5, 6], [7, 8, 9]]])
a.shape
#(1, 3, 3)
numpy는 다차원 배열을 다루기 위한 강력하고 효율적인 데이터 구조 ndarray를 생성할 수 있다.
파이썬 리스트로 다차원 배열을 생성하거나 내장 함수를 이용할 수 있다.
(1) arange()
0부터 함수 인자 값 - 1 까지의 값을 순차적으로 ndarray의 데이터 값을 변환한다.
seq_array = np.arange(10)
print(seq_array, type(seq_array))
out[]: [0 1 2 3 4 5 6 7 8 9] numpy.ndarray
(2) zeros()
모든 값을 0으로 채운 배열을 만든다.
zero1 = np.zeros((2,4))
out[]: array([[0., 0., 0., 0.],[0., 0., 0., 0.]])
(3) ones()
모든 값을 1로 채운 배열을 만든다.
one1 = np.ones((2,4))
out[]: array([[1., 1., 1., 1.], [1., 1., 1., 1.]])
(4) random.randn()
인자만큼의 랜덤한 숫자 배열을 만든다. 인자에서 중첩 괄호는 사용할 수 없다.
random = np.random.randn(2,4)
out[]: array([[-0.48832617, -1.36402212, -1.05448545, 0.54592232],
[-0.71801095, -0.62745696, 1.47853492, 1.15583403]])
(1) dtype
dtype 함수로 데이터 타입 속성을 확인할 수 있다.
Numpy에서 사용되는 주요 데이터 타입은 다음과 같다.
a.dtype
#dtype('int64')
ndarray는 동일한 데이터 타입을 가져야 한기 때문에, 만약 다양한 데이터 타입을 갖는 배열이 연산하는 경우에는 가장 범용적인 데이터 타입으로 자동 변환된다.
만약 리스트 내에 int형 값과 문자열이 섞여 있는 리스트 array2를 ndarray로 변환한 후 dtype을 출력해보면, 'U21'과 같은 문자열이 출력된다.
이는 유니코드 문자열을 나타내는 데이터 타입인데, 최대 길이가 21인 유니코드 문자열을 나타낸다.
(2) astype()
메모리를 절약해야 할 때 astype() 함수를 사용하여 ndarray 내 데이터 값들의 타입을 변경할 수 있다.
list1 = np.array([1.5, 2.4, 3.7])
print(list1.dtype)
float_list1 = list1.astype('int64')
print(float_list1.dtype)
out[]:
float64
int64
(3) reshape()
ndarray를 특정 차원 및 크기로 변환한다. 인자로 지정된 사이즈 변경이 불가능할 때 오류가 발생한다.
seq_array = np.arange(10)
print(seq_array.reshape(2,5))
out[]: [[0 1 2 3 4][5 6 7 8 9]]
reshape(-1, 1)을 통해 차원을 변경할 수도 있다.
아래 코드에서 array3d의 차원을 2차원 (2,2,2)에서 1차원 (8,1)으로 축소한 것을 볼 수 있다.
seq_array = np.arange(8)
array3d = seq_array.reshape((2,2,2))
print(array3d)
array2d = array3d.reshape(-1,1)
print(array2d)
out[] : [[[0 1][2 3]] [[4 5][6 7]]] [[0][1] [2][3] [4][5] [6][7]]
(4) T
numpy의 연산은 각 원자의 값에 연산을 처리하지 않고, 배열에 연산을 처리하면 배열 안에 있는 각 값들에게 일괄로 연산이 처리가 된다. 이를 벡터화라고 한다.
arr = np.arange(10)
arr + 2
out[]: array([ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
다차원 배열의 비교는 불리언 배열을 반환한다. 아래 코드와 같이 특정 데이터를 조회할 수 있다.
arr < 5
out[]: array([ True, True, True, True, True, False, False, False, False, False])
(1) 단일 값 추출
객체에 해당하는 위치의 인덱스 값을 [] 안에 입력한다.
array1 = np.arange(start=1, stop=10)
array1[3]
out[]: 4
다차원에서 단일 값을 추출할 수 있다.
array2 = array1.reshape(3, 3)
print('1행 1열 값: ', array2[0, 0])
out[]: 1행 1열 값: 9
(2) 슬라이싱
':'를 사용하여 연속 데이터를 추출한다.
arr = np.arange(10)
arr[3:9]
out[]: array([3, 4, 5, 6, 7, 8])
(3) 2차원 ndarray 슬라이싱
2차원 ndarray에서 슬라이싱으로 데이터에 접근할 때는 콤마(,)로 행과 열 인덱스를 지칭한다.
arr= np.arange(start = 1, stop= 10)
arr2 = arr.reshape(3,3)
arr2[0:2]
out[]: array([[1, 2, 3], [4, 5, 6]])
arr= np.arange(start = 1, stop= 10)
arr2 = arr.reshape(3,3)
arr2[0:3, 1:3]
out[]: array([[2, 3], [5, 6], [8, 9]])
arr= np.arange(start = 1, stop= 10)
arr2 = arr.reshape(3,3)
arr2[1:, :2]
out[]: array([[4, 5], [7, 8]])
arr= np.arange(start = 1, stop= 10)
arr2 = arr.reshape(3,3)
arr2[:3, 0]
out[]: array([1, 4, 7])
(4) 팬시 인덱싱
arr= np.arange(start = 1, stop= 10)
arr2 = arr.reshape(3,3)
arr2[[0]]
out[]: array([[1, 2, 3]])
arr= np.arange(start = 1, stop= 10)
arr2 = arr.reshape(3,3)
arr2[[0,2]]
out[]:
array([[1, 2, 3],
[7, 8, 9]])
arr= np.arange(start = 1, stop= 10)
arr2 = arr.reshape(3,3)
arr2[[0,2], 1]
out[]: array([2, 8])
arr= np.arange(start = 1, stop= 10)
arr2 = arr.reshape(3,3)
arr2[[0,2], 0:2]
out[]:
array([[1, 2],
[7, 8]])
arr_2d = np.array([[1, 2], ['a', 'b']])
print(arr_2d)
arr_2d[1][1] = 'K'
print(arr_2d)
out[]:
[['1' '2']['a' 'b']]
[['1' '2']['a' 'K']]
arr = np.arange(start=1, stop=10)
arr[arr < 5] = 10
print(arr)
out[]: [10 10 10 10 5 6 7 8 9]
(1) np.sort()
원본 행렬은 그래도 유지한 채 정렬된 행렬을 반환한다.
arr = np.arange(start=1, stop=10)
arr[arr < 5] = 10
arr_sort = np.sort(arr)
print(arr_sort)
print(arr)
out[]:
[ 5 6 7 8 9 10 10 10 10][10, 10, 10, 10, 5, 6, 7, 8, 9]
(2) ndarray.sort()
원본 행렬 자체를 정렬하기에 반환 값은 None이다.
arr = np.arange(start=1, stop=10)
arr[arr < 5] = 10
print(arr)
arr.sort()
print(arr)
out[]:
[10 10 10 10 5 6 7 8 9][ 5 6 7 8 9 10 10 10 10]
(3)axis 축 값 설정
행렬 정렬을 수행하기 위해서 axis 축 값을 설정해야 한다.
아래 코드에서 첫 번째 행을 정렬하면 [1,7], 두 번째 행을 정렬하면 [2,6] 이기 때문에 최종적으로 정렬된 배열은 [[1, 7], [2, 6]] 이다.
array2 = np.array([[7, 1], [2, 6]])
print(np.sort(array2, axis = 0))
out[]: [[1, 7], [2, 6]]
아래 코드에서 첫 번째 열 [7,2]를 정렬하면 [2,7], 두 번째 행을 정렬하면 [1,6] 이기 때문에 최종적으로 정렬된 배열은 [[2, 1], [7, 6]] 이다.
array2 = np.array([[7, 1], [2, 6]])
print(np.sort(array2, axis = 1))
out[]: [[2, 1], [7, 6]]
(4) np.argsort()
정렬된 행렬의 인덱스를 반환한다.
org_array = np.array([4, 2, 8, 6])
np.argsort(org_array)
out[]: array([1, 0, 3, 2])
(1) np.dot()
행렬의 내적을 구하기 위해서 np.dot() 함수를 사용한다.
아래 코드에서 a는 2 x 3 행렬이며, b는 3 x 2 행렬이다.
(내적의 조건: 첫 번째 행렬의 열 수가 두 번째 행렬의 행 수와 같아야 한다.)
행렬내적의 계산 과정은 다음과 같다.
[1, 2, 3][7, 9, 11] = 1 7 + 2 9 + 3 11 = 58
[1, 2, 3][8, 10, 12] = 1 8 + 2 10 + 3 12 = 64
[4, 5, 6][7, 9, 11] = 4 7 + 5 9 + 6 11 = 139
[4, 5, 6][8, 10, 12] = 4 8 + 5 10 + 6 * 12 = 154
a = np.array([[1, 2, 3], [4, 5, 6]])
b = np.array([[7, 8], [9, 10], [11, 12]])
print(np.dot(a,b))
out[]:
[[ 58 64][139 154]]
(2) np.transpose()
np.transpose() 함수는 원 행렬에서 행과 열의 위치를 교환한 행렬을 반환한다.
a = np.array([[1, 2], [3, 4]])
np.transpose(a)
out[]:
array([[1, 3],
[2, 4]])
배열 내 같은 위치의 원소끼리 연산을 한다.
a = np.array([[11, 12, 13], [2, 3, 4]])
a+a
out[]:
[[22 24 26][ 4 6 8]]
동일한 배열 모양을 가진 서로 다른 배열끼리도 연산이 가능하다.
이 때 배열의 모양은 동일해야 한다. 동일하지 않으면 "ValueError: operands could not be broadcast together with shapes "과 같은 에러가 뜨게 된다.
더하기 이외에 빼기, 곱하기, 나누기 등 사칙연산이 모두 가능하다.
a = np.array([[11, 12, 13], [2, 3, 4]])
a+3
out[]:
array([[14, 15, 16],
[ 5, 6, 7]])
a = np.array([[11, 12, 13], [2, 3, 4]])
b = np.array([[2, 3, 4], [11, 12, 13]])
a+b
out[]:
array([[13, 15, 17],
[13, 15, 17]])
(1) sum()
(2) mean()
(3) prod()
(4) max(), min()
(5) std(), var()