Data_Analysis_Track_33/Python

Python_numpy_02(인덱싱과 슬라이싱을 이용한 배열의 원소 조회 및 변경)

lsc99 2023. 10. 4. 18:44

인덱싱과 슬라이싱을 이용한 배열의 원소 조회 및 변경

배열 인덱싱(Indexing)

  • index
        - 배열내의 원소의 식별번호
        - 0부터 시작
  • indexing – index를 이용해 원소 조회
        - [] 표기법 사용
  • 구문
        - ndarray[index]
        - 양수는 지정한 index의 값을 조회한다.
        - 음수는 뒤부터 조회한다.
            - 마지막 index가 -1
        - 2차원배열의 경우
            - arr[0축 index, 1축 index]
            - 파이썬 리스트와 차이점
        - N차원 배열의 경우
            - arr[0축 index, 1축 index, ..., n축 index]
  • 팬시(fancy) 인덱싱
        - 여러개의 원소를 한번에 조회할 경우 리스트에 담아 전달한다.
        - 다차원 배열의 경우 각 축별로 list로 지정
        - arr[[1,2,3,4,5]]
            - 1차원 배열(vector): 1,2,3,4,5 번 index의 원소들 한번에 조회
        - arr[[0,3], [1,4]]
            - [0,3] - 1번축 index list, [1,4] - 2번축 index list
            - 2차원 배열(matrix): [0,1], [3,4] 의 원소들 조회

 

배열 a 생성

import numpy as np

a = np.arange(30)
a

 

a[0]
a[-1]
a[[1, 5, -1, -3]]

 

값 변경

# 변경
a[0] = 100
a

 

2차원 배열 생성

a2 = np.array([
    [1, 2, 3, 4, 5]
    ,[10, 20, 30, 40, 50]
    ,[100, 200, 300, 400, 500]
])
a2.shape

 

한 줄씩 실행 후 결과 확인

a2[0] # 0 axis의 index 지정 -> 1번 축 다가져온다

a2[[0,2]] # 0 axis

a2[0, 1] # 0 axis : index 0, 1 axis : index 1

a2[:, -1] # a2[, -1] 앞의 축의 index는 생략하지 못한다. 앞의 축의 모든 값을 다 가져와라 -> a2[:, -1]

a2[[0, 2], 2]

a2[[0, 1, 2], [0, 2, 4]]

슬라이싱

  • 배열의 원소들을 범위로 조회한다.
  • ndarry[start : stop : step ]
        - start : 시작 인덱스. 기본값 0
        - stop : 끝 index. stop은 포함하지 않는다. 기본값 마지막 index
        - step : 증감 간격. 기본값 1

 

다차원 배열 슬라이싱

  • 각 축에 slicing 문법 적용
  • 2차원의 경우
        - arr [0축 slicing, 1축 slicing]
            - arr[:3, :]
        - , 로 축을 구분한 다중 슬라이싱 사용
  • 다차원의 경우
        - arr[0축 slicing, 1축 slicing, ..., n축 slicing]
  • slicing과 indexing 문법은 같이 쓸 수 있다.

 

슬라이싱은 원본에 대한 View

  • slicing한 결과는 새로운 배열을 생성하는 것이 아니라 기존 배열을 참조한다.
  • slicing한 배열의 원소를 변경하면 원본 배열의 것도 바뀐다.
  • 배열.copy()
        - 배열을 복사한 새로운 배열 생성
        - 복사후 처리하면 원본이 바뀌지 않는다.

 

a2[:2] # 0축 : 0 ~ 2-1, 1번 축 다 가져온다.

a2[0, 1:3] # 0축 : 0, 1측 : 1 ~ 3-1

 

원본 배열 변경되는 경우

b = a[:10]

b[-1] = 90000
b

a # 참조 배열 b의 값 변경이 원본 배열 a의 값도 함께 변경시킨다.

 

원본 배열이 변경되지 않는 경우 -> deep copy

b2 = a[:10].copy() # copy() -> deep copy, 복사본을 만든다.

