본문 바로가기
국비 교육/데이터

[데이터 분석] Numpy - 3

by 육츠 2023. 11. 7.
Contents 접기

기본 연산

배열에 대한 산술 연산자는 요소별로 적용된다.( np.floor_divide == // , np.remainder == %)
연산 시 자료형은 적절하게 형변환(upcasting) 된다.

a = np.ones(3,dtype= np.int32)
print(a.dtype.name) # int32 # 데이터 타입 이름만 가져온다.

b = np.linspace(0,np.pi,3) 
print(b.dtype.name)

c = a + b # 서로 상응하는 원소끼리
print(c) # [1.         2.57079633 4.14159265]

통계 연산

통계연산은 axis와 밀접한 관계가 있다. 축 값을 입력하지 않으면 모든 원소에 대한 연산 결과를 출력한다.

a = np.arange(12).reshape(3,4)
print(a)
print("="*20)

print(a.sum()) # 모든 원소에 대한 합
print("="*20)

# 연산이니까 축 설정에 주의한다.
print(a.sum(axis = 0)) # 각 열의 합
print("="*20)
print(a.mean(axis = 1)) # 각 행의 평균 : 산술평균
print("="*20)
print(a.cumsum(axis = 1)) # 각행의 누적합
print("="*20)

누적합 : 수열 An에 대해서 각 인덱스까지의 구간의 합을 구하는 것 

- np.average()
주어진 축에 따라 가중 평균을 계산
avg = sum( a * weights) 각 원소의 가중치 값의 합 / sum(weights) 가중치들의 합

a = np.arange(6).reshape(3,2)
print(a)

# 0.75 = sum (0 * (1/4) + 1*(3/4)) / sum(1/4+3/4)
print(np.average(a, axis = 1, weights=[1/4,3/4])) 
# [0.75 2.75 4.75]

# 가중치 ? 행단위 평균 2 개의 열에 대한 가중치 2개 지정.

- np.amin() / np.amax()
axis을 따른 배열의 최소 또는 최대 값

a = np.arange(1,10).reshape(3,3)
print(a)

print(np.amin(a, axis=0)) # 열별 최소값 # [1 2 3]
print(np.amax(a, axis=0)) # 열별 최대값 # [7 8 9]

# 축의 값을 지정하지 않으면 전체 원소의 최소(amin)/ 최대(amax) 값을 반환
print(np.amax(a)) # 9

- np.median() : axis을 따른 배열 중앙값
- np.var(), np.std() : 지정된 축을 따른 분산과 표준편차를 구함

최소값, 최대값, 아규먼트 위치
- np.argmax() : 제일 큰 값의 위치 정보를 반환 (다차원 배열안에서)
- np.argmin() : 제일 작은 값의 위치 정보를 반환
- np.where(조건) : 조건에 맞는 값 위치 정보를 반환
- np.where(조건, 조건에 맞을때 값, 조건과 다를때 값)
위치 정보를 반환한다는 것 = 배열 안에서 어디에 자리해 있는지 반환해주는 것

a = np.array([3,5,66,8,9,63])
print(np.argmax(a)) # 2
print(np.argmin(a)) # 0

print(np.where(a > 5)) # a 배열에서 5를 초과하는 위치 정보
# (array([2, 3, 4, 5], dtype=int64),)

print(np.where(a > 5, 1,-1)) # a 배열에서 5를 초과하면 1 아니면 -1
# [-1 -1  1  1  1  1]
print(np.where(a > 5, a, 0 )) # a 배열에서 5를 초과하면 a 값, 아니면 0
# [ 0  0 66  8  9 63]

 

비교연산

연산자를 이용하여 요소별(element-wise) 비교 (==, !=, > , >=, <, <=)

import numpy as np

a = np.array([1,2,3])
b = np.array([1,2,3])
c = np.array([3,2,5])
 
print(a == b) # [ True  True  True]
print(a == c) # [False  True False]

원소의 위치에서 결과를 반환한다.

a = np.arange(1,10).reshape(3,3) # 3,3 2차원 배열
print(a)

result = a % 2 == 0 # 다차원 배열에 대해 요소별로 나머지가 0인지 비교
print(result) # 짝수에 대한 값만  True

print(np.sum(result)) # 4
# True = 1 -> 짝수 요소의 개수를 체크

result = a % 2 == 0 다차원 배열에 대해 요소별로 나머지가 0인지 비교하여 0이면 True 아니면 False를 반환한다.
print(np.sum(result)) True = 1 이므로 True의 개수를 카운팅 한다.

- np.all(), np.any()를 이용한 비교
np.all() : 요소 전체가 같아야 하나의 True 값을 반환 / np.any() : 요소 전체 중 하나만 같아도 True 값을 반환

a = np.array([1,2,3])
b = np.array([1,2,3])
c = np.array([3,2,5])

print(np.all(a == b)) # True
print(np.all(a == c)) # False

print(np.any(a == b)) # True
print(np.any(a == c)) # True

논리 연산

- np.logical_and(), np.logical_or(), np.logical_not(), np.logical_xor()

x = np.arange(1,11).reshape(2,5)
print("x 행렬:")
print(x)

y = x % 2 ==0
print("y행렬: 짝수면 True, 홀수면 False")
print(y)

z = x >=  4
print("z행렬: 4이상이면 True, 미만이면 False")
print(z)

print("="*20)
# 짝수이면서 4 이상인
print("y and z 행렬")
print(np.logical_and(y,z))
print("y and z 행렬의 총 합")

print(np.sum(np.logical_and(y,z))) # 4
# 두가지 조건을 만족하는 True의 총 개수
# 3가지 조건이라면 np.logical_and(np.logical_and(a,b),c) => 처리
 
print(x[np.logical_and(y,z)]) # boolean indexing
# [ 4  6  8 10]

print(np.sum(np.logical_and(y,z))) # 4  =  두가지 조건을 만족하는 True의 총 개수
만약 3가지 조건이라면 np.logical_and(np.logical_and(a,b),c) => 처리  
print(x[np.logical_and(y,z)]) # boolean indexing = 원소의 값으로 보여준다.

정렬

import numpy as np

np.random.seed(10)
data = np.random.randint(1,50,10) 
# 끝(end) 값을 포함하지 않음, 모양을 지정. (10,10) : 행렬을 지정할 수도 있음
print(data) # [43 25  4  9  1 22 20 11 44 42]

- np.sort() : 데이터를 오름차순으로 정렬하며, 내림차순으로 정렬하는 별도의 옵션은 없다.
- np.argsort() : 오름차순으로 정렬한 데이터의 정렬 전 인덱스 값을 반환한다. ( 정렬된 데이터에서 원본의 위치값을 반환해 주는 역할을 한다. )

print(np.sort(data)) # [ 1  4  9 11 20 22 25 42 43 44]
print(np.sort(data)[::-1]) # [44 43 42 25 22 20 11  9  4  1] : 내림차순
print(np.argsort(data))  # [4 2 3 7 6 5 1 9 0 8]
# :  정렬 후의 데이터의 인덱스값 반환

[문제] 학생 이름과 점수 데이터를 이용하여 성적이 우수한 학생 순서로 이름을 출력하세요.

name = ['홍길동','저팔계','사오정','전우치','삼장법사']
score = [75, 100, 95, 56, 99]

# 다차원 배열로 변경
name = np.array(name)
score = np.array(score)

sorted_idx = np.argsort(score)
print( name[sorted_idx][::-1]) 
# name 에 인덱싱 값으로 넣음 (성적 오름차순) 한 번 더 인덱싱

우선 다차원 배열에 넣어야 인덱싱 or 슬라이싱이 가능한 것이다.

행렬연산

- * 연산자
형태가 동일한 두 행렬을 원소끼리(element-wise) 곱하는 연산자 (shape이 다르면 오류가 발생한다.)
교환법칙이 성립 : AB == BA

a = np.array([[1,1],
              [0,1]]) # 2,2
b = np.array([[2,0],
             [3,4]]) # 2,2

# 교환 법칙 # 서로 상응 하는 원소끼리 곱함
print(a*b) # [[2 0]
print(b*a) # [0 4]]

a = np.arange(6).reshape(2,3)
b = np.arange(6).reshape(3,2)
# print(a*b) # 모양이 달라서 오류 발생

- np.dot()
두 벡터의 내적을 계산하는 함수
내적: 벡터에서 서로 대응하는 성분끼리 곱한 다음에 그것들을 모두 더한 것을 말하며, 내적의 결과는 단일값(스칼라값)이 된다.
a·b 또는 <a,b> 로 표현: a1b1 + a2b2 + ... + an*bn (행렬곱에서도 사용이 가능하지만, 공식문서에서는 np.matmul() 사용을 권장한다.)

a = np.array([3,4,6,3,9])
b = np.array([6,8,1,3,9])

c = np.dot(a,b) # or a.dot(b)   : 내적 계산   
print(c) # 3*6 + 4*8 + ... + 9*9

두 행렬의 곱을 계산하려면 첫 번째 행렬의 열의 크기와 두 번째 행렬의 행의 크기가 같아야 한다.
첫 번째 행렬이 nm 두번째 행렬 mn => 행렬곱이 가능하다. (예 2,3 · 2,2 = 불가) # 2x3 @ 3x2 -> 2x2 (크기는 nxn)
- np.matmul() or @ 연산자

a = np.array([[1,1],
              [0,1]]) # 2,2
b = np.array([[2,0],
             [3,4]]) 

# 교환법착 미성립 
print(a.dot(b)) # (0,0) : a 첫번째 행 * b 첫번째 열 
print(b.dot(a))

a = np.array([[1,1],
              [0,1]])
b = np.array([[2,0],
             [3,4]]) 
print(a@b)

a = np.array([[1,0,3],
             [0,2,4]]) # 2,3
b = np.array([[4,1],
             [2,2],
             [2,3]]) # 3,2

print(np.matmul(a,b)) # 2,2

전치(transpose)
기존 행렬의 행과 열을 교환하는 것으로 즉, 주 대각선을 기준으로 반사 대칭하는 것을 말한다.

x = np.arange(1,5).reshape(2,2)
print(x)    # 원본 [[1 2]
            #       [3 4]]
print(x.T)       # [[1 3]
                 #  [2 4]]
print(np.transpose(x))

 

인덱싱, 슬라이싱, 반복
- 인덱싱 a[0,0] : 리스트에서는 불가능한 표현이지만 numpy에서는 가능하다.

import numpy as np

a = np.arange(1,11).reshape(2,5)
print(a)
print(a[0][0])
print(a[0,0])

boolean indexing (masking)

a = np.arange(1,25).reshape(4,6)
print(a)

# 1. 
even_arr = a%2 == 0
print(even_arr)
# 2. 
print(a[even_arr])
# print(a[a%2 == 0]) [ 2  4  6  8 10 12 14 16 18 20 22 24] / 동일한 의미
# print(np.sum(a[a%2==0])) # 156

- Fancy indexing
배열의 각 요소 선택을 인덱스 배열을 전달해서 참조하는 방식으로 fancy indexing 은 (새로운)복사본이 생성된다.(원본에 영향을 미치지 않는다)

a = np.arange(15).reshape(5,3)
print(a)

# 0행과 2행만 인덱싱
print(a[[0,2],]) # 열은 비워두거나
print(a[[0,2], :]) # 콜론

# 전체 행에 대해 0열과 2열만 인덱싱  
print(a[:,[0,2]]) 

# 두 번 인덱싱
# print(a[[0,2,4]][:,[0,2]]) # 같은 의미
print(a[[0,2,4],:][:,[0,2]])

fancy indexing 은 복사본이 생성된다. (새로운 복사본이기 때문에 원본의 값은 영향을 받지 않는다.)

a = np.arange(15).reshape(5,3)
print(a)

copied = a[[0,2,4]][:,[0,2]]
print(copied) # [[ 0  2] ...

copied[0,:] = 100
print(copied) # [[ 100 2]] ...

print(a)  # [[ 0  2] ...
# 처음 형태와 값이 동일하다.

- 슬라이싱
ndarray[행 시작값 : 행 끝값, 열시작값 : 열끝값] 시작값부터 끝값 전 까지 슬라이싱한다.

a = np.arange(1,13).reshape(3,4)
print(a)

print(a[0:2])# 1행까지
print(a[0:2,0:4])

print(a[:2,:])# 1행까지, 열 전체

print(a[:2]) # 열 생략, default: 열전체

슬라이싱은 동일한 메모리 영역에 View(뷰)를 반환한다.때문에 뷰에 있는 값을 수정하면 원본데이터의 원소도 변경된다.

a = np.arange(9).reshape(3,3)
a_view = a[0:1]  # [[0 1 2]] # 첫번째 행만
print(a_view)

a_view[0] = [9,10,11]
print(a) # [[ 9 10 11]...

[문제] 슬라이싱 실습

a = np.arange(1,25).reshape(4,6)
print(a)

# 1) 가운데 요소 가져오기 (9,10,15,16)
print(a[1:3,2:4])

# 2) 0~1 행과 모든 컬럼 가져오기
print(a[:2, :]) # a[:2] 동일

# 3) 전체 행과 1,2열 가져오기
print(a[: ,1:3])


# 4) 1행과 0,1 열 가져오기
print(a[1,:2]) # a[1:2,:2]

# 5) 각 행의 마지막 열 데이터만 추출해서 2차원 열 데이터로 변경하기
print(a[:,5:6]) 

print(a[:,-1].reshape(-1,1))

인덱싱 & 슬라이싱과 차원
정수 인덱싱과 슬라이싱을 혼합해서 사용하면 낮은 차원의 배열이 생성된다.
하지만 슬라이싱만 사용하면 원본 배열과 동일한 차원의 배열이 생성된다.

a = np.arange(1,13).reshape(3,4)
print(a,a.shape, a.ndim)

print('슬라이싱만 사용')
slicedRow = a[:1,:]
print(slicedRow, slicedRow.shape, slicedRow.ndim) # [[1 2 3 4]] (1, 4) 2

print("인덱싱만 사용")
indexedRow = a[0]
print(indexedRow,indexedRow.shape,indexedRow.ndim) # 한 차원 낮아짐 # [1 2 3 4] (4,) 1

print("인덱싱과 슬라이싱 혼합")
indexedRow2 = a[0,:]
print(indexedRow2,indexedRow2.shape, indexedRow2.ndim) # 한 차원 낮아짐 # [1 2 3 4] (4,) 1

# 펜시인덱싱: (<-> 정수 인덱싱 : 하나의 값만 지정)을 하면 원본가 동일한 차원의 배열이 생성된다.`
print('펜시 인덱싱(배열 인덱싱)') # 배열의 형태(복수)로 지정
fancyIdx = a[[0,2]] # 0행과 2행
print(fancyIdx, fancyIdx.shape, fancyIdx.ndim) # 원본 배열과 동일

- Broadcasting
shape 이 다른 다차원 배열이 연산을 하려고 할때 특정조건이 만족되면 자동으로 연산이 가능한 상태로 변환되는 것.
벡터나 배열 또는 배열과 스칼라간에 사칙연산을 하면 같은 위치의 원소끼리 계산이 이뤄진다. 이때 벡터나 배열이 모양이 같아야 하는데 작은 차원의 데이터가 큰 차원의 데이터로 모양이 맞춰지는 것을 Broadcasting이라고 한다. (차원이 다른 데이터? 낮은 차원의 데이터가 큰 차원의 데이터에 맞춰 커지는 것)

random 모듈

- np.random.rand()
0 이상 1미만의 임의의 실수를 생성. shape을 전달하면 크기에 맞는 다차원 배열을 생성. [0,1) 범위에서 균등분포의 난수를 발생시킨다.

a = np.random.rand() # 난수 1개 발생
print(a) # 0.7239391394374821
 
b = np.random.rand(3) # 1차원 벡터 생성
print(b) # [0.47508861 0.59666377 0.06696942]

c= np.random.rand(3,4) # 2차원 벡터 생성
print(c)

- np.random.randint()
주어진 범위에 임의의 정수를 만든다.

# a = np.random.randint(3) # ** 범위 지정 ** : [0,3) 임의의 정수 1개
print(a) # 8

# a = np.random.randint(3, size = 10) # 0 이상 3미만 임의의 정수10개 생성
# np.random.randint(3,10) 위치인수 : 3이상 9미만의 범위를 설정하는 것이 됨
print(a) # 8

# a = np.random.randint(3,10) # 3이상 10미만 임의의 정수 1개 생성

# a = np.random.randint(1,46,size =3) # 1이상 46미만의 임의의 정수 3개 생성

a = np.random.randint(0,100,(3,3)) # 0 이상 100 미만의 임의의 정수 3x3 모양으로 생성
print(a)

- np.random.randn()
표준정규분포(표준편차1, 평균은 0 으로 이루어진 데이터) 로 샘플링된 난수를 발생

a = np.random.randn() # 난수 1개
print(a) # 1.1333255640898339

a = np.random.randn(3) # 1차원 벡터 생성
print(a) # [ 0.99075233 -1.95741501 -0.46475167]
 
a= np.random.randn(3,4) # 2차원 배열
print(a)

- np.random.choice()
추출하고자 하는 모양을 지정

a = np.random.randint(1,51, 20)
# replace = False: 중복 샘플링을 허용하지 않는다. (default : replace = True)
x = np.random.choice(a, size = (3,), replace = False)
print(x) # [43 27 21]

 

'국비 교육 > 데이터' 카테고리의 다른 글

[데이터 분석] Pandas - 1  (0) 2023.11.11
[데이터 분석] Numpy - 4 (실습문제)  (0) 2023.11.08
[데이터 분석] Numpy - 2  (0) 2023.11.07
[데이터 분석] Numpy -1  (0) 2023.11.07