Onik Lab.

NumPy 배열 형태

February 04, 2020 | 7 Minute Read • 0 Comments

안녕하세요. 이번 글은 지난번 글에 이어서 NumPy 배열인 ndarray를 어떻게 다루는지를 다루겠습니다.

NumPy 배열 값 표현

NumPy 배열의 값을 표현하는 것은 기본적으로 리스트, 튜플의 인덱싱(Indexing), 슬라이싱(Slicing), 증가폭 표현(Stride/Reverse)과 같은 방식을 취하나, 다차원 배열인 만큼 이를 표현하기 위해서 콤마(,) 부호를 사용하여 표현할 수 있습니다.

아래와 같이 2차원 배열이 있다고 했을 때, 각 차원을 구분하여 나타내는 방법은 다음과 같습니다.

import numpy as np
c = np.array([[0,1,2,3],[4,5,6,7],[8,9,10,11]])

print(c[1,2])   # 6
print(c[2,1:3]) # [9, 10, 11]
print(c[:,0])   # [0, 4, 8]

위와 같이 인덱싱, 슬라이싱은 동일한 형태로 구성되며, 차원도 콤마 기호로 구분하여 나타낼 수 있는 것을 알 수 있습니다.

또한 ndarray에서는 다차원 배열을 다음과 같이 나타낼 수 있습니다.

import numpy as np
c = np.array( [[[ 0, 1, 2], [ 10, 12, 13]],
	   [[100,101,102], [110,112,113]]])

print(c[1,...])            # [[100, 101, 102][110, 112, 113]]
print(c[...,2])            # [[2 13][102 113]]

c[1,…]은 c[1,:,:]와 같은 표현이고, c[…,2]는 c[:,:,2]와 같은 표현입니다.

또한 x[4,:,:,5,:]는 x[4,…,5,:]와 같은 형태로도 나타낼 수 있습니다.

이러한 점들(dots,’…‘)을 표현해서 다차원 배열의 부분 표현도 역시 가능합니다.

배열 반복

배열 반복은 리스트, 튜플과 동일한 형태로 사용할 수 있습니다.

import numpy as np

b = np.array([[0,1,2,3],[10,11,12,13],[20,21,22,23],[30,31,32,33],[40,41,42,43]])

for row in b:
    print(row)

[0 1 2 3]
[10 11 12 13]
[20 21 22 23]
[30 31 32 33]
[40 41 42 43]

다만 배열 차원이 아닌 요소를 대상으로 반복하려면 다음과 같이 나타냅니다.

for element in b.flat:
    print(element)

배열 형태 변경

배열 형태 변경은 지난 글에서 reshape() 메소드를 다루는 식으로 언급하였으며, 실제로도 reshape()를 거의 대부분 사용합니다.

다만 아래와 같이 다른 형태로 변경도 가능하니 참고하면 될 것 같습니다.

import numpy as np

c = np.array( [[[  0,  1,  2, 3],
                        [ 10, 12, 13, 15]],
                       [[100,101,102, 104],
                        [110,112,113, 114]],
                       [[200,201,202, 203],
                        [210,211,212, 215]]])
print(c.shape)			# (3, 2, 4)
print(c.ravel())			# [0, 1, 2, 3, 10, 12, 13, 15, 100, … ]
print(c.T)				# [[[0 100 200][10 110 210]][[1 101 201]…]]
print(c.T.shape)			# (4, 2, 3)

위 메소드 및 속성 외에도 resize()를 사용할 수도 있으나, reshape()로 충분히 대체가 가능하므로 이 부분은 생략하겠습니다.

배열 복사, 병합 및 분리

배열 복사는 copy() 메소드를 사용합니다. 이 메소드는 NumPy 뿐만 아니라 리스트, 튜플, 딕셔너리에도 동일하게 사용되는 메소드입니다.

import numpy as np

a = np.arange(12)	# [0 1 2 3 4 5 6 7 8 9 10 11]
d = a.copy()

