본문 바로가기
Machine Learning

넘파이(Numpy)에 대해 알아보자!

by rubyda 2020. 4. 3.
728x90
파이썬 머신러닝 완벽 가이드: 다양한 캐글 예제와 함께 기초 알고리즘부터 최신 기법까지 배우는/권철민 지음을 참고하여 공부하고 정리하는 스터디 포스팅입니다.

 

Untitled

1. NumPy(넘파이)란?

  • NumPy는 선형대수 기반의 프로그램을 쉽게 만들 수 있도록 지원하는 대표적인 패키지입니다.
  • NumPy는 배열 기반의 연산에 더해 다양한 데이터 핸들링 기능을 제공해 줍니다.

2. ndarray 클래스

  • 넘파이의 기반 데이터 타입은 ndarray입니다. ndarray를 활용하면 다차원(Multi-dimen-sion)배열을 쉽게 생성할 수 있고 다양한 연산들을 수행할 수 있습니다.
  • ndarray는 같은 종류의 데이터만 배열의 담을 수 있습니다.

2-1 배열 생성하기

  • array() 함수는 다양한 인자를 입력받아서 ndarray로 변환하는 기능을 하는 함수입니다.
  • 행과 열의 수를 튜플 형태로 가지고 있으며 이를 통해 ndarray 배열의 차원까지 알 수 있습니다.

    ndarray.shape: ndarray의 차원과 크기를 튜플 형태로 나태내어 줍니다.
    ndarray.dim: 각 array의 차원을 알려주는 함수입니다.

In [111]:
array1=np.array([1,2,3])
print('array1 type:',type(array1))
print('array1 array 형태:',array1.shape)
array1 type: <class 'numpy.ndarray'>
array1 array 형태: (3,)
In [112]:
array2=np.array([[1,2,3],
                     [2,3,4]])
print('array2 type:',type(array2))
print('array2 array 형태:',array2.shape)
array2 type: <class 'numpy.ndarray'>
array2 array 형태: (2, 3)
In [113]:
array3=np.array([[1,2,3]])
print('array3 type:',type(array3))
print('array3 array 형태:',array3.shape)
array3 type: <class 'numpy.ndarray'>
array3 array 형태: (1, 3)
In [114]:
print('array1 : {:0}차원, array2 : {:1}차원, array3 : {:2}차원'.format(array1.ndim, array2.ndim,array3.ndim))
array1 : 1차원, array2 : 2차원, array3 :  2차원
In [115]:
list1=[1,2,3]
print(type(list1))
array1=np.array(list1)
print(type(array1))
print(array1,array1.dtype)
<class 'list'>
<class 'numpy.ndarray'>
[1 2 3] int32

ndarry는 같은 데이터 타입만 가능하다고 위에서 정리하였습니다. 만약에 다른 데이터 유형이 담긴 리스트를 ndarry로 변경하면 어떻게 되는지 확인해봅시다.

In [116]:
list2=[1,2,'test']
array2=np.array(list2)
print(array2,array2.dtype)

list3=[1,2,3.0]
array3=np.array(list3)
print(array3,array3.dtype)
['1' '2' 'test'] <U11
[1. 2. 3.] float64

위에 결과를 봤을 때 데이터 크기가 더 큰 데이터 타입으로 형 변환이 적용이 된다는 것을 확인할 수 있습니다. list2의 경우 숫자형 값 1,2가 문자열 값인'1'과 '2'로 변환된 것을 알 수 있습니다.

2-2 ndarry를 편하게 해주는 함수

  • arange(): range()와 비슷한 함수로 쉽게 말해 array를 range로 표현하는 함수입니다.
    0부터 함수 인자 값 -1까지의 값을 순차적으로 ndarray의 데이터 값으로 변환하여 줍니다.
In [117]:
sequence_array=np.arange(10)
print(sequence_array)
print(sequence_array.dtype,sequence_array.shape)
[0 1 2 3 4 5 6 7 8 9]
int32 (10,)

위 예시에서는 0부터 10에서 -1을 더한 9까지의 연속적인 값으로 구성된 1차원 ndarry를 만들어 줍니다.

  • zeros(): 튜플 형태의 shape값을 입력했을 때 모든 값을 0으로 채운 ndarray를 반환해 주는 함수입니다.
In [118]:
zero=np.zeros((3,2),dtype='int32')
print(zero)
print(zero.dtype,zero.shape)
[[0 0]
 [0 0]
 [0 0]]
int32 (3, 2)
  • ones(): zeros와 비슷한 함수로 모든 값을 1로 채운 ndarry를 반환해 주는 함수입니다.
