개발

파이썬: NumPy에 대해 알아보기

Funital01 2026. 4. 15. 18:29
목록

1. NumPy

2. NumPy 배열 생성하기

3. 다양한 배열 생성 방법 - zeros, ones, arange, eye

4. 배열의 속성 찾기 - dtype, ndim, shape

5. 행렬 형태&타입 바꾸기 - reshape(), T, transpose(),  ravel(), astype()

6. 인덱싱/슬라이싱

7. 벡터화 연산

8. 기본 통계 함수 & Axis

NumPy

NumPy는 AI와 머신러닝에서 핵심적으로 사용되는 라이브러리입니다.

특히 대량의 데이터를 빠르게 처리해야 하는 분야에서 매우 중요한 역할을 합니다.

 

예를 들어 의료 AI와 같이 속도와 정확성이 중요한 분야에서는

데이터를 얼마나 빠르고 효율적으로 처리하느냐가 큰 차이를 만들어낼 수 있어요!

 

NumPy의 가장 큰 강점이 바로 연산 속도와 메모리 효율성입니다.

 

일반적인 파이썬 리스트는 각 요소가 개별 객체로 저장되고,

값 자체가 아닌 참조(주소)를 기반으로 관리됩니다.

반면에 NumPy 배열 연속된 메모리 공간에 데이터를 저장하기 때문에

데이터 접근과 연산 속도가 훨씬 빠릅니다.

 

실제로 속도 테스트를 통해 확인해봤습니다.

import numpy as np	# Numpy
import time
import random

# 파이썬 배열
data=[ random.uniform(100, 200) for i in range(100000)]
# Numpy 배열
ndata=np.array(data)

# 파이썬 배열 사용
start=time.time()
result=[item+10 for item in data]
end=time.time()
print(end-start, result[0])	# 0.02859663963317871 206.88789289423505

# Numpy 배열 사용
start=time.time()
ndata=ndata+10
end=time.time()
print(end-start, ndata[0])	# 0.0034132003784179688 206.88789289423505

 

100부터 200 사이의 랜덤한 숫자 10만개의 데이터에 대해,

각 인덱스별 값에 10을 더하는 연산수행을 파이썬 배열과 Numpy 배열 모두 진행을 해봤습니다.

 

실행 결과를 보면,

NumPy 배열을 사용한 경우가 파이썬 리스트보다 훨씬 빠른 수행 속도를 보이는 것을 확인할 수 있습니다.

 

여기서, NumPy를 사용하기 위해서는 다음과 같이 라이브러리를 불러오면 됩니다.

import numpy as np

 

np라는 별칭을 사용해서 간편하게 접근하도록 설정도 가능합니다!

 

NumPy 배열 생성하기

그렇다면 NumPy 배열은 어떻게 생성할 수 있을까요?

NumPy에서는 np.array()를 사용하여 배열을 만들 수 있습니다.

import numpy as np

ndata=np.array([10,20,30,40]) # 정수형 타입
print(ndata)	# [10 20 30 40]

ndata=np.array([10,20,30,40], dtype=np.float32) # 실수형 타입
print(ndata)	# [10. 20. 30. 40.]

 

이처럼 np.array()에 리스트를 넣으면 배열이 생성되고,

dtype 옵션을 통해 원하는 데이터 타입을 직접 지정할 수도 있습니다.

 

 

array() 안에서 대괄호([])의 구조에 따라 차원이 결정이 되는데요!

 

array() 값 안에 대괄호 []를 하나만 사용한다면,

이는 1차원 배열이라고 하고, 

2차원, 3차원 배열로도 지정이 가능합니다.

# 2차원
ndata2=np.array([[10,20,30,40],[100,200,300,400]])
# 3차원
narray=np.array([[[10,20,30,40],[100,200,300,400]],[[70,80,90,100],[100,200,300,400]]])

 

즉, 중첩된 리스트 구조로 다차원 배열(행렬)을 표현할 수 있습니다.

 

다양한 배열 생성 방법 - zeros, ones, arange, eye

직접 데이터 값을 지정해서 넣을 수도 있지만,

특정 값으로 채워진 배열을 빠르게 생성할 수 있는 다양한 함수를 제공합니다.

 

모든 요소가 0인 배열을 생성할 수 있는데요,

zero_data=np.zeros((2,3))  # 0으로 채우기
print(zero_data)

#=== 출력값 ===
# [[0. 0. 0.]
#  [0. 0. 0.]]

