학습 순서 10번. 데이터 분석 도구의 첫 챕터입니다.
⚠️ 이 저장소에서 처음으로 설치가 필요합니다.step01~step09는 파이썬만 있으면 됐지만 여기부터는 외부 라이브러리를 씁니다.
python3 -m venv .venv # 가상환경 (프로젝트마다 따로 두는 것이 표준)
source .venv/bin/activate # 윈도우는 .venv\Scripts\activate
pip install -r requirements.txtrequirements.txt 는 저장소 루트에 있습니다. 가상환경을 쓰는 이유는 프로젝트마다 필요한 버전이 다르기 때문입니다. 시스템 파이썬에 전부 설치하면 나중에 버전이 충돌합니다.
step05 에서 배운 리스트로도 숫자를 담을 수 있습니다. 그런데 왜 배열이 따로 필요할까요.
[1, 2, 3, 4] * 2 # [1, 2, 3, 4, 1, 2, 3, 4] ← 이어 붙이기
np.array([1,2,3,4]) * 2 # [2, 4, 6, 8] ← 각 원소에 곱하기리스트는 "값을 담는 상자"이고 배열은 "계산의 대상"입니다. 100만 개 숫자에 2를 곱할 때 리스트는 for 를 돌아야 하지만 배열은 한 줄입니다. 그리고 훨씬 빠릅니다 — vectorEx.py 를 실행하면 실측이 나옵니다 (이 환경에서 약 19배).
pandas의 DataFrame 도 내부는 NumPy 배열입니다. 여기를 이해하면 pandas가 훨씬 쉬워집니다.
ndarray를 만들고shape·dtype·ndim으로 구조를 읽는다- 리스트와 결정적으로 다른 두 가지 — 같은 타입만 담는다, 연산이 원소별로 적용된다
[행, 열]로 2차원을 한 번에 자른다- 불리언 인덱싱으로 조건에 맞는 값만 고른다
axis를 이해한다 (넘파이에서 가장 헷갈리는 개념)reshape·전치·합치기로 모양을 바꾼다
| 파일 | 내용 |
|---|---|
arrayEx1.py |
★ 배열 만들기, shape/dtype, 리스트와의 차이 |
arrayEx2.py |
★ 인덱싱·슬라이싱, 불리언 인덱싱, where, 뷰 vs 복사 |
vectorEx.py |
★ 벡터 연산, 브로드캐스팅, for 문과 속도 비교 |
statEx.py |
★★ 통계 함수와 axis |
reshapeEx.py |
reshape·flatten·전치·vstack/hstack |
test1.py |
연습문제 — 성적 행렬 집계, 표준화 |
python3 step10_numpy/arrayEx1.py전부 입력 없이 실행됩니다.
['홍길동', 23, 175.5, True] # 리스트 — 아무거나 섞어 담는다
np.array([1, 2.5, 3]) # → [1.0 2.5 3.0] 전부 float 로 통일
np.array([1, 2, '3']) # → ['1' '2' '3'] 전부 문자열로 통일!조용히 통일해 버립니다. 숫자 배열에 문자열이 하나 섞이면 전체가 문자열이 되어 계산이 안 됩니다. step08 에서 CSV를 읽으면 전부 문자열이었던 것과 이어지는 이야기입니다 — 그래서 dtype 확인이 중요합니다.
같은 타입만 담는 덕분에 원소가 같은 크기로 메모리에 나란히 놓이고, 그게 속도의 근거입니다.
b[1][2] # 리스트 방식도 되지만
b[1, 2] # 이게 넘파이 방식
b[:, 1] # 1열 전체 — 리스트로는 for 를 돌려야 했다
b[0:2, 1:3] # 부분 블록b[:, 1](열 전체 뽑기)이 리스트 대비 가장 큰 편의입니다. step05 의 중첩 리스트에서 열을 뽑으려면 [row[1] for row in table] 이 필요했습니다.
scores[scores >= 60] # 조건에 맞는 값만
(scores >= 60).sum() # 개수 (True 는 1로 세어진다)
(scores >= 60).mean() # 비율
scores[(scores >= 60) & (scores < 90)] # 여러 조건and/or 가 아니라 &/| 입니다. 그리고 각 조건을 괄호로 묶어야 합니다. & 가 비교 연산자보다 우선순위가 높아서 괄호가 없으면 엉뚱하게 묶입니다.
pandas의 필터링이 정확히 이 문법이라, 여기서 익혀 두면 그대로 씁니다.
d = np.array([1, 2, 3, 4, 5])
view = d[1:4] # '뷰' — 원본과 메모리를 공유한다
view[0] = 999
print(d) # [1 999 3 4 5] ← 원본이 바뀐다!리스트와 반대입니다. step05 에서 c[:] 는 복사였지만 넘파이 슬라이싱은 뷰입니다. 원본을 지키려면 .copy() 를 명시해야 합니다.
큰 배열을 자를 때마다 복사하면 메모리가 감당이 안 되기 때문에 이렇게 설계됐습니다. 편의가 아니라 성능을 위한 선택입니다.
가장 많이 틀리는 부분입니다.
국어 영어 수학
홍길동 85 73 68
김길동 90 88 95
최길동 55 62 48
↓ ↓ ↓
axis=0 (행을 따라 아래로 접는다) → 과목별 결과
홍길동 85 73 68 → axis=1 (열을 따라 옆으로 접는다) → 학생별 결과
table.mean(axis=0) # 과목별 평균 — 학생들이 뭉개진다
table.mean(axis=1) # 학생별 평균 — 과목들이 뭉개진다"지정한 축이 사라진다" 고 외우면 편합니다. shape 로 확인할 수 있습니다.
table.shape # (3, 3)
table.sum(axis=0).shape # (3,) ← 0번 축(행)이 사라졌다방향이 헷갈릴 때는 statEx.py 처럼 for 문 결과와 대조해 보세요. 확실합니다.
a + 100 # 모든 원소에 100
m + np.array([10, 20, 30]) # 행마다 같은 열에 더한다모양이 다른 배열끼리도 규칙에 맞으면 자동으로 늘려서 계산합니다. test1.py 의 표준화가 이걸 씁니다.
z = (table - table.mean(axis=0)) / table.std(axis=0)
# (4,3) - (3,) / (3,)(3,) 짜리가 4개 행에 각각 적용됩니다. for 문이 하나도 없습니다.
| 증상 | 원인 |
|---|---|
| 계산이 안 되고 문자열이 이어붙음 | dtype 이 문자열. astype(float) 필요 |
| 원본이 몰래 바뀜 | 슬라이싱은 뷰. .copy() 필요 |
ValueError: The truth value of an array... |
and/or 를 씀 → &/` |
| 결과가 행/열 반대로 나옴 | axis 를 반대로 지정 |
cannot reshape array of size 12 into shape (5,3) |
원소 개수가 안 맞음 |
astype(int) 결과가 예상과 다름 |
반올림이 아니라 버림 (1.7 → 1) |
arrayEx1.py에서np.array([1, 2, '3'])의dtype을 확인하고, 이 배열에+ 1을 해 보세요. 어떤 에러가 나나요?arrayEx2.py의 뷰 예제에서.copy()를 넣고 빼며 원본이 바뀌는지 확인하세요.statEx.py의table에 학생 두 명을 더 추가하고,axis=0/axis=1결과의shape가 어떻게 변하는지 예측한 뒤 확인하세요.test1.py의 표준화 결과에서 가장 잘한 과목(z값이 가장 큰 과목)을 학생별로 찾으세요. (힌트:argmax(axis=1))vectorEx.py의 속도 비교에서n을 10배로 늘리면 배수가 어떻게 변하나요?step04_loop/test1.py의 "1~100 중 3의 배수 개수"를 넘파이 한 줄로 구하세요.np.random으로 5×3 난수 배열을 만들고, 각 행에서 최댓값을 뺀 값을 구하세요. (step03의random모듈과 무엇이 다른지도 보세요)