IT/언어

[python] Numpy배열 ndarray를 결합시키는 방법(concatenate, stack, block 등)

개발자 두더지 2021. 11. 2. 23:28
728x90

 여러 개의 Numpy 배열 ndarray를 결합(연결)하기 위한 다양한 함수가 있다. 이번 포스팅에서는 아래의 내용에 대해서 설명하도록 하겠다.

- numpy.concatenate()의 기본 사용법

 - 결합할 배열 ndarray의 리스트를 지정

 - 결합할 축(차원)을 지정 : 인수 axis

- numpy.stack()으로 새로운 축(차원)에 따른 결합

- numpy.block()으로 배치를 지정하여 결합

- numpy.vstack()으로 세로로 결합

- numpy.hstack()으로 가로로 결합

- numpy.dstack()으로 깊이 방향으로 결합

 기존의 축(차원)에 따라 결합하는 것이 numpy.concatenate()로, 새로운 축에 따라 결합하는 것이 numpy.stack()이다. 예를 들어, 2차원 배열을 가로 세로로 결합하는 것이 numpy.concatenate()로, 2차원 배열을 거듭해 3차원 배열을 생성하는 것이 numpy.stack()이 된다.

 기본적으로는 numpy.concatenate()와 numpy.stack()으로 대응할 수 있지만, 특히 2차원 배열에 대해서 numpy.block()이나 numpy.vstack(), numpy.hstack()을 기억해두면 편리하다.

 그 외에 numpy.r_[]이나 numpy.c_[]이라는 방법도 있으나, 여기서는 다루지 않는다.

 

 

numpy.concatenate()의 기본 사용법


 여러 개의 배열 ndarray를 결합하는 기본적인 함수가 numpy.concatenate()이다. 기존의 축(차원)에 따라 결합한다.

결합할 배열 ndarray의 리스트를 지정

아래의 배열을 예로 사용한다.

import numpy as np

a1 = np.ones((2, 3), int)
print(a1)
# [[1 1 1]
#  [1 1 1]]

a2 = np.full((2, 3), 2)
print(a2)
# [[2 2 2]
#  [2 2 2]]

 첫 번째 인수에 결합하고 싶은 ndarray리스트를 지정한다. 튜플이어도 괜찮다.

print(np.concatenate([a1, a2]))
# [[1 1 1]
#  [1 1 1]
#  [2 2 2]
#  [2 2 2]]

 3개 이상이라도 동일하게 적용할 수 있다. 첫 번째 인수에 지정할 리스트나 튜플의 요소를 추가하면 된다.

a3 = np.full((2, 3), 3)
print(a3)
# [[3 3 3]
#  [3 3 3]]

print(np.concatenate([a1, a2, a3]))
# [[1 1 1]
#  [1 1 1]
#  [2 2 2]
#  [2 2 2]
#  [3 3 3]
#  [3 3 3]]

 

결합할 축(차원)을 지정 : 인수 axis

 두 번째 인수 axis에 결합할 축(차원)을 0시작으로 지정한다. 기본적으로 axis=0이다. 2차원 배열의 경우에는 새로운 축(0차원째)에 대해 결합이 이뤄지므로 세로로 결합한다. 

print(np.concatenate([a1, a2], 0))
# [[1 1 1]
#  [1 1 1]
#  [2 2 2]
#  [2 2 2]]

 axis = 1로 지정하면 2번째의 축(1차원째)이 열이므로 가로로 결합된다.

print(np.concatenate([a1, a2], 1))
# [[1 1 1 2 2 2]
#  [1 1 1 2 2 2]]

 존재하지 않는 축(차원)을 지정하면 에러가 발생한다. 새로운 축에 대해서 결합하고 싶은 경우, 예를 들어 2차원 배열을 겹쳐서 3차원 배열을 생성하고 싶은 경우 뒤에서 설명할 numpy.stack()등을 사용한다.

