지난 시간에는 넘파이 배열의 차원을 변환/추가/제거/교체하는 API들에 대해서 알아봤습니다.
이번에는 서로 다른 배열을 합치는 메서드에 대해서 알아보려고 합니다.
나눠져 있는 데이터를 합칠 때 주로 사용하는데 그 빈도가 높은 만큼 각 메서드 사용법을 기억해두면 좋을 것 같습니다.
2차원 배열 합치기
2차원 배열(행렬)을 합칠 때 np.vstack(), np.hstack(), np.dstack() 메서드를 사용할 수 있습니다.
np.vstack()
np.vstack(tup)
a = np.random.randint(0, 10, (1, 4))
b = np.random.randint(0, 10, (1, 4))
c = np.random.randint(0, 10, (1, 4))
print(a)
# [[3 2 8 8]]
print(b)
# [[8 6 0 1]]
print(c)
# [[3 8 7 3]]
arr_list = [a, b, c]
vstack = np.vstack(arr_list)
print(vstack.shape)
# (3, 4)
print(vstack)
# [[3 2 8 8]
# [8 6 0 1]
# [3 8 7 3]]
np.vstack() 메서드는 2차원 배열(행렬)을 세로 방향으로 합칠 때 사용합니다.
stack 대상 배열들을 리스트나 튜플에 담은 뒤 매개변수 tup의 값으로 전달합니다.
차원의 길이를 동일하게 전달하지 않으면 ValueError가 발생합니다.
a = np.random.randint(0, 10, (4, ))
b = np.random.randint(0, 10, (4, ))
print(a)
# [9 6 1 0]
print(b)
# [3 5 7 1]
vstack = np.vstack([a, b])
print(vstack.shape)
# (2, 4)
print(vstack)
# [[9 6 1 0]
# [3 5 7 1]]
np.vstack() 메서드를 사용해서 1차원 배열들을 합치면 2차원 배열을 얻게 됩니다.
잠시 뒤에 알아볼 np.hstack() 메서드와의 차이점을 확인할 수 있는 부분입니다.
a = np.random.randint(0, 10, (1, 3))
b = np.random.randint(0, 10, (1, 3))
print(a)
# [[0 5 9]]
print(b)
# [[4 7 8]]
vstack = np.vstack([a, b])
print(vstack.shape)
# (2, 3)
print(vstack)
# [[0 5 9]
# [4 7 8]]
마찬가지로 행벡터 형태의 2차원 배열을 합쳐도 문제없이 동작합니다.
a = np.random.randint(0, 10, (3, 1))
b = np.random.randint(0, 10, (3, 1))
print(a)
# [[0]
# [0]
# [7]]
print(b)
# [[5]
# [0]
# [3]]
vstack = np.vstack([a, b])
print(vstack.shape)
# (6, 1)
print(vstack)
# [[0]
# [0]
# [7]
# [5]
# [0]
# [3]]
열벡터 형태의 2차원 배열을 합치는 경우입니다.
예상했던 것처럼 세로로 합쳐진 결과를 확인할 수 있습니다.
a = np.random.randint(0, 10, (3, 4))
b = np.random.randint(0, 10, (4, ))
print(a)
# [[9 7 0 7]
# [4 9 8 7]
# [1 2 8 9]]
print(b)
# [8 8 8 1]
vstack = np.vstack([a, b])
print(vstack.shape)
# (4, 4)
print(vstack)
# [[9 7 0 7]
# [4 9 8 7]
# [1 2 8 9]
# [8 8 8 1]]
이번에는 차원이 다른 배열을 합치는 경우를 살펴보겠습니다.
위 코드에서는 shape이 (3, 4)인 2차원 배열과 (4, )인 1차원 배열을 합칩니다.
차원이 달라서 에러가 발생할 것 같지만 정상적으로 동작합니다.
이 또한 np.hstack() 메서드와의 차이점을 확인할 수 있는 부분입니다.
np.hstack()
np.hstack(tup)
np.hstack() 메서드는 2차원 배열(행렬)을 가로 방향으로 합칠 때 사용합니다.
np.vstack() 메서드와 마찬가지로 stack 대상 배열들을 리스트에 담고 매개변수 tup의 값으로 전달합니다.
차원의 길이 또한 일치하지 않으면 ValueError가 발생합니다.
a = np.random.randint(0, 10, (4, ))
b = np.random.randint(0, 10, (4, ))
print(a)
# [2 0 3 4]
print(b)
# [1 9 1 1]
hstack = np.hstack([a, b])
print(hstack.shape)
# (8,)
print(hstack)
# [2 0 3 4 1 9 1 1]
np.hstack() 메서드를 사용해서 서로 다른 1차원 배열을 합치는 경우입니다.
두 배열이 세로로 길게 이어질 것 같지만 결과는 오히려 가로로 길게 이어집니다.
np.vstack() 메서드와의 차이점을 확인할 수 있는 부분입니다.
a = np.random.randint(0, 10, (1, 3))
b = np.random.randint(0, 10, (1, 3))
print(a)
# [[3 2 8]]
print(b)
# [[6 2 4]]
hstack = np.hstack([a, b])
print(hstack.shape)
# (1, 6)
print(hstack)
# [[3 2 8 6 2 4]]
그렇다면 2차원 배열을 합치는 경우에는 예상했던 것처럼 배열을 합칠 수 있을까요?
정답은 아닙니다!
shape이 (1, 3)인 2차원 배열을 hstack 하면 가로로 이어집니다.
그 결과 (1, 6)인 2차원 배열을 얻게 됩니다.
어떻게 해야 서로 다른 두 배열을 세로로 길게 합칠 수 있는걸까요?
a = np.random.randint(0, 10, (3, 1))
b = np.random.randint(0, 10, (3, 1))
print(a)
# [[4]
# [3]
# [3]]
print(b)
# [[4]
# [8]
# [9]]
hstack = np.hstack([a, b])
print(hstack.shape)
# (3, 2)
print(hstack)
# [[4 4]
# [3 8]
# [3 9]]
정답은 열벡터 형태의 2차원 배열을 만들어야 한다는 것입니다.
수학과 달리 데이터 과학 분야에서는 행벡터를 중심으로 사고합니다.
따라서 1차원 배열은 행벡터로서 인식됩니다.
이를 세로로 길게 만들기 위해서는 2차원 열벡터 형태로 만들어야 합니다.
위 예제 코드에서는 서로 다른 두 배열 a, b 모두 shape이 (3, 1)인 2차원 배열입니다.
두 배열 모두 세로로 긴 형태입니다.
이 두 배열을 가로로 합치면 세로로 긴 형태를 유지하면서 이어지는 것을 확인할 수 있습니다.
a = np.random.randint(0, 10, (3, 4))
b = np.random.randint(0, 10, (3, ))
print(a)
# [[6 8 7 5]
# [3 2 4 3]
# [1 7 3 3]]
print(b)
# [6 8 0]
hstack = np.hstack([a, b.reshape(3, -1)])
print(hstack.shape)
# (3, 5)
print(hstack)
# [[6 8 7 5 6]
# [3 2 4 3 8]
# [1 7 3 3 0]]
1차원 배열과 2차원 배열을 가로로 합치는 경우를 살펴보겠습니다.
앞서 언급했듯이 1차원 배열은 행벡터로서 인식됩니다.
따라서 1차원 배열을 가로로 이어붙이기 위해서는 열벡터 형태의 2차원 배열로 형태를 변환해야 합니다.
이때 np.reshape() 메서드를 사용해서 배열의 형태를 바꿀 수 있습니다.
만약 형태를 변환하지 않고 메서드를 실행하면 길이가 일치하지 않기 때문에 ValueError가 발생합니다.
np.dstack()
np.dstack(tup)
R = np.random.randint(0, 10, (100, 200))
G = np.random.randint(0, 10, size=R.shape)
B = np.random.randint(0, 10, size=R.shape)
image = np.dstack([R, G, B])
print(image.shape)
# (100, 200, 3)
np.dstack() 메서드를 사용하면 새로운 차원을 생성하면서 배열을 합칠 수 있습니다.
위 예제 코드는 서로 다른 2차원 배열을 합쳐 3차원 배열을 리턴합니다.
a = np.random.randint(0, 10, (100, 200, 3))
b = np.random.randint(0, 10, size=a.shape)
c = np.random.randint(0, 10, size=a.shape)
d = np.dstack([a, b, c])
print(d.shape)
# (100, 200, 9)
e = np.concatenate([a, b, c], axis=-1)
print(e.shape)
# (100, 200, 9)
하지만 3차원 배열을 합치는 경우에는 차원을 추가하지 않습니다.
따라서 이 경우는 np.concatenate() 메서드를 사용해서 배열을 합쳐야 코드 가독성을 높일 수 있습니다.
고차원 배열 합치기
이전까지 2차원 배열(행렬)을 합치는 함수들에 대해 알아봤습니다.
지금부터는 3차원 이상의 배열을 합치는 보다 더 일반적인 메서드를 알아보도록 하겠습니다.
np.concatenate()
💡 np.concatenate((a1, a2, ...), axis)
np.concatenate() 메서드를 사용하면 특정 axis를 기준으로 배열을 합칠 수 있습니다.
a = np.random.randint(0, 10, (3, ))
b = np.random.randint(0, 10, (4, ))
c = np.random.randint(0, 10, (5, ))
print(a)
# [8 3 0]
print(b)
# [5 7 3 0]
print(c)
# [5 7 4 0 2]
concat1 = np.concatenate([a, b, c])
concat2 = np.concatenate([a, b, c], axis=0)
print(concat1)
# [8 3 0 5 7 3 0 5 7 4 0 2]
print(concat2)
# [8 3 0 5 7 3 0 5 7 4 0 2]
np.concatenate() 메서드를 사용해서 1차원 배열(벡터)을 합치면 가로로 이어붙입니다.
a = np.random.randint(0, 10, (1, 3))
b = np.random.randint(0, 10, (1, 3))
print(a)
# [[1 1 9]]
print(b)
# [[3 3 1]]
# np.vstack()과 동일
concat_0 = np.concatenate([a, b], axis=0)
# np.hstack()과 동일
concat_1 = np.concatenate([a, b], axis=1)
concat_2 = np.concatenate([a, b], axis=-1)
print(concat_0)
# [[1 1 9]
# [3 3 1]]
print(concat_1)
# [[1 1 9 3 3 1]]
print(concat_2)
# [[1 1 9 3 3 1]]
2차원 배열을 합치는 경우에는 다음과 같이 매개변수 axis 값을 전달할 수 있습니다.
- axis=0: 가로 방향(행 방향)으로 배열을 합칩니다.
- axis=1: 세로 방향(열 방향)으로 배열을 합칩니다.
- axis=-1: 마지막 차원을 기준으로 배열을 합칩니다.
np.vstack(), np.hstack() 메서드 대신 사용할 수 있다는 특징이 있습니다.
a = np.random.randint(0, 10, (3, 4, 5))
b = np.random.randint(0, 10, (10, 4, 5))
c = np.random.randint(0, 10, (3, 10, 5))
d = np.random.randint(0, 10, (3, 4, 10))
concat1 = np.concatenate([a, b], axis=0)
print(concat1.shape)
# (13, 4, 5)
concat2 = np.concatenate([a, c], axis=1)
print(concat2.shape)
# (3, 14, 5)
concat3 = np.concatenate([a, d], axis=2)
print(concat3.shape)
# (3, 4, 15)
3차원 배열을 합치는 경우에도 다음과 같이 매개변수 axis 값을 전달할 수 있습니다.
- axis=0: 채널을 기준으로 배열을 합칩니다.
- axis=1: 가로 방향(행 방향)으로 배열을 합칩니다.
- axis=2: 세로 방향(열 방향)으로 배열을 합칩니다.
- axis=-1: 마지막 차원을 기준으로 배열을 합칩니다. 세로 방향(열 방향)으로 배열을 합친다는 의미입니다.
axis 값으로 전달한 차원 외 차원들의 길이가 동일해야만 배열을 합칠 수 있습니다.
np.stack()
np.stack(array, axis)
a = np.random.randint(0, 10, (100, 200))
b = np.random.randint(0, 10, (100, 200))
c = np.random.randint(0, 10, (100, 200))
d = np.stack([a, b, c])
print(d.shape)
# (3, 100, 200)
np.dstack() 메서드와 마찬가지로 새로운 차원을 생성하면서 배열을 합칩니다.
하지만 새로운 차원이 추가되는 위치가 다르다는 차이점이 있습니다.
a = np.random.randint(0, 10, (100, 200, 300))
b = np.random.randint(0, 10, (100, 200, 300))
c = np.random.randint(0, 10, (100, 200, 300))
d = np.stack([a, b, c])
print(d.shape)
# (3, 100, 200, 300)
3차원 배열을 합칠 때도 새로운 차원을 생성합니다.
마지막으로 np.stack() 메서드를 사용해 3차원 배열을 합칠 때 서로 다른 axis 값을 전달하는 경우를 살펴보고 정리를 마치겠습니다.
a = np.random.randint(0, 10, (100, 200, 300))
b = np.random.randint(0, 10, (100, 200, 300))
c = np.random.randint(0, 10, (100, 200, 300))
d = np.stack([a, b, c], axis=0)
e = np.stack([a, b, c], axis=1)
f = np.stack([a, b, c], axis=2)
g = np.stack([a, b, c], axis=3)
h = np.stack([a, b, c], axis=-1)
print(d.shape)
# (3, 100, 200, 300)
print(e.shape)
# (100, 3, 200, 300)
print(f.shape)
# (100, 200, 3, 300)
print(g.shape)
# (100, 200, 300, 3)
print(h.shape)
# (100, 200, 300, 3)
정리를 마치며
이상으로 넘파이 배열을 합치는 메서드를 정리했습니다.
서로 다른 배열에 담긴 데이터를 합치는 경우 자주 사용하니까 잘 알아두면 좋을 것 같습니다.
'파이썬・ML > numpy' 카테고리의 다른 글
넘파이 배열 shape 속성에 대한 이해 (0) | 2023.07.27 |
---|---|
넘파이 np.repeat(), np.tile() 배열 반복하기 (0) | 2023.06.16 |
넘파이 배열의 차원 다루기 (0) | 2023.06.15 |
넘파이 벡터의 내적과 행렬의 곱셈 (0) | 2023.06.15 |
넘파이 정렬 메서드 알아보기 (0) | 2023.06.14 |