넘파이 3편

주요 내용

기본 설정

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

4.2 유니버설 함수: 항목별 함수 적용 (p. 158)

유니버설 함수는 어레이의 항목 각각에 대해 적용되는 함수이며, 반환값은 인자로 사용된 어레이와 동일한 모양의 어레이로 구현된다. 유니버설 함수를 줄여서 ufunc라 부른다. 60개 이상의 유니버설 함수가 존재하며, 그중 상당수가 수학 관련 함수이다. 모든 함수들의 리스트는 넘파이의 유니버설 함수 공식문서에서 확인할 수 있다. 여기서는 예제를 통해 유니버설 함수의 활용법을 살펴본다.

먼저 (2, 5) 모양의 어레이를 하나 생성하자.

np.sqrt() 함수

주어진 어레이 각 항목의 제곱근으로 이루어진 어레이가 반환된다.

np.exp() 함수

주어진 어레이 각 항목을 지수로 사용하는 지수승의 값으로 이루어진 어레이가 반환된다. 단, 밑은 오일러 상수 e가 사용된다.

np.maximum() 함수

여러 개의 어레이를 인자로 받는 함수에 대해서도 동일한 방식으로 작동한다. 예를 들어, maximum() 함수는 항목별 최댓값으로 이루어진 어레이를 반환한다.

np.divmod() 함수

여러 개의 어레이를 반환하는 유니버설 함수도 있다. 예를 들어, divmod() 함수는 어레이 나눗셈 실행 결과를 항목별 나눗셈의 몫으로 이루어진 어레이와 나머지로 이루어진 어레이의 튜플을 반환한다.

먼저 (2,4) 모양의 어레이를 하나 만들자.

위 어레이를 3으로 나누면 모든 항목 각각에 대한 몫과 나머지를 각각 따로 모아 두 개의 어레이로 이루어진 튜플을 반환한다.

두 어레이의 나눗셈도 가능하다.

부동소수점의 나눗셈에 대해서도 몫과 나머지를 구한다.

4.3 어레이 중심 프로그래밍 (p. 161)

연산과 함수 호출에 사용되는 넘파이 어레이는 기본적으로 항목 단위로 연산과 함수 호출이 이루어진다. 넘파이 어레이의 이런 특징을 잘 활용하도록 유도하는 프로그래밍을 어레이 중심 프로그래밍(array-oriented programming)이라 한다.

어레이를 중심으로 프로그래밍을 하면 예를 들어 많은 for 반복문을 생략할 수 있으며, 결과적으로 보다 효율적으로 코드를 구현할 수 있다. 또한 구현된 프로그램은 리스트를 이용하는 프로그램보다 빠르고 메모리 효율적으로 실행된다. 여기서는 몇 가지 예제를 이용하여 어레이 중심 프로그래밍을 소개한다.

예제: 2차원 격자(meshgrid) 어레이

아래 모양의 격자무뉘에 해당하는 2차원 어레이를 생성하고자 한다. 각 점의 좌표는 -1과 1사이의 값을 20개의 구간으로 균등하게 나눈 값들이다. 즉, 가로 세로 모두 21개의 점으로 구성된다.

주의사항: for 반복문을 전혀 사용하지 않아야 한다.

먼저 arange() 함수를 이용하여 -1와 1 사이의 구간을 20개의 구간으로 균등하게 나누는 어레이를 생성하려면 아래에서 처럼 -1에서 1.1 이전까지 0.1 스텝으로 증가하는 값들로 이루어진 어레이를 생성하면 된다.

np.meshgrid() 함수

meshgrid() 함수는 지정된 1차원 어레이 두 개를 이용하여 격자무늬의 좌표를 생성한다. 즉, 격자에 사용되는 점들의 x 좌표와 y 좌표를 따로따로 모아 두 개의 어레이를 반환한다.

xs와 ys를 이용하여 산점도를 그리면 원하는 격자무늬가 얻어진다.

예제: 2차원 이미지 그리기

xs와 ys 각각의 제곱을 합하여 제곱근을 구하면 21x21 크기의 대칭 어레이가 얻어진다.

z를 흑백사진으로 표현하면 다음과 같다. 21x21 크기의 해상도를 가진 흑백사진의 명암 대비를 쉽게 알아볼 수 있는 사진이 생성된다.

참고: 위 두 예제를 넘파이 어레이가 아니라 리스트와 for 반복문을 이용하여 구현하려고 시도하면 훨씬 많은 일을 해야 함을 어렵지 않게 알 수 있을 것이다.

4.3.2 통계 메서드 (p. 165)

넘파이 어레이에 사용된 항목들의 합(sum), 평균값(mean), 표준편차(std) 등 기본 통계함수를 지원한다.

연습을 위해 먼저 (3, 3) 모양의 어레이를 무작위로 생성한다.

mean() 메서드

어레이에 포함된 모든 값들의 평균값을 계산한다.

