| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
- 파이선
- GPT
- subnet
- Django
- Python
- chartjs-chart-financial
- vue
- database
- ec2
- 파이썬
- Chatbot
- pycharm
- AWS
- django db with ssh tunnel
- 오픈빌더
- 장고
- psycopg
- 따라하며 배우는 파이썬과 데이터 과학
- postgres
- 챗봇
- 파이참
- settings.py
- vpc
- RDS
- 개발일지
- 아나콘다
- 이터널리턴
- SSH Tunnel
- 파이팅
- Today
- Total
우아한 개발계발 블로그
따라하며 배우는 파이썬과 데이터 과학 (10) - 넘파이로 수치 데이터를 처리해보자 본문
2021.06.20 - [Study/따라하며 배우는 파이썬과 데이터 과학] - 따라하며 배우는 파이썬과 데이터 과학 (9) - 텍스트를 처리해보자
따라하며 배우는 파이썬과 데이터 과학 (9) - 텍스트를 처리해보자
2021.06.20 - [Study/따라하며 배우는 파이썬과 데이터 과학] - 따라하며 배우는 파이썬과 데이터 과학 (8) - 연관된 데이터를 딕셔너리로 짝을 짓자 따라하며 배우는 파이썬과 데이터 과학 (8) - 연관된
w00-ah.tistory.com
정규표현식에 대해 알아봤으니, 데이터 처리에 빠질 수 없는 넘파이에 대해 알아보자교수님과 결혼하는게 꿈입니다
10.1. NumPy
NumPy는 대용량의 배열과 행렬연산을 빠르게 수행하며, 고차원적인 수학 연산자와 함수를 포함하고 있는 파이선 라이브러리이다.
list가 있는데 왜 굳이 배열 라이브러리를 import하면서까지 사용해야하나 싶겠다면 [블로그1] , [블로그2]이 두 글을보고 오길 바란다. numpy의 속도를 비교해주는 블로그인데, 글을 읽는 것만으로도 넘파이가 압도적으로 빠르다는 것을 알 수있다.
책에 명시된대로 넘파이의 장점을 서술해보자면, 넘파이는 성능이 우수한 ndarray 객체를 제공한다. ndarray는 n차원 배열을 의미한다. 전통적으로 배열(array)은 동일한 자료형을 가진 데이터를 연속으로 저장한다.
ndarray 객체의 장점은 다음과 같다.
- C언어에 기반한 배열 구조이므로 메모리를 적게 차지하고 속도가 빠름.
- 배열과 배열 간의 수학적인 연산 가능. (연산자를 이용한 요소간의 연산)
- 고급 연산자와 풍부한 함수들을 제공
물론 사람이 느끼기엔 별로 크지않은 데이터들은 리스트와 넘파이의 속도를 비교해도 느끼지 못할 것이다.
그러나 그 데이터가 방대해지고 고차원화될수록 넘파이와 리스트사이에 연산속도 차이는 극명해진다.
넘파이의 장점인 배열간 벡터합 연산을 해보자
10.2. 넘파이 연산
>>> import numpy as np
>>> mid_scores = np.array([10, 20, 30])
>>> final_scores = np.array([60, 70, 80])
>>> total = mid_scores + final_scores
>>> print("시험성적의 합계 :", total) # 각 요소별 합계
>>> print("시험성적의 평균 :", total/2) # 모든 요소를 2로 나눈다.
시험성적의 합계 : [ 70 90 110]
시험성적의 평균 : [35. 45. 55.]
list로 두 배열을 합 연산을 진행했다면, 이는 [10, 20, 30, 60, 70, 80] 의 데이터를 반환 하였을 것이다.
그러나 넘파이는 벡터합 연산이 가능하므로, 우리가 원하는 시험성적의 합계와 평균을 얻을 수 있었다.
이처렁 넘파이는 list에서 허용되지 않던 요소간의 다양한 연산자를 활용한(+,-,*,/,**,//,% 등) 연산이 가능하다.
그리고 평균을 구하는 과정에서 알 수 있듯, ndarray 에 연산자를 통해 연산을 거치면 모든 요소에 해당하는 연산이 이루어진다.
10.3. 다차원 배열, 속성
넘파이의 핵심이 되는 다차원배열(ndarray)은 다음과 같은 다양한 속성을 가지고 있다.
| 속성 | 설명 |
| ndim | 배열 축 혹은 차원의 개수 |
| shape | 배열의 차원 차원으로 (m, n) 형식의 튜플 형이다. m과 n은 각 차원의 원소의 크기. |
| size | 배열 원소의 개수이다. 이 개수는 shape내 원소 크기의 곱과 같다. m * n |
| dtype | 배열내의 원소의 자료형을 기술하는 객체이다. 원시 c언어의 자료를 사용할 수 있다. |
| itemsize | 배열내의 원소의 크기를 바이트 단위로 기술한다. |
| data | 배열의 실제 원소를 포함하고 있는 버퍼 |
| stride | 배열 각 차원별로 다음 요소로 점프하는 데에 필요한 거리를 바이트로 표시한 값을 모든 튜플 |
이러한 속성을 이용하여 연산시에 일어나는 오류를 캐치할 수 있고, 미리 조건문을 세워 오류를 방지하는 코드를 작성할 수도 있을 것이다.
>>> a = np.array([1, 2, 3]) # 넘파이 ndarray 객체 생성
>>> a.shape # a 객체의 형태 (shape) => 차원과 크기를 알려준다.
(3,)
>>> a.ndim # a 객체의 차원
1
>>> a.dtype # a 객체 내부 자료형
dtype('int32')
>>> a.itemsize # a 객체 내부 자료형이 차지하는 메모리 크기 (byte)
4
>>> a.size # a 객체 전체 크기(항목의 수)
3
10.4. 인덱싱 & 슬라이싱
1차원 넘파이는 리스트와 동일한 방식으로 인덱싱/슬라이싱이 가능하다.
>>> scores = np.array([88,72,93,94,89,78,99])
>>> scores[2]
93
>>> scores[-1]
99
>>> scores[1:4]
array([72, 93, 94])
리스트와 마찬가지로 인덱스는 0부터 시작하고, 마지막 요소에 접근하려면 인덱스로 -1을 주면된다.
논리적인 인덱싱(logical indexing)
논리적인 인덱싱이란 어떤 조건을 주어서 배열에서 원하는 값을 추려내는 것이다.
>>> ages = np.array([18, 19, 25, 30, 28])
>>> y = ages > 20 # ages 에서 나이가 20살 이상인 사람을 판단
>>> y # 결과는 부울형의 넘파이 배열
array([False, False, True, True, True])
>>> ages[ ages > 20 ] # 위를 이용하여 20살 이상인 사람을 필터링
array([25, 30, 28]) # 조건식을 인덱스로 넘겨주면 필터링이 된다
10.5. 2차원 배열
파이썬에서 2차원 리스트는 "리스트의 리스트"라고 할 수 있다. 수학에서 행렬(matrix)과 비슷하지만 리스트는 행렬 연산을 지원하지 않는다.
>>> y = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> y
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> np_array = np.array(y)
>>> np_array
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
>>> np_array[0][2] # 리스트를 인덱싱할 때 많이 사용하는 스타일
3
>>> np_array[0, 2] # 넘파이를 인덱싱할 때 많이 사용하는 스타일
3
2차원 리스트를 만들고 이를 다시 2차원의 넘파이 배열로 변경하였다.
2차원 배열의 인덱싱 & 슬라이싱
2차원 배열에서 인덱싱을 하는 방법은, 2차원이므로 인덱싱도 2번하면 된다. 인덱싱하는 방법은 두가지로리스트를 인덱싱 하듯 [] 대괄호를 두번 써주던가, 하나의 대괄호에서 쉼표로 행과 열을 구분하여 입력하면 된다.
그리고 리스트 처럼 인덱스 표기법을 사용하여 배열의 요소도 변경할 수 있다. 하지만 파이썬 리스트와 달리, 넘파이 배열은 모든 항목이 동일한 자료형을 가진다는 것을 명심하여야 한다. 정수 배열에 부동 소수점 값을 삽입하려고 하면 소수점 이하값은 자동으로 사라진다. 한번 코드로 확인해보자.
>>> np_array
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
>>> np_array[2, 2]= 1.234
>>> np_array
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 1]])
넘파이에서 슬라이싱은 큰 행렬에서 작은 행렬을 끄집어내는 것으로 이해하면 된다.
>>> np_array = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]])
>>> np_array[0:2, 2:4] # 0에서 1까지의 행에서 2에서 3까지의 열로 이루어진 행렬을 지정
array([[3, 4],
[7, 8]])
다만 슬라이싱에서 주의해야할 점은 슬라이싱만큼은 넘파이 스타일로 슬라이싱 해야한다는 것이다.
리스트를 슬라이싱하듯 대괄호를 두개쓰게 되면 넘파이는 2차원 슬라이싱이 아니라, 슬라이싱을 두번하게 된다.
그러므로 넘파이 슬라이싱은 넘파이 스타일로 해야한다.
2차원 배열의 논리적인 인덱싱
>>> np_array = np.array([[1,2,3],[4,5,6],[7,8,9]]) # 2차원 배열 생성
>>> np_array > 5 # 각 요소를 5와 대소 비교
array([[False, False, False],
[False, False, True],
[ True, True, True]])
>>> np_array[np_array > 5] # 5보다 큰 요소만 필터링
array([6, 7, 8, 9])
>>> np_array[:,2] # 행에 상관없이 세번째 열만 필터링
array([3, 6, 9])
>>> np_array[:,2] > 5 # 핋터링 된 세번째 열들에 5와 대소비교
array([False, True, True])
>>> np_array % 2 == 0 # 모든 요소가 2로 나누어 떨어지는지 판별
array([[False, True, False],
[ True, False, True],
[False, True, False]])
>>> np_array[np_array % 2 == 0] # 2로 나누어 떨어지는 요소들을 필터링
array([2, 4, 6, 8])
10.6. arange() 함수
arange()함수는 range()함수와 거의 똑같이 특정한 범위의 정수를 가지는 넘파이 배열을 만든다.
range 처럼 arange(5) 는 [0, 1, 2, 3, 4]를 가지는 넘파이 배열을 생성한다.
>>> import numpy as np
>>> np.arange(5)
array([0, 1, 2, 3, 4])
>>> np.arange(1, 6)
array([1, 2, 3, 4, 5])
>>> np.arange(1, 10, 2)
array([1, 3, 5, 7, 9])
range 쓰듯 사용하면된다.
10.7. linspace(), logspace()
linspace()는 시작값부터 끝값까지 균일한 간격으로 지정된 개수만큼의 배열을 생성한다.
>>> np.linspace(0,10,100)
array([ 0. , 0.1010101 , 0.2020202 , 0.3030303 , 0.4040404 ,
0.50505051, 0.60606061, 0.70707071, 0.80808081, 0.90909091,
1.01010101, 1.11111111, 1.21212121, 1.31313131, 1.41414141,
1.51515152, 1.61616162, 1.71717172, 1.81818182, 1.91919192,
2.02020202, 2.12121212, 2.22222222, 2.32323232, 2.42424242,
2.52525253, 2.62626263, 2.72727273, 2.82828283, 2.92929293,
3.03030303, 3.13131313, 3.23232323, 3.33333333, 3.43434343,
3.53535354, 3.63636364, 3.73737374, 3.83838384, 3.93939394,
4.04040404, 4.14141414, 4.24242424, 4.34343434, 4.44444444,
4.54545455, 4.64646465, 4.74747475, 4.84848485, 4.94949495,
5.05050505, 5.15151515, 5.25252525, 5.35353535, 5.45454545,
5.55555556, 5.65656566, 5.75757576, 5.85858586, 5.95959596,
6.06060606, 6.16161616, 6.26262626, 6.36363636, 6.46464646,
6.56565657, 6.66666667, 6.76767677, 6.86868687, 6.96969697,
7.07070707, 7.17171717, 7.27272727, 7.37373737, 7.47474747,
7.57575758, 7.67676768, 7.77777778, 7.87878788, 7.97979798,
8.08080808, 8.18181818, 8.28282828, 8.38383838, 8.48484848,
8.58585859, 8.68686869, 8.78787879, 8.88888889, 8.98989899,
9.09090909, 9.19191919, 9.29292929, 9.39393939, 9.49494949,
9.5959596 , 9.6969697 , 9.7979798 , 9.8989899 , 10. ])
logspace()는 로그 스케일로 수들을 생성한다. 인자로 x, y, z를 입력받으면 10^x 부터 10^y 까지 z의 수가 생성된다.
>>> np.logspace(0,5,10)
array([1.00000000e+00, 3.59381366e+00, 1.29154967e+01, 4.64158883e+01,
1.66810054e+02, 5.99484250e+02, 2.15443469e+03, 7.74263683e+03,
2.78255940e+04, 1.00000000e+05])
이 두 함수들은 구간을 정해두고 균등한 간격의 수들이 필요할 때 사용하면 좋다.
10.8. reshape(), flatten()
new_array = old_array.reshape(shape)의 형태로 이루어지며, old_array를 새로 명시하는 배열의 형태로 바꾸어 준다.
>>> y = np.arange(12)
>>> y
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
>>> y.reshape(3, 4)
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> y.reshape(6, -1) # 인수로 -1을 전달하면 데이터의 개수에 맞춰 배열의 형태를 자동으로 정함
array([[ 0, 1],
[ 2, 3],
[ 4, 5],
[ 6, 7],
[ 8, 9],
[ 10, 11]])
요소의 개수가 적거나 새로이 생성될 배열의 형태가 원래의 배열과 호환되지 않는 경우에는 아래와 같은 오류를 뿜어내게 되니 조심하자.
y.reshape(7,2)
Traceback (most recent call last): File "<input>", line 1, in <module> ValueError: cannot reshape array of size 12 into shape (7,2)
flatten() 함수는 평탄화함수로 2차원 이상의 고차원 배열을 1차원 배열로 만들어 준다.
>>> y.flatten()
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
10.9. 난수
난수는 무작위성의 특징을 가지고 출현하는 수를 의미한다.
무작위성이라는 것은 특정한 패턴이 존재하지 않아 다음에 어떤 수가 나타날지 예측할 수 없다는 것이다.
일반적으로 컴퓨터 프로그래밍에서는 의사 난수(pseudo random number)을 사용한다. 시드(seed)라는 난수 발생의 씨앗이 될 수 를 주면, 이 값을 가지고 예측하기 힘든 난수를 만드는 것이다. 그래서 이 난수는 동일한 시드가 주어지면 동일한 난수를 생성한다.
넘파이에서 시드를 설정하는 것은 다음과 같다.
np.random.seed(100)
시드가 설정되면 아래 문장을 수행하여 5개의 난수를 얻을 수 있다. 난수는 0.0 ~ 1.0 사이의 값으로 생성된다. 앞서 말한 것처럼 시드가 동일하면 난수를 다시 생성할 때 똑같은 난수가 생성된다. 컴퓨터가 규칙을 가지고 생성한 수로 정확한 의미의 난수는 아니지만 난수의 가까운 수로 의사난수 라고 부른다.
>>> np.random.rand(5)
array([0.54340494, 0.27836939, 0.42451759, 0.84477613, 0.00471886])
난수를 얻을 때, 배열의 모양을 정하여 난수를 얻을 수있다.
>>> np.random.rand(3,3,3)
array([[[0.12156912, 0.67074908, 0.82585276],
[0.13670659, 0.57509333, 0.89132195],
[0.20920212, 0.18532822, 0.10837689]],
[[0.21969749, 0.97862378, 0.81168315],
[0.17194101, 0.81622475, 0.27407375],
[0.43170418, 0.94002982, 0.81764938]],
[[0.33611195, 0.17541045, 0.37283205],
[0.00568851, 0.25242635, 0.79566251],
[0.01525497, 0.59884338, 0.60380454]]])
정수 난수가 필요하다면, randint()를 사용하면된다.
>>> np.random.randint(1, 7, size=10)
array([1, 1, 4, 1, 3, 5, 3, 6, 3, 3])
array([[ 3, 2, 1, 9, 5, 1, 10],
[ 7, 3, 5, 2, 6, 4, 5],
[ 5, 4, 8, 2, 2, 8, 8],
[ 1, 3, 10, 10, 4, 3, 6]])
10.10. 정규 분포 난수
randn 함수는 정규 분포를 따르는 난수 생성한다.
>>> np.random.randn(5)
array([-0.25187914, -0.84243574, 0.18451869, 0.9370822 , 0.73100034])
>>> np.random.randn(5, 4)
array([[ 1.36155613, -0.32623806, 0.05567601, 0.22239961],
[-1.443217 , -0.75635231, 0.81645401, 0.75044476],
[-0.45594693, 1.18962227, -1.69061683, -1.35639905],
[-1.23243451, -0.54443916, -0.66817174, 0.00731456],
[-0.61293874, 1.29974807, -1.73309562, -0.9833101 ]])
10.11. median() 중앙값
median() 함수는 중앙값을 계산하여 반환한다.
다음은 평균은 175cm이고 표준편차는 10을 따르는 10000명의 키를 난수로 생성하고
이 데이터의 평균과 중앙값을 계산하는 코드이다.
>>> m = 175
>>> sigma = 10
>>> heights = m + sigma * np.random.randn(10000)
>>> heights
array([178.57507753, 158.86421497, 189.70713867, ..., 164.64185107,
183.81793513, 174.29176488])
>>> np.mean(heights)
175.00929416315643
>>> np.median(heights)
174.99083946416877
중앙값은 비정상적으로 큰 이상치(outlier) 값으로 인해 전체의 평균값이 전체의 대표성을 가지지못하는 경우를 막기위해서 사용된다.
10.11. 상관관계
상관관계란 쉽게 말하자면 키가 큰사람이 키가 작은 사람에 비해 몸무게가 많이 나가는 "경향"있는 것,
재산이 부유한 사람은 자동차의 가격이 높은 "경향" 이 있다고 할 때, 이러한 키와 몸무게, 재산과 자동차 가격의 상호 의존성이 있는 관계에 있다고 볼 수 있는데, 이러한 의존성의 정도가 바로 상관관계이다. "경향"이 크면클수록 상관관계도 높다고 볼 수 있다. (나는 이렇게 이해했다.)
corrcoef() 함수는 요소들의 상관관계를 계산한다.
corrcoef(x, y) 가 실행되면, 아래의 행렬과 같은 데이터를 반환한다.


