넘파이 2편

주요 내용

기본 설정

numpy 모듈과 시각화 도구 모듈인 matplotlib.pyplot에 대한 기본 설정을 지정한다.

4.1 넘파이 다차원 어레이 객체(ndarray)

4.1.4 인덱스, 인덱싱, 슬라이싱 (p. 144)

리스트의 인덱스, 인덱싱, 슬라이싱 개념을 넘파이 어레이에 확장시킨다. 리스트의 경우보다 보다 다양한 기능을 제공하며 데이터 분석에서 매우 중요한 역할을 수행한다.

1차원 어레이 인덱싱, 슬라이싱

1차원 어레이의 경우 리스트의 경우와 거의 동일하게 작동한다.

주의사항: 위 기능은 리스트에서는 제공되지 않는다.

아래와 같이 리스트를 값으로 지정하면 작동한다.

뷰(view) 이해

넘파이 어레이에 대해 슬라이싱을 실행하면 지정된 구간에 해당하는 어레이를 새로 생성하는 게 아니라 지정된 구간의 정보를 이용만 한다. 이렇게 작동하는 기능이 (view)이다. 즉, 어레이를 새로 생성하지 않고 기존 어레이를 적절하게 활용한다.

참고: 넘파이 어레이와 관련된 많은 기능이 뷰 기능을 이용한다. 아래에서 소개하는 전치 어레이를 구하는 과정도 뷰를 이용한다.

어레이 전체 항목을 특정 값으로 한꺼번에 바꾸려면 [:]로 슬라이싱 한다.

copy() 메서드

원본을 그대로 유지하고자 한다면 어레이를 새로 생성해서 사용해야 하며, 이를 위해 copy() 메서드를 활용한다.

arr_slice2를 변경해도 arr은 영향받지 않는다.

2차원 어레이 인덱싱

2차원 이상의 다차원 어레이는 보다 다양한 인덱싱, 슬라이싱 기능을 제공한다.

리스트의 인덱싱을 그대로 사용할 수 있다.

위 인덱싱을 2차원 어레이 인덱싱 방식으로 아래와 같이 쉽게 할 수 있다.

3차원 어레이 인덱싱

arr3d는 (2, 2, 3) 모양의 3차원 어레이다.

모양이 (2, 2, 3)인 3차원 어레이의 0번 인덱스 항목은 (2, 3) 크기의 2차원 어레이다.

0번 인덱스 항목인 2차원 어레이의 항목을 일정한 값으로 바꾸기 위해 인덱싱을 활용할 수 있다.

모양이 (2, 2, 3)인 3차원 행렬의 1번 행, 0번 열의 항목은 길이가 3인 1차원 어레이다.

실제로 아래 처럼 1번행과 1번 행의 0번 열의 값을 확인하면 동일한 값이 나온다.

모양이 (2, 2, 3)인 3차원 행렬의 1번 행, 0번 열, 2번 인덱스의 항목은 길이가 정수 9이다.

실제로 아래 처럼 1번행과 1번 행, 0번 열, 2번 인덱스의 값을 확인하면 동일한 값이 나온다.

2차원 어레이 슬라이싱 (p. 148)

리스트 슬라이싱 방식을 동일하게 적용할 수 있다.

행과 열을 함께 슬라이싱하려면 행과, 열에 대한 슬라이싱을 동시에 지정한다.

인덱싱과 슬라이싱이 행과 열 각각에 대해 독립적으로 사용될 수 있다.

주의사항: 인덱싱을 사용하는 만큼 결과 어레이의 차원이 기존 어레이의 차원보다 1씩 줄어든다.

동일한 항목을 사용하지만 인덱싱을 사용할 때와 아닐 때의 결과는 다른 모양의 어레이가 된다.

모양은 사용되는 슬라이싱의 구간에 의존한다.

따라서 결과는 (1, 2) 모양의 어레이다.

연습

아래 그림 모양의 2차원 어레이를 생성한다.

참고: 아래에서는 길이가 36인 1차원 어레이를 (6, 6) 모양의 2차원 어레이로 항목을 재배열하기 위해 reshape() 함수를 사용한다. reshape() 함수에 대한 자세한 설명은 뒤에서 이루어진다.

<그림 출처: geeksforgeeks>

위 그림에 색깔별로 표시된 어레이를 슬라이싱을 이용하여 구하라.

