12. 넘파이 어레이#

넘파이 라이브러리가 제공하는 다차원 어레이 자료형을 소개한다.

주요 내용

  • 넘파이 어레이 자료형

  • 다차원 어레이 소개

  • 어레이 기초 연산 소개

기본 설정

numpy 모듈에 대한 기본 설정을 지정한다.

# 넘파이
import numpy as np

# 램덤 시드
np.random.seed(12345)

# 어레이에 포함된 부동소수점들의 표현 정확도 지정
np.set_printoptions(precision=4, suppress=True)

12.1. 넘파이란?#

넘파이numpy는 NUMerical PYthon의 줄임말이며, 파이썬 데이터 과학에서 가장 중요한 도구를 제공하는 라이브러리이다. 넘파이가 제공하는 가장 중요한 요소는 아래 두 가지이다.

  • 다차원 어레이(배열)

  • 메모리 효율적이며 빠른 어레이 연산

넘파이의 기능을 잘 이해한다면 이어서 다룰 판다스pandas 라이브러리가 지원하는 데이터프레임Dataframe 자료형의 기능 또한 쉽게 이해할 수 있다.

리스트 연산과 넘파이 어레이 연산의 속도 차이를 아래 코드가 보여준다. 아래 코드는 0부터 1천만까지의 숫자를 각각 두 배하는 연산에 필요한 시간을 측정한다. 결과적으로 넘파이 어레이를 이용한 연산이 몇 십배 정도 빠르다.

%time 매직 커맨드

%time은 코드 실행시간을 측정하는 IPython의 매직 커맨드 중의 하나이며, 파이썬 자체의 기능이 아니다.

my_array = np.arange(10000001)
my_list = list(range(10000001))
%timeit for _ in range(10): my_array2 = my_array * 2
150 ms ± 2.29 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit for _ in range(10): my_list2 = [x * 2 for x in my_list]
4.1 s ± 28 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

이전 코드에서 사용된 np.arange() 함수는 range() 함수와 유사하게 작동하지만 반환값은 항상 넘파이 어레이다.

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

넘퍼이 어레이 자료형의 연산은 기본적으로 항목별로 실행된다. 따라서 어레이와 정수의 곱은 어레이의 각 항목에 정수를 곱해서 새로운 어레이를 생성한다.

np.arange(10) * 2
array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])

이제부터 넘파이에 대해 필수적으로 알아 두어야만 하는 내용들을 정리하며 살펴본다.

12.2. 다차원 어레이#

넘파이 어레이는 리스트와는 달리 포함된 모양, 항목의 자료형 등에 대한 자료형 정보도 포함한다. 모양에 따라 차원이 결정되며, 모든 항목은 동일한 자료형을 갖는다. 가장 많이 활용되는 어레이의 차원은 다음과 같다.

12.2.1. 1차원 어레이#

리스트와 유사한 모양을 가지며, 리스트와 튜플 등에 np.array() 함수를 적용하여 생성할 수 있다. 1차원 어레이는 벡터vector로도 불리며, 한 개의 axis을 갖는다.

  • 리스트 활용

data1 = [6, 7.5, 8, 0, 1]
arr1 = np.array(data1)
arr1
array([6. , 7.5, 8. , 0. , 1. ])
  • 튜플 활용

data2 = (6, 7.5, 8, 0, 1)
arr1 = np.array(data2)
arr1
array([6. , 7.5, 8. , 0. , 1. ])

ndarray 자료형

넘파이 어레이 자체의 자료형은 ndarray이다.

type(arr1)
numpy.ndarray

참고로 리스트의 자료형은 list다.

type(data1)
list

12.2.2. 2차원 어레이#

동일한 길이의 리스트를 항목으로 갖는 중첩 리스트를 2차원 어레이로 변환할 수 있다. 따라서 2차원 어레이는 어레이의 모든 항목은 동일한 크기의 1차원 어레이이다. 2차원 어레이는 행렬matrix로도 불리며, rowcolumn 두 개의 축을 갖는다.

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

