본문 바로가기
Python/넘파이 배열 프로그래밍

[01] 넘파이 배열

by 준보틱스 2024. 6. 25.

넘파이 배열

많은 숫자 데이터를 하나의 변수에 넣고 관리할 때 리스트는 속도가 느리고 메모리를 많이 차지하는 단점이 있다. 배열(array)을 사용하면 적은 메모리로 많은 데이터를 빠르게 처리할 수 있다. 배열은 리스트와 비슷하다. 하지만 모든 원소가 자료형이어야 하고, 원소의 갯수를 바꿀 수 없다.

파이썬은 자체적으로 배열 자료형을 제공하지 않는다. 따라서 배열을 구현한 다른 패키지를 import한다. 가장 대표적인 패키지는 넘파이(NumPy)이다.

넘파이는 수치해석용 파이썬 패키지이다. 다차원의 배열 자료구조 "ndarray" 클래스를 지원하며 벡터와 행렬을 사용하는 선형대수 계산에 주로 사용된다.

넘파이는 C로 구현된 CPython에서만 사용할 수 있다. 넘파이의 배열 연산은 C로 구현된 내부 반복문을 사용하기 때문에 파이썬 반복문에 비해 속도가 빠르며 벡터화 연산을 이용해 간단한 코드로도 복잡한 선형대수 연산을 수행할 수 있다. 또한 배열 인덱싱을 사용한 질의(Query) 기능을 이용하여 간단한 코드로도 복잡한 수식을 계산할 수 있다.

  • 배열(Array)

배열이란 인덱스와 값이 쌍으로 이루어진 연속적인 메모리 위치의 집합이라고 정의할 수 있다. 즉, 배열은 연관된 데이터를 모아서 관리하기 위해 사용되는 데이터 타입이다. 변수가 하나의 데이터를 저장하기 위한 것이라면 배열은 여러 개의 데이터를 저장하기 위한 것이라고 할 수 있다.

1차원 배열은 단순히 나열 즉 가로만 있었다면, 2차원 배열은 가로와 세로, 3차원 배열은 가로, 세로, 높이까지 가지고 있다.

Numpy package import

>>> import numpy as np

1차원 배열 만들기

numpy의 array 함수에 리스트를 넣으면 ndarray 클래스 객체(배열)로 변환해준다. 출력값을 보면 리스트와 비슷해보지만 ndarray이다.

>>> import numpy as np
>>> ar = np.array([0,1,2,3,4,5,6,7,8,9])
>>> print(ar)
>>> print(type(ar))
[0 1 2 3 4 5 6 7 8 9]
<class 'numpy.ndarray'>

 

리스트 객체는 각각의 원소가 다른 자료형이 될 수 있다. 그러나 배열 객체는 연속적인 메모리 배치를 가지기 때문에 모든 원소가 자료형이어야 한다.

벡터화 연산

배열 객체는 배열의 각 원소에 대한 반복 연산을 하나의 명령어로 처리하는 벡터화 연산을 지원한다.

예를 들어, 여러 개의 데이터를 모두 2배씩 해야 하는 경우를 생각해보자.

만약, 그냥 print(2*data)를 하게 된다면 출력은 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 로 출력된다. 그렇기에 숫자 값 자체를 2배 해주려면 for 문을 이용하거나 벡터화 연산을 이용해야한다.

>>> data = [0,1,2,3,4,5,6,7,8,9]
(1) for 문 이용
>>> data = [0,1,2,3,4,5,6,7,8,9]
>>> answer = []
>>> for di in data:
        answer.append(2*di)
>>> print(answer)
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

(2) 벡터화 연산 이용
>>> import numpy as np
>>> data = [0,1,2,3,4,5,6,7,8,9]
>>> x = np.array(data)
>>> print(2*x)
[ 0 2 4 6 8 10 12 14 16 18]

2차원 배열 만들기

ndarray는 N-dimensional Array의 약자이다. 이름대로 1차원, 2차원, 3차원 등 다차원 배열 자료 구조를 지원한다. 2차원 배열은 행렬(matrix)라고 하는데 행렬에서는 가로줄을 행(row), 세로줄을 열(column)이라고 부른다. 리스트의 리스트를 이용하면 2차원 배열을 생성할 수 있다. 안쪽 리스트의 길이는 행렬의 열의 수(가로 크기)가 되고 바깥쪽 리스트의 길이는 행렬의 행의 수(세로 크기)가 된다.

>>> import numpy as np

>>> a = np.array([[0,1,2],[3,4,5]])
>>> print(a)
[[0 1 2]
[3 4 5 ]]

# 행의 갯수
>>> print(len(a))
2
# 열의 갯수
>>> print(len(a[0]))
3

3차원 배열 만들기

리스트의 리스트의 리스트를 이용하면 3차원 배열도 생성할 수 있다. 크기를 나타낼 때는 가장 바깥쪽 리스트의 길이부터 가장 안쪽 리스트의 길이의 순서로 표시된다.

>>> import numpy as np

>>> d = np.array([[[1, 2, 3, 4],
                   [5, 6, 7, 8],
                   [9, 10, 11, 12]],
                  [[11, 12, 13, 14],
                   [15, 16, 17, 18],
                   [19, 20, 21, 22]]])   # 2 x 3 x 4 array

