본문 바로가기
데이터 분석/기초

[Python] NumPy 라이브러리 기초 2

by hyeok1235 2022. 11. 24.

 

만약 NumPy 라이브러리를 처음 접한다면 NumPy의 기초가 설명되어 있는 이 글을 참고하시길 바랍니다.

 

 

[Python] NumPy 라이브러리 기초 1

NumPy(Numerical Python)은 오픈소스 파이썬 라이브러리 중 하나로, 다차원 배열과 행렬 구조를 활용해 숫자 데이터를 다루기 쉽게 한다. 다양한 수학적 연산을 지원하는 파이썬 라이브러리인 NumPy는 

hyeok1235.tistory.com


이번 글에서는 NumPy의 배열을 조작하는 방법을 조금 더 소개하고, 배열을 수학적으로 활용할 때 필요한 함수들과 메소드들을 소개하겠습니다.

① 1차원 배열을 2차원 배열로 바꾸기

배열에서 일렬로 나열되어 있는 요소들에 축을 추가해서 기존 배열의 dimension을 증가시킬 수 있습니다(1차원 배열은 2차원 배열로, 2차원 배열로 3차원 배열로). 이때 사용할 수 있는 것은 np.newaxisnp.expand_dims입니다.

>>> a = np.array([1, 2, 3, 4, 5, 6])
>>> a.shape
(6,)
>>> a_row = a[np.newaxis, :]
>>> a_row.shape
(1, 6)
>>> print(a_row)
[[1 2 3 4 5 6]]
>>>
>>> a_col = a[:, np.newaxis]
>>> a_col.shape
(6, 1)
>>> print(a_col)
[[1]
 [2]
 [3]
 [4]
 [5]
 [6]]

a_rownp.newaxis를 1차원에 추가해서 a_row.shape가 (1, 6)이 된 것을 확인할 수 있습니다. a_col은 반대로 np.newaxis를 2차원에 추가해서 a_col.shape가 (6, 1)이 되었습니다.

np.expand_dims를 사용해서도 지정한 위치에 축을 추가할 수 있습니다.

>>> b = np.array([1, 2, 3, 4, 5, 6])
>>> b.shape
(6,)
>>> b_row = np.expand_dims(a, axis=0)
>>> b_row.shape
(1, 6)
>>> b_col = np.expand_dims(a, axis=1)
>>> b_col.shape
(6, 1)

 

② 기존의 배열로부터 새로운 배열 생성하기

i) 2개의 배열을 하나로 합치기
a1a2라는 2차원 배열이 존재한다고 합시다:

>>> a1 = np.array([[1, 1],
...                [2, 2]])
>>> a2 = np.array([[3, 3],
...                [4, 4]])

np.vstack()을 사용해서 세로로 합칠 수 있고, np.hstack()을 사용해서 가로로 합칠 수 있습니다.

>>> np.vstack((a1, a2))
array([[1, 1],
       [2, 2],
       [3, 3],
       [4, 4]])
>>> np.hstack((a1, a2))
array([[1, 1, 3, 3],
       [2, 2, 4, 4]])


ii) 하나의 배열을 여러개의 작은 배열로 나누기
다음과 같은 배열 x가 있다고 합시다:

>>> x = np.arange(1, 13).reshape(2, 6)
>>> x
array([[ 1,  2,  3,  4,  5,  6],
       [ 7,  8,  9, 10, 11, 12]])

np.hsplit()을 사용해서 리턴할 배열의 개수나, 배열을 나누는 열의 위치를 지정할 수 있습니다. 전자는 정수의 형태로 숫자를 입력하면 되고, 후자는 튜플의 형태로 원하는 위치를 입력하면 됩니다.

>>> np.hsplit(x, 3)
  [array([[1, 2],
         [7, 8]]), array([[ 3, 4],
         [ 9, 10]]), array([[ 5, 6],
         [11, 12]])]
>>> np.hsplit(x, (3, 4))
[array([[1, 2, 3],
       [7, 8, 9]]), array([[ 4],
       [10]]), array([[ 5, 6],
       [11, 12]])]


iii) 배열 복사하기
view 메소드를 사용하면 기존의 배열과 동일하게 보이는 새로운 배열을 만들 수 있습니다.

>>> a = np.array([[0, 1, 2, 3],
                  [4, 5, 6, 7]])
>>> b = a.view()
>>> b
array([[0, 1, 2, 3],
       [4, 5, 6, 7]])

그런데 view 메소드를 사용해서 복사된 새로운 배열 값을 변경하면 원본의 값도 변경됩니다. 즉, 얕은 복사가 된 것입니다.

>>> b[1][1] = 99
>>> b
array([[ 0,  1,  2,  3],
       [ 4, 99,  6,  7]])
>>> a
array([[ 0,  1,  2,  3],
       [ 4, 99,  6,  7]])


