NumPy 배열 활용

인덱싱, 슬라이싱, 축, 브로드캐스팅과 파일 입출력

1. 학습 목표

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

2. 1차원 배열 인덱싱과 슬라이싱

import numpy as np


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

Python의 인덱스는 0부터 시작한다.

print(array[0])
print(array[2])
print(array[-1])

슬라이싱은 array[start:stop:step] 형식을 사용한다. stop 위치의 원소는 포함하지 않는다.

print(array[2:6])
print(array[2:6:2])
print(array[::-1])

결과는 각각 다음과 같다.

[4 3 3 4]
[4 3]
[6 5 4 3 3 4 3 3]

3. 2차원 배열 인덱싱

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

2차원 배열에서는 쉼표 앞이 행 인덱스, 뒤가 열 인덱스이다.

print(matrix[0, 0])
print(matrix[1, :])
print(matrix[:, 1])
print(matrix[0:2, 1:3])

각 결과는 다음과 같다.

[[2 3]
 [5 6]]

4. 조건을 이용한 선택

배열에 비교 연산을 적용하면 각 원소에 대한 Boolean 배열을 얻는다.

temperatures = np.array([300, 450, 700, 550])

is_high = temperatures >= 500
print(is_high)
print(temperatures[is_high])

500 K 이상인 값만 선택한 결과는 [700 550]이다. 조건식을 배열에 직접 넣을 수도 있다.

print(temperatures[temperatures >= 500])

5. 슬라이싱의 view와 copy

NumPy의 기본 슬라이싱은 원본 데이터의 일부를 공유하는 view를 반환하는 경우가 많다.

original = np.array([10, 20, 30, 40])
part = original[1:3]

part[0] = 999

print(part)
print(original)

part를 바꾸면 original도 바뀐다. 원본과 독립된 배열이 필요하면 copy()를 사용한다.

original = np.array([10, 20, 30, 40])
part = original[1:3].copy()

part[0] = 999

print(part)
print(original)

6. reshape과 전치

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

print(matrix)
print(matrix.shape)

행과 열을 바꾸려면 전치(transpose)를 사용한다.

transposed = matrix.T

print(transposed)
print(transposed.shape)

matrix의 shape은 (3, 4)이고 matrix.T의 shape은 (4, 3)이다.

7. axis를 이용한 계산

다음 배열은 두 시편에 대해 세 번씩 측정한 값이라고 하자.

measurements = np.array([
    [10.0, 12.0, 11.0],
    [20.0, 18.0, 22.0],
])

shape은 (2, 3)이다.

column_mean = measurements.mean(axis=0)
row_mean = measurements.mean(axis=1)

print(column_mean)
print(row_mean)
print(column_mean.shape)
print(row_mean.shape)

axis 번호를 외우기보다 입력 shape에서 어느 축이 사라지고 어떤 shape이 남는지 확인하면 이해하기 쉽다.

8. 브로드캐스팅

브로드캐스팅(broadcasting)은 shape이 다른 배열 사이의 연산을 가능한 형태로 확장하여 계산하는 규칙이다.

8.1. 배열과 스칼라

celsius = np.array([0.0, 25.0, 100.0])
kelvin = celsius + 273.15

print(kelvin)

스칼라 273.15가 모든 원소에 더해진다.

8.2. 각 열에 서로 다른 값 더하기

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

offset = np.array([10.0, 20.0, 30.0])
corrected = data + offset

print(corrected)

shape (3,)offset이 각 행에 반복 적용된다.

[[11. 22. 33.]
 [14. 25. 36.]]

두 배열의 마지막 축부터 크기를 비교하여, 크기가 같거나 한쪽이 1이면 브로드캐스팅할 수 있다. 처음에는 연산 전후의 shape을 출력하면서 확인하는 것이 좋다.

9. 텍스트와 CSV 파일 읽기

링크에서 파일을 내려받아 Notebook과 같은 폴더에 저장한 뒤 실습한다.

공백으로 구분된 파일은 다음과 같이 읽는다.

matrix_txt = np.loadtxt("matrix_01.txt")

print(matrix_txt)
print(matrix_txt.shape)
print(matrix_txt.dtype)

쉼표로 구분된 CSV 파일은 delimiter=","를 지정한다.

matrix_csv = np.loadtxt(
    "matrix_01.csv",
    delimiter=",",
)

print(matrix_csv)

웹 링크의 경로와 Python이 사용하는 로컬 파일 경로는 다를 수 있다. FileNotFoundError가 발생하면 현재 작업 폴더와 내려받은 파일의 위치를 확인한다.

10. 배열 저장하기

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

np.savetxt(
    "saved_matrix.txt",
    matrix,
    fmt="%.2f",
)

np.savetxt(
    "saved_matrix.csv",
    matrix,
    delimiter=",",
    fmt="%d",
)

저장한 뒤 다시 읽어 원래 shape과 값이 유지되는지 확인하는 것이 좋다.

11. 재료공학 예제

세 시편의 힘을 세 번씩 측정한 데이터가 다음과 같다고 하자.

force_n = np.array([
    [1000.0, 1020.0, 980.0],
    [1500.0, 1530.0, 1470.0],
    [2000.0, 1980.0, 2020.0],
])

area_mm2 = np.array([
    [10.0],
    [12.0],
    [20.0],
])

force_n의 shape은 (3, 3)이고 area_mm2의 shape은 (3, 1)이다. 면적값 하나가 같은 시편의 세 측정값에 브로드캐스팅된다.

stress_mpa = force_n / area_mm2
mean_stress_mpa = stress_mpa.mean(axis=1)

print(stress_mpa)
print(mean_stress_mpa)

$1\ \mathrm{N/mm^2}=1\ \mathrm{MPa}$이므로 응력 단위는 MPa이다. 각 행의 평균을 구했으므로 시편별 평균응력 세 개가 나온다.

12. 자주 만나는 오류

12.1. IndexError

배열 범위를 벗어난 위치를 선택하면 발생한다. shape과 인덱스가 0부터 시작한다는 점을 확인한다.

12.2. ValueError: cannot reshape

원소 개수와 새 shape의 원소 개수가 다를 때 발생한다.

values = np.arange(12)
print(values.size)

12개 원소는 (3, 4)로 바꿀 수 있지만 (5, 3)으로는 바꿀 수 없다.

12.3. 브로드캐스팅 오류

두 shape이 브로드캐스팅 규칙에 맞지 않으면 오류가 발생한다. 연산 전에 두 배열의 shape을 출력하여 마지막 축부터 비교한다.

13. 정리

14. 연습 문제

문제 1

다음 코드의 출력값을 쓰시오.

a = np.array([10, 20, 30, 40])
print(a[1])

문제 2

배열 a의 첫 번째부터 세 번째 원소까지 선택하는 표현을 쓰시오. 단, 세 번째 원소까지 포함한다.

문제 3

2차원 배열 A의 두 번째 열 전체를 선택하는 표현을 쓰시오.

문제 4

다음 배열에서 5보다 큰 원소만 선택하는 표현을 쓰시오.

a = np.array([2, 5, 7, 9])

문제 5

shape이 (2, 3)인 배열의 행별 평균을 구할 때 사용할 axis를 쓰시오.

문제 6

CSV 파일 data.csvnp.loadtxt()로 읽는 표현을 쓰시오.

문제 7

슬라이싱한 배열을 원본과 독립적으로 만들 때 사용하는 메서드는 무엇인가?