이전 포스팅에서 넘파이 배열의 shape 속성을 정리한 적이 있습니다.
이번에는 그 연속선 상에서 배열의 차원을 다루는 방법들에 대해 알아보도록 하겠습니다.
import numpy as np
형태(shape) 변환하기
np.reshape(a, newshape, order='C')
a = np.arange(9)
b = a.reshape((-1, 9))
c = a.reshape((9, -1))
print(a)
# [0 1 2 3 4 5 6 7 8]
print(b)
# [[0 1 2 3 4 5 6 7 8]]
print(c)
# [[0]
# [1]
# [2]
# [3]
# [4]
# [5]
# [6]
# [7]
# [8]]
np.reshape() 메서드를 사용하면 배열의 형태를 바꿀 수 있습니다. (배열의 차원을 추가하는 등)
잠시 이전에 정리한 내용을 짚고 넘어가겠습니다.
np.reshape()를 사용하기 위해서는 기존 배열과 차원 변환 후 배열의 원소의 개수가 동일해야 합니다.
이 조건을 만족하면 어떠한 배열이든 reshape이 가능합니다.
사실 np.reshape() 메서드는 차원을 유지한 상태에서 배열의 형태를 변환하는 것이 주목적이기 때문에 차원을 추가하는 것을 목적으로 사용한다면 가독성이 떨어지는 문제가 발생합니다.
따라서 웬만하면 차원 추가가 목적인 경우에는 잠시 후에 알아볼 expand_dims() 메서드를 사용하는 편이 좋습니다.
a = np.random.normal(size=(100, 200))
b = a.reshape((1, *a.shape))
c = a.reshape((*a.shape, 1))
print(a.shape)
# (100, 200)
print(b.shape)
# (1, 100, 200)
print(c.shape)
# (100, 200, 1)
위 예제 코드에서는 튜플을 언팩하기 위해서 asterisk를 사용했습니다.
asterisk(*)를 사용하면 배열의 형태를 변환할 때 코드의 유지보수성이 향상된다는 특징이 있습니다.
차원 추가하기
배열의 차원을 늘릴 때 np.reshape() 메서드를 사용하기보다는 슬라이싱 혹은 expand_dims() 메서드를 주로 사용합니다.
슬라이싱
💡 np.newaxis = None
arr = np.arange(9)
row_vec1 = arr[np.newaxis, :]
print(row_vec1.shape)
# (1, 9)
row_vec2 = arr[None, :]
print(row_vec2.shape)
# (1, 9)
col_vec1 = arr[:, np.newaxis]
print(col_vec1.shape)
# (9, 1)
col_vec2 = arr[:, None]
print(col_vec2.shape)
# (9, 1)
슬라이싱을 활용하면 배열의 차원을 추가할 수 있습니다.
np.newaxis나 None 대신에 -1을 입력해도 동일한 결과를 리턴합니다.
a = np.random.normal(size=(100, 200))
b = a[np.newaxis, ...]
c = a[..., np.newaxis]
print(a.shape)
# (100, 200)
print(b.shape)
# (1, 100, 200)
print(c.shape)
# (100, 200, 1)
위 예제 코드와 같이 배열의 양끝에 새로운 차원을 추가할 수도 있습니다.
np.expand_dims()
np.expand_dims(a, axis)
a = np.arange(9)
b = np.expand_dims(a, axis=0)
c = np.expand_dims(a, axis=1)
print(b.shape)
# (1, 9)
print(b)
# [[0 1 2 3 4 5 6 7 8]]
print(c.shape)
# (9, 1)
print(c)
# [[0]
# [1]
# [2]
# [3]
# [4]
# [5]
# [6]
# [7]
# [8]]
np.expand_dims() 메서드를 사용하면 배열의 차원을 추가할 수 있습니다.
배열의 차원을 추가할 때 슬라이싱과 더불어 자주 사용되는 메서드입니다.
하지만 np.expand_dims() 메서드를 사용하면 명시적으로 차원을 늘린다는 의미가 전달되기 때문에 코드의 가독성을 높이는 효과가 있습니다.
차원 제거하기
np.reshape(), np.flatten()
a = np.ones(shape=(1, 10))
print(a.shape)
# (1, 10)
print(a)
# [[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]]
b = a.reshape((10, ))
print(b.shape)
# (10,)
print(b)
# [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
c = a.reshape((-1, ))
print(c.shape)
# (10,)
print(c)
# [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
d = a.flatten()
print(d.shape)
# (10,)
print(d)
# [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
np.reshape, np.flatten() 메서드를 사용하면 기존 배열의 차원을 제거할 수 있습니다.
a = np.ones(shape=(1, 3, 4))
b = np.ones(shape=(3, 4, 1))
c = a.reshape(*a.shape[1:])
print(c.shape)
# (3, 4)
print(c)
# [[1. 1. 1. 1.]
# [1. 1. 1. 1.]
# [1. 1. 1. 1.]]
d = b.reshape(*b.shape[:-1])
print(d.shape)
# (3, 4)
print(d)
# [[1. 1. 1. 1.]
# [1. 1. 1. 1.]
# [1. 1. 1. 1.]]
asterisk(*)를 사용해서 튜플을 슬라이싱 한 뒤 언팩하는 식으로 배열의 차원을 제거할 수 있습니다.
슬라이싱
arr = np.arange(9).reshape(3, 3)
print(arr)
# [[0 1 2]
# [3 4 5]
# [6 7 8]]
row = arr[1, :]
print(row.shape)
# (3,)
print(row)
# [3 4 5]
col = arr[:, 1]
print(col.shape)
# (3,)
print(col)
# [1 4 7]
기존 배열을 슬라이싱 하면 차원이 하나 감소하는 원리를 이용해서 배열의 차원을 제거할 수 있습니다.
a = np.ones(shape=(1, 3, 4))
b = np.ones(shape=(3, 4, 1))
c = a[0, ...]
print(c.shape)
# (3, 4)
print(c)
# [[1. 1. 1. 1.]
# [1. 1. 1. 1.]
# [1. 1. 1. 1.]]
d = b[..., 0]
print(d.shape)
# (3, 4)
print(d)
# [[1. 1. 1. 1.]
# [1. 1. 1. 1.]
# [1. 1. 1. 1.]]
위 예제 코드와 같이 배열 인덱싱을 통해 배열 shape 양끝 차원을 제거할 수도 있습니다.
np.squeeze()
np.squeeze(a, axis=None)
a = np.ones(shape=(1, 3, 4))
print(a)
# [[[1. 1. 1. 1.]
# [1. 1. 1. 1.]
# [1. 1. 1. 1.]]]
b = np.squeeze(a)
print(b.shape)
# (3, 4)
print(b)
# [[[1. 1. 1. 1.]
# [1. 1. 1. 1.]
# [1. 1. 1. 1.]]]
c = a.squeeze()
print(c.shape)
# (3, 4)
print(c)
# [[[1. 1. 1. 1.]
# [1. 1. 1. 1.]
# [1. 1. 1. 1.]]]
np.squeeze() 메서드는 차원의 값이 1인 차원을 모두 제거합니다.
a = np.ones(shape=(1, 1, 4, 1, 3, 1))
b = np.squeeze(a)
print(b.shape)
# (4, 3)
print(b)
# [[1. 1. 1.]
# [1. 1. 1.]
# [1. 1. 1.]
# [1. 1. 1.]]
c = a.squeeze()
print(c.shape)
# (4, 3)
print(c)
# [[1. 1. 1.]
# [1. 1. 1.]
# [1. 1. 1.]
# [1. 1. 1.]]
매우 큰 차원의 경우에도 차원의 값이 1이라면 해당 차원은 제거됩니다.
차원 교체하기
np.swapaxes()
np.swapaxes(a, axis1, axis2)
a = np.random.normal(size=(3, 4, 5, 6))
b = np.swapaxes(a, 0, 1)
print(b.shape)
# (4, 3, 5, 6)
c = np.swapaxes(a, 0, 2)
print(c.shape)
# (5, 4, 3, 6)
d = np.swapaxes(a, 0, 3)
print(d.shape)
# (6, 4, 5, 3)
np.swapaxes() 메서드를 사용하면 매개변수 axis1과 axis2로 스왑할 차원을 지정할 수 있습니다.
두 개의 차원을 교체할 때 사용할 수 있습니다.
a = np.random.normal(size=(3, 200, 100))
b = np.swapaxes(a, 0, -1)
print(b.shape)
# (100, 200, 3)
주로 첫 번째 차원과 마지막 차원을 바꿀 때 사용합니다.
np.moveaxis()
np.moveaxis(a, source, destination)
a = np.random.normal(size=(3, 4, 5, 6))
b = np.moveaxis(a, 0, 1)
print(b.shape)
# (4, 3, 5, 6)
c = np.moveaxis(a, 0, 2)
print(c.shape)
# (4, 5, 3, 6)
d = np.moveaxis(a, 0, -1)
print(d.shape)
# (4, 5, 6, 3)
np.moveaxis() 메서드를 사용하면 특정 차원을 특정 위치로 이동시킬 수 있습니다.
그렇게 자주 사용하지는 않습니다.
np.transpose()
np.transpose(a, axes=None)
a = np.random.normal(size=(3, 4))
b = np.transpose(a)
print(b.shape)
# (4, 3)
c = a.T
print(c.shape)
# (4, 3)
np.transpose() 메서드를 사용하면 행과 열을 바꿉니다.
a = np.random.normal(size=(3, 4, 5, 6, 7))
b = np.transpose(a)
print(b.shape)
# (7, 6, 5, 4, 3)
c = a.T
print(c.shape)
# (7, 6, 5, 4, 3)
고차원 배열을 다룰 때 np.transpose() 메서드의 장점을 알 수 있습니다.
동일한 결과를 np.swapaxes(), np.moveaxis() 메서드를 사용해 구현하려면 복잡하기 때문입니다.
a = np.random.normal(size=(3, 4, 5))
b = np.transpose(a, axes=(0, 1, 2))
print(b.shape)
# (3, 4, 5)
c = np.transpose(a, axes=(1, 2, 0))
print(c.shape)
# (4, 5, 3)
d = np.transpose(a, axes=(2, 0, 1))
print(d.shape)
# (5, 3, 4)
e = np.transpose(a, axes=(2, 1, 0))
print(e.shape)
# (5, 4, 3)
매개변수 axes 값으로 인덱스 정보가 담긴 튜플을 전달하면 서로 다른 차원들끼리의 교체가 자유로워집니다.
'파이썬・ML > numpy' 카테고리의 다른 글
넘파이 np.repeat(), np.tile() 배열 반복하기 (0) | 2023.06.16 |
---|---|
넘파이 np.hstack(), np.vstack(), np.concatenate() 배열 합치기 (0) | 2023.06.16 |
넘파이 벡터의 내적과 행렬의 곱셈 (0) | 2023.06.15 |
넘파이 정렬 메서드 알아보기 (0) | 2023.06.14 |
넘파이 반올림 메서드 알아보기 (0) | 2023.06.14 |