# 3. Pandas Series 기초

## 📚 학습 목표
- Pandas Series의 기본 개념과 구조 이해
- Series 생성 및 조작 방법 습득
- Series 인덱싱과 슬라이싱 활용
- Series 연산과 메서드 사용법 익히기

## 🔍 문제 설명

Pandas Series는 1차원 라벨링된 배열로, 인덱스와 값의 쌍으로 구성됩니다. DataFrame의 기본 구성 요소이며, 데이터 분석에서 매우 중요한 역할을 합니다.

### 실전 활용 사례
- **시계열 데이터**: 주가, 온도, 판매량 등의 시간별 데이터
- **특성 벡터**: 머신러닝에서 단일 특성 데이터
- **통계 분석**: 평균, 분산, 분위수 등의 계산
- **데이터 검증**: 데이터 타입과 범위 확인

## 📝 문제

### 문제 3-1: Series 생성
1. 1부터 10까지의 숫자로 Series를 생성하고 인덱스를 'A'부터 'J'까지 설정하세요.
2. 딕셔너리를 사용하여 Series를 생성하세요.
3. 스칼라 값으로 Series를 생성하세요.

### 문제 3-2: Series 인덱싱과 슬라이싱
4. Series에서 특정 인덱스의 값을 추출하세요.
5. 조건부 필터링을 사용하여 짝수 값만 추출하세요.
6. 슬라이싱을 사용하여 특정 범위의 값을 추출하세요.

### 문제 3-3: Series 연산과 메서드
7. Series의 기본 통계 정보를 확인하세요.
8. Series에 스칼라 연산을 적용하세요.
9. 두 Series 간의 연산을 수행하세요.

## 💡 해답 및 설명

### 해답 3-1: Series 생성

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

# 1. 1부터 10까지의 숫자로 Series 생성하고 인덱스 설정
s1 = pd.Series(np.arange(1, 11), index=list('ABCDEFGHIJ'))
print("Series 1:")
print(s1)
print(f"인덱스: {s1.index}")
print(f"값: {s1.values}")
print(f"데이터 타입: {s1.dtype}")

# 2. 딕셔너리를 사용하여 Series 생성
data_dict = {'서울': 1000, '부산': 800, '대구': 600, '인천': 900, '광주': 500}
s2 = pd.Series(data_dict)
print("\nSeries 2 (딕셔너리로 생성):")
print(s2)

# 3. 스칼라 값으로 Series 생성
scalar_series = pd.Series(5, index=['A', 'B', 'C', 'D', 'E'])
print("\nSeries 3 (스칼라 값):")
print(scalar_series)

# 추가 생성 방법들
print("\n다양한 Series 생성 방법:")
# 리스트로 생성
s4 = pd.Series([1, 2, 3, 4, 5])
print("리스트로 생성:", s4)

# 튜플로 생성
s5 = pd.Series((10, 20, 30, 40, 50))
print("튜플로 생성:", s5)

# NumPy 배열로 생성
s6 = pd.Series(np.random.randn(5), index=['a', 'b', 'c', 'd', 'e'])
print("NumPy 배열로 생성:", s6)
```

### 해답 3-2: Series 인덱싱과 슬라이싱

```python
# 4. Series에서 특정 인덱스의 값을 추출
print("특정 인덱스 값 추출:")
print("s1['A']:", s1['A'])  # 라벨 인덱싱
print("s1[0]:", s1[0])      # 위치 인덱싱
print("s1.iloc[2]:", s1.iloc[2])  # 정수 위치 인덱싱
print("s1.loc['C']:", s1.loc['C'])  # 라벨 인덱싱

# 5. 조건부 필터링
print("\n조건부 필터링:")
even_values = s1[s1 % 2 == 0]
print("짝수 값만:", even_values)

# 5보다 큰 값만
greater_than_5 = s1[s1 > 5]
print("5보다 큰 값:", greater_than_5)

# 6. 슬라이싱
print("\n슬라이싱:")
print("s1['A':'E']:", s1['A':'E'])  # 라벨 슬라이싱 (끝 포함)
print("s1[0:5]:", s1[0:5])          # 위치 슬라이싱 (끝 미포함)
print("s1.iloc[1:4]:", s1.iloc[1:4])  # 정수 위치 슬라이싱
print("s1.loc['B':'F']:", s1.loc['B':'F'])  # 라벨 슬라이싱