참고: np.mean() 함수를 호출하면 어레이 객체에 포함된 mean() 메서드가 사용된다.

sum() 메서드

어레이에 포함된 모든 값들의 합을 계산한다.

cumsum() 메서드

어레이에 포함된 모든 값들의 누적합을 계산한다.

cumprod() 메서드

어레이에 포함된 모든 값들의 누적곱을 계산한다.

축(axis) 활용

앞서 언급된 모든 함수는 축(axis)을 이용한 함수 적용도 지원한다. 즉, 축을 지정하여 축에 따른 결과를 모든 어레이를 생성한다. 축 지정은 axis 키워드 인자를 사용한다.

평균값
누적합
누적곱

4.3.3 부울 어레이 메서드 (p. 167)

참인 항목의 개수

특정 조건을 만족하는 항목들의 개수를 구하는 과정은 다음과 같다.

아래 코드는 임의로 생성된 100개의 부동소수점 중에서 양수의 개수를 계산한다.

any()all() 메서드

4.3.4 정렬 (p. 168)

sort() 메서드

정해진 축을 기준으로 오름차순으로 정열할 때 사용한다. 물론 1차원 어레이는 축을 지정할 필요가 없다.

다차원 어레이는 축을 이용하여 행 또는 열 기준으로 정렬할 수 있다.

행을 따라 정렬하려면 인자 0을 사용한다.

주의사항: (책의 설명과는 달리) sort() 메서드는 기존의 어레이를 직접 변환한다. 즉, arr이 직접 변경된다.

열을 따라 정렬하려면 인자 1을 사용한다.

예제

어레이에 사용된 값들의 백분위수를 정렬을 이용하여 쉽게 구할 수 있다.

예를 들어, 임의로 생성된 1,000개의 부동 소수점들 중에서 하위 5%에 해당하는 수를 구해보자.

먼저 정렬 한 다음에 하위 5%에 해당하는 위치를 구하여 인덱싱하면 바로 답이 나온다.

하위 5%의 위치는 어레이의 길이에 0.05를 곱해준 값에 해당한다. 인덱스로 사용해야 하기에 정수로 형변환해주면 되며, 예상한 대로 50번 인덱스의 값을 가리킨다.

따라서 하위 5%에 해당하는 값은 아래와 같다.

argmax()/argmin() 메서드

각각 지정된 축을 기준으로 항목들을 가장 큰/작은 값의 항목이 위치한 인덱스를 반환한다.

축을 지정하지 않으면 전체 항목을 대상으로 하며, 반환된 값은 어레이를 1차원으로 변환했을 때의 순서를 보여준다.

아래 결과인 5는 5번 인덱스, 여기서는 마지막에 사용된 항목을 가리킨다.

축을 지정하면 축별로 인덱스를 반환한다.

예를 들어, 열별 최대 값이 위치한 인덱스는 당연히 1이 나온다. 이유는 1번 행(둘쨋줄)의 값이 0번 행(첫째줄)의 값들보다 모두 크기 때문이다.

반면에 행별 최대 값이 위치한 인덱스는 비슷한 이유로 2이다.

4.5 선형 대수 (p. 172)

행렬 곱셈, 전치 행렬, 역행렬 등을 2차원 어레이로 계산하는 방식을 간단한 예제를 이용하여 소개한다.

행렬곱

먼저 두 개의 행렬을 2차원 어레이로 구현하자. 행렬 x는 (2, 3) 모양의 2차원 어레이다.

$$ \text{x} = \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix} $$

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

$$ \text{y} = \begin{bmatrix} 6 & 23 \\ -1 & 7 \\ 8 & 9 \end{bmatrix} $$

두 행렬의 곱 x ydot() 메서드를 이용하여 구한다. 결과는 (2, 2) 모양의 어레이다.

$$ \text{x y} = \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix} \begin{bmatrix} 6 & 23 \\ -1 & 7 \\ 8 & 9 \end{bmatrix} = \begin{bmatrix} 28 & 64\\ 67 & 181 \end{bmatrix} $$

np.dot() 함수를 이용해도 동일한 결과를 얻는다.

@ 연산자

np.dot() 함수 대신 @ 기호를 중위 연산자로 사용할 수 있다.

전치 행렬

전치 행렬은 주어진 행렬의 행과 열을 서로 교환해서 얻어진다.

$$ \begin{bmatrix} 1 & 2 \\ 3 & 4 \\ 5 & 6 \end{bmatrix}^{\,T} = \begin{bmatrix} 1 & 3 & 5 \\ 2 & 4 & 6 \end{bmatrix} $$

위 전치 행렬에 사용된 2차원 어레이는 아래처럼 생성할 수 있다.

전치 행렬은 전치 어레이로 구현된다.

x y의 전치 행렬은 y의 전치 행렬과 x의 전치 행렬의 곱이다.

역행렬

역행렬은 numpy.linalg 모듈에 포함된 inv() 함수를 이용하여 구한다.

X @ (inv(X)) 거의 항등 함수로 계산된다.

