1️⃣ dtype과 itemsize
NumPy의 배열(ndarray)은 **모든 원소가 같은 타입(dtype)**을 가져야 효율적으로 동작!!
c = np.arange(1, 5)
print(c.dtype, c)
# int64 [1 2 3 4]
c = np.arange(1.0, 5.0)
print(c.dtype, c)
# float64 [1. 2. 3. 4.]
d = np.arange(1, 5, dtype=np.complex64)
#complex64는 복소수를 의미
print(d.dtype, d)
# complex64 [1.+0.j 2.+0.j 3.+0.j 4.+0.j]
e = np.arange(1, 5, dtype=np.complex64)
# complex64비트 = 8바이트
print(e.itemsize)
# 8 (bytes)
- dtype은 해당 배열의 원소의 데이터 타입을 반환
- itemsize는 해당 배열의 각 원소의 데이터 타입의 크기를 반환(1바이트 = 8비트)
- 각 원소의 데이터 타입의 크기를 반환하기 때문에 배열의 원소의 개수에 영향을 받지 x
2️⃣ 배열의 메모리 구조 .data
f = np.array([[1,2],[1000, 2000]], dtype=np.int32)
print(f.data)
# <memory at 0x7da5477dcad0>
# 메모리 내용 확인
print(f.data.tobytes())
# b'\x01\x00\x00\x00\x02\x00\x00\x00\xe8\x03\x00\x00\xd0\x07\x00\x00'
- f는 2차원 배열이지만 실제로는 1차원으로 평탄화된 형태의 바이트 버퍼에 저장됨
- 이 바이트 버퍼에 접근하는 방법은 .data 속성을 통해 가능
3️⃣ .shape vs .reshape()
g = np.arange(24)
print(g.shape, g.ndim)
# (24,) 1
g.shape = (6, 4)
print(g.shape, g.ndim)
# (6, 4) 2
g.shape = (2, 3, 4)
print(g.shape, g.ndim)
# (2, 3, 4) 3
#-------------------------------------------
g2 = g.reshape(4, 6)
print(g2)
# [[ 0 1 2 3 4 5]
# [ 6 7 8 9 10 11]
# [12 13 14 15 16 17]
# [18 19 20 21 22 23]]
g2[1, 2] = 999
print(g2)
# [[ 0 1 2 3 4 5]
# [ 6 7 999 9 10 11]
# [12 13 14 15 16 17]
# [18 19 20 21 22 23]]
print(g)
# [[[ 0 1 2 3]
# [ 4 5 6 7]
# [ 999 9 10 11]]
#
# [[12 13 14 15]
# [16 17 18 19]
# [20 21 22 23]]
#-------------------------------------------
g2 = g.reshape(4, 6).copy()
print(g2)
# [[ 0 1 2 3 4 5]
# [ 6 7 8 9 10 11]
# [12 13 14 15 16 17]
# [18 19 20 21 22 23]]
g2[1, 2] = 999
print(g2)
# [[ 0 1 2 3 4 5]
# [ 6 7 999 9 10 11]
# [12 13 14 15 16 17]
# [18 19 20 21 22 23]]
print(g)
# [[[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
#
# [[12 13 14 15]
# [16 17 18 19]
# [20 21 22 23]]
- g.shape으로 해당 배열의 어떠한 모양의 배열인지 알 수 있음.
- 또한, g.shape = (6, 4) 와 같은 형식으로 명시적으로 배열의 모양을 변경 가능!!
- 이 경우, 기존 배열 g의 형태를 변경하는 것!! - g.reshape()의 경우 g2 = g.reshape(4, 6)과 같은 형태로 사용
- 이 경우, 기존 배열 g의 형태는 변경하지 않고, 새로운 배열 g2를 생성하여 (4, 6)의 형태로 만듬
- reshape()을 할 경우에도, 배열 g와 g2는 하나의 메모리를 공유하기 때문에, 하나가 바뀌면 다른 하나도 같이 변경됨! - g.reshape().copy()의 경우 g2 = g.reshape(4, 6).copy()와 같은 형태로 사용
- 이 경우, 기존 배열 g의 형태는 변경하지 않고, 새로운 배열 g2를 생성하여 (4, 6)의 형태로 만듬
- reshape().copy()을 할 경우에도, 배열 g와 g2는 메모리를 공유하지 않기 때문에, 하나가 바뀌어도 다른 하나도 같이 변경 x!
**.shape, .reshape() 으로 배열을 변경하더라도 , 기존 데이터의 size 원소의 총 개수는 일정하게 유지해야함!!()**
24 = 6 * 4 = 2 * 3 * 4
4️⃣ ravel()
print(g.ravel())
# [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23]
- 어떠한 형태의 배열도 1차원 배열로 반환!!
5️⃣ 산술 연산
a = np.array([14, 23, 32, 41])
b = np.array([5, 4, 3, 2])
print("a + b =", a + b)
# [19 27 35 43]
print("a - b =", a - b)
# [ 9 19 29 39]
print("a * b =", a * b)
# [70 92 96 82]
print("a / b =", a / b)
# [ 2.8 5.75 10.66666667 20.5 ]
print("a ** b =", a ** b)
# [537824 279841 32768 1681]
- 산술 연산은 두 배열 간의 모양이 같을 경우에만 가능함!!
- 만약 두 배열 간의 모양이 다르더라도, Broadcasting의 3가지 규칙에 의해 모양이 통일될 수 있을 경우 산술 연산 가능
6️⃣ Broadcasting – 3가지 규칙
# 1번째 규칙
h = np.arange(5).reshape(1, 1, 5)
print(h + [10, 20, 30, 40, 50])
# [[[10 21 32 43 54]]]
# 2번째 규칙
k = np.arange(6).reshape(2, 3)
print(k + [[100], [200]])
# [[100 101 102]
# [203 204 205]]
# 2번째 규칙
print(k + [100, 200, 300])
# [[100 201 302]
# [103 204 305]]
# 3번째 규칙(에러 발생 예)
try:
k + [33, 44]
except ValueError as e:
print(e)
# operands could not be broadcast together with shapes (2,3) (2,)
1번째 규칙: 차원이 다르면 앞에 1을 추가
- 배열 h는 (1, 1, 5)의 모양을 가지고 있고, [10, 20, 30, 40, 50] 은 (5,) 의 모양을 가지고 있음.
- 두 배열의 차원이 3, 1로 서로 다르기 때문에 차원이 1인 (5,) 앞에 1을 추가하여 (1, 1, 5) 과 같이 3차원으로 변경
- 앞에 1을 추가하는 건 가능하지만, 뒤에 1을 추가하는 건 불가!!
- [10, 20, 30, 40, 50] -> [[[10, 20, 30, 40, 50]]] - 두 배열의 모양이 같기 때문에 산술 연산 가능!!
2번째 규칙: 차원이 같으면, 가장 큰 배열 크기만큼 반복
- 배열 k는 (2, 3)의 모양을 가지고 있고, [[100], [200]] 은 (2, 1)의 모양을 가지고 있음.
- 두 배열의 차원이 2로 같지만, 모양이 다름. (2, 1) 배열을 y축으로 3번 반복 복사해서 (2, 3)으로 만듬
- [[100], [200]] -> [[100 100 100], [200 200 200]] - 두 배열의 모양이 같기 때문에 산술 연산 가능!!
** k + [100, 200, 300] **
- (3,) 의 [100, 200, 300] 을 (2, 3)인 k와 같은 모양으로 맞추어야함.
- (3,) -> (1, 3) -> (2, 3)
- [100, 200, 300] -> [[100, 200, 300]] -> [[100, 200, 300], [100, 200, 300]] - 두 배열의 모양이 같기 때문에 산술 연산 가능!!
3번째 규칙: 1, 2 번째 규칙을 적용해서 모양을 통일시킬 수 없으면, 에러 발생(산술 연산 불가능!!)
- 배열 k는 (2, 3)의 모양을 가지고 있고, [33, 44] 는 (2,)의 모양을 가지고 있음
- 1번째 규칙을 적용해 (2,) -> (1, 2)로 차원을 일치
- 2번째 규칙을 이용해 (1, 2)에서 (2, 3)으로 통일 시킬 수 없기 때문에 에러 발생!!
7️⃣ Upcasting + 데이터 타입 범위
k1 = np.arange(5, dtype=np.uint8)
k2 = k1 + np.array([5,6,7,8,9], dtype=np.int8)
print(k2.dtype, k2)
# int16 [ 5 7 9 11 13]
k3 = k1 + 1.5
print(k3.dtype, k3)
# float64 [1.5 2.5 3.5 4.5 5.5]
- 데이터 타입이 서로 다른 두 배열간의 산술 연산에서는 산술 연산 결과 배열이 연산된 두 배열의 데이터 타입 범위를 모두 포함할 수 있는 타입으로 반환!!
- ex) unit8(0 ~ 255) + int 8(-128 ~ 127) = int 16(-32,768 ~ 32,767) 으로 반환
| dtype | 비트 | 범위 |
| int8 | 8 | -128 ~ 127 |
| uint8 | 8 | 0 ~ 255 |
| int16 | 16 | -32,768 ~ 32,767 |
| float64 | 64 | ±1.8e308 |
8️⃣ 조건 연산 & boolean indexing
m = np.array([20, -5, 30, 40])
print(m < 25)
# [True True False False]
print(m[m < 25])
# [20 -5]
- m < 25 와 같이 사용하면 [True True False False] 와 같은 boolean indexing으로 반환
- m[m < 25] = m[True True False False] = [20, -5]
9️⃣ ndarray 메서드
a = np.array([[-2.5, 3.1, 7], [10, 11, 12]])
print(a.mean())
# 6.766666666666667
for func in (a.min, a.max, a.sum, a.prod, a.std, a.var):
print(func.__name__, '=', func())
# min = -2.5
# max = 12.0
# sum = 40.6
# prod(배열 전체의 곱) = -71610.0
# std(표준편차) = 5.0848...
# var(분산) = 25.8555...
c = np.arange(24).reshape(2, 3, 4)
print(c)
# [[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11]]
#
# [[12, 13, 14, 15],
# [16, 17, 18, 19],
# [20, 21, 22, 23]]
c.sum(axis=0)
# [12, 14, 16, 18],
# [20, 22, 24, 26],
# [28, 30, 32, 34]
c.sum(axis=1)
# [12, 15, 18, 21],
# [48, 51, 54, 57]
c.sum(axis=(0,2))
# [60, 92, 124]
# [실습] ndarray c에 대해서 x-axis 방향으로 max 함수가 적용되도록 코드 작성
# result = c.max(axis=2)
# [[ 3 7 11]
# [15 19 23]]
- 배열의 전체 원소의 값들에 대해서 min, max, sum 등의 ndarray 메서드를 적용할 수 있음.
- c.shape == (2, 3, 4)
- axis=0: 크기 2 → 총 2개의 "매트릭스(z축)" -> (3, 4) 모양으로 반환
- axis=1: 크기 3 → 각 매트릭스에 3개의 "행(y축)" -> (2, 4) 모양으로 반환
- axis=2: 크기 4 → 각 행에 4개의 "열(x축)" -> (2, 3) 모양으로 반환
- c.sum(axis=0)
- c[0][0][0] + c[1][0][0] = 0 + 12 = 12
- c[0][2][3] + c[1][2][3] = 11 + 23 = 34 - c.sum(axis=1)
- 0+4+8 = 12, 1+5+9 = 15, ... - c.sum(axis=(0,2))
- y = 0: [0,1,2,3] + [12,13,14,15] → 합 = 60
- y = 1: [4~7] + [16~19] → 합 = 92
- y = 2: [8~11] + [20~23] → 합 = 124
- (3, ) 모양으로 반환
🔟 Universal Functions (ufuncs) - 범용 함수
# 원본 배열
a = np.array([[-2.5, 3.1, 7], [10, 11, 12]])
# 제곱
print(np.square(a))
# [[ 6.25 9.61 49. ]
# [100. 121. 144. ]]
# 절댓값
print(np.abs(a))
# [[2.5 3.1 7.],
# [10. 11. 12.]]
# 제곱근 (음수는 nan 발생)
print(np.sqrt(a))
# [[ nan 1.76068169 2.64575131]
# [3.16227766 3.31662479 3.46410162]]
# 지수 함수 (e^x)
print(np.exp(a))
# [[0.082 22.19 1096.63],
# [22026.46 59874.14 162754.79]]
# 자연로그 (음수는 nan)
print(np.log(a))
# [[nan 1.13 1.94],
# [2.30 2.39 2.48]]
# 부호 반환: 음수→-1, 양수→1, 0→0
print(np.sign(a))
# [[-1. 1. 1.],
# [1. 1. 1.]]
# 올림 (ceil)
print(np.ceil(a))
# [[-2. 4. 7.],
# [10. 11. 12.]]
# 정수부, 소수부 분리
frac, integer = np.modf(a)
print("소수부:\n", frac)
print("정수부:\n", integer)
# NaN 여부 확인
print(np.isnan(a))
# 모두 False
# 코사인 (라디안 기준)
print(np.cos(a))
# [[-0.80 -0.999 0.75], [-0.839 0.004 0.843]]
1️⃣1️⃣ Binary ufunc
a = np.array([1, -2, 3, 4])
b = np.array([2, 8, -1, 7])
print(np.add(a, b))
# [ 3 6 2 11]
print(np.greater(a, b))
# [False False True False]
print(np.maximum(a, b))
# [2 8 3 7]
print(np.copysign(a, b))
# [ 1. 2. -3. 4.]
# a의 절대값을 유지하면서, b의 부호를 따라감
1️⃣2️⃣ 배열 인덱싱
a = np.array([1, 5, 3, 19, 13, 7, 3])
print(a[3])
# [19]
print(a[2:5])
# [ 3 19 13]
print(a[2:-1])
# [ 3, 19, 13, 7]
print(a[:2])
# [1, 5]
print(a[2::2]) # 2번째 인덱스부터 마지막까지 2간격으로
# [ 3, 13, 3]
print(a[::-1]) # reverse order, a는 그대로 남아 있음
# [ 3, 7, 13, 19, 3, 5, 1]
a[2:5] = -1
print(a)
# [ 1 5 -1 -1 -1 7 3]
a[2:5] = [997, 998, 999]
print(a)
# [ 1 5 997 998 999 7 3]
- a[2:5] 의 경우 끝 인덱스인 5는 포함 x
1️⃣3️⃣ Python 배열과의 차이
a[2:5] = -1
# 슬라이스 전체에 -1 적용됨 (broadcast)
# 에러 예시
# a[2:5] = [1,2,3,4,5,6] → ValueError
# del a[2:5] → 삭제 불가
#----------------------------------------------------------------------------------------------
a = np.array([1, 5, -1, 4, -1, 7, 3])
# 슬라이싱으로 뷰(view) 생성
a_slice = a[2:6] # [-1, 4, -1, 7]
a_slice[1] = 1000 # a[3]이 1000으로 바뀜!
print(a) # → 원본 배열도 바뀜!
# [ 1, 5, -1, 1000, -1, 7, 3]
# 반대로 원본을 수정하면 slice도 영향을 받음
a[3] = 2000
print(a_slice) # → 슬라이스도 같이 바뀜!
# [-1, 2000, -1, 7]
#----------------------------------------------------------------------------------------------
another_slice = a[2:6].copy()
another_slice[1] = 3000 # 복사본만 바뀜!
print(a) # 원본 배열 영향 없음
# [1, 5, -1, 2000, -1, 7, 3]
a[3] = 4000
print(another_slice) # 복사본 유지됨
# [-1, 3000, -1, 7]
1️⃣4️⃣ Multidimensional Indexing
b = np.arange(48).reshape(4, 12)
print(b)
# [[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
# [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
# [24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35],
# [36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]]
print(b[1, 2])
# 14
b[1, :]
# array([12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23])
# shape (12,)
b[:, 1]
# array([ 1, 13, 25, 37])
# shape (4,)
print(b[1, :])
# [12 13 14 15 16 17 18 19 20 21 22 23]
# 벡터 (1차원)
print(b[1:2, :])
# [[12 13 14 15 16 17 18 19 20 21 22 23]]
# 행이 1개인 2차원 배열
1️⃣5️⃣ Fancy Indexing
b = np.arange(48).reshape(4, 12)
print(b)
# [[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
# [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
# [24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35],
# [36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]]
print(b[(0,2), 2:5]) # 행 0과 2, 열 2부터 4까지 (2:5)
# [[ 2 3 4]
# [26 27 28]]
print(b[:, (-1, 2, -1)]) # 모든 행, 열 [-1, 2, -1] 순서대로 선택
# [[11 2 11]
# [23 14 23]
# [35 26 35]
# [47 38 47]]
print(b[(-1, 2, -1, 2), (5, 9, 1, 9)]) # (행, 열)을 각각 짝지어서 개별 좌표 선택
# [41, 33, 37, 33]
1️⃣6️⃣ 고차원 인덱싱
c = b.reshape(4,2,6)
print(c)
# [[[ 0, 1, 2, 3, 4, 5],
# [ 6, 7, 8, 9, 10, 11]],
#
# [[12, 13, 14, 15, 16, 17],
# [18, 19, 20, 21, 22, 23]],
#
# [[24, 25, 26, 27, 28, 29],
# [30, 31, 32, 33, 34, 35]],
#
# [[36, 37, 38, 39, 40, 41],
# [42, 43, 44, 45, 46, 47]]]
c[2, 1, 4] # matrix 2, row 1, col 4
# 34
c[2, :, 3] # matrix 2, all rows, col 3
# [27, 33]
c[2, 1] # Return matrix 2, row 1, all columns. This is equivalent to c[2, 1, :]
# [30, 31, 32, 33, 34, 35]
1️⃣7️⃣ Ellipsis (...) 와 Boolean Indexing
c = b.reshape(4,2,6)
print(c)
# [[[ 0, 1, 2, 3, 4, 5],
# [ 6, 7, 8, 9, 10, 11]],
#
# [[12, 13, 14, 15, 16, 17],
# [18, 19, 20, 21, 22, 23]],
#
# [[24, 25, 26, 27, 28, 29],
# [30, 31, 32, 33, 34, 35]],
#
# [[36, 37, 38, 39, 40, 41],
# [42, 43, 44, 45, 46, 47]]]
print(c[2, ...]) # matrix 2, all rows, all columns. This is equivalent to c[2, :, :]
# [[24 25 26 27 28 29]
# [30 31 32 33 34 35]]
print(c[2, 1, ...]) # matrix 2, row 1, all columns. This is equivalent to c[2, 1, :]
# [30, 31, 32, 33, 34, 35]
print(c[2, ..., 3]) # matrix 2, all rows, column 3. This is equivalent to c[2, :, 3]
# [27, 33]
print(c[..., 3]) # matrix 2, row 1, all columns. This is equivalent to c[2, 1, :]
# [[ 3 9]
# [15 21]
# [27 33]
# [39 45]]
#----------------------------------------------------------------------------------------------
b = np.arange(48).reshape(4, 12)
print(b)
# [[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
# [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
# [24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35],
# [36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]]
rows_on = np.array([True, False, True, False])
cols_on = np.array([False, True, False] * 4)
print(b[rows_on, :]) # Rows 0, 2 출력
# [[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
# [24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]]
print(b[:, cols_on]) # Columns 1, 4, 7, 10 출력
# [[ 1, 4, 7, 10],
# [13, 16, 19, 22],
# [25, 28, 31, 34],
# [37, 40, 43, 46]]
1️⃣8️⃣np.ix_()
b = np.arange(48).reshape(4, 12)
print(b)
# [[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
# [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
# [24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35],
# [36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]]
rows_on = np.array([True, False, True, False])
cols_on = np.array([False, True, False] * 4)
print(b[np.ix_(rows_on, cols_on)])
# [[ 1 4 7 10]
# [25 28 31 34]]
print(b % 3 == 1) # 배열 b의 각 원소에 대해서 b % 3 == 1 을 수행해서 참 거짓 반환
# [[False, True, False, False, True, False, False, True, False, False, True, False],
# [False, True, False, False, True, False, False, True, False, False, True, False],
# [False, True, False, False, True, False, False, True, False, False, True, False],
# [False, True, False, False, True, False, False, True, False, False, True, False]]
print(b[b % 3 == 1]) # True 인것들만 반환
# [1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43, 46]
1️⃣9️⃣실습 예제
c = b.reshape(4,2,6)
print(c)
# [[[ 0, 1, 2, 3, 4, 5],
# [ 6, 7, 8, 9, 10, 11]],
#
# [[12, 13, 14, 15, 16, 17],
# [18, 19, 20, 21, 22, 23]],
#
# [[24, 25, 26, 27, 28, 29],
# [30, 31, 32, 33, 34, 35]],
#
# [[36, 37, 38, 39, 40, 41],
# [42, 43, 44, 45, 46, 47]]]
# 1. matrix 1, 3의 row 1 역순
print(c[(1,3), 1, ::-1])
# [[23 22 21 20 19 18]
# [47 46 45 44 43 42]]
# 2. 모든 matrix의 row 0의 col 1,2,5
print(c[:, 0, [1,2,5]])
# [[ 1 2 5]
# [13 14 17]
# [25 26 29]
# [37 38 41]]
# 3. 짝수이면서 30 이상
print(c[(c % 2 == 0) & (c >= 30)])
# [30 32 34 36 38 40 42 44 46]
'AI(ML & DL)' 카테고리의 다른 글
| [기계학습] Python NumPy 란 ?? ( 4 ) (0) | 2025.03.31 |
|---|---|
| [기계학습] Python NumPy 란 ?? ( 3 ) (0) | 2025.03.28 |
| [기계학습] Python NumPy란?? ( 1 ) (0) | 2025.03.21 |
| [기계학습] ML(Machine Learning)의 주요 과제(데이터, 알고리즘 문제) (1) | 2025.03.17 |
| [기계학습] ML(Machine Learning) 시스템의 종류 (0) | 2025.03.14 |