
Numpy에 관해
많은 숫자 데이터를 하나의 변수에 넣고 관리 할 때 리스트는 속도가 느리고 메모리를 많이 차지하는 단점이 있다. 배열(array)을 사용하면 적은 메모리로 많은 데이터를 빠르게 처리할 수 있다. 배열은 리스트와 비슷하지만 다음과 같은 점에서 다르다.
-
모든 원소가 같은 자료형이어야 한다.
-
원소의 갯수를 바꿀 수 없다.
파이썬은 자체적으로 배열 자료형을 제공하지 않는다. 따라서 배열을 구현한 다른 패키지를 임포트해야 한다. 파이썬에서 배열을 사용하기 위한 표준 패키지는 넘파이(NumPy)다.
넘파이는 수치해석용 파이썬 패키지이다. 다차원의 배열 자료구조 클래스인 ndarray 클래스를 지원하며 벡터와 행렬을 사용하는 선형대수 계산에 주로 사용된다. 내부적으로는 BLAS 라이브러리와 LAPACK 라이브러리를 사용하고 있으며 C로 구현된 CPython에서만 사용할 수 있다.
넘파이의 배열 연산은 C로 구현된 내부 반복문을 사용하기 때문에 파이썬 반복문에 비해 속도가 빠르며 벡터화 연산(vectorized operation)을 이용하여 간단한 코드로도 복잡한 선형 대수 연산을 수행할 수 있다. 또한 배열 인덱싱(array indexing)을 사용한 질의(Query) 기능을 이용하여 간단한 코드로도 복잡한 수식을 계산할 수 있다.
넘파이 패키지 임포트
1 | |
1차원 배열 만들기
1 | |
1 |
|
리스트와 비슷해 보이지만 type 명령으로 자료형을 살펴보면 ndarray임을 알 수 있다.
1 | |
1 | |
만들어진 ndarray 객체의 표현식(representation)을 보면 바깥쪽에 array()란 것이 붙어 있을 뿐 리스트와 동일한 구조처럼 보인다. 그러나 배열 객체와 리스트 객체는 많은 차이가 있다.
우선 리스트 클래스 객체는 각각의 원소가 다른 자료형이 될 수 있다. 그러나 배열 객체 객체는 C언어의 배열처럼 연속적인 메모리 배치를 가지기 때문에 모든 원소가 같은 자료형이어야 한다. 이러한 제약사항이 있는 대신 원소에 대한 접근과 반복문 실행이 빨라진다.
벡터화 연산
벡터화 연산(vectorized operation)
배열 객체는 배열의 각 원소에 대한 반복 연산을 하나의 명령어로 처리하는 벡터화 연산(vectorized operation)을 지원한다. 예를 들어 다음처럼 여러개의 데이터를 모두 2배 해야 하는 경우를 생각하자.
1 | |
하지만 벡터화 연산을 사용하면 다음과 같이 for 반복문이 없이 한번의 연산으로 할 수 있다. 계산 속도도 반복문을 사용할 때 보다 훨씬 빠르다.
1 | |
참고로 일반적인 리스트 객체에 정수를 곱하면 객체의 크기가 정수배 만큼으로 증가한다.
1 | |
1 | |
2차원 배열 만들기
ndarray 는 N-dimensional Array의 약자이다. 이름 그대로 1차원 배열 이외에도 2차원 배열, 3차원 배열 등의 다차원 배열 자료 구조를 지원한다. 2차원 배열은 행렬(matrix)이라고 하는데 행렬에서는 가로줄을 행(row)이라고 하고 세로줄을 열(column)이라고 부른다.
다음과 같이 리스트의 리스트(list of list)를 이용하면 2차원 배열을 생성할 수 있다. 안쪽 리스트의 길이는 행렬의 열의 수 즉, 가로 크기가 되고 바깥쪽 리스트의 길이는 행렬의 행의 수, 즉 세로 크기가 된다. 예를 들어 2개의 행과 3개의 열을 가지는 2 x 3 배열은 다음과 같이 만든다.
1 | |
3차원 배열 만들기
1 | |
배열의 차원과 크기 알아내기
배열의 차원 및 크기를 구하는 더 간단한 방법은 배열의 ndim 속성과 shape 속성을 이용하는 것이다. ndim 속성은 배열의 차원, shape 속성은 배열의 크기를 반환한다.
1 | |
1 | |
1 | |
배열의 인덱싱
일차원 배열의 인덱싱은 리스트의 인덱싱과 같다.
1 | |
다차원 배열일 때는 다음과 같이 콤마(comma ,)를 사용하여 접근할 수 있다. 콤마로 구분된 차원을 축(axis)이라고도 한다. 그래프의 x축과 y축을 떠올리면 될 것이다.
a = np.array([[0, 1, 2], [3, 4, 5]])
1 | |
a[0, 0] # 첫번째 행의 첫번째 열
1 | |
a[0, 1] # 첫번째 행의 두번째 열
1 | |
a[-1, -1] # 마지막 행의 마지막 열
1 | |
배열 슬라이싱
배열 객체로 구현한 다차원 배열의 원소 중 복수 개를 접근하려면 일반적인 파이썬 슬라이싱(slicing)과 comma(,)를 함께 사용하면 된다.
a = np.array([[0, 1, 2, 3], [4, 5, 6, 7]]) a
1 | |
a[0, :] # 첫번째 행 전체
1 | |
a[:, 1] # 두번째 열 전체
1 | |
a[1, 1:] # 두번째 행의 두번째 열부터 끝열까지
1 | |
a[:2, :2]
1 | |
배열 인덱싱
넘파이 배열 객체의 또다른 강력한 기능은 팬시 인덱싱(fancy indexing)이라고도 부르는 배열 인덱싱(array indexing) 방법이다. 인덱싱이라는 이름이 붙었지만 사실은 데이터베이스의 질의(Query) 기능을 수행한다. 배열 인덱싱에서는 대괄호(Bracket, [])안의 인덱스 정보로 숫자나 슬라이스가 아니라 위치 정보를 나타내는 또 다른 ndarray 배열을 받을 수 있다. 여기에서는 이 배열을 편의상 인덱스 배열이라고 부르겠다. 배열 인덱싱의 방식에은 불리언(Boolean) 배열 방식과 정수 배열 방식 두가지가 있다.
먼저 불리안 배열 인덱싱 방식은 인덱스 배열의 원소가 True, False 두 값으로만 구성되며 인덱스 배열의 크기가 원래 ndarray 객체의 크기와 같아야 한다.
예를 들어 다음과 같은 1차원 ndarray에서 짝수인 원소만 골라내려면 짝수인 원소에 대응하는 인덱스 값이 True이고 홀수인 원소에 대응하는 인덱스 값이 False인 인덱스 배열을 넣으면 된다.
1 | |
1 | |
1 | |
넘파이의 자료형
dtype 인수로 지정할 자료형은 다음 표에 보인것과 같은 “dtype 접두사”로 시작하는 문자열이고 이 글자 뒤에 오는 숫자는 바이트 수 혹은 글자 수를 의미한다. 예를 들어 f8은 8바이트(64비트) 부동소수점 실수를 뜻하고 U4 는 4글자 유니코드 문자열을 뜻한다. 숫자를 생략하면 운영체제에 따라 알맞은 크기를 지정한다.
| dtype 접두사 | 설명 | 사용 예 |
|---|---|---|
| b | 불리언 | b (참 혹은 거짓) |
| i | 정수 | i8 (64비트) |
| u | 부호 없는 정수 | u8 (64비트) |
| f | 부동소수점 | f8 (64비트) |
| c | 복소 부동소수점 | c16 (128비트) |
| O | 객체 | 0 (객체에 대한 포인터) |
| S | 바이트 문자열 | S24 (24 글자) |
| U | 유니코드 문자열 | U24 (24 유니코드 글자) |
1 | |
Inf와 NaN
넘파이에서는 무한대를 표현하기 위한 np.inf(infinity)와 정의할 수 없는 숫자를 나타내는 np.nan(not a number)을 사용할 수 있다. 다음 예와 같이 1을 0으로 나누려고 하거나 0에 대한 로그 값을 계산하면 무한대인 np.inf이 나온다. 0을 0으로 나누려고 시도하면 np.nan이 나온다.
np.array([0, 1, -1, 0]) / np.array([1, 0, 0, 0])
1 | |
np.log(0)
1 | |
np.exp(-np.inf)
1 | |
배열 생성
NumPy는 몇가지 단순한 배열을 생성하는 명령을 제공한다.
- zeros, ones
- zeros_like, ones_like
- empty
- arange
- linspace, logspace
a = np.zeros(5) a
1array([0., 0., 0., 0., 0.])b = np.zeros((2, 3)) b
1
2array([[0., 0., 0.], [0., 0., 0.]])문자열 배열도 가능하지면 모든 원소의 문자열 크기가 같아야 한다. 만약 더 큰 크기의 문자열을 할당하면 잘릴 수 있다. d = np.zeros(5, dtype=”U4”) d
1array(['', '', '', '', ''], dtype='<U4')
[0] = “abc” d[1] = “abcd” d[2] = “ABCDE” d
1array(['abc', 'abcd', 'ABCD', '', ''], dtype='<U4')
0이 아닌 1로 초기화된 배열을 생성하려면 ones 명령을 사용한다.
e = np.ones((2, 3, 4), dtype=”i8”) e
1 | |
만약 크기를 튜플로 명시하지 않고 다른 배열과 같은 크기의 배열을 생성하고 싶다면 ones_like, zeros_like 명령을 사용한다.
f = np.ones_like(b, dtype=”f”) f
1
2array([[1., 1., 1.], [1., 1., 1.]], dtype=float32)
배열의 크기가 커지면 배열을 초기화하는데도 시간이 걸린다. 이 시간을 단축하려면 배열을 생성만 하고 특정한 값으로 초기화를 하지 않는 empty 명령을 사용할 수 있다. empty 명령으로 생성된 배열에는 기존에 메모리에 저장되어 있던 값이 있으므로 배열의 원소의 값을 미리 알 수 없다.
g = np.empty((4, 3)) g
1 | |
arange 명령은 NumPy 버전의 range 명령이라고 볼 수 있다. 특정한 규칙에 따라 증가하는 수열을 만든다.
np.arange(10) # 0 .. n-1
1 |
|
np.arange(3, 21, 2) # 시작, 끝(포함하지 않음), 단계
1 | |
linspace 명령이나 logspace 명령은 선형 구간 혹은 로그 구간을 지정한 구간의 수만큼 분할한다.
np.linspace(0, 100, 5) # 시작, 끝(포함), 갯수
1 | |
np.logspace(0.1, 1, 10)
1 | |
전치 연산
2차원 배열의 전치(transpose) 연산은 행과 열을 바꾸는 작업이다. 이는 배열의 T 속성으로 구할 수 있다. 메서드가 아닌 속성이라는 점에 유의 한다.
A = np.array([[1, 2, 3], [4, 5, 6]]) A
1 | |
A.T
1 |
|
배열의 크기 변형
일단 만들어진 배열의 내부 데이터는 보존한 채로 형태만 바꾸려면 reshape 명령이나 메서드를 사용한다. 예를 들어 12개의 원소를 가진 1차원 행렬은 3x4 형태의 2차원 행렬로 만들 수 있다.
a = np.arange(12) a
1 |
|
b = a.reshape(3, 4) b
1 |
|
사용하는 원소의 갯수가 정해저 있기 때문에 reshape 명령의 형태 튜플의 원소 중 하나는 -1이라는 숫자로 대체할 수 있다. -1을 넣으면 해당 숫자는 다를 값에서 계산되어 사용된다.
a.reshape(3, -1)
1
2
3
1
2
3array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]])
a.reshape(2, 2, -1) ``` array([[[ 0, 1, 2], [ 3, 4, 5]],
1 | |
다차원 배열을 무조건 1차원으로 만들기 위해서는 flatten 나 ravel 메서드를 사용한다.
a.flatten()
1 |
|
a.ravel()
1 |
|
배열 사용에서 주의할 점은 길이가 5인 1차원 배열과 행, 열의 갯수가 (5,1)인 2차원 배열 또는 행, 열의 갯수가 (1, 5)인 2차원 배열은 데이터가 같아도 엄연히 다른 객체라는 점이다.
x = np.arange(5) x
1 | |
x.reshape(1, 5)
1 | |
x.reshape(5, 1)
1
2
3
4
5array([[0], [1], [2], [3], [4]])
배열 연결
행의 수나 열의 수가 같은 두 개 이상의 배열을 연결하여(concatenate) 더 큰 배열을 만들 때는 다음과 같은 명령을 사용한다.
hstack, vstack, dstack, stack, r_, c_, tile
1 | |
hstack : 2개 이상의 배열을 옆으로 연결
1 | |
1 | |
vstack : 2개 이상의 배열을 위아래로 연결
1 | |
1 | |
dstack : 깊이(depth) 방향으로 배열 연결
1 | |
1 | |
c.shape # (3, 2, 4)
stack : dstack의 기능을 확장한 것으로 dstack처럼 마지막 차원으로 연결하는 것이 아니라 사용자가 지정한 차원(축으로) 배열을 연결
1 | |
1 | |
c.shape #(2, 3, 4)\
1 | |
1 | |
r_ : hstack 명령과 비슷하게 배열을 좌우로 연결한다
np.r_[np.array([1, 2, 3]), np.array([4, 5, 6])]
1 | |
c_ : 배열의 차원을 증가시킨 후 좌우로 연결한다. 만약 1차원 배열을 연결하면 2차원 배열이 된다.
np.c_[np.array([1, 2, 3]), np.array([4, 5, 6])]
1 |
|
tile : 동일한 배열을 반복하여 연결한다.
a = np.array([[0, 1, 2], [3, 4, 5]]) np.tile(a, 2)
1 | |
np.tile(a, (3, 2))
1 | |
출처 : 데이터사이언스 스쿨