4.7.1 1차원 배열의 첨자와 슬라이싱

배열 인덱싱과 레이저 커팅

[그림] 방대한 데이터 블록에서 원하는 구역만 레이저로 정밀 절단하는 슬라이싱 기술

데이터 추출 작전: 1차원 슬라이싱 5단계

1차원 배열 생성

실습을 위해 0부터 9까지의 숫자를 제곱한 10칸 짜리 1차원 배열(줄자) a를 생성하겠습니다.

import numpy as np

# 0제곱부터 9제곱까지 10개의 숫자 생성
a = np.power(np.arange(10), 2)
print("베이스 1차원 배열 a:\n", a)

출력:

베이스 1차원 배열 a:
 [ 0  1  4  9 16 25 36 49 64 81]

[1단계] 단건 저격: 단일 요소 인덱싱

0부터 시작하는 ‘정향(Positive)’ 위치 번호와, 맨 뒤에서부터 -1로 시작하는 ‘역방향(Negative)’ 위치 번호를 사용해 특정 데이터를 정확히 저격하여 뽑아낼 수 있습니다.

1단계 단건 인덱싱 애니메이션

좌측에서는 0, 1, 2, 3.. 순으로 세고, 우측에서는 -1, -2, -3.. 순으로 거꾸로 셉니다.

# 앞에서 4번째 데이터 (인덱스 3)
print("a[3]의 값:", a[3])

# 뒤에서 2번째 데이터 (인덱스 -2)
print("a[-2]의 값:", a[-2])

실행 결과:

a[3]의 값: 9
a[-2]의 값: 64

[2단계] 레이저 커팅: 범위를 끊어내는 부분 슬라이싱

연속된 구간을 칼로 도려내듯 추출하는 방법입니다.

[시작(Start) : 끝(Stop)] 형식을 사용하며, 항상 Stop 번호 직전(-1)까지만 잘린다는 점을 주의해야 합니다.

2단계 슬라이싱 애니메이션

시작점부터 자르고, 끝점은 ‘그 앞까지만’ 도려내어 뿅! 들어올립니다.

# 인덱스 1부터 3(4의 앞)까지 잘라내기
slice1 = a[1:4]
print("a[1:4] 결과:", slice1)

# 뒤에서 5번째부터 뒤에서 2번째(-1의 앞)까지 잘라내기
slice2 = a[-5:-1]
print("a[-5:-1] 결과:", slice2)

실행 결과:

a[1:4] 결과: [1 4 9]
a[-5:-1] 결과: [25 36 49 64]

💡 잠깐! 콜론 1개(:)와 2개(::)의 결정적 차이

슬라이싱의 기저에 깔려 있는 완전한 3단 공식은 [시작(start) : 끝(stop) : 간격(step)] 입니다. 이 공식에서 콜론의 개수에 따라 의미가 크게 달라집니다.

  • [start:stop] (콜론 1개): 오직 시작점과 끝점 범위만 지정할 때 씁니다. 이 경우 세 번째 값인 간격(step)은 적지 않은 것이며, 기본값인 ‘1칸씩 전진(+1)’이 자동 적용됩니다.
  • [::step] (콜론 2개): 가운데에 적어야 할 시작점과 끝점을 모두 생략(비워둠)하고, 두 번째 콜론을 바로 써서 간격(step) 구역으로 워프하겠다는 뜻입니다. 시작과 끝을 비워두었으므로 “처음부터 끝까지 전체 구역”이라는 범위가 내부적으로 자동 적용됩니다.

[3단계] 거울 뒤집기: 간격(Step)과 역순 슬라이싱

기본 커팅 뒤에 간격(Step)을 지시해 봅시다.

만약 Step 자리에 음수(-)를 넣으면 배열이 우로 향하지 않고 좌로 역진격합니다.

특히 앞서 배운 콜론 2개 문법을 활용하여 [::-1] 이라고 입력하면, “시작과 끝 범위 지정은 생략할 테니(::) 처음부터 끝까지 모조리 역방향(-1)으로 훑어라!” 라는 강력한 배열 뒤집기 특수키가 됩니다.

3단계 역방향 슬라이싱 애니메이션

Step에 -1이 붙는 순간, 칼날이 역방향으로 향하며 거울처럼 데이터가 뒤집힙니다.

# 8번 위치에서 시작해 3번(2 전)까지 역순으로 1칸씩!
print("a[8:2:-1] 역진격 결과:", a[8:2:-1])

# 시작과 끝 생략(전체) + 역방향(-1) = 전체 배열 뒤집기!
print("a[::-1] 전체 역순 결과:", a[::-1])

