5주차: NumPy 배열과 벡터 연산
이번 강의에서 배울 것
- 파이썬 리스트와 NumPy 배열(ndarray)의 차이를 설명할 수 있다.
- shape, ndim, dtype, size로 배열의 구조를 읽을 수 있다.
- 원소별 곱(*)과 행렬곱(np.dot, @)을 구분해 계산할 수 있다.
- axis를 지정해 합계·평균·최솟값과 argmin·argsort를 구할 수 있다.
- 2차원 배열을 인덱싱·슬라이싱하고 reshape·전치로 모양을 바꿀 수 있다.
학습 경로
이전: 보충 · Pandas 기본기능 총정리 · 다음: 6주차 · 정규표현식, XML, JSON
범위: 5주차 슬라이드 3–34 · Drill 501–507 · 실습 노트북 RPY_5주차_2.ipynb
단원 도입
| 핵심 포인트 | 중요도 | 이렇게 확인한다 |
|---|---|---|
*(원소별 곱)와 np.dot·@(행렬곱)의 차이 | ★★★ | 같은 두 배열의 두 결과를 손으로 계산하기 |
shape·ndim·dtype·size 읽기 | ★★★ | 1·2·3차원 배열의 속성 쓰기 (Drill 501) |
axis=0(열 방향) vs axis=1(행 방향) | ★★ | np.mean(A, axis=0) 결과 예측하기 |
2차원 슬라이싱 A[행, 열] | ★★ | 표에서 잘라낸 부분 배열 쓰기 |
| 분산과 표준편차의 관계 | ★ | np.sqrt(np.var(x)) == np.std(x) 설명하기 |
형광펜 읽는 법: 노랑 핵심 정의·결론 · 빨강 헷갈리기 쉬운 구분·함정 · 파랑 외워야 할 순서·목록
코드 읽는 법: 코드 옆 주석의 # → 뒤가 실제 실행 결과다. 슬라이드의 결과와 다르면 슬라이드 정정 박스에서 이유를 설명한다.
PART 1 · 왜 NumPy인가
NumPy란
3p
- NumPy는 scientific computing(과학 계산)을 위한 라이브러리
- 속도와 메모리 면에서 이점이 큼
- Pandas도 NumPy를 기반으로 만들어졌다 →
DataFrame의 각 열 속에는 NumPy 배열이 들어 있다
import numpy as np # 관례적인 별칭은 np리스트와 ndarray
4p~5p
list1 = ["dance", 9, 5.3, "disco", 8] # 서로 다른 자료형도 OK
list1[2] # → 5.3
list1[3] # → 'disco'
a = np.array([9, 5, 7, 6, 8])
a[1] # → 5| 구분 | 파이썬 리스트 list | NumPy 배열 ndarray |
|---|---|---|
| 요소의 자료형 | 섞여도 됨 | 모두 같은 자료형 |
| 크기 | 자유롭게 늘고 줄어듦 | 일반적으로 고정 |
| 인덱싱·슬라이싱 | 가능 | 가능 (다차원은 A[행, 열]) |
* 2의 의미 | 리스트를 두 번 이어 붙임 | 각 원소에 2를 곱함 |
| 수학 연산 | 반복문 필요 | 배열 전체에 바로 적용 |
[9, 5, 7, 6, 8] * 2 # → [9, 5, 7, 6, 8, 9, 5, 7, 6, 8]
np.array([9, 5, 7, 6, 8]) * 2 # → array([18, 10, 14, 12, 16])배열의 속성
6p~8p
a = np.array([9, 5, 7, 6, 8])
type(a) # → numpy.ndarray
a.dtype # → dtype('int64') ← 정수 배열
a.size # → 5 ← 원소 개수
a.ndim # → 1 ← 차원 수
a.shape # → (5,) ← 각 차원의 길이, 튜플
b = np.array([9.1, 5.2, 7.2, 6.3])
b.dtype # → dtype('float64') ← 실수 배열| 속성 | 뜻 | np.array([9, 5, 7, 6, 8]) |
|---|---|---|
shape | 각 차원의 길이를 담은 튜플 | (5,) |
ndim | 차원 수 = len(shape) | 1 |
size | 전체 원소 수 = shape 값들의 곱 | 5 |
dtype | 원소의 자료형 | int64 |
원소 수정과 1차원 슬라이싱
9p~10p
a = np.array([9, 5, 7, 6, 8])
a[0] = 100
a # → array([100, 5, 7, 6, 8])
a = np.array([9, 5, 7, 6, 8])
c = a[1:4]
c # → array([5, 7, 6]) ← 끝 인덱스 4는 포함하지 않음
a[3:5] = 1, 2
a # → array([9, 5, 7, 1, 2])Drill 501 · 1·2·3차원 배열의 속성
11p
a1 = np.array([1, 2, 3]) # 1차원: 벡터
a2 = np.array([[1, 2.0, 3.3],
[4, 5, 6.5]]) # 2차원: 행렬
a3 = np.array([[[1, 2, 3], [4, 5, 6], [7, 8, 9]],
[[10, 11, 12], [13, 14, 15], [16, 17, 18]]]) # 3차원
a1.shape, a1.ndim, a1.dtype, a1.size # → ((3,), 1, dtype('int64'), 3)
a2.shape, a2.ndim, a2.dtype, a2.size # → ((2, 3), 2, dtype('float64'), 6)
a3.shape, a3.ndim, a3.dtype, a3.size # → ((2, 3, 3), 3, dtype('int64'), 18)PART 2 · 배열 만들기
Drill 502 · ones, zeros와 astype
12p
ones = np.ones((10, 2)) # 10행 2열, 모두 1.0
ones.dtype # → dtype('float64')
ones.astype(int) # → 정수 1로 바뀐 새 배열
zeros = np.zeros((5, 3, 3)) # 3×3 행렬 5개, 모두 0.0
zeros.dtype # → dtype('float64')| 함수 | 만드는 배열 | 기본 dtype |
|---|---|---|
np.ones(shape) | 모든 원소가 1 | float64 |
np.zeros(shape) | 모든 원소가 0 | float64 |
np.random.random(shape) | 0 이상 1 미만의 난수 | float64 |
np.random.randint(high, size=shape) | 0 이상 high 미만의 정수 난수 | int64 |
np.linspace(start, stop, num) | 구간을 num개로 균등 분할 | float64 |
Drill 503 · 난수와 seed
13p
np.random.random((5, 3)) # 실행할 때마다 다른 0~1 실수
np.random.seed(0) # 난수 시작점 고정
np.random.randint(10, size=(5, 3))
# → array([[5, 0, 3],
# [3, 7, 9],
# [3, 5, 2],
# [4, 7, 6],
# [8, 8, 1]])
import pandas as pd
np.random.seed(0)
df = pd.DataFrame(np.random.randint(10, size=(5, 3))) # 배열 → DataFrame
df.columns = ["영수", "철수", "영희"]linspace: 구간을 균등하게 나누기
21p
np.linspace(-2, 2, num=5) # → array([-2., -1., 0., 1., 2.])
np.linspace(-2, 2, 11) # → array([-2. , -1.6, -1.2, -0.8, -0.4, 0. , 0.4, 0.8, 1.2, 1.6, 2. ])PART 3 · 기본 연산
벡터 합·차·상수배·상수 더하기
14p~15p, 18p
a = np.array([9, 5, 7, 6, 8])
b = np.array([1, 1, 1, 1, 2])
a + b # → array([10, 6, 8, 7, 10]) 벡터 합
a - b # → array([ 8, 4, 6, 5, 6]) 벡터 차
2 * a # → array([18, 10, 14, 12, 16]) 상수배
a + 1 # → array([10, 6, 8, 7, 9]) 모든 원소에 1을 더함같은 위치의 원소끼리 계산한다. 반복문이 필요 없다.
원소별 곱 vs 내적
16p~17p
a * b # → array([ 9, 5, 7, 6, 16]) element-wise 곱
np.dot(a, b) # → 43 dot product(내적)집계 메서드와 수학 함수
19p~20p, 22p
a = np.array([9, 5, 7, 6, 8])
a.mean() # → 7.0 (9+5+7+6+8)/5
a.max() # → 9
x = np.array([0, np.pi / 2]) # np.pi = π
np.sin(x) # → array([0., 1.]) sin(0), sin(π/2)Drill 504
a1 = np.array([1, 2, 3])
a2 = np.array([[1, 2.0, 3.3], [4, 5, 6.5]])
np.sum(a1) # → 6
np.mean(a2) # → 3.6333... 축을 안 주면 전체 6개 원소의 평균
np.max(a2) # → 6.5
np.min(a2) # → 1.0
np.log(a1) # → array([0. , 0.69314718, 1.09861229]) 자연로그
np.exp(a1) # → array([ 2.71828183, 7.3890561 , 20.08553692])
np.square(a1) # → array([1, 4, 9])| 종류 | 함수 | 결과 모양 |
|---|---|---|
| 집계(aggregate) | sum, mean, max, min, var, std | 값 하나 (axis를 주면 한 차원이 줄어듦) |
| 원소별(universal function) | sin, log, exp, square, sqrt | 입력과 같은 모양 |
| 위치(argument) | argmin, argmax, argsort | 값이 아니라 인덱스 |
axis: 어느 방향으로 줄일까
실습 노트북
A = np.array([[3, 3],
[4, 4]])
np.mean(A) # → 3.5 전체 평균
np.mean(A, axis=0) # → array([3.5, 3.5]) 열마다(위→아래로 내려가며) 평균
np.mean(A, axis=1) # → array([3., 4.]) 행마다(왼→오른쪽으로) 평균
np.min(A, axis=0) # → array([3, 3])Drill 505 · 분산과 표준편차
23p
high_var_array = np.array([1, 100, 200, 300, 4000, 5000])
low_var_array = np.array([2, 4, 6, 8, 10])
np.var(high_var_array), np.var(low_var_array) # → (4296133.47..., 8.0)
np.std(high_var_array), np.std(low_var_array) # → (2072.71..., 2.828...)
np.sqrt(np.var(high_var_array)) # → 2072.711623024829Drill 506 · 비교 연산과 arg 함수
24p
a1 = np.array([1, 2, 3])
a2 = np.array([[1, 2.0, 3.3], [4, 5, 6.5]])
a1 > 5 # → array([False, False, False])
a1 == a1 # → array([ True, True, True])
a1 >= a2
# → array([[ True, True, False],
# [False, False, False]])
np.argsort(a1) # → array([0, 1, 2]) 정렬했을 때의 인덱스 순서
np.argmin(a1) # → 0 최솟값의 위치
np.argmin(a2, axis=0) # → array([0, 0, 0]) 열마다 최솟값이 있는 행 번호PART 4 · 다차원 배열
2차원 배열 만들기와 속성
25p~26p
a = [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]]
A = np.array(a)
A.ndim # → 2
A.shape # → (3, 4) (행 수, 열 수)
A.size # → 12 3 × 4| 열 0 | 열 1 | 열 2 | 열 3 | |
|---|---|---|---|---|
| 행 0 | 1 | 1 | 1 | 1 |
| 행 1 | 2 | 2 | 2 | 2 |
| 행 2 | 3 | 3 | 3 | 3 |
2차원 인덱싱과 슬라이싱
27p~29p
A[0][1] # → 1 행 0, 열 1
A[2][3] # → 3 행 2, 열 3
A[2, 3] # → 3 같은 뜻, NumPy 권장 표기
A[0:2, 2] # → array([1, 2]) 행 0~1, 열 2
A[0:2, 0:2] # → array([[1, 1],
# [2, 2]]) 행 0~1, 열 0~1행렬 연산
30p~33p
A = np.array([[1, 1], [2, 2]])
B = np.array([[3, 3], [4, 4]])
A + B # → [[4, 4], [6, 6]]
A - B # → [[-2, -2], [-2, -2]]
2 * A # → [[2, 2], [4, 4]]
A * B # → [[3, 3], [8, 8]] 원소별 곱
np.dot(A, B) # → [[7, 7], [14, 14]] 행렬곱
A @ B # → [[7, 7], [14, 14]] 행렬곱의 다른 표기Drill 507 · reshape와 전치
34p
a2 = np.array([[1, 2.0, 3.3], [4, 5, 6.5]]) # shape (2, 3)
a2.reshape(2, 3, 1).shape # → (2, 3, 1) 원소 6개는 그대로, 모양만 변경
a2.T # → 행과 열을 바꾼 전치 행렬
a2.T.shape # → (3, 2)
matrix = np.random.random(size=(5, 3, 3))
matrix.shape # → (5, 3, 3)
matrix.T.shape # → (3, 3, 5) 축 순서가 거꾸로 뒤집힘정답과 해설
1. a * b → array([6, 20]), np.dot(a, b) → 2×3 + 4×5 = 26. 원소별 곱은 배열, 내적은 숫자 하나다.
2. ndim = 3, size = 4×2×3 = 24.
3. (3,). axis=1(열 방향 축)이 사라지고 행마다 하나씩 남는다.
4. array([[2, 2], [3, 3]]). 행 12, 열 12를 자른다.
5. [[1×1+2×3, 1×2+2×4], [3×1+4×3, 3×2+4×4]] = [[7, 10], [15, 22]].
6. 다르다. [[1, 2, 3], [4, 5, 6]]을 reshape(3, 2)하면 원소를 순서대로 다시 채워 [[1, 2], [3, 4], [5, 6]]이 되고, .T는 행과 열을 바꿔 [[1, 4], [2, 5], [3, 6]]이 된다.
플래시카드
5주차 — 파이썬 리스트와 NumPy 배열의 가장 큰 차이 두 가지는?
배열은 모든 원소의 자료형이 같고(섞이면 하나로 변환), 반복문 없이 배열 전체에 연산을 적용한다(벡터화). 리스트에 * 2를 하면 이어 붙이고, 배열에 * 2를 하면 각 원소에 2를 곱한다.
5주차 — shape, ndim, size, dtype은 각각 무엇인가?
shape은 각 차원의 길이 튜플, ndim은 차원 수, size는 전체 원소 수, dtype은 원소 자료형. 모두 괄호 없이 쓰는 속성이다.
5주차 — a * b와 np.dot(a, b)의 차이는?
*는 같은 자리끼리 곱한 배열(element-wise), np.dot·@는 행렬곱(1차원이면 곱해서 더한 내적 숫자 하나).
5주차 — axis=0과 axis=1로 평균을 내면 각각 무엇이 남는가?
axis=0은 위에서 아래로 접어 열마다 하나, axis=1은 왼쪽에서 오른쪽으로 접어 행마다 하나가 남는다. 지정한 축이 사라진다.
5주차 — np.random.seed(0)을 쓰는 이유는?
난수 생성의 시작점을 고정해 같은 난수열을 다시 얻기 위해서(재현성). 단, seed 이후 난수 함수를 부르는 순서까지 같아야 같은 값이 나온다.
5주차 — np.linspace(-2, 2, 5)의 결과와 range와의 차이는?
[-2., -1., 0., 1., 2.]. 끝값을 포함하고 세 번째 인자는 점의 개수다. range는 끝값을 제외하고 세 번째 인자가 간격이다.
5주차 — 표준편차와 분산의 관계는?
표준편차 = 분산의 제곱근. np.sqrt(np.var(x))는 np.std(x)와 같다.
5주차 — 브로드캐스팅이란?
모양이 다른 배열끼리 연산할 때 길이가 1이거나 빠진 차원을 자동으로 늘려 맞추는 규칙. 뒤쪽 차원부터 길이가 같거나 1이어야 한다.
핵심 점검
- NumPy 배열의 모든 원소는 이다.
np.array([9, 5, 7, 6, 8]).shape의 결과는 이다.- 원소별 곱은
*, 행렬곱은np.dot()또는 로 쓴다. np.mean(A, axis=0)은 마다 하나의 평균을 돌려준다.np.argmin()은 최솟값이 아니라 최솟값의 를 돌려준다.A[0:2, 2]에서 쉼표 앞은 , 쉼표 뒤는 이다.- 표준편차는 분산의 이다.
내용을 이해했다면 학습 완료를 체크해 주세요.