포스트

[Python 100일 챌린지] Day 62 - NumPy 활용

[Python 100일 챌린지] Day 62 - NumPy 활용

NumPy 마스터로 가는 길! 🎯

어제 배열 만들기와 기본 연산을 배웠죠? 오늘은 인덱싱, 슬라이싱, 브로드캐스팅으로 데이터를 자유자재로 다뤄봐요! 실전에서 가장 많이 쓰는 기법들이에요! 💪

(30-40분 완독 ⭐⭐)

🎯 오늘의 학습 목표

📚 사전 지식


🎯 학습 목표 1: 배열 인덱싱 마스터하기

1.1 기본 인덱싱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np

# 1차원 배열
arr = np.array([10, 20, 30, 40, 50])

print(arr[0])   # 10 (첫 번째)
print(arr[-1])  # 50 (마지막)
print(arr[2])   # 30 (세 번째)

# 2차원 배열
arr2d = np.array([[1, 2, 3],
                  [4, 5, 6],
                  [7, 8, 9]])

print(arr2d[0, 0])  # 1 (0행 0열)
print(arr2d[1, 2])  # 6 (1행 2열)
print(arr2d[2, 1])  # 8 (2행 1열)

# Python 리스트와의 차이
print(arr2d[1][2])  # 6 - 이것도 가능하지만
print(arr2d[1, 2])  # 6 - 이게 더 빠르고 권장!

1.2 팬시 인덱싱 (Fancy Indexing)

여러 인덱스를 한 번에 지정할 수 있어요!

💡 팬시 인덱싱을 비유하면?

일반 인덱싱: 책장에서 한 권씩 책을 꺼내는 것 (arr[0], arr[1], arr[2]) 팬시 인덱싱: 리스트를 주고 “1번, 3번, 5번 책 한꺼번에 줘!” (arr[[0, 2, 4]])

한 번에 여러 위치의 값을 가져오거나, 원하는 순서로 재배열할 수 있어요!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import numpy as np

arr = np.array([10, 20, 30, 40, 50])

# 여러 인덱스 한번에
indices = [0, 2, 4]
print(arr[indices])  # [10 30 50]

# 리스트로 직접 지정
print(arr[[1, 3]])   # [20 40]

# 2차원에서
arr2d = np.array([[1, 2, 3],
                  [4, 5, 6],
                  [7, 8, 9]])

# 특정 행들 선택
print(arr2d[[0, 2]])
# [[1 2 3]
#  [7 8 9]]

# 특정 위치들 선택 (대각선 추출!)
rows = [0, 1, 2]
cols = [0, 1, 2]
print(arr2d[rows, cols])  # [1 5 9]

# 어떻게 동작하는지 시각화:
# arr2d = [[1, 2, 3],    rows[0]=0, cols[0]=0 → arr2d[0,0] = 1
#          [4, 5, 6],    rows[1]=1, cols[1]=1 → arr2d[1,1] = 5
#          [7, 8, 9]]    rows[2]=2, cols[2]=2 → arr2d[2,2] = 9

1.3 불리언 인덱싱

조건으로 데이터를 필터링하는 강력한 기능!

💡 불리언 인덱싱을 비유하면?

마트에서 “5000원 이상인 상품만 보여줘!“라고 하면, 조건에 맞는 상품만 골라주죠? 불리언 인덱싱도 똑같아요! 조건을 던지면 True인 것만 돌려줍니다.

1
2
3
조건 = [T, F, F, T, T]   # True/False 마스크
배열 = [1, 2, 3, 4, 5]
결과 = [1,       4, 5]   # True 위치만 추출!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import numpy as np

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

# 조건으로 필터링
print(arr[arr > 5])         # [6 7 8 9 10]
print(arr[arr % 2 == 0])    # [2 4 6 8 10] (짝수만)
print(arr[arr % 3 == 0])    # [3 6 9] (3의 배수만)

# 여러 조건 조합
print(arr[(arr > 3) & (arr < 8)])  # [4 5 6 7]
print(arr[(arr < 3) | (arr > 8)])  # [1 2 9 10]

# 2차원에서
scores = np.array([[85, 90, 78],
                   [92, 88, 95],
                   [65, 70, 72]])