a = a.reshape(3,4)
print(a)		 # [[0 1 2 3] [4 5 6 7] [8 9 10 11]]
print(d)		 # [0 1 2 3 4 5 6 7 8 9 10 11]

배열 병합은 두 개 이상의 배열을 합치는 기능으로, 같은 크기를 가진 배열을 합칠 수 있습니다. 다만 2차원 이상 배열의 경우 어떤 형태로 합치는가에 따라 구성이 달라질 수 있습니다.

합치는 메소드는 vstack(), hstack() 두 가지를 사용하며, 예제는 다음과 같습니다.

import numpy as np

a = np.floor(10*np.random.random((2,2)))	# [[7,6],[2,3]]	(2x2)
b = np.floor(10*np.random.random((2,2)))	# [[7,3],[2,1]]	(2x2)

print(np.vstack((a,b))) 			        # [[7 6][2 3][7 3][2 1]] (4x2)
print(np.hstack((a,b)))			            # [[7 6 7 3][2 3 2 1]] (2x4)

그 외에도 column_stack() 메소드가 있으며, 1차원 배열을 2차원 배열로 통합하는 데 사용합니다.

배열 분리는 병합과 반대의 개념으로 보면 되며, hsplit(), vsplit() 메소드를 사용합니다. 하지만 배열 분리는 병합처럼 간단하지 않으며, 어떤 형태로 분리할 것인지에 따라 분리 형태가 달라집니다.

import numpy as np

a = np.floor(10*np.random.random((2,12)))

print(a)
np.hsplit(a,3)
np.hsplit(a,(3,4)) 	

출력 결과는 다음과 같습니다.

array([[ 9., 5., 6., 3., 6., 8., 0., 7., 9., 7., 2., 7.],
       [ 1., 4., 9., 2., 2., 1., 0., 6., 2., 2., 4., 0.]])

[array([[ 9., 5., 6., 3.], [ 1., 4., 9., 2.]]),
array([[ 6., 8., 0., 7.], [ 2., 1., 0., 6.]]),
array([[ 9., 7., 2., 7.], [ 2., 2., 4., 0.]])]

[array([[ 9., 5., 6.], [ 1., 4., 9.]]),
 array([[ 3.], [ 2.]]),
 array([[ 6., 8., 0., 7., 9., 7., 2., 7.],
        [ 2., 1., 0., 6., 2., 2., 4., 0.]])]

hsplit(a,3)을 하게 되면 3개의 배열로 균등 분배를 하게 되는 반면,

hsplit(a,(3,4))를 하게 되면, 인자가 2개 이상이 오므로 균등 분배가 아니라 해당 숫자에 대한 인덱스를 기준으로 분리하게 됩니다.

위 예제를 보면, (2x12) 형태의 배열이 있을 때 (3,4)로 분리하게 되면,

  • 첫 인덱스(0)부터 3번 인덱스 전까지 분리한 다음,
  • 3번 인덱스부터 4번 인덱스 전까지 분리하고,
  • 나머지 인덱스를 분리하는 형태로 진행됩니다.

사실 배열 분리를 NumPy에서 많이 사용하지는 않습니다만, 기본 개념인 만큼 언급하고 넘어가기 위함이니 참고하시기 바랍니다.

이상 NumPy 배열 형태에 대한 글을 마치도록 하며, NumPy 기본 개념은 여기까지가 될 것 같습니다.

NumPy Tutorial에서는 Numpy Array Indexing 개념도 같이 포함하고 있지만, NumPy에서 주로 사용하는 기능보다는 상대적으로 고급 기능이고 덜 사용하므로 이 블로그에서는 지금 다루지 않고 추후에 다루도록 할 에정이며, NumPy와 관련해서는 기본적인 배열이 위와 같이 구성되어 있고 이를 바탕으로 다른 라이브러리 및 모듈을 사용하여 개발할 경우 참조하여 많이 사용하므로 더불어 참고하는 정도로 보시면 될 것 같습니다.