# print(np.concatenate([a1, a2], 2))
# AxisError: axis 2 is out of bounds for array of dimension 2

 결합 축 이외의 축의 사이즈가 일치하지 않고 있으면 에러가 된다. 부족한 부분이 NaN으로 채워지거나 하지 않는다.

a2_ = np.full((3, 3), 2)
print(a2_)
# [[2 2 2]
#  [2 2 2]
#  [2 2 2]]

print(np.concatenate([a1, a2_], 0))
# [[1 1 1]
#  [1 1 1]
#  [2 2 2]
#  [2 2 2]
#  [2 2 2]]

# print(np.concatenate([a1, a2_], 1))
# ValueError: all the input array dimensions except for the concatenation axis must match exactly

 2차원 이외의 배열에서도 동일하다. 두 번째 인수 axis에 축(차원)을 지정한다. 원래 차원에 존재하지 않는 축에는 결합되지 않는다.

a1 = np.ones(3, int)
print(a1)
# [1 1 1]

a2 = np.full(3, 2)
print(a2)
# [2 2 2]

print(np.concatenate([a1, a2], 0))
# [1 1 1 2 2 2]

# print(np.concatenate([a1, a2], 1))
# AxisError: axis 1 is out of bounds for array of dimension 1

 또한, 원래 배열의 차원의 수가 다른 경우는 에러가 된다. 후술할 numpy.block이나 numpy.vstack()을 사용하면, 1차원 배열과 2차원 배열의 세로 결합이 가능하다.

a1 = np.ones((2, 3), int)
print(a1)
# [[1 1 1]
#  [1 1 1]]

a2 = np.full(3, 2)
print(a2)
# [2 2 2]

# print(np.concatenate([a1, a2], 0))
# ValueError: all the input arrays must have same number of dimensions

 

 

numpy.stack()으로 새로운 축(차원)에 따른 결합


 기존의 축(차원)에 따라 결합하는 numpy.concatenate()에 대비하여 새로운 축에 의해 결합하는 함수는 numpy.stack()이다. 새로운 축에 따른 배열을 중첩하는 이미지이다.

 첫 번째 인수에 연결하는 배열 ndarray의 리스트, 두 번째에 인수 axis에 결합할 축(차원)을 0부터 시작하는 것으로 지정하는 것은 numpy.concatenate()와 동일하다. 그러나, axis는 결합된 배열을 위한 축을 지정한다.

1차원 배열의 예

1차원 배열의 경우 아래를 예로 살펴보자.

a1 = np.ones(3, int)
print(a1)
# [1 1 1]

print(a1.shape)
# (3,)

a2 = np.full(3, 2)
print(a2)
# [2 2 2]

print(a2.shape)
# (3,)

 numpy.stack()은 새로운 축으로 결합하므로, 결과의 배열은 1차원이 아닌 2차원배열이 된다. 두 번째 인수 axis의 기본은 axis=0이다. 결과 배열의 0차원째(행)에 따라 결합된다.

print(np.stack([a1, a2]))
# [[1 1 1]
#  [2 2 2]]

print(np.stack([a1, a2], 0))
# [[1 1 1]
#  [2 2 2]]

print(np.stack([a1, a2], 0).shape)
# (2, 3)

 axis=1이라면 아래와 같이 된다.

print(np.stack([a1, a2], 1))
# [[1 2]
#  [1 2]
#  [1 2]]

print(np.stack([a1, a2], 1).shape)
# (3, 2)

결과의 배열에 존재하지 않는 축을 지정하면 에러가 된다.

# print(np.stack([a1, a2], 2))
# AxisError: axis 2 is out of bounds for array of dimension 2

 axis = -1로하면 마지막 축이 짖어된다. 이 경우는 axis=1과 동일하다.

print(np.stack([a1, a2], -1))
# [[1 2]
#  [1 2]
#  [1 2]]

print(np.stack([a1, a2], -1).shape)
# (3, 2)

 차원수 ndim가 동일하도 형태shape가 다르면 에러가 난다.

a2_ = np.full(4, 2)
print(a2_)
# [2 2 2 2]

