자료 안내: 여기서 다루는 내용은 아래 사이트의 내용을 일부 참고하여 생성되었음.

http://www.scipy-lectures.org/

넘파이 어레이: 인덱싱과 슬라이싱 1

주요 내용

리스트의 경우처럼 인덱싱과 슬라이싱을 이용하여 어레이 항목들을 확인, 수정할 수 있으며, 새로운 어레이를 생성할 수 있다. 여기서는 어레이의 인덱싱과 슬라이싱의 다양한 활용을 배운다.

1차원 어레이의 인덱싱과 슬라이싱

1차원 어레이의 경우 리스트의 인덱싱, 슬라이싱과 동일하게 처리한다. 다만, 슬라이싱의 경우 리턴값 또한 1차원 어레이이다.


In [1]:
import numpy as np

인덱싱

인덱싱은 숫자 0부터 인덱스가 시작하며, -1은 마지막 항목을 의미한다.


In [2]:
a = np.arange(10)
a


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

In [3]:
a[0], a[2], a[-1]


Out[3]:
(0, 2, 9)

슬라이싱

두 개의 콜론(':')을 사용하여 슬라이싱 구간의 처음과 끝, 그리고 스텝 옵션을 지정할 수 있다. -1은 역순으로 나열하는 것을 의미한다.


In [4]:
a[::-1]


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

In [5]:
a[2:9:3] # [시작:끝:계단]


Out[5]:
array([2, 5, 8])

In [6]:
a[:4]


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

In [7]:
a[1:3]


Out[7]:
array([1, 2])

In [8]:
a[::2]


Out[8]:
array([0, 2, 4, 6, 8])

In [9]:
a[3:]


Out[9]:
array([3, 4, 5, 6, 7, 8, 9])

다차원 어레이의 인덱싱과 슬라이싱

다차원 어레이에서 사용되는 인덱싱과 슬라이싱은 해당 어레이의 차원에 맞추어 작동한다. 즉, 어레이의 차원과 상응하는 튜플 형식으로 인덱싱과 슬라이싱 정보를 사용해야 한다.

리스트의 인덱싱 되돌아보기

먼저 중첩 리스트의 경우 인덱싱을 어떻게 하는지 확인해 보자.


In [10]:
b = [[0, 1, 2], [3, 4, 5]]

기본적으로 b는 길이가 2인 리스트이다.


In [11]:
len(b)


Out[11]:
2

b에서 0을 뽑아내고자 할 경우 인덱싱을 두 번 연속해서 사용해야 하는데, 이는 0b의 첫째 항목의 첫째 항목이기 때문이다. 즉,


In [12]:
b[0][0]


Out[12]:
0

5의 경우 둘째 항목의 셋째 항목이다.


In [13]:
b[1][2]


Out[13]:
5

다차원 어레이 인덱싱

반면에 b를 이차원 어레이로 다루어 보자.


In [14]:
b = np.array([[0, 1, 2], [3, 4, 5]])
b


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

2차원 어레이의 경우 인덱스를 행과 열의 개념을 이용하여 아래와 같은 방식으로 사용하면 된다.

  • 0을 추출하고자 할 때: 0행, 0열에 위치한 항목의 의미로 [0, 0]을 사용한다.

In [15]:
b[0, 0]


Out[15]:
0
  • 5의 경우는 1행, 2열에 위치한 항목의 의미로 [1, 2]를 사용한다.

In [16]:
b[1,2]


Out[16]:
5

주의: 인덱스는 0부터 시작한다.

보다 다양한 예제들을 이용하여 어레이 인덱싱을 연습해보자.

아래 코드에서 정의된 d는 2차원 어레이이며, 대각선에 0, 1, 2를 담고 있다.


In [17]:
d = np.diag(np.arange(3))
d


Out[17]:
array([[0, 0, 0],
       [0, 1, 0],
       [0, 0, 2]])

1의 위치는 1행, 1열이므로 [1, 1]을 이용한다.


In [18]:
d[1, 1]


Out[18]:
1

어레이 수정

어레이는 가변자료형이다. 따라서 리스트의 경우와 마찬가지로 인덱싱을 활용하여 항목을 수정할 수 있다.

예제

어레이 d의 2행, 1열의 항목값인 0을 10으로 변경하고자 하면 다음과 같이 인덱싱을 이용하면 된다.


In [19]:
d[2, 1] = 10
d


Out[19]:
array([[ 0,  0,  0],
       [ 0,  1,  0],
       [ 0, 10,  2]])

주의:

그렇다면 d[2]는 무엇을 의미할까? d를 리스트라 생각하면 된다. 즉, d의 2번 항목인 [0, 10, 2] 어레이를 가리킨다.


In [20]:
d[2]


Out[20]:
array([ 0, 10,  2])

다차원 어레이 슬라이싱

다차원 어레이의 슬라이싱은 좀 더 생소하다. 복잡하거나 어렵지는 않지만 작동방식에 좀 익숙해져야 한다.

다차원 어레이 생성

먼저, 다음과 같은 모양의 2차원 어레이를 생성해보자. 단, 수동으로 항목들을 입력하면 안된다.

