넘파이는 파이썬에서 선형대수 기반의 프로그램을 쉽게 만들 수 있도록 지원하는 대표적인 패키지다. C/C++ 과 같은 저수준 언어 기반의 호환 API를 제공한다. 수행 성능이 매우 중요한 부분은 C/C++ 기반의 코드로 작성하고 이를 넘파이에서 호출하는 방식으로 쉽게 통합할 수 있다. 구글의 대표적인 딥러닝 프레임워크인 텐서플로는 이런 방식으로 배열 연산 수행 속도를 개선하고 넘파이와도 호환될 수 있게 작성됐다.
일반적으로 데이터는 2차원 형태의 행과 열로 이뤄져있기 때문에 넘파이가 판다스의 편리성에 비해 떨어진다는 단점이 있다.
물론 머신러닝 알고리즘이나 패키지를 직접 만드는 개발자가 아니라면 넘파이를 상세하게 알 필요는 없다. 단, 입력 데이터와 출력 데이터를 넘파이 배열 타입으로 사용하기 때문에 이해하는 것은 매우 중요하다.
넘파이 ndarray 개요
# 넘파이 모듈 임포트
import numpy as np # 약어로 모듈을 표현해주는 것이 관례다.
'''
numpy 기반 데이터 타입은 ndarray다. 다차원 배열을 쉽게 생성하고 다양한 연산 수행가능하다.
'''
'''
array() 함수는 파이썬의 리스트와 같은 다양한 인자를 입력받아서 ndarray로 변환하는 기능을
수행한다. ndarray 배열의 shape 변수는 행과 열의 수를 튜플 형태로 가지고 있으며 따라서
배열의 차원까지 알 수 있다.
'''
array1 = np.array([1,2,3])
print('array1 type: ', type(array1)) # array1 type: <class 'numpy.ndarray'>
print('array1 array 형태: ', array1.shape) # array1 array 형태: (3,) 1차원, 3개의 데이터
array2 = np.array([[1,2,3],
[2,3,4]])
print('array2 type: ', type(array2))
print('array2 array 형태: ', array2.shape)
'''
array2 type: <class 'numpy.ndarray'>
array2 array 형태: (2, 3) 2차원, 2행 3열
'''
array3 = np.array([[1,2,3]])
print('array3 type: ', type(array3))
print('array3 array 형태: ', array3.shape)
'''
array3 type: <class 'numpy.ndarray'>
array3 array 형태: (1, 3) 2차원, 1행 3열
'''
# ndarray.shape은 ndarray의 차원과 크기를 튜플 형태로 알려준다.
Python
복사
# ndarray.ndim을 이용하면 array의 차원을 확인할 수 있다.
print('array1: {0}차원, array2: {1}차원, array3: {2}차원'.format(array1.ndim, array2.ndim,
array3.ndim))
'''
array1: 1차원, array2: 2차원, array3: 2차원
'''
'''
array() 함수의 인자로는 파이썬의 리스트 객체가 주로 사용된다. []는 1차원이고, [[]]는 2차원과
같은 형태로 배열의 차원과 크기를 쉽게 표현할 수 있다.'''
Python
복사
ndarray의 데이터 타입
ndarray내의 데이터 값은 숫자 값, 문자열 값, 불 값 모두 가능하다.
•
숫자형 : int, unsigned int , float, complex 타입까지
데이터 타입은 같은 데이터 타입만 가능하다. 즉 한개의 ndarray 객체에 int와 float가 함께 있을 수 없다. ndarray 내 데이터 타입은 dtype 속성으로 확인할 수 있다.
list1 = [1,2,3]
print(type(list1)) # <class 'list'>
array1 = np.array(list1)
print(type(array1)) # <class 'numpy.ndarray'>
print(array1, array1.dtype) # [1 2 3] int32
Python
복사
서로 다른 데이터 타입을 가질 수 있는 리스트와 다르게 ndarray 내의 데이터 타입은 같은 데이터 타입만 가능하다고 했다. 만약, 다른 데이터 유형이 섞여 있는 리스트를 ndarray형태로 변경하면 데이터 크기가 더 큰 데이터 타입으로 형 변환을 일괄 적용한다.
만약, int형과 string형이 섞여 있는 리스트와 int형과 float형이 섞여 있는 리스트를 ndarray로 변경하면 데이터 값의 타입이 어떻게 될까?
list2 = [1,2,'test']
array2 = np.array(list2)
print(array2, array2.dtype) # ['1' '2' 'test'] <U11
list3 = [1,2,3.0]
array3 = np.array(list3)
print(array3, array3.dtype) # [1. 2. 3.] float64
Python
복사
결과를 확인해보면 결국 더 큰 데이터 타입으로 변환된 것을 확인할 수 있다.
'''
물론 데이터 내 타입변환은 astype() 메서드를 사용할 수 있다. 인자로 원하는 데이터 타입을
문자열로 지정해주면 된다.
'''
'''
일반적으로 데이터 변환은 대용량 데이터를 사용할 경우 메모리를 더 절약해야 할 때 이용한다.
예를들어 float형을 int형으로 변경한다든지의 경우다.
'''
array_int = np.array([1,2,3])
array_float = array_int.astype('float64') # 데이터 타입 실수로 변경
print(array_float, array_float.dtype) # [1. 2. 3.] float64
array_int1 = array_float.astype('int32') # 다시 데이터 타입 int32로 변경
print(array_int1, array_int1.dtype) # [1 2 3] int32
array_float1 = np.array([1.1,2.1,3.1])
array_int2 = array_float1.astype('int32') # 데이터 타입 정수로 변경
print(array_int2, array_int2.dtype) # [1 2 3] int32
Python
복사
ndarray를 편리하게 생성하기-arrange, zeros, ones
특정 크기와 차원을 가진 ndarray를 연속값이나 0 또는 1로 초기화해 쉽게 생성해야 할 필요가 있는 경우. 주로 테스트용으로 데이터를 만들거나 대규모의 데이터를 일괄적으로 초기화해야 할 경우에 사용
1.
arange() 는 array를 range()로 표현하는 것.
0부터 함수 인자 값 -1까지의 값을 순차적으로 ndarray의 데이터값으로 변환해준다.
default 함수 인자는 stop값이다. 1차원 ndarray를 형성해준다.
start값도 부여해 0이 아닌 다른값으로 시작할 수도 있다.
sequence_array = np.arange(10)
print(sequence_array) # [0 1 2 3 4 5 6 7 8 9]
print(sequence_array.dtype, sequence_array.shape) # int32 (10,) 1차원 10개의 데이터
Python
복사
2.
zeros() 는 함수 인자로 튜플 형태의 shape값을 입력하면 모든 값을 0으로 채운 해당 shape를 가진 ndarray를 반환해준다.
3.
ones() 는 함수 인자로 튜플 형태의 shape값을 입력하면 모든 값을 1로 채운 해당 shape를 가진 ndarray를 반환해준다. 특별히 dtype을 정해주지 않으면 default로 float64형의 데이터로 채운다.
zero_array = np.zeros((3,2), dtype='int32') # 3행 2열의 이차원 0의 배열 형성
print(zero_array)
'''
[[0 0]
[0 0]
[0 0]]
'''
print(zero_array.dtype, zero_array.shape) # int32 (3, 2)
one_array = np.ones((3,2)) # 3행 2열의 이차원 1의 배열 형성
print(one_array)
'''
[[1. 1.]
[1. 1.]
[1. 1.]]
'''
print(one_array.dtype, one_array.shape) # float64 (3, 2)
Python
복사
ndarray의 차원과 크기를 변경하는 reshape()
reshape() 메서드는 ndarray를 특정 차원 및 크기로 변환한다. 변환을 원하는 크기를 함수 인자로 설정하면 된다.
다음은 0~9까지의 1차원 ndarray를 2x5 형태로 , 5x2 형태의 2차원 배열로 변환해주는 코드다.
array1 = np.arange(10)
print('array1:\n', array1)
'''
array1:
[0 1 2 3 4 5 6 7 8 9]
'''
array2 = array1.reshape(2,5) # 2x5의 2차원 배열로 변환
print('array2:\n', array2)
'''
array2:
[[0 1 2 3 4]
[5 6 7 8 9]]
'''
array3 = array1.reshape(5,2) # 5x2의 2차원 배열로 변환
print('array3:\n', array3)
'''
array3:
[[0 1]
[2 3]
[4 5]
[6 7]
[8 9]]
'''
'''
물론, 지정된 사이즈로 변경이 안되면 오류를 발생시킨다. 예를들어, (10,) 데이터를 (4,3) 형태로
변경할 수 없다.
'''
Python
복사
'''
실전에서 reshape()의 인자로 -1을 적용하는 경우가 효율적으로 사용하는 경우다. -1을 인자로
사용하면 원래 ndarray와 호환되는 새로운 shape로 변환해준다.
'''
print(array1) # [0 1 2 3 4 5 6 7 8 9]
array2 = array1.reshape(-1,5) # 열만 5로 정하고 알아서 행을 맞춰 정하기
print('array2 shape: ', array2.shape) # array2 shape: (2, 5)
array3 = array1.reshape(5,-1) # 행만 5로 정하고 열은 알아서 맞춰 정하기
print('array3 shape: ', array3.shape) # array3 shape: (5, 2)
# 물론 -1을 사용하더라도 호환될 수 없는 형태는 오류를 발생시킨다. 예를들어 (10,)의 데이터를
(-1,4)로 변경할 수 없다.
'''
-1 인자는 reshape(-1,1) 와 같은 형태로 자주 사용된다.
Python
복사
여러 개의 넘파이 ndarray는 stack 이나 concat으로 결합할 때 각각의 ndarray의 형태를 통일해 유용하게 사용된다.
다음 예제는 reshape(-1,1)을 이용해 3차원을 2차원으로, 1차원을 2차원으로 변경한다.
array1 = np.arange(8)
array3d = array1.reshape((2,2,2)) # 3차원의 배열. 각 배열은 2행 2열
print('array3d:\n', array3d.tolist()) # tolist()는 배열을 리스트 자료형으로 변환할때 사용
'''
array3d:
[[[0, 1], [2, 3]], [[4, 5], [6, 7]]]
'''
# 3차원 ndarray를 2차원 ndarray로 변환
array5 = array3d.reshape(-1,1)
print('array5:\n', array5.tolist())
'''
array5:
[[0], [1], [2], [3], [4], [5], [6], [7]]
'''
print('array5 shape:', array5.shape) # array5 shape: (8, 1)
# 1차원 ndarray를 2차원 ndarray로 변환
array6 = array1.reshape(-1,1)
print('array6:\n', array6.tolist())
'''
array6:
[[0], [1], [2], [3], [4], [5], [6], [7]]
'''
print('array6 shape:', array6.shape) # array6 shape: (8, 1)
Python
복사
넘파이의 ndarray의 데이터 세트 선택하기 - 인덱싱
1.
특정한 데이터만 추출 : 원하는 위치의 인덱스 값을 지정하면 해당 위치의 데이터가 반환된다.
2.
슬라이싱: 연속된 인덱스상의 배열을 추출하는 방식.
3.
팬시 인덱싱 : 일정한 인덱싱 집합을 리스트 또는 배열 형태로 지정해 해당 위치에 있는 데이터의 배열을 반환한다.
4.
불린 인덱싱 : 특정 조건에 해당하는지 여부인 True/False 값 인덱싱 집합을 기반으로 True에 해당하는 인덱스 위치에 있는 데이터의 ndarray를 반환한다.
단일 값 추출
•
1차원 배열에서 한 개의 데이터 추출
# 1부터 9까지의 1차원 ndarray 생성
array1 = np.arange(start=1, stop=10)
print('array:\n', array1)
'''
array:
[1 2 3 4 5 6 7 8 9]
'''
#index는 0부터 시작하므로 array1[2]는 3번째 index위치의 데이터값을 의미함
value = array1[2] # 3번째 index
print('value:', value) # value: 3
print(type(value)) # <class 'numpy.int32'> --> 인덱싱 한 값은 ndarray 내의 데이터 형태가
# 된다.
print('맨 뒤의 값:', array1[-1], '맨 뒤에서 두 번째 값:', array[-2])
'''
맨 뒤의 값: 9 맨 뒤에서 두 번째 값: 8
'''
# 인덱싱으로 데이터 값도 수정가능하다.
array1[0] = 9
array1[8] = 0
print('array1:', array1) # array1: [9 2 3 4 5 6 7 8 0]
Python
복사
•
다차원 배열에서 단일 값 추출하기
# 1차원과 다른 점은 2차원의 경우 로우와 칼럼 위치의 인덱스를 통해 접근하는 것이다.
# 앞예제의 1차원 배열을 2차원의 3x3 배열로 변환 후 추출해 보자.
array1d = np.arange(start=1, stop=10) # 1~ 9까지의 1차원
array2d = array1d.reshape(3,3) # 2차원 3x3 배열로 변환
print(array2d)
'''
[[1 2 3]
[4 5 6]
[7 8 9]]
'''
print('(row=0, col=0) index 가리키는 값:', array2d[0,0]) # (row=0, col=0) index 가리키는 값: 1
print('(row=0, col=1) index 가리키는 값:', array2d[0,1]) # (row=0, col=1) index 가리키는 값: 2
print('(row=1, col=0) index 가리키는 값:', array2d[1,0]) # (row=1, col=0) index 가리키는 값: 4
print('(row=2, col=2) index 가리키는 값:', array2d[2,2]) # (row=2, col=2) index 가리키는 값: 9
Python
복사
슬라이싱
단일 데이터값 추출을 제외하고 슬라이싱, 팬시 인덱싱, 불린 인덱싱으로 추출된 데이터 세트는 모두 ndarray 타입이다.
array1 = np.arange(start=1,stop=10)
array3 = array1[0:3] # index 0부터 2까지 슬라이싱
print(array3) # [1 2 3]
print(type(array3)) # <class 'numpy.ndarray'>
Python
복사
1.
‘:’ 사이의 시작, 종료 인덱스는 모두 생략 가능하다.
2.
시작 인덱스를 생략하면 자동으로 맨 처음부터 추출한다.
3.
종료 인덱스를 생략하면 자동으로 맨 마지막까지 추출한다.
4.
둘 다 생략하면 자동으로 맨처음부터 맨 마지막까지 추출한다.
array1 = np.arange(start=1,stop=10)
array4 = array1[:3] # 맨처음부터 2까지
print(array4) # [1 2 3]
array5 = array1[3:] # 3번째부터 끝까지
print(array5) # [4 5 6 7 8 9]
array6 = array1[:] # 맨처음부터 끝까지
print(array6) # [1 2 3 4 5 6 7 8 9]
Python
복사
•
다차원 슬라이싱
array1d = np.arange(start=1, stop=10)
array2d = array1d.reshape(3,3) # 3x3 2차원 배열
print('array2d:\n', array2d)
'''
array2d:
[[1 2 3]
[4 5 6]
[7 8 9]]
'''
print('array2d[0:2, 0:2] \n', array2d[0:2, 0:2])
'''
array2d[0:2, 0:2]
[[1 2]
[4 5]]
'''
print('array2d[1:3, 0:3] \n', array2d[1:3, 0:3])
'''
array2d[1:3, 0:3]
[[4 5 6]
[7 8 9]]
'''
print('array2d[1:3, :] \n', array2d[1:3, :])
'''
array2d[1:3, :]
[[4 5 6]
[7 8 9]]
'''
print('array2d[:, :] \n', array2d[:, :])
'''
array2d[:, :]
[[1 2 3]
[4 5 6]
[7 8 9]]
'''
print('array2d[:2, 1:] \n', array2d[:2, 1:])
'''
array2d[:2, 1:]
[[2 3]
[5 6]]
'''
print('array2d[:2, 0] \n', array2d[:2, 0])
'''
array2d[:2, 0]
[1 4]
'''
Python
복사
2차원 ndarray에서 슬라이싱을 할 때 열의 인덱싱을 없애면 1차원 ndarray를 반환한다.
예를들어, array2d[0] 과 같이 하면 첫 번째 행을 반환하게 되고 1차원이다. 3차원 ndarray에서 없애면 2차원의 배열을 반환한다.
print(array2d[0]) # [1 2 3]
print(array2d[1]) # [4 5 6]
print('array2d[0] shape:', array2d[0].shape, 'array2d[1] shape :', array2d[1].shape)
'''
array2d[0] shape: (3,) array2d[1] shape : (3,)
'''
Python
복사
•
팬시 인덱싱
리스트나 ndarray로 인덱스 집합을 지정하면 해당 위치의 인덱스에 해당하는 ndarray를 반환하는 인덱싱 방식이다.
먼저 2차원 ndarray에 적용해보자.
array1d = np.arange(start=1, stop=10)
array2d = array1d.reshape(3,3) # 2차원 배열로 변환
array3 = array2d[[0,1],2]
print('array2d[[0,1],2] => ', array3.tolist())
array4 = array2d[[0,1], 0:2]
print('array2d[[0,1], 0:2] => ', array4.tolist())
array5 = array2d[[0,1]]
print('array2d[[0,1]] => ', array5.tolist())
Python
복사
•
불린 인덱싱
조건 필터링과 검색을 할 수 있기 때문에 자주 사용되는 인덱싱 방식이다. ndarray의 인덱스를 지정하는 [ ] 내에 조건문을 그대로 기재하기만 하면 된다.
array1d = np.arange(start=1, stop=10)
#[]안에 array1d > 5 를 적용해 보겠다.
array3 = array1d[array1d > 5]
print('array1d > 5 불린 인덱싱 결과 값 :', array3)
array1d > 5
boolean_indexes = np.array([False,False,False,False,False,True,True,True,True])
array3 = array1d[boolean_indexes]
print('불린 인덱스로 필터링 결과 :', array3)
# 위의 결과는 아래의 결과처럼 표현할 수도 있다.
indexes = np.array([5,6,7,8])
array4 = array1d[indexes]
print('일반 인덱스로 필터링 결과 :', array4)
Python
복사
행렬의 정렬 - sort(), argsort()
넘파이에서 행렬을 정렬하는 대표적인 방법인 np.sort(), ndarray.sort(), 그리고 정렬된 행렬의 인덱스를 반환하는 argsort() 에 대해서 공부한다.
•
행렬 정렬 :
◦
np.sort() : 넘파이에서 sort()를 호출하는 방식. 원 행렬은 그대로 유지한 채 원 행렬의 정렬된 행렬을 반환
◦
ndarray.sort() : 행렬 자체에서 sort()를 호출하는 방식. 원 행렬 자체를 정렬한 형태로 반환하며 반환 값은 None이다.
행렬 정렬을 수행해본다.
org_array = np.array([3,1,9,5])
print('원본 행렬: ', org_array)
# np.sort() 로 정렬
sort_array1 = np.sort(org_array)
print('np.sort() 호출 후 반환된 정렬 행렬 :', sort_array1) # 오름차순으로 정렬
print('np.sort() 호출 후 원본 행렬 :', org_array)
# ndarray.sort() 로 정렬
sort_array2 = org_array.sort()
print('ndarray.sort() 호출 후 반환된 정렬 행렬 :', sort_array2) # 오름차순으로 정렬
print('ndarray.sort() 호출 후 원본 행렬 :', org_array)
Python
복사
둘의 정렬 방식 모두 기본적으로 오름차순으로 정렬한다. 내림차순으로 정렬하려면 [::-1]을 적용한다.
# 내림차순으로 정렬
sort_array1_desc = np.sort(org_array)[::-1]
print('내림차순으로 정렬 :', sort_array1_desc)
Python
복사
행렬이 2차원 이상일 경우에 axis 설정을 통해서 행 방향, 열 방향으로 정렬을 수행할 수 있다.
(axis = 0 은 행방향, axis = 1 은 열방향)
array2d = np.array([[8,12],[7,1]])
sort_array2d_axis0 = np.sort(array2d, axis=0) # 행방향으로 정렬, 즉 세로방향으로 정렬
print('로우 방향으로 정렬:\n', sort_array2d_axis0)
sort_array2d_axis1 = np.sort(array2d, axis=1) # 열방향으로 정렬, 즉 가로방향으로 정렬
print('칼럼 방향으로 정렬:\n', sort_array2d_axis1)
Python
복사
•
정렬된 행렬의 인덱스 반환하기
원본 행렬이 정렬되었을 때 기존 원본 행렬의 원소에 대한 인덱스를 필요로 할 때 np.argsort()를 이용한다. 정렬 행렬의 원본 행렬 인덱스를 ndarray 형으로 반환한다.
org_array = np.array([3,1,9,5]) # 1차원 배열
sort_indices = np.argsort(org_array) # 기존 원본 배열의 인덱스 추출
print(type(sort_indices))
print('행렬 정렬 시 원본 행렬의 인덱스: ', sort_indices)
# 마찬가지로 내림차순으로 정렬한다고 하면
sort_indices_desc = np.argsort(org_array)[::-1]
print('행렬 내림차순 정렬 시 원본 행렬의 인덱스: ', sort_indices_desc)
Python
복사
넘파이의 ndarray는 메타 데이터를 가질 수 없다. 따라서 실제 값과 그 값이 뜻하는 메타 데이터를 별도의 ndarray로 각각 가져야 한다. 예를 들어 학생별 시험 성적을 데이터로 표현하기 위해서는 학생의 이름과 시험 성적을 각각의 ndarray로 가져야 한다.
name_array = np.array(['John','Mike','Sarah','Kate','Samuel'])
score_array = np.array([78,95,84,98,88])
sort_indices_asc = np.argsort(score_array)
print('성적 오름차순 시 score_array의 인덱스: ', sort_indices_asc)
print('성적 오름차순으로 name_array의 이름 출력: ', name_array[sort_indices_asc])
Python
복사
선형대수 연산 - 행렬 내적과 전치 행렬 구하기
넘파이는 매우 다양한 선형대수 연산을 지원한다. 기본 연산인 행렬의 내적과 전치 행렬을 구하는 방법을 알아본다.
•
행렬 내적(행렬 곱)
A = np.array([[1,2,3],[4,5,6]]) # 2행 3열의 행렬
B = np.array([[7,8],[9,10],[11,12]]) # 3행 2열의 행렬
dot_product = np.dot(A,B)
print('행렬 내적 결과:\n', dot_product)
Python
복사
•
전치 행렬
넘파이의 transpose()를 이용해서 전치 행렬을 구한다.
A = np.array([[1,2], [3,4]]) # 2x2 행렬
transpose_mat = np.transpose(A)
print('A의 전치 행렬:\n', transpose_mat)
Python
복사