다운로드
작성자: admin 작성일시: 2016-09-22 14:03:58 조회수: 31396 다운로드: 443
카테고리: Python 태그목록:

NumPy 배열

많은 숫자 데이터를 하나의 변수에 넣고 관리 할 때 리스트는 속도가 느리고 메모리를 많이 차지하는 단점이 있다. 배열(array)을 사용하면 적은 메모리로 데이터를 빠르게 처리할 수 있다. 배열은 리스트와 비슷하지만 다음과 같은 점에서 다르다.

  1. 모든 원소가 같은 자료형이어야 한다.
  2. 원소의 갯수를 바꿀 수 없다.

파이썬은 자체적으로 배열 자료형을 제공하지 않는다. 따라서 배열을 구현한 다른 패키지를 임포트해야 한다. 파이썬에서 배열을 사용하기 위한 표준 패키지는 NumPy(보통 "넘파이"라고 발음한다)이다.

NumPy는 수치해석용 파이썬 패키지이다. 다차원의 배열 자료구조 클래스인 ndarray 클래스를 지원하며 벡터와 행렬을 사용하는 선형대수 계산에 주로 사용된다. 내부적으로는 BLAS 라이브러리와 LAPACK 라이브러리를 사용하고 있으며 C로 구현된 CPython에서만 사용할 수 있다.

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

NumPy 패키지 임포트

배열을 사용하기 위해서는 우선 다음과 같이 NumPy 패키지를 임포트한다. NumPy는 np라는 이름으로 임포트하는 것이 관례이다.

In [1]:
import numpy as np

1차원 배열 만들기

NumPy의 array라는 함수에 리스트를 넣으면 배열로 변환해 준다. 따라서 1 차원 배열을 만드는 방법은 다음과 같다.

In [2]:
ar = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
ar
Out:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

리스트와 비슷해 보이지만 type 명령으로 자료형을 살펴보면 ndarray임을 알 수 있다.

In [3]:
type(ar)
Out:
numpy.ndarray

만들어진 ndarray 객체의 표현식(representation)을 보면 바깥쪽에 array()란 것이 붙어 있을 뿐 리스트와 동일한 구조처럼 보인다. 그러나 배열 객체와 리스트 객체는 많은 차이가 있다.

우선 리스트 클래스 객체는 각각의 원소가 다른 자료형이 될 수 있다. 그러나 배열 객체 객체는 C언어의 배열처럼 연속적인 메모리 배치를 가지기 때문에 모든 원소가 같은 자료형이어야 한다. 이러한 제약사항이 있는 대신 원소에 대한 접근과 반복문 실행이 빨라진다.

벡터화 연산

배열 객체는 배열의 각 원소에 대한 반복 연산을 하나의 명령어로 처리하는 벡터화 연산(vectorized operation)을 지원한다. 예를 들어 다음처럼 여러개의 데이터를 모두 2배 해야 하는 경우를 생각하자.

In [4]:
data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

for 반복문을 사용하면 다음과 같이 구현할 수 있다.

In [5]:
answer = []
for di in data:
    answer.append(2 * di)
answer
Out:
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

하지만 벡터화 연산을 사용하면 다음과 같이 for 반복문이 없이 한번의 연산으로 할 수 있다. 계산 속도도 반복문을 사용할 때 보다 훨씬 빠르다.

In [6]:
x = np.array(data)
x
Out:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [7]:
2 * x
Out:
array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])

참고로 일반적인 리스트 객체에 정수를 곱하면 객체의 크기가 정수배 만큼으로 증가한다.

In [8]:
L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(2 * L)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

벡터화 연산은 비교 연산과 논리 연산을 포함한 모든 종류의 수학 연산에 대해 적용된다. 선형 대수에 적용되는 벡터화 연산에 대해서는 나중에 보다 자세히 설명한다.

In [9]:
a = np.array([1, 2, 3])
b = np.array([10, 20, 30])
In [10]:
2 * a + b
Out:
array([12, 24, 36])
In [11]:
a == 2
Out:
array([False,  True, False])
In [12]:
b > 10
Out:
array([False,  True,  True])
In [13]:
(a == 2) & (b > 10)
Out:
array([False,  True, False])

2차원 배열 만들기