만약에 열에 대해 슬라이싱 대신 인덱싱을 사용하면 1차원 어레이를 얻는다.

스텝

리스트 슬라이싱의 경우처럼 스텝도 사용할 수 있다.

3차원 어레이 인덱싱/슬라이싱

기본적으로 2차원 어레이 슬라이싱 기능과 동일하게 작동한다. 여기서는 칼라 이미지 데이터를 3차원 어레이로, 흑백 이미지를 2차원 어레이로 다루면서 인덱싱과 슬라이싱을 이용하여 이미지를 조작하는 간단한 방법을 설명한다.

먼저 너구리 얼굴 이미지를 가져온다. 아래 코드는 scipy 패키지에서 기본으로 제공하는 너구리 얼굴 사진을 3차원 어레이로 가져온다.

주의사항: 아래와 같은 코드가 있다는 정도 기억해 두기 바란다.

face는 너구리 얼굴 이미지를 3차원 어레이로 불러온다.

face는 아래 모양의 3차원 어레이를 가리킨다.

위 이미지에서 세로, 가로 축에 보여지는 숫자가 픽셀 수를 보여주며 정확한 픽셀 수는 다음과 같다.

RGB 색상 정보

768x1024 개의 픽셀에 포함된 길이 3인 어레이는 R(빨강), G(초록), B(파랑) 색상에 대한 정보를 각각 담는다. 또한 색상 정보는 각각 0부터 255 사이의 값을 사용한다.

어레이에 사용된 값들의 정확한 자료형은 uint8, 즉, 8바이트로 표현된 양의 정수 자료형이다.

색상 정보 정규화

여기서는 픽셀 정보를 0과 1사이의 부동소수점으로 변경해서 사용한다. 이유는 여러 이미지 변환을 시도할 때 0과 1사이의 부동소수점의 값들이 나올 때 정확하게 기능하기 때문이다.

RGB 정보의 최댓값이 255이기에 모든 항목을 255로 나누어 0과 1사이의 값으로 정규화시킨다.

흑백 이미지 변환

흑백 이미지는 보통 하나의 RGB 정보만 가져오는 것으로 구할 수 있다. 예를 들어, 빨강색 정보만 가져오려면 아래처럼 3차원 인덱싱을 실행한다. 아래 코드는 이미지의 행과 열은 그대로 두고 RGB 정보에서 R(빨강)에 대한 정보만 인덱싱으로 가져온다.

흑백사진으로 보여주려면 imshow() 함수의 cmap 키워드 인자를 gray style 을 사용하도록 지정해야 한다.

참고: cmap 키워드: 색상 지도(color map)을 가리키는 매개변수이며, 'gray'를 사용하여 흑백사진으로 출력한다. 색상 지도에 대한 자세한 안내는 Matplotlib: Choosing Colormaps를 참조한다.

참고: 진정한 흑백사진으로 변경하려면 RGB 정보를 전부 이용하여 하나의 값을 계산해야 한다. 흑백 이미지의 명암을 구현하는 벡터 내적 수식은 다음과 같다.

$$ [R, G, B]\cdot[0.2989, 0.5870, 0.1140] = 0.2989 \cdot R + 0.5870 \cdot G + 0.1140 \cdot B $$

벡터 내적 연산은 어레이의 내적 연산을 통해 쉽게 계산할 수 있다.

결과는 2차원 어레이며, 흑백 이미지의 정보를 모두 갖고 있다.

보다 선명한 명암을 보여주는 이미지가 결과로 나온다.

이미지 크기 조정

이미지 크기 조정은 픽셀 수를 조절하는 방식으로 이루어진다. 가장 단순한 방식은 행과 열에서 각각 2개씩 건너뛰며 픽셀을 선택하는 것이다. 일부 데이터가 상실되지만 작은 이미지에는 눈으로 보일 정도로 영향을 받지는 않는다.

아래 코드는 행과 열에 대해 모두 스텝 2를 지정하고 슬라이싱을 적용한다. 즉, 2x2 모양을 이루는 네 개의 픽셀 중에 상단 왼편에 있는 픽셀만 선택한다.

행과 열의 픽셀 수가 모두 절반으로 줄었다.

이미지를 확인하면 살짝 흐려진 느낌을 받는다.

보간법