0 1 2 3 4 5
10 11 12 13 14 15
20 21 22 23 24 25
30 31 32 33 34 35
40 41 42 43 44 45
50 51 52 53 54 55
  • 여러 가지 방법이 있을 수 있으나 shape을 변경하는 방법을 활용하는 것이 가장 기초적이다. 먼저, np.arange 함수를 이용하여 길이가 36인 어레이를 생성한 다음에 shape 값을 변경하여 2차원 어레이를 생성한다.

In [21]:
a = np.arange(36)
a.shape = (6,6)
a


Out[21]:
array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11],
       [12, 13, 14, 15, 16, 17],
       [18, 19, 20, 21, 22, 23],
       [24, 25, 26, 27, 28, 29],
       [30, 31, 32, 33, 34, 35]])

In [22]:
a = np.arange(36).reshape((6,6))
a


Out[22]:
array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11],
       [12, 13, 14, 15, 16, 17],
       [18, 19, 20, 21, 22, 23],
       [24, 25, 26, 27, 28, 29],
       [30, 31, 32, 33, 34, 35]])

이제 각 행별로 4의 배수를 더하면 원하는 어레이가 얻어지는 성질을 이용한다. 어레이와 숫자의 연산은 각 항목별로 실행된다는 점을 이용한다.


In [23]:
a = np.arange(36).reshape((6,6))

for i in range(len(a)):
        for j in range(6):
            a[i, j] = a[i, j] + 4*i
            
a


Out[23]:
array([[ 0,  1,  2,  3,  4,  5],
       [10, 11, 12, 13, 14, 15],
       [20, 21, 22, 23, 24, 25],
       [30, 31, 32, 33, 34, 35],
       [40, 41, 42, 43, 44, 45],
       [50, 51, 52, 53, 54, 55]])
  • 어레이의 연산은 항목별로 이루어진다는 점에 착안한다면 아래와 같이 코드를 작성할 수 있다.

    주의: a[i]a 어레이의 i번 행을 가리킨다. 예를 들어,

      a[2] = array([12, 13, 14, 15, 16, 17])
    
    

    이다. 따라서 a[2] + 4*2a[2]의 각각의 항목에 8을 더해주는 식으로 계산된다.

      a[2] + 4*2 = array([20, 21, 22, 23, 24, 25])

In [24]:
a = np.arange(36).reshape((6,6))

for i in range(len(a)):
    a[i] = a[i] + 4*i

a


Out[24]:
array([[ 0,  1,  2,  3,  4,  5],
       [10, 11, 12, 13, 14, 15],
       [20, 21, 22, 23, 24, 25],
       [30, 31, 32, 33, 34, 35],
       [40, 41, 42, 43, 44, 45],
       [50, 51, 52, 53, 54, 55]])
  • 브로드캐스팅이란 고급 기술을 이용하면 훨씬 간단하게 원하는 어레이를 구할 수 있다. 브로드캐스팅은 다음 시간에 자세히 다룬다.

In [25]:
a = np.arange(6) + np.arange(0, 51, 10)[:, np.newaxis]
a


Out[25]:
array([[ 0,  1,  2,  3,  4,  5],
       [10, 11, 12, 13, 14, 15],
       [20, 21, 22, 23, 24, 25],
       [30, 31, 32, 33, 34, 35],
       [40, 41, 42, 43, 44, 45],
       [50, 51, 52, 53, 54, 55]])

연습

이제 위 어레이를 이용하여 슬라이싱을 연습하도록 해보자. 좀 생소하게 다가올 것이지만, 행과 열의 규칙을 각각 적용하면 된다는 점에 주의하면 그렇게 복잡하지는 않을 것이다.

먼저 아래 그림에 나와 있는대로 슬라이싱을 따라해보면서 슬라이싱이 어떻게 작동하는지 확인해보자.

코드의 색깔과 어레이 그림에서 표시된 동일한 색을 가진 상자들 사이의 관계를 주시하면 된다.

주황색 영역

  • 규칙:
    • 행은 0행으로 고정, 즉 인덱스 [0] 사용.
    • 열은 3열에서 5열 전까지, 즉 슬라이싱 [3:5] 사용.

In [26]:
a[0, 3:5]


Out[26]:
array([3, 4])

하늘색 영역

  • 규칙:
    • 행은 4행에서 마지막 행까지, 즉 슬라이싱 [4:] 사용.
    • 열도 4열에서 마지막 열열까지, 즉 슬라이싱 [4:] 사용.

In [27]:
a[4:, 4:]


Out[27]:
array([[44, 45],
       [54, 55]])

빨강색 영역

  • 규칙:
    • 행은 전체 대상, 즉 슬라이싱 [:] 사용.
    • 열은 2열 고정, 즉 인덱싱 [2] 사용.

In [28]:
a[:, 2]


Out[28]:
array([ 2, 12, 22, 32, 42, 52])

초록색 영역

  • 규칙:
    • 행은 2행과 4행 대상, 즉 슬라이싱 [2::2] 사용.
    • 열은 0열, 2열, 4열 대상, 즉 인덱싱 [::2] 사용.

