2-2. NumPy: 배열 인덱싱과 브로드캐스팅

1. NumPy 배열 인덱싱 소개

NumPy는 파이썬에서 과학적 계산을 위한 핵심 라이브러리입니다. 특히 다차원 배열(ndarray)을 효율적으로 다루는 기능을 제공하며, 이는 데이터 분석, 머신러닝, 딥러닝 등 다양한 분야에서 필수적으로 사용됩니다. NumPy의 핵심 기능 중 하나는 바로 배열 인덱싱(Array Indexing)입니다. 배열 인덱싱은 배열 내의 특정 요소에 접근하고 조작하는 방법을 의미하며, 데이터를 효과적으로 처리하고 분석하는 데 중요한 역할을 합니다. NumPy의 배열 인덱싱은 파이썬 리스트의 인덱싱보다 훨씬 유연하고 강력하며, 다양한 형태의 인덱싱 기법을 제공하여 복잡한 데이터 조작을 가능하게 합니다.

1) 배열 인덱싱의 중요성

배열 인덱싱은 다음과 같은 이유로 중요합니다.

  • 데이터 접근: 배열 내의 개별 요소 또는 부분 집합에 접근하여 값을 읽고 수정할 수 있습니다.
  • 데이터 필터링: 특정 조건을 만족하는 데이터만 선택적으로 추출할 수 있습니다.
  • 데이터 변환: 배열의 특정 부분을 변경하여 새로운 배열을 생성하거나 데이터를 변환할 수 있습니다.
  • 성능 향상: NumPy는 C로 구현되어 있어 파이썬의 list에 비해 빠른 인덱싱 연산을 제공합니다.

2) 배열 인덱싱의 종류

NumPy는 다양한 종류의 배열 인덱싱을 지원합니다. 주요 유형은 다음과 같습니다.

  • 정수 인덱싱 (Integer indexing)
  • 슬라이싱 (Slicing)
  • 불리언 인덱싱 (Boolean indexing)
  • 고급 인덱싱 (Advanced indexing)

2. 정수 인덱싱 (Integer Indexing)

정수 인덱싱은 배열의 각 차원(dimension)에 정수를 사용하여 특정 요소를 선택하는 방법입니다. 각 정수는 해당 차원의 특정 위치를 나타냅니다. 예를 들어, 2차원 배열 arr이 있을 때, arr[i, j]arri번째 행, j번째 열에 있는 요소를 선택합니다.

1) 1차원 배열의 정수 인덱싱

1차원 배열의 경우, 단일 정수 인덱스를 사용하여 특정 요소에 접근합니다. 인덱스는 0부터 시작하며, 음수 인덱스를 사용하여 배열의 끝에서부터 요소를 참조할 수도 있습니다.

import numpy as np

arr = np.array([10, 20, 30, 40, 50])
print(arr[0])  # 출력: 10
print(arr[2])  # 출력: 30
print(arr[-1]) # 출력: 50 (마지막 요소)

2) 다차원 배열의 정수 인덱싱

다차원 배열의 경우, 각 차원에 대한 인덱스를 쉼표로 구분하여 지정합니다. 예를 들어, 2차원 배열 arr의 경우, arr[행 인덱스, 열 인덱스]와 같이 사용합니다.

arr_2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(arr_2d[0, 0])  # 출력: 1 (0행 0열)
print(arr_2d[1, 2])  # 출력: 6 (1행 2열)

3) 정수 인덱싱을 사용한 값 변경

정수 인덱싱을 사용하여 배열의 특정 요소 값을 변경할 수 있습니다.

arr = np.array([1, 2, 3])
arr[0] = 10
print(arr)  # 출력: [10  2  3]

arr_2d = np.array([[1, 2, 3], [4, 5, 6]])
arr_2d[0, 1] = 20
print(arr_2d) # 출력: [[ 1 20  3]
                #        [ 4  5  6]]

3. 슬라이싱 (Slicing)

슬라이싱은 배열의 일부분을 선택하는 방법입니다. start:stop:step 형식을 사용하여 시작 인덱스, 종료 인덱스, 간격을 지정합니다. 슬라이싱은 새로운 배열을 반환하며, 원본 배열을 변경하지 않습니다.

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