참고: 컴퓨터를 이용한 부동소수점 연산은 완벽함과 거리가 아주 멀다.

numpy.linalg 모듈에서 제공하는 선형대수 관련 함수들은 NumPy: 선형 대수에서 찾아볼 수 있다.

4.6 난수 생성 (p. 174)

가장 많이 사용된 무작위 수 생성함수 3개와 시드(seed)의 역할을 살펴본다.

np.random.randn() 함수

임의의 부동소수점을 표준 정규 분포를 따르면서 지정된 수만큼 생성한다.

np.random.rand() 함수

0과 1사의 임의의 부동소수점을 균등 분포를 따르면서 지정된 수만큼 생성한다.

np.random.randint() 함수

지정된 구간 사이에서 임의의 정수를 균등 분포를 따르면서 지정된 수만큼 생성한다.

시드(seed) 기능

위에서 살펴본 무작위 함수들은 모두 실행할 때마다 조금씩 다른 무작위수를 생성한다. 하지만 시드를 지정하면 무작위 수도 동일하게 결정된다. 시드는 컴퓨터가 사용하는 난수표(random number table)의 특정 지점을 지정하는 역할을 수행한다.

연습

붓꽃(아이리스) 데이터를 이용하여 활용법을 살펴 보기 위해 먼저 데이터를 인터넷 상에서 가져온다.

위 주소의 iris.data 파일을 datasets/iris/라는 하위 디렉토리에 저장한다.

다운로드된 iris.data 파일에는 아래 형식의 데이터가 150개 들어 있다.

5.1,3.5,1.4,0.2,Iris-setosa

하나의 데이터에 사용된 값들은 하나의 아이리스(붓꽃)에 대한 꽃잎, 꽃받침과 관련된 특성(features)과 품종을 나타내며, 보다 구체적으로 아래 순서를 따른다.

꽃받침 길이, 꽃받침 너비, 꽃잎 길이, 꽃잎 너비, 품종

이 중에 마지막 품종 특성은 문자열이고 나머지 특성은 부동소수점, 즉 수치형 데이터이다. 여기서는 연습을 위해 수치형 데이터를 담고 있는 네 개의 특성만 가져온다.

처음 5개의 샘플은 앞서 살펴본 것과 동일하다. 이번에는 다만 2차원 어레이로 보일 뿐이다.

문제: 2차원 어레이에서 결측치(nan)를 전혀 갖지 않은 행만 선택하는 함수 drop_2d()를 정의하라.

힌트: 넘파이 자체에 행을 특정 값을 갖는/갖지 않는 행을 삭제하는 기능은 없지만, is.nan() 함수와 부일 인덱싱을 활용할 수 있다.

견본 답안

iris_2d 어레이를 이용하여 drop_2d() 함수를 어떻게 정의해야 할지 살펴보자. 먼저 iris_2d 어레이에 누락치의 존재 여부를 판단해야 한다.

np.isnan() 함수는 누락치가 있는 위치는 True, 나머지 위치는 False를 갖는 부울 어레이를 생성한다.

만약 결측치가 있다면 True가 한 번 이상 사용되었기에 any() 메서드를 이용하여 누착치의 존재 여부를 판단할 수 있다.

그런데 누락치가 전혀 없다. 따라서 하나의 누락치를 임의로 만들어 보자. 예를 들어, 처음 5개 샘플의 꽃잎 너비(3번 열)의 값을 nan으로 대체하자.

이제 누락치가 존재하기에 any() 메서드는 True를 반환한다.

sum() 함수를 이용하여 5개의 누락치가 있음을 정확하게 파악할 수도 있다.

sum() 메서드를 사용할 수도 있다.

행 단위로 누락치의 존재를 찾기 위해 행별로 sum() 함수를 실행한다. 즉, 축을 1로 지정한다.

정확히 150개의 행에 대한 누락치 존재 여부를 보여준다.

이제 위 코드와 부울 인덱싱을 활용하여 누락치가 없는 행만 추출할 수 있다.

위 어레이의 처음 5개의 샘플 데이터는 iris_2d 어레이에서 5번에서 9번 인덱스에 위치한 샘플 데이터와 동일하다.

이제 drop_2d() 함수를 다음과 같이 정의할 수 있다.

iris_2d에 위 함수를 적용하면 이전과 동일한 결과를 얻는다.

문제: iris_2d 데이터셋에 사용된 붓꽃들의 품종은 아래 세 개이다.

150개의 품종을 무작위로 선택하되 Iris-setosa 품종이 다른 품종들의 두 배로 선택되도록 하라.

힌트: np.random.choice() 함수를 활용하라.

견본답안:

np.random.choice() 함수의 p 키워드 인자를 이용한다. 사용되는 인자는 [0.5, 0.25, 0.25] 이다.

세 개의 이름 중에서 무작위로 150개의 이름을 선택하였다.

품종별 비율은 대략적으로 2:1:1 이다.