[데이터 분석] 넘파이(NumPy) 튜토리얼 겸 기초 총 정리 (feat. reshape에서 -1의 의미)
안녕하세요! 이번 시간엔 파이썬의 선형대수 라이브러리인 넘파이 사용방법에 대해 알아봅시다.
머신러닝에는 크게 아래와 같이 4가지의 라이브러리가 있는데 이 중 NumPy에 관해 먼저 알아봅시다.
- NumPy - 선형대수 라이브러리
- Pandas - 데이터 분석 라이브러리
- Matplotlib - 시각화 라이브러리
- Scikit-Learn - 머신러닝 주요 라이브러리(알고리즘 포함)
넘파이가 뭐냐?라고 하면 넘파이는 Python의 선형대수 라이브러리로 C언어 기반으로 작성되어서 다차원 배열이나 행렬의 계산 속도가 압도적으로 빠릅니다. 저도 처음 배울 때는 넘파이보다 판다스가 시각적으로 좋고 더 많이 쓰이는 것 같은데 넘파이는 머신러닝에서 별로 안 중요하나?라고 생각한 적이 있었는데 이는 넘파이의 위력을 맛본 뒤로는 필요할 때 적절히 사용하는 게 매우 중요하다고 느꼈습니다. 듣기로는 딥러닝의 input layer를 쌓을 때도 사용된다고 하니 잘 쓰고 익숙해지면 좋겠지요?
오늘 배울 내용 중 중요한 내용의 목차는 아래와 같습니다.
제목 | 내용 | |
1 | 넘파이의 기본 자료형 ndarray | np.array |
2 | 타입/차원/구조 확인 메서드 | dtype, type, ndim, shape |
3 | 편리하게 생성 방법 | arange, zeros, ones |
4 | 자료형/구조 변경 | astype, reshape |
5 | reshape에서 -1의 의미는? | reshape(-1,n), reshape(n,-1) |
6 | 배열의 원소를 정렬 | sort, argsort |
7 | 차원 axis=0,1,2란? | axis=0,1,2 |
8 | 인덱싱/슬라이싱 | [행,열] |
9 | 불린 인덱싱 | 배열[조건식] |
1. 넘파이의 기본 자료형 ndarray
넘파이의 기본 자료형은 ndarray입니다. 이는 기존 배열과 외관은 동일하지만 내부 구조가 달라 이것을 사용하면 속도가 빠릅니다. 선언하는 방법은 아래와 같습니다. 먼저 선언은 import numpy as np로 합니다.
넘파이의 자료구조는 np.array(데이터)이고 판다스는 pd.Series(데이터) pd.DataFrame(데이터)입니다.
엄청 간단하죠? (파이썬이 직관적인게 생각한대로 거의 되더라고요) 넘파이의 array, 판다스(pd)의 Series이런식으로 말이죠. 그래서 다시 본론으로 돌아와 넘파이의 배열은 np.array(데이터)로 생성할 수 있다!
그리고 뒤에서 나오겠지만 넘파이에 ndarray라는 자료구조가 있고 판다스에는 Series와 Dataframe이라는 자료구조가 있습니다. 그런데 이 세가지를 생성하는 방법은 모두 동일합니다.
객체=np.array(데이터)
import numpy as np
arr=np.array([1,2,3,4])
arr
[Output]
array([1, 2, 3, 4])
2. NumPy 확인 메서드
넘파이 객체는 아래와 같은 명령어로 차원 타입 등을 확인할 수 있습니다.
아래 중에서 가장 많이 쓰이는 건 shape 메서드입니다. shape 메서드는 객체의 행과 열에 대한 정보를 (행,열)과 같이 튜플로 반환해 줍니다.
객체.dtype | 객체의 타입(list, ndarray, tuple) |
type(객체) | 객체의 데이터 타입(int32, float64) |
객체.ndim | 차원확인 |
객체.shape | 구조확인(shape) |
아래는 그 예시입니다.
arr=np.array([[1,2,3],
[4,5,6]])
print(type(arr)) #전체 타입
print(arr.dtype) #데이터 타입
print(arr.ndim) #차원
print(arr.shape) # shape
[Output]
<class 'numpy.ndarray'>
int64
2
(2, 3)
3. Numpy ndarray 편리하게 생성 방법
위와 같이 넘파이에(np). arrange(n)를 붙여주면 기존 파이썬의. range(n)처럼 0부터 (n-1)까지 n개의 순차 배열을 만들어 줍니다. (ex. 0,1,2 ~)
그리고 zeros와 ones를 통해 각각 0과 1로 채워진 shape을 지정한 배열을 만들 수 있습니다. 이런 게 왜 필요하냐 하면 이런 shape이 정해진 껍데기가 필요한 경우가 있기 때문에 그때 유용하게 사용됩니다.
np.arange(N)
|
0~(N-1)까지 연속된 배열 생성 |
np.zeros((shape),dtype=‘자료형’)
|
지정한 shape대로 0으로 채운 ndarray 반환 |
np.ones(shape)
|
지정한 shape대로 1으로 채운 ndarray 반환 |
# arange, 10까지 순차적 배열 생성
rangeArr=np.arange(10)
print("range\n",rangeArr,'\n')
# zeros, 0으로 채워진 배열 생성
zeroArr=np.zeros((3,2),dtype=int)
print("zeros\n",zeroArr,'\n')
# ones, 1로 채워진 배열 생성
oneArr=np.ones((4,3))
print("ones\n",oneArr)
[Output]
range
[0 1 2 3 4 5 6 7 8 9]
zeros
[[0 0]
[0 0]
[0 0]]
ones
[[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]]
4. NumPy 타입변경 - astype, reshape
자 이제 엄청 중요한 내용인 넘파이의 reshape이 나왔습니다. reshape이 왜 중요하냐 하면 넘파이는 빠른 속도가 필요한 딥러닝의 layer 모델의 입력 parameter 등 여러 군데서 유용하게 사용되는데 이때 그냥 넣는 게 아니라 모델에서 원하는 형태(보통(-1,1))로 바꾸어서 넣어줘야 하는데 이 바꾸는 걸 reshape이 해줍니다.
근데 이 넘파이가 파이썬의 장점을 충분히 잘 살린 언어인 게 reshape 어떻게 하면 될 것 같나요?
뭔가 객체.reshape(형태)로 해주면 될 것 같지 않나요? 맞습니다. 굉장히 직관적이죠.
그럼 바로 봅시다. 그리고 reshape과 더불어서 또 자주 쓰이는 astype이라고 자료형을 바꿔주는 메서드도 같이 정리해 봅시다. astype 또한 객체.astype(바꿀 자료형)으로 쓸 수 있습니다. 너무 쉽죠?
배열.astype(type)
|
자료형 변경 (int, float, object) |
배열.reshape(a,b)
|
shape(구조) 변경 (a=행 크기, b=열 크기) |
참고로 이때 reshape을 하려면 기존 배열 행렬의 곱과 바꿀 배열 행렬의 곱(a*b)가 같아야 합니다.
너무 당연한 말이죠? 예를 들어 0~10까지 11개의 배열을 .reshape(2,5)로 바꿀 수 없다는 말입니다.
왜? 마지막 10이 남으니까~ 바로 예시를 봅시다!
- astype 예시
아래 예시는 배열을 하나 생성하고 숫자 배열을 문자로 변환하는 예시네요,
a=np.array([1,2,3,4])
str_a=a.astype('str')
print(a)
print(str_a)
[Output]
[1 2 3 4]
['1' '2' '3' '4']
- reshape 예시
아래 예시는 0~14까지의 배열을 3행 5열로 바꾸고(reshape(3,5) 5행 3열로도 바꿔보는 예시입니다.
arr1=np.arange(15)
print("//original array\n",arr1)
arr2=arr1.reshape(3,5)
print("\n//reshape to (3,5) array\n",arr2)
arr3=arr1.reshape(5,3)
print("\n//reshape to (5,3) array\n",arr3)
[Output]
//original array
[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14]
//reshape to (3,5) array
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]]
//reshape to (5,3) array
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]
[12 13 14]]
5. NumPy reshape에서 -1의 의미는?
객체.reshape(-1,n) |
n개의 칼럼으로 고정되게 변형
|
객체.reshape(n,-1) |
n개의 행으로 고정되게 변형
|
예를 들어 shape이 (1,10)인 배열에 reshape(2,5)를 하는 것과 reshape(2,-1) 혹은 reshape(-1,5)를 하는 것은 모두 동일한 의미입니다. 왜? 요소의 크기가 10인 배열에서 reshape(2,-1)로 행을 2개로 고정하면 열은 자연스레 5개가 되야하기 때문에 reshape(-1,5)와 reshpae(2,-1)은 동일합니다. 즉 reshape는 n개의 칼럼 혹은 행을 고정시킨 형태로 변형해 준다고 정리할 수 있네요.
그럼 이를 이용하면 아래와 같은 결론을 낼 수도 있겠네요. reshape(-1,1)은 1개의 칼럼으로 고정시킨 상태로 바꾸는 것이니까 열벡터가 되고 reshape(1,-1)은 그 반대니까 행벡터가 되겠네요.
그래서 이제 우리가 모델을 돌릴 때 input parameter의 shape을 reshape(-1,1)이나 reshape(1,-1)로 바꿔달라 하면 우리는 단순히 코드를 치는 게 아니라 기하학적으로 행 벡터 또는 열 벡터의 형태로 모델에 입력해야 하구나를 이해할 수 있어요.
reshape(-1,1) | 열벡터 |
reshape(1,-1) | 행벡터 |
- reshape -1 예시
a=np.arange(10)
print(a)
a1=a.reshape(2,5)
print('\n',a1)
# (2,-1)이나 (-1,5)나 결과는 동일하다.
a2=a.reshape(2,-1)
print('\n',a2)
a3=a.reshape(-1,5)
print('\n',a3)
[Output]
[0 1 2 3 4 5 6 7 8 9]
[[0 1 2 3 4]
[5 6 7 8 9]]
[[0 1 2 3 4]
[5 6 7 8 9]]
[[0 1 2 3 4]
[5 6 7 8 9]]
- reshape(-1,1) , reshpae(1,-1) 예시
arr=np.arange(10)
print("original arr:",arr,'\n')
colVector=arr.reshape(-1,1)
print("Column Vector: \n",colVector,'\n')
rowVector=arr.reshape(1,-1)
print("Row Vector: ",rowVector,'\n')
[Output]
original arr: [0 1 2 3 4 5 6 7 8 9]
Column Vector:
[[0]
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]]
Row Vector: [[0 1 2 3 4 5 6 7 8 9]]
6. 배열의 원소를 정렬 - sort, argsort
배열의 원소를 정렬하는 데는 먼저 아래 1,2번과 같은 두 가지 방법이 있습니다. 둘의 차이는 원본 유지의 유무고 이제 정렬을 했는데 원래 정렬하기 전의 인덱스가 필요한 경우, 즉 정렬한 원소의 원래 위치는 np.argsort()를 통해 알 수 있습니다.
1 | np.sort(배열) | 배열을 정렬해서 반환,원본 유지 |
2 | 배열.sort() | 배열 정렬, 원본 정렬 !! |
3 | np.argsort(배열) | 정렬된 배열의 원래 인덱스(위치) 반환 |
바로 예시를 봅시다. 먼저 원본을 np.sort로 정렬하면 정렬된 배열이 반환되고 마지막 줄에 np.argsort()를 쓴 것의 출력 값을 보면 차례로 정렬 후 원소는 1,3,4,5 ~ 이렇게 가는데 이게 각각 정렬한 인덱스인 0,4,2,1~과 대응하여 정렬된 1은 원래 0번째 인덱스에 위치했고 정렬된 3은 원래 4번째 인덱스에 위치했다고 해석할 수 있습니다.
arr=np.array([1,5,4,6,3,7,8])
print("원본\n",arr)
print("\n정렬후\n",np.sort(arr))
print("\n정렬한 인덱스\n",np.argsort(arr))
[Output]
원본
[1 5 4 6 3 7 8]
정렬후
[1 3 4 5 6 7 8]
정렬한 인덱스
[0 4 2 1 3 5 6]
axis=0 | 행 기준, 생략시 디폴트 값 |
axis=1 | 열 기준 |
axis=2 | 높이 기준 |
8. 인덱싱/슬라이싱
Python의 기초문법을 안다고 가정 여기서 슬라이싱에 대한 설명은 생략하겠습니다.
인덱싱은 우리가 아는 행, 열 값을 차례로 인자로 주는 방식은 동일한데 C/C++과 다른 점은 C/C++은 2차원 인덱싱에서 배열[행][열]과 같이 사용하는데 파이썬은 배열[행, 열]이다만 아시면 됩니다.
그래서 정리하면 아래와 같습니다. 아래에서 2차원의 슬라이싱을 보시면 a:b는 행의 범위, c:d는 열의 범위를 뜻합니다.
그래서 저 명령을 사용하면 원하는 위치의 원소에 쉽게 접근할 수 있습니다.
9. 불린 인덱싱
불린(Boolean) 인덱싱은 참/거짓 인덱싱이라는 이름에 걸맞게 조건 중 참이 되는 것만 출력합니다. 배열이나 원소에 대괄호 안에 조건식을 넣어서 사용합니다. 아래 예시를 통해 확인해 봅시다.
객체=배열[조건식]
아래 예시를 보시면 모든 값마다 조건에 대한 True/False 값을 구하고 True값만 return 합니다. 간단하죠? 근데 이게 실제로 유용하게 사용될 때가 꽤 있어서 알아놓으시면 좋습니다.
ndarr=np.arange(7)
under5=ndarr[ndarr<5]
print(ndarr)
print(ndarr<5)
print(under5)
[Output]
[0 1 2 3 4 5 6]
[ True True True True True False False]
[0 1 2 3 4]
그래서 마지막으로 오늘 배운 것을 한 번 총정리해보면 아래와 같습니다. 아래 중 파란 글씨는 자주 쓰이는 것을 칠한 것이니 기억해 놓으시면 편합니다.
그럼 여기까지 읽어주셔서 감사합니다. 궁금한 점이 있으시면 아래 댓글로 얼마든지 달아주세요. 감사합니다!!!