━━━━ ◇ ━━━━
따라하며 배우는 파이썬과 데이터 과학/PART 2. 데이터 과학과 인공지능

Chapter 10. 넘파이로 수치 데이터를 처리해보자

이번 시간의 목차

1. 넘파이가 무엇이길래?

2. 다차원 배열에 대해 자세하게 알아보자!

3. 다차원 배열에서의 인덱싱은?

4. 다차원 배열에서의 슬라이싱은?

5. 넘파이의 다양한 함수

6. 정규 분포(1) - 난수 생성

7. 정규 분포(2) - 정규 분포를 따르는 난수, 평균과 중앙값

8. 상관 관계가 뭐지?

9. 마무리

 

 

 

자, 가자! 파이썬의 세계로!


 

넘파이가 무엇이길래? 

 

 우리는 7장에서 리스트에 대해 배웠다. 여러 데이터를 한 곳에 저장할 수 있기 때문에 자료구조로서 강력하고 활용도가 높지만, 단점이 많다. 대표적인 단점으로는 리스트 속 숫자 데이터에 대한 연산을 하려면 인덱스 하나하나에 접근해서 일일이 반복문을 통해 계산을 해야 하는 복잡함이 있다. 

 

 그래서 많은 데이터 과학자가 리스트 대신에 넘파이(Numpy)를 선호한다. 넘파이는 대용량의 배열과 행렬 연산을 빠르게 수행하고, 고차원 수학 연산자와 함수를 포함하는 파이썬 라이브러리이다. 그야말로 파이썬에서 수치 데이터를 다루는 가장 기본적이고 강력한 패키지이다. 

 

 데이터 분석을 위한 패키지인 판다스(Pandas)나 기계학습을 위한 사이킷런(Scikit-Learn), 텐서플로우(Tensorflow) 등이 넘파이 위에서 작동하기 때문에 데이터 분석이나 기계 학습 프로젝트를 원한다면 넘파이에 대해 확실히 이해해야 한다. 

 

 

 넘파이의 가장 핵심적인 객체는 다차원 배열이다. N차원 배열을 ndarray(2D, 3D할 때 그 nd + 배열array)라는 객체로 제공한다. 위처럼 2차원, 3차원 등의 배열을 만들 수 있다. 배열의 각 요소는 인덱스(Index)라고 불리는 정수로 참조되고, 넘파이에서의 차원은 (Axis)이라고 불린다. 배열(Array)는 동일한 자료형을 가진 데이터를 연속으로 저장하기 때문에 ndarray도 동일한 자료형만 저장한다. 하지만 리스트는 동일하지 않더라도 저장할 수 있다. 

 

 그래서 결국 왜 이 ndarray를 쓰냐고 묻는다면, 아래의 세 가지 이유를 들 수 있다.

  • ndarray는 C 언어에 기반한 배열 구조이므로 메모리가 적게 들고 속도가 빠르다.
  • ndarray를 사용하면 배열과 배열 간에 수학적인 연산을 적용할 수 있다.
  • ndarray는 고급 연산자와 풍부한 함수들을 제공한다. 

 

 잘 와닿지 않는다면 직접 해 보자.

 

>>> mid_scores = [102030]           #파이썬 기본 리스트로
>>> final_scores = [708090]         #학생 1, 2, 3의 중간, 기말 점수 목록 생성
>>> total = mid_scores + final_scores   #중간, 기말 합을 구하고 싶지만
>>> total
[102030708090]                #원하는 결과가 나오지 않는다.
 
>>> import numpy as np                  #넘파이를 np 라는 이름으로 불러온다.
>>> mid_scores = np.array([102030]) #array()를 이용한 넘파이 배열 생성
>>> final_scores = np.array([708090])
>>> total = mid_scores + final_scores   #둘의 합을 구한다. 
>>> total
array([ 80100120])                  #값끼리 덧셈이 된다!
cs

 

기본 리스트를 사용하면 리스트 속 값끼리 바로 계산하기가 어렵지만, 넘파이 배열을 사용하면 바로 계산이 된다. 넘파이 역시 모듈이기 때문에 불러오려면 import numpy를 입력해주어야 한다. 뒤에 붙은 as np는 앞의 numpy라는 이름을 대체하는 별명(Alias)이다. 이 별명은 6장 함수와 모듈에서 이미 한 번 설명했었다. 보통 numpy의 별명으로 np를 붙여 사용하기 때문에 넘파이 사용 프로그램에서 이 코드의 호출은 필수이다. 

 

