본문 바로가기

인공지능/데이터 분석

4일차 - 넘파이 배열 산술 연산

반응형

넘파이 배열의 산술 연산

  • 배열의 중요한 특징은 for문을 작성하지 않고 데이터를 일괄 처리할 수 있다는 것! -> 벡터화라고 합니다.
  • 같은 크기의 배열간의 산술 연산은 배열의 각 원소 단위로 적용이 됩니다.
import numpy as np
arr = np.array([[1.,2.,3.], [4.,5.,6.]])
arr
array([[1., 2., 3.],
       [4., 5., 6.]])
arr * arr
array([[ 1.,  4.,  9.],
       [16., 25., 36.]])
arr - arr
array([[0., 0., 0.],
       [0., 0., 0.]])
  • 스칼라 인자가 포함된 산술 연산의 경우에 배열 내의 모든 원소에 스칼라 값이 적용이 됩니다!
1 / arr
array([[1.        , 0.5       , 0.33333333],
       [0.25      , 0.2       , 0.16666667]])
arr ** 0.5
array([[1.        , 1.41421356, 1.73205081],
       [2.        , 2.23606798, 2.44948974]])
  • 같은 크기를 가지는 배열 간의 비교 연산은 불리언 배열을 반환합니다!
arr2 = np.array([[0., 4., 1.], [7., 2., 12.]])
arr2
array([[ 0.,  4.,  1.],
       [ 7.,  2., 12.]])
arr2 > arr
array([[False,  True, False],
       [ True, False,  True]])

색인과 슬라이싱의 기초!

  • 데이터의 부분집합이나, 개별 요소를 선택하기 위한 수많은 방법이 존재합니다
  • 1차원 배열은 단순한데, 표면적으로는 파이썬의 리스트와 유사하게 동작을 합니다
arr = np.arange(10)
arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
arr[5]
5
arr[5:8]
array([5, 6, 7])
arr[5:8] = 12
arr
array([ 0,  1,  2,  3,  4, 12, 12, 12,  8,  9])
  • 파이썬 리스트 자료구조와 다른점은 배열 조각은 원본 배열의 뷰라는 점이다!
  • 즉 데이터는 복사되지 않고 뷰에 대한 변경은 그대로 원본 배열에 반영이됩니다.
arr_slice = arr[5:8]
arr_slice
array([12, 12, 12])
  • 위 arr_slice의 값을 변경하면 원래 배열인 arr의 값도 바뀌어 있음을 확인해보자
arr_slice[1] = 12345
arr
array([    0,     1,     2,     3,     4,    12, 12345,    12,     8,
           9])
  • 넘파이는 대용량의 데이터 처리를 염두에 두고 설계되었기 떄문에 만약 넘파이가 데이터 복사를 남발한다면 성능과 메모리 문제에 마주치게 될 것이다.
arr2d = np.array([[1,2,3], [4,5,6], [7,8,9]])
arr2d[2]
array([7, 8, 9])
arr2d[0][2]
3
arr2d[0, 2]
3
  • 배열을 복사하고자 할 떄는 다음과 같이 코드를 사용하자.
arr3d = np.array([[[1,2,3], [4,5,6]],[[7,8,9],[10,11,12]]])
arr3d
array([[[ 1,  2,  3],
        [ 4,  5,  6]],

       [[ 7,  8,  9],
        [10, 11, 12]]])
arr3d.shape
(2, 2, 3)
old_values = arr3d.copy()
old_values
array([[[ 1,  2,  3],
        [ 4,  5,  6]],

       [[ 7,  8,  9],
        [10, 11, 12]]])

불리언 값으로 선택하기?

