# Part 5: AI 핵심 라이브러리: NumPy, Pandas, Matplotlib

**⬅️ 이전 시간: [Part 4: 객체 지향 프로그래밍(OOP)](../04_object_oriented_programming/part_4_object_oriented_programming.md)**
**➡️ 다음 시간: [Part 5.5: NumPy로 배우는 선형대수](../05.5_linear_algebra_with_numpy/part_5.5_linear_algebra_with_numpy.md)**

---

## 1. 학습 목표 (Learning Objectives)

이번 파트가 끝나면, 여러분은 다음을 할 수 있게 됩니다.

- NumPy `ndarray`를 생성하고, 벡터화 연산과 브로드캐스팅을 활용하여 고성능 수치 계산을 수행할 수 있습니다.
- Pandas `DataFrame`을 사용하여 CSV, Excel 등의 데이터를 불러오고, `loc`/`iloc`으로 원하는 데이터를 정밀하게 선택하고 필터링할 수 있습니다.
- `groupby`를 사용하여 데이터를 특정 기준으로 나누고, 각 그룹에 대한 집계(평균, 합계 등)를 효율적으로 계산할 수 있습니다.
- 여러 `DataFrame`을 `merge`나 `concat`을 사용해 비즈니스 목적에 맞게 결합할 수 있습니다.
- 데이터 분석 프로젝트에서 NumPy와 Pandas가 어떻게 상호작용하며 문제를 해결하는지 큰 그림을 이해합니다.

## 2. 핵심 요약 (Key Summary)
이 파트에서는 데이터 분석과 머신러닝의 필수 라이브러리인 NumPy와 Pandas를 학습합니다. NumPy를 사용하여 고성능 다차원 배열 `ndarray`를 다루고, 벡터화 연산과 브로드캐스팅을 통해 효율적인 수치 계산을 수행합니다. Pandas의 `DataFrame`을 이용하여 표 형식의 데이터를 읽고, `loc`/`iloc`으로 데이터를 정밀하게 선택하며, `groupby`로 데이터를 집계하고, `merge`/`concat`으로 여러 데이터를 결합하는 등 데이터 전처리의 핵심 기술들을 익힙니다.

- **핵심 키워드**: `NumPy`, `ndarray`, `Pandas`, `DataFrame`, `Series`, `Vectorization`, `Broadcasting`, `Indexing`, `Filtering`, `loc`, `iloc`, `groupby`, `merge`, `concat`, `pivot_table`

## 3. 도입: 데이터 과학자의 새로운 연장통 (Introduction)

지난 4주간 우리는 파이썬이라는 언어와 객체 지향 설계 능력을 길렀습니다. 이제 이 강력한 언어를 날개 삼아, 데이터의 세계를 본격적으로 탐험할 시간입니다. 이번 주부터는 데이터 분석과 머신러닝 프로젝트의 실질적인 '주연 배우'들인 NumPy, Pandas 사용법을 깊이 있게 학습합니다.

> [!TIP]
> 본 파트의 모든 예제 코드는 `../../source_code/part_5_ai_core_libraries.py` 파일에서 직접 실행해볼 수 있습니다.

### AI 프로젝트의 큰 그림: 라이브러리 관계도

각 라이브러리를 배우기 전에, 이들이 프로젝트에서 어떻게 함께 사용되는지 전체적인 그림을 이해하는 것이 중요합니다.

```mermaid
graph TD
    subgraph "데이터 수집 및 전처리"
        A["<b>Pandas (DataFrame)</b><br/>- CSV/Excel/DB에서 데이터 로드<br/>- 결측치 처리, 필터링, 그룹화, 병합"]
    end

    subgraph "고성능 수치 연산"
        B["<b>NumPy (ndarray)</b><br/>- DataFrame에서 변환<br/>- 벡터/행렬 연산, 수학 함수"]
    end

    subgraph "머신러닝 모델링 & 평가"
        C["<b>Scikit-learn (Estimator)</b><br/>- 모델 학습(fit), 예측(predict)<br/>- 모델 평가, 하이퍼파라미터 튜닝"]
    end

    subgraph "데이터 시각화"
        D["<b>Matplotlib & Seaborn</b><br/>(Figure, Axes)<br/>- 데이터 탐색(EDA)<br/>- 모델 결과 시각화"]
    end

    A -->|"데이터 정제"| A
    A -->|"df.values, df['column']"| B
    B -->|"모델 입력"| C
    A -->|"df.plot()"| D
    B -->|"plt.plot(array)"| D
    C -->|"결과 분석"| D
```

