NumPy 배열 활용

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

Contents

1. 학습 목표

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

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

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

Python의 인덱스는, list와 마찬가지로 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])

각 결과는 다음과 같다.

4. 조건을 이용한 선택

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

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

is_high = temperatures >= 500 # 제일 왼쪽 '='가 오른쪽 조건식 결과를 assignment
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이 다른 배열 사이의 연산을 가능한 형태로 확장하여 계산하는 규칙이다. 두 배열의 마지막 축부터 크기를 비교하여, 크기가 같거나 한쪽이 1이면 브로드캐스팅할 수 있다.

8.1. 배열과 스칼라

celsius = np.array([0.0, 25.0, 100.0])
kelvin = celsius + 273.15
# 273.15의 크기가 1이므로 브로드캐스팅되어 모든 원소에 더해진다.
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])
# data의 shape (2, 3)인 배열에, shape (3,)인 offset이 각 행에 반복 적용되어 브로드캐스팅된다.
# data의 마지막 축의 크기(3)와 offset의 크기(3)가 맞아서 브로드캐스팅이 가능하다.
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],
])
# area_mm2.shape: (3,1)

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이다. 각 행의 평균을 구했으므로 시편별 평균응력 세 개가 나온다.

위의 예제를 아래와 비교해보자.

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])
## area_mm2.shape: (3,)

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

print(stress_mpa)
print(mean_stress_mpa)

12. 자주 만나는 오류

12.1. IndexError

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

12.2. ValueError: cannot reshape

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

values = np.arange(12)
print(values.size)
values.reshape(3,4)
values.reshape(5,3)  # ValueError 발생

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를 설명하시오. 즉, 각 행에 있는 값들의 평균을 구하기 위해 np.mean method에 활용할 axis argument가 무엇이어야 하나? 예를 들어, 아래와 같이 2,3 행렬이 주어질 때의 경우를 생각해보자.

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

문제 6

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

문제 7

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

이전 강의 강의 목록 다음 강의