집계: 최소값, 최대값 그리고 그 밖의 모든 것

데이터 세트를 분석할 때 가장 먼저 하는 일은 다양한 요약 통계량(summary statistics)을 계산하는 것입니다. 평균과 표준편차는 데이터의 전반적인 특징을 요약해 주는 가장 대표적인 지표입니다. 하지만 이 외에도 합계, 곱, 중앙값, 최소/최대값, 분위수 등 다양한 집계 방식이 유용하게 쓰입니다.

NumPy는 배열 데이터를 빠르게 처리할 수 있는 다양한 내장 집계 함수를 제공합니다. 이번 섹션에서는 그중 주요 함수들을 직접 사용해 보겠습니다.

배열의 값 합산하기

가장 간단한 예로, 배열에 담긴 모든 값의 합계를 구해 보겠습니다. 파이썬에서는 기본 내장 함수인 sum()을 사용할 수 있습니다.

import numpy as np

rng = np.random.default_rng()
L = rng.random(100)
sum(L)

NumPy의 sum() 함수 역시 사용법은 매우 비슷하며, 위와 같은 간단한 경우 결과도 동일합니다.

np.sum(L)

하지만 NumPy 버전은 내부적으로 컴파일된 코드를 실행하기 때문에 계산 속도가 훨씬 빠릅니다.

big_array = rng.random(1000000)
%timeit sum(big_array)
%timeit np.sum(big_array)

한 가지 주의할 점은 파이썬의 sum과 NumPy의 np.sum이 완전히 똑같지는 않다는 사실입니다. 이 때문에 가끔 혼란이 생기기도 합니다. 특히 선택적 인자(optional arguments)의 의미가 서로 다릅니다. 예를 들어 sum(x, 1)은 합계를 1부터 시작해 더하라는 뜻이지만, np.sum(x, 1)은 첫 번째 축(axis 1)을 따라 더하라는 뜻입니다. 또한 np.sum은 다차원 배열의 구조를 완벽히 이해하고 작동합니다.

최소값과 최대값

파이썬에는 배열의 최소값과 최대값을 찾는 min()max() 함수가 기본으로 내장되어 있습니다.

min(big_array), max(big_array)

NumPy에서도 동일한 기능을 하는 함수를 제공하며, 역시 실행 속도가 훨씬 빠릅니다.

np.min(big_array), np.max(big_array)
%timeit min(big_array)
%timeit np.min(big_array)

min, max, sum을 비롯한 대부분의 NumPy 집계 함수는 배열 객체에서 직접 메서드 형태로 호출할 수도 있습니다.

print(big_array.min(), big_array.max(), big_array.sum())

NumPy 배열을 다룰 때는 가능하면 항상 NumPy에서 제공하는 집계 함수를 사용하는 것이 좋습니다.

다차원 집계

집계 연산은 행이나 열 단위로 수행해야 할 때가 많습니다. 다음과 같은 2차원 배열이 있다고 가정해 봅시다.

M = rng.integers(0, 10, (3, 4))
print(M)

인자를 생략하면 NumPy 집계 함수는 다차원 배열의 모든 요소에 대해 연산을 수행합니다.

M.sum()

연산을 수행할 축(axis)을 지정할 수도 있습니다. 예를 들어 axis=0을 지정하면 각 열 내에서 최소값을 찾습니다.

M.min(axis=0)

이 결과는 네 개의 열에 각각 대응하는 네 개의 최소값을 반환합니다.

마찬가지로 각 행 내에서 최대값을 찾을 수도 있습니다.

M.max(axis=1)

여기서 축을 지정하는 방식이 조금 헷갈릴 수 있습니다. axis 키워드는 결과로 나올 차원이 아니라, 축소(collapse)할 차원을 지정하는 것이라 이해하면 쉽습니다. 즉, axis=0을 지정하면 0번 축(행)을 따라 값을 합치겠다는 의미이므로, 각 열 단위로 집계가 일어납니다.

기타 집계 함수

NumPy는 이와 비슷한 방식으로 사용할 수 있는 수많은 집계 함수를 제공합니다. 또한 데이터에 결측값(NaN)이 포함되어 있을 때 이를 무시하고 계산해 주는 ‘NaN-safe’ 버전의 함수들도 함께 제공합니다(결측값 처리 섹션 참조).

다음 표는 자주 쓰이는 NumPy 집계 함수들을 정리한 것입니다.

함수 이름 NaN-safe 버전 설명
np.sum np.nansum 요소들의 합계 계산
np.prod np.nanprod 요소들의 곱 계산
np.mean np.nanmean 요소들의 평균 계산
np.std np.nanstd 표준편차 계산
np.var np.nanvar 분산 계산
np.min np.nanmin 최소값 찾기
np.max np.nanmax 최대값 찾기
np.argmin np.nanargmin 최소값의 인덱스 찾기
np.argmax np.nanargmax 최대값의 인덱스 찾기
np.median np.nanmedian 요소들의 중앙값 계산
np.percentile np.nanpercentile 요소들의 백분위수(순위 기반 통계) 계산
np.any 해당 없음 요소 중 하나라도 참인지 확인
np.all 해당 없음 모든 요소가 참인지 확인

이 함수들은 앞으로 이 책의 예제들에서 매우 자주 등장할 것입니다.

예시: 미국 대통령들의 평균 키는 얼마일까요?

NumPy 집계 함수를 사용해 실제 데이터를 요약해 보겠습니다. 역대 미국 대통령들의 키 데이터를 예로 들어보죠. 이 데이터는 data/president_heights.csv 파일에 이름과 키가 쉼표로 구분되어 저장되어 있습니다.

!head -4 data/president_heights.csv

3부에서 자세히 다룰 Pandas 패키지를 사용해 파일을 읽고 키 정보를 추출해 보겠습니다(단위는 cm입니다).

import pandas as pd

data = pd.read_csv("data/president_heights.csv")
heights = np.array(data["height(cm)"])
print(heights)

추출한 데이터를 바탕으로 다양한 요약 통계량을 계산해 보죠.

print("Mean height:       ", heights.mean())
print("Standard deviation:", heights.std())
print("Minimum height:    ", heights.min())
print("Maximum height:    ", heights.max())

위와 같이 집계 함수는 배열 전체를 하나의 대표값으로 요약하여 데이터의 전반적인 특징을 보여줍니다. 분위수를 계산해 데이터의 분포를 더 자세히 살펴볼 수도 있습니다.

print("25th percentile:   ", np.percentile(heights, 25))
print("Median:            ", np.median(heights))
print("75th percentile:   ", np.percentile(heights, 75))

데이터를 보니 미국 대통령들의 평균 키는 약 182cm로, 6피트가 조금 안 되는 수준임을 알 수 있습니다.

수치만 보는 것보다 Matplotlib 같은 도구를 사용해 시각화하면 데이터의 흐름을 훨씬 더 쉽게 파악할 수 있습니다(Matplotlib은 4부에서 자세히 다룹니다). 아래 코드를 실행해 히스토그램을 그려보겠습니다.

%matplotlib inline
import matplotlib.pyplot as plt

plt.style.use("seaborn-whitegrid")
plt.hist(heights)
plt.title("Height Distribution of US Presidents")
plt.xlabel("height (cm)")
plt.ylabel("number");