shape 속성

어레이 객체의 shape 속성은 생성된 어레이의 모양을 저장한다. 예를 들어 위 2차원 어레이의 모양은 행과 열의 개수로 구성된 튜플 (2, 4)이다.

arr2.shape
(2, 4)

1차원 어레이, 즉 벡터의 모양은 벡터에 포함된 항목의 개수로 구성된 길이가 1인 튜플로 표현된다.

arr1.shape
(5,)

ndim 속성

차원은 ndim 속성에 저장되며, shape에 저정된 튜플의 길이와 동일하다.

arr2.ndim
2

1차원 어레이의 차원은 1이다.

arr1.ndim
1

12.2.3. 3차원 어레이#

3차원 이상의 어레이는 처음에는 매우 생소하게 다가올 수 있다. 하지만 이미지 데이터 분석에서 가장 기본으로 사용되는 차원이기에 3차원 어레이에 익숙해져야 한다.

(n, m, p) 모양의 3차원 어레이를 이해하는 두 가지 방법은 다음과 같다.

첫째, 바둑판을 (n, m) 크기의 격자로 나누고 각각의 칸에 길이가 p 인 1차원 어레이가 위치하는 것으로 이해한다. 예를 들어, 아래 그림은 작은 컬러 고양이 사진을 (5, 5, 3) 모양의 어레이로 표현한 방식을 보여준다. 즉, 모든 항목이 (R, G, B) 색상 정보를 담은 길이가 3인 1차원 어레이인 2차원 어레이로 표현되었다.

둘째, (m, p) 모양의 2차원 어레이 n 개를 항목으로 갖는 1차원 어레이로 이해한다. 아래 그림은 (3, 2) 모양의 2차원 어레이를 4개 포함한 1차원 어레이로 표현된 3차원 어레이로 이해할 수 있다.

12.2.4. 어레이 객체 생성 함수#

배열을 쉽게 생성할 수 있는 함수는 다음과 같으며, 각 함수의 기능은 numpy cheat sheet를 참고한다.

  • np.array()

  • np.arange()

  • np.diag()

  • np.empty()

  • np.ones()

  • np.zeros()

np.array() 함수는 앞서 소개하였으며 추가로 np.zeros(), np.arange() 두 함수를 간략하게 다룬다. 나머지 함수들은 연습문제를 통해 학습하도록 한다.

zeros() 함수

0으로 이루어진 어레이를 생성한다. 1차원인 경우 정수를 인자로 사용한다.

np.zeros(10)
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])

2차원부터는 정수들의 튜플로 모양을 지정한다.

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

       [[0., 0.],
        [0., 0.],
        [0., 0.]],

       [[0., 0.],
        [0., 0.],
        [0., 0.]],

       [[0., 0.],
        [0., 0.],
        [0., 0.]]])

arange() 함수

range() 함수와 유사하게 작동하며 부동소수점 스텝도 지원한다.

np.arange(15)
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14])
np.arange(0, 1, 0.1)
array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])

12.3. dtype 종류#

넘파이 어레이의 dtype 속성은 어레이 항목의 자료형을 담고 있으며, 파이썬 표준 라이브러리에서 제공하는 int, float, str, bool 등을 보다 세분화시킨 자료형을 제공한다.

12.3.1. 기본 dtype#

여기서는 세분화된 자료형을 일일이 설명하기 보다는 예제를 이용하여 세분화된 자료형의 형식을 살펴본다. 자료형 세분화는 주로 자료형의 객체가 사용하는 메모리 용량을 제한하는 형식으로 이루어진다. 이를 통해 보다 메모리 효율적이며 빠른 계산이 가능해졌다.

자료형

자료형 코드

설명

int8 / uint8

‘i1’ / ‘u1’

signed / unsigned 8 비트 정수

int16 / uint16

‘i2’ / ‘u2’