names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
data = np.random.randn(7,4)
names
array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'], dtype='<U4')
data
array([[ 0.64970248, -0.46226598,  0.21661209, -0.69074799],
       [-0.95037364,  2.11594096,  0.61363522,  0.92437664],
       [ 0.73387692,  1.55822528,  0.97945432, -0.98023047],
       [ 0.5109838 ,  0.97557105,  0.19437312,  0.27851104],
       [-0.61899306, -1.20616555,  0.66466932,  0.15551166],
       [-0.68280328,  0.28284084,  0.17623186, -1.35898851],
       [-0.49745574,  0.43597481,  0.47123873,  0.1581405 ]])
  • == 비교연산을 사용하면 넘파이 배열을 대상으로 불리언 배열을 반환한다.
names == 'Bob'
array([ True, False, False,  True, False, False, False])
  • 이 불리열 배열을 통해 배열의 색인으로 사용할 수도 있다.
data[names=='Bob']
array([[ 0.64970248, -0.46226598,  0.21661209, -0.69074799],
       [ 0.5109838 ,  0.97557105,  0.19437312,  0.27851104]])
data[data<0]
array([-0.46226598, -0.69074799, -0.95037364, -0.98023047, -0.61899306,
       -1.20616555, -0.68280328, -1.35898851, -0.49745574])

팬시 색인

  • 정수 배열을 사용한 색인을 설명하기 위해 넘파이에서 차용한 단어입니다.
arr = np.empty((8, 4))
for i in range(8):
    arr[i] = i
arr
array([[0., 0., 0., 0.],
       [1., 1., 1., 1.],
       [2., 2., 2., 2.],
       [3., 3., 3., 3.],
       [4., 4., 4., 4.],
       [5., 5., 5., 5.],
       [6., 6., 6., 6.],
       [7., 7., 7., 7.]])
  • 특정한 순서로 (Row)행을 선택하고 싶다면, 원하는 순서가 명시된 정수가 담긴 ndarray나 리스트를 넘기면된다.
arr[[4, 3, 0, 6]]
array([[4., 4., 4., 4.],
       [3., 3., 3., 3.],
       [0., 0., 0., 0.],
       [6., 6., 6., 6.]])

배열 전치와 축 바꾸기

  • 배열 전치는 데이터를 복사하지 않고 데이터의 모양이 바뀐 뷰를 반환하는 특별한 기능이다.
  • ndarray는 transpose 메서드와 T라는 이름의 특수한 속성을 가지고 있다.
arr = np.arange(15).reshape((3, 5))
arr
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14]])
arr.T
array([[ 0,  5, 10],
       [ 1,  6, 11],
       [ 2,  7, 12],
       [ 3,  8, 13],
       [ 4,  9, 14]])
arr = np.random.randn(6, 3)
arr
array([[-0.83106776,  1.9785927 , -0.1639391 ],
       [-0.96238735,  0.52149878,  1.03238923],
       [-0.60844264, -1.09199097, -3.0480151 ],
       [-0.15331207, -1.33649829, -1.14063182],
       [-0.15917109, -1.66588211, -1.15287215],
       [ 0.25132811,  0.16884861, -0.13960122]])

행렬의 내적!

np.dot(arr.T, arr)
array([[ 2.09907132, -0.96931657,  1.32051937],
       [-0.96931657,  9.96913509,  6.96385609],
       [ 1.32051937,  6.96385609, 13.03274326]])
arr = np.arange(16).reshape((2,2,4))
arr
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7]],

       [[ 8,  9, 10, 11],
        [12, 13, 14, 15]]])
arr.transpose((1, 0, 2))
array([[[ 0,  1,  2,  3],
        [ 8,  9, 10, 11]],

       [[ 4,  5,  6,  7],
        [12, 13, 14, 15]]])
arr
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7]],

       [[ 8,  9, 10, 11],
        [12, 13, 14, 15]]])
arr.swapaxes(1, 2)
array([[[ 0,  4],
        [ 1,  5],
        [ 2,  6],
        [ 3,  7]],

       [[ 8, 12],
        [ 9, 13],
        [10, 14],
        [11, 15]]])
arr.swapaxes(1, 2).shape
(2, 4, 2)

