본문 바로가기

MLOps 개발자 양성과정/python

[Day-11] 데이터 분석을 위한 패키지(Numpy)

<목차>

1. 배열생성하기

2. 배열의 연산

3. 배열의 인덱싱과 슬라이싱


NumPy

: 다차원 배열 데이터를 효과적으로 처리할 수 있음

 

ㆍ 배열과 리스트?
- 파이썬에서 배열과 리스트 모두 인덱스 값 갖고 있음
- [ ]로 표시

ㆍ 리스트와 배열의 차이?//

- 배열 명확한 규칙성을 갖고 있음
- 배열 크기를 정확히 알고 있어 메모리 훨씬 효율적
- 배열 한계가 명확함(개수 안맞으면 에러)
- 배열은 배열간 연산이 가능함
   리스트 +는 앞의 리스트에 뒤의 리스트 값을 연결함
- 배열은 배열 전체에 연산이 가능함
  리스트의 *은 리스트의 요소를 반복함
- 리스트 원래는 인덱스 값이 없음(내 앞사람의 등을 잡는 방식으로 기억한다(pointer))

ㆍ배열과 튜플의 차이?

- 튜플은 변경, 삭제 어려움
- 배열은 그 범위를 뛰어넘는 값이 있다면 공간을 넓힘

 

01. 배열 생성하기

1) numpy 불러오기

- Numpy를 패키지에서 불러와서 별칭을 np로 설정

import numpy as np

 

 

2) 시퀀스 데이터로부터 배열 생성

arr_obj = np.array(seq_data)

* 시퀀스 데이터로 리스트와 튜플 타입 데이터 모두 사용가능(주로 리스트 데이터 활용)

 ㆍ정수 배열 생성

import numpy as np

data1 = [0, 1, 2, 3, 4, 5]
a1 = np.array(data1)
a1
>>> array([0, 1, 2, 3, 4, 5])

 

ㆍ정수와 실수가 혼합된 배열 생성

 - 정수 실수 혼합되어 있을 경우 모두 실수로 변환

data2 = [0.1, 5, 4, 12, 0.5]
a2 = np.array(data2)
a2
>>> array([ 0.1,  5. ,  4. , 12. ,  0.5])

 

ㆍ 배열의 속성 표현 (ndarray.속성)

data1 = [0, 1, 2, 3, 4, 5]
a1 = np.array(data1)
a1.dtype
>>> dtype('int32')

data2 = [0.1, 5, 4, 12, 0.5]
a2 = np.array(data2)
a2.dtype
>>> dtype('float64')
※ 'int32' 'float64' ?

배열은 크기 지정해 가지고 있어 최대 문자수 지정
배열 메모리 효율적인 이유


 

ㆍ 데이터를 직접 넣어서 배열 생성 가능

np.array([0.5, 2, 0.01, 8])
>>> array([0.5 , 2.  , 0.01, 8.  ])

np.array([[1,2,3], [4,5,6], [7,8,9]]) #2차원 배열도 생성 가능
>>> np.array([[1,2,3], [4,5,6], [7,8,9]])

 

 

3) arrange: 범위를 지정해 배열 생성

arr_obj = np.arrange([start,] stop [,step])

np.arange(0, 10, 2)
>>> array([0, 2, 4, 6, 8])

np.arange(1, 10) #step 생략
>>> array([1, 2, 3, 4, 5, 6, 7, 8, 9])

np.arange(5) #start , step 생략 
>>> array([0, 1, 2, 3, 4])

 

4) reshape(): 1차 배열을 2차배열로 변경

reshape(m,n) : m x n의 형태의 2차원 배열로 변경

 ※ 배열의 원소 개수와 reshape(m, n)의 m * n의 개수가 같아야 함

np.arange(12).reshape(4,3)
>>> array([[ 0,  1,  2],
       [ 3,  4,  5],
       [ 6,  7,  8],
       [ 9, 10, 11]])

 