b2[-1] = 9
b2 # deep copy -> 참조 배열의 값 변경이 원본 배열의 값을 변경시키지 않는다.

a

boolean indexing

  • Index 연산자에 같은 형태(shape)의 Boolean 배열을 넣으면 True인 index의 값만 조회 (False가 있는 index는 조회하지 않는다.)
  • ndarray내의 원소 중에서 원하는 조건의 값들만 조회할 때 사용
        - ndarray는 element-wise 연산을 지원한다. 이를 이용해 boolean indexing으로 원하는 조건의 값들을 조회할 수 있다.
  • boolean indexing을 masking이라고도 한다.

 

넘파이 비교연산자

  • 파이썬의 and, or, not은 사용할 수 없다.
  • &: and연산
  • |: or 연산
  • ~: not 연산
  • 피연산자는 ( )로 묶어야 한다.

 

element-wise(원소별) 연산

a = np.array([10, 5, -10, 2, 7])
# element-wise(원소별) 연산
a + 10

a > 0 # 원소별로 비교

# pandas와 같이 python의 논리연산자(and, or, not)는 사용불가.
(a > 0) & (a < 5)

# boolean indexing
a[(a > 0) & (a < 5)]

 

boolean indexing

arr = np.random.randint(-100, 100, (3, 10))
arr.shape

# 양수만 조회
arr[arr >= 0] # boolean indexing 의 결과: 1차원 배열

arr[(arr>=-10) & (arr<=10)]

 

np.where()

  • True의 index 조회
        - np.where(boolean 배열) - True인 index를 반환
            - 반환타입: Tuple . True인 index들을 담은 ndarray를 축별로 Tuple에 묶어서 반환한다.
        - boolean연산과 같이사용하여 배열내에 특정 조건을 만족하는 값들을 index(위치)를 조회할 때 사용한다.
  • True와 False를 다른 값으로 변환
        - np.where(boolean 배열, True를 대체할 값, False를 대체할 값)
            - 배열내의 True를 True를 대체할 값으로 False를 False를 대체할 값 으로 변환한다.

 

arr.shape

# Boolean 배열 -> True의 index를 반환.
np.where(arr > 0)   # tuple -> (축별 index 배열, )

np.where(arr > 0, '양수', '음수') # True : 양수, False : 음수 변환

 

기타

  • np.any(boolean 배열)
        - 배열에 True가 하나라도 있으면 True 반환
        - 배열내에 특정조건을 만족하는 값이 하나 이상 있는지 확인할 때 사용
  • np.all(boolean 배열)
        - 배열의 모든 원소가 True이면 True 반환
        - 배열내의 모든 원소가 특정 조건을 만족하는지 확인 할 때 사용

 

np.any

np.any([True, False, False]) # True가 하나 이상이면 True, 모두 False이면 False
np.any([False, False])

 

np.all

np.all([True, False, False]) # 모두 True일 때만 True, 나머지 경우에는 False
np.all([True, True])

 

np.any & np.all 활용

# arr이 모두 양수인지?
np.all(arr>0)

# arr의 원소 중 양수가 있는지?
np.any(arr>0)

 

상황별 활용

  • 특정 조건이 True인 값들을 조회 -> boolean indexing
  • 특정 조건이 True인 값들의 index -> np.where
  • 특정 조건의 값이 하나라도 있는지 -> np.any
  • 모든 값들이 특정 조건을 만족하는지(True) -> np.all

배열의 형태(shape) 변경

  • 배열의 원소의 개수를 유지하는 상태에서 shape을 변경할 수있다.
        - 예) (16, ) -> (4,4) -> (2,2,4) -> (2,2,2,2), -> (4,4,1) -> (1, 16)

 

reshape()을 이용한 차원 변경

  • numpy.reshape(a, newshape) 또는 ndarray.reshape(newshape)
        - a: 형태를 변경할 배열
        - newshape : 변경할 형태 설정.
            - 원소의 개수를 유지하는 shape으로만 변환 가능하다.
            - 각 axis(축)의 size를 지정할 때 하나의 축의 size를 -1로 줄 수있다. 그러면 알아서 축 size를 설정해 준다. (전체 size / 지정한 axis들 size의 곱)
        - 둘다 원본을 바꾸지 않고 reshape한 새로운 배열을 만들어 반환한다.

 