np.zeros(shape)를 사용해서 모든 요소가 0인 배열을 만들어 낼 수 있습니다.

 

이번에는 모든 요소가 1인 배열을 생성할 수도 있습니다.

이때는 np.ones(shape)를 사용하면 됩니다.

one_data=np.ones((2,3))
print(one_data)

# === 출력값 ===
# [[1. 1. 1.]
#  [1. 1. 1.]]

one_data=np.ones((2,3,2))
print(one_data)

# === 출력값 ===
# [[[1. 1.]
#   [1. 1.]
#   [1. 1.]]
# 
#  [[1. 1.]
#   [1. 1.]
#   [1. 1.]]]

 

 

원하는 값으로도 모든 요소를 지정한 값으로 채우는 배열을 생성할 수도 있는데,

이때는 np.full(shape, 값) 을 사용하면 됩니다.

full_data=np.full((2,3),7)
print(full_data)

#=== 출력값 ===
# [[[1. 1.]
#   [1. 1.]
#   [1. 1.]]

 

 

위에서 살펴본 zeros(), ones(), full()과 같은 배열 생성 함수들은

2차원 배열뿐만 아니라 3차원 이상의 다차원 배열 생성에도 동일하게 사용할 수 있습니다.

 

 

 

추가적으로, 연속된 숫자 배열을 생성할 수도 있습니다.

t2=np.arange(1,10)
print(t2)	# [1 2 3 4 5 6 7 8 9]

 

이는, 파이썬에서 range() 함수로 연속된 숫자 배열을 생성한 방식과 유사하다는 것을 확인할 수 있었어요!

 

이어서, 연속된 숫자 값에서 균등한 간격으로 해당 개수의 값만 나오도록도 설정을 할 수가 있습니다.

# linespace(시작값, 끝값, 개수)
t1=np.linspace(1,10,4)  # 라인 간격
print(t1)	# [ 1.  4.  7. 10.]

 

코드와 같이, 1부터 10까지 숫자가 있는 리스트를 4개의 아이템이 들어가도록

균등한 간격으로 나뉘어서 출력되는 것을 확인할 수 있었습니다.

 

이에 더해,

선형대수학을 공부하다 보면

단위행렬(Identity Matrix)이라는 개념을 자주 접하게 됩니다.

보통 기호 I로 표시를 하죠.

 

단위행렬은 대각선 요소는 1이고, 나머지는 모두 0인 정사각 행렬로,

어떤 행렬과 곱해도 원래의 값을 유지한다는 성질을 갖고 있어요.

 

Numpy에서는 이 단위행렬을 간단하게 eye() 함수를 통해 생성이 가능합니다.

e1=np.eye(2)
print(e1)
# === 출력값 ===
# [[1. 0.]
#  [0. 1.]]

e2=np.eye(3)
print(e2)
# === 출력값 ===
# [[1. 0. 0.]
#  [0. 1. 0.]
#  [0. 0. 1.]]

 

예를 들어, np.eye(2)는 2×2 단위행렬을, np.eye(3)은 3×3 단위행렬을 생성하는 것을 확인할 수 있습니다.

 

즉, eye(n)은 크기가 nxn 단위행렬을 만들어주고,

선형대수 계산이나 머신러닝에서 행렬 연산을 할때 자주 사용하게 됩니다!

 

 

배열의 속성 찾기 - dtype, ndim, shape

지금까지 다양한 방법으로 NumPy 배열을 생성해보았는데요,

이제는 생성한 배열의 데이터 구조를 파악하는 방법도 알아봐야하겠죠?

narray=np.array([[10,20,30,40],[100,200,300,400]])
print(narray.dtype) # int64
print(narray.ndim)  # 2
print(narray.shape) # (2, 4)

 

만약 데이터 타입에 대해 확인을 하고 싶다면 dtype을 사용해 파악할 수 있고,

배열이 몇 차원인지를 구분하고 싶다면 ndim을 사용해 구분할 수 있고,

shape를 통해 배열의 형태를 파악해 행과 열의 개수가 얼마인지를 확인할 수 있습니다.

NumPy 배열 구조를 쉽게 확인할 수 있는 주요 속성들을 다음과 같습니다.

  • dtype : 배열에 저장된 데이터의 자료형을 확인할 수 있습니다.
  • ndim : 배열이 몇 차원인지를 나타내 구분할 수 있습니다.
  • shape : 배열의 구조(행,열)를 튜플 형태로 보여줍니다.

 

