넘파이 어레이: 어레이 모양 변경

주요 내용

어레이의 모양(shape)을 임의로 변경시키는 방법을 다룬다.

  • reshape 함수 활용
  • 어레이 자료형의 shape 속성 활용

In [1]:
import numpy as np

reshape 함수

먼저 1차원 어레이를 생성해보자.


In [2]:
a_1D_array = np.arange(1,7)

이제 아래 모양의 어레이를 생성해보자.

$$\left [ \begin{matrix} 1 & 2 \\ 3 & 4 \\ 5 & 6 \end{matrix} \right ]$$

아래와 같이 직접 작성할 수 있다.


In [3]:
a_2D_array_1 = np.array([[1,2], [3, 4], [5,6]])
a_2D_array_1


Out[3]:
array([[1, 2],
       [3, 4],
       [5, 6]])

하지만 shape 속성을 변경하는 방식으로 a_1D_array로부터 구할 수도 있다.


In [4]:
a_2D_array = np.reshape(a_1D_array, (3,2))
a_2D_array


Out[4]:
array([[1, 2],
       [3, 4],
       [5, 6]])

주의: 기존의 a_1D_array는 변경되지 않았다.


In [5]:
a_1D_array


Out[5]:
array([1, 2, 3, 4, 5, 6])

앞서 다룬 reshape() 함수는 넘파이 모듈의 함수이다. 하지만, 동일한 이름의 어레이 메소드가 존재한다.


In [6]:
a_2D_array_2 = a_1D_array.reshape(2,3)
a_2D_array_2


Out[6]:
array([[1, 2, 3],
       [4, 5, 6]])

주의: np.shape()shape() 메소드는 내부적으로는 동일한 함수이다.

shape 속성

넘파이의 어레이는 가변자료형이다. 즉, 어레이의 내용과 모양을 변경시킬 수 있다.

항목 변경

항목 변경은 인덱싱 기능을 이용한다. 인덱싱에 대한 보다 자세한 설명은 다음 장에서 다루며, 여기서는 리스트의 인덱싱 방식과 동일한 방식으로 설명한다.

예제

a_1D_array의 2번 인덱스 항목을 9로 변경하려면 아래와 같이 하면 된다.


In [7]:
a_1D_array[2] = 9
a_1D_array


Out[7]:
array([1, 2, 9, 4, 5, 6])

예제

a_2D_array의 2번 인덱스 항목의 1번 인덱스 항목을 8로 변경하려면 아래와 같이 하면 된다.


In [8]:
a_2D_array[2][1] = 8
a_2D_array


Out[8]:
array([[1, 2],
       [9, 4],
       [5, 8]])

뷰(View) 방식

reshape 함수를 이용하여 a_1D_array로부터 생성된 a_2D_array는 a_1D_array와 여전히 의존적인 관계를 유지한다. 이유는 a_1D_array의 특정 위치의 값이 변하면 a_2D_array에서 그 위치에 해당한 값도 동일하게 변한다. 이렇게 작동하는 방식을 뷰(View) 방식이라고 부른다.

뷰 방식을 이용하는 함수의 결과물은 완전히 새로운 값을 생성하는 것이 아니라 기존의 값을 지켜보면서 정해진 규칙에 따라 모양만을 바꾸어 값을 리턴하는 방식을 따른다. 따라서 기존의 데이터에 변경이 발생이 발생하면 뷰 방식의 결과도 그에 상응하여 다른 값을 갖게 된다.

주의: 뷰 방식은 역 방향으로도 영향을 미친다. 예를 들어, a_2D_array의 값을 변경하면 a_1D_array의 값도 변경된다.


In [9]:
a_2D_array[2][0] = -1
a_2D_array


Out[9]:
array([[ 1,  2],
       [ 9,  4],
       [-1,  8]])

In [10]:
a_1D_array


Out[10]:
array([ 1,  2,  9,  4, -1,  8])

뷰 방식으로 어레이의 모양을 변경하는 다양한 방식이 존재한다. 대표적으로 전치행렬을 생성하는 T 속성이 그렇다.


In [11]:
a_2D_array


Out[11]:
array([[ 1,  2],
       [ 9,  4],
       [-1,  8]])

In [12]:
a_transposed_2D = a_2D_array.T
a_transposed_2D


Out[12]:
array([[ 1,  9, -1],
       [ 2,  4,  8]])

In [13]:
a_2D_array


Out[13]:
array([[ 1,  2],
       [ 9,  4],
       [-1,  8]])

In [14]:
a_transposed_2D[0][1] = -2
a_transposed_2D