print(a2_.shape)
# (4,)

# print(np.stack([a1, a2]))
# ValueError: all input arrays must have the same shape

 

2차원 배열의 예

계속해서 2차원 배열을 예로 살펴보자.

a1 = np.ones((3, 4), int)
print(a1)
# [[1 1 1 1]
#  [1 1 1 1]
#  [1 1 1 1]]

print(a1.shape)
# (3, 4)

a2 = np.full((3, 4), 2)
print(a2)
# [[2 2 2 2]
#  [2 2 2 2]
#  [2 2 2 2]]

print(a2.shape)
# (3, 4)

 적용법은 1차원 배열과 동일하다. 결과는 2차원 + 1차원으로 3차원 배열이 된다. 결과의 형태 shape를 의식하면 이해하기 쉬울 것이다.

print(np.stack([a1, a2]))
# [[[1 1 1 1]
#   [1 1 1 1]
#   [1 1 1 1]]
# 
#  [[2 2 2 2]
#   [2 2 2 2]
#   [2 2 2 2]]]

print(np.stack([a1, a2], 0))
# [[[1 1 1 1]
#   [1 1 1 1]
#   [1 1 1 1]]
# 
#  [[2 2 2 2]
#   [2 2 2 2]
#   [2 2 2 2]]]

print(np.stack([a1, a2], 0).shape)
# (2, 3, 4)
print(np.stack([a1, a2], 1))
# [[[1 1 1 1]
#   [2 2 2 2]]
# 
#  [[1 1 1 1]
#   [2 2 2 2]]
# 
#  [[1 1 1 1]
#   [2 2 2 2]]]

print(np.stack([a1, a2], 1).shape)
# (3, 2, 4)

print(np.stack([a1, a2], 1)[:, 0, :])
# [[1 1 1 1]
#  [1 1 1 1]
#  [1 1 1 1]]

print(np.stack([a1, a2], 1)[:, 1, :])
# [[2 2 2 2]
#  [2 2 2 2]
#  [2 2 2 2]]
print(np.stack([a1, a2], 2))
# [[[1 2]
#   [1 2]
#   [1 2]
#   [1 2]]
# 
#  [[1 2]
#   [1 2]
#   [1 2]
#   [1 2]]
# 
#  [[1 2]
#   [1 2]
#   [1 2]
#   [1 2]]]

print(np.stack([a1, a2], 2).shape)
# (3, 4, 2)

print(np.stack([a1, a2], 2)[:, :, 0])
# [[1 1 1 1]
#  [1 1 1 1]
#  [1 1 1 1]]

print(np.stack([a1, a2], 2)[:, :, 1])
# [[2 2 2 2]
#  [2 2 2 2]
#  [2 2 2 2]]

 결과의 배열에 존재하지 않는 축을 지정하면 에러가 발생한다.

# print(np.stack([a1, a2], 3))
# AxisError: axis 3 is out of bounds for array of dimension 3

 axis=-1로 하면 마지막 축을 의미한다. 이 경우는 axis=2의 결과와 동일하게 된다.

print(np.stack([a1, a2], -1))
# [[[1 2]
#   [1 2]
#   [1 2]
#   [1 2]]
# 
#  [[1 2]
#   [1 2]
#   [1 2]
#   [1 2]]
# 
#  [[1 2]
#   [1 2]
#   [1 2]
#   [1 2]]]

print(np.stack([a1, a2], -1).shape)
# (3, 4, 2)

 차원 수 ndim이 동일해도 형태shape가 다르면 에러가 난다.

a2_ = np.full((2, 3), 2)
print(a2_)
# [[2 2 2]
#  [2 2 2]]

# print(np.stack([a1, a2_]))
# ValueError: all input arrays must have the same shape

 3차원 이상 혹은 그 이상의 다차원의 경우도 동일하다.

 

 