In [119]:
one=np.ones((3,2))
print(one)
print(one.dtype,one.shape)
[[1. 1.]
 [1. 1.]
 [1. 1.]]
float64 (3, 2)

2-2 ndarry의 차원과 크기를 변경해 주는 reshape()

  • reshape()는 ndarray를 원하는 차원과 크기로 변환해 주는 함수입니다.
In [120]:
array1=np.arange(10)
print('array1\n',array1)

array2=array1.reshape(2,5)
print('array2\n',array2)

array3=array1.reshape(5,2)
print('array3\n',array3)
array1
 [0 1 2 3 4 5 6 7 8 9]
array2
 [[0 1 2 3 4]
 [5 6 7 8 9]]
array3
 [[0 1]
 [2 3]
 [4 5]
 [6 7]
 [8 9]]

입력한 함수 인자에 맞게 변환이 일어난 것을 확인할 수 있습니다.

  • reshape()는 데이터의 형태를 모를 때 유용하게 활용을 할 수 있습니다. 행과 열중 하나의 값을 정해주고 나머지 부분에 -1을 넣어주면 내가 정한 값에 맞춰서 변환이 일어나게 됩니다.
    다시 정리해 보면 -1의 의미는 변경된 배열의 -1 위치의 차원은 "원래 배열의 길이와 남은 차원으로 부터 추정”이 된다라고 말할 수 있습니다. 자세한 부분은 예시를 통해 알아보도록 하겠습니다.
In [121]:
array1=np.arange(10)
print(array1)
array2=array1.reshape(-1,5)
array3=array1.reshape(5,-1)
print('array2:',array2.shape)
print('array3:',array3.shape)
[0 1 2 3 4 5 6 7 8 9]
array2: (2, 5)
array3: (5, 2)

위에 예시중 reshape(-1,5)의 의미는 고정된 5개의 컬럼에 맞는 로우를 자동으로 새롭게 변환하라는 의미입니다. 결과값을 보면 그ㅔ 맞게 2x5의 형태로 변환이 된 것을 확인할 수 있습니다.

In [122]:
array1=np.arange(10)
array4=array1.reshape(-1,4)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-122-7f01f0f59baa> in <module>
      1 array1=np.arange(10)
----> 2 array4=array1.reshape(-1,4)

ValueError: cannot reshape array of size 10 into shape (4)

하지만 위의 예시처럼 10개의 1차원 데이터를 4개의 컬럼을 가진 로우로는 변경할 수 없기 때문에 에러가 발생하게 됩니다.

2-2 ndarray의 데이터 세트 선택하기 - 인덱싱(Indexing)

1. 특정한 데이터 추출하기

  • 원하는 위치의 인덱스 값을 지정하면 해당 위치의 있는 데이터가 반환됩니다.

먼저 1개의 데이터값을 선택하고자 한다면 ndarray 객체에 해당하는 위치의 인덱스 값을 [] 안에 입력해주면 됩니다.

In [123]:
# 1부터 9까지의 1차원 ndarray 생성하기 #
array1=np.arange(start=1,stop=10)
print(array1)

value=array1[2]
print(value)
value2=array1[-1]
print(value2)
[1 2 3 4 5 6 7 8 9]
3
9

인덱스는 0부터 시작하기 때문에 array1[2]는 3번째 인덱스 위치의 값인 3이 출력된 것을 확인할 수 있습니다. 인덱스 -1은 맨 뒤의 데이터값으로 9가 출력된 것을 확인할 수 있습니다.

In [124]:
print(array1)
array1[0]=8
array1[7]=1
print(array1)
[1 2 3 4 5 6 7 8 9]
[8 2 3 4 5 6 7 1 9]

위와 같이 데이터값도 간단히 변경을 할 수 있습니다.

다음은 다차원의 ndarry에서 단일 값을 추출해 보도록 하겠습니다.
여기서 핵심은 콤마(,)를 사용하여 인덱스를 통해 접근한다는 점입니다. ex) [row,col]

위에서 배운 reshape함수를 활용해서 1차원 데이터를 2차원 데이터로 변환시킨 후 실습을 해보도록 하겠습니다.

In [125]:
array1d=np.arange(start=1,stop=10)
array2d=array1d.reshape(3,3)
print(array2d)

print(array2d[0,0])
print(array2d[0,1])
print(array2d[1,0])
print(array2d[2,2])
[[1 2 3]
 [4 5 6]
 [7 8 9]]
1
2
4
9

위 예시중 array2[1,0]을 살펴보자면 [row,col]이므로 1행중 0번째 열값인 4를 의미하게 됩니다.