signed / unsigned 16 비트 정수

int32 / uint32

‘i4’ / ‘u4’

signed / unsigned 32 비트 정수

int64 / uint64

‘i8’ / ‘u8’

signed / unsigned 64 비트 정수

float16

‘f2’

16비트 부동소수점

float32

‘f4’ 또는 ‘f’

32비트 부동소수점

float64

‘f8’ 또는 ‘d’

64비트 부동소수점

float128

‘f16’ 또는 ‘g’

128비트 부동소수점

bool

‘?’

부울 값

object

‘O’

임의의 파이썬 객체

string_

‘S’

고정 길이 아스키 문자열 / 예) S8, S10

unicode_

‘U’

고정 길이 유니코드 문자열 / 예) U8, U10

float64 자료형

arr1 = np.array([1, 2, 3], dtype=np.float64)

arr1.dtype
dtype('float64')
arr1 = np.array([1, 2, 3], dtype='f8')

arr1.dtype
dtype('float64')

int32 자료형

arr2 = np.array([1, 2, 3], dtype=np.int32)

arr2.dtype
dtype('int32')
arr2 = np.array([1, 2, 3], dtype='i4')

arr2.dtype
dtype('int32')

문자열 자료형

문자열은 기본적으로 유니코드로 처리되며 크기는 최장 길이의 문자열에 맞춰 결정된다.

np.array(['python', 'data']).dtype
dtype('<U6')
numeric_strings = np.array(['1.25', '-9.6', '42'])
numeric_strings.dtype
dtype('<U4')

bool 자료형

np.array([True, False], dtype='?').dtype
dtype('bool')

12.3.2. 형변환: astype() 메서드#

astype() 메서드를 이용하여 dtype을 변경할 수 있다. 즉, 항목의 자료형을 강제로 변환시킨다.

  • int 자료형을 float 자료형으로 형변환하기

arr = np.array([1, 2, 3, 4, 5])
arr.dtype
dtype('int64')
float_arr = arr.astype(np.float64)
float_arr.dtype
dtype('float64')
  • float 자료형을 int 자료형으로 형변환하기

    • 소수점 이하는 버림.

arr = np.array([3.7, -1.2, -2.6, 0.5, 12.9, 10.1])
arr
array([ 3.7, -1.2, -2.6,  0.5, 12.9, 10.1])
arr.astype(np.int32)
array([ 3, -1, -2,  0, 12, 10], dtype=int32)
  • 숫자 형식의 문자열을 숫자로 형변환하기: 문자열 자료형의 크기는 넘파이가 알아서 정함

numeric_strings = np.array(['1.25', '-9.6', '42'])
numeric_strings.dtype
dtype('<U4')
numeric_strings.astype(float)
array([ 1.25, -9.6 , 42.  ])
numeric_strings2 = np.array(['1.25345', '-9.673811345', '42'], dtype=np.string_)
numeric_strings2.dtype
dtype('S12')
  • 부동소수점으로 형변환하면 지정된 정밀도에 따라 소수점 이하를 자른다.

numeric_strings2.astype(float)
array([ 1.2534, -9.6738, 42.    ])

주의사항: 앞서 부동소수점 정밀도를 4로 지정했기 때문에 어레이 항목은 모두 소수점 이하 네 자리까지만 보여준다.

np.set_printoptions(precision=4, suppress=True)

부동소수점 정밀도를 변경하면 그에 따라 다르게 결정된다.

np.set_printoptions(precision=6, suppress=True)
numeric_strings2.astype(float)
array([ 1.25345 , -9.673811, 42.      ])

12.4. 어레이 연산#

넘파이 어레이 연산은 기본적으로 항목별로 이루어진다. 즉, 지정된 연산을 동일한 위치의 항목끼리 실행하여 새로운, 동일한 모양의 어레이를 생성한다.

arr = np.array([[1., 2., 3.], [4., 5., 6.]])
arr
array([[1., 2., 3.],
       [4., 5., 6.]])