행렬 형태&타입 바꾸기 - reshape(), T, transpose(),  ravel(), astype()

NumPy에서는 np.array()로 생성한 배열의 형태(shape)를 변경할 수도 있어요.

이때 사용하는 메서드가 바로 reshape()입니다.

reshape()는 배열의 데이터는 그대로 유지하고, 행과 열의 구조만 지정해준대로 바꿔주는 역할을 합니다.

ndata=np.array([1,2,3,4,5,6])
print(ndata.ndim)	# 1

# reshape(행,열)
change_data=ndata.reshape(2,3)  # 2행 3열
# === 출력값 ===
# [[1 2 3]
#  [4 5 6]]

# 행벡터
t1=change_data.reshape(1,6) # [[1 2 3 4 5 6]]
print(t1.ndim)	# 2

# 열벡터
t2=change_data.reshape(6,1) # 열벡터
# === 출력값 ===
# [[1]
#  [2]
#  [3]
#  [4]
#  [5]
#  [6]]
print(t2.ndim)	# 2

 

예를 들어, 1차원 배열인 [1, 2, 3, 4, 5, 6] reshape(2, 3)을 통해 2행 3열의 2차원 배열로 변환할 수 있습니다.

이 과정에서 전체 데이터의 개수는 반드시 동일해야 한다는 점 꼭 주의해주세요! 

즉, 다양한 형태로 재구성할 수는 있지만, 개수가 안맞으면 오류가 발생해요.

 

각 구조의 차원을 출력한 값에서 특이한 점을 발견하지 않았나요?

분명 reshape()를 하기 전에는 1차원 벡터로 출력이 되었는데,

reshape()를 하고 나니 2차원 행렬로 바뀐 형태임을 확인할 수 있습니다.

 

reshape()를 사용하면 단순히 형태만 바뀌는 것이 아니라 차원 자체도 변경이 되기에,

이 부분도 참고하시면 좋을 것 같아요!

 

⚠️ 참고

반대로, 이렇게 만들어진 다차원 배열을 다시 1차원으로 되돌리고 싶을 때는
 ravel() 메서드를 사용할 수 있습니다. 

ravel()은 배열을 평탄화해서 하나의 1차원 배열로 만들어 줍니다.
print(t1.ravel())	# [1 2 3 4 5 6]
print(t2.ravel())	# [1 2 3 4 5 6]​

 

 

 

행과 열의 위치를 서로 바꾸는 연산은 다른 방식으로도 수행할 수 있습니다.

마찬가지로, 선형대수학에서 자주 등장하는 전치(Transpose, T) 개념입니다.

전치는 행렬의 행과 열을 서로 뒤바꾸는 연산을 의미합니다.

 

NumPy에서는 배열에 .T 속성을 사용하거나, np.transpose() 함수를 통해 전치 연산을 수행할 수 있고,

두 방법 모두 동일하게 결과를 출력하게 됩니다.

p=np.array([1,2,3,4])
p1=p.T
print(p1)	# [1 2 3 4]

p2=p.reshape(1,-1)
p3=p2.T
print(p3)
# === 실행값 ===
# [[1]
#  [2]
#  [3]
#  [4]]

p4=np.transpose(p2)
print(p4)
# === 실행값 ===
# [[1]
#  [2]
#  [3]
#  [4]]

 

다만, 주의해야할 점이 있는데요!

1차원 배열에서는 전치를 적용해도 형태가 변하지 않습니다.😮

예를 들어, [1, 2, 3, 4]와 같은 1차원 배열에 .T를 적용해도 여전히 1차원 구조이기 때문에 변화가 없습니다.

 

이러한 이유는 NumPy에서 1차원 배열은 행과 열의 개념이 명확하게 구분이 안되기 때문입니다.

즉, 전치를 적용할 “행과 열” 자체가 정의되어 있지 않기 때문에 결과가 동일하게 유지됩니다.

 

그래서, 먼저 reshape()를 사용해 2차원 배열로 변환해줘야 원하던 전치가 적용됩니다.

T 말고도 transpose()를 사용해서도 동일한 기능을 제공하는 것도 확인할 수 있었습니다.

 

추가로, reshape()에서 -1은 전체 데이터 개수를 유지하면서 나머지 한 축의 크기를 자동으로 맞춰달라는 의미를 갖습니다.