- **시작은 Pandas**: 대부분의 프로젝트는 Pandas `DataFrame`으로 데이터를 불러와 정제하는 것에서 시작합니다.
- **계산은 NumPy로**: 정제된 데이터는 고성능 수치 계산을 위해 NumPy 배열(`ndarray`)로 변환되어 머신러닝 모델의 입력으로 준비됩니다.
- **모델링은 Scikit-learn으로**: NumPy 배열을 사용하여 Scikit-learn으로 모델을 학습하고 예측합니다.
- **모든 과정은 Matplotlib으로 시각화**: 데이터의 특징을 파악(EDA)하거나 모델의 성능을 분석하는 데 사용됩니다.

이 흐름을 염두에 두면, 지식이 파편적으로 남지 않고 하나의 큰 그림으로 연결될 것입니다.

---

## 4. NumPy: 고성능 수치 계산의 초석

> **🎯 1일차 목표:** NumPy를 사용하여 고성능 과학 계산 및 다차원 배열을 다룹니다.

**NumPy(Numerical Python)**는 파이썬에서 과학 계산을 위한 가장 기본적인 패키지입니다. `ndarray`라는 효율적인 다차원 배열 객체를 중심으로, 벡터, 행렬 등 고성능 수치 연산을 위한 방대한 함수들을 제공합니다.

> **💡 비유: NumPy 배열은 '계란판', 파이썬 리스트는 '장바구니'**
> - **파이썬 리스트 ('장바구니')**: 뭐든 담을 수 있어 유연하지만, 내용물이 제각각이라 전체를 한 번에 계산하기는 어렵고 느립니다.
> - **NumPy 배열 ('계란판')**: 오직 한 종류(숫자)만 담을 수 있는 '계란판'과 같습니다. 모든 칸이 균일하고 규칙적이어서, '모든 계란에 도장 찍기' 같은 작업을 기계로 한 번에 처리할 수 있습니다. 이 **균일성** 덕분에 NumPy는 엄청나게 빠른 속도로 대규모 숫자 계산을 할 수 있습니다.

### 4-1. `ndarray` 생성 및 연산

```python
import numpy as np

# 파이썬 리스트로부터 배열 생성
py_list = [[1, 2, 3], [4, 5, 6]]
np_array = np.array(py_list)
print("Shape:", np_array.shape) # (2, 3)

# 벡터화(Vectorization) 연산: for문 없이 모든 요소에 대해 한 번에 연산
result = np_array * 2 + 1
print("배열 연산 결과:\n", result)

# 브로드캐스팅(Broadcasting): 서로 다른 크기의 배열 간 연산
matrix = np.array([[1, 2, 3], [4, 5, 6]]) # shape: (2, 3)
vector = np.array([10, 20, 30])           # shape: (3,)
result_broadcast = matrix + vector
print("브로드캐스팅 결과:\n", result_broadcast)
```

### 4-2. 인덱싱과 슬라이싱

```python
arr = np.arange(10) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# 인덱싱 및 슬라이싱
print("3번째 원소:", arr[2])
print("3~5번째 원소:", arr[2:5])

# 조건(Boolean) 인덱싱: 특정 조건을 만족하는 원소만 추출하는 강력한 기능
print("3보다 큰 원소:", arr[arr > 3])
print("짝수만 추출:", arr[arr % 2 == 0])
```

---

## 5. Pandas: '슈퍼 엑셀' DataFrame 다루기

> **🎯 2-4일차 목표:** Pandas를 사용하여 표 형식의 데이터를 자유자재로 읽고, 선택하고, 필터링하고, 그룹화하고, 병합합니다.