ndarray 는 N-dimensional Array의 약자이다. 이름 그대로 1차원 배열 이외에도 2차원 배열, 3차원 배열 등의 다차원 배열 자료 구조를 지원한다. 2차원 배열은 행렬(matrix)이라고 하는데 행렬에서는 가로줄을 (row)이라고 하고 세로줄을 (column)이라고 부른다.

다음과 같이 리스트의 리스트(list of list)를 이용하면 2차원 배열을 생성할 수 있다. 안쪽 리스트의 길이는 행렬의 열의 수 즉, 가로 크기가 되고 바깥쪽 리스트의 길이는 행렬의 행의 수, 즉 세로 크기가 된다. 예를 들어 2 x 3 배열은 다음과 같이 만든다.

In [14]:
c = np.array([[0, 1, 2], [3, 4, 5]])  # 2 x 3 array
c
Out:
array([[0, 1, 2],
       [3, 4, 5]])

2차원 배열의 행과 열의 갯수는 다음처럼 구한다.

In [15]:
# 행의 갯수
len(c)
Out:
2
In [16]:
# 열의 갯수
len(c[0])
Out:
3

연습 문제 4.1.1

NumPy를 사용하여 다음과 같은 행렬을 만든다.

10 20 30 40
50 60 70 80

3차원 배열 만들기

리스트의 리스트의 리스트를 이용하면 3차원 배열도 생성할 수 있다. 크기를 나타낼 때는 가장 바깥쪽 리스트의 길이부터 가장 안쪽 리스트 길이의 순서로 표시한다. 예를 들어 2 x 3 x 4 배열은 다음과 같이 만든다.

In [17]:
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

3차원 배열의 행, 열, 깊이는 다음과 같이 구할 수 있다.

In [18]:
len(d), len(d[0]), len(d[0][0])
Out:
(2, 3, 4)

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

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

In [19]:
# a = np.array([1, 2, 3])
print(a.ndim)
print(a.shape)
1
(3,)
In [20]:
# c = np.array([[0, 1, 2], [3, 4, 5]])
print(c.ndim)
print(c.shape)
2
(2, 3)
In [21]:
print(d.ndim)
print(d.shape)
3
(2, 3, 4)

배열의 인덱싱

일차원 배열의 인덱싱은 리스트의 인덱싱과 같다.

In [22]:
a = np.array([0, 1, 2, 3, 4])
In [23]:
a[2]
Out:
2
In [24]:
a[-1]
Out:
4

다차원 배열일 때는 다음과 같이 콤마(comma ,)를 사용하여 접근할 수 있다. 콤마로 구분된 차원을 축(axis)이라고도 한다. 그래프의 x축과 y축을 떠올리면 될 것이다.

In [25]:
a = np.array([[0, 1, 2], [3, 4, 5]])
a
Out:
array([[0, 1, 2],
       [3, 4, 5]])
In [26]:
a[0, 0]  # 첫번째 행의 첫번째 열
Out:
0
In [27]:
a[0, 1]  # 첫번째 행의 두번째 열
Out:
1
In [28]:
a[-1, -1]  # 마지막 행의 마지막 열
Out:
5

배열 슬라이싱

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

In [29]:
a = np.array([[0, 1, 2, 3], [4, 5, 6, 7]])
a
Out:
array([[0, 1, 2, 3],
       [4, 5, 6, 7]])
In [30]:
a[0, :]  # 첫번째 행 전체
Out:
array([0, 1, 2, 3])
In [31]:
a[:, 1]  # 두번째 열 전체
Out:
array([1, 5])
In [32]:
a[1, 1:]  # 두번째 행의 두번째 열부터 끝열까지
Out:
array([5, 6, 7])
In [33]:
a[:2, :2]
Out:
array([[0, 1],
       [4, 5]])

연습 문제 4.1.2

다음 행렬과 같은 행렬이 있다.

m = np.array([[ 0,  1,  2,  3,  4],
              [ 5,  6,  7,  8,  9],
              [10, 11, 12, 13, 14]])
  1. 이 행렬에서 값 7 을 인덱싱한다.
  2. 이 행렬에서 값 14 을 인덱싱한다.
  3. 이 행렬에서 배열 [6, 7] 을 슬라이싱한다.
  4. 이 행렬에서 배열 [7, 12] 을 슬라이싱한다.
  5. 이 행렬에서 배열 [[3, 4], [8, 9]] 을 슬라이싱한다.

배열 인덱싱

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

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