유니버설 함수 : 배열의 각 원소를 빠르게 처리하는 함수

  • ufunc라고 불린다.
  • ndarray 안에 있는 데이터 원소별로 연산을 수행하는 함수다.
  • 하나 이상의 스칼라값을 받아서 하나 이상의 스칼라 결괏값을 반환하는 간단한 함수를 고속으로 수행할 수 있는 벡터화된 래퍼 함수이다.
arr = np.arange(10)
arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
np.sqrt(arr)
array([0.        , 1.        , 1.41421356, 1.73205081, 2.        ,
       2.23606798, 2.44948974, 2.64575131, 2.82842712, 3.        ])
np.exp(arr)
array([1.00000000e+00, 2.71828183e+00, 7.38905610e+00, 2.00855369e+01,
       5.45981500e+01, 1.48413159e+02, 4.03428793e+02, 1.09663316e+03,
       2.98095799e+03, 8.10308393e+03])
  • 1개의 인자를 취해서 단일 스칼라를 반환하는 함수 = 단한 유니버설 함수
  • 2개의 인자를 취해서 단일 배열을 반환하는 함수 = 이항 유니버셜 함수
x = np.random.randn(8)
y = np.random.randn(8)
x
array([-2.99702555, -1.96604291,  1.65472109,  1.31742047,  0.36986162,
       -0.76923293, -0.08517917, -0.01027089])
y
array([-0.3155695 ,  0.14878357, -1.00018072, -0.72599019,  1.0683331 ,
        0.68041058, -0.48463785,  1.84850971])
np.maximum(x, y)
array([-0.3155695 ,  0.14878357,  1.65472109,  1.31742047,  1.0683331 ,
        0.68041058, -0.08517917,  1.84850971])
  • 여러 개의 배열을 반환하는 유니버설 함수도 있다. modf는 파이썬 내장 함수인 divmod의 벡터화 버전
  • 분수를 받아서 몫과 나머지를 함께 반환한다.
arr = np.random.randn(7) * 5
arr
array([-4.7163141 ,  1.23722633, -0.88310779,  1.50559534, -9.25547766,
        4.98500097,  4.09266686])
remainder, whole_part = np.modf(arr)
remainder
array([-0.7163141 ,  0.23722633, -0.88310779,  0.50559534, -0.25547766,
        0.98500097,  0.09266686])
whole_part
array([-4.,  1., -0.,  1., -9.,  4.,  4.])

배열을 이용한 배열 지향 프로그래밍

  • Numpy 배열을 사용하면 반복문 없이 간결한 배열 연산을 사용해 많은 종류의 데이터 처리 작업을 할 수 있다.
  • 배열 연산을 사용해서 반복문을 명시적으로 제거하는 기법을 벡터화라고 한다.
points = np.arange(-5, 5, 0.01)
# meshgrid함수는 두 개의 1차원 배열을 받아서 가능한 모든 (x,y)짝을 만들 수 있는 2차원 배열 두개를 반환.
xs, ys = np.meshgrid(points, points)
ys
array([[-5.  , -5.  , -5.  , ..., -5.  , -5.  , -5.  ],
       [-4.99, -4.99, -4.99, ..., -4.99, -4.99, -4.99],
       [-4.98, -4.98, -4.98, ..., -4.98, -4.98, -4.98],
       ...,
       [ 4.97,  4.97,  4.97, ...,  4.97,  4.97,  4.97],
       [ 4.98,  4.98,  4.98, ...,  4.98,  4.98,  4.98],
       [ 4.99,  4.99,  4.99, ...,  4.99,  4.99,  4.99]])
