15. 어레이 변형#
어레이의 모양을 변형해서 새로운 어레이를 생성하는 다양한 방식을 살펴본다.
주요 내용
어레이 변형
차원 추가와 삭제
어레이 이어붙이기/쌓기
브로드캐스팅
기본 설정
numpy
모듈과 시각화 도구 모듈인 matplotlib.pyplot
에 대한 기본 설정을 지정한다.
# 넘파이
import numpy as np
# 램덤 시드
np.random.seed(12345)
# 어레이 사용되는 부동소수점들의 정확도 지정
np.set_printoptions(precision=4, suppress=True)
# 파이플롯
import matplotlib.pyplot as plt
# 도표 크기 지정
plt.rc('figure', figsize=(10, 6))
15.1. 차원 추가와 삭제#
reshape()
메서드와 함께
항목은 그대로 유지하면서 어레이의 모양만 변형시키는 방법으로
차원 추가와 삭제 기법이 활용된다.
15.1.1. 차원 추가#
어레이에 임의의 축을 추가하는 방식으로 차원이 하나 더 추가된 어레이를 생성할 수 있다. 어느 축을 추가하느냐에 따라 생성된 어레이의 모양은 달라진다.
예제
다음 길이가 3인 1차원 어레이를 이용하자.
arr_1d = np.random.normal(size=3)
arr_1d
array([-0.2047, 0.4789, -0.5194])
arr_1d
는 원래 0번 축 하나만 갖는데,
아래 코드는 여기에 1번 축을 추가하여 2차원 어레이로 만든다.
arr_1d[:, np.newaxis]
array([[-0.2047],
[ 0.4789],
[-0.5194]])
reshape()
메소드로도 동일한 결과를 얻을 수 있다.
arr_1d.reshape((3, 1))
array([[-0.2047],
[ 0.4789],
[-0.5194]])
아래 코드는 기존의 0번 축을 1번 축으로 바꾼다.
arr_1d[np.newaxis, :]
array([[-0.2047, 0.4789, -0.5194]])
reshape()
메소드로도 동일한 결과를 얻을 수 있다.
arr_1d.reshape((1,3))
array([[-0.2047, 0.4789, -0.5194]])
예제
2차원 어레이에 축을 추가하면 3차원 어레이가 생성되며, 작동방식은 앞서와 동일하다.
arr = np.random.normal(size=(4, 3))
arr
array([[-0.5557, 1.9658, 1.3934],
[ 0.0929, 0.2817, 0.769 ],
[ 1.2464, 1.0072, -1.2962],
[ 0.275 , 0.2289, 1.3529]])
arr[:,:,np.newaxis].shape
(4, 3, 1)
arr[:,:,np.newaxis]
array([[[-0.5557],
[ 1.9658],
[ 1.3934]],
[[ 0.0929],
[ 0.2817],
[ 0.769 ]],
[[ 1.2464],
[ 1.0072],
[-1.2962]],
[[ 0.275 ],
[ 0.2289],
[ 1.3529]]])
arr[:,np.newaxis,:].shape
(4, 1, 3)
arr[:,np.newaxis,:]
array([[[-0.5557, 1.9658, 1.3934]],
[[ 0.0929, 0.2817, 0.769 ]],
[[ 1.2464, 1.0072, -1.2962]],
[[ 0.275 , 0.2289, 1.3529]]])
15.1.2. 차원 삭제#
ravel()
메서드와 flatten()
메서드는 어레이를 1차원으로 변형한다.
즉, 차원을 모두 없앤다.
arr = np.arange(15).reshape((5, 3))
arr
array([[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11],
[12, 13, 14]])
arr1 = arr.ravel()
arr1
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])
arr2 = arr.flatten()
arr2
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])
차이점은 ravel()
메서드는 뷰(view)를 사용하는 반면에 flatten()
메서드는 어레이를 새로 생성한다.
예를 들어, 아래처럼 arr1
의 항목을 변경하면 arr
의 항목도 함께 변경된다.
arr1[0] = -1
arr
array([[-1, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11],
[12, 13, 14]])
arr2
은 arr
과 전혀 상관이 없다.
arr2[0] = -7
arr
array([[-1, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11],
[12, 13, 14]])
15.2. 어레이 쪼개기#
np.split()
함수
어레이를 지정된 기준에 따라 여러 개의 어레이로 쪼갠다. 반환값은 쪼개진 어레이들의 리스트다.
아래 예제를 살펴보자.
arr = np.random.randn(6, 5)
arr
array([[ 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, -0.8608],
[ 0.5601, -1.2659, 0.1198, -1.0635, 0.3329],
[-2.3594, -0.1995, -1.542 , -0.9707, -1.307 ]])
np.split()
함수의 인자는 하나의 인덱스이거나 여러 인덱스들의 리스트가 사용된다.
먼저, 정수 리스트가 들어오면 축이 정한 방향으로 리스트에 포함된 정수를 이용하여 여러 개의 구간으로 쪼갠다.
아래 코드는 행을 기준으로 행의 인덱스를 0-1, 2, 3-5 세 개의 구간으로 쪼갠다. 따라서 결과는 네 개의 어레이로 이루어진 리스트가 되며, 각 어레의 모양은 다음과 같다.
(2, 5), (1, 5), (3, 5)
np.split(arr, [2, 3]) # np.split(arr, [2, 3],axis=0)
[array([[ 0.8864, -2.0016, -0.3718, 1.669 , -0.4386],
[-0.5397, 0.477 , 3.2489, -1.0212, -0.5771]]),
array([[0.1241, 0.3026, 0.5238, 0.0009, 1.3438]]),
array([[-0.7135, -0.8312, -2.3702, -1.8608, -0.8608],
[ 0.5601, -1.2659, 0.1198, -1.0635, 0.3329],
[-2.3594, -0.1995, -1.542 , -0.9707, -1.307 ]])]
반면에 열을 기준으로 0, 1-2, 3-4 세 개의 구간으로 쪼개면 다음과 같으며, 각 어레이의 모양은 다음과 같다.
(7, 1) (7, 2), (7, 2)
np.split(arr, [1, 3], axis=1)
[array([[ 0.8864],
[-0.5397],
[ 0.1241],
[-0.7135],
[ 0.5601],
[-2.3594]]),
array([[-2.0016, -0.3718],
[ 0.477 , 3.2489],
[ 0.3026, 0.5238],
[-0.8312, -2.3702],
[-1.2659, 0.1198],
[-0.1995, -1.542 ]]),
array([[ 1.669 , -0.4386],
[-1.0212, -0.5771],
[ 0.0009, 1.3438],
[-1.8608, -0.8608],
[-1.0635, 0.3329],
[-0.9707, -1.307 ]])]
둘째 인자로 하나의 정수를 사용하면 행 또는 열을 기준으로 등분한다. 아래 코드는 6개의 행을 3등분 한다.
np.split(arr, 3)
[array([[ 0.8864, -2.0016, -0.3718, 1.669 , -0.4386],
[-0.5397, 0.477 , 3.2489, -1.0212, -0.5771]]),
array([[ 0.1241, 0.3026, 0.5238, 0.0009, 1.3438],
[-0.7135, -0.8312, -2.3702, -1.8608, -0.8608]]),
array([[ 0.5601, -1.2659, 0.1198, -1.0635, 0.3329],
[-2.3594, -0.1995, -1.542 , -0.9707, -1.307 ]])]
등분을 위해서는 행 또는 열의 약수만 둘째 인자로 사용할 수 있다. 그렇지 않으면 오류가 발생한다. 예를 들어, 아래 코드처럼 열을 2등분 하려 하면 오류가 발생한다. 이유는 5개의 열을 2등분 할 수 없기 때문이다.
>>> np.split(arr, 2, axis=1)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Input In [16], in <module>
----> 1 np.split(arr, 2, axis=1)
File <__array_function__ internals>:180, in split(*args, **kwargs)
File ~\miniconda3\envs\homl3\lib\site-packages\numpy\lib\shape_base.py:872, in split(ary, indices_or_sections, axis)
870 N = ary.shape[axis]
871 if N % sections:
--> 872 raise ValueError(
873 'array split does not result in an equal division') from None
874 return array_split(ary, indices_or_sections, axis)
ValueError: array split does not result in an equal division
np.vsplit()
/np.hsplit()
함수
두 함수는 np.split()
함수에 축을 각각 0과 1로 지정한 함수이다.
np.vsplit(arr, z)
:=np.split(arr, z, axis=0)
np.vsplit(arr, [2, 3, 5])
[array([[ 0.8864, -2.0016, -0.3718, 1.669 , -0.4386],
[-0.5397, 0.477 , 3.2489, -1.0212, -0.5771]]),
array([[0.1241, 0.3026, 0.5238, 0.0009, 1.3438]]),
array([[-0.7135, -0.8312, -2.3702, -1.8608, -0.8608],
[ 0.5601, -1.2659, 0.1198, -1.0635, 0.3329]]),
array([[-2.3594, -0.1995, -1.542 , -0.9707, -1.307 ]])]
np.hsplit(arr, z)
:=np.split(arr, z, axis=1)
np.hsplit(arr, [1, 3])
[array([[ 0.8864],
[-0.5397],
[ 0.1241],
[-0.7135],
[ 0.5601],
[-2.3594]]),
array([[-2.0016, -0.3718],
[ 0.477 , 3.2489],
[ 0.3026, 0.5238],
[-0.8312, -2.3702],
[-1.2659, 0.1198],
[-0.1995, -1.542 ]]),
array([[ 1.669 , -0.4386],
[-1.0212, -0.5771],
[ 0.0009, 1.3438],
[-1.8608, -0.8608],
[-1.0635, 0.3329],
[-0.9707, -1.307 ]])]
15.3. 어레이 이어붙이기와 쌓기#
np.concatenate()
함수
두 개의 어레이를 이어붙인다. 지정되는 축에 따라 좌우로 또는 상하로 이어붙인다. 아래 세 어레이를 이용하여 사용법을 설명한다.
arr1 = np.array([[1, 2, 3], [4, 5, 6]])
arr1
array([[1, 2, 3],
[4, 5, 6]])
arr2 = np.array([[7, 8, 9], [10, 11, 12]])
arr2
array([[ 7, 8, 9],
[10, 11, 12]])
arr3 = np.array([[13, 14, 15], [16, 17, 18]])
arr3
array([[13, 14, 15],
[16, 17, 18]])
위아래로 이어붙이려면 축을 0으로 정한다. 이어붙이 어레이로 이루어진 리스트 또는 튜플을 사용함에 주의한다.
np.concatenate([arr1, arr2, arr3], axis=0)
array([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12],
[13, 14, 15],
[16, 17, 18]])
np.concatenate((arr1, arr2, arr3), axis=0)
array([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12],
[13, 14, 15],
[16, 17, 18]])
좌우로 이어붙이려면 축을 1로 정한다.
np.concatenate([arr1, arr2, arr3], axis=1)
array([[ 1, 2, 3, 7, 8, 9, 13, 14, 15],
[ 4, 5, 6, 10, 11, 12, 16, 17, 18]])
np.concatenate((arr1, arr2, arr3), axis=1)
array([[ 1, 2, 3, 7, 8, 9, 13, 14, 15],
[ 4, 5, 6, 10, 11, 12, 16, 17, 18]])
np.vstack()
/np.hstack()
함수
두 함수는 np.concatenate()
함수에 축을 각각 0과 1로 지정한 함수이다.
np.vstack((x, y, ...))
:=np.concatenate((x, y, ...), axis=0)
np.vstack((arr1, arr2, arr3))
array([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12],
[13, 14, 15],
[16, 17, 18]])
np.hstack((x, y, ...))
:=np.concatenate((x, y, ...) axis=1)
np.hstack((arr1, arr2, arr3))
array([[ 1, 2, 3, 7, 8, 9, 13, 14, 15],
[ 4, 5, 6, 10, 11, 12, 16, 17, 18]])
np.r_[]
/np.c_[]
객체
vstack()
/hstack()
과 동일한 기능을 수행하는 특수한 객체들이다.
np.r_[x, y, ...]
:=np.vstack((x, y, ...))
np.r_[arr1, arr2, arr3]
array([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12],
[13, 14, 15],
[16, 17, 18]])
np.c_[x, y, ...]
:=np.hstack((x, y, ...))
np.c_[arr1, arr2, arr3]
array([[ 1, 2, 3, 7, 8, 9, 13, 14, 15],
[ 4, 5, 6, 10, 11, 12, 16, 17, 18]])
15.4. 브로드캐스팅#
브로드캐스팅broadcasting 모양이 서로 다른 두 어레이가 주어졌을 때 두 모양을 통일시킬 수 있다면 두 어레이의 연산이 가능하도록 도와주는 기능이다. 설명을 위해 하나의 어레이와 하나의 정수의 곱셈이 작동하는 과정을 살펴본다.
arr = np.arange(6).reshape((2,3))
arr
array([[0, 1, 2],
[3, 4, 5]])
위 어레이에 4를 곱한 결과는 다음과 같다.
arr * 4
array([[ 0, 4, 8],
[12, 16, 20]])
결과가 항목별로 곱해지는 이유는 arr * 4
가 아래 어레이의 곱셈과 동일하게 작동하기 때문이다.
즉, 정수 4로 채워진 동일한 모양의 어레이를 먼저 생성한 후에 항목별 곱셈을 진행한다.
이와 같이 어레이의 모양을 확장하여 항목별 연산이 가능해지도록 하는 기능은 두 어레이의 모야을 통일시킬 수 있는 경우 항상 작동한다.
15.4.1. 브로드캐스팅과 연산#
어레이 연산을 실행할 때 브로드캐스팅이 가능한 경우 자동 적용된다.
예제
아래 코드는 1차원 어레이를 2차원 어레이로 확장하여 다른 어레이와 모양을 맞춘 후 연산을 실행한 결과를 보여준다.
arr2 = np.arange(4).reshape((4,1)).repeat(3,axis=1)
arr2
array([[0, 0, 0],
[1, 1, 1],
[2, 2, 2],
[3, 3, 3]])
arr3 = np.arange(1, 4)
arr3
array([1, 2, 3])
arr2 + arr3
array([[1, 2, 3],
[2, 3, 4],
[3, 4, 5],
[4, 5, 6]])
아래 그림이 위 연산이 작동하는 이유를 설명한다.
동일한 이유로 다음 연산도 가능하다.
arr3_a = np.arange(1, 4)[np.newaxis, :]
arr3_a
array([[1, 2, 3]])
arr2 + arr3_a
array([[1, 2, 3],
[2, 3, 4],
[3, 4, 5],
[4, 5, 6]])
예제
아래 예제는 2차원 어레이의 칸을 복제하여 모양을 맞춘 후 연산을 실행한다.
arr4 = np.arange(1, 5).reshape((4,1))
arr4
array([[1],
[2],
[3],
[4]])
arr2 + arr4
array([[1, 1, 1],
[3, 3, 3],
[5, 5, 5],
[7, 7, 7]])
그런데 브로드캐스팅이 가능하지 않으면 오류가 발생한다. 예를 아래 두 어레이의 덧셈은 불가능하다.
x = np.arange(0, 31, 10)
arr5 = np.c_[x, x, x]
arr5
array([[ 0, 0, 0],
[10, 10, 10],
[20, 20, 20],
[30, 30, 30]])
arr4_a = arr4.flatten()
arr4_a
array([1, 2, 3, 4])
>>> arr5+ arr4_a
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In [80], line 1
----> 1 arr5+ arr4_a
ValueError: operands could not be broadcast together with shapes (4,3) (4,)
아래 그림이 이유를 설명한다.
예제
아래 예제는 2차원 어레이를 3차원으로 확장한 후에 연산을 진행하는 것을 보여준다.
arr6 = np.arange(24).reshape((3, 4, 2))
arr6
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]]])
arr7 = np.arange(8).reshape((4, 2))
arr7
array([[0, 1],
[2, 3],
[4, 5],
[6, 7]])
arr6 + arr7
array([[[ 0, 2],
[ 4, 6],
[ 8, 10],
[12, 14]],
[[ 8, 10],
[12, 14],
[16, 18],
[20, 22]],
[[16, 18],
[20, 22],
[24, 26],
[28, 30]]])
예제
아래 코드는 어레이의 열별 평균값이 0이 되도록 하려 한다.
arr = np.random.randn(4, 3)
arr
array([[ 0.2863, 0.378 , -0.7539],
[ 0.3313, 1.3497, 0.0699],
[ 0.2467, -0.0119, 1.0048],
[ 1.3272, -0.9193, -1.5491]])
기존 어레이의 열별 평균값을 각각의 열에서 뺀다.
arr.mean(0) # arr.mean(axis=0)
array([ 0.5479, 0.1992, -0.3071])
demeaned = arr - arr.mean(0)
demeaned
array([[-0.2615, 0.1788, -0.4468],
[-0.2166, 1.1506, 0.377 ],
[-0.3012, -0.211 , 1.3119],
[ 0.7793, -1.1184, -1.242 ]])
이제 열별 평균값을 확인하면 0이 된다.
demeaned.mean(0)
array([-0., 0., 0.])
예제
아래 코드는 어레이의 행별 평균값이 0이 되도록 하려 한다.
arr
array([[ 0.2863, 0.378 , -0.7539],
[ 0.3313, 1.3497, 0.0699],
[ 0.2467, -0.0119, 1.0048],
[ 1.3272, -0.9193, -1.5491]])
row_means = arr.mean(1)
row_means
array([-0.0299, 0.5836, 0.4132, -0.3804])
row_means.reshape((4, 1))
array([[-0.0299],
[ 0.5836],
[ 0.4132],
[-0.3804]])
demeaned = arr - row_means.reshape((4, 1))
demeaned.mean(1)
array([ 0., 0., -0., 0.])
15.4.2. 브로드캐스팅과 항목 대체#
브로드캐스팅으로 어레이의 항목을 대체할 수 있다. 설명을 위해 아래 어레이를 사용한다.
arr = np.zeros((4, 3))
arr
array([[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]])
예제
모든 항목을 5로 대체한다.
arr[:] = 5
arr
array([[5., 5., 5.],
[5., 5., 5.],
[5., 5., 5.],
[5., 5., 5.]])
예제
모든 열을 지정된 열로 대체한다.
col = np.array([1.28, -0.42, 0.44, 1.6])
col[:, np.newaxis]
array([[ 1.28],
[-0.42],
[ 0.44],
[ 1.6 ]])
arr[:] = col[:, np.newaxis]
arr
array([[ 1.28, 1.28, 1.28],
[-0.42, -0.42, -0.42],
[ 0.44, 0.44, 0.44],
[ 1.6 , 1.6 , 1.6 ]])
예제
0번, 1번 행을 특정 값으로 대체한다.
arr[:2] = [[-1.37], [0.509]]
arr
array([[-1.37 , -1.37 , -1.37 ],
[ 0.509, 0.509, 0.509],
[ 0.44 , 0.44 , 0.44 ],
[ 1.6 , 1.6 , 1.6 ]])
15.5. 연습문제#
참고: (실습) 고급 넘파이