가장 일반적으로 사용되는 이미지 변경 방법은 보간법(interpolation)이다. 이미지 크기 변경에 사용되는 다양한 보간법 기법이 있지만 여기서는 두 픽셀 사이의 평균값을 취하는 방식을 이용한다. 보간법의 다양한 방식에 대한 설명은 OpenCV 보간법을 참조한다.

아래 코드는 짝수 인덱스의 값과 홀수 인덱스의 값의 평균을 취하는 방식으로 보간법을 활용한다.

4분의 1 크기의 두 이미지 데이터가 조금 다르기는 하지만 이미지 상으로 차이점을 발견하기 어렵다.

참고: 4차원 이상의 어레이에 대해서는 기본적으로 2, 3차원 어레이 대상과 동일하게 작동한다. 하지만 시각화가 기본적으로 불가능하고, 사람이 직접 4차원 이상의 슬라이싱을 조작하는 것도 매우 어렵다. 따라서 2, 3차원 어레이 슬라이싱의 기본 아이디어만 이해했다면 그것으로 충분하다는 점만 언급한다.

4.1.5 부울 인덱싱 (p. 150)

부울 인덱싱은 앞서 설명한 인덱싱/슬라이싱 기법이 처리하지 못하는 인덱싱/슬라이싱을 지원한다.

1차원 부울 어레이 활용

1차원 부울 어레이를 이용한 인덱싱을 설명하기 위해 아래 두 개의 어레이를 이용한다.

names에 포함된 이름이 Bob인지 여부를 확인하면 부울 값으로 이루어진 길이가 7인 어레이가 생성된다. 즉, 항목별 비교 연산이 이루어진다.

이제부터 name_Bob이 가리키는 길이가 7인 1차원 어레이에서 True가 위치한 인덱스를 이용하여 data가 가리키는 2차원 어레이를 대상으로 부울 인덱싱이 작동하는 방법을 설명한다.

먼저, data 의 행의 길이가 7임에 주목하라. 이제 name_Bob에서 True가 위치한 인덱스에 해당하는 항목만 data에서 슬라이싱하려면 다음과 같이 부울 인덱싱을 사용한다. 결과는 data에서 0번행과 3번행만 가져온다. 이유는 name_Bob에서 0번, 3번 인덱스의 항목만 True이기 때문이다.

이처럼 1차원 부울 어레이를 이용한 인덱싱은 하나의 축에 대해 슬라이싱을 적용하는 것과 동일하게 작동한다. 다만, 부울 어레이의 길이가 사용되는 축의 길이와 동일해야 한다. 따라서 부울 인덱싱과 일반 인덱싱, 슬라이싱을 혼합할 수 있다.

부울 연산자(~, &, |)를 사용하여 얻어진 부울 어레이 표현식을 부울 인덱싱에 직접 활용할 수 있다. 예를 들어, 이름이 Bob 아닌 이름이 위치한 인덱스에 해당하는 행만 가져오려면 == 대신에 ~=를 이용하거나 ==~ 연산자를 함께 이용한다.

다음은 Bob 또는 Will 이 위치한 인덱스에 해당하는 행만 가져온다.

참고: 부울 인덱싱에 사용되는 부울 어레이를 마스크(mask)라고 한다. 따라서 mask 단어가 종종 부울 인덱싱에 사용되는 마스크 변수로 사용된다.

항목 업데이트

1차원 부울 배열을 이용하여 전체 행 또는 전체 열을 특정 값으로 변경할 수 있다.

아래 코드는 names에서 Joe가 사용되지 않은 항목의 인덱스에 해당하는 행에 포함된 항목을 모두 7로 변경한다.

부울 인덱싱 활용 변수 선언

부울 인덱싱은 뷰를 이용하지 않고 항상 새로운 어레이를 생성한다.

data2의 0번 행을 모두 -1로 변경해도 data는 변하지 않는다.

하지만 data는 변경되지 않았다.

다차원 부울 어레이 활용

아래 표현식은 data와 동일한 모양의 부울 어레이를 생성한다. 이유는 부등호 연산이 항목별로 작동하기 때문이다.

이제 mask를 이용하여 모든 음수 항목을 0으로 변경할 수 있다. 방식은 리스트의 인덱싱을 이용하여 항목을 변경하는 방식과 매우 유사하다.