Numpy

6 minute read

1. Numerical Computing

  • 소수의 전문가 (과학자, 엔지니어)를 위한 학문분야였음
  • 효율성 / 편리성이 중요했음
  • 넘파이도 초보자를 위한 파트는 아님

2. Numpy

  • ndarray
  • 속도가 빠르다
  • stride 기법 도입하여 인덱싱, 슬라이싱 빠르게 만듦
  • C로 만들어짐
  • CPU 지원
  • 전문가가 사용할 수 있게 미묘한 차이를 가진 메서드를 다양하게 지원
  • BUT 오래전에 만들어서 GPU 지원을 안함
import numpy as np

3. Array

  • 3차원 Array부터 Tensor라고 함
  • cube도 3차원

4. ndarray

(1) np.array

1차원

a = np.array([1, '1']) # 파사드 방식(팩토리 메서드)
a # array(['1', '1'], dtype='<U11')
  • 다른 데이터타입(숫자, 문자)을 입력하더라도 homogeneous이기 때문에 자동으로 문자 타입으로 변환

2차원

aa = np.array([[1,2], [3, 4]])
aa
# array([[1, 2],
#       [3, 4]])

3차원

aa = np.array([[[1,2], [3, 4]], [[1,2], [3, 4]]])
aa

# array([[[1, 2],
#         [3, 4]],

#        [[1, 2],
#         [3, 4]]])
  • 앞에 있는 대괄호 개수로 차원을 판단할 수 있음
  • 차원은 공백으로 구분하며, 데이터가 많을 때는 …으로 표현

(2) shape, dtype

aa.shape # (2, 2, 2)
aa.dtype # dtype('int32')
  • aa.ndim : aa.shape의 개수로도 확인할 수 있음
  • aa.size : aa.shape의 값을 다 곱해서도 확인할 수 있음

(3) 데이터 초기화

다음과 같이 다양한 방법으로 데이터를 생성해볼 수 있다.

b = np.ones((3,4)) # 1로 채우기
b = np.zeros((3,4)) # 0으로 채우기
b = np.full((3,4), 5) # 5로 채우기

c = np.ones_like(b)
# b의 shape처럼 만들어 1로 채움

np.eye(4, k=1)
np.identity(4)
np.tri(4)

np.arange(10) # array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

5. 인덱싱, 슬라이싱

큰 것부터 뽑는다고 생각하면 쉽다.

2차원

a = np.arange(25).reshape(5,5)
a
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19],
       [20, 21, 22, 23, 24]])
a[0][1] # 1
a[0,1] # 1
a[0,:] # array([0, 1, 2, 3, 4])

3차원

b = np.arange(24).reshape(2,3,4)
b
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]],

       [[12, 13, 14, 15],
        [16, 17, 18, 19],
        [20, 21, 22, 23]]])
b[1,1,2] # 18
  • 한 개 이상의 :은 …으로 바꿀 수 있으며, 맨 끝에 있는 …은 생략이 가능하다.
b[1, :, :]
b[1, ...]
b[1]

# array([[12, 13, 14, 15],
#       [16, 17, 18, 19],
#       [20, 21, 22, 23]])

6. 차원 추가

  • None or np.newaxis: 차원을 추가하는 용도로 만들어짐
  • 마지막 :은 생략 가능
c = np.arange(25).reshape(5,5)

c[None, :].shape # (1, 5, 5)
c[:, None].shape # (5, 1, 5)
c[:,:, None].shape # (5, 5, 1)

7. elementwise

ndarray로는 같은 위치의 요소끼리 계산하는 것이 가능하다.

a = [1,2,3]
b = [4,5,6]
a+b # [1, 2, 3, 4, 5, 6]

x = np.array(a)
y = np.array(b)
x+y # array([5, 7, 9])
e = np.array([[1,2],[3,4]])
e*e
# array([[ 1,  4],
#        [ 9, 16]])