**Pandas**는 `DataFrame`(2차원 테이블)과 `Series`(1차원 배열) 자료구조를 사용하여, 엑셀이나 DB 테이블처럼 데이터를 직관적으로 다루게 해주는 라이브러리입니다. 데이터 전처리에 없어서는 안 될 필수 도구입니다.

### 5-1. `Series`, `DataFrame` 생성 및 탐색

```python
import pandas as pd

# DataFrame 생성
data = {'제품': ['사과', '바나나', '체리', '딸기'],
        '가격': [1000, 500, 2000, 1500],
        '재고': [10, 20, 15, 5]}
df = pd.DataFrame(data)
print("DataFrame:\n", df)

# 기본 탐색
print(df.head())      # 맨 위 5개 행
df.info()             # 각 열의 정보와 결측치 확인
print(df.describe())  # 숫자형 열의 기술 통계량
```

### 5-2. 데이터 선택과 필터링 (`loc`, `iloc`)

> - **`.loc` (Label-based)**: 학생의 **'이름'**으로 자리를 찾는 방식 (`df.loc[행_이름, 열_이름]`)
> - **`.iloc` (Integer-based)**: 학생이 앉아있는 **'자리 번호'**로 찾는 방식 (`df.iloc[행_번호, 열_번호]`)

```python
# loc: 1번 인덱스 행의 '제품', '재고' 열
print(df.loc[1, ['제품', '재고']])

# iloc: 0번 행의 0, 2번 열
print(df.iloc[0, [0, 2]])

# 조건 필터링: 가격이 1000원 이상인 제품
print("\n가격 1000원 이상:\n", df[df['가격'] >= 1000])

# 복합 조건 필터링: 가격 1000원 미만이고(and) 재고 10개 미만인 제품
# & (and), | (or), ~ (not)
print("\n가격 1000원 미만 AND 재고 10개 미만:\n", df[(df['가격'] < 1000) & (df['재고'] < 10)])
```
> 🚫 **`SettingWithCopyWarning` 주의!**: 값을 변경할 때는 `df.loc[행조건, 열이름] = 값` 형태로 한 번에 접근해야 의도치 않은 오류를 막을 수 있습니다.

### 5-3. 데이터 분해와 조립 (`groupby`)

> **💡 비유: `groupby`는 '반별 성적 평균 계산' (Split-Apply-Combine)**
> 1.  **Split (나누기)**: 전체 학생 명단을 '1반', '2반'으로 나눕니다 (`df.groupby('반')`).
> 2.  **Apply (적용하기)**: 각 반별로 '평균 점수 계산'을 수행합니다 (`.agg('mean')`).
> 3.  **Combine (합치기)**: 각 반의 평균 점수를 모아 하나의 최종 성적표로 합칩니다.

```python
data = {'팀': ['A', 'B', 'A', 'B', 'A'], '선수': ['손흥민', '김민재', '황희찬', '이강인', '조규성'], '득점': [12, 1, 8, 5, 10]}
df_player = pd.DataFrame(data)

# '팀' 별로 그룹화하여 득점의 합계, 평균, 선수 수 계산
team_agg = df_player.groupby('팀')['득점'].agg(['sum', 'mean', 'count'])
print(team_agg)
```

### 5-4. 데이터 병합과 연결 (`merge`, `concat`)

> - **`merge`**: '학생 명단'과 '도서관 대출 기록'을 '학번'이라는 공통 열을 기준으로 합치는 것 (SQL의 JOIN과 유사).
> - **`concat`**: '1반 학생 명단' 아래에 '2반 학생 명단'을 그대로 이어 붙이는 것.

```python
df1 = pd.DataFrame({'id': ['A01', 'A02', 'A03'], '이름': ['김파이', '이판다', '박넘파']})
df2 = pd.DataFrame({'id': ['A01', 'A02', 'A04'], '과목': ['수학', '영어', '과학']})

# merge: 공통 'id'를 기준으로 병합 (left: df1의 모든 id 유지)
left_join = pd.merge(df1, df2, on='id', how='left')
print("Left Join:\n", left_join)

df3 = pd.DataFrame({'이름': ['최사이'], '나이': [21]})
# concat: df1 아래에 df3을 이어 붙이기
concatenated_df = pd.concat([df1, df3], ignore_index=True)
print("\nConcatenated DataFrame:\n", concatenated_df)
```