# 간격을 두 칸씩 띄워서 역방향으로 징검다리 뛰기
print("a[::-2] 두 칸씩 역순 결과:", a[::-2])

실행 결과:

a[8:2:-1] 역진격 결과: [64 49 36 25 16  9]
a[::-1] 전체 역순 결과: [81 64 49 36 25 16  9  4  1  0]
a[::-2] 두 칸씩 역순 결과: [81 49 25  9  1]

[4단계] 데이터 갈아끼우기: 슬라이싱 구역 덮어쓰기

잘라낸 구역에 = 기호를 써서 새로운 데이터를 주입할 수 있습니다.

스칼라 값을 넣으면 구역 전체에 브로드캐스팅(동일하게 복제)되고, 구역과 길이가 똑같은 리스트를 넣으면 1:1로 정확하게 덮어쓰기 됩니다.

4단계 덮어쓰기 애니메이션

방금 잘라낸 빈 3칸의 구역에, 단일 스칼라 -100 이 스스로를 3개로 확장(Broadcast)하여 일괄적으로 꽂힙니다.

# [단일 값 브로드캐스팅 덮어쓰기]
# 인덱스 2~4 구역(총 3칸)에 -100을 복제하여 쑤셔 넣습니다.
a[2:5] = -100
print("스칼라 덮어쓰기 후 a:\n", a)

# [정확한 개수의 배열 덮어쓰기]
# 총 3칸의 구역이므로, 3칸짜리 리스트를 넣으면 에러 없이 각자 들어갑니다.
a[2:5] = [-5, -5, -5]
print("1:1 배열 덮어쓰기 후 a:\n", a)

실행 결과:

스칼라 덮어쓰기 후 a:
 [   0    1 -100 -100 -100   25   36   49   64   81]
1:1 배열 덮어쓰기 후 a:
 [ 0  1 -5 -5 -5 25 36 49 64 81]

🚨 [주의] 형태 파괴: 크기 불일치 에러 (ValueError)

만약 잘라낸 공간은 3칸인데, 집어넣으려는 데이터가 딱 1개(스칼라)도 아니고, 3개(완전 일치)도 아닌 어중간한 상태라면 어떻게 될까요? Numpy는 어찌해야 할지 몰라 충돌을 일으키며 작동을 거부합니다.

크기 불일치 에러 애니메이션

공간은 3개가 뚫려있는데, 대입하려는 박스는 2개뿐이라 공간이 1개 남습니다. Numpy는 이런 애매한 핏(Fit)을 결코 용납하지 않습니다!

try:
    # 3칸의 공간(인덱스 2~4)에 2칸짜리 블록을 욱여넣으려 시도
    a[2:5] = [-5, -5] 
except ValueError as e:
    print("🚨 공간 크기 오류 발생:", e)

실행 결과:

🚨 공간 크기 오류 발생: could not broadcast input array from shape (2,) into shape (3,)

영문 에러 메시지를 직역하면 “2번째 모양(2,)을 3번째 모양(3,) 공간으로 억지로 잡아늘릴(broadcast) 수 없어!” 라는 뜻입니다.


[5단계] 징검다리 덮어쓰기 심화

마지막으로 [::2] (두 칸씩 건너뛰기) 슬라이싱과 조합하여 짝수 인덱스 자리들만 골라 지뢰를 심듯 데이터를 갈아끼울 수도 있습니다.

5단계 징검다리 덮어쓰기 애니메이션

토끼뜀을 뛰며 지나간 자리(0, 2, 4, 6, 8 위치)에만 타겟팅 포커스가 맞춰져 일괄 교체됩니다.

# 모두 5로 채워진 길이가 10인 배열 b 생성
b = np.array([5] * 10)

# 두 칸 간격(0, 2, 4, 6, 8 위치)으로 -10을 스칼라 브로드캐스팅!
b[::2] = -10
print("짝수 간격에 -10 지뢰 심기 결과:\n", b)

# 똑같이 두 칸 간격(총 5자리)에, 길이가 5인 새로운 배열을 1:1 매핑시켜 덮어쓰기!
b[::2] = np.arange(5) # [0, 1, 2, 3, 4]
print("짝수 간격에 1:1 순차적 숫자 심기 결과:\n", b)

실행 결과:

짝수 간격에 -10 지뢰 심기 결과:
 [-10   5 -10   5 -10   5 -10   5 -10   5]
짝수 간격에 1:1 순차적 숫자 심기 결과:
 [0 5 1 5 2 5 3 5 4 5]
서브목차