numpy.block()으로 배치를 지정하여 결합


 보다 직감적으로 여러 개의 배열을 결합하는 것이 numpy.block()이다. 원래 배열을 어떻게 배치할 것인가를 표시한 리스트를 인수에 지정한다. 배열을 가로로 나열한 리스트를 지정하면 그 순서대로 가로로 결합된다.

a1 = np.ones((2, 3), int)
print(a1)
# [[1 1 1]
#  [1 1 1]]

a2 = np.full((2, 3), 2)
print(a2)
# [[2 2 2]
#  [2 2 2]]

print(np.block([a1, a2]))
# [[1 1 1 2 2 2]
#  [1 1 1 2 2 2]]

 2차원 리스트로 가로 방향의 배열을 지정하면 그대로 결합된다.

print(np.block([[a1], [a2]]))
# [[1 1 1]
#  [1 1 1]
#  [2 2 2]
#  [2 2 2]]

print(np.block([[a1, a2], [a2, a1]]))
# [[1 1 1 2 2 2]
#  [1 1 1 2 2 2]
#  [2 2 2 1 1 1]
#  [2 2 2 1 1 1]]

 인수를 3차원으로 하면 결과도 3차원이 된다.

print(np.block([[[a1]], [[a2]]]))
# [[[1 1 1]
#   [1 1 1]]
# 
#  [[2 2 2]
#   [2 2 2]]]

print(np.block([[[a1]], [[a2]]]).shape)
# (2, 2, 3)

 아래와 같이 1차원 배열과 2차원 배열을 결합하는 것도 가능하다.

a3 = np.full(6, 3)
print(a3)
# [3 3 3 3 3 3]

print(np.block([[a1, a2], [a3]]))
# [[1 1 1 2 2 2]
#  [1 1 1 2 2 2]
#  [3 3 3 3 3 3]]

[] 로 차원을 정하지 않으면 에러가 발생하므로 주의하자.

# print(np.block([[a1, a2], a3]))
# ValueError: List depths are mismatched. First element was at depth 2, but there is an element at depth 1 (arrays[1])

 또한, 요소 수가 과하게 부족한 경우에도 에러가 난다.

# print(np.block([[a1, a2, a3]]))
# ValueError: all the input array dimensions except for the concatenation axis must match exactly

 

 

numpy.vstack()으로 세로로 결합


 numpy.vstack()은 배열을 가로(vertical)로 결합하는 함수이다. 기본적으로 axis=0으로 한 numpy.concatenate()와 동일하다.

a1 = np.ones((2, 3), int)
print(a1)
# [[1 1 1]
#  [1 1 1]]

a2 = np.full((2, 3), 2)
print(a2)
# [[2 2 2]
#  [2 2 2]]

print(np.vstack([a1, a2]))
# [[1 1 1]
#  [1 1 1]
#  [2 2 2]
#  [2 2 2]]

print(np.concatenate([a1, a2], 0))
# [[1 1 1]
#  [1 1 1]
#  [2 2 2]
#  [2 2 2]]

 1차원 배열에 대해서는 각 배열을 2차원으로 확장한 후에 axis=0의 numpy.concatenate()가 호출되므로, 결과는 2차원 배열이 된다. 동작으로써는 axis=0으로 한 numpy.stack()과 동일하다.

a1 = np.ones(3, int)
print(a1)
# [1 1 1]

a2 = np.full(3, 2)
print(a2)
# [2 2 2]

print(np.vstack([a1, a2]))
# [[1 1 1]
#  [2 2 2]]

print(np.stack([a1, a2], 0))
# [[1 1 1]
#  [2 2 2]]

 1차원 배열이 2차원 배열으로 확장되므로, 2차원 배열과 1차원 배열도 결합할 수 있다.

a1 = np.ones((2, 3), int)
print(a1)
# [[1 1 1]
#  [1 1 1]]

a2 = np.full(3, 2)
print(a2)
# [2 2 2]

print(np.vstack([a1, a2]))
# [[1 1 1]
#  [1 1 1]
#  [2 2 2]]

 

 

