[Data Science] NumPy - indexing과 다양한 연산

ByungJik_Oh·2025년 3월 24일

[Hyundai Rotem KDT]

목록 보기
2/22
post-thumbnail


📚 ndarray indexing과 slicing


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의 연산


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이나 전치행렬을 사용하여 형 변환 후 크기를 맞추고 연산을 수행해야 한다.


📚 NumPy 함수


집계함수

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]]
  1. sum() : 합
  2. cumsum() : 누적합
  3. mean() : 평균
  4. max() : 최대값
  5. min() : 최소값
  6. argmax() : 최대값의 1차원 index
  7. argmin() : 최소값의 1차원 index
  8. std() : 표준편차
  9. sqrt() : 제곱근
  10. exp() : 자연상수의 제곱값 (자연상수 ee = 2.7182...)
  11. log10() : 상용 log 값
  12. log() : 자연 log 값 (자연상수 ee = 2.7182...)

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, 열


profile
精進 "정성을 기울여 노력하고 매진한다"

0개의 댓글