[Python 100일 챌린지] Day 61 - NumPy 기초
🎉 Phase 7: 데이터 분석 기초 시작!
드디어 데이터 분석의 세계로 들어왔습니다! 엑셀로 1만 행 데이터 처리하면 느리고 힘들죠? NumPy를 쓰면 100만 행도 1초에 처리할 수 있어요! Python 데이터 분석의 기초 중의 기초, 오늘부터 시작합니다! 💪
(25-35분 완독 ⭐⭐)
📌 Phase 7 로드맵
| Day | 주제 | 핵심 내용 |
|---|---|---|
| 61 (오늘) | NumPy 기초 | 배열 생성, 연산, 형태 변환 |
| 62 | NumPy 활용 | 인덱싱, 슬라이싱, 브로드캐스팅 |
| 63 | Pandas 기초 | DataFrame, Series |
| 64 | Pandas 활용 | 데이터 조작, 필터링 |
| 65 | 데이터 전처리 | 결측치, 중복, 타입 변환 |
| 66 | 데이터 분석 | 그룹화, 집계, 피벗 |
| 67 | Matplotlib 기초 | 기본 그래프 그리기 |
| 68 | Matplotlib 활용 | 다양한 시각화 |
| 69 | 실전 데이터 분석 | CSV 데이터 분석 |
| 70 | 미니 프로젝트 | 종합 분석 프로젝트 |
💡 Phase 7 목표: 데이터를 불러오고 → 처리하고 → 분석하고 → 시각화하는 전 과정을 배워요!
🎯 오늘의 학습 목표
📚 사전 지식
- Day 7: 리스트 다루기 (list) - Python 리스트 기초
- Day 5: 숫자 다루기 (int, float) - 숫자 연산
🎯 학습 목표 1: NumPy가 무엇인지 이해하기
한 줄 설명
NumPy = 빠른 숫자 계산을 위한 Python 라이브러리 ⚡
1.1 NumPy가 필요한 이유
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# ❌ 순수 Python 리스트로 100만 개 숫자 더하기
import time
python_list = list(range(1000000))
start = time.time()
result = [x * 2 for x in python_list]
print(f"Python 리스트: {time.time() - start:.4f}초")
# ✅ NumPy로 100만 개 숫자 더하기
import numpy as np
numpy_array = np.array(range(1000000))
start = time.time()
result = numpy_array * 2
print(f"NumPy 배열: {time.time() - start:.4f}초")
# 출력 예시:
# Python 리스트: 0.0823초
# NumPy 배열: 0.0012초 ← 약 70배 빠름!
💡 왜 빠를까? NumPy는 C언어로 만들어져서 메모리를 효율적으로 사용하고, 벡터 연산을 지원해요!
1.2 NumPy 설치하기
1
2
# NumPy 설치
pip install numpy
설치 확인:
1
python -c "import numpy as np; print(f'NumPy {np.__version__} 설치 완료!')"
1.3 NumPy 임포트하기
1
2
3
import numpy as np # np로 줄여서 사용하는 것이 관례!
print(np.__version__) # 버전 확인
💡 왜 np?
numpy를 매번 치기 귀찮으니까np로 줄여요. 전 세계 개발자들이 이렇게 써요!
🎯 학습 목표 2: NumPy 배열 만들기
2.1 리스트에서 배열 만들기
💡 ndarray vs 리스트, 뭐가 다를까?
구분 Python 리스트 NumPy ndarray 저장 방식 여러 타입 섞어서 OK 한 가지 타입만 속도 느림 🐢 빠름 🚀 연산 for문 필요 한 번에 처리 비유 여러 크기의 상자들 같은 크기 상자가 줄줄이 리스트는 유연하지만 느리고, ndarray는 빠르지만 타입이 통일되어야 해요!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np
# 1차원 배열
arr1 = np.array([1, 2, 3, 4, 5])
print(arr1) # [1 2 3 4 5]
print(type(arr1)) # <class 'numpy.ndarray'>
# 2차원 배열 (행렬)
arr2 = np.array([[1, 2, 3],
[4, 5, 6]])
print(arr2)
# [[1 2 3]
# [4 5 6]]
# 3차원 배열
arr3 = np.array([[[1, 2], [3, 4]],
[[5, 6], [7, 8]]])
print(arr3.shape) # (2, 2, 2)
2.2 특별한 배열 만들기
💡 언제 zeros, ones, full을 쓸까?
- zeros: 점수판 초기화, 빈 이미지 만들기, 카운터 배열
- ones: 가중치 초기화(모두 1), 마스크 배열
- full: 특정 값으로 채울 때 (예: 기본값 -1로 초기화)
- eye: 행렬 연산, 단위행렬 필요할 때
처음부터 값을 채워서 배열을 만들고 싶을 때 사용해요!
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
import numpy as np
# 0으로 채운 배열
zeros = np.zeros((3, 4)) # 3행 4열
print(zeros)
# [[0. 0. 0. 0.]
# [0. 0. 0. 0.]
# [0. 0. 0. 0.]]
# 1로 채운 배열
ones = np.ones((2, 3)) # 2행 3열
print(ones)
# [[1. 1. 1.]
# [1. 1. 1.]]
# 특정 값으로 채운 배열
full = np.full((2, 2), 7) # 7로 채움
print(full)
# [[7 7]
# [7 7]]
# 단위 행렬 (대각선이 1)
eye = np.eye(3)
print(eye)
# [[1. 0. 0.]
# [0. 1. 0.]
# [0. 0. 1.]]
2.3 연속된 숫자 배열 만들기
💡 arange vs linspace, 언제 뭘 쓸까?
함수 지정하는 것 비유 사용 예 arange간격 (step) 계단 오르기 (한 칸씩, 두 칸씩) 인덱스, 정수 시퀀스 linspace개수 (num) 케이크 자르기 (5등분, 10등분) 그래프 x축, 시간 구간
- 간격이 중요하면 →
arange- 개수가 중요하면 →
linspace
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
# arange: range()와 비슷 - "간격"을 지정
arr1 = np.arange(10) # 0~9 (간격 1이 기본)
print(arr1) # [0 1 2 3 4 5 6 7 8 9]
arr2 = np.arange(1, 10, 2) # 1부터 10 미만, 2씩 증가
print(arr2) # [1 3 5 7 9]
# linspace: 균등 간격으로 나누기 - "개수"를 지정
arr3 = np.linspace(0, 1, 5) # 0~1을 5개로 나눔 (시작과 끝 포함!)
print(arr3) # [0. 0.25 0.5 0.75 1. ]
arr4 = np.linspace(0, 100, 11) # 0~100을 11개로
print(arr4) # [ 0. 10. 20. 30. 40. 50. 60. 70. 80. 90. 100.]
⚠️ 주의:
arange는 끝 값을 포함하지 않지만,linspace는 끝 값을 포함해요!
2.4 랜덤 배열 만들기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import numpy as np
# 0~1 사이 랜덤 실수
rand = np.random.rand(3, 2) # 3x2 배열
print(rand)
# 정수 랜덤
randint = np.random.randint(1, 10, size=(2, 3)) # 1~9 사이 정수
print(randint)
# 정규분포 랜덤
randn = np.random.randn(3) # 평균 0, 표준편차 1
print(randn)
# 시드 고정 (재현 가능한 랜덤)
np.random.seed(42)
print(np.random.rand(3)) # 항상 같은 결과!
💡 시드(seed)가 뭔가요?
컴퓨터의 “랜덤”은 사실 가짜 랜덤(의사 난수)이에요. 시드는 랜덤의 시작점을 정하는 것!
- 같은 시드 → 항상 같은 “랜덤” 결과
- 다른 시드 → 다른 “랜덤” 결과
비유하면 같은 주사위를 같은 방식으로 던지면 같은 결과가 나오는 것과 비슷해요!
1 2 3 4 5 np.random.seed(42) # "42번 시작점에서 랜덤 시작해!" print(np.random.rand(3)) # [0.37454012 0.95071431 0.73199394] np.random.seed(42) # 다시 42번 시작점으로! print(np.random.rand(3)) # [0.37454012 0.95071431 0.73199394] ← 같음!시드를 고정하면 다른 사람도, 내일의 나도 같은 결과를 얻을 수 있어요! 디버깅할 때 유용합니다.
🎯 학습 목표 3: 배열의 기본 연산 익히기
3.1 산술 연산 (벡터 연산)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
a = np.array([1, 2, 3, 4])
b = np.array([10, 20, 30, 40])
# 기본 연산 - 원소별로 적용!
print(a + b) # [11 22 33 44]
print(a - b) # [ -9 -18 -27 -36]
print(a * b) # [ 10 40 90 160]
print(a / b) # [0.1 0.1 0.1 0.1]
print(a ** 2) # [ 1 4 9 16]
# 스칼라 연산
print(a + 10) # [11 12 13 14]
print(a * 2) # [2 4 6 8]
💡 벡터 연산: for문 없이 배열 전체에 연산이 적용돼요. 이게 NumPy의 핵심!
3.2 비교 연산
1
2
3
4
5
6
7
8
9
10
import numpy as np
arr = np.array([1, 2, 3, 4, 5])
print(arr > 3) # [False False False True True]
print(arr == 3) # [False False True False False]
print(arr <= 2) # [ True True False False False]
# 조건으로 필터링
print(arr[arr > 3]) # [4 5]
조건 결합하기 (논리 연산자)
NumPy에서 여러 조건을 결합할 때는 Python의 and, or 대신 &, |, ~를 사용해요:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
# & : AND (그리고) - 두 조건 모두 참
even_and_big = (arr % 2 == 0) & (arr > 5)
print(arr[even_and_big]) # [6 8 10]
# | : OR (또는) - 둘 중 하나라도 참
small_or_big = (arr < 3) | (arr > 8)
print(arr[small_or_big]) # [1 2 9 10]
# ~ : NOT (부정) - 조건 반전
not_even = ~(arr % 2 == 0)
print(arr[not_even]) # [1 3 5 7 9]
⚠️ 주의: 반드시 각 조건을 괄호로 감싸야 해요!
arr % 2 == 0 & arr > 5❌
3.3 집계 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import numpy as np
arr = np.array([1, 2, 3, 4, 5])
print(np.sum(arr)) # 15 (합계)
print(np.mean(arr)) # 3.0 (평균)
print(np.std(arr)) # 1.414... (표준편차)
print(np.min(arr)) # 1 (최솟값)
print(np.max(arr)) # 5 (최댓값)
# 2차원 배열에서
arr2 = np.array([[1, 2, 3],
[4, 5, 6]])
print(np.sum(arr2)) # 21 (전체 합)
print(np.sum(arr2, axis=0)) # [5 7 9] (열 합)
print(np.sum(arr2, axis=1)) # [6 15] (행 합)
💡 axis 이해하기:
1 2 3 4 5 6 7axis=0 (행 방향 ↓) axis=1 (열 방향 →) 열0 열1 열2 열0 열1 열2 ↓ ↓ ↓ 행0 [[ 1 2 3 ]] 행0 [[ 1 2 3 ]] → 합: 6 행1 [[ 4 5 6 ]] 행1 [[ 4 5 6 ]] → 합: 15 ↓ ↓ ↓ 합:5 합:7 합:9
axis=0: 행을 따라 내려가며 계산 (결과는 열 개수만큼)axis=1: 열을 따라 옆으로 가며 계산 (결과는 행 개수만큼)
3.4 수학 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import numpy as np
arr = np.array([1, 4, 9, 16])
print(np.sqrt(arr)) # [1. 2. 3. 4.] 제곱근
print(np.exp(arr)) # 지수 함수
print(np.log(arr)) # 자연로그
# 삼각함수
angles = np.array([0, np.pi/2, np.pi])
print(np.sin(angles)) # [0. 1. 0.]
# 반올림
arr2 = np.array([1.2, 2.5, 3.7])
print(np.round(arr2)) # [1. 2. 4.]
print(np.floor(arr2)) # [1. 2. 3.] 내림
print(np.ceil(arr2)) # [2. 3. 4.] 올림
🎯 학습 목표 4: 배열의 속성과 형태 다루기
4.1 배열 속성 확인하기
1
2
3
4
5
6
7
8
9
import numpy as np
arr = np.array([[1, 2, 3],
[4, 5, 6]])
print(arr.shape) # (2, 3) - 형태 (2행 3열)
print(arr.ndim) # 2 - 차원 수
print(arr.size) # 6 - 총 원소 개수
print(arr.dtype) # int64 - 데이터 타입
4.2 배열 형태 바꾸기
💡 reshape를 비유하면?
12개의 레고 블록이 일렬로 있다고 상상해 보세요:
1 [□□□□□□□□□□□□] ← 1차원 (12,)이걸 3행 4열로 재배치하면:
1 2 3 [□□□□] [□□□□] ← 2차원 (3, 4) [□□□□]레고 블록 개수는 그대로! 배치만 바뀌는 거예요. 그래서
reshape(3, 4)가 가능하려면 3 × 4 = 12개여야 해요!
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
arr = np.arange(12) # [0, 1, 2, ..., 11]
# reshape: 형태 변경
arr2d = arr.reshape(3, 4) # 3행 4열로
print(arr2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
arr3d = arr.reshape(2, 2, 3) # 2x2x3으로
print(arr3d.shape) # (2, 2, 3)
# -1 사용: 자동 계산
arr_auto = arr.reshape(4, -1) # 4행, 열은 자동
print(arr_auto.shape) # (4, 3)
# flatten: 1차원으로 펼치기
flat = arr2d.flatten()
print(flat) # [ 0 1 2 3 4 5 6 7 8 9 10 11]
4.3 데이터 타입 변환
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
# 정수 배열
arr_int = np.array([1, 2, 3])
print(arr_int.dtype) # int64
# 실수로 변환
arr_float = arr_int.astype(float)
print(arr_float) # [1. 2. 3.]
print(arr_float.dtype) # float64
# 문자열로 변환
arr_str = arr_int.astype(str)
print(arr_str) # ['1' '2' '3']
4.4 배열 합치기
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
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
# 1차원 합치기
print(np.concatenate([a, b])) # [1 2 3 4 5 6]
# 2차원 합치기
c = np.array([[1, 2], [3, 4]])
d = np.array([[5, 6], [7, 8]])
# 세로로 합치기 (axis=0)
print(np.vstack([c, d]))
# [[1 2]
# [3 4]
# [5 6]
# [7 8]]
# 가로로 합치기 (axis=1)
print(np.hstack([c, d]))
# [[1 2 5 6]
# [3 4 7 8]]
💡 실전 팁 & 주의사항
✅ 좋은 습관
1
2
3
4
5
6
7
8
9
10
11
12
import numpy as np
# 1. 항상 np로 임포트
import numpy as np # ✅ 권장
# import numpy # ❌ 비권장
# 2. 배열 생성 시 dtype 명시
arr = np.array([1, 2, 3], dtype=np.float64)
# 3. 복사본 만들기 (원본 보호)
original = np.array([1, 2, 3])
copy = original.copy() # ✅ 독립적인 복사본
⚠️ 주의사항
💡 뷰(view) vs 복사(copy), 왜 중요할까?
비유:
- 뷰(view) = 같은 책을 보는 두 사람 (한 명이 낙서하면 다른 사람도 보임!)
- 복사(copy) = 책을 복사해서 각자 갖기 (한 명이 낙서해도 원본은 깨끗!)
NumPy는 메모리 절약을 위해 기본적으로 뷰를 사용해요. 원본을 보호하고 싶으면 반드시
.copy()사용!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
# 뷰(view) vs 복사(copy)
arr = np.array([1, 2, 3, 4, 5])
# 슬라이싱은 뷰를 반환 (원본 공유!)
view = arr[1:4]
view[0] = 100
print(arr) # [ 1 100 3 4 5] ← 원본도 변경됨! 😱
# 복사본을 원하면 .copy() 사용
arr = np.array([1, 2, 3, 4, 5])
copy = arr[1:4].copy()
copy[0] = 100
print(arr) # [1 2 3 4 5] ← 원본 유지! 😊
⚠️ 이 실수 자주 해요! 데이터 분석할 때 원본 데이터를 실수로 바꾸면 큰일! 원본을 건드리고 싶지 않으면 항상
.copy()습관화하세요.
🧪 연습 문제
문제 1: 성적 분석하기
학생 5명의 국어, 영어, 수학 점수가 있습니다. 각 과목별 평균과 학생별 총점을 구하세요.
1
2
3
4
5
6
7
8
scores = np.array([
[85, 90, 88], # 학생1
[78, 82, 80], # 학생2
[92, 88, 95], # 학생3
[65, 70, 72], # 학생4
[88, 85, 90] # 학생5
])
# 과목별 평균? 학생별 총점?
💡 힌트 (단계별 접근)
단계 1: 데이터 구조 이해하기
- 행(가로) = 학생 (5명)
- 열(세로) = 과목 (국어, 영어, 수학)
단계 2: axis 방향 결정하기
- 과목별 평균 = 같은 열의 값들을 평균 →
axis=0(열 방향으로 계산) - 학생별 총점 = 같은 행의 값들을 합계 →
axis=1(행 방향으로 계산)
단계 3: 함수 적용하기
np.mean(scores, axis=0)→ 열(과목)별 평균np.sum(scores, axis=1)→ 행(학생)별 합계
axis 다시 한번!
1
2
3
4
5
국어 영어 수학
학생1 [[ 85 90 88 ]] → axis=1: 행 방향 계산 (학생별)
학생2 [[ 78 82 80 ]]
↓ ↓ ↓
axis=0: 열 방향 계산 (과목별)
✅ 정답 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import numpy as np
scores = np.array([
[85, 90, 88],
[78, 82, 80],
[92, 88, 95],
[65, 70, 72],
[88, 85, 90]
])
# 과목별 평균 (axis=0: 열 방향)
subject_avg = np.mean(scores, axis=0)
print(f"국어 평균: {subject_avg[0]:.1f}")
print(f"영어 평균: {subject_avg[1]:.1f}")
print(f"수학 평균: {subject_avg[2]:.1f}")
# 학생별 총점 (axis=1: 행 방향)
student_total = np.sum(scores, axis=1)
for i, total in enumerate(student_total, 1):
print(f"학생{i} 총점: {total}")
문제 2: 조건 필터링
1부터 100까지의 숫자 중에서 3의 배수이면서 5의 배수가 아닌 숫자들의 합을 구하세요.
💡 힌트 (단계별 접근)
단계 1: 1~100 숫자 배열 만들기
np.arange(1, 101)사용 (101은 포함 안 됨!)
단계 2: 조건 나누기
- 조건 A: 3의 배수 →
arr % 3 == 0 - 조건 B: 5의 배수가 아님 →
arr % 5 != 0
단계 3: 조건 결합하기
- A AND B →
&사용 - 중요: 각 조건을 괄호로 감싸기!
(arr % 3 == 0) & (arr % 5 != 0)
단계 4: 필터링
arr[조건]형태로 조건에 맞는 숫자만 추출
단계 5: 합계 구하기
np.sum(필터링된_배열)
체크포인트: 3의 배수이면서 5의 배수도 되는 숫자(15, 30, 45, …)는 제외되어야 해요!
✅ 정답 코드
1
2
3
4
5
6
7
8
9
10
11
import numpy as np
arr = np.arange(1, 101)
# 조건: 3의 배수이면서 5의 배수가 아닌 것
condition = (arr % 3 == 0) & (arr % 5 != 0)
# 조건에 맞는 숫자들
filtered = arr[condition]
print(f"해당 숫자들: {filtered}")
print(f"합계: {np.sum(filtered)}")
📝 오늘 배운 내용 정리
- NumPy = 빠른 숫자 계산 라이브러리 (Python 리스트보다 수십 배 빠름)
- 배열 생성:
np.array(),np.zeros(),np.ones(),np.arange(),np.linspace() - 벡터 연산: for문 없이 배열 전체에 연산 적용
- 집계 함수:
sum(),mean(),std(),min(),max() - 배열 형태:
shape,reshape(),flatten()
🔗 관련 자료
📚 이전 학습
Day 60: 미니 프로젝트: 뉴스 스크래퍼 ⭐⭐⭐
어제는 Phase 6 마무리로 웹 스크래핑 프로젝트를 완성했어요!
📚 다음 학습
Day 62: NumPy 활용 ⭐⭐
내일은 배열 인덱싱, 슬라이싱, 브로드캐스팅 등 NumPy 심화 내용을 배워요!
“늦었다고 생각할 때가 가장 빠른 때입니다. 오늘도 한 걸음 성장했어요!” 🚀
Day 61/100 Phase 7: 데이터 분석 기초 #100DaysOfPython