numpy import, x 생성

import numpy as np

x = np.arange(20)
print(x.shape)
x

 

numpy.reshape(a, newshape) 형태

r1 = np.reshape(x # shape변환할 대상
                ,(4,5) # 변경할 shape -> size(원소개수)가 바뀌면 안됨
               )
print(r1.shape)
r1

 

ndarray.reshape(newshape) 형태 (메소드 사용)

r2 = x.reshape(4, 5) # 메소드 사용 -> 가변인자를 이용해서 변환할 shape을 지정
print(r2.shape)
r2

 

축 자동 지정

# 변경할 축 중에 하나는 -1로 지정할 수 있다.
# ex) 원소개수 20개, 하나의 축의 size를 4로 지정했다면 다른 축의 size는 자동으로 5가 되기에 편하게 -1로 지정해도 5가 되어 변환된다.
x.reshape(4, -1)
x.reshape(2, 2, -1)

 

 

 

# (20, ) -> 2차원 (1, 20) # 차원만 늘리는 형태, size가 1인 축(axis) : dummy axis
x.reshape(1, -1)
x.reshape(-1, 1) # (20, 1)

 

배열의 원소의 개수를 유지하는 상태에서 shape을 변경할 수있다

x.reshape(2, 8) # 20 -> 16 (개수가 바뀜) -> 오류 발생

 

사이즈가 바뀌지 않는 한 차원은 계속 늘릴 수 있다.

r3 = x.reshape(1, 2, 2, 5, 1, 1)
print(r3.shape)
r3

 

r3.reshape(-1) # r3를 1차원
r3.reshape(2,10)

 

ndarray.flatten() : 다차원 ndarray를 1차원으로

r3.flatten() # 다차원 -> 1차원

 

차원 늘리기(확장)

  • Dummy axis(축)을 늘린다.
        - Dummy axis: size가 1인 axis 를 말한다.
  • reshape() 을 이용해 늘릴 수 있다.
  • indexer와 np.newaxis 변수를 이용해 늘린다.
        - ndarray[..., np.newaxis] 또는 ndarray[np.newaxis, ...]
            - 맨앞 또는 맨 마지막에 dummy axis(축)을 늘릴때 사용한다.
            - 축을 늘리려는 위치에 np.newaxis를 지정하고 ... 으로 원본 배열의 shape을 유지함을 알려준다.

 

dummy axis

r1 = x.reshape(1, -1) # 앞에 dummy axis 추가
r1.shape

 

r2 = r1.reshape(-1, 1)
r2.shape

 

a = np.arange(20).reshape(4, 5)
a.shape

 

 

# (4, 5) -> (1, 4, 5)
a.reshape(1, a.shape[0], -1)

 

a.shape
b = a[np.newaxis, ...]
b.shape

 

a.shape
c = a[..., np.newaxis, np.newaxis]
c.shape

 

원하는 축을 늘리기

# dummy axis를 추가하는 함수. np.expand_dims(대상대열, 추가할 축)
a.shape
d = np.expand_dims(a, axis=1)
d.shape

 

 

차원 줄이기(축소)

  • numpy.squeeze(배열, axis=None), 배열객체.squeeze(axis=None)
  • 배열에서 지정한 축(axis)을 제거하여 차원(rank)를 줄인다.
  • 제거하려는 축의 size는 1이어야 한다.
  • 축을 지정하지 않으면 size가 1인 모든 축을 제거한다.
        - (3,1,1,2) => (3,2)

 

numpy.squeeze() -> dummy axis 제거

b.shape
r = np.squeeze(b)
r.shape

 

dummy axis 여러개 제거, 축을 지정하지 않으면 모든 dummy axis가 제거된다.

c = np.arange(20).reshape(1, 4, 5, 1, 1)
c.shape

r2 = np.squeeze(c)
print(r2.shape)

 