---

## 6. 연습 문제 (Exercises)

> **🎯 5일차 목표:** NumPy와 Pandas의 기술을 종합하여 실제적인 데이터 문제를 해결합니다.

아래는 어느 온라인 상점의 일자별 판매 데이터입니다. 이 데이터를 사용하여 아래의 질문에 답하는 코드를 작성해보세요.

```python
import pandas as pd
import numpy as np

data = {
    'Date': ['2024-01-01', '2024-01-01', '2024-01-02', '2024-01-02', '2024-01-03', '2024-01-03'],
    'Category': ['Electronics', 'Books', 'Electronics', 'Books', 'Electronics', 'Books'],
    'Product': ['Laptop', 'Python Intro', 'Mouse', 'AI for All', 'Keyboard', 'Deep Learning'],
    'Price_USD': [1200, 45, 25, 55, 75, 60],
    'Quantity': [5, 10, 20, 8, 15, 12]
}
sales_df = pd.DataFrame(data)
```

### 문제 1: 매출 분석
1.  `Price_KRW` (원화 가격) 열을 새로 만드세요. 환율은 1 USD = 1300 KRW로 가정하고, NumPy를 이용해 한 번에 계산하세요.
2.  `Total_Sales_KRW` (총 판매액) 열을 만드세요. (`Price_KRW` * `Quantity`)
3.  카테고리(`Category`)별 총 판매액 합계를 계산하세요 (`groupby` 사용).

### 문제 2: 재고 관리
1.  현재 `sales_df`에는 없는 '재고(Stock)' 정보를 담은 아래 `stock_df`를 만드세요.
2.  `sales_df`와 `stock_df`를 상품명(`Product`)을 기준으로 합쳐(`merge`), 판매된 상품의 현재 재고를 함께 볼 수 있는 `report_df`를 생성하세요.

```python
stock_data = {
    'Product': ['Laptop', 'Python Intro', 'Mouse', 'AI for All', 'Keyboard', 'Deep Learning', 'Monitor'],
    'Stock': [50, 100, 150, 80, 120, 90, 40]
}
stock_df = pd.DataFrame(stock_data)
```

---

## 7. 트러블슈팅 (Troubleshooting)

- **`ValueError: operands could not be broadcast together with shapes (...) (...)`가 발생했나요?**
  - NumPy 배열 간의 연산에서 발생하며, 브로드캐스팅 규칙에 맞지 않는다는 의미입니다. 두 배열의 차원 수나 각 차원의 크기가 호환되는지 확인하세요. 예를 들어, `(3, 4)` 배열과 `(4,)` 벡터는 더할 수 있지만, `(3, 4)` 배열과 `(3,)` 벡터는 기본적으로 더할 수 없습니다.
- **`KeyError: '...'`가 발생하며 특정 열을 찾을 수 없나요?**
  - DataFrame에 해당 이름의 열이 없는 경우입니다. `df.columns`를 출력하여 실제 열 이름에 오타나 불필요한 공백이 있는지 확인해보세요.
- **`SettingWithCopyWarning` 경고가 계속 나타나나요?**
  - 이 경고는 Pandas가 원본을 수정하려는 것인지, 사본을 수정하려는 것인지 헷갈릴 때 발생합니다. `df[df['A'] > 0]['B'] = 1` 과 같이 여러 단계로 접근하여 값을 할당하는 대신, `df.loc[df['A'] > 0, 'B'] = 1` 처럼 `.loc`를 사용하여 한 번에 명확하게 위치를 지정하여 값을 할당하면 경고를 피할 수 있습니다.
- **`merge`를 했는데 데이터가 예상보다 적거나 많게 나오나요?**
  - `how` 매개변수(`'left'`, `'right'`, `'inner'`, `'outer'`)의 동작을 다시 한번 확인해보세요. 기본값인 `'inner'`는 양쪽 DataFrame에 모두 존재하는 키만 남기므로 데이터가 줄어들 수 있습니다. 또한, 병합하려는 키에 중복된 값이 있다면 결과 행이 예상보다 많아질 수 있습니다.

