NumPy 배열 기초

배열 생성, 자료형, 형태와 원소별 연산

1. 학습 목표

이번 강의가 끝나면 다음을 할 수 있어야 한다.

2. NumPy가 필요한 이유

NumPy(Numerical Python)는 수치 계산에 사용하는 Python 라이브러리이다. NumPy 배열은 같은 종류의 수치 데이터를 효율적으로 저장하고, 여러 원소에 대한 계산을 간단하게 표현한다.

Python 리스트의 각 원소에 2를 곱하려면 반복문과 append()를 사용할 수 있다.

values = [1, 2, 3]
doubled = []

for value in values:
    doubled.append(2 * value)

print(doubled)

NumPy 배열에서는 배열 전체에 2를 곱할 수 있다.

import numpy as np

values = np.array([1, 2, 3])
doubled = 2 * values

print(doubled)

이처럼 배열 전체에 적용되는 연산을 벡터화된 연산(vectorized operation)이라고 한다.

3. NumPy 불러오기

NumPy는 일반적으로 np라는 짧은 이름으로 불러온다.

import numpy as np

설치되지 않았다면 터미널에서 다음 명령을 사용할 수 있다.

python -m pip install numpy

현재 사용 중인 NumPy 버전은 다음과 같이 확인한다.

print(np.__version__)

4. 배열 만들기

4.1. 리스트로부터 만들기

np.array()는 입력 데이터로 numpy.ndarray 객체를 만든다.

vector = np.array([1, 2, 3])
matrix = np.array([
    [1, 2, 3],
    [4, 5, 6],
])

print(vector)
print(matrix)

4.2. 일정한 값으로 채우기

배열의 형태(shape)는 튜플로 전달한다.

zeros = np.zeros((2, 3))
ones = np.ones((2, 3))
sevens = np.full((2, 3), 7)

print(zeros)
print(ones)
print(sevens)

np.zeros((2, 3))의 괄호가 두 겹인 이유는 (2, 3)이라는 shape 튜플을 하나의 인자로 전달하기 때문이다.

4.3. 연속된 값 만들기

np.arange()는 시작값 이상, 끝값 미만의 값을 일정한 간격으로 만든다.

even_numbers = np.arange(0, 10, 2)
print(even_numbers)

결과는 [0 2 4 6 8]이다.

np.linspace()는 시작값과 끝값을 포함하여 지정한 개수의 값을 만든다.

temperatures = np.linspace(300.0, 500.0, 5)
print(temperatures)

결과는 [300. 350. 400. 450. 500.]이다.

함수 지정하는 값 끝값 포함
np.arange(start, stop, step) 간격 포함하지 않음
np.linspace(start, stop, num) 원소 개수 기본적으로 포함

5. 배열의 주요 속성

array = np.array([
    [1, 2, 3],
    [4, 5, 6],
])

print(array.shape)
print(array.ndim)
print(array.size)
print(array.dtype)

NumPy의 배열 차원(ndim)과 물리학에서 사용하는 텐서의 rank는 같은 개념이 아니므로 구분한다.

6. 자료형

NumPy 배열은 일반적으로 한 가지 자료형의 원소를 저장한다.

integer_array = np.array([1, 2, 3])
float_array = np.array([1.0, 2.0, 3.0])

print(integer_array.dtype)
print(float_array.dtype)

정수와 실수를 함께 넣으면 실수를 보존할 수 있는 자료형으로 변환된다.

mixed = np.array([1, 2.5, 3])
print(mixed)
print(mixed.dtype)

dtype을 직접 지정할 수도 있다.

measurements = np.array(
    [1, 2, 3],
    dtype=float,
)

print(measurements)

자료형마다 표현할 수 있는 범위와 정밀도가 다르다. 처음에는 측정값처럼 소수점이 필요한 데이터에는 실수형을 사용한다는 점을 기억하면 된다.

7. 원소별 연산 (element-wise operation)

크기가 같은 두 배열에 사칙연산을 적용하면 같은 위치의 원소끼리 계산한다.

a = np.array([1.0, 2.0, 3.0])
b = np.array([4.0, 5.0, 6.0])