z = np.sqrt(xs **2 + ys ** 2)
z
array([[7.07106781, 7.06400028, 7.05693985, ..., 7.04988652, 7.05693985,
        7.06400028],
       [7.06400028, 7.05692568, 7.04985815, ..., 7.04279774, 7.04985815,
        7.05692568],
       [7.05693985, 7.04985815, 7.04278354, ..., 7.03571603, 7.04278354,
        7.04985815],
       ...,
       [7.04988652, 7.04279774, 7.03571603, ..., 7.0286414 , 7.03571603,
        7.04279774],
       [7.05693985, 7.04985815, 7.04278354, ..., 7.03571603, 7.04278354,
        7.04985815],
       [7.06400028, 7.05692568, 7.04985815, ..., 7.04279774, 7.04985815,
        7.05692568]])

배열 연산으로 조건절 표현하기

xarr = np.array([1.1, 1.2, 1.3, 1.4, 1.5])
yarr = np.array([2.1, 2.2, 2.3, 2.4, 2.5])
cond = np.array([True, False, True, True, False])
# numpy.where 함수는 x if 조건 else y 같은 삼항식의 벡터화된 버전이다.
result = [(x if c else y) for x, y, c in zip(xarr, yarr, cond)]
result = np.where(cond, xarr, yarr)
result
array([1.1, 2.2, 1.3, 1.4, 2.5])
arr = np.random.randn(4, 4)
arr
array([[ 0.06651596, -1.3590567 , -1.2165859 ,  0.4260299 ],
       [ 0.85873081,  1.40853078,  0.13979622,  0.19865918],
       [-1.04369912, -0.31363141,  0.66667987,  0.41386212],
       [ 0.53607042,  0.66933874,  0.15702505, -0.76718912]])
arr > 0
array([[ True, False, False,  True],
       [ True,  True,  True,  True],
       [False, False,  True,  True],
       [ True,  True,  True, False]])
np.where(arr>0, 2, -2)
array([[ 2, -2, -2,  2],
       [ 2,  2,  2,  2],
       [-2, -2,  2,  2],
       [ 2,  2,  2, -2]])

수학 메서드와 통계 메서드

  • 배열 전체 혹은 배열에서 한 축을 따르는 자료에 대한 통계를 계산하는 수학 함수는 배열 메서드로 사용할 수 있다.
  • 전체의 합, 평균, 표준편차는 넘파이의 최상위 함수를 이용하거나 배열의 인스턴스 메서드를 사용해서 구할 수 있다.
arr = np.random.randn(5, 4)
arr
array([[ 0.09010645,  0.78384464, -0.64005509,  0.29694192],
       [ 0.16335734,  1.64485338,  0.11131861,  0.66706878],
       [ 0.06236939, -0.64491206,  0.81633166,  0.85329599],
       [-0.40561694,  1.20175951,  1.21824149,  0.3095776 ],
       [-0.55408664,  1.88614613, -0.43434863, -0.26785509]])
arr.mean()
0.35791692251129537
np.mean(arr)
0.35791692251129537
arr.sum()
7.158338450225908
arr.mean(axis=1) # 해당 axis에 대한 통계를 계산하고, 차수 낮은 배열을 반환한다.
array([0.13270948, 0.64664953, 0.27177124, 0.58099041, 0.15746394])
arr.sum(axis=0)
array([-0.6438704 ,  4.8716916 ,  1.07148805,  1.85902921])

집합 관련 함수

  • 배열 내에서 중복된 원소를 제거하고 남은 원소를 정렬된 형태로 반환하는 np.unique
names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
np.unique(names)
array(['Bob', 'Joe', 'Will'], dtype='<U4')

배열 데이터의 파일 입출력

  • 넘파이는 디스크에서 텍스트나 바이너리 형식의 데이터를 불러오거나 저장할 수 있다.
  • 내장 이진 형식을 살펴본다.
  • 많은 사람들이 텍스트나 표 형식의 데이터는 판다스를 통해 처리한다.
arr = np.arange(10)
np.save('some_array', arr)
np.load('some_array.npy')
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
# np.savez함수로 여러 개의 배열을 압축된 형식으로 저장할 수 있다.
np.savez('array_archive.npz', a=arr, b=arr)
arch = np.load('array_archive.npz')
arch['b']
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
반응형