더 자세한 문제 해결 가이드나 다른 동료들이 겪은 문제에 대한 해결책이 궁금하다면, 아래 문서를 참고해주세요.

- **➡️ [Geumdo-Docs: TROUBLESHOOTING.md](../../../TROUBLESHOOTING.md)**

---

## 🚀 캡스톤 미니 프로젝트: 타이타닉 생존자 데이터 분석 (EDA)

이번 장에서 배운 NumPy와 Pandas는 모든 AI 프로젝트의 출발점인 '탐색적 데이터 분석(Exploratory Data Analysis, EDA)'의 핵심 도구입니다. 이번 미니 프로젝트에서는 데이터 과학 입문용으로 가장 유명한 '타이타닉 호 생존자 데이터'를 사용하여, 실제 데이터 분석 프로젝트의 축소판을 경험해 봅니다.

데이터를 불러오고, 정제하고, 새로운 정보를 파생시키고, 그룹별로 분석하여 의미 있는 인사이트를 발견하고, 시각화하는 전체 과정을 직접 수행하게 됩니다.

### 프로젝트 목표

주어진 타이타닉 승객 데이터를 Pandas DataFrame으로 분석하여, "어떤 승객이 더 많이 살아남았을까?"라는 질문에 대한 답을 데이터에 기반하여 찾아내고, 분석 결과를 시각화하여 보고합니다.

**샘플 데이터 (CSV 형식):**
```csv
PassengerId,Survived,Pclass,Name,Sex,Age,SibSp,Parch,Fare,Embarked
1,0,3,"Braund, Mr. Owen Harris",male,22,1,0,7.25,S
2,1,1,"Cumings, Mrs. John Bradley (Florence Briggs Thayer)",female,38,1,0,71.2833,C
3,1,3,"Heikkinen, Miss. Laina",female,26,0,0,7.925,S
4,1,1,"Futrelle, Mrs. Jacques Heath (Lily May Peel)",female,35,1,0,53.1,S
5,0,3,"Allen, Mr. William Henry",male,35,0,0,8.05,S
6,0,3,"Moran, Mr. James",male,,0,0,8.4583,Q
7,0,1,"McCarthy, Mr. Timothy J",male,54,0,0,51.8625,S
8,0,3,"Palsson, Master. Gosta Leonard",male,2,3,1,21.075,S
9,1,3,"Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg)",female,27,0,2,11.1333,S
10,1,2,"Nasser, Mrs. Nicholas (Adele Achem)",female,14,1,0,30.0708,C
```

**최종 분석 리포트 (예시):**
```
*** Titanic Survival Analysis Report ***

[Basic Statistics]
Total Passengers: 10
Survival Rate: 40.0%

[Survival Rate by Gender]
Sex
female    1.000000
male      0.000000
Name: Survived, dtype: float64

[Survival Rate by Pclass]
Pclass
1    0.500000
2    1.000000
3    0.400000
Name: Survived, dtype: float64

(여기에 Matplotlib/Seaborn을 이용한 시각화 차트가 추가될 것입니다)
```

### 단계별 구현 가이드

**1. 데이터 로드 및 기본 탐색**
- 위 CSV 텍스트를 `io.StringIO`를 사용하여 Pandas DataFrame으로 불러오세요. (실제 파일이 없어도 문자열을 파일처럼 읽게 해줍니다)
- `df.info()`, `df.describe()`를 사용해 데이터의 기본 정보를 파악하세요.