arr = np.array([10, 20, 30, 40, 50])
print(arr[1:4])   # 출력: [20 30 40] (인덱스 1부터 3까지)
print(arr[:3])    # 출력: [10 20 30] (처음부터 인덱스 2까지)
print(arr[2:])    # 출력: [30 40 50] (인덱스 2부터 끝까지)
print(arr[::2])   # 출력: [10 30 50] (0, 2, 4)

2) 다차원 배열의 슬라이싱

다차원 배열의 슬라이싱은 각 차원에 대해 슬라이스 범위를 지정합니다.

arr_2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(arr_2d[:2, 1:])  # 출력: [[2 3]
                     #        [5 6]]

3) 슬라이싱과 값 변경

슬라이싱을 사용하여 배열의 일부분을 선택하고 값을 변경할 수 있습니다. 슬라이싱은 원본 배열의 뷰(view)를 반환하므로, 슬라이싱된 부분을 변경하면 원본 배열도 함께 변경됩니다.

arr = np.array([1, 2, 3, 4, 5])
arr_slice = arr[1:3]
arr_slice[:] = 0  # 슬라이스의 모든 요소를 0으로 변경
print(arr)       # 출력: [1 0 0 4 5] (원본 배열도 변경됨)

주의할 점은, 슬라이싱은 원본 배열의 뷰를 반환하므로, 슬라이싱된 부분을 수정하면 원본 배열이 함께 변경된다는 것입니다. 만약 원본 배열을 변경하지 않고 슬라이싱된 배열을 사용하고 싶다면, copy() 메서드를 사용하여 새로운 배열을 생성해야 합니다.

arr = np.array([1, 2, 3, 4, 5])
arr_slice = arr[1:3].copy()
arr_slice[:] = 0
print(arr)      # 출력: [1 2 3 4 5] (원본 배열은 변경되지 않음)

4. 불리언 인덱싱 (Boolean Indexing)

불리언 인덱싱은 불리언 배열을 사용하여 배열의 특정 요소를 선택하는 방법입니다. 불리언 배열은 각 요소에 대해 True 또는 False 값을 가지며, True에 해당하는 요소만 선택됩니다.

1) 불리언 배열 생성

불리언 배열은 비교 연산자(>, <, ==, != 등)를 사용하여 생성할 수 있습니다.

arr = np.array([1, 2, 3, 4, 5])
bool_arr = arr > 2  # 각 요소가 2보다 큰지 비교
print(bool_arr)     # 출력: [False False  True  True  True]

2) 불리언 배열을 사용한 인덱싱

불리언 배열을 사용하여 배열의 요소를 선택합니다.

arr = np.array([1, 2, 3, 4, 5])
bool_arr = arr > 2
print(arr[bool_arr])  # 출력: [3 4 5]

3) 불리언 인덱싱의 활용

불리언 인덱싱은 데이터 필터링에 매우 유용합니다. 예를 들어, 특정 조건을 만족하는 데이터만 추출하거나, 이상치를 제거하는 데 사용할 수 있습니다.

arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
filtered_arr = arr[arr % 2 <mark class="highlight"> 0]  # 짝수만 선택
print(filtered_arr) # 출력: [ 2  4  6  8 10]

5. 브로드캐스팅 (Broadcasting)

브로드캐스팅은 NumPy에서 서로 다른 shape(차원)을 가진 배열 간의 연산을 가능하게 하는 강력한 기능입니다. 브로드캐스팅은 더 작은 shape의 배열을 더 큰 shape의 배열과 호환되도록 "확장"하여 연산을 수행합니다. 브로드캐스팅은 명시적으로 메모리를 복사하지 않고, 효율적인 연산을 수행할 수 있도록 해줍니다.==

1) 브로드캐스팅의 기본 원리

브로드캐스팅은 두 배열의 shape을 비교하여 다음 규칙에 따라 작동합니다.

  • 규칙 1: 두 배열의 차원 수가 다르면, 차원 수가 적은 배열의 shape을 왼쪽(가장 앞쪽)에 1을 추가하여 shape을 맞춥니다.
  • 규칙 2: 두 배열의 각 차원에서 shape이 일치하거나, 둘 중 하나의 shape이 1이면 브로드캐스팅이 가능합니다. 이 경우 shape이 1인 배열은 해당 차원 전체에 걸쳐 복제됩니다.
  • 규칙 3: 두 배열의 shape이 일치하지 않고, shape이 1도 아닌 차원이 있다면, ValueError가 발생합니다.