ㆍ 배열의 형태 출력 (ndarray.shape)

b1 = np.arange(12).reshape(4,3)
b1.shape
>>> (4, 3)

#1차원 배열의 경우
b2 = np.arange(5)
b2.shape
>>> (5,) #뒤에 안나와

 

5) linspace():  범위의 시작과 끝을 지정하고 동일한 간격으로 개수만큼 나눈 배열을 생성

arr_obj = np.linspace(start, stop [,num])  (#num을 지정하지 않으면 50개)

# 0 ~ pi 까지 동일한 간격으로 10개로 나누기   
np.linspace(0, np.pi, 10 ) # 'np.pi'는 Numpy에서 π를 입력할 때 이용
>>> array([0.        , 0.34906585, 0.6981317 , 1.04719755, 1.3962634 ,
       1.74532925, 2.0943951 , 2.44346095, 2.7925268 , 3.14159265])

 

6) 특별한 형태의 배열 생성

ㆍ zeros() : 모든 원소가 0인 다차원 배열 생성

np.zeros(10)
>>> array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])

np.zeros((3,4))
>>> array([[0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])

 

ㆍ ones() : 모든 원소가 1인 다차원 배열 생성

np.ones(5)
>>> array([1., 1., 1., 1., 1.])

np.ones((3,5))
>>> np.ones((3,5))

 

ㆍ eye() : 단위행렬 생성(n x n 정사각형 행렬에서 주 대각선이 모두 1이고 나머지가 0인 행렬)

np.eye(3)
>>> array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])

 

7) astype(): 배열의 데이터 타입 변환

num_arr = arr.astype(dtype)

Numpy 배열 숫자뿐 아니라 문자열도 원소로 가질 수 있음

#문자열을 실수로
str_a1 = np.array(['1.567', '0.123', '5.123', '9', '8'])
num_a1 = str_a1.astype(float)
num_a1
>>> array([1.567, 0.123, 5.123, 9.   , 8.   ])

str_a1.dtype
>>> dtype('<U5')
num_a1.dtype
>>> dtype('float64')


#실수를 정수로
num_f1 = np.array([10, 21, 0.549, 4.75, 5.98])
num_i1 = num_f1.astype(int)
num_i1
>>> array([10, 21,  0,  4,  5]) #소숫점 버림

num_f1.dtype
>>> dtype('float64')
num_i1.dtype
>>> dtype('int32')

 

7) random: 난수 배열의 생성

ㆍ rand() : 0과 1 사이의 실수 난수를 갖는 Numpy 배열 생성

    rand_num = np.random.rand(d0, d1, d2, .....,dn)  

np.random.rand(2,3)
>>> array([[0.46826822, 0.06670429, 0.34700295],
       [0.78281807, 0.45567602, 0.56706535]])
       
np.random.rand()
>>> 0.943817939582156
       
np.random.rand(2,3,4) # z=층 y=행 x=열
>>> array([[[0.82252066, 0.0778985 , 0.86145235, 0.32814737],
        [0.79310682, 0.24176504, 0.628546  , 0.81633578],
        [0.84290595, 0.41124939, 0.02590378, 0.59567939]],

       [[0.47073607, 0.81741497, 0.65395495, 0.19010422],
        [0.15744914, 0.12794505, 0.72291499, 0.23141058],
        [0.58288676, 0.27485333, 0.68207206, 0.98836223]]])

 

ㆍ randint(): [low, high] 사이의 정수 난수를 갖는 배열 생성

    rand_num = np.random.randint([low,] high [,size])

np.random.randint(46, size=(3, 2))
>>> array([[45, 30],
       [39, 16],
       [ 3,  1]])

np.random.randint(1, 30) # 사이즈를 지정하지 않으면 1
>>> 20

 

02. 배열의 연산

1) 기본 연산

- 배열의 형태가 같다면 연산이 가능