2. 슬라이싱(Slicing)

  • 슬라이싱은 연속된 인덱스상의 ndarray를 추출하는 방법입니다.
  • ':'기호를 사용해서 연속된 데이터를 추출할 수 있습니다.(:사이에 시작 인덱스와 종료 인덱스를 표시하는 방식입니다.)
In [126]:
print(array1)
array3=array1[0:3]
print(array3)
[8 2 3 4 5 6 7 1 9]
[8 2 3]
  • 슬라이싱 기호인 ':' 사이의 시작, 종료 인덱스는 생략하여 표시할수도 있습니다.

    1) ':'기호 앞에 시작 인덱스를 생략하면 맨 처음 인덱스인 0으로 간주하게 됩니다.
    2) ':'기호 뒤에 종료 인덱스를 생략하면 맨 마지막 인덱스로 간주하게 됩니다.
    3) ':'기호 앞과 뒤에 시작과 종료 인덱스를 생략하면 맨처음과 맨 마지막 인덱스를 간주하게 됩니다.

In [127]:
print(array1)
array4=array1[:3]
print(array4)

array5=array1[3:]
print(array5)

array6=array1[:]
print(array6)
[8 2 3 4 5 6 7 1 9]
[8 2 3]
[4 5 6 7 1 9]
[8 2 3 4 5 6 7 1 9]

다음은 2차원 ndarray에서 슬라이싱으로 데이터를 출력해 보도록 하겠습니다. 슬라이싱도 앞의 예제와 비슷하게 콤마(,)를 사용하여 인덱스에 접근하게 됩니다.

In [128]:
array1d=np.arange(start=1,stop=10)
array2d=array1d.reshape(3,3)
print("array2d\n",array2d)

print("array2d[0:2,0:2]\n",array2d[0:2,0:2])
print("array2d[1:4,0:4]\n",array2d[1:4,0:4])
print("array2d[:5,:]\n",array2d[:5,:])
array2d
 [[1 2 3]
 [4 5 6]
 [7 8 9]]
array2d[0:2,0:2]
 [[1 2]
 [4 5]]
array2d[1:4,0:4]
 [[4 5 6]
 [7 8 9]]
array2d[:5,:]
 [[1 2 3]
 [4 5 6]
 [7 8 9]]

3. 팬시 인덱싱(Fancy Indexing)

  • 일정한 인덱싱 집합을 리스트 또는 ndarray 형태로 지정하여 해당 위치에 있는 데이터의 ndarray를 반환해 줍니다.

2차원 데이터를 통해 실습을 해보겠습니다.

In [129]:
array1d=np.arange(start=1,stop=10)
array2d=array1d.reshape(3,3)
print(array2d)

array3=array2d[[0,1],2]
print('array2d[[0,1],2] => ',array3.tolist())

array4=array2d[[0,1],0:2]
print('array2d[[0,1],0:2] =>', array4.tolist())
[[1 2 3]
 [4 5 6]
 [7 8 9]]
array2d[[0,1],2] =>  [3, 6]
array2d[[0,1],0:2] => [[1, 2], [4, 5]]

위의 예시중 array2d[[0,1],2]를 살펴보자면 로우 축에 팬시 인덱싱인[0,1]을 컬럼 축에는 인덱싱 2를 적용한 것입니다. 그 결과(row,col)인덱스가 (0,2),(1,2)로 적용이 되어서 [3,6]이 반환된 것을 확인할 수 있습니다.

불린 인덱싱(Boolean Indexing)

  • 특정 조건에 해당이 되는지 True/False값 인덱싱 집합을 기반으로 하여 True에 해당하는 인덱스 위치에 있는 ndarray의 값을 반환합니다.
  • 불린 인덱싱은 조건과 검색을 동식에 할 수 있기 때문에 매우 많이 사용되는 인덱싱 방식입니다.(for loop/if else문 보다 훨씬 간단하게 구현할 수 있습니다.)
In [130]:
array1d > 5
Out[130]:
array([False, False, False, False, False,  True,  True,  True,  True])

위와 같이 ndarray 객체에 조건식만 붙이게 되면 False,True 형태로 출력이 됩니다. 해석을 해보자면 5보다 큰 데이터는 True, 그렇지 않은 경우는 False가 반환되는 것을 확인할 수 있습니다.

In [131]:
# []안에 array1d > 5 Boolean indexing을 적용
array3=array1d[array1d>5]
print(array3)
[6 7 8 9]

조건으로 반환된 ndarry 객체를 인덱싱을 지정하는 [] 안에 입력하게 되면 False값은 무시되고 True값이 있는 위치의 인덱스 값만 반환이 되는것을 확인할 수 있습니다. 다시 말하자면 False 인덱스가 있는 0~4는 무시되고 인덱스 [5,6,7,8]이 만들어지며 이 위치 인덱스에 해당되는 값인 [6,7,8,9]가 출력된 것입니다.