는 x 와 y의 상관관계를 의미한다.
꼭 두가지 데이터의 상관관계만 볼 수 있는 것은 아니다.
데이터가 세가지라면 리스트로 묶어 인자에 전달하면 세가지를 한꺼면에 상관관계를 비교해준다.
>>> x = [i for i in range(100)]
>>> y = [i ** 2 for i in range(100)]
>>> z = [100 * np.sin(3.14*i/100) for i in range(100)]
>>> result = np.corrcoef([x, y, z])
>>> print(result)
[[ 1. 0.96764439 0.03763255]
[ 0.96764439 1. -0.21532645]
[ 0.03763255 -0.21532645 1. ]]
그래프로 생각하면
x는 1씩 증가하는 100개의 값
y는 x의 요소들을 제곱한 100개의 값
z는 진폭이 100인 사인 함수로 0도에서 부터 180도 까지의 그래프위의 100개의 점이다.
세개의 데이터를 비교해보니 x와 y의 상관관계가 제일높고, y와 z의 상관관계가 제일 떨어지는 것을 확인할 수 있다.
처음 다뤄본 넘파이는 어려웠다. 이러한 기능이 왜 필요할까부터 여러가지 의문점이 들었지만, 통계를 내리는 부분에서 넘파이의 힘을 볼 수 있어서 좋았다.
넘파이의 사용법이 헷갈릴 때는 공식사이트에서 넘파이에 대해 더 자세히 설명하고 있으니 참고하자.
https://numpy.org/doc/stable/contents.html#
NumPy Documentation — NumPy v1.20 Manual
numpy.org
'Study > 따라하며 배우는 파이썬과 데이터 과학' 카테고리의 다른 글
| 따라하며 배우는 파이썬과 데이터 과학 (12) – 판다스로 데이터를 분석해보자 (0) | 2021.06.20 |
|---|---|
| 따라하며 배우는 파이썬과 데이터 과학 (11) - 차트를 멋지게 그려보자 (0) | 2021.06.20 |
| 따라하며 배우는 파이썬과 데이터 과학 (9) - 텍스트를 처리해보자 (0) | 2021.06.20 |
| 따라하며 배우는 파이썬과 데이터 과학 (8) - 연관된 데이터를 딕셔너리로 짝을 짓자 (0) | 2021.06.20 |
| 따라하며 배우는 파이썬과 데이터 과학 (7) - 데이터를 리스트와 튜플로 묶어보자 (0) | 2021.06.20 |