넘파이 배열을 만들려면 array()  함수를 사용한다. 괄호 속에 파이썬 리스트를 넣으면 넘파이 배열로 만들어준다. 이렇게 만들어진 넘파이 배열은 배열 요소별로 지정된 연산을 수행할 수 있다. 위에서 좀 더 나아가 보면 중간 점수와 기말 점수의 평균을 구할 수도 있다.

 

>>> print('시험 성적의 합계 :', total)
시험 성적의 합계 : [ 80 100 120]
 
>>> print('시험 성적의 평균 :', total/2#시험을 2번 쳤으니 
시험 성적의 평균 : [40. 50. 60.]         #평균은 합계를 2로 나눈 것이다.  
cs

 


 

다차원 배열에 대해 자세하게 알아보자!

 

 넘파이의 핵심이라 할 수 있는 이 다차원배열(Ndarray)의 속성은 아래의 단어를 통해 구할 수 있다. 

 

>>> a = np.array([13579]) #넘파이 ndarray 객체 생성
>>> a.shape                       #객체의 형태(shape)
(5,)
>>> a.ndim                        #객체의 차원 
1
>>> a.dtype                       #객체 내부의 자료형 
dtype('int32')
>>> a.itemsize                    #객체 내부 자료형이 차지하는 메모리 크기 
4                                 #byte 단위
>>> a.size                        #객체의 전체 항목 수
5 
cs

 

이런 속성을 이용해서 프로그램 속 오류를 찾거나 배열의 상세한 정보를 얻을 수 있다. 

속성에 대한 자세한 설명은 아래에 정리해두었다.

 

속성 설명
ndim 배열 축 혹은 차원의 개수
shape 배열의 차원으로(m, n)형식의 튜플 형이다. 이때 m과 n은 각 차원의 원소의 크기를 알려주는 정수.
size 배열 원소의 개수이다. 이 개수는 shape내의 원소의 크기의 곱과 같다. (m, n) 형태 배열의 size는 m * n.
dtype 배열 내의 원소의 형을 기술하는 객체이다. 넘파이는 파이썬 표준 자료형을 사용할 수 있으나 
넘파이 자체의 자료형인 bool_, character, int_, int8, int16, int32, int64,
float, float8, float16_, float32, float64, complex_, complex64, object 형을 사용할 수 있다. 
itemsize 배열내의 원소의 크기를 바이트 단위로 기술한다. 예를 들어 int32 자료형의 크기는 32/8 = 4bytes 가 된다.
data 배열의 실제 원소를 포함하고 있는 버퍼
stride 배열 각 차원별로 다음 요소로 점프하는 데에 필요한 거리를 바이트로 표시한 값을 모은 튜플

 

 조금 전에 해 봤던 연산을 다시 한 번 해보자. 

 

>>> a = np.array([13579])
 
>>> a + 10      
array([1113151719])
>>> a - 10
array([-9-7-5-3-1])
>>> a * 10
array([1030507090]) 
>>> a * 0.5         #실수를 곱할 수도 있다.
array([0.51.52.53.54.5])
>>> a / 2
array([0.51.52.53.54.5])
 
cs

 

이 넘파이 배열에는 수학적인 연산자를 얼마든지 적용할 수 있다. 또 항목 각각에 대해서 비교 연산자를 사용할 수도 있다. 

 

>>> a < 5
array([ True,  TrueFalseFalseFalse])
 
>>> a == 3
array([False,  TrueFalseFalseFalse])
cs

 

 

비교 연산자를 사용하면 이렇게 각각에 대한 참, 거짓을 판단하여 반환한다. 

 


 

다차원 배열에서의 인덱싱은?

 

 리스트에서는 각 항목에 접근하려면 인덱스를 이용했다. 

넘파이에서도 마찬가지로 특정한 요소를 추출하려면 인덱스를 사용할 수 있다. 첫 항목을 0으로 시작해서 인덱스가 부여된다.

 

>>> scores = np.array([88729394897899])
>>> scores[:3]
array([887293])
>>> scores[-1]
99
 
>>> scores[1:5]
array([72939489])
>>> scores[3:-2]
array([9489])
cs

 

리스트와 마찬가지로 슬라이싱도 가능하다. 시작 인덱스나 종료 인덱스는 생략이 가능하고, 끝 인덱스로 음수를 넣으면 양수 인덱스와 마찬가지로 음수 인덱스에 해당하는 항목의 전까지 출력해준다. 

 

 이 인덱싱을 잘 이용하면 어떤 조건을 주어서 배열에서 원하는 값을 추려내는 논리적인 인덱싱(Logical Indexing)을 해낼 수 있다. 위의 scores 배열을 이용한 간단한 예시를 들어보자.

 

#합격점을 85점이라고 가정하자.
>>> scores > 85
array([ TrueFalse,  True,  True,  TrueFalse,  True])
 
#합격한 점수만 뽑아온다. 
>>> scores[scores>85]
array([8893948999])
cs

 

 넘파이 배열에 비교 연산자를 사용하면 부울형의 넘파이 배열을 반환해준다. 이 부울형 넘파이 배열에서 True를 반환한 항목만 잘라내고 싶다면 인덱스로 부울형 배열을 넣어주면 True를 반환하는 항목만 잘라서 새 넘파이 배열을 반환해 준다.

 

 넘파이는 다차원 배열도 지원한다. 그리고 이 다차원 배열 역시 인덱싱이 가능하다.

다차원 배열의 인덱싱을 알아보기 위해 우선 다차원 배열을 만들어보자.

 

>>> import numpy as np
>>> y = [[123], [456], [789]] #2차원 리스트
>>> y
[[123], [456], [789]]         #행렬을 지원하지는 않는다.
 
>>> np_array = np.array(y)                #2차원 넘파이 배열
>>> np_array                              #행렬을 지원한다. 
array([[123],
       [456],
       [789]])
cs

 

 리스트로는 다차원 행렬이 만들어지지 않지만, np.array( )를 사용하면 쉽게 다차원 배열을 만들 수 있다. 

위의 2차원 배열에서 특정 요소를 가져올 때는 두 가지의 방법이 쓰인다.

 

 

 첫 번째는 인덱스를 두 개 사용하는 것이다. 처음 쓰인 인덱스는 행의 번호이고, 두 번째 인덱스는 열의 번호이므로 위처럼 np_array[0][2] 는 3을 의미하게 된다. 

 

 두 번째는 순서쌍으로 나타내는 것이다. 인덱스가 부여되는 방법은 위와 동일하고, 대신 대괄호 하나 속에 [0, 2]처럼 순서쌍을 써 주면 된다. 이런 인덱싱 방법을 넘파이 스타일(Numpy Style)이라고 한다. 넘파이 배열은 행렬이기 때문에 [row, col] 인덱스를 사용하면 row행을 가져와서 col 번째 항목을 찾는 것이 아닌, 바로 그 항목에 접근하는 것임을 알아야 한다. 

 

>>> np_array[01= 20     #항목을 변경할 수 있다.
>>> np_array[21= 7.5    #동일한 자료형만 취급하기 때문에 
>>> np_array
array([[ 120,  3],
       [ 4,  5,  6],
       [ 7,  7,  9]])       #정수를 소수로 바꿀 수는 없다. 
cs

 

 리스트처럼 인덱싱을 통해 특정 항목을 바꿀 수도 있다. 하지만 넘파이 배열은 동일한 자료형만 취급하기 때문에 위의 정수 자료형 배열 속 항목을 소수로 바꾸면 소숫점 이하는 자동으로 버려지게 된다.

 


 

다차원 배열에서의 슬라이싱은?

 

  넘파이에서 슬라이싱은 큰 행렬 속에서 작은 행렬을 뽑아내는 것으로 이해하면 쉽다. 

 

>>> np_array = np.array([[1234], [5678], [9101112], [13141516]])
>>> np_array
array([[ 1,  2,  3,  4],
       [ 5,  6,  7,  8],
       [ 9101112],
       [13141516]])   #(4*4) 크기의 2차원 행렬 
 
>>> np_array[0:22:4]      #0-1행 2-3열의 항목을 잘라온다. 
array([[34],
       [78]])
cs

 

리스트와 마찬가지로 뛰어세기나 한 행, 한 열을 통째로 가져오는 것도 가능하다.

 

>>> np_array[:, 1]      #1열을 잘라온다.
array([ 2,  61014])
 
>>> np_array[::2, ::2]  #행과 열을 2씩 뛰어세고 잘라온다. 
array([[ 1,  3],
       [ 911]])
cs

 

 이해하기 쉽게 그림으로 설명하자면 이렇게 된다.

 

 

 

 이번엔 다차원 배열에서 논리적인 연산을 해 보자. 

 

>>> np_array
array([[123],
       [456],
       [789]])
 
 
>>> np_array>5    #np_array 속 항목이 5보다 큰지 검사 
array([[FalseFalseFalse],
       [FalseFalse,  True],
       [ True,  True,  True]])
 
>>> np_array[:, 2#np_array의 일부만 가져온다.
array([369])
>>> np_array[:, 2> 5  #일부가 5보다 큰지 검사 
array([False,  True,  True])
 
>>> np_array % 2 == 0   #짝수인지 검사 
array([[False,  TrueFalse],
       [ TrueFalse,  True],
       [False,  TrueFalse]])
 
>>> np_array[np_array % 2 == 0#짝수인 항목만 가져온다.
array([2468])
cs

 

이런 논리적 연산을 이용하면 특정수의 배수를 추출하는 등 필터링 작업을 손쉽게 해낼 수 있다. 

 


 

넘파이의 다양한 함수

 

 넘파이도 많은 함수를 가지고 있다.

이번에는 arage(), linspace(), logspace(), reshape(), flatten() 함수에 대해 알아보자.

 

arange()
>>> #np.arange([start,] stop, [step])
 
>>> np.arange(5)  #끝 인자는 생략할 수 없다.
array([01234])
>>> np.arange(16#시작 인자를 사용함.
array([12345]) 
>>> np.arange(0102#시작, 끝, 건너뛰기 인자를 사용함.
array([02468])
cs

 

arange() 함수는 특정 범위의 정수를 가지는 넘파이 배열을 만들어준다.

데이터 생성을 시작할 값, 데이터 생성을 멈출 값, 그리고 생성 간격을 인자로 받는다. 예를 들어 arange(5)는 array([0, 1, 2, 3, 4])를 반환한다.

 

arange()는 우리가 for 반복문이랑 함께 섞어 썼던 range() 함수와 비슷하다. range() 함수로 생성된 것은 반복 가능(iterable) 객체이고 이것으로 리스트를 만들 수 있다. 

 

 

linspcae()
>>> #np.linspace(start, stop, num = 50)
 
>>> np.linspace(01050)
array([ 0.        ,  0.20408163,  0.40816327,  0.6122449 ,  0.81632653,
        1.02040816,  1.2244898 ,  1.42857143,  1.63265306,  1.83673469,
        2.04081633,  2.24489796,  2.44897959,  2.65306122,  2.85714286,
        3.06122449,  3.26530612,  3.46938776,  3.67346939,  3.87755102,
        4.08163265,  4.28571429,  4.48979592,  4.69387755,  4.89795918,
        5.10204082,  5.30612245,  5.51020408,  5.71428571,  5.91836735,
        6.12244898,  6.32653061,  6.53061224,  6.73469388,  6.93877551,
        7.14285714,  7.34693878,  7.55102041,  7.75510204,  7.95918367,
        8.16326531,  8.36734694,  8.57142857,  8.7755102 ,  8.97959184,
        9.18367347,  9.3877551 ,  9.59183673,  9.7959183710.        ])
cs

 

linspace() 함수는 시작값부터 끝값까지 균일한 간격으로 지정된 개수만큼의 배열을 생성한다. 

데이터 생성을 시작할 값, 데이터 생성을 멈출 값, 그리고 생성할 데이터의 개수를 인자로 받는다. 데이터의 개수의 기본값은 50이다. 예시로는 0에서 10까지의 수를 50개로 쪼갠 수를 생성해보았다. 이는 상당히 많이 사용되는 함수이다.

 

 

logspace()
>>> #np.logspace(start, stop, num = 50, base = 10)
 
>>> np.logspace(0550, base = 2)
array([ 1.        ,  1.07329065,  1.15195282,  1.23638019,  1.3269953 ,
        1.42425165,  1.52863599,  1.64067071,  1.76091654,  1.88997526,
        2.02849277,  2.17716233,  2.33672798,  2.50798829,  2.69180039,
        2.88908419,  3.10082705,  3.32808868,  3.57200647,  3.83380115,
        4.11478293,  4.41635805,  4.74003581,  5.08743612,  5.46029763,
        5.8604864 ,  6.29000526,  6.75100385,  7.24578931,  7.77683793,
        8.34680745,  8.9585504 ,  9.6151283910.3198274211.07617429,
       11.8879543112.7592302313.6943625214.6980312715.77525955,
       16.9314385918.1723547519.5042184720.9336953422.4679395 ,
       24.1146294225.8820063127.7789154129.8148502132.        ])
cs

 

logspace() 함수는 시작값부터 끝값의 base를 밑으로 하는 로그 스케일로 수를 생성한다. 

인자는 linspace() 함수와 동일하지만, base 라는 키워드 인자가 있다. base는 지수의 밑이 될 수이고, 시작값과 끝값은 base의 지수로 올라간 뒤 숫자가 쪼개진다. 예로는 base를 2로, 시작값을 0, 끝값을 5로 두면 2^0 ~ 2^5를 50개로 쪼갠 수를 생성했다. 

 

 

reshape()
>>> #B = A.reshape(shape)
 
>>> A = np.arange(111)
>>> A
array([ 1,  2,  3,  4,  5,  6,  7,  8,  910])
 
>>> B = A.reshape(52)  # A를 5행 2열로 만든다.
>>> B
array([[ 1,  2],
       [ 3,  4],
       [ 5,  6],
       [ 7,  8],
       [ 910]])
 
>>> C = A.reshape(5-1# -1은 데이터의 개수에 맞춰 
>>> C                    # 자동으로 형태를 결정한다. 
array([[ 1,  2],
       [ 3,  4],
       [ 5,  6],
       [ 7,  8],
       [ 910]])
cs

 

reshape() 함수는 데이터의 개수는 유지한 채로 배열의 차원과 형태를 변경해준다. 이 함수의 인자 shape는 튜플의 형태로 넘겨주는 것이 원칙이지만, 꼭 튜플형으로 써주지 않아도 동일하게 처리된다. 원래 배열과 reshape()에 의해 생성될 배열의 형태가 맞지 않는 경우에는 오류가 난다. 

 

 

flatten()
>>> B                 #2차원 배열 B
array([[ 1,  2],
       [ 3,  4],
       [ 5,  6],
       [ 7,  8],
       [ 910]])
 
>>> B.flatten()       #2차원 이상의 배열을 1차원으로 평탄화 시킨다. 
array([ 1,  2,  3,  4,  5,  6,  7,  8,  910])
cs

 

flatten() 함수는 2차원 이상의 고차원 배열을 1차원 배열로 평탄화 시켜주는 역할을 한다. 별다른 인자는 필요로 하지 않는다. 

 


 

정규 분포(1) - 난수 생성

 

 데이터 과학에서는 직접 입력하기도 힘들 정도로 많은 데이터를 다뤄야 한다. 어떤 데이터 간의 관계를 파악하는 등 데이터를 분석해야 하기 위해서는 일단 데이터를 생성해야 하는데, 그 많은 데이터를 직접 생성하기는 힘들 것이다. 따라서 직접 데이터를 입력하고 분석하기 전에 확률 분포에서 난수를 생성하여 실험 데이터로 사용하는 것부터 시작할 수 있다. 

 

 난수(Random Number)는 무작위성(Randomness)의 특징을 갖고 출현하는 수를 의미한다. 이 난수를 자연적으로 생성하기는 어렵기 때문에 컴퓨터 프로그래밍에서도 쉬운 일이 아니다. 그래서 일반적으로 컴퓨터 프로그래밍에서는 의사 난수(Pseudo Random Number)를 사용한다. 이것은 시드(Seed)라고 하는 난수 발생의 씨앗이 될 수를 주면, 이 값을 기반으로 예측하기 어려운 수를 만들어내는 것으로, 정확한 의미의 난수는 아니지만 난수에 가까운 수이다.

 

 넘파이에서 난수의 시드를 설정하고 난수를 생성하는 문장은 아래와 같다.

 

>>> np.random.seed(100)
 
>>> np.random.rand(5)       #1차원 배열 
array([0.543404940.278369390.424517590.844776130.00471886])
>>> np.random.rand(24)    #2차원 배열 
array([[0.121569120.670749080.825852760.13670659],
       [0.575093330.891321950.209202120.18532822]])
cs

 

보다시피 난수는 0과 1 사이의 숫자로 생성된다. 이 범위를 0과 1이 아닌 다른 범위로 변경하고 싶다면 아래처럼 써주면 된다.

 

>>> a = 10
>>> b = 20
 
>>> (b - a) * np.random.rand(5+ a   #10에서 20 사이의 난수 5개를 생성 
array([11.0837689 , 12.1969749319.7862378518.1168314911.71941013])
cs

 

소수가 아닌 정수로 된 난수가 필요하다면 randint() 를 사용하면 된다. randint(a, b)는 3장 random 모듈에서도 한 번 다뤄봤다. 넘파이의 random 에서도 randint()를 제공한다. 

 

>>> np.random.randint(111, size = 10)      #1에서 10사이의 난수 10개 생성 
array([6455482288])
 
>>> np.random.randint(111, size = (25))  #1에서 11사이의 2*5 
array([[ 1,  31010,  4],
       [ 3,  6,  9,  2,  1]])
cs

 


 

정규 분포(2) - 정규 분포를 따르는 난수, 평균과 중앙값

 

 위에서 생성한 난수는 난수이기는 하지만, 특정한 구간에서 난수를 생성하다 보면 해당 구간 전체에서 데이터가 골고루 나타나게 된다. 하지만 현실에서는 많은 사건 발생 확률이 정규 분포를 띈다. 이 정규 분포를 따르도록 난수를 생성하려면 어떻게 해야 할까? 

 

정규 분포 그래프, 위키백과

 

 정규 분포란 위와 같은 형태를 가지는 확률 분포 함수이다. 각각의 분포는 평균(m)과 표준편차(σ)가 주어진다. 확률분포가 평균값에서 가장 높고, 평균값에서 멀어질수록 발생 확률이 낮아진다. 표준편차가 크면 클수록 데이터의 흩어지는 정도가 커지기 때문에 발생 확률이 평평하고 펴진 상태에 가까워진다. 

 

 파이썬에서는 정규 분포를 따르도록 난수를 생성할 수 있는 함수를 제공해준다. 바로 randn()이다. 예를 들어 정규 분포를 따르는 난수 5개를 생성하려면 아래처럼 입력하면 된다. 

 

>>> np.random.randn(5)
array([0.568770160.538488771.315574181.4789783 , 0.22142626])
 
>>> np.random.randn(25)
array([[-1.11789416-0.18499993-0.43118745,  0.14253625-1.49639345],
       [ 0.30687456-0.04634723,  0.41714428-0.60377052,  0.68596725]])
cs

 

이 난수들의 평균은 0이고 표준편차는 1이 된다. 이 평균값(mu)과 표준편차(sigma)를 바꾸고 그에 맞는 정규분포를 만들고 싶다면 아래처럼 입력하면 된다.

 

>>> mu = 10
>>> sigma = 2
>>> randoms = mu + sigma * np.random.randn(54)
 
>>> randoms
array([[11.7199475710.3831108812.3564558611.20056714],
       [ 6.3106802710.08517618,  9.9518300210.76811569],
       [ 9.901815  ,  6.79661765,  9.7224748710.02417253],
       [ 7.40371167,  9.74222792,  9.43415673,  9.64166349],
       [11.3515035414.76119068,  7.70659828,  9.41771331]])
cs

 

위의 예시는 평균이 10이고 표준편차가 2일 때의 정규 분포를 따르는 난수를 생성하고 있다. 

 

 

 이제 가상 데이터가 아니라 실제 데이터를 조작해보자. 

10000명의 키를 난수로 생성하고, 평균과 중앙값을 구해보자. 

 

>>> m = 175        #10000명의 키 평균이 175 cm라고 가정하자.
>>> sigma = 10
 
>>> heights = m + sigma * np.random.randn(10000)
>>> heights
array([166.45774228183.63406468171.8361877 , ..., 169.5667928 ,
       160.32799726189.93610508])
 
>>> np.mean(heights)     #평균값 
175.05694108221613
>>> np.median(heights)   #중앙값 
175.06255564841598
cs

 

이렇게 생성된 데이터에서 보통 사람들의 키(대표값)를 구하는 방법이 바로 평균과 중앙값이다. 위의 예시에서 보면 별 차이가 없어보이지만, 평균과 중앙값은 용도가 약간씩 다르다. 

 

>>> a = np.array([359218])   #비정상적으로 큰 데이터 18이 있다. 
>>> np.mean(a)
7.4
>>> np.median(a)                     #[3, 5, 9, 2, 18] 중 가운데 항목을 구한다. 
5.0
cs

 


 

상관 관계가 뭐지?

 

 이번에는 상관 관계에 대해 알아보자. 

보통 키가 큰 사람의 몸무게가 키가 작은 사람에 비해서 많이 나가고, 집이 부유한 사람일 수록 비싼 차를 타는 경향이 있는 것도 볼 수 있다. 이렇게 키와 몸무게, 재력과 자동차의 가격은 상호 의존성이 있는 관계에 있다고 볼 수 있다. 이런 의존성의 정도를 상관 관계라고 한다.

 

넘파이에서는 corrcoeff(x, y)함수를 통해 요소 간 상관 관계를 계산할 수 있다.

 

>>> import numpy as np
>>> x = [i for i in range(100)]       #0부터 99까지의 수
>>> y = [i ** 2 for i in range(100)]  #0부터 99까지의 수의 제곱 
 
>>> result = np.corrcoef(x, y)
>>> print(result)
[[1.         0.96764439]
 [0.96764439 1.        ]]
cs

 

 

 x와 y는 데이터를 담고 있는 리스트나 배열이 올 수 있다. 완전한 음의 상관관계일 경우 -1, 상관관계가 전혀 없을 때는 0, 완전한 양의 상관관계를 가질 때 1을 반환한다. 자기 자신과의 상관관계는 1이므로 이 행렬의 대각선은 언제나 1이다. 

 

 이 상관 관계는 여러 개의 데이터에 대해서도 계산할 수 있다. 

 

>>> x = [ i for i in range(100) ]
>>> y = [i ** 2 for i in range(100) ]
>>> z = [100 * np.sin(3.14 * i/100for i in range(100)] #0에서 180도까지 사인값
 
>>> np.corrcoef( [x, y, z] )   #z와 x, y는 상관관계가 크게 없다.
array([[ 1.        ,  0.96764439,  0.03763255],
       [ 0.96764439,  1.        , -0.21532645],
       [ 0.03763255-0.21532645,  1.        ]])
cs

 

새 데이터 z는 진폭이 100이고 범위가 0에서 180도인 사인값이다. 사인 함수는 주기함수이기 때문에 선형으로 증가하는 x나 비선형적으로 증가하는 y와는 큰 상관 관계가 없다. 실제로 큰 상관이 없어서 음수 상관관계가 나온 것을 볼 수 있다. 

 


 

마무리

 

 이번 시간에는 데이터를 강력하게 다룰 수 있는 넘파이에 대해 알아보았다. 

마지막으로 심화문제 10.4와 10.5를 풀어보고 마치도록 하자. 접은글 속 해답 코드는 참고만 하자.

 

10.4 :

1) 넘파이의 인덱싱 기능을 활용하여 5*5 크기의 행렬을 생성하라. 이 행렬의 내부는 모두 0과 1로 이루어져 있는데, 그 패턴은 다음과 같이 체크판 형태를 가진다. 

[[10101],
[01010],
[10101],
[01010],
[10101]])
cs
더보기
>>> import numpy as np
 
>>> a = np.array([i % 2 for i in range(126)]) #체크판 열 생성
#[i % 2 for i in range(1, 26)] 
#= [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
 
>>> a.reshape(55# 5 * 5 사이즈로 만든다. 
array([[10101],
       [01010],
       [10101],
       [01010],
       [10101]])
cs

 

 

2) 이 행렬의 행방향 성분의 합을 다음과 같이 출력하여라.

행렬의 행 방향 성분의 합 :
[3 2 3 2 3]
cs
더보기
import numpy as np
 
= np.array([i % 2 for i in range(126)])
= a.reshape(55)
 
print('[', end = ' ')
for t in range(len(b)) :
    print(sum(b[:, t]), end = ' '#행을 인덱싱해서 불러온다. 
 
print(']')
 
cs

 

 

10.5: 0에서 32까지의 값을 순서대로 가지는 (4, 4, 2) 형태의 3차원 배열 a가 다음과 같이 있다. 이 배열에 대하여 10번째와 20번째 원소를 구하는 식을 만드시오. (힌트: flatten() 함수를 사용하여라.)

= np.arange(0, 32).reshape(442)
-------------------------------------
10번째 원소 : 9
20번째 원소 : 19 
cs
더보기
import numpy as np
= np.arange(032).reshape(442)
= a.flatten()   #1차원 배열로 만들어 순서를 찾기 쉽게 만든다. 
 
print('10번째 원소 : ', b[9])
print('20번째 원소 : ', b[19])
 
cs
COMMENT