마케터의 데이터 분석 공부#4 : NumPy
2.5 불리언값으로 선택해보기
중복된 이름이 포함된 배열과 7행 4열의 랜덤한 배열을 만들어보자.
IN
GD_names = np.array(['jack', 'flush', 'heart', 'jack', 'heart', 'heart', 'flush' ])
GD_data=np.random.randn(7,4)
IN
GD_names
OUT
array(['jack', 'flush', 'heart', 'jack', 'heart', 'heart', 'flush'],
dtype='<U5')
IN
GD_data
OUT
array([[ 0.06349745, 0.62571537, -1.79162919, -1.32486211],
[-0.85244435, -0.47752742, -0.00828793, -0.58553865],
[-1.27599468, -0.5319131 , -0.50685417, -0.04457998],
[-1.28968382, 0.07817952, -1.25808074, -1.30973125],
[ 0.01830178, 2.7703779 , 0.21580349, 0.69058324],
[ 0.0207521 , -0.32622679, -0.30068597, 0.05677987],
[ 0.62578743, -0.18624268, 0.26901157, -0.51297461]])
다양한 예제에 앞서 GD_names의 값들이 GD_data 배열의 각 행에 대응한다고 가정해보자.
IN
GD_names == 'jack'
OUT
array([ True, False, False, True, False, False, False])
만약 전체 행에서 ‘jack’과 같은 이름을 선택해 보겠다. 산술 연산과 마찬가지로 배열에 대한 비교 연산도 벡터화되므로 GD_names를 ‘jack’ 문자열과 비교하면 불리언 배열을 반환한다.
IN
GD_data[GD_names == 'jack']
OUT
array([[ 0.06349745, 0.62571537, -1.79162919, -1.32486211],
[-1.28968382, 0.07817952, -1.25808074, -1.30973125]])
이 반환되는 불리언 배열은 배열의 색인으로 사용할 수 있다. 불리언 배열은 반드시 색인하려는 축의 길이와 동일한 길이를 가져야 한다. 불리언값으로 배열을 선택할 때는 불리언 배열의 크기가 다르더라도 실패하지 않기 때문에, 이 기능을 사용할 때는 주의해야 한다.
IN
GD_data[GD_names == 'jack', 1:]
OUT
array([[ 0.62571537, -1.79162919, -1.32486211],
[ 0.07817952, -1.25808074, -1.30973125]])
IN
GD_data[GD_names == 'jack', 2]
OUT
array([-1.79162919, -1.25808074])
다음 예제로는 GD_names == ‘jack’인 행에서 1: 열과 2열을 선택해 보았다.
IN
GD_names != 'jack'
OUT
array([False, True, True, False, True, True, True])
IN
GD_data[~(GD_names == 'jack')]
OUT
array([[-0.85244435, -0.47752742, -0.00828793, -0.58553865],
[-1.27599468, -0.5319131 , -0.50685417, -0.04457998],
[ 0.01830178, 2.7703779 , 0.21580349, 0.69058324],
[ 0.0207521 , -0.32622679, -0.30068597, 0.05677987],
[ 0.62578743, -0.18624268, 0.26901157, -0.51297461]])
‘jack’이 아닌 요소들을 선택하려면 != 연산자를 사용하는 방법이 있고, ~를 사용해서 조건절을 부인하는 방법도 있다.
IN
conv = GD_names == 'jack'
GD_data[~conv]
OUT
array([[-0.85244435, -0.47752742, -0.00828793, -0.58553865],
[-1.27599468, -0.5319131 , -0.50685417, -0.04457998],
[ 0.01830178, 2.7703779 , 0.21580349, 0.69058324],
[ 0.0207521 , -0.32622679, -0.30068597, 0.05677987],
[ 0.62578743, -0.18624268, 0.26901157, -0.51297461]])
~ 연산자는 일반적인 조건을 반대로 쓸 때 유용하다.
IN
comm = (GD_names == 'jack') | (GD_names == 'heart')
comm
OUT
array([ True, False, True, True, True, True, False])
IN
GD_data[comm]
OUT
array([[ 0.06349745, 0.62571537, -1.79162919, -1.32486211],
[-1.27599468, -0.5319131 , -0.50685417, -0.04457998],
[-1.28968382, 0.07817952, -1.25808074, -1.30973125],
[ 0.01830178, 2.7703779 , 0.21580349, 0.69058324],
[ 0.0207521 , -0.32622679, -0.30068597, 0.05677987]])
두 가지 이름을 선택하려면 &(and)나 |(or) 같은 논리 연산자를 사용한 여러 개의 불리언 조건을 사용하면 된다. 배열에 불리언 색인을 이용해서 데이터를 선택하게 되면 내용이 반환되지 않더라도 항상 데이터 복사가 발생하므로 이점을 유의하여 사용하도록 하자. 그리고 파이썬 예약어인 and와 or는 불리언 배열에서 사용할 수 없다. 대신 &(and)와 |(or)를 사용한다.
IN
GD_data[GD_data<0] = 0
GD_data
OUT
array([[0.06349745, 0.62571537, 0. , 0. ],
[0. , 0. , 0. , 0. ],
[0. , 0. , 0. , 0. ],
[0. , 0.07817952, 0. , 0. ],
[0.01830178, 2.7703779 , 0.21580349, 0.69058324],
[0.0207521 , 0. , 0. , 0.05677987],
[0.62578743, 0. , 0.26901157, 0. ]])
불리언 배열에 값을 대입하는 것은 유연하게 이루어진다. GD_data에 저장된 모든 음수를 0으로 바꿔보았다.
IN
GD_data[GD_names != 'flush'] = 3
GD_data
OUT
array([[3. , 3. , 3. , 3. ],
[0. , 0. , 0. , 0. ],
[3. , 3. , 3. , 3. ],
[3. , 3. , 3. , 3. ],
[3. , 3. , 3. , 3. ],
[3. , 3. , 3. , 3. ],
[0.62578743, 0. , 0.26901157, 0. ]])
이제 1차원 불리언 배열을 사용해서 전체 행이나 열을 선택하는 것은 쉽게 할 수 있을 것이다. GD_names의 불리언 배열을 활용해서 GD_data 배열에 3을 대입해 보았다. 사실 2차원 데이터에 대한 이런 유형의 연산은 pandas를 사용해서 처리하는 것이 편리하다. 나중에 살펴보도록 하자.
2.6 팬시 색인 알아보기
팬시 색인은 정수 배열을 사용한 색인을 설명하기 위해 Numpy에서 사용하는 단어다.
IN
GD_arr = np.empty((10,4))
for i in range(10):
GD_arr[i]=i
GD_arr
OUT
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.],
[8., 8., 8., 8.],
[9., 9., 9., 9.]])
정수 배열 색인을 알아보기 위해 10x4 크기의 배열을 만들었다.
IN
GD_arr[[6, 3, 0, 8]]
OUT
array([[6., 6., 6., 6.],
[3., 3., 3., 3.],
[0., 0., 0., 0.],
[8., 8., 8., 8.]])
특정한 순서로 행을 선택하고 싶다면 원하는 순서가 명시된 정수가 담긴 ndarray나 리스트를 넘기면 된다.
IN
GD_arr[[-7, -4, -1]]
OUT
array([[3., 3., 3., 3.],
[6., 6., 6., 6.],
[9., 9., 9., 9.]])
색인으로 음수를 사용할 수도 있는데, 끝에서부터 행을 선택하게 된다.
IN
GD_arr = np.arange(40).reshape((10,4))
GD_arr
OUT
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],
[36, 37, 38, 39]])
IN
GD_arr[[2, 4, 6, 8],[1, 2, 3, 0]]
OUT
array([ 9, 18, 27, 32])
다차원 색인 배열을 넘기는 경우에는 각각의 색인 튜플에 대응하는 1차원 배열이 선택된다. 출력 결과를 보면 (2,1), (4,2), (6,3), (8,0)에 대응하는 원소들이 선택되었다. 배열이 몇 차원이든지 팬시 색인의 결과는 항상 1차원이다. 만약 행렬의 열과 행에 대응하는 사각형 모양의 값을 선택하고 싶다면 아래의 코드처럼 명령을 수행하며 된다.
IN
GD_arr[[2, 4, 6, 8]][:, [1, 2, 3, 0]]
OUT
array([[ 9, 10, 11, 8],
[17, 18, 19, 16],
[25, 26, 27, 24],
[33, 34, 35, 32]])
2.7 배열 전치와 축 바꾸기
배열 전치는 데이터를 복사하지 않고 데이터의 모양이 바뀐 뷰를 반환하는 특별한 기능이다. ndarray는 transpose 메서드와 T라는 이름의 특수한 속성을 가지고 있다. 예제를 통해 알아보도록 하자.
IN
GD_arr = np.arange(20).reshape((4,5))
GD_arr
OUT
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19]])
IN
GD_arr.T
OUT
array([[ 0, 5, 10, 15],
[ 1, 6, 11, 16],
[ 2, 7, 12, 17],
[ 3, 8, 13, 18],
[ 4, 9, 14, 19]])
reshape는 원하는 차원의 형태로 데이터를 만들어주는 함수인데 여기서는 reshape 함수를 사용하여 (4,5)형태의 데이터를 생성했다. 그리고 T를 활용하여 배열을 전치해 보았다.
IN
GD_arr = np.random.randn(2,3)
GD_arr
OUT
array([[-0.04938853, -0.43215832, -0.6406539 ],
[-0.2085099 , 0.74299529, -1.49607973]])
IN
np.dot(GD_arr.T,GD_arr)
OUT
array([[ 0.0459156 , -0.13357821, 0.34358839],
[-0.13357821, 0.73880281, -0.83471628],
[ 0.34358839, -0.83471628, 2.64869199]])
np.dot을 활용하여 행렬의 내적도 구할 수 있다.
IN
GD_arr = np.arange(30).reshape((2,3,5))
GD_arr
OUT
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]]])
IN
GD_arr.transpose((2,1,0))
OUT
array([[[ 0, 15],
[ 5, 20],
[10, 25]],
[[ 1, 16],
[ 6, 21],
[11, 26]],
[[ 2, 17],
[ 7, 22],
[12, 27]],
[[ 3, 18],
[ 8, 23],
[13, 28]],
[[ 4, 19],
[ 9, 24],
[14, 29]]])
다차원 배열의 경우 transpose 메서드는 튜플로 축 번호를 받아서 치환한다. 기존 차원의 형태를 0부터 커지는 숫자형태로 인식하고 원하는 차원의 형태의 값을 transpose에 넘겨주는 것이다. 예제 속의 기존 배열의 형태는 (2,3,5)이다. 여기서 2는 0, 3은 1, 5는 2라는 숫자로 인식하여 (5,3,2) 형태의 차원을 표현하기 위해서는 transpose에 (2,1,0)이라는 값을 넘겨주면 되는 것이다. 그냥 원하는 차원의 형태를 기존 차원 값의 형태에서 위치를 숫자로 표현해 주면 되는 것이다.
IN
GD_arr
OUT
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]]])
IN
GD_arr.swapaxes(1,2)
OUT
array([[[ 0, 5, 10],
[ 1, 6, 11],
[ 2, 7, 12],
[ 3, 8, 13],
[ 4, 9, 14]],
[[15, 20, 25],
[16, 21, 26],
[17, 22, 27],
[18, 23, 28],
[19, 24, 29]]])
ndarray에는 swapaxes라는 메서드도 있는데, 2개의 축 번호를 받아서 배열을 뒤바꾼다. transpose와 비슷하다. 하지만 swapaxes의 경우에는 넘겨받는 2개의 축 번호에 해당하는 위치를 바꾼다. 위 예제 속의 GD_arr은 (2,3,5) 형태의 차원을 가지고 있다. 여기서 swapaxes에 (1,2)를 넘겨주게 되면 3과 5의 위치가 바뀌어 (2,5,3) 형태의 차원으로 바뀌게 되는 것이다.
다차원 배열 객체에 대한 설명은 여기서 마무리 짓고, 다음 글에서 배열의 각 원소를 처리하는 유니버설 함수에 대해서 알아보도록 하자.