코드와 같이 전체 원소 개수는 4개이고, reshape(1,-1)로 변환할 때, 1행은 고정하기에 열 개수는 자동으로 맞춰달라고 하는 것이고,

결과와 같이 자동으로 4가 되는 것을 확인할 수 있습니다.

 

 

정리하자면,

reshape(), T, transpose()는 배열의 구조를 변경할 때, 

ravel()은 다차원 배열을 다시 1차원으로 되돌릴 때 사용하는 메서드!

 

나중에 데이터를 다양한 형태로 다룰 때 많이 사용이 되기 때문에 반드시 숙지해야될 것 같아요.🧐

 

 

지금까지 행렬의 구조를 변경하는 방법에 대해 바꿔봤다면,

행렬의 데이터 타입을 변경하는 방법도 알아봐야겠죠?

 

NumPy에서는 astype() 메서드를 사용해 배열의 각 요소 타입을 원하는 형태로 변환할 수 있습니다.

# astype()
n=np.array([1,2,3,4])
print(n, n.dtype)	# [1 2 3 4] int64

n2=n.astype(np.float32)
print(n2, n2.dtype)	# [1. 2. 3. 4.] float32

 

예를 들어, [1, 2, 3, 4]와 같은 정수형 배열은 기본적으로 int64 타입을 가지지만,

astype(np.float32)를 사용하면 모든 요소를 float32 타입으로 변환할 수 있습니다.

 

이때 주의해야할 점은, astype() 기존 배열을 직접 변경하는 것이 아니기 때문에,

변환된 결과를 사용하려면 새로운 변수에 다시 할당해줘야 합니다!

 

 

연산 목적에 따라 타입을 변경해야 하는 상황에서 매우 유용하게 활용할 수 있을 것 같아요🙂

 

인덱싱/슬라이싱

NumPy에서는 1차원 배열뿐만 아니라 2차원 행렬에서도 인덱싱과 슬라이싱을 자유롭게 사용할 수 있습니다.

patients_matrix = np.array([
    [45, 70.5, 175, 120, 80],   # 환자1
    [32, 55.0, 162, 110, 75],   # 환자2
    [67, 82.3, 168, 140, 90],   # 환자3
    [28, 48.7, 155, 105, 70],   # 환자4
])

print(patients_matrix[1])	# [ 32.  55. 162. 110.  75.]
print(patients_matrix[1:3])	# [[ 32.   55.  162.  110.   75. ]
                                #  [ 67.   82.3 168.  140.   90. ]]
print(patients_matrix[0][1:4])	# [ 70.5 175.  120. ]
print(patients_matrix[0,1:4])	# [ 70.5 175.  120. ]
print(patients_matrix[1:3,1:4])	# [[ 55.  162.  110. ]
                                #  [ 82.3 168.  140. ]]

 

예를 들어, patients_matrix[1]은 두 번째 행 전체를 가져오며,

 patients_matrix[1:3]은 두 번째부터 세 번째 행까지를 슬라이싱합니다.

 

특정 행의 일부 열만 선택하고 싶다면 patients_matrix[0][1:4]처럼 사용할 수 있고,

추가적인 동일한 방식은 patients_matrix[0, 1:4]처럼 한 번에 행과 열을 함께 지정하는 방법입니다.

 

또한 patients_matrix[1:3, 1:4]와 같이 사용하면, 여러 행과 열을 동시에 선택할 수 있습니다.

 

정리하자면, 대괄호 [] 안에서

  • ,를 기준으로 앞쪽은 행, 뒤쪽은 열을 의미하며
  • 슬라이싱(:)을 통해 원하는 범위를 선택할 수 있습니다.

 

또한, 조건부 인덱싱(Boolean Indexing)을 통해 특정 조건을 만족하는 데이터만 한 번에 추출할 수도 있습니다.

score=np.array([100,90,60,70])

# 과정1
result=score>=80
print(result)	# [ True  True False False]
# 과정2
data_result=score[result]
print(data_result)	# [100  90]

# 이 과정을 한번에 쓰기(과정1+2)
data_result2=score[score>=80] 
print(data_result2)	# [100  90]

 

score >= 80과 같은 조건식은 True/False 배열을 생성하고,

이를 다시 배열에 적용하면 조건을 만족하는 값만 선택할 수 있습니다.

 

이 과정을 score[score >= 80]처럼 한 줄로 간단하게 표현할 수 있어 매우 편리합니다.

 

벡터화 연산

