본 과정은 제가 파이썬 수업을 들으면서 배운 내용을 복습하는 과정에서 적어본 것입니다.
틀린 부분이 있다면 댓글에 남겨주시면 고치도록 하겠습니다.
확실하지 않은 내용은 ‘(?)’을 함께 적었으니 그 내용을 아신다면 댓글에 남겨주시면 감사하겠습니다.
2019년 11월 12일
NumPy
- Vectorization 기법으로 구현된 행렬 및 벡터 연산 라이브러리입니다.
- 넘파이는 immutable이고, homogeneous, Sequence 입니다.
Sequence이므로 인덱스와 슬라이싱이 가능합니다. Numpy는 Numarray와 Numeric이라는 오래된 Python 패키지를 계승해서 나온 수학 및 과학 연산을 위한 패키지입니다. 넘파이는 속도가 빠르고, 사용하기가 쉽습니다. 다차원 배열을 위한 기능과 선형 대수 연산과 푸리에 변환(Fourier transform) 같은 고수준 수학 함수와 유사 난수 생성기를 포함합니다. 따라서 실제 코딩의 양을 줄일뿐만아니라, 벡터 계산은 병렬 계산이 가능하기 때문에, Multi core 활용이 가능합니다. scikit-learn에서 NumPy 배열은 기본 데이터 구조입니다. scikit-learn은 Numpy 배열 형태의 데이터를 입력으로 받습니다. Numpy의 핵심 기능은 다차원(n-차원) 배열인 ndarray 클래스입니다. Array에 대해 간단하게 알아보도록 하겠습니다.
Numerical Python = NumPy
- 벡터, 행렬 연산을 위한 수치해석용 Python 라이브러리입니다. numpy는 np로 불러옵니다.
import numpy as np
Array
- 연속적으로 데이터가 이루어진 배열을 array라고 합니다. 넘파이의 자료구조인 array는 homogeneous 이며 immuable, Sequence 입니다. 3차원부터 ndarray라고 합니다.
a = np.array(0)
a
# array(0)
## numpy로 만든 array의 타입은 numpy.ndarray이다.
type(a)
# numpy.ndarray
b = np.array([1,2,3,4,5]) # 여러 요소를 표함하는 다차원 배열을 선언할 경우, 관례적으로 list로 묶어준다.
b
# array([1,2,3,4,5])
등간격(Strides)
행 넘어갈 때 12bytes가 필요하고 열 넘어갈 때 4bytes가 필요하다. Strides로 지정해 놓았던 덕분에 빠르게 값을 찾아갈 수 있다. C 언어에서 포인트 개념과 똑같다. Strides도 dtype, shpae을 알 수 있다.
- np.itemsize
a = np.arange(10)
a.itemsize
# 4
a.dtype
# dtype('int32')
다차원 배열
- 이차원 및 삼차원 배열
## 이차원
c = np.array([1,2,3],[4,5,6])
c
# array([[1,2,3],
[4,5,6]])
# ndim
c.ndim
# 2
## 삼차원
d = np.array([[[1,2,3],[4,5,6]]])
d
# array([[[1, 2, 3],
[4, 5, 6]]])
d.ndim
# 3
- 원소의 개수를 알고 싶을 때는 size를 사용한다.
a = np.arange(10)
a.size
넘파이 불러오기 및 버전 체크
- 넘파이는 np로 이름을 축약해서 가져옵니다.
import numpy as np
np.__version__
# '1.16.2'
- 넘파이에서 설명서 찾기
np.info('array') # array에 대한 설명서를 보여준다.
np.lookfor('array') # array가 들어가져있는 기법들에 대해 다 찾아준다.
- np.array는 C방식과 포트란(Fortran)방식을 지원해줍니다. 하지만 여기서는 C방식으로만 작성하도록 하겠습니다. 데이터 저장 순서의 차이가 있을 뿐입니다. C 방식은 왼쪽에서 오른쪽이고, 포트란 방식은 위에서 아래로 저장됩니다.
a = np.arange(10)
a.flags
'''
C_CONTIGUOUS : True
F_CONTIGUOUS : True
OWNDATA : True
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False
'''
# 내부 저장 방식: C 방식/ 포트란(F) 방식
shape & dtype
넘파이에서 가장 많이 쓰는 속성입니다.
- shpae: 행렬의 사이즈를 확인하는 함수입니다.
- dtype: 행렬의 type을 확인해주는 함수입니다.
gpas = np.array([1,2,'2'])
gpas.shape # shape, dtype은 메소드인데, property를 쓰면 ()(괄호)를 안써도 된다.
# (3,)
gpas.dtype
# dtype('<U11')
property
class B:
def __init__(self,a):
self.y = a
@property # descriptor 이다.
def x(self):
return self.y
b = B(2)
b.x # 괄호를 안써도 된다.
# 2
넘파이 계산식
- reduction: 대표성
속도가 빠릅니다.
from functools import reduce
reduce(lambda x, y : x+y, [1,2,3,4])
# 10
reduce(np.add, [1,2,3,4])
# 10
np.add.reduce([1,2,3,4])
# 10
%time reduce(lambda x,y:x+y, range(10000))
'''
Wall time: 999 µs
49995000
'''
%time reduce(np.add, range(10000))
'''
Wall time: 38 ms
49995000
'''
%time np.add.reduce(np.arange(10000))
'''
Wall time: 0 ns
49995000
'''
%time np.sum(np.arange(10000))
'''
Wall time: 999 µs
49995000
'''
- np.add()
np.add은 call signature이다.
## np.add
np.add(3,4)
# call signature
# ufunc: type이 ufunc (universial function) -> 데이터가 여러개 들어오는 것
## np.accumulate
# 누적으로 계산
np.add.accumulate([1,2,3,4])
# array([ 1, 3, 6, 10], dtype=int32)
## np.sum
np.sum([3,4])
## from itertools import accumulate
list(accumulate([1,2,3,4,], lambda x,y:x+y))
# [1, 3, 6, 10]
ufunc(universal function) 범용적인 함수 즉, python, numpy 둘다 있는 함수입니다.
- 1개의 배열에 대한 ufunc 함수
abs,fabs => 절대값
ceil => 올림
floor => 내림
modf => 정수부분과 소수점 부분 분리
rint => 올림하거나 내림하거나 5를 기준으로
log, log10, log2, log1p => 로그 값 취하기
exp => exponential 지수함수 (정확히 어떻게 계산되는지는 모르겠음)
sqrt => 루트
square => 제곱
isnan => nan인지 체크
isinfinite => 유한한 수안자 체크
logical_not => 모르겠음
sign = > 0을 제외하고 다 1로 반환 (사실 정확하지 않음)
sin, cos, tan => sin, cos, tan값 계산
arcsin, arccos, arctan => 역삼각함수 계산
- 2개의 배열에 대한 ufunc 함수
add => 각 요소 더하기
subtract => 각 요소 빼기
multiply => 각 요소 곱하기
divide => 각 요소 나눈 값
floor_divide => 각 요소 나눈 몫
mod => 각 요소 나눈 나머지
power => 승 계산 ex) 2,3 => 2의 3 승 : 8
maximum, fmax => 더 큰 값
minimum, fmin => 더 작은 값
greater => 앞 값이 더 크면 True 작으면 False
greater_equal => 앞 값이 크거나 같으면 True 작으면 False
less => greater 반대
less_equal => greater_equal 반대
equal => 같으면 True
not_equal => 다르면 True
Numpy 연산식
NumPy 연산식을 이용하려면, 넘파이 array의 사이즈를 맞춰야한다.
Broadcasting
기본 연산은 개별 원소마다 적용됩니다. 따라서 모양이 다른 배열간의 연산은 불가능합니다. 하지만 특정 조건이 만족되면 배열 변환이 자동으로 일어나서 연산이 가능합니다. 이를 브로드캐스팅이라고 합니다. 즉 ,브로드캐스팅은 벡터연산에서 자동으로 크기 맞춰주는 기법입니다.
a = np.array([[1,2]])
b = np.array([3,2])
print(a.shape)
print(b.shape)
# shape이 다르지만 내적계산을 한다. 자동으로 크기르 맞춰주는 것을 broadcasting이라고 한다.
a@b # 내적: 1*3 + 2*2
'''
(1, 2)
(2,)
array([7])
'''
broadcasting으로 차원 맞추기
- np.ix_()
x = np.array([1,2,3])
y = np.array([10, 20, 30])
a, b = np.ix_(y,x)
a, b
'''
(array([[10],
[20],
[30]]), array([[1, 2, 3]]))
'''
# 양쪽으로 broadcasting
a+b
'''
array([[11, 12, 13],
[21, 22, 23],
[31, 32, 33]])
'''
- np.meshgrid()
XX,YY =np.meshgrid(x,y)
XX, YY
'''
(array([[1, 2, 3],
[1, 2, 3],
[1, 2, 3]]), array([[10, 10, 10],
[20, 20, 20],
[30, 30, 30]]))
'''
XX + YY
'''
array([[11, 12, 13],
[21, 22, 23],
[31, 32, 33]])
'''
- np.atleast_#d() (#: 1,2,3)
차원을 늘려줍니다.
a, b = np.atleast_2d(x,y)
a, b
# (array([[1, 2, 3]]), array([[10, 20, 30]]))
a+b.T
'''
array([[11, 12, 13],
[21, 22, 23],
[31, 32, 33]])
'''
- np.ogrid[]
대괄호 쓰는 애들을 indexer(인덱서)라고 합니다.
XX, YY = np.ogrid[10:40:10, 1:4]
XX,YY # broadcasting 이용
'''
(array([[10],
[20],
[30]]), array([[1, 2, 3]]))
'''
XX + YY
'''
array([[11, 12, 13],
[21, 22, 23],
[31, 32, 33]])
'''
np.ogrid[10:40:10, 1:4:4j] # j는 등분을 해준다.
'''
[array([[10.],
[20.],
[30.]]), array([[1., 2., 3., 4.]])]
'''
- np.mgrid[]
a,b = np.mgrid[10:40:10, 1:4]
a,b
'''
(array([[10, 10, 10],
[20, 20, 20],
[30, 30, 30]]), array([[1, 2, 3],
[1, 2, 3],
[1, 2, 3]]))
'''
a+b
'''
array([[11, 12, 13],
[21, 22, 23],
[31, 32, 33]])
'''
np.reshape vs. np.resize
둘다 array의 shape을 바꾸는 것이지만, reshape은 메소드방식으로 쓰고, resize는 function 방식으로 씁니다. 또한 resize같은 경우 모양을 안맞춰줘도 되며 크기를 줄일수도 있고, 크기를 늘릴 수도 있습니다. 이러한 형태때문에 np.resize는 거의 쓰지않습니다. 왜냐하면 size는 개수 기준으로 맞춰주기에 임의로 숫자를 생성해버릴 수 있기 때문입니다.
a = np.arange(20)
## reshape
a.reshape(4,5) # immutable이기 때문에 바뀌지 않는다.
# array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19]])
a.reshape(5,-1) # 메소드 방식
# array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15],
[16, 17, 18, 19]])
## np.resize
np.resize(a, (4,5)) # function 방식
# array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19]])
np.resize(a, (4,8)) # 크기가 줄어들고, 늘어나게 한다.
# array([[ 0, 1, 2, 3, 4, 5, 6, 7],
[ 8, 9, 10, 11, 12, 13, 14, 15],
[16, 17, 18, 19, 0, 1, 2, 3],
[ 4, 5, 6, 7, 8, 9, 10, 11]])
np.resize(a, (4,-1))
# array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
a = np.arange(20)
a.resize(4,8)
a
# array([[ 0, 1, 2, 3, 4, 5, 6, 7],
[ 8, 9, 10, 11, 12, 13, 14, 15],
[16, 17, 18, 19, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0]])
np.ravel vs. np.flatten
차원을 줄이는 함수를 ravel, flatten이라고 합니다. 둘 다 차원을 줄이지만 ravel은 view 개념을 갖고 있고, flatten은 copy 개념을 갖고 있습니다.
a = np.arange(10)
a.resize(2,5)
a
'''
array([[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9]])
'''
a.ravel()
a.flatten()
# array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
- view vs. copy
- view 개념: 넘파이의 ndarray(이하 배열)을 슬라이싱할 때 파이썬 리스트와 다르게 원본의 참조가 생성되는 것을 의미합니다. 따라서 원본이 바뀌면 참조본도 같이 바뀌게 됩니다.
- copy 개념: 넘파이의 ndarray(이하 배열)의 복사본을 만들기 때문에 원본이 바뀌어도 복사본에는 아무런 영향을 받지 않습니다.
2019년 11월 13일
np.newaxis vs. np.expand_dims()
차원을 늘려줄 때는 np.newaxis와 np.expand_dims를 사용합니다. 생성하는데 차이가 있는데, np.newaxis는 인덱서 방식으로 생성해주고, np.expand_dims는 함수 방식으로 생성합니다.
x = np.arange(6).reshape(2,3)
## np.newaxis
x[:,:,np.newaxis] # :을 축약하는 기법으로 ...이 있지만, 쓰지 않는 것을 추천한다.
'''
array([[[0],
[1],
[2]],
[[3],
[4],
[5]]])
'''
x[...,np.newaxis] # 인덱서 방식
'''
array([[[0],
[1],
[2]],
[[3],
[4],
[5]]])
## Newaxis == None # np.newaxis 대신 None을 써도 된다.
np.newaxis is None
# Ture
'''
## np.expand_dims
np.expand_dims(x,0) # axis 개념
# 사용하기 위해서 모양을 맞춰주기 위해 필요하다.
'''
array([[[0, 1, 2],
[3, 4, 5]]])
'''
np.repeat vs. np.tile
repeat는 flatten해준 다음 반복을 해주고, tile은 행렬 모양을 유지하면서 반복해줍니다.
## repeat
np.repeat([3,4],3)
# array([3, 3, 3, 4, 4, 4]) # 각원소를 3번 반복해준다.
## tile
a = np.array([[1,2,3],[4,5,6]])
np.tile(a,(1,2))
'''
array([[1, 2, 3, 1, 2, 3],
[4, 5, 6, 4, 5, 6]])
'''
숫자 간격 등분
np.linspace(0,0.9,10)
# array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])
shape 쪼개기
- np.split
x = np.arange(12)
a,b,c = np.split(x, 3) # 행을 3등분으로 나누어 준다.
len(a)
# 4
np.split(x,(2,3)) # 행으로 3번째와 4번째를 나누었다.
# [array([0, 1]), array([2]), array([ 3, 4, 5, 6, 7, 8, 9, 10, 11])]
- np.vsplit
수직으로 쪼개줍니다.
x = np.arange(12).reshape(3,4)
np.vsplit(x,3)
# [array([[0, 1, 2, 3]]), array([[4, 5, 6, 7]]), array([[ 8, 9, 10, 11]])]
- np.hsplit
가로로 쪼개줍니다.
np.hsplit(x,2)
'''
[array([[0, 1],
[4, 5],
[8, 9]]), array([[ 2, 3],
[ 6, 7],
[10, 11]])]
'''
shape 합치기
- np.r_
수직으로 합쳐줍니다. (행별로 합쳐줍니다.)x = np.arange(12).reshape(3,4) x, y = np.hsplit(x,2) x, y ''' (array([[0, 1], [4, 5], [8, 9]]), array([[ 2, 3], [ 6, 7], [10, 11]])) ''' np.r_[x,y] ''' array([[ 0, 1], [ 4, 5], [ 8, 9], [ 2, 3], [ 6, 7], [10, 11]]) ''' np.r_[[x],[y]] ''' array([[[ 0, 1], [ 4, 5], [ 8, 9]], [[ 2, 3], [ 6, 7], [10, 11]]]) ''' - np.c_
가로로 합쳐줍니다. (열별로 합쳐줍니다.)
np.c_[x,y]
'''
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
'''
- np.concatenate
axis에 따라 행과 열별로 합쳐줄 수 있습니다.
np.concatenate((x,y), axis =0)
'''
array([[ 0, 1],
[ 4, 5],
[ 8, 9],
[ 2, 3],
[ 6, 7],
[10, 11]])
'''
np.concatenate((x,y), axis =1)
'''
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
'''
- np.vstack/np.row_stack
세로로 합쳐줍니다. (행 별로 합쳐줍니다.)
np.vstack((x,y))
np.row_stack((x,y))
'''
array([[ 0, 1],
[ 4, 5],
[ 8, 9],
[ 2, 3],
[ 6, 7],
[10, 11]])
'''
- np.hstack/np.column_stack
가로로 합쳐줍니다. (열 별로 합쳐줍니다.)
np.hstack((x,y))
np.column_stack((x,y))
'''
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
'''
- np.dstack
차원별로 합쳐줍니다.
x, y
'''
(array([[0, 1],
[4, 5],
[8, 9]]), array([[ 2, 3],
[ 6, 7],
[10, 11]]))
'''
np.dstack((x,y))
'''
array([[[ 0, 2],
[ 1, 3]],
[[ 4, 6],
[ 5, 7]],
[[ 8, 10],
[ 9, 11]]])
'''
Einsum
넘파이에 있는 einsum은 내부적으로 최적화가 잘 되어있어 행렬변환이나 계산에 매우 용이합니다. 다음 사이트에 정리가 잘되어있으니 확인해보시기 바랍니다.
- summation
import numpy as np
A = np.array([0, 1, 2])
B = np.array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]])
A[:,np.newaxis].shape
# (3,1)
B.shape
# (3, 4)
(A[:, np.newaxis] * B)
'''
array([[ 0, 0, 0, 0],
[ 4, 5, 6, 7],
[16, 18, 20, 22]])
'''
(A[:, np.newaxis] * B).sum(axis=1)
# array([ 0, 22, 76])
# i,ij->i (AB곱한 행렬에서 행별 sum)
np.einsum('i,ij->i', A, B) # 가변 포지셔널 방식
# array([ 0, 22, 76])
# ij-> (행렬 합)
x = np.array([[1,2,3],[2,3,4]])
np.einsum('ij->',x)
# 15
# i-> (1차 행렬 합)
x = np.array([1,2,3])
np6.einsum('i->',x)
# 6
# ij->j (열별 합)
a = np.arange(6).reshape(2,3)
a
'''
array([[0, 1, 2],
[3, 4, 5]])
'''
np.einsum('ij->j', a)
np.sum(a,axis=0)
# array([3, 5, 7])
# ij->i (행별 합)
np.einsum('ij->i', a)
np.sum(a,axis=1)
# array([ 3, 12])
- Transpose (전치)
a = np.arange(6).reshape(2,3)
a
'''
array([[0, 1, 2],
[3, 4, 5]])
'''
a.T
'''
array([[0, 3],
[1, 4],
[2, 5]])
'''
np.einsum('ij->ji', a)
'''
array([[0, 3],
[1, 4],
[2, 5]])
'''
- Matrix-Vector Multiplication
b = np.arange(3)
a,b
'''
(array([[0, 1, 2],
[3, 4, 5]]), array([0, 1, 2]))
'''
np.einsum('ik,k->i', a, b) # a는 행렬, b는 vector를 곱하는 것이다. (문자, 숫자 다름)
# array([ 5, 14])
- Matrix-Matrix Multiplication 행렬곱 (행렬 크기를 맞춰야합니다.) 행렬크기가 (2,3)이면 곱하는 행렬은 (3,..) 행이 3과 같아야합니다. 즉, ij의 행렬크기라면 j_가 되어야합니다.
b = np.arange(15).reshape(3, 5)
a.shape
# (2, 3)
b.shape
# (3, 5)
np.einsum('ik,kj->ij', a, b)
'''
array([[ 25, 28, 31, 34, 37],
[ 70, 82, 94, 106, 118]])
'''
- HADAMARD(element-wise) Prodcut element-wise는 같은 행렬 위치에 있는 애들끼리 곱하는 것입니다.
b = np.arange(6,12).reshape(2, 3)
np.einsum('ij,ij->ij', a, b)
'''
b = np.arange(6,12).reshape(2, 3)
np.einsum('ij,ij->ij', a, b)
'''
- Dot Product (내적)
b = np.arange(3)
c = np.arange(3,6) # -- a vector of length 3 containing [3, 4, 5]
np.einsum('i,i->', b, c)
# 14
b = np.arange(6,12).reshape(2, 3)
np.einsum('ij,ij->', a, b) # ij가 a, ij->에 b 대입
# 145
- Outer Product (외적)
a = np.arange(3)
b = np.arange(3,7) # -- a vector of length 4 containing [3, 4, 5, 6]
np.einsum('i,j->ij', a, b)
'''
array([[ 0, 0, 0, 0],
[ 3, 4, 5, 6],
[ 6, 8, 10, 12]])
'''
메소드 방식/함수 방식
students_gpas = np.array([
[4.0, 3.286, 3.5, 4.0],
[3.2, 3.8, 4.0, 4.0],
[3.96, 3.92, 4.0, 4.0]
], np.float16)
np.mean(students_gpas) # 함수 방식
# 3.805
students_gpas.mean() # 메소드 방식
# 3.805
- 메소드/함수 방식
class B: def x(self): print(‘a’) b = B() # 인스턴스화 b.x() # 메소드 방식 B.x(b) # 함수 방식
■ Zen of NumPy (and Pandas)
○ Strided is better than scattered
○ Contiguous is better than strided
○ Descriptive is better than imperative (use data-types)
○ Array-oriented and data-oriented is often better than object-oriented
○ Broadcasting is a great idea –use where possible
○ Split-apply-combine is a great idea – use where possible
○ Vectorized is better than an explicit loop
○ Write more ufuncs and generalized ufuncs (numba can help)
○ Unless it’s complicated — then use numba
○ Think in higher dimensions
- 판다스는 64비트를 쓴다. (빅데이터에 사용하기 때문에)
무단 배포 금지
Comments