행렬곱을 하려면 np.array 말고 np.mat로 생성하거나 @를 사용할 수 있다.

e@e
# array([[ 7, 10],
#        [15, 22]])

8. Copy / View

  • copy를 해놓으면 원본으로 돌아갈 수 있으므로 원본 백업 용도로 사용할 수 있다.
  • 파이썬과 numpy에서의 copy 활용 방법이 약간 다르다.
shallow / deep 파이썬 넘파이
shallow copy .copy() .view()
deep copy copy.deepcopy() .copy()

copy가 필요한 상황 (파이썬)

a = [1,2,3]
b = a
a[0] = 100
b
# [100, 2, 3]

같은 것을 가르키게 되므로 mutable인 a를 변경할 시 b도 함께 변경된다.

copy가 필요한 상황 (넘파이)

x = np.array([1,2,3])
y = x
y[0] = 100
x
# array([100,   2,   3])

같은 것을 가르키게 되므로 mutable인 x를 변경할 시 y도 함께 변경된다.

copy 적용 (파이썬)

a = [1,2,3]
b = a.copy() # a[:]도 같은 결과를 도출함
a[0] = 100
b
# [1, 2, 3]

만약 a = [[1,2], [3,4]]처럼 겹으로 쌓여있으면 위와 같이 했을 때 b는 변경되므로 deep copy가 필요할 것이다.

import copy
a = [[1,2], [3,4]]
b = copy.deepcopy(a)
a[0][0] = 100
b
# [[1, 2], [3, 4]]

deep copy를 적용하니 변경되지 않았다.

copy 적용 (numpy)

x = np.array([[1,2],[3,4]])
y = x.copy()
y[0,0] = 100
x
# array([[1, 2],
#        [3, 4]])

numpy는 기본 copy가 deep copy이다. 파이썬의 copy처럼 사용하고 싶다면 view를 사용한다.

x = np.array([[1,2],[3,4]])
y = x.view()
y[0,0] = 100
x
# array([[100,   2],
#        [  3,   4]])

shallow copy가 되었기 때문에 y를 변경했을 때 x도 변경되었다.

9. Stride

  • 데이터의 다음 데이터로 갈 때 필요한 메모리 크기
  • stride를 이용하면 속도가 빨라진다.
  • numpy는 기본적으로 일렬로 데이터를 표현하는데 stride를 이용해 N차원 표현이 가능하다.
  • stride 개념에서 axis 개념이 도출된다.
a = np.arange(25)
a.strides # (4,)

b = a.reshape(5,5)
b.strides # (20, 4)
# 행은 20만큼, 열은 4만큼씩 +

# 스트라이드를 할당하는 것은 안됨
a.strides = (20,4)

10. Broadcasting

a = np.array([1,2,3])
b = np.array([1,2,3]).reshape(3,1)
a + b

# array([[2, 3, 4],
#        [3, 4, 5],
#        [4, 5, 6]])

11. Data Type

(1) Typecasting

array를 생성하면서 다음과 같이 데이터타입을 지정할 수 있다.

a = np.arange(1000, dtype=np.float64)
데이터타입 약자 확인 방법
np.sctypeDict

각 데이터타입의 값 범위를 확인할 수 있는 여러 메서드 또한 존재한다.

# int8의 값 범위 확인
np.iinfo(np.int8)

# float64의 값 범위 확인
np.finfo(np.float64)

만약 기존에 이미 생성된 데이터의 데이터타입을 바꾸고자 한다면 as~메서드를 사용할 것이다. 예를 들어, float로 바꾸고자 한다면 np.asfarray()를 사용할 수 있으며 가장 범용적으로 사용하는 것은 astype()이다.

a = np.array([1,2,3])
b = np.asfarray(a)
c = a.astype('i8')
d = a.astype(np.int64)

(2) 사용자 정의 데이터타입

Structured Arrays

