

ndarray indexing과 slicing
arr = np.arange(10, 20, 1) # 1차원 ndarray를 생성
print(arr) # [10 11 12 13 14 15 16 17 18 19]
print(arr[0]) # 10
arr[0] = 100
print(arr) # [100 11 12 13 14 15 16 17 18 19]
위와 같이 indexing을 할 경우 기본 Python의 list와 똑같이 실행된다.
arr = np.arange(0, 5, 1)
arr1 = arr[0:2]
print(arr) # [0 1 2 3 4]
print(arr1) # [0 1]
arr[0] = 100
print(arr) # [100 1 2 3 4]
print(arr1) # [100 1]
그러나 slicing의 경우 Python의 list는 slicing을 할 때 새로운 list가 생성되는 반면, NumPy의 ndarray의 경우 View가 생성된다. (arr[0]의 값을 바꾸면 arr1[0]의 값도 함께 변하는 것을 볼 수 있다.
View : view는 기존 ndarray를 (n, m)으로 바꾸어 보여주는 창을 만들어 주는 것 (값 저장 X)
2차원 ndarray의 indexing과 slicing
arr = np.arange(1, 17).reshape(4, 4)
print(arr)
# [[ 1 2 3 4]
# [ 5 6 7 8]
# [ 9 10 11 12]
# [13 14 15 16]]
print(arr[1,1]) # 6
print(arr[2,:]) # [ 9 10 11 12]
print(arr[1:3,:])
# [[ 5 6 7 8]
# [ 9 10 11 12]]
print(arr[1:3,1:3])
# [[ 6 7]
# [10 11]]
2차원 이상의 ndarray의 경우 ','를 기준으로 indexing을 해야 한다.
Boolean indexing
arr = np.random.randint(0, 10, (10,))
print(arr) # [5 8 9 5 0 0 1 7 6 9]
mask1 = np.array([True, False, True, False, True, False, True, False, True, False])
print(arr[mask1]) # [5 9 0 1 6]
Boolean indexing을 위해선 Boolean Mask가 필요하다. 이때 Boolean Mask는 indexing 하려는 ndarray와 shape이 같아야 한다. 또한, Mask 안의 값은 bool형의 데이터로 구성되어야 한다.
mask2 = (arr % 2 == 0)
print(mask2)
# [False True False False True True False False True False]
print(arr[mask2]) # [8 0 0 6]
print(arr[(arr % 2) == 0]) # [8 0 0 6]
또한, 조건식을 사용하여 Boolean Mask를 생성할 수도 있다. 이처럼 ndarray에서 짝수만 추출하고 싶을 때, 조건식을 사용하여 Mask를 생성한 뒤, ndarray에 지정하여 원하는 값만 추출할 수 있다.
Fancy indexing
arr = np.arange(12).reshape(3, 4).copy()
print(arr)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(arr[np.ix_([0,2],[0,2])])
# [[ 0 2]
# [ 8 10]]
Fancy indexing은 ndarray에 index 배열을 전달하여 배열 요소를 참조하는 방식이다.
print(arr[[0,2],2]) # [ 2 10]
만약 arr[0, 2]의 값과 arr[2, 2]의 값을 추출하고 싶다면 위와 같이 행의 위치를 [0, 2], 열의 위치를 2로 전달해주면 된다.
이때 arr[0, 0], arr[0, 2], arr[2, 0], arr[2, 2]의 값을 추출하고 싶다면 어떻게 해야할까?
print(arr[[0,2],[0,2]]) # [ 0 10]
이처럼 행의 위치를 [0, 2], 열의 위치를 [0, 2]로 전달해주면 될 것 같지만 그렇지 않다.
방법 1 : 행을 먼저 추출한 후, 해당 행에 대해 Fancy indexing 적용
print(arr[[0,2]][:,[0,2]]) # [[ 0 2] # [ 8 10]]
방법 2 : NumPy의 ix_() 함수 사용
print(arr[np.ix_([0,2],[0,2])]) # [[ 0 2] # [ 8 10]]
위 두 방법으로 의도했던 값을 추출할 수 있다.
ndarray의 기본 연산
arr = np.arange(1, 13).reshape(3, 4).copy()
print(arr)
# [[ 1 2 3 4]
# [ 5 6 7 8]
# [ 9 10 11 12]]
print(arr * 3) # ndarray * scalar
# [[ 3 6 9 12]
# [15 18 21 24]
# [27 30 33 36]]
print(arr + np.array([1, 2, 3, 4])) # (3 * 4) + (1 * 4)
# [[ 2 4 6 8]
# [ 6 8 10 12]
# [10 12 14 16]]
기본적으로 ndarray의 사칙연산은 같은 shape의 ndarray끼리 이루어진다. 그러나 shape이 다르더라도 바로 오류를 출력하진 않는다. 이때 최대한 shape을 맞추려고 하는데, 이를 broadcasting이라고 한다.
행렬곱 연산
행렬곱?
행렬곱이 없다면 matrix연산은 무조건 같은크기의 사칙연산만을 수행할 수 있다. 하지만 행렬곱을 사용하면 행렬곱 조건을 만족하는 다양한 크기의 행렬을 이용하여 연속적으로 행렬곱을 수행시킬 수 있기 때문이다.
이러한 특성이 Machine Learning이나 Image Processing에서 자주 사용된다.
ex)
입력 : 32 x 32 matrix (image)
출력 : 32 x 10 matrix (다양한 처리가 적용된 image)
행렬곱
(32 x 32) dot (32 x 128) dot (128 x 64) dot (64 x 10)
-> 32 x 10
arr1 = np.array([[1, 2, 3], [4, 5, 6]])
arr2 = np.arange(10, 16, 1).reshape(3, 2).copy()
print(arr1)
# [[1 2 3]
# [4 5 6]]
print(arr2)
# [[10 11]
# [12 13]
# [14 15]]
print(np.matmul(arr1, arr2))
# [[ 76 82]
# [184 199]]
두 행렬간의 행렬곱은 matmul() 혹은 dot() 함수로 수행가능하다. 정확히는 행렬곱은 matmul(), 1차원 벡터의 내적을 구할 때는 dot()을 사용하지만 dot()의 인자로 2차원이 들어올 경우 행렬곱을 수행한다.
np.matmul(A, B)에서 A 행렬의 열 vector와 B 행렬의 행 vector의 size가 같아야 한다. 그렇지 않으면 reshape이나 전치행렬을 사용하여 형 변환 후 크기를 맞추고 연산을 수행해야 한다.
집계함수
arr = np.arange(1, 7).reshape(2, 3).copy()
print(arr)
# [[1 2 3]
# [4 5 6]]
print(np.sum(arr)) # 21
print(arr.sum()) # 21
print(np.cumsum(arr)) # [ 1 3 6 10 15 21]
print(np.mean(arr)) # 3.5
print(np.max(arr)) # 6
print(np.min(arr)) # 1
print(np.argmax(arr)) # 5
print(np.argmin(arr)) # 0
print(np.std(arr)) # 1.707825127659933
print(np.sqrt(arr))
# [[1. 1.41421356 1.73205081]
# [2. 2.23606798 2.44948974]]
print(np.exp(arr))
# [[ 2.71828183 7.3890561 20.08553692]
# [ 54.59815003 148.4131591 403.42879349]]
print(np.log10(arr))
# [[0. 0.30103 0.47712125]
# [0.60205999 0.69897 0.77815125]]
print(np.log(arr))
# [[0. 0.69314718 1.09861229]
# [1.38629436 1.60943791 1.79175947]]
axis
np.random.seed(1)
arr = np.random.randint(0, 5, (3, 2, 3))
print(arr)
# [[[3 4 0]
# [1 3 0]]
# [[0 1 4]
# [4 1 2]]
# [[4 2 4]
# [3 4 2]]]
print(arr.sum()) # 42
print(arr.sum(axis=0)) # 면
# [[7 7 8]
# [8 8 4]]
print(arr.sum(axis=1)) # 행
# [[4 7 0]
# [4 2 6]
# [7 6 6]]
print(arr.sum(axis=2)) # 열
# [[ 7 4]
# [ 5 7]
# [10 9]]
NumPy의 모든 집계함수는 axis를 기준으로 동작한다. 만약 axis(축)를 지정하지 않으면 axis는 None으로 간주되고 모든 대상 영역이 전체 ndarray로 설정된다.
이때 axis에 설정되는 값은 ndarray의 차원에 따라 다르다.
ex)
2차원 ndarray일 때,
- axis=0, 행 (가장 고차원)
- axis=1, 열
3차원 ndarray일 때,
- axis=0, 면 (가장 고차원)
- axis=1, 행
- axis=2, 열