축을 지정하여 dummy axis 제거

r3 = np.squeeze(c, axis = 0)
r3.shape

배열 연산

벡터화 - 벡터 연산

  • 배열과 scalar 간의 연산은 원소단위로 계산한다.
  • 배열간의 연산은 같은 index의 원소끼리 계산 한다.
        - Element-wise(원소별) 연산 이라고도 한다.
        - 배열간의 연산시 배열의 형태(shape)가 같아야 한다.
        - 배열의 형태가 다른 경우 Broadcast 조건을 만족하면 연산이 가능하다.

 

사용할 배열 x, y, z 생성

import numpy as np

x = np.arange(10).reshape(2, 5)
y = np.arange(10,20).reshape(2, 5)
z = np.arange(8).reshape(2, 4)

 

배열과 스칼라간의 연산

# 배열과 스칼라간 연산
x + 10
x > 5
#  x >= 2 and x <= 5
(x >= 2) & (x <= 5)
x ** 2

 

배열 간의 연산

# 배열 간의 연산 -> 같은 index값 끼리 계산
x - y
x > y

 

shape이 다를 경우 연산 -> 오류 발생

x - z # shape이 다르면 계산이 되지 않는다.

 

내적 (Dot product) 연산

  • @ 연산자 또는 numpy.dot(벡터/행렬, 벡터/행렬) 함수 사용
  • 같은 index의 원소끼리 곱한뒤 결과를 모두 더한다.
  • 벡터간의 내적의 결과는 스칼라가 된다.
  • 조건
        - 두 벡터의 차원(원소의개수)가 같아야 한다.
        - 앞의 벡터는 행벡터 뒤의 벡터는 열벡터 이어야 한다.
            - numpy 에서는 vector 끼리 연산시 앞의 벡터는 행벡터로 뒤의 벡터는 열벡터로 인식해 처리한다.

 

x = np.array([1, 2, 3])
y = np.array([4, 5, 6])
# 내적 (Dot product) 연산 -> 결과 : 스칼라
r1 = x @ y
r2 = np.dot(x, y) 
print(r1, r2)

 

행렬 곱

  • 같은 index의 앞 행렬의 행과 뒤 행렬의 열간에 내적을 한다.
  • 행렬과 행렬을 내적하면 그 결과는 행렬이 된다.
  • 앞 행렬의 열수와 뒤 행렬의 행수가 같아야 한다.
  • 내적의 결과의 형태(shape)는 앞행렬의 행수와 뒤 행렬의 열의 형태를 가진다.
        - (3 x 2)와 (2 x 5) = (3 x 5)
        - (1 x 5)와 (5 x 1) = (1 x 1)

 

A = np.arange(1, 7).reshape(2, 3)
B = np.arange(1, 7).reshape(3, 2)
print(A.shape, B.shape) # 앞 행렬의 0번 축과 뒤 행렬의 1번 축의 size가 같아야 한다.

 

A @ B
np.dot(A, B)

 

행렬 곱의 조건(앞 행렬의 열수와 뒤 행렬의 행수가 같아야 한다.)에 만족하지 못하여 오류 발생

C = np.arange(1, 7).reshape(2, 3)
A.shape, C.shape
A @ C

 

내적의 예

  • 가중합
    가격: 사과 2000, 귤 1000, 수박 10000
    개수: 사과 10, 귤 20, 수박 2
    총가격?
    2000*10 + 1000 * 20 + 10000 * 2

 

price = np.array([2000, 1000, 10000])
cnt = np.array([10, 20, 2])
price * cnt

 

총 가격

np.sum(price * cnt)
price @ cnt

 

여러 사람이 과일을 구매했을 경우 각각 내야 하는 금액은?

 

cnts = np.array([
    [10, 20, 2] # 첫 번째 사람이 산 개수
    ,[5, 2, 10] # 두 번째
    ,[7, 30, 10]# 세 번째
    ,[20, 10, 0]# 네 번째
])
cnts.shape

 

price_2 = price[..., np.newaxis] # price.shape(-1, 1)
price_2.shape

 

r = cnts @ price_2
r