arr2 = np.array([[3., 2., 1.], [4., 2., 12.]])
arr2
array([[ 3.,  2.,  1.],
       [ 4.,  2., 12.]])

덧셈

arr + arr2
array([[ 4.,  4.,  4.],
       [ 8.,  7., 18.]])

숫자와의 연산은 모든 항목에 동일한 값을 사용한다.

arr + 2.4
array([[3.4, 4.4, 5.4],
       [6.4, 7.4, 8.4]])

행별로 다른 일정한 값을 더하려면 다음과 같이 1개의 열로 구성된 2차원 어레이를 사용하면 된다. 아래 두 코드는 행별로 각각 4와 8을 더한다.

arr3 = np.array([[4], [8]])
arr3
array([[4],
       [8]])
arr + arr3
array([[ 5.,  6.,  7.],
       [12., 13., 14.]])

뺄셈

arr - arr2
array([[-2.,  0.,  2.],
       [ 0.,  3., -6.]])
3.78 - arr
array([[ 2.78,  1.78,  0.78],
       [-0.22, -1.22, -2.22]])

아래 코드는 행별로 각각 4와 8을 뺀다.

arr - arr3
array([[-3., -2., -1.],
       [-4., -3., -2.]])

곱셈

나눗셈 또한 항목별로 연산이 이루어진다. 따라서 0이 항목으로 포함되면 오류가 발생한다.

arr * arr2
array([[ 3.,  4.,  3.],
       [16., 10., 72.]])
1 * arr
array([[1., 2., 3.],
       [4., 5., 6.]])
arr * 3.2
array([[ 3.2,  6.4,  9.6],
       [12.8, 16. , 19.2]])

아래 코드는 행별로 각각 4와 8을 곱한다.

arr * arr3
array([[ 4.,  8., 12.],
       [32., 40., 48.]])

나눗셈

나눗셈 또한 항목별로 연산이 이루어진다. 따라서 0이 항목으로 포함되면 오류가 발생한다.

arr / arr2
array([[0.333333, 1.      , 3.      ],
       [1.      , 2.5     , 0.5     ]])
1 / arr
array([[1.      , 0.5     , 0.333333],
       [0.25    , 0.2     , 0.166667]])
arr / 3.2
array([[0.3125, 0.625 , 0.9375],
       [1.25  , 1.5625, 1.875 ]])

아래 코드는 행별로 각각 4와 8을 나눈다.

arr / arr3
array([[0.25 , 0.5  , 0.75 ],
       [0.5  , 0.625, 0.75 ]])

거듭제곱(지수승)

arr ** arr2
array([[1.000000e+00, 4.000000e+00, 3.000000e+00],
       [2.560000e+02, 2.500000e+01, 2.176782e+09]])
2 ** arr
array([[ 2.,  4.,  8.],
       [16., 32., 64.]])
arr ** 0.5
array([[1.      , 1.414214, 1.732051],
       [2.      , 2.236068, 2.44949 ]])

아래 코드는 행별로 각각 4승과 8승을 계산한다.

arr ** arr3
array([[      1.,      16.,      81.],
       [  65536.,  390625., 1679616.]])

비교 연산

arr2 > arr
array([[ True, False, False],
       [False, False,  True]])
arr2 <= arr
array([[False,  True,  True],
       [ True,  True, False]])
1.2 < arr
array([[False,  True,  True],
       [ True,  True,  True]])
1.2 >= arr2
array([[False, False,  True],
       [False, False, False]])
arr == arr
array([[ True,  True,  True],
       [ True,  True,  True]])
arr != arr2
array([[ True, False,  True],
       [False,  True,  True]])

논리 연산

사용 가능한 논리 연산은 아래 세 가지이다.

  • ~: 부정(not) 연산자

  • &: 논리곱(and) 연산자

  • |: 논리합(or) 연산자

