포스트

[Python 100일 챌린지] Day 61 - NumPy 기초

[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 목표: 데이터를 불러오고 → 처리하고 → 분석하고 → 시각화하는 전 과정을 배워요!

🎯 오늘의 학습 목표

📚 사전 지식


🎯 학습 목표 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
7
axis=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)}")

📝 오늘 배운 내용 정리

  1. NumPy = 빠른 숫자 계산 라이브러리 (Python 리스트보다 수십 배 빠름)
  2. 배열 생성: np.array(), np.zeros(), np.ones(), np.arange(), np.linspace()
  3. 벡터 연산: for문 없이 배열 전체에 연산 적용
  4. 집계 함수: sum(), mean(), std(), min(), max()
  5. 배열 형태: shape, reshape(), flatten()

🔗 관련 자료


📚 이전 학습

Day 60: 미니 프로젝트: 뉴스 스크래퍼 ⭐⭐⭐

어제는 Phase 6 마무리로 웹 스크래핑 프로젝트를 완성했어요!

📚 다음 학습

Day 62: NumPy 활용 ⭐⭐

내일은 배열 인덱싱, 슬라이싱, 브로드캐스팅 등 NumPy 심화 내용을 배워요!


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

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