x = np.array([('Rex', 9, 81.0), ('Fido', 3, 27.0)],
              dtype=[('name', 'U10'), ('age', 'i4'), ('weight', 'f4')])

위와 같은 예시에서, dtype 리스트의 각 튜플 안에는 첫번째에 이름, 두번째에 데이터타입이 들어가게 된다.
이렇게 생성한 데이터에 대해서는 Pandas와 유사한 방법으로 데이터 추출이 가능하다. (Pandas는 Numpy 기반으로 만들어졌다.)

x['age'] # array([9, 3])

단, attribute 방식은 불가능하다.

x.age # AttributeError

Record Arrays

recordarr = np.rec.array([(1, 2., 'Hello'), (2, 3., "World")],
                    dtype=[('foo', 'i4'),('bar', 'f4'), ('baz', 'S10')])

Record Arrays를 사용하게 될 경우, 두 가지 방식의 데이터 추출 모두 가능하다. 즉, Pandas에서도 Record Arrays를 사용했음을 알 수 있다.

recordarr['foo'] # array([1, 2])
recordarr.foo # array([1, 2])

12. Shape

(1) reshape

a = np.arange(25)
a.reshape(5,5) # 5 X 5
# 리턴이 있으므로 mutable 아님. a는 바뀌지 않았음

a.reshape(5,-1) # 5 X 5

전체 사이즈에 맞게 reshape해야 한다.
reshape에 하나의 값이 전달되었을 때 나머지 값은 -값으로 어떤 것을 넣더라도 자동으로 전체 사이즈에 맞게 reshape된다. scikit에서는 -1만 허용하므로 -1을 사용하는 것이 좋다.

(2) resize

a.resize(5,6, refcheck=False)
a.resize(5,4, refcheck=False)
# 여기서는 리턴 없음. a가 바로 변경됨

resize에 의해 새로 추가되는 값은 0으로 표현되며, 원래보다 사이즈를 줄일 경우에는 앞부분 값으로 잘리게 된다.

13. 쪼개기 / 붙이기

(1) split

x = np.arange(25).reshape(5,5)
y = np.arange(24).reshape(4,6)

np.vsplit(y, 2) # vertical split
np.split(y, 2, axis=0) # vertical split
np.hsplit(y, 2) # horizontal split
np.split(y, 2, axis=1) # horizontal split

np.vsplit(x, 2) # TypeError
np.hsplit(x, 2) # TypeError
# 홀수이기 때문에 등분이 안되었음

np.vsplit(x, (2,3))
# 2보다 작은 것, 2와 3사이, 3보다 큰 것으로 구간을 쪼개기

split 하고 나면 unpacking도 가능해진다.

a, b = np.vsplit(y, 2)
# a
array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11]])
# b
array([[12, 13, 14, 15, 16, 17],
       [18, 19, 20, 21, 22, 23]])

(2) stack, concatenate, r_, c_

위의 unpacking한 결과를 활용할 것이다.

np.stack([a, b])

2차원 2개를 붙였는데 3차원 array가 된 것을 확인할 수 있다. 만약 vstack, hstack을 사용한다면 2차원 결과를 반환받을 수 있다. indexer 기반(판다스의 iloc, loc처럼)의 r_, c_를 사용할 수도 있다.

np.vstack([a, b])
np.r_[a,b] # vstack과 같은 결과를 반환
np.hstack([a, b])
np.c_[a,b] # hstack과 같은 결과를 반환

stack 대신에 concatenate를 사용할 수도 있다. concatenate는 stack과 다르게 바로 2차원 결과를 반환한다.

np.concatenate([a,b])

+ 기타 유용한 명령어

lookfor

# sum이 들어간 모든 것을 찾아줌
np.lookfor('sum')
scipy.lookfor('sum')

# numpy에서 제공하는 모든 것을 찾아줌
np.lookfor('*')

# sum에 대한 설명을 더 자세히 보고 싶을 때
np.info(np.sum)

Leave a comment