각 행렬의 값을 for문을 사용하지 않고 모든 요소들에 특정 값을 연산을 해줄 수도 있는데요,

이를 벡터화 연산이라고 합니다.

d1=np.arange(1,10).reshape(3,3)
print(d1)
# === 출력값 ===
# [[1 2 3]
#  [4 5 6]
#  [7 8 9]]

d2=d1+10
print(d2)
# === 출력값 ===
# [[11 12 13]
#  [14 15 16]
#  [17 18 19]]

d3[0]=d3[0]*2
print(d3)
# === 출력값 ===
# [[22 24 26]
#  [14 15 16]
#  [17 18 19]]

 

예를 들어, d1 + 10과 같이 작성하면 배열의 모든 값에 10이 더해집니다.

 

이러한 방식은 코드가 간결해지고, 실행 속도 또한 크게 향상되는 장점이 있습니다.

 

기본 통계 함수 & Axis

Numpy에는 기본적인 통계 함수들도 제공을 합니다.

data=np.array([10,20,30,40,50,60]).reshape(2,3)

# 최소값
d=np.min(data)
print(d)

# 최대값
d2=np.max(data)
print(d2)

# 평균값
d3=np.mean(data)
print(d3)

# 표준편차값
d4=np.std(data) # (10-35)^2+...
print(d4)

 

np.min(), np.max(), np.mean(), np.std() 등을 사용하면

최소값, 최대값, 평균, 표준편차를 손쉽게 구할 수 있습니다.

 

 

또한 argmin() argmax()를 사용하면 각각 최솟값과 최댓값이 위치한 인덱스를 반환받을 수 있어,

데이터의 위치 정보를 파악하는 데에도 유용합니다.

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

result=np.argmax(array)
print(result)	# 5

result2=np.argmin(array)
print(result2)	# 0

 

예를 들어, 1부터 6까지 있는 배열에서,

가장 큰값인 6의 위치는 5이고, 가장 작은 값의 위치인 0이 출력되는 것을 확인할 수 있었습니다.

 

즉, argmin은 배열에서 최솟값이 위치한 인덱스를 반환해주고,

argmax는 배열에서 최댓값이 위치한 인덱스를 반환해줍니다!

 

 

추가적으로, 다차원 배열에서 어느 방향으로 연산을 수행할지를 지정하는 axis 라는 축의 개념이 있습니다.

 

2차원 배열 기준으로 보면,

 axis의 값은 0또는 1의 값을 가질 수 있는데요.

 

axis=0이면 세로 방향, 즉 열별 통계의 연산을 수행하고자 할때 사용하시면 되고,

axis=1이면 가로방향, 즉 행별 통계 연산을 수행하고자 할 때 사용하시면 됩니다.

 

추가로, 다차원의 경우, n-1 값의 방향값을 지정해줄 수 있답니다!

 

data=np.array([10,20,30,40,50,60]).reshape(2,3)

d=np.min(data)
print(d)	# 10

d_axis0=np.min(data, axis=0)
print(d_axis0)	# [10 20 30]

d_axis1=np.min(data, axis=1)
print(d_axis1)	# [10 40]

 

예를 들어, 10부터 60의 숫자가 있는 2행 3열의 행렬에서,

이 전체 행렬의 최솟값은 10이지만,

만약 axis=0을 지정해주면 세로 방향의 축이 되어, 각 열별 최솟값 통계를 출력하게 되고,

10과 30, 20과 40, 30과 60 각각의 최솟값을 비교해서 선정한 값이 나오게 됩니다.

 

만약 axis=1을 지정해주면 가로 방향의 축이 되어,

각 행별 최솟값 통계를 출력하게 되고 "10,20,30" 한 행, "40,50,60" 한 행 각각의 최솟값을 비교해서 선정한 값이 나오게 됩니다.

 

이 axis 개념이 중요한 이유는, 데이터를 어떤 기준으로 요약하고 분석할지를 결정하는 핵심 요소이기 때문입니다.

같은 데이터라도 axis 설정에 따라 완전히 다른 결과가 나오기 때문에,

다차원 데이터를 다룰 때 반드시 정확하게 이해하고 사용하는 것이 중요합니다.🧐

 

지금까지 배운 NumPy의 다양한 기능들을 잘 숙지해서

행렬 내적이나 가중치 합과 같은 연산을 바탕으로

실제 선형 방정식 기반의 예측 모델까지 구현할 수 있도록 연습해야겠어요.🙂