arr1 = np.array([10, 20, 30, 40])
arr2 = np.array([1, 2, 3, 4])

arr1 + arr2
>>> array([11, 22, 33, 44])
arr1 - arr2
>>> array([ 9, 18, 27, 36])
arr1 * arr2
>>> array([ 10,  40,  90, 160])
arr1 / arr2
>>> array([10., 10., 10., 10.])
arr2**2
>>> array([ 1,  4,  9, 16], dtype=int32)

#복합연산
arr1 / (arr2 ** 2)
>>> array([10.        ,  5.        ,  3.33333333,  2.5       ])

#비교연산
arr1 > 20 #각각의 원소별로 값 반환
array([False, False,  True,  True])

 

2) 통계를 위한 연산

ㆍ 합계sum()와 평균mean()

arr3 = np.arange(5)
arr3
>>> array([0, 1, 2, 3, 4])

[arr3.sum(), arr3.mean()]
>>> [10, 2.0]

 

ㆍ 표준편차std()와 분산var()

표준편차: 평균을 중심으로 퍼져있는 정도 (분산**1/2)

분산: 변량이 평균으로 부터 떨어져있는 정도

(표준편차와 분산이 평균으로부터 가까울 수록 신뢰도가 높다)

[arr3.std(), arr3.var()]
>>> [1.4142135623730951, 2.0]

 

ㆍ 최솟값min()과 최댓값max()

[arr3.min(), arr3.max()]
>>> [0, 4]

 

ㆍ 누적합cumsum()과 누적곱cumprod()

arr4 = np.arange(1,5)
arr4
>>> array([1, 2, 3, 4])

arr4.cumsum() #계속 더해지는 결과 모두 반환
>>> array([ 1,  3,  6, 10], dtype=int32)

arr4.cumprod()
>>> array([ 1,  2,  6, 24], dtype=int32)

 

3) 행렬 연산

A = np.array([0, 1, 2, 3]).reshape(2,2)
A
>>> array([[0, 1],
       [2, 3]])

B = np.array([3, 2, 0, 1 ]).reshape(2,2)B
>>> array([[3, 2],
       [0, 1]])

ㆍ 행렬곱

: 두 개의 행렬에서 한 개의 행렬을 만들어내는 이항연산

A.dot(B)
>>> array([[0, 1],
       [6, 7]])
       
np.dot(A,B)
>>> array([[0, 1],
       [6, 7]])
       
B.dot(A) #어떤 게 기준이 되는지에 따라 값이 다름
>>> array([[4, 9],
       [2, 3]])

 

 

ㆍ 전치행렬

: 행과 열의 위치를 바꾸는 것(대각선을 기준으로 바꿈)

np.transpose(A)
>>> array([[0, 2],
       [1, 3]])
       
A.transpose()
>>> array([[0, 2],
       [1, 3]])

 

ㆍ 역행렬

: 행렬의 곱셈에 대한 역원 (단위행렬이 되게 하는 값)

np.linalg.inv(A)
>>> array([[0, 2],
       [1, 3]])

 

ㆍ 행렬식

: 행렬식의 값이 0인 경우에는 역행렬을 가질 수 없다. (det A = ad - bc = 0이면 A의 역행렬은 존재하지 않는다.)

np.linalg.det(A)
>>> -2.0

 

03. 배열의  인덱싱과 슬라이싱


인덱싱

: 배열의 위치나 조건을 지정해 배열의 원소를 선택하는 것

 

1) 1차원 배열의 인덱싱

 

ㆍ원소값 선택 및 변경

  배열명[위치1, 위치2,...,위치n]
a1 = np.array([0,10,20,30,40,50])
a1
>>> array([ 0, 10, 20, 30, 40, 50])

#원소 가져오기
a1[0]
>>> 0

# 원소 변경하기
a1[5] = 70
a1
>>> array([ 0, 10, 20, 30, 40, 70])