```python
import pandas as pd
import numpy as np
import io

csv_data = """PassengerId,Survived,Pclass,Name,Sex,Age,SibSp,Parch,Fare,Embarked
1,0,3,"Braund, Mr. Owen Harris",male,22,1,0,7.25,S
2,1,1,"Cumings, Mrs. John Bradley (Florence Briggs Thayer)",female,38,1,0,71.2833,C
3,1,3,"Heikkinen, Miss. Laina",female,26,0,0,7.925,S
4,1,1,"Futrelle, Mrs. Jacques Heath (Lily May Peel)",female,35,1,0,53.1,S
5,0,3,"Allen, Mr. William Henry",male,35,0,0,8.05,S
6,0,3,"Moran, Mr. James",male,,0,0,8.4583,Q
7,0,1,"McCarthy, Mr. Timothy J",male,54,0,0,51.8625,S
8,0,3,"Palsson, Master. Gosta Leonard",male,2,3,1,21.075,S
9,1,3,"Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg)",female,27,0,2,11.1333,S
10,1,2,"Nasser, Mrs. Nicholas (Adele Achem)",female,14,1,0,30.0708,C
"""

df = pd.read_csv(io.StringIO(csv_data))
```

**2. 데이터 정제 및 피처 엔지니어링**
- `Age` 열의 결측치(NaN)를 전체 승객의 **중앙값(median)**으로 채워 넣으세요.
- `SibSp` (형제/배우자 수)와 `Parch` (부모/자녀 수)를 더하고 1을 추가하여 `FamilySize` (가족 총원)라는 새로운 열을 만드세요.

**3. 그룹 분석 (Group Analysis)**
- `groupby()`를 사용하여 다음을 계산하고 출력하세요.
  - 성별(`Sex`)에 따른 생존율(`Survived`의 평균)
  - 객실 등급(`Pclass`)에 따른 생존율
  - 객실 등급(`Pclass`)별 운임(`Fare`)의 평균
  - `FamilySize`별 생존율

**4. 데이터 시각화 (Matplotlib/Seaborn 소개)**
- 데이터 분석 결과를 직관적으로 이해하기 위해 시각화는 필수입니다.
- `matplotlib`과 `seaborn` 라이브러리를 사용하여, 위에서 분석한 '객실 등급별 생존율'과 '성별 생존율'을 막대그래프로 그려보세요.

```python
# !pip install matplotlib seaborn
import matplotlib.pyplot as plt
import seaborn as sns

# 성별에 따른 생존율 시각화
sns.barplot(x='Sex', y='Survived', data=df)
plt.title('Survival Rate by Gender')
plt.show()

# 객실 등급에 따른 생존율 시각화
sns.barplot(x='Pclass', y='Survived', data=df)
plt.title('Survival Rate by Pclass')
plt.show()
```

이 EDA 과정을 통해 얻은 인사이트("여성의 생존율이 높다", "1등석 승객의 생존율이 높다" 등)는 나중에 머신러닝 모델을 만들 때 어떤 정보를 더 중요하게 고려해야 할지 결정하는 중요한 근거가 됩니다.

---

## 8. 되짚어보기 (Summary)

이번 주차에는 데이터 과학의 양대 산맥인 **NumPy**와 **Pandas**를 마스터했습니다.

- **NumPy '계란판'**: 균일한 데이터(`ndarray`)를 빠르고 효율적으로 계산하는 방법을 배웠습니다.
- **Pandas '슈퍼 엑셀'**: 표 형태의 데이터(`DataFrame`)를 자유자재로 불러오고, 선택/필터링하고, `groupby`로 요약하고, `merge`/`concat`으로 합치는 강력한 데이터 전처리 기술을 익혔습니다.
- **큰 그림**: Pandas로 데이터를 준비하고, NumPy로 계산을 수행하는 데이터 분석의 핵심 흐름을 이해했습니다.

이제 여러분은 대부분의 정형 데이터를 원하는 형태로 가공하고, 기본적인 통계를 파악할 수 있는 강력한 무기를 갖추게 되었습니다.

## 9. 더 깊이 알아보기 (Further Reading)
- [Pandas 공식 10분 완성 튜토리얼](https://pandas.pydata.org/docs/user_guide/10min.html)
- [NumPy 공식 시작하기 가이드](https://numpy.org/doc/stable/user/absolute_beginners.html)
- [Chris Albon's Notes On pandas and NumPy](https://chrisalbon.com/): 특정 상황에 필요한 코드 스니펫을 찾기에 매우 유용한 사이트

---

**➡️ 다음 시간: [Part 5.5: NumPy로 배우는 선형대수](../05.5_linear_algebra_with_numpy/part_5.5_linear_algebra_with_numpy.md)** 