[Numpy] Numpy의 제어

indexing & slicing#

python list의 indexing, slicing과 상당히 유사하다.

#   indexing
arr = np.arange(10,20,1)

print(arr)

for item in arr:
    print(item)

for idx,item in enumerate(arr):
    print(idx, item)

# 2차원 ndarray의 indexing
arr = np.array([[1,2,3],
            [4,5,6],
            [7,8,9],
            [10,11,12]])
print(arr)

print(arr[1,2])   

print(arr[2,:])   # 1차원의 ndarray가 결과로 나와요(view)

print(arr[1:3,:])

#   slicing
arr = np.arange(0,5,1)
print(arr)

tmp = arr[0:3]
print(tmp)

arr[0] = 100

print(arr)
print(tmp)

numpy의 ndarray에 대해서 slicing을 하면 view가 생성된다.

Boolean Indexing#

Boolean Indexing은 배열의 각 요소의 선택여부를 True,False로 구성된 Boolean Mask를 이용하여 지정하는 방식으로 Boolean Mask안에서 True로 지정된 요소만 indexing하는 방식.

arr = np.arange(0,5,1)
print(arr)

print(arr[3])    # 3    기본 indexing

#          [0     1      2      3      4      5     6      7     8     9]
b_mask = [True, False, False, False, False, True, False, False, True, True]

print(arr[b_mask])    # [0 5 8 9]

print(arr + 1)
# 만약 ndarray에 사칙연산이 수행되려면 shape이 같아야해요!
# 만약 연산을 할때 shape이 다르면 shape을 자동으로 맞출려고 해요!

arr2 = np.arange(10,13,1)
print(arr2)
print(arr + arr2)

print(arr + 1)
# [0 1 2 3 4]
# [1 1 1 1 1]

arr % 2 == 0
# [0 1 2 3 4]
# [2 2 2 2 2]
# [0 1 0 1 0]
# [0 0 0 0 0]
# [True, False, True, False, True]
arr[arr % 2 == 0]
#   array([0, 2, 4])

#   ex) 1부터 100까지 3의 배수의 개수는??
arr = np.arange(1,101,1)
print(len(arr[arr % 3 == 0]))   # 33

Fancy Indexing#

ndarray에 index 배열(list, ndarray)을 전달해서 indexing하는 방식

arr = np.arange(10,15,1)
print(arr)

print(arr[1])      # 숫자 indexing(기본)

print(arr[[0, 2, 3]])   # index number로 구성된 list 즉, index list를 이용하여 indexing 작업을 하는 것

arr = np.arange(0,12,1).reshape(3,4).copy()

print(arr)

# indexing (기본)
print(arr[2,1])

print(arr[0:2,2])       # [6]  6
print(arr[1:2,2:3])     # [[6]]

print(arr[[0,2],2])     # [2 10]
print(arr[[0,2],2:3])   # [[2]
                        #  [10]]
    
# print(arr[[0,2],[0,2]])    # numpy 구조상 fancy indexing을 두번 이상 사용할 수 없다

print(arr[np.ix_([0,2],[0,2])]) # 이렇게 작성해야 예상된 shape가 나온다. fancy indexing을 두번 사용하고 싶은 경우 np.ix_ 를 통해서 작업하자
# [[0 2]
#  [8 10]]

print(arr[[0,2]][:,[0,2]])  # 혹은 이런식으로 행과 열을 나누어서 연산해도 가능하다
# [[ 0  2]
#  [ 8 10]]

사칙연산과 행렬 곱#

arr1 = np.array([[1, 2, 3], [4, 5, 6]])
arr2 = np.arange(10, 16, 1).reshape(2, 3).copy()

print(arr1)
print(arr2)

# 같은 shape인 경우 연산 수행이 가능
print(arr1 + arr2)

a = [1, 2, 3]
b = [4, 5, 6]
print(a+b)  # python의 list인 경우 concatenate

# 행렬곱연산 (matrix multiplication)
a = np.array([[1, 2], [3, 4], [5, 6]])
b = np.array([[1, 2, 3], [4, 5, 6]])
print(np.matmul(a, b))

Broadcasting#

arr1 = np.array([[1, 2, 3], [4, 5, 6]])
arr2 = np.array([1, 2, 3])
# arr1과 arr2에 대해서 사칙연산을 수행하고 싶어요
# shape가 같아야 연산을 수행할 수 있어요

print(arr1 + arr2)      # OK
print(arr1 * 3)         # scaler(단일 숫자)는 broadcasting이 쉬워요

# np.matmul 연산에서는 broadcasting이 발생하지 않아요

Numpy에서의 반복문을 위한 iterator#