#1차원 배열에서 여러 개의 원소 선택하기
a1[1,3,4]
>>> array([10, 30, 40])

 

2) 2차원 배열 인덱싱

 

ㆍ 원소값 선택 및 변경

배열명[행위치, 열위치]
a2 = np.arange(10,100,10).reshape(3,3)
a2
>>> array([[10, 20, 30],
       [40, 50, 60],
       [70, 80, 90]]
       
# 원소 가져오기       
a2[0,2]
>>> 30
a2[1,2]
>>> 60

#원소 값 변경하기
a2[2,2] = 95
>>> array([[10, 20, 30],
       [47, 57, 67],
       [70, 80, 95]])

 

ㆍ 특정 행 변경

* 하나만 입력하면 행위치를 지정함

a2 = np.arange(10,100,10).reshape(3,3)
a2
>>> array([[10, 20, 30],
       [40, 50, 60],
       [70, 80, 90]])

a2[1]
>>> array([40, 50, 60])

a2[1] = np.array([45,55,65]) #배열 지정해서 변경
a2
>>> array([[10, 20, 30],
       [45, 55, 65],
       [70, 80, 95]])
       
a2[1] = [47,57,67] #리스트로 변경도 가능
a2
>>> array([[10, 20, 30],
       [47, 57, 67],
       [70, 80, 95]])

 

ㆍ여러 원소 선택하기

배열명 [행위치1, 행위치2,..행위치[n]], [열_위치1, 열위치2,...,열위치[n]]    
※ [행위치1, 열위치1], 아님 주의!!
a2[2,2] = 95
a2
>> array([[10, 20, 30],
       [47, 57, 67],
       [70, 80, 95]])
       
a2[[0,2],[0,1]]
>>> array([10, 80])

a2[[0,2],[0,2]]
>>> array([10, 95])

 

ㆍ조건에 따른 배열

배열명[조건]
a = np.array([1,2,3,4,5,6,0])

a[a > 3] #value 값
>>> array([4, 5, 6])

a[(a % 2) == 0]
>>> array([2, 4, 6, 0])

 

슬라이싱

: 범위를 지정해 배열의 일부분을 선택

 

1) 1차원 배열의 슬라이싱

배열[시작_위치:끝_위치
※ 범위 끝위치-1
b1 = np.array([0,10,20,30,40,50])
b1[1:4]
>>> array([10, 20, 30])

b1[:3]
>>> array([ 0, 10, 20])

 

 ㆍ슬라이싱으로 원소 변경

b1 = array([ 0, 10, 25, 35, 45, 50])

b1[2:5] = np.array([25,35,45])
b1
>>> array([ 0, 10, 25, 35, 45, 50])

b1[3:6] = 60 #같은 값으로 변경
b1
>>> array([ 0, 10, 25, 60, 60, 60])

 

2) 2차원 배열 슬라이싱

:배열[행_시작_위치:행_끝_위치, 열_시작_위치:열_끝_위치]
※ 범위는 끝 위치 -1

ㆍ 행.열 슬라이싱

b2 = np.arange(10,100,10).reshape(3,3)
b2 = array([[10, 20, 30],
       [40, 50, 60],
       [70, 80, 90]])
       
b2[1:3, 1:3]
>>> array([[50, 60],
       [80, 90]])
       
b2[:3, 1:]
>>> array([[20, 30],
       [50, 60],
       [80, 90]])

 

ㆍ 행을 지정하고 열을 슬라이싱

b2 = array([[10, 20, 30],
       [40, 50, 60],
       [70, 80, 90]])
       
b2[1][0:2]
>>> array([40, 50])

 

ㆍ 슬라이싱된 배열에 값 지정하기

b2[0:2, 1:3] = np.array([[25,35], [55,65]])
b2
>>> array([[10, 25, 35],
       [40, 55, 65],
       [70, 80, 90]])