앞서 i)와 ii)에서 소개한 함수들도 view 메소드와 마찬가지로 얕은 복사가 됩니다. 깊은 복사를 하기 위해서는 copy 메소드를 사용하면 됩니다. copy 메소드의 사용방법은 view 메소드와 동일합니다. * slicing과 indexing을 사용해서 새로운 배열을 생성하는 방법은 리스트와 동일하기 때문에 따로 설명을 추가하지는 않았습니다.

* 조심해야 할 점은 리스트의 경우 깊은 복사가 되는 반면, 배열의 경우 얕은 복사가 된다는 차이가 있다는 것입니다.

③ 배열 연산

앞서, 배열에 축을 추가하거나, 기존의 데이터들을 통해 새로운 배열을 만드는 것은 연산을 위해 사전에 데이터를 조작하는 과정에 필요한 것입니다.
NumPy의 배열은 기본적으로 사칙연산을 모두 지원합니다. data1data2 배열이 다음과 같이 있다고 합시다:

>>> data1 = np.array([1, 2])
>>> data2 = np.ones(2, dtype=int)

사칙연산 기호를 사용해서 배열 간의 사칙연산을 진행할 수 있습니다.

>>> data1 + data2
array([2, 3])
>>> data1 - data2
array([0, 1])
>>> data1 * data2
array([1, 4])
>>> data1 / data2
array([1., 1.])


배열 안의 요소들 사이에서의 사칙연산도 가능합니다.
ex) sum 메소드는 n차원 배열에서 요소들의 합을 리턴합니다. 이때, 축을 지정하면 그 축을 기준으로 요소들을 합쳐 새로운 배열을 생성합니다. axis에 숫자를 매기는 순서는 인덱스 접근 순서와 같습니다.

>>> a = np.array([[[ 1,  2],
                   [ 3,  4]],
                  [[ 5,  6],
                   [ 7,  8]],
                  [[ 9, 10],
                   [11, 12]]])
>>> a.sum(axis=0)
array([[15, 18],
       [21, 24]])
>>> a.sum(axis=1)
array([[ 4,  6],
       [12, 14],
       [20, 22]])
>>> a.sum(axis=2)
array([[ 3,  7],
       [11, 15],
       [19, 23]])

 

④ 브로드캐스팅

브로드캐스팅은 배열과 숫자와의 연산, 또는 크기가 서로 다른 배열 간의 연산을 지원하는 개념입니다. 행렬의 연산과 동일하게 진행된다고 생각하면 됩니다. 스칼라와 행렬 곱셈, 크기가 맞는 서로 다른 행렬의 연산도 가능합니다.

>>> data1 = np.array([1.0, 2.0])
>>> data * 1.6
array([1.6, 3.2])
>>> data2 = np.array([[1, 2],
                      [3, 4],
                      [5, 6]])
>>> data3 = data2.copy()
>>> data2 * data3
array([[ 1,  4],
       [ 9, 16],
       [25, 36]])

 

⑤ 배열의 형태 바꾸기

[Python] NumPy 라이브러리 기초 1에서 설명한 reshape 메소드를 사용하지 않고 배열의 형태를 바꾸는 방법들을 소개하겠습니다.

▶ 전치행렬
먼저 배열을 행렬로 보는 관점을 유지했을 때, transpose 메소드와 T를 통해 전치행렬의 개념을 가져올 수 있습니다.

>>> arr = np.arange(6).reshape((2, 3))
>>> arr
array([[0, 1, 2],
       [3, 4, 5]])
>>> arr.transpose()
array([[0, 3],
       [1, 4],
       [2, 5]])
>>> arr.T
array([[0, 3],
       [1, 4],
       [2, 5]])


▶ 배열 반전하기
배열을 반전해서 요소들의 순서를 뒤집기 위해서 np.flip() 함수를 사용할 수 있습니다.

>>> arr = np.array([[1, 2, 3], [4, 5, 6]])
>>> reversed_arr = np.flip(arr)
>>> reversed_arr
array([[6, 5, 4],
       [3, 2, 1]])


▶ n차원 배열을 1차원 배열로 만들기
n차원 배열을 1차원 배열로, 즉, 요소들을 순서에 맞춰서 일렬로 나열하기 위해서는 np.flatten()np.ravel() 함수를 사용할 수 있습니다.

>>> arr = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
>>> arr.flatten()
array([1, 2, 3, 4, 5, 6, 7, 8])
>>> arr.ravel()
array([1, 2, 3, 4, 5, 6, 7, 8])


두 함수의 차이는 np.flatten()의 경우 깊은 복사를 사용하고, np.ravel()은 얕은 복사를 사용한다는 것입니다. (np.ravel()로 새롭게 만들어진 배열의 값을 변경한다면 원본 배열의 값도 변경됩니다.)

728x90
반응형