3. 행렬의 정렬(sort와 argsort)

  • 넘파이에서 행렬을 정렬 해주는 대표적인 방법은 np.sort()ndarray.sort()가 있습니다. 이에 더해 정렬된 행렬의 인덱스를 반환하는 argsort()에 대해서도 알아보도록 하겠습니다.

    - np.sort(): 넘파이에서 sort()를 호출(원 행렬은 그대로 유지한 채 원 행렬의 정렬된 행렬을 반환)
    - ndarray.sort(): 행렬 자체에서 호출(원 행렬 자체를 정렬한 형태로 반환하며 반환 값은 None)
    - argsort(): 정렬 행렬의 원본 행렬 인덱스를 ndarray형으로 반환시켜 줍니다.

설명만으로는 이해가 잘 안 갈 수 있으니 예제를 통해 알아봅시다.

In [132]:
org_array=np.array([1,5,7,3])
print('원본 행렬:',org_array)
#np.sort()로 정렬
sort_array1=np.sort(org_array)
print('np.sort() 호출 후 반환된 정렬 행렬',sort_array1)
print('np.sort() 호출 후 원본 행렬',org_array)
#ndarray.sort()로 정렬
sort_array2=org_array.sort()
print('org_array.sort() 호출 후 반환된 행렬',sort_array2)
print('org_array.sort() 호출 후 원본 행렬',org_array)
원본 행렬: [1 5 7 3]
np.sort() 호출 후 반환된 정렬 행렬 [1 3 5 7]
np.sort() 호출 후 원본 행렬 [1 5 7 3]
org_array.sort() 호출 후 반환된 행렬 None
org_array.sort() 호출 후 원본 행렬 [1 3 5 7]

원본 행렬[1,5,7,3]에 대해서 np.sort()는 원본 행렬을 변경하지 않고 정렬된 형태로 반환이 되며, ndarray.sort()는 원본 행렬 자체를 정렬한 값으로 변환함을 알 수 있습니다. 두 함수는 모두 기본적으로 오름차순으로 정렬을 하게 됩니다. 만약 내림차순으로 정렬을 하고 싶다면 [::-1]을 적용하면 됩니다.

In [133]:
sort_array1_desc=np.sort(org_array)[::-1]
print(sort_array1_desc)
[7 5 3 1]

행렬이 2차원 이상이 경우에는 axis축 값 설정을 통해서 row또는 col방향으로 정렬을 할 수 있습니다.

In [134]:
array2d=np.array([[8,12],[7,1]])

# 로우 방향으로 정렬
sort_array2d_axis0=np.sort(array2d,axis=0)
print('로우 방향으로 정렬:\n',sort_array2d_axis0)

# 로우 방향으로 정렬
sort_array2d_axis1=np.sort(array2d,axis=1)
print('컬럼 방향으로 정렬:\n',sort_array2d_axis1)
로우 방향으로 정렬:
 [[ 7  1]
 [ 8 12]]
컬럼 방향으로 정렬:
 [[ 8 12]
 [ 1  7]]
In [135]:
# np.argsort()
org_array=np.array([3,1,9,5])
sort_indices=np.argsort(org_array)
print(sort_indices)
[1 0 3 2]

정렬 행렬의 원본 행렬 인덱스가 잘 출력된 것을 확인할 수 있습니다.
여기서도 내림차순으로 정렬을 하고 싶으면 위에서와 같이 [::-1]을 적용하면 됩니다.

In [136]:
org_array=np.array([3,1,9,5])
sort_indices_desc=np.argsort(org_array)[::-1]
print(sort_indices_desc)
[2 3 0 1]

argsort()는 넘파이에서 매우 활용도가 높습니다. 넘파이의 ndarray는 실제 값과 그 값이 뜻하는 메타 데이터를 별도의 ndarray로 각각 가져야 합니다.
예들 들어 학생별로 시험 성적을 데이터로 표현을 하고자 한다면 학생의 이름과 시험 성적을 각각 ndarray로 가져야 합니다.

In [137]:
import numpy as np

name=np.array(['minji',"choonseok","yoonju","jongwon","siyeon"])
score=np.array([98,85,80,95,97])

sort_indicies_asc=np.argsort(score)
print(sort_indicies_asc)
print(name[sort_indicies_asc])
[2 1 3 4 0]
['yoonju' 'choonseok' 'jongwon' 'siyeon' 'minji']

예시로 시험 성적순으로 학생 이름을 출력해 보았습니다.
위와 비슷한 예제로 넘파이의 데이터 추출에서 많이 사용이 됩니다

In [ ]: