🔥알림🔥
① 테디노트 유튜브 -
구경하러 가기!
② 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])
댓글남기기