[Python 100일 챌린지] Day 62 - NumPy 활용
NumPy 마스터로 가는 길! 🎯
어제 배열 만들기와 기본 연산을 배웠죠? 오늘은 인덱싱, 슬라이싱, 브로드캐스팅으로 데이터를 자유자재로 다뤄봐요! 실전에서 가장 많이 쓰는 기법들이에요! 💪
(30-40분 완독 ⭐⭐)
🎯 오늘의 학습 목표
📚 사전 지식
- Day 61: NumPy 기초 - 배열 생성과 기본 연산
🎯 학습 목표 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 3arr2d[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을 추가 (예:
(3,)→(1, 3))- 크기가 1이면: 다른 배열의 크기에 맞춰 늘림 (예:
(1, 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 2np.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}등급")
📝 오늘 배운 내용 정리
- 팬시 인덱싱: 여러 인덱스를 한번에
arr[[0, 2, 4]] - 불리언 인덱싱: 조건으로 필터링
arr[arr > 5] - 2차원 슬라이싱:
arr2d[행범위, 열범위] - 브로드캐스팅: 다른 형태의 배열 자동 연산
- 조건부 처리:
np.where(),np.select()
🔗 관련 자료
📚 이전 학습
Day 61: NumPy 기초 ⭐⭐
어제는 NumPy 배열 생성과 기본 연산을 배웠어요!
📚 다음 학습
Day 63: Pandas 기초 ⭐⭐
내일은 데이터 분석의 핵심 라이브러리 Pandas를 시작해요!
“늦었다고 생각할 때가 가장 빠른 때입니다. 오늘도 한 걸음 성장했어요!” 🚀
Day 62/100 Phase 7: 데이터 분석 기초 #100DaysOfPython