2) 브로드캐스팅 예시

import numpy as np

# 1차원 배열과 스칼라의 덧셈
arr = np.array([1, 2, 3])
scalar = 2
result = arr + scalar  # 스칼라가 배열과 동일한 shape으로 브로드캐스팅됨
print(result) # 출력: [3 4 5]

# 2차원 배열과 1차원 배열의 덧셈
arr_2d = np.array([[1, 2, 3], [4, 5, 6]])  # (2, 3)
arr_1d = np.array([10, 20, 30])           # (3,)
result = arr_2d + arr_1d
print(result)
# 출력: [[11 22 33]
#        [14 25 36]]
# arr_1d가 각 행에 브로드캐스팅됨

3) 브로드캐스팅의 활용

브로드캐스팅은 다양한 상황에서 활용됩니다.

  • 배열과 스칼라 연산: 배열의 모든 요소에 동일한 값을 더하거나 곱하는 경우.
  • 배열 간의 연산: 서로 다른 shape을 가진 배열 간의 연산을 수행하는 경우. 예를 들어, 이미지 처리에서 채널별 연산을 수행할 때 유용합니다.
  • 데이터 정규화: 각 열의 평균을 빼고 표준편차로 나누는 등의 연산을 수행할 때.

4) 브로드캐스팅 예시: 이미지 처리

이미지 처리는 브로드캐스팅의 강력한 활용 사례입니다. 이미지 데이터는 일반적으로 3차원 배열로 표현됩니다. (높이, 너비, 채널). 예를 들어, 이미지를 밝게 만들고 싶다면 각 픽셀의 RGB 값을 증가시켜야 합니다. 브로드캐스팅을 사용하면 각 픽셀의 RGB 채널에 스칼라 값을 쉽게 더할 수 있습니다.

이미지 처리 예시 뒤

다음은 간단한 예시입니다.

import numpy as np

# 가상의 이미지 데이터 (높이 2, 너비 3, 채널 3: RGB)
image = np.array([[[255, 0, 0], [0, 255, 0], [0, 0, 255]],
                  [[255, 255, 0], [255, 0, 255], [0, 255, 255]]])

# 밝기 조절 값 (스칼라)
brightness_factor = 50

# 브로드캐스팅을 사용하여 밝기 조절
adjusted_image = image + brightness_factor

print(adjusted_image)

위의 코드에서 brightness_factor는 스칼라 값이며, image 배열의 각 RGB 채널에 브로드캐스팅되어 더해집니다. 결과적으로, 이미지의 밝기가 증가합니다.

5) 브로드캐스팅 주의사항

  • Shape 호환성: 브로드캐스팅을 수행하려면 배열의 shape이 브로드캐스팅 규칙을 따라야 합니다. 호환되지 않는 shape 간의 연산을 시도하면 ValueError가 발생합니다.
  • 메모리 사용: 브로드캐스팅은 메모리 복사를 명시적으로 하지 않지만, 내부적으로는 필요한 경우 복사를 수행할 수 있습니다. 따라서 대규모 배열에 대한 브로드캐스팅 연산은 메모리 사용량이 증가할 수 있습니다.
  • 성능: 브로드캐스팅은 일반적으로 효율적이지만, 복잡한 연산의 경우 성능에 영향을 미칠 수 있습니다. 가능하면 반복문 대신 NumPy의 내장 함수를 사용하는 것이 좋습니다.

6. 결론

NumPy의 배열 인덱싱과 브로드캐스팅은 NumPy를 효과적으로 사용하기 위한 핵심적인 기능입니다. 정수 인덱싱, 슬라이싱, 불리언 인덱싱을 통해 배열의 특정 요소에 접근하고 조작할 수 있으며, 브로드캐스팅을 통해 다양한 shape의 배열 간의 연산을 수행할 수 있습니다. 이러한 기능들을 잘 이해하고 활용하면, 데이터 분석, 과학적 계산, 머신러닝 등 다양한 분야에서 데이터를 효율적으로 처리하고 분석할 수 있습니다. NumPy의 강력한 기능을 활용하여 데이터를 효과적으로 다루는 것은 매우 중요합니다. 꾸준한 연습과 실제 데이터에 대한 적용을 통해 NumPy의 숙련도를 높여나가시기 바랍니다.

비슷한 글 추천

Comments (0)

No comments yet. Be the first to comment!