numpy.hstack()으로 가로로 결합


 numpy.hstack()은 배열을 가로(horizontal)으로 결합하는 함수이다. 기본적으로는 axis = 1로 한 numpy.concatenate()와 동일하다.

a1 = np.ones((2, 3), int)
print(a1)
# [[1 1 1]
#  [1 1 1]]

a2 = np.full((2, 3), 2)
print(a2)
# [[2 2 2]
#  [2 2 2]]

print(np.hstack([a1, a2]))
# [[1 1 1 2 2 2]
#  [1 1 1 2 2 2]]

print(np.concatenate([a1, a2], 1))
# [[1 1 1 2 2 2]
#  [1 1 1 2 2 2]]

 1차원 배열에 대해서는 axis=0의 numpy.concatenate()가 호출된다.

a1 = np.ones(3, int)
print(a1)
# [1 1 1]

a2 = np.full(3, 2)
print(a2)
# [2 2 2]

print(np.hstack([a1, a2]))
# [1 1 1 2 2 2]

print(np.concatenate([a1, a2], 0))
# [1 1 1 2 2 2]

 numpy.hstack()으로는 numpy.vstack()와 같이 1차원 배열이 2차원 배열로 확장되거나 하지 않기 때문에, 2차원 배열과 1차원 배열을 합칠 수 없다.

a1 = np.ones((2, 3), int)
print(a1)
# [[1 1 1]
#  [1 1 1]]

a2 = np.full(2, 2)
print(a2)
# [2 2]

# print(np.hstack([a1, a2]))
# ValueError: all the input arrays must have same number of dimensions

 

 

numpy.dstack()으로 깊이 방향으로 결합


 numpy.dstack()은 배열의 깊이(depth) 방향으로 결합하는 함수이다. 기본적으로는 axis=2로 한 numpy.concatenate()와 동일하다.

 2차원 이하의 배열은 3차원으로 확장된 후에 결합된다(M, N) -> (M, N, 1). 

a1 = np.ones((3, 4), int)
print(a1)
# [[1 1 1 1]
#  [1 1 1 1]
#  [1 1 1 1]]

a2 = np.full((3, 4), 2)
print(a2)
# [[2 2 2 2]
#  [2 2 2 2]
#  [2 2 2 2]]

print(np.dstack([a1, a2]))
# [[[1 2]
#   [1 2]
#   [1 2]
#   [1 2]]
# 
#  [[1 2]
#   [1 2]
#   [1 2]
#   [1 2]]
# 
#  [[1 2]
#   [1 2]
#   [1 2]
#   [1 2]]]

print(np.dstack([a1, a2]).shape)
# (3, 4, 2)

print(np.dstack([a1, a2])[:, :, 0])
# [[1 1 1 1]
#  [1 1 1 1]
#  [1 1 1 1]]

print(np.dstack([a1, a2])[:, :, 1])
# [[2 2 2 2]
#  [2 2 2 2]
#  [2 2 2 2]]

 아래의 numpy.concatenate()와 동일하다.

print(np.concatenate([a1.reshape(3, 4, 1), a2.reshape(3, 4, 1)], 2))
# [[[1 2]
#   [1 2]
#   [1 2]
#   [1 2]]
# 
#  [[1 2]
#   [1 2]
#   [1 2]
#   [1 2]]
# 
#  [[1 2]
#   [1 2]
#   [1 2]
#   [1 2]]]

 1차원 배열도 3차원으로 확장된다(N, ) -> (1, N, 1).

a1 = np.ones(3, int)
print(a1)
# [1 1 1]

a2 = np.full(3, 2)
print(a2)
# [2 2 2]

print(np.dstack([a1, a2]))
# [[[1 2]
#   [1 2]
#   [1 2]]]

print(np.dstack([a1, a2]).shape)
# (1, 3, 2)

print(np.dstack([a1, a2])[:, :, 0])
# [[1 1 1]]

print(np.dstack([a1, a2])[:, :, 1])
# [[2 2 2]]

참고자료

https://note.nkmk.me/python-numpy-concatenate-stack-block/

728x90