# 역순 슬라이싱
print("역순 슬라이싱:", s1[::-1])
```

### 해답 3-3: Series 연산과 메서드

```python
# 7. Series의 기본 통계 정보
print("Series 기본 통계 정보:")
print(f"평균: {s1.mean()}")
print(f"중앙값: {s1.median()}")
print(f"표준편차: {s1.std()}")
print(f"분산: {s1.var()}")
print(f"최솟값: {s1.min()}")
print(f"최댓값: {s1.max()}")
print(f"합계: {s1.sum()}")
print(f"개수: {s1.count()}")

# describe() 메서드로 한 번에 확인
print("\n전체 통계 요약:")
print(s1.describe())

# 8. Series에 스칼라 연산 적용
print("\n스칼라 연산:")
print("원본 Series:", s1)
print("2배:", s1 * 2)
print("제곱:", s1 ** 2)
print("제곱근:", np.sqrt(s1))
print("로그:", np.log(s1))

# 9. 두 Series 간의 연산
s7 = pd.Series([10, 20, 30, 40, 50], index=['A', 'B', 'C', 'D', 'E'])
s8 = pd.Series([1, 2, 3, 4, 5], index=['A', 'B', 'C', 'D', 'E'])

print("\n두 Series 간의 연산:")
print("s7:", s7)
print("s8:", s8)
print("덧셈:", s7 + s8)
print("뺄셈:", s7 - s8)
print("곱셈:", s7 * s8)
print("나눗셈:", s7 / s8)

# 인덱스가 다른 경우
s9 = pd.Series([1, 2, 3], index=['A', 'B', 'F'])
print("\n인덱스가 다른 Series 연산:")
print("s7 + s9:", s7 + s9)  # 공통 인덱스만 연산, 나머지는 NaN
```

## 🔧 주요 Series 메서드와 속성

### 기본 속성
- `series.index`: 인덱스 객체
- `series.values`: 값 배열
- `series.dtype`: 데이터 타입
- `series.shape`: 형태 (튜플)
- `series.size`: 원소 개수

### 통계 메서드
- `series.mean()`: 평균
- `series.median()`: 중앙값
- `series.std()`: 표준편차
- `series.var()`: 분산
- `series.min()`, `series.max()`: 최솟값, 최댓값
- `series.sum()`: 합계
- `series.count()`: 비NaN 값 개수
- `series.describe()`: 통계 요약

### 인덱싱 메서드
- `series.loc[]`: 라벨 인덱싱
- `series.iloc[]`: 정수 위치 인덱싱
- `series.at[]`: 단일 라벨 값
- `series.iat[]`: 단일 위치 값

### 데이터 처리 메서드
- `series.isnull()`: NaN 값 확인
- `series.notnull()`: 비NaN 값 확인
- `series.fillna()`: NaN 값 채우기
- `series.dropna()`: NaN 값 제거
- `series.unique()`: 고유 값
- `series.value_counts()`: 값 빈도

## 📊 추가 연습문제

### 연습문제 1: Series 생성과 조작
```python
# 다음 문제들을 풀어보세요:
# 1. 월별 평균 온도 데이터를 Series로 생성하세요.
#    (1월: -2, 2월: 1, 3월: 7, 4월: 13, 5월: 18, 6월: 23)
# 2. 해당 Series에서 봄철(3~5월) 온도만 추출하세요.
# 3. 연평균 온도를 계산하세요.
```

### 연습문제 2: 조건부 필터링
```python
# 다음 Series를 생성하고 문제를 풀어보세요:
scores = pd.Series([85, 92, 78, 96, 88, 75, 91, 83, 79, 94], 
                   index=['학생1', '학생2', '학생3', '학생4', '학생5', 
                          '학생6', '학생7', '학생8', '학생9', '학생10'])

# 1. 90점 이상인 학생들의 점수를 추출하세요.
# 2. 평균 점수를 계산하세요.
# 3. 표준편차를 계산하세요.
# 4. 최고점과 최저점의 차이를 구하세요.
```

### 연습문제 3: Series 연산
```python
# 두 개의 Series를 생성하고 연산을 수행해보세요:
sales_2022 = pd.Series([100, 120, 90, 150, 80], 
                       index=['1월', '2월', '3월', '4월', '5월'])