# 80점 이상인 점수만
print(scores[scores >= 80])  # [85 90 92 88 95]

🎯 학습 목표 2: 배열 슬라이싱 활용하기

2.1 1차원 슬라이싱

💡 슬라이싱 문법 정리: arr[시작:끝:간격]

문법 의미 예시 결과
arr[2:7] 2부터 6까지 [0,1,2,3,4,5,6,7,8,9] [2,3,4,5,6]
arr[:5] 처음부터 4까지 위와 동일 [0,1,2,3,4]
arr[5:] 5부터 끝까지 위와 동일 [5,6,7,8,9]
arr[::2] 2칸씩 건너뛰기 위와 동일 [0,2,4,6,8]
arr[::-1] 역순 위와 동일 [9,8,...,0]

⚠️ 주의: 끝 인덱스는 포함되지 않아요! arr[2:7]은 인덱스 7은 제외!

1
2
3
4
5
6
7
8
9
import numpy as np

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

print(arr[2:7])    # [2 3 4 5 6]  (인덱스 7은 제외!)
print(arr[:5])     # [0 1 2 3 4]  (처음부터)
print(arr[5:])     # [5 6 7 8 9]  (끝까지)
print(arr[::2])    # [0 2 4 6 8]  (2칸씩)
print(arr[::-1])   # [9 8 7 6 5 4 3 2 1 0]  (역순)

2.2 2차원 슬라이싱

💡 2차원 슬라이싱 문법: arr2d[행범위, 열범위]

1
2
3
arr2d[0:2, 1:3]
      ↑     ↑
    행범위  열범위
  • 콤마(,) 앞: 범위
  • 콤마(,) 뒤: 범위
  • : 혼자 쓰면: 전체 선택
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import numpy as np

arr2d = np.array([[1, 2, 3, 4],
                  [5, 6, 7, 8],
                  [9, 10, 11, 12]])

# 행 슬라이싱
print(arr2d[0:2])  # 0~1행
# [[1 2 3 4]
#  [5 6 7 8]]

# 열 슬라이싱
print(arr2d[:, 1:3])  # 모든 행, 1~2열
# [[ 2  3]
#  [ 6  7]
#  [10 11]]

# 부분 배열 추출
print(arr2d[0:2, 1:3])  # 0~1행, 1~2열
# [[2 3]
#  [6 7]]

# 특정 열만
print(arr2d[:, 0])   # [1 5 9] (0번째 열)
print(arr2d[:, -1])  # [4 8 12] (마지막 열)

2.3 슬라이싱으로 값 변경하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import numpy as np

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

# 여러 값 한번에 변경
arr[1:4] = 0
print(arr)  # [1 0 0 0 5]

# 2차원에서
arr2d = np.array([[1, 2, 3],
                  [4, 5, 6],
                  [7, 8, 9]])

# 특정 영역 변경
arr2d[0:2, 0:2] = 0
print(arr2d)
# [[0 0 3]
#  [0 0 6]
#  [7 8 9]]

⚠️ 주의: 슬라이싱은 원본 배열의 뷰(view)를 반환해요. 변경하면 원본도 바뀝니다!


🎯 학습 목표 3: 브로드캐스팅 이해하기

3.1 브로드캐스팅이란?

서로 다른 형태의 배열끼리 연산할 때, NumPy가 자동으로 형태를 맞춰주는 기능!

💡 브로드캐스팅을 비유하면?

비유 1: 복사기

  • [1, 2, 3] + 10을 계산할 때
  • NumPy가 10을 복사해서 [10, 10, 10]으로 만들어줌
  • 실제로 복사하진 않고, 그런 것처럼 연산함 (메모리 효율적!)

비유 2: 벽지 붙이기

  • 작은 패턴(작은 배열)을 큰 벽(큰 배열)에 반복해서 붙이는 것과 비슷해요
  • 형태가 맞아야 빈틈없이 붙일 수 있듯이, 브로드캐스팅도 규칙이 있어요!
1
2
3
4
5
6
7
8
import numpy as np

# 배열 + 스칼라 (기본 브로드캐스팅)
arr = np.array([1, 2, 3])
print(arr + 10)  # [11 12 13]

