파이썬3 노트

numpy 공부: [Indexing] (2)

Jonchann 2018. 8. 8. 16:20

5. Boolean or "mask" index arrays


y = np.arange(35).reshape(5, 7)
# y = [[0, 1, 2, 3, 4, 5, 6],
#    [7, 8, 9, 10, 11, 12, 13],
#    [14, 15, 16, 17, 18, 19, 20],
#    [21, 22, 23, 24, 25, 26, 27],
#    [28, 29, 30, 31, 32, 33, 34]]

b = y > 20
print(y[b]) #[21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34]

boolean을 인덱스에 사용하는 것도 가능하다.

대신 정수로 인덱싱하는 것과 다르게 출력값은 1차원이 된다.

참고로 print(y[b])와 print(y[np.nonzeros(b)])는 같은 출력값을 가진다.


참고로 b를 출력해보면,

[[False False False False False False False]
 [False False False False False False False]
 [False False False False False False False]
 [ True  True  True  True  True  True  True]
 [ True  True  True  True  True  True  True]]

y와 같은 모양의 boolean 행렬이 되어있는 것을 알 수 있다.

때문에 boolean으로 인덱싱 하고도 y와 같은 차원을 갖는 출력값을 얻기 원한다면, b를 인덱싱한 것으로 y를 인덱싱 하면 된다(저번 글에 배열로 배열을 인덱싱 하는 것을 적어놓았음).

# b[: , 5] = [False False False True True]
print(y[b[ : , 5]])
# [[21 22 23 24 25 26 27],
#  [28 29 30 31 32 33 34]]

b[:, 5]의 마지막 2개만 True이므로 2차원 행렬인 y의 4, 5번째 행 만을 출력한다.


처음부터 b를 boolean배열로 만들어서 인덱싱하는 것도 가능하다.

x = np.arange(30).reshape(2, 3, 5)
# [[[ 0  1  2  3  4]
#   [ 5  6  7  8  9]
#   [10 11 12 13 14]]
#
#  [[15 16 17 18 19]
#   [20 21 22 23 24]
#   [25 26 27 28 29]]]

b = np.array([[True, True, False], [False, True, True]])
print(y[b])
# [[ 0  1  2  3  4]
#  [ 5  6  7  8  9]
#  [20 21 22 23 24]
# [25 26 27 28 29]]

하지만 출력은 한 차원 적게 나온다. 행렬로 뽑아냈으니 그런건가.


f = np.arange(15).reshape(3, 5)
b1 = np.array([True, True, False])
b2 = np.array([False, True, False, True, True])

print(f[b1, :])
# [[0 1 2 3 4]
#  [5 6 7 8 9]]

print(f[:, b2])
# [[ 1  3  4]
#  [ 6  8  9]
#  [11 13 14]]

참고로 여기서 print(f[b1, b2])는 오류가 난다.

IndexError: shape mismatch: indexing arrays could not be broadcast together with shapes (2,) (3,) 



6. Combining index arrays with slices


저번 글에 적었던 것 처럼 범위를 정해서 행렬을 잘라내는 것을 해보자.

print(y[np.array([0, 2, 4]), 1:3])

여기서도 마찬가지로 np.array([0, 2, 4])라는 배열을 이용해 y의 행의 범위를 정해준다.


[[0 1 2 3 4 5 6]

 [14 15 16 17 18 19 20]

 [28 29 30 31 32 33 34]]


여기서 1:3으로 인덱스 1에 해당하는 열부터 인덱스 3-1에 해당하는 열까지 슬라이스 한다.


[[1 2]

 [15 16]

 [29 30]]


퀵 튜토리얼에 다른 예도 많이 나오니 같이 적어놔야겠다.

p = np.arange(12).reshape(3, 4)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]
u = np.array([[0, 1],
              [1, 2]])
v = np.array([[2, 1],
              [3, 3]])
# [[ 2  5]
#  [ 7 11]]

u의 [0, 1]은 p의 인덱스 0, 1 번째에 해당하는 행을, 출력할 행렬의 인덱스 0의 범위로 지정한 것이고 


[0 1 2 3]

[4 5 6 7]