1차원 Data면 그냥 사용하면 되지만, 2, 3차원에서는 많은 Data를 처리하기에는 문제가 발생한다. 그래서 ndarray에 대한 반복 처리를 할 때는 iterator를 이용하는걸 권장한다. iternext()finished라는 속성을 이용하여 ndarray의 모든 요소를 반복적으로 access가 가능하다.

# 1차원 ndarray
arr = np.array([1, 2, 3, 4, 5])

# 일반적인 for문을 이용한 ndarray access
for tmp in arr:
    print(tmp, end=' ')

# # iterator를 이용한 1차원 ndarray access
# # 지정된 flags의 형태를 가지는 iterator를 추출
it = np.nditer(arr, flags=['c_index'])  # c_index = index의 형태가 c언어의 index형태와 같게 추출하세요 (일반적으로 사용함)
                                        # arr[0] 형태이며 index를 가지고 있다
print(arr[it[0]])

while not it.finished:
    idx = it.index
    print(arr[idx], end=' ')
    it.iternext()


# 2차원 이상 ndarray
arr = np.array([[1, 2, 3], [4, 5, 6]])  # shape = (2, 3)

for i in range(arr.shape[0]):
    for j in range(arr.shape[1]):
        print(arr[i, j], end=' ')
# 가능하긴함 다만, 차수에 따라... 지옥이 되겠지..

it = np.nditer(arr, flags=['multi_index'])

while not it.finished:
    idx = it.multi_index      # (0,0) 좌표처럼 나타남
    print(arr[idx], end=' ')
    it.iternext()

단일 차원이 고정된다면.. 단일 for문쓰는게 당연하지만, 동적으로 차수가 변경되거나, 다차원인 경우에는 이런 itorator를 사용하는 것이 훠어어어어얼씬 더 유익하다.

Ndarray에서 사용 가능한 여러 집계 함수#

arr = np.arange(1, 7, 1).reshape(2, 3).copy()
print(arr)

print(arr.sum())    # 모든 요소 총합 출력 
print(np.sum(arr))  # 전자는 ndarray가 가지고 있는 자체 함수 호출과 numpy가 가지고 있는 기능을 호출

print(np.mean(arr)) # 모든 요소 평균 값
print(np.max(arr))  # 최대 값
print(np.min(arr))  # 최소 값
print(np.argmax(arr))   # 최대 값을 찾아서 그 값의 index를 return
print(np.std(arr))  # 표준편차

axis#

numpy의 모든 집계함수는 axis(축)를 기준으로 계산한다 위의 예처럼 axis를 명시하지 않으면 axis는 None을 설정되는데 이런 경우 집계함수의 대상은 전체 ndarray로 간주한다.

# 1차원 ndarray인 경우
arr = np.array([1, 2, 3, 4, 5])
print(arr.sum(axis=0))  # 1차원인 경우 축의 번호는 0이고 이때 0의 의미는 열(열방향)

# 2차원 ndarray인 경우
arr = np.array([[1, 2, 3], [4 ,5 ,6]])
print(arr.sum(axis=0))  # 2차원인 경우 축의 번호는 0과 1을 쓸 수 있고 0이 행, 1이 열

# 3차원
arr = np.array([[1, 2, 3], [4 ,5 ,6], [7, 8, 9]])
print(arr.argmax(axis=1))   # 행 방향으로 가장 큰 값의 index이기에 2, 2, 2 가 나온다

np.random.seed(1)

arr = np.random.randint(0, 5, (2, 2, 3))
print(arr)
print(arr.sum(axis=0))

중간에 간단한 문제 하나.#

랜덤으로 정수형 난수(1 ~ 10 모두 포함하여 12개)를 생성하여 3행 4열 2차원 ndarray로 만들고, 이 안에 있는 난수 중 5 이상인 숫자들의 합을 구해서 출력

np.random.seed(1)

arr = np.random.randint(1, 11, (3,4))

print(arr[arr >= 5].sum())

ndarray의 concatenate()#

Numpy 배열을 합치기 위한 함수이며 axis값을 조절하여 어떤 축을 기준으로 배열을 합칠 것인지 정할 수 있다.

arr = np.array([[1, 2, 3],
                [4, 5, 6]])

tmp = np.array([7, 8, 9])

result = np.concatenate((arr,tmp.reshape(1,3)), axis=0)

print(result)
# [[1 2 3]
#  [4 5 6]
#  [7 8 9]]

arr = np.array([[1, 2, 3],
                [4, 5, 6]])

tmp = np.array([7,8,9,10])

result = np.concatenate((arr,tmp.reshape(2,2)), axis=1)

print(result)
# [[ 1  2  3  7  8]
#  [ 4  5  6  9 10]]

ndarray의 삭제 delete()#

np.random.seed(1)
arr = np.random.randint(0,10,(3,4))

print(arr)

# result = np.delete(arr,1)  # 1번 index를 지울꺼예요!

# print(result)

result = np.delete(arr, 3, axis=1)
print(result)