# 내부적으로는 이렇게 동작:
# [1, 2, 3] + [10, 10, 10] = [11, 12, 13]

3.2 브로드캐스팅 규칙

📋 브로드캐스팅 3가지 규칙:

  1. 차원이 다르면: 작은 쪽 앞에 1을 추가 (예: (3,)(1, 3))
  2. 크기가 1이면: 다른 배열의 크기에 맞춰 늘림 (예: (1, 3) + (3, 3)(3, 3) + (3, 3))
  3. 크기가 다르고 1도 아니면: 에러! 브로드캐스팅 불가
배열 A 배열 B 결과 가능?
(3,) (3,) (3,) ✅ 같은 형태
(3, 3) (3,) (3, 3) ✅ 행마다 적용
(3, 1) (1, 3) (3, 3) ✅ 양쪽 확장
(3,) (4,) - ❌ 에러!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import numpy as np

# 예제 1: (3,) + (3,) - 같은 형태
a = np.array([1, 2, 3])
b = np.array([10, 20, 30])
print(a + b)  # [11 22 33]

# 예제 2: (3, 3) + (3,) - 1차원이 확장됨
arr2d = np.array([[1, 2, 3],
                  [4, 5, 6],
                  [7, 8, 9]])
row = np.array([10, 20, 30])

print(arr2d + row)
# [[11 22 33]
#  [14 25 36]
#  [17 28 39]]

# 예제 3: (3, 1) + (3,) - 열 벡터와 행 벡터
col = np.array([[1], [2], [3]])  # 2차원 배열 (3행 1열)
row = np.array([10, 20, 30])     # 1차원 배열 (원소 3개)

# 형태 확인해보기
print(f"col.shape: {col.shape}")  # (3, 1)
print(f"row.shape: {row.shape}")  # (3,)

print(col + row)
# [[11 21 31]
#  [12 22 32]
#  [13 23 33]]

💡 형태(shape) 읽는 법:

  • (3,) → 1차원, 원소 3개
  • (3, 1) → 2차원, 3행 1열 (세로로 긴 열 벡터)
  • (1, 3) → 2차원, 1행 3열 (가로로 긴 행 벡터)

3.3 실전 브로드캐스팅 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import numpy as np

# 성적 정규화 (각 과목별로 평균 빼기)
scores = np.array([[85, 90, 78],
                   [92, 88, 95],
                   [65, 70, 72]])

# 각 열(과목)의 평균
means = np.mean(scores, axis=0)
print(f"과목별 평균: {means}")

# 평균 빼기 (브로드캐스팅!)
normalized = scores - means
print(normalized)

# 온도 변환: 섭씨 → 화씨
celsius = np.array([0, 10, 20, 30, 40])
fahrenheit = celsius * 9/5 + 32
print(fahrenheit)  # [ 32.  50.  68.  86. 104.]

🎯 학습 목표 4: 실전 데이터 조작하기

4.1 조건에 따른 값 변경

np.where() - 이것 아니면 저것

💡 np.where() 비유: 엑셀의 IF 함수와 똑같아요! =IF(조건, 참일때, 거짓일때)

1
2
np.where(점수 >= 60, "합격", "불합격")
        ↑ 조건      ↑ 참    ↑ 거짓
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import numpy as np

# np.where: 조건에 따라 다른 값 반환
arr = np.array([1, 2, 3, 4, 5])

# 3보다 크면 '크다', 아니면 '작다'
result = np.where(arr > 3, '크다', '작다')
print(result)  # ['작다' '작다' '작다' '크다' '크다']

# 숫자로도 가능
result2 = np.where(arr > 3, 1, 0)
print(result2)  # [0 0 0 1 1]

# 점수를 합격/불합격으로
scores = np.array([85, 42, 73, 91, 55])
pass_fail = np.where(scores >= 60, '합격', '불합격')
print(pass_fail)  # ['합격' '불합격' '합격' '합격' '불합격']

np.select() - 여러 조건 처리

np.where()는 조건이 2개(참/거짓)일 때 좋지만, 3개 이상의 조건이 필요하면 np.select()가 편해요!

💡 np.where() vs np.select() 비교:

상황 사용할 함수 비유
합격/불합격 (2가지) np.where() 동전 던지기 (앞/뒤)
A/B/C/D/F (5가지) np.select() 주사위 던지기 (여러 면)

np.select()엑셀의 중첩 IF 또는 SWITCH 문과 비슷해요!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np

scores = np.array([95, 82, 67, 73, 88, 91, 55, 78])

# np.select(조건리스트, 값리스트, default=기본값)
conditions = [
    scores >= 90,  # 첫 번째 조건: 90점 이상
    scores >= 80,  # 두 번째 조건: 80점 이상
    scores >= 70   # 세 번째 조건: 70점 이상
]
choices = ['A', 'B', 'C']  # 각 조건에 해당하는 값

grades = np.select(conditions, choices, default='D')
print(grades)  # ['A' 'B' 'D' 'C' 'B' 'A' 'D' 'C']

💡 중요: np.select()먼저 매칭되는 조건이 적용돼요! 95점은 90점 이상, 80점 이상, 70점 이상 모두 해당하지만, 가장 먼저 매칭되는 ‘A’가 적용됩니다.

4.2 정렬하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import numpy as np

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

# 정렬된 복사본 반환
print(np.sort(arr))  # [1 1 2 3 4 5 6 9]

# 내림차순
print(np.sort(arr)[::-1])  # [9 6 5 4 3 2 1 1]

# 정렬된 인덱스 반환
print(np.argsort(arr))  # [1 3 6 0 2 4 7 5]

# 2차원 정렬
arr2d = np.array([[3, 1, 2],
                  [6, 4, 5]])

print(np.sort(arr2d, axis=1))  # 각 행 내에서 정렬
# [[1 2 3]
#  [4 5 6]]

4.3 유일한 값 찾기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np

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

# 중복 제거
print(np.unique(arr))  # [1 2 3 4]

# 개수와 함께
values, counts = np.unique(arr, return_counts=True)
print(values)  # [1 2 3 4]
print(counts)  # [1 2 3 1]

# 딕셔너리로 만들기
for v, c in zip(values, counts):
    print(f"{v}: {c}")

4.4 결측값 처리

💡 NaN (Not a Number)이란?

데이터 분석에서 빈 값, 결측값, 측정 불가 등을 나타내요.

  • 설문조사에서 응답하지 않은 항목
  • 센서에서 측정 실패한 데이터
  • 엑셀에서 빈 셀과 비슷해요!

⚠️ 주의: np.nan과 연산하면 결과도 nan이 됩니다!

1
np.nan + 5  # → nan (전염됨!)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np

# nan (Not a Number) 다루기
arr = np.array([1.0, 2.0, np.nan, 4.0, 5.0])

print(np.isnan(arr))  # [False False  True False False]
print(np.sum(np.isnan(arr)))  # 1 (nan 개수)

# nan 제외하고 계산
print(np.nanmean(arr))  # 3.0 (nan 제외 평균)
print(np.nansum(arr))   # 12.0 (nan 제외 합계)

# nan을 다른 값으로 대체
arr_filled = np.where(np.isnan(arr), 0, arr)
print(arr_filled)  # [1. 2. 0. 4. 5.]

💡 실전 팁 & 주의사항

✅ 성능 팁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import numpy as np

# 1. 벡터 연산 사용하기 (for문 피하기)
arr = np.arange(1000000)

# ❌ 느림
result = []
for x in arr:
    result.append(x * 2)

# ✅ 빠름
result = arr * 2

# 2. 미리 배열 크기 지정하기
# ❌ 계속 크기 변경
result = np.array([])
for i in range(1000):
    result = np.append(result, i)

# ✅ 미리 할당
result = np.empty(1000)
for i in range(1000):
    result[i] = i

⚠️ 흔한 실수

1
2
3
4
5
6
7
8
9
10
11
import numpy as np

# 1. 뷰 vs 복사 혼동
arr = np.array([1, 2, 3, 4, 5])
view = arr[1:4]    # 뷰 (원본 공유)
copy = arr[1:4].copy()  # 복사본 (독립적)