>>> print(d)
[[[ 1  2  3  4]
  [ 5  6  7  8]
  [ 9 10 11 12]]

 [[11 12 13 14]
  [15 16 17 18]
  [19 20 21 22]]]

# 3차원 배열의 깊이, 행, 열 구하는 방법
>>> len(d), len(d[0]), len(d[0][0])

배열의 차원과 크기 알아내기

배열의 차원 및 크기를 구하는 더 간단한 방법은 배열의 ndim, shpae 를 이용하는 것이다. ndim은 배열의 차원, shape는 배열의 크기를 반환한다.

>>> import numpy as np

>>> a = np.array([1,2,3])

>>> print(a.ndim)
>>> print(a.shape)
1
(3,)

배열의 인덱싱

  • 일차원 배열의 인덱싱

일차원 배열의 경우 리스트의 인덱싱과 동일하다.

>>> import numpy as np

>>> a = np.array([0,1,2,3,4,5])

>>> print(a[2])
>>> print(a[-1])
2
5
  • 다차원 배열의 인덱싱

다차원 배열일 경우 콤마(,)를 사용하여 접근할 수 있다. 콤마로 구분된 차원을 축(axis)라고도 한다.

>>> import numpy as np

>>> a = np.array([[0,1,2],[3,4,5]])

>>> print(a)
[[0,1,2]
 [3,4,5]]

# 첫번째 행의 첫번째 열
>>> print(a[0,0])
0
# 첫번째 행의 두번째 열
>>> print(a[0,1])
1

배열의 슬라이싱

배열로 구현한 다차원 배열의 원소 중 복수 개를 접근하려면 일반적인 파이썬 슬라이싱과 콤마(,)를 함께 사용하면 된다.

>>> import numpy as np

>>> a = np.array([0,1,2,3],[4,5,6,7])
>>> print(a)
[[0 1 2 3]
 [4 5 6 7]]

# 첫번째 행 전체
a[0, :]
[0 1 2 3]

# 두번째 열 전체
a[:, 1]
[1 5]

# 두번째 행의 두번째 열부터 끝열까지
a[1, 1:]
[5 6 7]

배열의 인덱싱(팬시 인덱싱)

넘파이 배열 객체의 또 다른 강력한 기능은 팬시 인덱싱(fancy indexing)이라고도 부르는 배열 인덱싱(array indexing) 방법이다. 이는 데이터베이스의 질의(Query) 기능을 수행한다. 배열 인덱싱에서는 대괄호([])안의 인덱스 정보로 숫자나 슬라이스가 아니라 위치 정보를 나타내는 또 다른 ndarray 배열을 받을 수 있다. 배열 인덱싱의 방식에는 불리언(Boolean) 배열 방식과 정수 배열 방식 두가지가 있다.

불리언 배열 인덱싱 방식은 인덱스 배열의 원소가 True, False 두 값으로만 구성되며 인덱스 배열의 크기가 원래 ndarray 객체의 크기와 같아야 한다.

1차원 ndarray에서 짝수인 원소만 골라내려면 짝수인 원소에 대응하는 인덱스 값이 True이고 홀수인 원소에 대응하는 인덱스 값이 False인 인덱스 배열을 넣는다.

>>> import numpy as np

>>> a = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> idx = np.array([True, False, True, False, True,
                   False, True, False, True, False])
>>> print(a[idx])
>>> print(a%2)
>>> print(a%2 == 0)
>>> print(a[a%2 == 0])
[0 2 4 6 8]
[0 1 0 1 0 1 0 1 0 1]
[ True False  True False  True False  True False  True False]
[0 2 4 6 8]

 

정수 배열 인덱싱에서는 인덱스 배열의 원소 각각이 원래 ndarray 객체 원소 하나를 가리키는 인덱스 정수이여야 한다. 예를 들어 1차원 배열에서 홀수번째 원소만 골라내는 것은 다음과 같다.

>>> import numpy as np
>>> a = np.array([11, 22, 33, 44, 55, 66, 77, 88, 99])
>>> idx = np.array([0, 2, 4, 6, 8])
>>> print(a[idx])
[11, 33, 55, 77, 99]

 

이 때는 배열 인덱스의 크기가 원래의 배열 크기와 달라도 상관없다. 같은 원소를 반복해서 가리키는 경우에는 배열 인덱스가 원래의 배열보다 더 커지기도 한다.

>>> import numpy as np
>>> a = np.array([11, 22, 33, 44, 55, 66, 77, 88, 99])
>>> idx = np.array([0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2])
>>> print(a[idx])
[11, 11, 11, 11, 11, 11, 22, 22, 22, 22, 22, 33, 33, 33, 33, 33]
  • 다차원 배열에서의 배열 인덱싱
>>> import numpy as np
>>> a = np.array([[1,2,3,4],[5,6,7,8],[9,10,11,12]])
>>> print(a)
[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]
>>> print(a[:,[True, False, False, True]])
[[ 1  4]
 [ 5  8]
 [ 9 12]]
>>> print(a[[2,0,1], :])
[[ 9 10 11 12]
 [ 1  2  3  4]
 [ 5  6  7  8]]