~(arr == arr)
array([[False, False, False],
       [False, False, False]])
(arr == arr) & (arr2 == arr2)
array([[ True,  True,  True],
       [ True,  True,  True]])
~(arr == arr) | (arr2 != arr)
array([[ True, False,  True],
       [False,  True,  True]])

12.5. 어레이 변형: reshape() 메서드#

주어진 어레이의 항목을 그대로 유지하면서 모양만 변형시키는 방식과 활용법을 소개한다.

reshape() 메서드

reshape() 메서드를 활용하여 주어진 어레이의 모양을 원하는 대로 변형한다. 단, 항목의 수가 변하지 않도록 모양을 지정해야 한다. 예를 들어, 길이가 8인 1차원 어레이가 다음과 같다.

arr = np.arange(8)
arr
array([0, 1, 2, 3, 4, 5, 6, 7])

이제 (4, 2) 모양의 2차원 어레이로 모양을 변형할 수 있다.

arr.reshape((4, 2))
array([[0, 1],
       [2, 3],
       [4, 5],
       [6, 7]])

항목의 수만 같으면 임의의 차원의 어레이를 임의의 차원의 어레이로 변형시킬 수 있다.

arr.reshape((4, 2)).reshape((2, 2, 2))
array([[[0, 1],
        [2, 3]],

       [[4, 5],
        [6, 7]]])

-1의 역할

어레의 모양을 지정할 때 튜플의 특정 위치에 -1을 사용할 수 있다. 그러면 그 위치의 값은 튜플의 다른 항목의 정보를 이용하여 자동 결정된다. 예를 들어, 아래 코드에서 -1은 4를 의미한다. 이유는 20개의 항목을 5개의 행으로 이루어진 2차원 어레이로 지정하려면 열은 4개 있어야 하기 때문이다.

arr = np.arange(20)
arr.reshape((5, -1))
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15],
       [16, 17, 18, 19]])
arr.reshape((5, 4))
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15],
       [16, 17, 18, 19]])

동일한 이유로 아래에서 -1은 5를 의미한다.

arr.reshape((2, -1, 2))
array([[[ 0,  1],
        [ 2,  3],
        [ 4,  5],
        [ 6,  7],
        [ 8,  9]],

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

       [[10, 11],
        [12, 13],
        [14, 15],
        [16, 17],
        [18, 19]]])

예제

아래 모양의 2차원 어레이를 생성하라.

array([[ 0,  1,  2,  3,  4,  5],
       [10, 11, 12, 13, 14, 15],
       [20, 21, 22, 23, 24, 25],
       [30, 31, 32, 33, 34, 35],
       [40, 41, 42, 43, 44, 45],
       [50, 51, 52, 53, 54, 55]])

어레이의 모양이 (6, 6)이기에 우선 np.arange(36)reshape() 메서드를 이용하여 아래 어레이를 생성한다.

arr_1 = np.arange(36).reshape(6, 6)
arr_1
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, 25, 26, 27, 28, 29],
       [30, 31, 32, 33, 34, 35]])

이제 0행부터 차례대로 0, 4, 8, 12, 16, 20을 더해야 하기에 언급된 값들을 항목으로 갖는 (6, 1) 모양의 어레이를 다음과 같이 생성한다.

arr_2 = np.arange(0, 21, 4).reshape(6, 1)
arr_2
array([[ 0],
       [ 4],
       [ 8],
       [12],
       [16],
       [20]])

이제 두 어레이를 더하면 원하는 2차원 어레이가 생성된다.

arr = arr_1 + arr_2
arr
array([[ 0,  1,  2,  3,  4,  5],
       [10, 11, 12, 13, 14, 15],
       [20, 21, 22, 23, 24, 25],
       [30, 31, 32, 33, 34, 35],
       [40, 41, 42, 43, 44, 45],
       [50, 51, 52, 53, 54, 55]])

12.6. 연습문제#

참고: (실습) 넘파이 어레이