sales_2023 = pd.Series([110, 130, 95, 160, 85], 
                       index=['1월', '2월', '3월', '4월', '5월'])

# 1. 2023년 대비 2022년 매출 증가율을 계산하세요.
# 2. 두 해의 평균 매출을 비교하세요.
# 3. 월별 매출 차이를 계산하세요.
```

## ⚠️ 주의사항 및 팁

1. **인덱스 라벨**: Series의 인덱스는 라벨이므로 문자열로 접근할 때는 `loc`를 사용하는 것이 좋습니다.
2. **데이터 타입**: Series는 동일한 데이터 타입을 가져야 하므로, 혼합 타입은 object 타입이 됩니다.
3. **NaN 처리**: 연산 결과가 정의되지 않으면 NaN이 반환됩니다.
4. **인덱스 정렬**: Series 연산 시 인덱스가 자동으로 정렬됩니다.
5. **메모리 효율성**: 큰 데이터셋에서는 적절한 데이터 타입을 선택하는 것이 중요합니다.

## 🎯 실전 활용 예제

### 예제 1: 주식 가격 분석
```python
# 일별 주식 가격 데이터
np.random.seed(42)
dates = pd.date_range('2023-01-01', periods=30, freq='D')
stock_prices = pd.Series(np.random.normal(100, 5, 30).cumsum(), index=dates)

print("주식 가격 데이터:")
print(stock_prices.head())

# 수익률 계산
returns = stock_prices.pct_change()
print("\n일별 수익률:")
print(returns.head())

# 통계 정보
print(f"\n평균 수익률: {returns.mean():.4f}")
print(f"수익률 표준편차: {returns.std():.4f}")
print(f"최고 수익률: {returns.max():.4f}")
print(f"최저 수익률: {returns.min():.4f}")
```

### 예제 2: 온도 데이터 분석
```python
# 월별 평균 온도 데이터
temperatures = pd.Series([-2, 1, 7, 13, 18, 23, 26, 25, 20, 13, 6, 0],
                        index=['1월', '2월', '3월', '4월', '5월', '6월',
                               '7월', '8월', '9월', '10월', '11월', '12월'])

print("월별 평균 온도:")
print(temperatures)

# 계절별 온도 분석
spring = temperatures[['3월', '4월', '5월']]
summer = temperatures[['6월', '7월', '8월']]
autumn = temperatures[['9월', '10월', '11월']]
winter = temperatures[['12월', '1월', '2월']]

print(f"\n봄 평균 온도: {spring.mean():.1f}°C")
print(f"여름 평균 온도: {summer.mean():.1f}°C")
print(f"가을 평균 온도: {autumn.mean():.1f}°C")
print(f"겨울 평균 온도: {winter.mean():.1f}°C")
```

### 예제 3: 판매 데이터 분석
```python
# 제품별 판매량 데이터
sales_data = pd.Series([150, 200, 120, 300, 180, 250, 90, 160, 220, 140],
                       index=['노트북', '스마트폰', '태블릿', '이어폰', '스마트워치',
                              '키보드', '마우스', '모니터', '프린터', '스피커'])

print("제품별 판매량:")
print(sales_data)

# 판매량 기준 정렬
sorted_sales = sales_data.sort_values(ascending=False)
print("\n판매량 순으로 정렬:")
print(sorted_sales)

# 상위 5개 제품
top_5 = sorted_sales.head()
print(f"\n상위 5개 제품 총 판매량: {top_5.sum()}개")

# 평균 판매량 대비 성과
performance = sales_data / sales_data.mean()
print("\n평균 대비 판매 성과:")
print(performance)
```

## 📚 다음 단계

이제 Pandas Series의 기본을 익혔습니다. 다음 단계에서는:
- DataFrame 생성과 기초 조작
- 데이터 인덱싱, 슬라이싱, 필터링
- 결측치 처리와 데이터 정제
- 그룹화와 집계

을 학습하게 됩니다.

---

**참고 자료:**
- [Pandas Series 공식 문서](https://pandas.pydata.org/docs/reference/api/pandas.Series.html)
- [Pandas Series 튜토리얼](https://pandas.pydata.org/docs/user_guide/dsintro.html#series) 