print(a + b)
print(a - b)
print(a * b)
print(a / b)
print(a**2)

여기서 a * b는 행렬곱이 아니라 원소별 곱이다. 행렬과 벡터의 곱은 뒤의 벡터·행렬 연산 강의에서 다룬다.

스칼라와 배열을 계산하면 모든 원소에 같은 연산을 적용한다.

stress_mpa = np.array([100.0, 150.0, 200.0])

print(stress_mpa / 1000.0)
print(stress_mpa + 10.0)

8. 집계 연산 (reduction)

여러 원소를 하나의 값으로 요약하는 계산을 집계 연산(reduction)이라고 한다.

values = np.array([3.0, 5.0, 7.0, 9.0])

print(values.sum())
print(values.mean())
print(values.min())
print(values.max())
print(values.std())

위 결과는 각각 합계, 평균, 최솟값, 최댓값과 모집단 표준편차이다.

함수 형태로도 같은 계산을 할 수 있다.

print(np.sum(values))
print(np.mean(values))

9. 배열 형태 바꾸기 (reshape)

reshape()는 원소 개수를 유지하면서 배열의 형태를 바꾼다.

values = np.arange(12)
matrix = values.reshape(3, 4)

print(values.shape)
print(matrix.shape)
print(matrix)

12개의 원소는 $3\times4$ 또는 $2\times6$으로 바꿀 수 있지만 $5\times3$으로는 바꿀 수 없다.

배열을 다시 1차원으로 펼칠 때는 ravel()을 사용할 수 있다.

flat = matrix.ravel()

print(flat)
print(flat.shape)

10. 리스트와 배열의 차이

+ 연산의 의미가 서로 다르다.

list_a = [1, 2, 3]
list_b = [4, 5, 6]

array_a = np.array([1, 2, 3])
array_b = np.array([4, 5, 6])

print(list_a + list_b)
print(array_a + array_b)

배열은 수치 계산에 편리하지만 이름, 결정구조, 설명처럼 서로 다른 종류의 데이터를 함께 저장할 때는 리스트나 딕셔너리가 더 적합할 수 있다.

11. 간단한 속도 비교

속도를 비교하려면 두 코드가 같은 데이터를 대상으로 같은 계산을 수행해야 한다.

python_values = list(range(1_000_000))
numpy_values = np.array(python_values)

Jupyter Notebook에서 다음 두 셀을 각각 실행할 수 있다.

%%timeit
sum(python_values)
%%timeit
numpy_values.sum()

결과는 컴퓨터와 NumPy 버전에 따라 달라진다. 특정 실행시간을 외우는 것이 아니라, 큰 수치 배열에서 NumPy 연산이 일반적인 Python 반복보다 효율적일 수 있음을 확인하는 실습이다.

12. 재료공학 예제

세 인장 시편의 힘과 초기 단면적이 다음과 같이 측정되었다고 하자.

force_n = np.array([1000.0, 1500.0, 2200.0])
area_mm2 = np.array([10.0, 12.0, 20.0])

stress_mpa = force_n / area_mm2

print(stress_mpa)
print(f"평균 응력: {stress_mpa.mean():.2f} MPa")

$1\ \mathrm{N/mm^2}=1\ \mathrm{MPa}$이므로 결과의 단위는 MPa이다. 배열을 사용하면 여러 시편의 응력을 한 번에 계산할 수 있다.

13. 정리

14. 연습 문제

문제 1

NumPy를 np라는 이름으로 불러오는 문장을 쓰시오.

문제 2

다음 배열의 shape, ndim, size를 구하라.

a = np.array([
    [1, 2, 3],
    [4, 5, 6],
])

문제 3

2행 3열의 모든 원소가 0인 배열을 만드는 표현을 쓰시오.

문제 4

np.arange(0, 7, 2)의 결과를 쓰시오.

문제 5

다음 코드의 결과를 쓰시오.

a = np.array([1, 2, 3])
print(2 * a)

문제 6

배열 a의 평균을 구하는 표현을 하나 쓰시오.