예를 들어 다음과 같은 1차원 ndarray에서 짝수인 원소만 골라내려면 짝수인 원소에 대응하는 인덱스 값이 True이고 홀수인 원소에 대응하는 인덱스 값이 False인 인덱스 배열을 넣으면 된다.

In [34]:
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])
a[idx]
Out:
array([0, 2, 4, 6, 8])

조건문 연산을 사용하면 다음과 같이 간단히 쓸 수 있다.

In [35]:
a % 2
Out:
array([0, 1, 0, 1, 0, 1, 0, 1, 0, 1])
In [36]:
a % 2 == 0
Out:
array([ True, False,  True, False,  True, False,  True, False,  True,
       False])
In [37]:
a[a % 2 == 0]
Out:
array([0, 2, 4, 6, 8])

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

In [38]:
a = np.array([11, 22, 33, 44, 55, 66, 77, 88, 99])
idx = np.array([0, 2, 4, 6, 8])
a[idx]
Out:
array([11, 33, 55, 77, 99])

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

In [39]:
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])
a[idx]
Out:
array([11, 11, 11, 11, 11, 11, 22, 22, 22, 22, 22, 33, 33, 33, 33, 33])

배열 인덱싱은 다차원 배열의 각 차원에 대해서도 할 수 있다.

In [40]:
a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
a
Out:
array([[ 1,  2,  3,  4],
       [ 5,  6,  7,  8],
       [ 9, 10, 11, 12]])
In [41]:
a[:, [True, False, False, True]]
Out:
array([[ 1,  4],
       [ 5,  8],
       [ 9, 12]])
In [42]:
a[[2, 0, 1], :]
Out:
array([[ 9, 10, 11, 12],
       [ 1,  2,  3,  4],
       [ 5,  6,  7,  8]])

연습 문제 4.1.3

다음 행렬과 같은 배열이 있다.

x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
              11, 12, 13, 14, 15, 16, 17, 18, 19, 20])
  1. 이 배열에서 3의 배수를 찾아라.
  2. 이 배열에서 4로 나누면 1이 남는 수를 찾아라.
  3. 이 배열에서 3으로 나누면 나누어지고 4로 나누면 1이 남는 수를 찾아라.

질문/덧글

차이점이 무엇일까요? tch2*** 2017년 2월 14일 3:47 오전

a = np.linspace(0, 9, 10) 라고 했을때,
1. a[ [a % 2 == 0] ]
2. a[ a % 2 == 0 ]

위 두 값이 동일하게 출력이 됩니다.

2번은 1번에 대한 줄임표현인가요?
아니면 2번과 1번은 서로 다른 연산인건가요?

답변: 차이점이 무엇일까요? 관리자 2017년 2월 17일 5:10 오후

값은 같지만 연산은 다릅니다.
원래는 ``a[ a % 2 == 0 ]`` 이 올바른 코드입니다.
다만 인덱스 값으로 같은 결과를 내는 다차원 배열 또는 리스트의 리스트가 들어간 것 뿐입니다.

다차원 배열 안에 다차원 배열 넣기 shin*** 2017년 9월 26일 4:59 오후

matrix = array([[0 for col in range(10)] for row in range(10)])

이런 식으로 생성한 10 by 10 행렬에

p_q = array([[1,0,0,0,0,0],
[0,1,0,0,0,0],
[0,0,1,0,0,0],
[1,0,0,-1,0,-2],
[0,1,0,0,-1,-2]])

이런 식으로 생성한 p_q 6 by 6 행렬을

matrix 행렬에 matrix[:6,:6] 이 범위에 넣고 싶으면 어떻게 해야 하나요?

답변: 다차원 배열 안에 다차원 배열 넣기 관리자 2017년 10월 2일 10:51 오전

```
matrix[:5,:6] = p_q
```

3차원 배열의 깊이, 행, 열 anso*** 2018년 9월 20일 10:32 오후

3차원 배열의 행, 열, 깊이는 다음과 같이 구할 수 있다.
len(d), len(d[0]), len(d[0][0])
(2, 3, 4)
라고 되어 있는데 깊이, 행, 열 순 아닌가요?

답변: 3차원 배열의 깊이, 행, 열 관리자 2018년 9월 22일 3:07 오후

3차원 배열을 입체에 비유하는 과정의 용어 사용의 문제입니다.