[1, 2]는 p의 인덱스 1, 2번째에 해당하는 행을, 출력할 행렬의 인덱스 1의 범위로 지정한 것이다.


[4 5 6 7]

[8 9 10 11]


v의 [2, 1]은 u가 지정한 인덱스 0의 범위에서 각각 인덱스 2, 1에 해당하는 열의 요소를 가져오라는 것이고


[0 1 2 3]

[4 5 6 7]


[3, 3]는 인덱스 1의 범위에서 각각 인덱스 3, 3에 해당하는 열의 요소를 가져오라는 것이다.


[4 5 6 7]

[8 9 10 11]


더 간단히 하기 위해 s = np.array([u, v])라 하고 p[s]를 출력하면 IndexError가 일어나지만 p[tuple(s)]라 하면 위와 동일한 결과를 얻을 수 있다.



7. Structural indexing tools


축을 추가해 차원을 늘리는 방법으로는 np.newaxis가 있다.

아직까지 100% 이해된 것이 아니기 때문에 여러 변주를 주면서 적어보겠다.


print(x[np.newaxis])와 print(x[np.newaxis, :])는 같은 값이 출력되므로 하나만 적겠다.

z = np.arange(12).reshape(4, 3)
# [[0 1 2]
#  [3 4 5]
#  [6 7 8]
#  [9 10 11]]

print(x[np.newaxis])
# [[[0 1 2]
#    [3 4 5]
#    [6 7 8]
#    [9 10 11]]]
print(x[np.newaxis, np.newaixs])

# [[[[0 1 2]
#     [3 4 5]
#     [6 7 8]
#     [9 10 11]]]]


print(x[:, np.newaxis, :])와 print(x[:, np.newaxis])는 같은 값이 출력되므로 하나만 적겠다.

print(x[:, np.newaxis, :])
# [[[0 1 2]]
# 
#   [[3 4 5]]
# 
#   [[6 7 8]]
# 
#   [[9 10 11]]]
print(x[:, np.newaxis]*2)
# [[[0 2 4]]
# 
#  [[6 8 10]]
# 
#  [[12 14 16]]
# 
#  [[18 20 22]]]

print(x[:, np.newaxis]*x)
# [[[  0   1   4]
#   [  0   4  10]
#   [  0   7  16]
#   [  0  10  22]]
# 
#  [[  0   4  10]
#   [  9  16  25]
#   [ 18  28  40]
#   [ 27  40  55]]
# 
#  [[  0   7  16]
#   [ 18  28  40]
#   [ 36  49  64]
#   [ 54  70  88]]
# 
#  [[  0  10  22]
#   [ 27  40  55]
#   [ 54  70  88]
#   [ 81 100 121]]]

위에 적은 x[:, np.newaxis]*2는 각 행의 차원을 하나 늘린 후 2를 곱한 값이다. 3차원 텐서가 출력됐다.


아래 적은 x[:, np.newaxis]*x는 전체 텐서에 각 행을 순서대로 곱한 것이다. 즉

x의 첫 번째 행 [0 1 2]를 x 에 element-wise로 곱한다. 그것이 [[0 1 4], [0 4 10], [0 7 16], [0, 10, 22]]가 된다.

x의 두 번째 행 [3 4 5]를 마찬가지로 곱한다. 그것이 [[0 4 10], [9 16 25], [18 28 40], [27 40 55]]가 된다.

이렇게 다 곱하면 x.shape = (4, 4, 3)인 3차원 텐서가 출력된다.


: 의 위치를 바꿔주면 출력값은 완전히 달라진다.

print(x[np.newaxis, :]*2)
# [[[ 0  2  4]
#   [ 6  8 10]
#   [12 14 16]
#   [18 20 22]]]

print(x[np.newaxis, :]*x)
# [[[  0   1   4]
#   [  9  16  25]
#   [ 36  49  64]
#   [ 81 100 121]]]

 x[np.newaxis, :]*2의 경우 2차원 행렬에 2를 곱해 차원을 하나 늘린 3차원 텐서가 출력되고, x[np.newaxis, :]*x의 경우 2차원 행렬에 2차원 행렬 x를 element-wise로 곱한 뒤 차원이 하나 늘어난 3차원 텐서를 출력한다.