데이터 분석을 위한 NumPy의 기초

작성자 : 원민

1. NumPy

NumPy란

  • NumPy란?
    Numerical Python의 줄임말로, 파이썬에서 산술 계산을 위한 가장 중요한 필수 패키지 중 하나입니다. 과학 계산을 위한 대부분의 패키지는 NumPy의 배열 객체를 데이터 교환을 위한 공통 언어처럼 사용합니다.
  • NumPy에서 제공하는것
    • 효율적인 다차원 배열인 ndarray는 빠른 배열 계산과 유연한 브로드캐스팅 기능을 제공합니다.
    • 반복문을 작성할 필요 없이 전체 데이터 배열을 빠르게 계산할 수 있는 표준 수학 함수입니다.
    • 배열 데이터를 디스크에 쓰거나 읽을 수 있는 도구와 메모리에 적재된 파일을 다루는 도구입니다.
    • 선형대수, 난수 생성기, 푸리에 변환 기능입니다.
    • C, C++, 포트란으로 작성한 코드를 연결할 수 있는 C API 입니다.
  • 장점
    대용량 데이터 배열을 효율적으로 다룰 수 있도록 설계합니다.

2. NumPy ndarray : 다차원 배열 객체

2.1 ndarray 생성하기

import numpy as np
data1 = [6, 7.5, 8, 0, 1]
arr1 = np.array(data1)
arr1
array([6. , 7.5 , 8. , 0. , 1. ])

같은 길이를 가지는 리스트를 내포하고 있는 순차 데이터는 다차원 배열로 변환이 가능합니다.

data2 = [[1, 2, 3, 4], [5, 6, 7, 8]]
arr2 = np.array(data2)
arr2
array([[1, 2, 3, 4],
    [5, 6, 7, 8]])

또한 np.array는 새로운 배열을 생성하기 위한 여러 함수를 가지고 있는데, 예를 들어 zeros와 ones는 주어진 길이나 모양에 각각 0과 1이 들어있는 배열을 생성합니다. empty함수는 초기화되지 않은 배열을 생성합니다.

 np.zeros(10)
 array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
 np.zeros((3,6))
 array([[0., 0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0., 0.]])
np.empty((2, 3, 2))
array([[[0., 0.],
        [0., 0.],
        [0., 0.]],
       [[0., 0.],
        [0., 0.],
        [0., 0.]]])

2.2 NumPy 배열의 산술 연산

배열의 중요한 특징은 for문을 작성하지 않고 데이터를 일괄 처리할 수 있다는 것입니다. 이를 벡터화라고 하는데, 같은 크기의 배열 간의 산술 연산은 배열의 각 원소 단위로 적용됩니다.

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.3333],
       [0.25, 0.2, 0.1667.]])

2.3 색인과 슬라이싱 기초

색인은 데이터의 부분집합이나 개별 요소를 선택하기 위한 수많은 방법이 존재합니다. 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처럼 배열 조각에 스칼라값을 대입하면 12 선택 영역 전체로 전파됩니다.
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])

단순히 [:]로 슬라이스를 하면 배열의 모든 값을 할당합니다.

arr_slice[:] = 64
arr
array([0, 1, 2, 3, 4, 64, 64, 64, 8, 9])

2차원 배열에서 각 색인에 해당하는 요소는 스칼라값이 아니라 1차원 배열입니다.

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[0]은 2X3 크기의 배열입니다.

arr3d[0]
array([[1, 2, 3],
       [4, 5, 6]])

arr3d[0]에는 스칼라값과 배열 모두 대입할 수 있습니다.

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

arr[1, 0]은 (1, 0)으로 색인되는 1차원 배열과 그 값을 반환합니다.

arr3d[1, 0]
array([7, 8, 9])

arr2d[:2]는 ‘arr2d의 시작부터 두 번째 로우까지의 선택’이라고 이해하면 됩니다.

arr2d[:2]
array([[1, 2, 3],
       [4, 5, 6]])

색인을 여러개 넘쳐서 다차원을 슬라이싱하는 것도 가능합니다.

arr2d[:2, 1:]
array([[2, 3],
       [5, 6]])

정수 색인과 슬라이스를 함께 사용하면 한 차원 낮은 슬라이스를 얻을 수 있습니다.

arr2d[1, :2]
array([4, 5])

2.4 불리언값으로 선택하기

