🔥알림🔥
① 테디노트 유튜브 -
구경하러 가기!
② LangChain 한국어 튜토리얼
바로가기 👀
③ 랭체인 노트 무료 전자책(wikidocs)
바로가기 🙌
④ RAG 비법노트 LangChain 강의오픈
바로가기 🙌
⑤ 서울대 PyTorch 딥러닝 강의
바로가기 🙌
넘파이(Numpy) 튜토리얼
본 포스팅은 파이썬(Python) 코딩 입문자를 위한 튜토리얼 시리즈 연재 중 일부입니다. 이번 튜토리얼에서는 파이썬 수치계산용 라이브러리인 넘파이(numpy) 를 다룹니다.
Numpy
- 파이썬 외부 라이브러리
- 파이썬을 활용한 과학 컴퓨팅 전용 모듈
- 복잡한 행렬계산, 선형대수, 통계등의 기능 제공
모듈 import
- 별칭(alias)은 주로
np
를 사용합니다.
import numpy as np
버젼 체크
np.__version__
'1.19.5'
np.__name__
'numpy'
파이썬 리스트
mylist1 = [1, 2, 3]
mylist2 = [4, 5, 6]
파이썬의 리스트(list) type입니다.
type(mylist1)
list
리스트(list)는 확장성에는 용이하지만, 수치계산(numerical calculation)이 어렵습니다.
만약, mylist1
과 mylist
를 더하는 연산을 하고 싶지만, 이를 지원하지 않습니다. 뿐만 아니라 머신러닝에서 중요한 2차원의 행렬연산이나 고차원 연산도 지원하지 않습니다.
mylist1 + mylist2
[1, 2, 3, 4, 5, 6]
배열(Array)
- numpy의 배열은 동일한 type을 가집니다.
- 파이썬의 list로는 고성능 수치계산이 어렵기 때문에 numpy의 배열로 수치계산을 수행합니다.
배열(array)의 생성
myarray = np.array([1, 2, 3])
myarray
array([1, 2, 3])
type은 ndarray
로 생성되는 것을 볼 수 있는데, n-dimensional array
(N차원 배열) type입니다.
type(myarray)
numpy.ndarray
ndim: 배열의 차원
myarray1 = np.array([1, 2, 3])
myarray1
array([1, 2, 3])
ndim
은 배열의 차원을 나타냅니다.
myarray1.ndim
1
중첩된 리스트(list)로 생성시 2차원 배열이 생성됩니다.
myarray2 = np.array([[1, 2, 3], [4, 5, 6]])
myarray2
array([[1, 2, 3], [4, 5, 6]])
myarray2.ndim
2
shape: 크기 확인
- shape는 튜플(tuple) 형태로 출력됩니다.
myarray1.shape
(3,)
myarray2.shape
(2, 3)
dtype: 타입 확인
myarray = np.array([[1, 2, 3], [4, 5, 6]])
myarray
array([[1, 2, 3], [4, 5, 6]])
myarray.dtype
dtype('int64')
dtype
을 지정하여 생성할 수 있습니다.
myarray = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float)
myarray
array([[1., 2., 3.], [4., 5., 6.]])
myarray.dtype
dtype('float64')
size: 총 요소의 수
myarray = np.array([1, 2, 3])
myarray
array([1, 2, 3])
myarray.size
3
myarray = np.array([[1, 2, 3], [4, 5, 6]])
myarray
array([[1, 2, 3], [4, 5, 6]])
myarray.size
6
T: 전치(transpose), 행과 열의 교환
myarray = np.array([1, 2, 3])
myarray
array([1, 2, 3])
myarray.T
array([1, 2, 3])
myarray = np.array([[1, 2, 3], [4, 5, 6]])
myarray
array([[1, 2, 3], [4, 5, 6]])
myarray.T
array([[1, 4], [2, 5], [3, 6]])
arange(): 생성
np.arange()
는 파이썬의range()
와 유사하며 범위를 통해 배열(array)을 생성합니다.- np.arange(start, stop, step)
stop 지정
np.arange(10)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
start, stop 지정
np.arange(2, 10)
array([2, 3, 4, 5, 6, 7, 8, 9])
start, stop, step 지정
np.arange(1, 10, 2)
array([1, 3, 5, 7, 9])
linspace(): 범위 내 균일하게 생성
np.linspace()
는 수의 범위를 균일하게 나누고자 할 때 사용np.linspace(start, stop, count)
: start 부터 stop 까지 count개 구간으로 나눕니다.
1에서 10까지 3개 구간으로 나누고 싶을 때
np.linspace(1, 10, 3)
array([ 1. , 5.5, 10. ])
zeros(), ones(), full()
np.zeros()
: 0으로 채워진 배열 생성np.ones()
: 1로 채워진 배열 생성np.full(shape, fill_value)
: 지정한 값(fill_value)으로 채워진 배열 생성
np.zeros(shape=(3, 5))
array([[0., 0., 0., 0., 0.], [0., 0., 0., 0., 0.], [0., 0., 0., 0., 0.]])
np.ones(shape=(3, 5))
array([[1., 1., 1., 1., 1.], [1., 1., 1., 1., 1.], [1., 1., 1., 1., 1.]])
np.full(shape=(3, 3), fill_value=5)
array([[5, 5, 5], [5, 5, 5], [5, 5, 5]])
eye()
- identical matrix 생성
- 대각 방향으로 1로 채워진 행렬
np.eye(5)
array([[1., 0., 0., 0., 0.], [0., 1., 0., 0., 0.], [0., 0., 1., 0., 0.], [0., 0., 0., 1., 0.], [0., 0., 0., 0., 1.]])
random.randn(): 난수 생성
- 정규 분포 범위내의 난수(random) 생성
np.random.randn(5)
array([ 0.86613735, -0.23910739, 0.94770316, -0.44173603, -0.04880852])
np.random.randn(3, 5)
array([[ 0.90753445, -0.39195615, 0.19528836, -0.52381007, 0.64119731], [-0.48278787, 0.6591194 , -0.33430174, -0.7407682 , -0.34993516], [ 0.74057702, -1.12567724, -0.23199983, -1.43454871, -0.63003699]])
인덱싱(indexing)
1차원 배열
arr1 = np.array([1, 2, 3, 4, 5])
arr1
array([1, 2, 3, 4, 5])
shape를 확인합니다.
arr1.shape
(5,)
arr1[2]
3
arr1[-1]
5
인덱스의 범위를 넘어가는 인덱스 접근시 파이썬 리스트(list)와 마찬가지로 Error가 발생합니다.
arr1[6]
--------------------------------------------------------------------------- IndexError Traceback (most recent call last) <ipython-input-44-cbf9362dfd88> in <module> ----> 1 arr1[6] IndexError: index 6 is out of bounds for axis 0 with size 5
2차원
arr2 = np.array([[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10],
[11, 12, 13, 14, 15]
])
arr2
array([[ 1, 2, 3, 4, 5], [ 6, 7, 8, 9, 10], [11, 12, 13, 14, 15]])
arr2.shape
(3, 5)
2차원 배열의 indexing은 다음과 같이 2가지 방식으로 접근 가능합니다.
arr2[2, 3]
14
arr2[2][3]
14
슬라이싱(slicing)
1차원 배열
arr1 = np.array([1, 2, 3, 4, 5])
arr1
array([1, 2, 3, 4, 5])
arr1[:2]
array([1, 2])
arr1[-2:]
array([4, 5])
2차원 배열
arr2 = np.array([[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10],
[11, 12, 13, 14, 15]
])
arr2
array([[ 1, 2, 3, 4, 5], [ 6, 7, 8, 9, 10], [11, 12, 13, 14, 15]])
행(row)은 전체 선택, 열(column)은 1:3 슬라이싱
arr2[:, 1:3]
array([[ 2, 3], [ 7, 8], [12, 13]])
행(row)은 :2, 열(column)은 1:3 슬라이싱
arr2[:2, 1:3]
array([[2, 3], [7, 8]])
reshape: 형태 변경
- 형태(shape)는 자유롭게 변경이 가능하나 변경 후의 데이터 개수(size)는 변경 전과 같아야 합니다.
-1
은 자동으로 크기를 계산합니다.
arr = np.arange(15)
arr
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])
print(f'ndim: {arr.ndim}, shape: {arr.shape}')
ndim: 1, shape: (15,)
변경 후의 데이터 개수(size)가 맞지 않는다면 Error가 발생합니다.
arr.reshape(5, 2)
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-58-ca5876f56154> in <module> ----> 1 arr.reshape(5, 2) ValueError: cannot reshape array of size 15 into shape (5,2)
행: 3, 열: 5로 변경
arr.reshape(3, 5)
array([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14]])
열은 -1
로 지정하여 자동으로 계산한 경우
arr.reshape(5, -1)
array([[ 0, 1, 2], [ 3, 4, 5], [ 6, 7, 8], [ 9, 10, 11], [12, 13, 14]])
행을 -1
로 지정하여 자동으로 계산한 경우
arr.reshape(-1, 5)
array([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14]])
원본 데이터에 적용하기 위해서는 재대입을 해야합니다.
arr = np.arange(15)
print(arr.shape)
arr
(15,)
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])
arr.reshape(3, -1)
array([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14]])
변경 사항이 적용되지 않습니다.
arr
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])
arr = arr.reshape(3, -1)
print(arr.shape)
arr
(3, 5)
array([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14]])
행열 연산
element-wise operations
: 행과 열이 같은 배열을 계산하면 값은 위치에 있는 값들이 계산됩니다.element-wise operations
은 shape이 행렬의 shape이 같아야 합니다.
x = np.array([[10, 20], [30, 40]])
y = np.array([[5, 6], [7, 8]])
x
array([[10, 20], [30, 40]])
y
array([[5, 6], [7, 8]])
덧셈
x + y
array([[15, 26], [37, 48]])
곱셈
x * y
array([[ 50, 120], [210, 320]])
뺄셈
x - y
array([[ 5, 14], [23, 32]])
나눗셈
x / y
array([[2. , 3.33333333], [4.28571429, 5. ]])
modulus, floor division
x % y
array([[0, 2], [2, 0]])
x // y
array([[2, 3], [4, 5]])
shape가 맞지 않으면 안됩니다!
x = np.array([[1, 2, 3], [3, 4, 5]])
y = np.array([[5, 6, ], [7, 8]])
x + y
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-76-cd60f97aa77f> in <module> ----> 1 x + y ValueError: operands could not be broadcast together with shapes (2,3) (2,2)
브로드캐스팅
- 단순 스칼라(salar) 값 연산시
- 제곱, 거듭제곱 등의 연산시
x = np.array([[1, 2], [3, 4]])
x
array([[1, 2], [3, 4]])
x + 10
array([[11, 12], [13, 14]])
x ** 2
array([[ 1, 4], [ 9, 16]])
np.sqrt()
는 루트 연산입니다.
np.sqrt(x)
array([[1. , 1.41421356], [1.73205081, 2. ]])
행렬의 곱연산
*
: 일반 곱셈 부호@
: matmul, 행열의 곱연산, 내적곱(dot product)
a = np.array([[1, 2], [3, 4]])
a
array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
b
array([[5, 6], [7, 8]])
일반 곱을 수행한 결과
a * b
array([[ 5, 12], [21, 32]])
행렬곱을 수행한 결과
a @ b
array([[19, 22], [43, 50]])
Boolean Indexing
- 조건 배열(Boolean array)을 활용하여 인덱싱하는 방법입니다.
- 조건에 해당하는 결과가
True
인 요소를 반환합니다. - 인덱싱 후 결과는 항상 1차원 배열로 반환합니다.
arr = np.arange(1, 13).reshape(3, 4)
arr
array([[ 1, 2, 3, 4], [ 5, 6, 7, 8], [ 9, 10, 11, 12]])
Boolean 배열을 생성합니다.
# Boolean 배열 생성
bool_arr = arr > 5
bool_arr
array([[False, False, False, False], [False, True, True, True], [ True, True, True, True]])
위에서 생성한 boolean 배열인 bool_arr
로 인덱싱 합니다.
arr[bool_arr]
array([ 6, 7, 8, 9, 10, 11, 12])
Fancy Indexing
- 다른 배열(array)이나 리스트(list)를 사용하여 배열을 인덱싱 하는 방법입니다.
arr = np.arange(100, 110)
arr
array([100, 101, 102, 103, 104, 105, 106, 107, 108, 109])
인덱스 리스트(list)를 생성하여 fancy indexing 합니다.
# 인덱스 리스트 생성
idx = [0, 2, 5]
arr[idx]
array([100, 102, 105])
np.arange()
로 인덱스를 생성할 배열 생성후 fancy indexing 합니다.
# np.arange로 인덱스 생성
arr_idx = np.arange(1, 9, 2)
arr_idx
array([1, 3, 5, 7])
arr[arr_idx]
array([101, 103, 105, 107])
배열 참조
- 배열을 슬라이싱 한 후 결과 값은 원본 배열을 참조(reference)합니다.
- 따라서, 슬라이싱한 배열 수정시 원본 배열에서도 수정되는 현상이 발생합니다.
a = np.arange(1, 13)
a
array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
슬라이싱하여 a1 변수에 대입합니다.
# 슬라이싱으로 a1 생성
a1 = a[:5]
a1
array([1, 2, 3, 4, 5])
a1
배열의 0번 인덱스의 값을 999로 변경합니다.
a1[0] = 999
a1
array([999, 2, 3, 4, 5])
# 값을 참조하기 때문에 원본 데이터도 수정된다.
print(f'a : {a}')
print(f'a1 : {a1}')
a : [999 2 3 4 5 6 7 8 9 10 11 12] a1 : [999 2 3 4 5]
방법1) 값을 참조하는 현상을 방지하려면 copy() 사용
copy()
는 배열에 대한 복사본을 생성합니다.
a = np.arange(1, 13)
a
array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
a1 = a[:5].copy()
a1[0] = 999
a1
array([999, 2, 3, 4, 5])
# 결과 확인
print(f'a : {a}')
print(f'a1 : {a1}')
a : [ 1 2 3 4 5 6 7 8 9 10 11 12] a1 : [999 2 3 4 5]
방법2) fancy indexing
사용
a = np.arange(1, 13)
a
array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
a1 = a[np.arange(5)]
a1
array([1, 2, 3, 4, 5])
a1[0] = 999
a1
array([999, 2, 3, 4, 5])
print(f'a : {a}')
print(f'a1 : {a1}')
a : [ 1 2 3 4 5 6 7 8 9 10 11 12] a1 : [999 2 3 4 5]
(주의) 배열 연산시 배열 참조 현상이 발생 할 수 있습니다.
[참고]
- arr = arr 10은 arr 10 과정에서 새로운 배열을 생성합니다.
- arr *= 10은 원본 배열을 직접 수정합니다.
a = np.arange(1, 13)
a
array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
case 1) 곱셈 연산 수행
- a = a * 10 과 같은 형식으로 곱셈을 수행한 경우
a1 = a[:5] * 10
a1
array([10, 20, 30, 40, 50])
배열 참조가 발생하지 않습니다.
print(f'a : {a}')
print(f'a1 : {a1}')
a : [ 1 2 3 4 5 6 7 8 9 10 11 12] a1 : [10 20 30 40 50]
case 2) 곱셈 연산 수행
- a *= 10 과 같은 형식으로 곱셈을 수행한 경우
a = np.arange(1, 13)
a
array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
a2 = a[:5]
a2 *= 10
a2
array([10, 20, 30, 40, 50])
배열 참조가 발생하여 원본 데이터가 수정되었음을 확인할 수 있습니다.
print(f'a : {a}')
print(f'a1 : {a1}')
a : [10 20 30 40 50 6 7 8 9 10 11 12] a1 : [10 20 30 40 50]
where()
- 문법:
np.where(조건, 참인 경우 배열, 거짓인 경우 배열)
xarr = np.array([1.1, 1.2, 1.3, 1.4, 1.5])
yarr = np.array([2.1, 2.2, 2.3, 2.4, 2.5])
condition = np.array([True, False, True, True, False])
np.where(condition, xarr, yarr)
array([1.1, 2.2, 1.3, 1.4, 2.5])
난수로 채워진 (4 X 4) 배열을 생성합니다.
a = np.random.randn(4, 4)
a
array([[ 0.45815529, 1.52310673, 0.59797612, -1.80292333], [ 0.58553934, -0.45815217, -0.44710476, 0.25488142], [-1.34731032, -0.75313604, -0.20437942, -0.87595057], [ 0.4630922 , 1.38550283, 0.25242706, 0.13867071]])
np.where(a >= 0, '양수', '음수')
array([['양수', '양수', '양수', '음수'], ['양수', '음수', '음수', '양수'], ['음수', '음수', '음수', '음수'], ['양수', '양수', '양수', '양수']], dtype='<U2')
통계
axis: 축
- 배열의 통계 함수는 요소별로 동작하며 축(axis) 지정으로 연산의 방향을 정할 수 있습니다.
- axis를 지정하지 않은 경우에는 모든 요소에 대한 통계 결과를 반환합니다.
axis=0
: 행(row) 별 연산 결과를 반환합니다.axis=1
: 열(column) 별 연산 결과를 반환합니다.
sum(): 합계
arr = np.arange(1, 13).reshape(3, 4)
arr
array([[ 1, 2, 3, 4], [ 5, 6, 7, 8], [ 9, 10, 11, 12]])
arr.sum()
78
arr.sum(axis=0)
array([15, 18, 21, 24])
arr.sum(axis=1)
array([10, 26, 42])
mean(): 평균
arr = np.arange(1, 13).reshape(3, 4)
arr
array([[ 1, 2, 3, 4], [ 5, 6, 7, 8], [ 9, 10, 11, 12]])
arr.mean()
6.5
arr.mean(axis=0)
array([5., 6., 7., 8.])
arr.mean(axis=1)
array([ 2.5, 6.5, 10.5])
min(), max(): 최소값, 최대값
# 난수 seed값 설정
np.random.seed(123)
# 샘플 배열 생성
arr = np.random.permutation(np.arange(1, 13)).reshape(3, 4)
arr
array([[ 6, 1, 5, 10], [ 9, 8, 11, 4], [ 2, 7, 12, 3]])
최소값
arr.min()
1
arr.min(axis=0)
array([2, 1, 5, 3])
arr.min(axis=1)
array([1, 4, 2])
최대값
arr
array([[ 6, 1, 5, 10], [ 9, 8, 11, 4], [ 2, 7, 12, 3]])
arr.max()
12
arr.max(axis=0)
array([ 9, 8, 12, 10])
arr.max(axis=1)
array([10, 11, 12])
var(), std(): 분산, 표준편차
- 분산과 표준편차: 데이터가 평균으로부터 얼마나 퍼져있는지 정도를 나타내는 지표
분산($\sigma^2$) 공식
$\Large \sigma^2 = \frac{\sum_{i=1}^{n}(x_i - \mu)^2} {n}$
표준편차($\sigma$) 공식
$\Large \sigma = \sqrt{\frac{\sum_{i=1}^{n}(x_i - \mu)^2} {n}}$
# 샘플 배열 생성
arr = np.arange(1, 13).reshape(3, 4)
arr
array([[ 1, 2, 3, 4], [ 5, 6, 7, 8], [ 9, 10, 11, 12]])
arr.var()
11.916666666666666
arr.var(axis=0)
array([10.66666667, 10.66666667, 10.66666667, 10.66666667])
arr.var(axis=1)
array([1.25, 1.25, 1.25])
표준편차는 분산에 루트를 씌운 결과 입니다.
np.sqrt(arr.var()), arr.std()
(3.452052529534663, 3.452052529534663)
입출력
save(): 단일 배열 저장
- 저장할 파일명, 배열(array) 순으로 저장합니다.
- 저장한 파일은
.npy
확장자로 저장됩니다. (파일로 저장)
arr = np.arange(10)
arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
np.save('my_arr', arr)
load(): 저장한 배열 로드
.npy
혹은.npz
확장자를 가지는 저장된 배열을 불러옵니다.
loaded_arr = np.load('my_arr.npy')
loaded_arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
savez(): 다중 배열 저장
- 다중 배열 저장시
key=저장할배열
형태로 저장합니다. - 확장자는
.npz
입니다.
arr1 = np.arange(10)
arr2 = np.arange(10, 20)
arr1
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
arr2
array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
키=배열 로 지정하여 저장합니다.
np.savez('my_arrz', a1=arr1, a2=arr2)
저장한 배열을 불러옵니다.
loaded_arr = np.load('my_arrz.npz')
files
: 배열이 저장된 key 값을 조회할 수 있습니다.
loaded_arr.files
['a1', 'a2']
key 값으로 배열을 불러옵니다.
loaded_arr['a1']
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
loaded_arr['a2']
array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
댓글남기기