넘파이에서는 기본 파이썬에서 제공하지 않는 강력한 무기가 있습니다.
그건 바로 ndarray를 이용한 인덱싱이 가능하다는 점입니다.
ndarray 인덱싱 기본 형태
import numpy as np
a = np.arange(10)
print(f"ndarray: \n{a}\n")
indices = np.array([0, 3, 4])
print(a[indices])
output :
indices 변수 안에 np.array를 지정하고 그 array안에 원하는 값의 인덱스를 넣어주는 형태입니다. '
인덱스들의 배열인 indices를 다시 a[indices] 형태로 넣어주면 최종적으로 인덱스의 해당하는 값들이 출력되는 것이지요.
기본 파이썬 문법에서는 a[0,3,4] 이런식으로 한번에 넣을 수 없는데 넘파이를 사용하니 편리하지요?
ndarray 인덱싱을 이용하여 shape 변경
ndarray 인덱싱을 이용할때 더욱 강력한 점은 최종 출력값을 원하는 shape으로 다시 만들 수 있다는 사실입니다.
import numpy as np
a = np.random.randint(0, 10, (10,)) # 0이상 10미만 int값중 랜덤으로 10개의 배열만들기
print(f"ndarray: \n{a}\n")
indices = np.array([[1,2,3], [5,8,9]]) # 인덱스 값들이 (2,3)의 형태이다.
print('indices shape', indices.shape)
print(a[indices]) # 출력된 값들은 인덱스의 shape형태로 나타난다.
output :
보시는것처럼 indices 의 shape은 (2,3)의 행렬의 형태입니다.
이건 내부적으로 (2,3)의 공간을 만들어넣고 각 위치에 해당하는 인덱스값을 값을 대입해 최종 결과값을 만드는 형태인것이지요.
그림으로 볼까요?
최종결과값인 a[indices]는 indices의 shape과 같은것을 확인할 수 있습니다.
indice가 3차원인 경우는 어떤지 확인해볼까요?
import numpy as np
a = np.random.randint(0, 20, (10,)) # 0이상 20미만인 값을 (10,)로 만들기
print(f"ndarray: \n{a}\n")
indices = np.random.randint(0, 10, size=(2, 3, 4)) # (2,3,4)형태이며 0이상 10미만값중 랜덤으로 뽑은값
print('indices shape', indices.shape)
print(f"indices: \n{indices}")
print(f"a[indices]: \n{a[indices]}")
output :
indices가 2차원일때와 마찬가지로 3차원일때도 최종 output의 결과가 indices의 shape을 그대로 따르는 것을 볼 수 있습니다.
그림을 통해서도 확인해보죠.
화살표가 너무 많아 일부는 생략을 했는데요.
indices의 shape대로 먼저 행렬을 만들고, 그안에 해당하는 인덱스값을 기준으로 a에서 값을 가져오는것이지요.
당연히 indices의 shape과 같은 최종 output을 확인할 수 있습니다.
여기까지 공부하신분들은 아! 최종결과는 무조건 indices의 shape을 무조건 따르는구나 라고 생각하실 수도있는데요.
사실 문제가 그렇게 간단하지 않습니다.
왜냐하면 지금까지는 a가 단순히 1차원 배열이었거든요
a의 값이 2차원 이상일 경우
import numpy as np
a = np.arange(12).reshape((3,4))
print(f"ndarray: \n{a}\n")
indices = np.array([0,2]) # 만드록 싶은 형태가 (2,)
print(f"indices: \n{indices}")
print(f"a[indices]: \n{a[indices]}")
output :
결과가 좀 다른걸 느끼시나요?
지금까지 배운원리대로라면 indices가 (2,)의 형태니까
최종 결과 값도 (2,)이지 않나?라고 생각하실 겁니다.
하지만 최종 결과가 (2,4)입니다.
그림을 통해 왜그런지 확인해보겠습니다.
우선 앞에서 배웠던것처럼 indices(2,) 니까 (2,)로 만듭니다.
그런데 위에서보는것처럼 인덱스 0을 가져오려고 보니까
스칼라값인 0을 가져오는게 아닙니다.
바로 행렬에서 가장 바깥쪽 차원인 row를 가져오는것입니다.
정리하겠습니다.
1. indices의 shape을 통해 (2,)의 공간을 준비해두었습니다.
2. 그런데 인덱싱을 통해 막상 값을 가져오려다보니 (4,)의 벡터값인 겁니다.
3. 결국 (4,)의 가져오면 이는 (4,)가 2개 있는 것이고 다시말하면 (2,4)를 의미하게 됩니다.
다른 예를 볼까요?
import numpy as np
a = np.arange(12).reshape((3,4))
print(f"ndarray: \n{a}\n")
indices = np.array([0,0,1,1]) # 만드록 싶은 형태가 (4,)
print(f"indices: \n{indices}")
print(f"a[indices]: \n{a[indices]}")
output :
이번엔 indices가 (4,)의 형태입니다. 그림으로 볼까요?
indices의 shape이 (4,)이니 먼저 (4,)의 형태의 공간을 만들어둡니다.
인덱싱을 통해 값을 가져오려고 보니 스칼라값이 아닌 (4,)의 벡터입니다.
즉 (4,)를 4개가진 행렬인 (4,4)가 최종적인 output이 됩니다.
a의 값이 2차원 이상 (확장)
지금까지는 indices의 shape이 1차원이였다면 지금부터는 2차원일때는 어떤 모습인지 확인해보겠습니다.
import numpy as np
a = np.arange(12).reshape((3,4))
print(f"ndarray: \n{a}\n")
indices = np.array([[0,1,2],[-3,-2,-1]]) # 만드록 싶은 형태가 (2,3) 2차원형태
print(f"indices: \n{indices}")
print(f"a[indices]: \n{a[indices]}")
output :
이번에는 indices가 2차원 행렬 (2,3)의 형태입니다.
즉 먼저 (2,3)의 공간을 만든다음 인덱싱을 통해 a에서 벡터들을 가져오는 형태인것이지요.
그림을 통해 확인해보겠습니다.
가장 작은 차원부터 말로 표현하자면..
(4,)가 3개가 존재합니다. ==>(3,4)
그런데 이 (3,4) 짜리 2개 있었던 것이지요 ===> (2,3,4)
최종적으로는 아래와 같은 형태를 가지게 되는것이지요.
indices 가 두개인 경우
지금까지는 인덱스를 하나의 차원으로만 가져오다보니 a가 2차원일경우 row 벡터를 가져오는 상황이었습니다.
하지만 indices가 두개라면 row가 아닌 스칼라 값을 가져올 수 있겠죠?
코드로 확인해보겠습니다.
import numpy as np
a = np.arange(12).reshape((3,4))
print(f"ndarray: \n{a}\n")
indices0, indices1 = np.array(0), np.array(0) # shape = ()
print('0차 :', a[indices0,indices1])
indices0, indices1 = np.array([1]), np.array([2]) # shape = (1,)
print('1차 :' ,a[indices0,indices1])
indices0 = np.array([[0, 1, 2], [0, 1, 2]]) # shape = (2,3)
indices1 = np.array([[0, 1, 2], [1, 2, 3]])
print('2차 :\n' ,a[indices0,indices1])
output:
지금까지했던거와 차이가 있다면 indices가 두개의 형태로 만들고 그 두개의 값이 각각 a[indices0, indices1]로 들어가니 스칼라값을 가져올 수 있는 겁니다.
그런데보시면 규칙성을 찾을 수 있으실겁니다.
indices의 차원에 따라 output도 그대로 같은 차원으로 나오는걸요.
위에서 배웠던 내용과 다르지않죠? 물론 indices가 두개가 있다는점은 다르지만 결국 둘은 서로 짝지어지기 때문에 각각의 shape이 중요합니다.
그림을 통해서 알아볼까요?
a[indices0,indices1]의 인덱스로는 위에 파란색 사각형 끼리 짝을 지어 인덱스로 들어가게 됩니다.
indices의 shape이 (2,3)입니다.
즉 (2,3)의 공간을 먼저 만들고
그 공간에 지정한 인덱스에 상응하는 값(여기선 스칼라값)을 가져오는것입니다.
두개의 인덱스를 가져오니 2차원 행렬에서 값을 가져오는데 스칼라 값을 가져올 수가 있네요.
그럼 a가 3차원이라면?
그렇다면 두개의 인덱스를 이용해도 결국 벡터를 가져오니 indices의 shape이 그대로 유지되지 않겠죠.?
이 부분은 여러분들의 숙제로 남기겠습니다.!
정리
아마 처음 해당 개념을 접하시는분들은 조금 어려우실수도 있을거에요.
하지만 자주보다보면 낯선게 익숙해지면서 당연하게 여겨지시고 이해가(?) 되실테니 반복해서 보시길 추천드려요:)
이글과 읽으면 좋은글
- np.resize vs np.reshape 시리즈 1
- np.resize vs np.reshape 시리즈 2
- np.flatten vs np.ravel 비교 분석
- 브로드 캐스팅 파헤치기 1편
- 브로드 캐스팅 파헤치기 2편
'머신러닝,딥러닝 > 넘파이,numpy' 카테고리의 다른 글
[넘파이 기초] 반올림, 올림, 버림 (0) | 2021.04.27 |
---|---|
[넘파이 기초] axis, keepdims 마스터하기 (0) | 2021.04.23 |
[넘파이 기초] broadcasting(브로드 캐스팅) 파헤치기 2편 (0) | 2021.04.17 |
[넘파이 기초] broadcasting(브로드 캐스팅) 파헤치기 1편 (0) | 2021.04.16 |
[넘파이 기초] flatten 와 ravel의 차이 | 메모리 관리(.copy() vs .view()) (0) | 2021.04.10 |
댓글