중복된 이름이 포함된 배열이 있다고 하고 numpy.random 모듈에 있는 randn함수를 사용해서 임의의 표준 정규 분포 데이터를 생성합니다.

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.0929, 0.2817, 0.769, 1.2464],
       [1.0072, -1.2962, 0.275, 0.2289],
       [1.3529, 0.8864, -2.0016, -0.3718],
       [1.669, -0.4386, -0.5397, 0.477],
       [3.2489, -1.0212, -0.5771, 0.1241]
       [0.3026, 0.5238, 0.0009, 1.3438],
       [-0.7135, -0.8312, -2.3702, -1.8608]])

각각의 이름은 data배열의 각 로우에 대응한다고 가정합시다.

names == 'Bob'
array([True, False, False, True, False, False, False], dtype=bool)

0번째와 3번째만 True임을 알 수 있습니다. 이 불리언 배열은 배열의 색인으로 사용할 수 있습니다.

data[names == 'Bob']

0번째와 3번째만 출력되는 것을 알 수 있습니다.

array([[0.0929, 0.2817, 0.769, 1.2464],
       [1.669, -0.4386, -0.5397, 0.477]])

names == ‘Bob’인 로우에서 2: 컬럼을 선택하면 다음과 같습니다.

data[names == 'Bob', 2:]
array([[0.769, 1.2464],
       [-0.5397, 0.477]])

반대로 ‘Bob’이 아닌 요소들을 선택하려면 !=연산자를 사용하거나 ~를 사용하여 조건절을 부인하면 됩니다.

cond = names == 'Bob'
data[~cond]
array([[1.0072, -1.2962, 0.275, 0.2289],
       [1.3529, 0.8864, -2.0016, -0.3718],
       [3.2489, -1.0212, -0.5771, 0.1241],
       [0.3026, 0.5238, 0.0009, 1.3438],
       [-0.7135, -0.8312, -2.3702, -1.8608]])
또한 &(and)나 (or) 같은 논리 연산자를 사용한 여러 개의 불리언 조건을 사용하면 여러개를 선택할 수 있습니다.

2.5 팬시 색인

팬시 색인은 정수 배열을 사용한 색인을 설명하기 위해 NumPy에서 차용한 단어입니다.

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.]])

특정한 순서로 로우를 선택하고 싶다면 그냥 원하는 순서가 명시된 정수가 담긴 ndarray나 리스트를 넘기면 됩니다.

arr[[4, 3, 0, 6]]
array([[4., 4., 4., 4.],
       [3., 3., 3., 3.],
       [0., 0., 0., 0.],
       [6., 6., 6., 6.]])

2.6 배열 전치와 축 바꾸기

배열 전치는 데이터를 복사하지 않고 데이터의 모양이 바뀐 뷰를 반환하는 특별한 기능입니다.

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]])

행렬의 내적은 np.dot을 이용해서 구할 수 있습니다.

arr = np.random.randn(6, 3)
arr
array([[-0.8608, 0.5601, -1.2659],
       [0.1198, -1.0635, 0.3329],
       [-2.3594, -0.1995, -1.542],
       [-0.9707, -1.307, 0.2863],
       [0.378, -0.7539, 0.3313],
       [1.3497, 0.0699, 0.2467]])
np.dot(arr.T, arr)
array([[9.2291, 0.9394, 4.948],
       [0.9394, 3.7662, -1.3622],
       [4.948, -1.3622, 4.3437]])

다차원 배열의 경우 transpose메서드는 튜플로 축 번호를 받아서 치환합니다.

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]]])

ndarray에는 swapaxes라는 메서드가 있는데 두 개의 축 번호를 받아서 배열을 뒤바꿉니다.

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]]])

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

유니버설 함수는 하나 이상의 스칼라값을 받아서 하나 이상의 스칼라 결과값을 반환하는 간단한 함수를 고속으로 수행할 수 있는 벡터화된 래퍼함수입니다. sqrtexp같은 간단한 변형을 전체 원소에 적용할 수 있습니다.

arr = np.arange(10)
arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
np.sqrt(arr)
array([0., 1., 1.4142, 1.7321, 2., 2.2361, 2.4495, 2.6458, 2.8284, 3.])
np.exp(arr)
array([1., 2.7183, 7.3891, 20.0855, 54.5982, 148.4132, 403.4288, 1096.6332, 2980.958, 8103.0839])

Categories:

Analytics