# 2. 브로드캐스팅 형태 오류
a = np.array([[1, 2], [3, 4]])  # (2, 2)
b = np.array([1, 2, 3])         # (3,)
# a + b  # Error! 형태가 안 맞음

🧪 연습 문제

문제 1: 이미지 밝기 조절

3x3 그레이스케일 이미지(0~255)가 있습니다. 모든 픽셀 값을 50 증가시키되, 255를 넘지 않도록 하세요.

1
2
3
image = np.array([[100, 150, 200],
                  [50, 100, 150],
                  [200, 220, 240]])
💡 힌트 (단계별 접근)

단계 1: 문제 이해하기

  • 픽셀 값 범위: 0~255 (그레이스케일)
  • 모든 값에 50을 더하되, 255를 넘으면 안 됨

단계 2: 접근 방법 선택

  • 방법 A: np.where() 사용
    • 먼저 50을 더한 후
    • np.where(결과 > 255, 255, 결과) - 255 초과면 255로, 아니면 그대로
  • 방법 B: np.clip() 사용 (더 간단!)
    • np.clip(배열, 최소값, 최대값) - 범위를 벗어나면 자동 조절

단계 3: 코드 작성

1
2
brightened = image + 50  # 먼저 50 더하기
result = np.clip(brightened, 0, 255)  # 0~255 범위로 제한
정답 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np

image = np.array([[100, 150, 200],
                  [50, 100, 150],
                  [200, 220, 240]])

# 방법 1: np.where 사용
brightened = image + 50
result1 = np.where(brightened > 255, 255, brightened)
print(result1)

# 방법 2: np.clip 사용 (더 간단!)
result2 = np.clip(image + 50, 0, 255)
print(result2)

문제 2: 성적 등급 매기기

90점 이상 A, 80점 이상 B, 70점 이상 C, 나머지 D 등급을 매기세요.

1
scores = np.array([95, 82, 67, 73, 88, 91, 55, 78])
💡 힌트 (단계별 접근)

단계 1: 등급 규칙 정리

  • A: 90점 이상
  • B: 80점 이상 (90점 미만)
  • C: 70점 이상 (80점 미만)
  • D: 70점 미만

단계 2: np.select() 구조 이해하기

1
np.select(조건_리스트, 값_리스트, default=기본값)

단계 3: 조건 순서가 중요!

  • 높은 점수 조건부터 체크해야 해요!
  • 95점은 90 이상, 80 이상, 70 이상 모두 참이지만
  • 먼저 매칭되는 “90 이상” 조건의 ‘A’가 적용됨

단계 4: 코드 작성

1
2
3
4
5
6
7
conditions = [
    scores >= 90,  # 먼저 체크! 95점은 여기서 'A'
    scores >= 80,  # 85점은 여기서 'B'
    scores >= 70   # 75점은 여기서 'C'
]
choices = ['A', 'B', 'C']
grades = np.select(conditions, choices, default='D')
정답 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np

scores = np.array([95, 82, 67, 73, 88, 91, 55, 78])

# np.select 사용
conditions = [
    scores >= 90,
    scores >= 80,
    scores >= 70
]
choices = ['A', 'B', 'C']
grades = np.select(conditions, choices, default='D')

for score, grade in zip(scores, grades):
    print(f"{score}점: {grade}등급")

📝 오늘 배운 내용 정리

  1. 팬시 인덱싱: 여러 인덱스를 한번에 arr[[0, 2, 4]]
  2. 불리언 인덱싱: 조건으로 필터링 arr[arr > 5]
  3. 2차원 슬라이싱: arr2d[행범위, 열범위]
  4. 브로드캐스팅: 다른 형태의 배열 자동 연산
  5. 조건부 처리: np.where(), np.select()

🔗 관련 자료


📚 이전 학습

Day 61: NumPy 기초 ⭐⭐

어제는 NumPy 배열 생성과 기본 연산을 배웠어요!

📚 다음 학습

Day 63: Pandas 기초 ⭐⭐

내일은 데이터 분석의 핵심 라이브러리 Pandas를 시작해요!


“늦었다고 생각할 때가 가장 빠른 때입니다. 오늘도 한 걸음 성장했어요!” 🚀

Day 62/100 Phase 7: 데이터 분석 기초 #100DaysOfPython
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.