r[0] # 첫 번째 사람이 내야하는 금액
r[2] # 세 번째 사람이 내야하는 금액

기술통계함수

  • 통계 결과를 계산해 주는 함수들
  • 구문
        1. np.전용함수(배열)
            - np.sum(x)
        2. 일부는 배열.전용함수() 구문 지원
            - x.sum()
        - 공통 매개변수
            - axis=None: 다차원 배열일 때 통계값을 계산할 axis(축)을 지정한다. None(기본값)은 flatten후 계산한다.
  • 배열의 원소 중 누락된 값(NaN - Not a Number) 있을 경우 연산의 결과는 NaN으로 나온다.
  • 안전모드 함수
        - 배열내 누락된 값(NaN)을 무시하고 계산
  • https://docs.scipy.org/doc/numpy-1.15.1/reference/routines.statistics.html

 

사용할 array a 생성

np.random.seed(0)
a = np.random.randint(100, size = 10).astype('float32')
a

 

여러가지 메소드 활용

np.sum(a), a.sum()

a.min(), a.max()

a.argmin(), a.argmax() # min, max값의 index

 

새로운 array b 생성

# 평균
b = np.array([80, 90, 100])
b.mean()

 

가중치 array c 생성, 가중평균 구하기

# 가중치 부여한 평균 (가중평균)
c = np.array([3, 3, 1])
np.average(a = b, weights = c)

 

# 가중평균
(b @ c)/c.sum()

 

array a에 결측치 추가

# a에 결측치를 추가
a[1] = np.nan
a

 

# 결측치가 껴있어서 모두 결측치가 결과로 나온다.
a.mean(), a.sum(), a.std(), a.min(), a.max(), a.argmax()

 

안전모드 함수(결측치 제외한 결과값 출력)

# 결측치를 제외한 결과값이 나온다
np.nanmean(a), np.nansum(a), np.nanstd(a), np.nanmin(a), np.nanmax(a), np.nanargmax(a)

 

사용할 array arr 생성

arr = np.arange(20).reshape(4, 5)
arr.shape

 

 

# 2차원 -> 0차원(상수-스칼라)
arr.sum() # 다차원 배열 -> 기본 : 전체집계

# 2차원 -> 1차원
arr.sum(axis=0) # 0번 축을 기준으로 집계 -> 0번 축의 index가 다르고 나머지 축의 index는 같은 값끼리 계산.

arr.sum(axis=1) # 1번 축을 기준으로 집계 -> 1번 축의 index가 다르고 나머지 축의 index는 같은 값끼리 계산.

 

keepdims = True -> 배열의 차원 유지

# 2차원 -> 2차원
r = arr.sum(axis = 0
        ,keepdims = True # arr배열의 차원을 유지
       )
print(r.shape)
r

r = arr.sum(axis = 1, keepdims = True)
print(r.shape)
r

브로드캐스팅

  • 사전적의미 : 퍼트린다. 전파한다.
  • 형태(shape)가 다른 배열 연산시 배열의 형태를 맞춰 연산이 가능하도록 한다.
        - 모든 형태를 다 맞추는 것은 아니고 조건이 맞아야 한다.
  • 조건
        1. 두 배열의 축의 개수가 다르면 작은 축의개수를 가진 배열의 형태(shape)의 앞쪽을 1로 채운다.
            - (2, 3) + (3, ) => (2, 3) + (1, 3)
        2. 두 배열의 차원 수가 같지만 각 차원의 크기가 다른 경우 어느 한 쪽에 1이 있으면 그 1이 다른 배열의 크기와 일치하도록 늘어난다.
            - 1 이외의 나머지 축의 크기는 같아야 한다.
            - 늘리면서 원소는 복사한다.
            - (2, 3) + (1, 3) => (2, 3)+(2, 3)

 

브로드캐스팅에 사용할 x,y 생성

x = np.array([0, 1, 2])
y = np.ones(shape=(3,3))
print(x.shape, y.shape)

 

x + y

 

다른 array 두개 생성 후 브로드캐스팅

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

 

a + b