Out[14]:
array([[ 1, -2, -1],
       [ 2,  4,  8]])

In [15]:
a_2D_array


Out[15]:
array([[ 1,  2],
       [-2,  4],
       [-1,  8]])

뷰 방식을 이용할 때 예상치 못한 결과를 얻을 수도 있음을 보여주는 예제가 매우 많다. 연습문제를 참조하기 바란다.

모양(shape) 변경

어레이 자료형의 shape 속성을 이용하여 어레이의 모양을 변경시키는 방법을 배운다.

a_1D_array는 1차원 어레이이며 길이가 6이다.


In [16]:
a_1D_array.shape


Out[16]:
(6,)

shape 속성값을 다른 모양으로 바꿀 수 있다. 대신에, 새로 생성된 모양 역시 동일한 숫자의 항목을 포함해야 한다.


In [17]:
a_1D_array.shape = (3,2)
a_1D_array


Out[17]:
array([[ 1,  2],
       [-2,  4],
       [-1,  8]])

3 * 2 = 6 임에 주의하라. 그렇지 않으면 오류가 발생한다.


In [18]:
a_1D_array.shape = (4, 2)


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-18-bf6420d2e8b2> in <module>()
----> 1 a_1D_array.shape = (4, 2)

ValueError: cannot reshape array of size 6 into shape (4,2)

shape를 (4, 2)로 변경하면 8개의 값이 필요한데 기존에 6개의 값만 사용되었기에 오류가 발생하는 것이다.

원래 모양으로 되돌리기 위해서는 다시 shape 을 변경시켜주면 된다.


In [19]:
a_1D_array.shape = (6,)
a_1D_array


Out[19]:
array([ 1,  2, -2,  4, -1,  8])

flatten 메소드

어떠한 모양의 어레이도 1차원 어레이로 변경시켜주는 특별한 어레이 메소드가 flatten() 이다.


In [20]:
array_flattened = a_2D_array.flatten()

In [21]:
array_flattened


Out[21]:
array([ 1,  2, -2,  4, -1,  8])

주의: flatten() 메소드는 뷰 방식으로 작동하지 않는다.


In [22]:
array_flattened[0] = -3
array_flattened


Out[22]:
array([-3,  2, -2,  4, -1,  8])

In [23]:
a_2D_array


Out[23]:
array([[ 1,  2],
       [-2,  4],
       [-1,  8]])

연습문제

전치행렬이 뷰 방식에 따라 사용되기에 아래와 같은 함수를 호출할 때 조심해야 한다.

아래 코드의 경우 두 개의 행렬을 각 항목별로 더하는 방식을 취한다. 따라서 행렬 aa.T를 인자로 입력하면 대칭행렬이 리턴되는 것을 기대할 수 있다. 하지만 결과는 다르게 나온다. 이유는 a.T 가 뷰 방식을 따르고 중첩 for문이 진행되는 동안 항목 값들이 계속해서 변하기 때문이다.


In [24]:
def mat_add(a, b):
    for i in range(a.shape[0]):
        for j in range(a.shape[1]):
            a[i,j] += b[i,j]
    return a

In [25]:
c = np.arange(9).reshape((3,3))
c


Out[25]:
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])

어레이 c는 다음 행렬에 대응한다.

$$\left [ \begin{matrix} 0 & 1 & 2 \\ 3 & 4 & 5 \\ 6 & 7 & 8 \end{matrix} \right ]$$

따라서 c.T는 다음 행렬에 대응한다.

$$\left [ \begin{matrix} 0 & 3 & 6 \\ 1 & 4 & 7 \\ 2 & 5 & 8 \end{matrix} \right ]$$

이제 mat_add(c, c.T)는 아래의 대칭행렬이 될 것으로 기대한다.

$$\left [ \begin{matrix} 0 & 4 & 8 \\ 4 & 8 & 12 \\ 8 & 12 & 16 \end{matrix} \right ]$$

하지만 결과가 다르게 나온다.


In [26]:
mat_add(c, c.T)


Out[26]:
array([[ 0,  4,  8],
       [ 7,  8, 12],
       [14, 19, 16]])

이유가 무엇일까? 먼저 0번 행의 경우는 예상과 일치한다. 그런데 1번 행부터 값이 달라졌다. 이는, 예를 들어 (i, j) = (1, 0) 의 경우 c.T[1, 0]의 값이 c[0, 1]인데 0번 행을 계산하면서 값이 1에서 이미 4로 변경되었기 때문이다. 따라서

c[1,0] + c.T[1,0] = c[1,0] + c[0,1] = 3 + 4 = 7

이 되는 것이다.