In [29]:
a[2::2, ::2]


Out[29]:
array([[20, 22, 24],
       [40, 42, 44]])

마스크(mask) 인덱싱

True, False의 불리언 값들로만 이루어진 어레이를 마스크(mask)라 부른다. 마스크를 이용하여 인덱싱을 실행할 수 있다.

주의: 마스크 인덱싱은 뷰 방식을 따르지 않는다.

예제: 무작위로 생성된 정수들 중에서 3의 배수만 추출하기

무작위로 선택된 정수들의 어레이를 생성하기 위해서는 np.random.randint 함수를 이용한다.

주의: 무작위성을 높이기 위해 앞서 np.random.rand의 경우처럼 seed 값을 이용하자.


In [30]:
np.random.seed(5)

이제 0과 20 사이에서 무작위로 15개의 정수를 3 x 5 행렬의 모양으로 생성하자.


In [31]:
a = np.random.randint(0, 20, size=(3, 5))
a


Out[31]:
array([[ 3, 14, 15,  6, 16],
       [ 9,  8,  4,  7, 16],
       [16,  7, 12, 15, 17]])

a의 항목들이 3의 배수 여부를 알려주는 어레이는 다음처럼 생성할 수 있다.


In [32]:
mask = (a % 3 == 0)
mask


Out[32]:
array([[ True, False,  True,  True, False],
       [ True, False, False, False, False],
       [False, False,  True,  True, False]], dtype=bool)

주의: 어레이의 연산은 항목별로 실행된다.


In [33]:
a % 3


Out[33]:
array([[0, 2, 0, 0, 1],
       [0, 2, 1, 1, 1],
       [1, 1, 0, 0, 2]])

또한 두 어레이의 모양이 다르면 브로드캐스팅 기능을 이용하여 두 어레이의 모양을 가능하면 통일시킨다. 물론, 모양의 통일이 불가능하면 오류가 발생한다. 브로드캐스팅에 대해서는 다음 시간에 자세히 배운다.

아래 코드에서 a % 33 x 5 모양의 2차원 어레이 인 반면에 숫자 0은 어레이가 전혀 아니다. 하지만 숫자 0으로 이루어진 3 x 5 모양의 어레이로 모양을 만들면 두 어레이가 동일한 모양을 갖게 되고, 따라서 항목별로 비교가 가능해진다.

사실은 a % 3 도 이와 같은 방식으로 이해할 수 있다. 즉, 먼저 3으로 이루어진 3 x 5 모양의 어레이로 만든 다음에 두 어레이 간의 나머지 연산을 실행한다.


In [34]:
a % 3 == 0


Out[34]:
array([[ True, False,  True,  True, False],
       [ True, False, False, False, False],
       [False, False,  True,  True, False]], dtype=bool)

이제 a의 항목들 중에서 mask의 항목들 중 True가 위치한 곳과 동일한 곳에 위치한 항목들만 뽑아서 1차원 어레이를 만들어 준다. 즉, a의 항목들 중에서 3의 배수인 숫자들만 뽑아서 어레이로 만들어서 리턴한다.


In [35]:
multiple_3 = a[mask]
multiple_3


Out[35]:
array([ 3, 15,  6,  9, 12, 15])

다음처럼 보다 간단한 구현이 가능하다.


In [36]:
a[a % 3 == 0]


Out[36]:
array([ 3, 15,  6,  9, 12, 15])

마스크 인덱싱 활용

마스크 인덱싱을 이용하여 특정 항목들의 값을 동시에 변경할 수 있다. 예를 들어, 생성된 정수들의 어레이어서 3배수값들만을 -1로 변경하고 할 때 마스크 인덱싱 기능을 활용할 수 있다.


In [37]:
a[a % 3 == 0] = -1
a


Out[37]:
array([[-1, 14, -1, -1, 16],
       [-1,  8,  4,  7, 16],
       [16,  7, -1, -1, 17]])

연습문제

연습

슬라이싱을 이용하여 아래 기능을 수행하는 함수 odd_numberseven_numbers를 구현하라.

  • odd_numbers 함수는 양의 정수 n을 입력 받아 0부터 n까지의 정수 중에서 홀수를 역순으로 담은 1차원 어레이를 리턴한다.

      In [31]: odd_numbers(10)
      Out[31]: array([9, 7, 5, 3, 1])
  • even_numbers 함수는 양의 정수 n을 입력 받아 0부터 n까지의 정수 중에서 짝수를 담은 1차원 어레이를 리턴한다.

      In [32]: even_numbers(10)
      Out[32]: array([0, 2, 4, 6, 8, 10])

힌트: arange(), 함수와 슬라이싱을 적절히 활용한다.


In [38]:
def odd_numbers(n):
    odds = np.arange(1, n, 2)
    return odds[::-1]

odd_numbers(10)


Out[38]:
array([9, 7, 5, 3, 1])

In [39]:
def even_numbers(n):
    evens = np.arange(0, n+1, 2)
    return evens

even_numbers(10)


Out[39]:
array([ 0,  2,  4,  6,  8, 10])