4. 라이브러리

4.2. Pandas 라이브러리

4.2.1. 판다스 객체

4.2.1.1. 판다스 객체 소개

  • Pandas
    • NumPy를 기반으로 만들어진 데이터 분석용 라이브러리
    • 레이블(label)로 식별되는 데이터를 쉽게 다룰 수 있도록 지원
    • 고성능 데이터 조작, 탐색, 전처리, 변환, 요약, 통계 연산 기능 제공
    • 주요 객체로는 Series(1차원), DataFrame(2차원), Index(레이블 관리 구조)가 있음


객체 설명
Series - 1차원 레이블이 붙은 배열로, 각 데이터에 고유한 인덱스가 있음
- 모든 원소가 동일한 자료형으로 저장되며, 숫자, 문자열, bool 등 다양한 자료형을 지원
DataFrame - 2차원 표 형태의 데이터 구조로, 행(row)과 열(column)로 구성됨
- 각 열은 Series로 구성되어 있으며, 각 열이 서로 다른 자료형을 가질 수 있음
- DataFrame은 정렬(같은 인덱스 공유)된 Series 객체의 연속으로 볼 수 있음
Index - 데이터에 레이블을 부여하는 객체로, 행 또는 열에 사용됨
- 데이터 정렬, 선택, 결합 등을 효율적으로 할 수 있도록 지원


4.2.1.2. 판다스 객체 생성

함수 설명
pd.Series() 1차원 Series 객체 생성
pd.DataFrame() 2차원 DataFrame 객체 생성
pd.Index() Index 객체 생성
import numpy as np
import pandas as pd
# Series 생성1 : 리스트 사용
sr = pd.Series([1, 2, 3, 4, 5])
print(sr)
0    1
1    2
2    3
3    4
4    5
dtype: int64
# Series 생성2 : 넘파이 배열 사용
sr = pd.Series(np.array([1, 2, 3, 4, 5]))
print(sr)
0    1
1    2
2    3
3    4
4    5
dtype: int32
# Series 생성3 : 딕셔너리 사용
# 딕셔너리 키는 인덱스로 자동 지정됨
sr = pd.Series({'a': 10, 'b': 20, 'c': 30})
print(sr)
a    10
b    20
c    30
dtype: int64
# Series 생성4 : 인덱스 지정
sr = pd.Series([1, 2, 3], index = ['a', 'b', 'c'])
print(sr)
a    1
b    2
c    3
dtype: int64
# DataFrame 생성1 : Series 사용
sr = pd.Series([1, 2, 3], index = ['a', 'b', 'c'])
df = pd.DataFrame(sr, columns = ['value'])
print(df)
   value
a      1
b      2
c      3
# DataFrame 생성2 : 리스트 사용
lst = [['Alice', 22], ['Bob', 20], ['Charlie', 27]]
df = pd.DataFrame(lst, columns=['Name', 'Age'])
print(df)
      Name  Age
0    Alice   22
1      Bob   20
2  Charlie   27
# DataFrame 생성3 : 넘파이 배열 사용
arr = np.array([['Alice', 22], ['Bob', 20], ['Charlie', 27]])
df = pd.DataFrame(arr, columns=['Name', 'Age'])
print(df)
      Name Age
0    Alice  22
1      Bob  20
2  Charlie  27
# DataFrame 생성4 : 딕셔너리 사용
# 딕셔너리 키는 열 이름으로 자동 지정됨
dct = {'Name': ['Alice', 'Bob', 'Charlie'], 'Age': [22, 20, 27]}
df = pd.DataFrame(dct)
print(df)
      Name  Age
0    Alice   22
1      Bob   20
2  Charlie   27
# DataFrame 생성5 : 딕셔너리 + 리스트 컴프리헨션 사용
dct = [{'a': i, 'b': 2*i} for i in range(3)]
df = pd.DataFrame(dct)
print(df)
   a  b
0  0  0
1  1  2
2  2  4
# DataFrame 생성6 : Series 사용
# 서로 다른 인덱스를 가진 Series로 DataFrame을 생성하면
# 공통된 인덱스를 기준으로 맞춰지고, 누락된 값은 NaN(Not a Number)으로 채워짐
sr1 = pd.Series([1, 2, 3], index = ['a', 'b', 'c'])
sr2 = pd.Series([4, 5, 6], index = ['a', 'c', 'd'])
df = pd.DataFrame({'x': sr1, 'y': sr2})
print(df)
     x    y
a  1.0  4.0
b  2.0  NaN
c  3.0  5.0
d  NaN  6.0
# DataFrame 생성7 : 인덱스 및 열 이름 지정
data = [[101, 22], [102, 20], [103, 27]]
columns = ['ID', 'Age']
index = ['Alice', 'Bob', 'Charlie']
df = pd.DataFrame(data, columns=columns, index=index)
print(df)
          ID  Age
Alice    101   22
Bob      102   20
Charlie  103   27
# Index 생성1 : 리스트 사용
idx = pd.Index(['a', 'b', 'c', 'd'])
idx
Index(['a', 'b', 'c', 'd'], dtype='object')
# Index 생성2 : range() 사용
idx = pd.Index(range(1, 6))
idx
RangeIndex(start=1, stop=6, step=1)


4.2.1.3. 데이터프레임 속성

속성 설명
.shape 데이터프레임의 모양(행, 열)
.index 행 인덱스 조회 및 변경
.columns 열 이름 조회 및 변경
.dtypes 각 열의 자료형 확인
# 데이터프레임 속성
df = pd.DataFrame({
    'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eva'],
    'Age': [25, 30, 35, 40, 28],
    'Score': [85.5, 90.3, 78.2, 88.9, 92.5]
})

print(df.shape)
print(df.index)
print(df.columns)
print(df.dtypes)
(5, 3)
RangeIndex(start=0, stop=5, step=1)
Index(['Name', 'Age', 'Score'], dtype='object')
Name      object
Age        int64
Score    float64
dtype: object
# 행 인덱스 변경
df.index = ['a', 'b', 'c', 'd', 'e']
print(df)
      Name  Age  Score
a    Alice   25   85.5
b      Bob   30   90.3
c  Charlie   35   78.2
d    David   40   88.9
e      Eva   28   92.5
# 열 이름 변경
df.columns = ['Student Name', 'Student Age', 'Exam Score']
print(df)
  Student Name  Student Age  Exam Score
a        Alice           25        85.5
b          Bob           30        90.3
c      Charlie           35        78.2
d        David           40        88.9
e          Eva           28        92.5


4.2.1.4. 데이터프레임 정보 조회

함수 설명
info() 기본 정보(행 개수, 열 개수, 데이터 자료형, 결측값 여부 등) 출력
head() 상위 n개의 행 출력(초기 값 n=5)
describe() 수치형 데이터의 요약 통계량(평균, 표준편차 등) 제공
df = pd.DataFrame({
    'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eva'],
    'Age': [25, 30, 35, 40, 28],
    'Score': [85.5, 90.3, 78.2, 88.9, 92.5]
})
# 기본 정보
print(df.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5 entries, 0 to 4
Data columns (total 3 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   Name    5 non-null      object 
 1   Age     5 non-null      int64  
 2   Score   5 non-null      float64
dtypes: float64(1), int64(1), object(1)
memory usage: 252.0+ bytes
None
# 상위 5개 행
print(df.head())
      Name  Age  Score
0    Alice   25   85.5
1      Bob   30   90.3
2  Charlie   35   78.2
3    David   40   88.9
4      Eva   28   92.5
# 요약 통계량
print(df.describe())
            Age      Score
count   5.00000   5.000000
mean   31.60000  87.080000
std     5.94138   5.576917
min    25.00000  78.200000
25%    28.00000  85.500000
50%    30.00000  88.900000
75%    35.00000  90.300000
max    40.00000  92.500000


4.2.2. 데이터프레임 인덱싱과 슬라이싱

4.2.2.1. 열 인덱싱과 슬라이싱

  • 열 이름을 사용하여 직접 선택
    • 여러 개의 열을 선택할 때는 열 이름을 리스트로 작성
  • loc[:, ] : 레이블(label) 기반 접근 방식
  • iloc[:, ] : 정수(integer) 기반 접근 방식
df = pd.DataFrame({
    'A': [1, 2, 3],
    'B': [4, 5, 6],
    'C': [7, 8, 9],
    'D': [10, 11, 12]
}, index=['x', 'y', 'z'])
# 열 인덱싱과 슬라이싱1 : 열 이름 사용
df['A']                                      # 열 이름
df[['A', 'C']]                               # 열 이름 리스트
#df['A':'B']                                 # 열 이름 슬라이싱은 안 됨, Error!
A C
x 1 7
y 2 8
z 3 9
# 열 인덱싱과 슬라이싱2 : 레이블 기반 접근
df.loc[:, 'A']                               # 열 이름
df.loc[:, ['A', 'C']]                        # 열 이름 리스트
df.loc[:, 'A':'B']                           # 열 이름 슬라이싱
df.loc[:, 'B'::2]                            # 열 이름 스트라이딩
df.loc[:, [True, False, True, True]]         # bool 리스트
A C D
x 1 7 10
y 2 8 11
z 3 9 12
# 열 인덱싱과 슬라이싱3 : 정수 기반 접근
df.iloc[:, 0]                                # 정수
df.iloc[:, [0, 3]]                           # 정수 리스트
df.iloc[:, range(2)]                         # range
df.iloc[:, 0:3]                              # 슬라이싱
df.iloc[:, 1::2]                             # 스트라이딩
B D
x 4 10
y 5 11
z 6 12


4.2.2.2. 행 인덱싱과 슬라이싱

  • loc[, :] : 레이블(label) 기반 접근 방식
  • iloc[, :] : 정수(integer) 기반 접근 방식
# 행 인덱싱과 슬라이싱1 : 레이블 기반 접근
df.loc['x', ]                                # 인덱스 이름
df.loc[['x', 'y'], :]                        # 인덱스 이름 리스트
df.loc['x':'y', :]                           # 인덱스 이름 슬라이싱
df.loc['x'::1, :]                            # 인덱스 이름 스트라이딩
df.loc[[True, False, True], :]               # bool 리스트
df.loc[list(df['A'] < 3), :]                 # bool 리스트
df.loc[df['A'] < 3, :]                       # bool 리스트
A B C D
x 1 4 7 10
y 2 5 8 11
# 행 인덱싱과 슬라이싱2 : 정수 기반 접근
df.iloc[0, :]                                # 정수
df.iloc[[0, 2], :]                           # 정수 리스트
df.iloc[range(2), :]                         # range
df.iloc[0:1, :]                              # 슬라이싱
df.iloc[1::2, :]                             # 스트라이딩
df.iloc[[True, False, True], :]              # bool 리스트 (권장하지 않음)
df.iloc[list(df['A'] < 3), :]                # bool 리스트 (권장하지 않음)
#df.iloc[df['A'] < 3, :]                     # bool 리스트 (Error!)
A B C D
x 1 4 7 10
y 2 5 8 11
# 행 인덱싱과 슬라이싱3 : 정수 기반 접근
df.iloc[0]                                   # 정수
df.iloc[[0, 2]]                              # 정수 리스트
df.iloc[range(2)]                            # range
df.iloc[0:1]                                 # 슬라이싱
df.iloc[1::2]                                # 스트라이딩
df.iloc[[True, False, True]]                 # bool 리스트(권장하지 않음)
df.iloc[list(df['A'] < 3)]                   # bool 리스트(권장하지 않음)
#df.iloc[df['A'] < 3]                        # bool 리스트 (Error!)
A B C D
x 1 4 7 10
y 2 5 8 11
# 행 인덱싱과 슬라이싱4
# 인덱싱은 열을 참조하는 반면, 슬라이싱은 행을 참조함
#df[0]                                       # Error!
df[0:2]                                      # 권장하지 않음
df['x':'y']                                  # 권장하지 않음
df[df['A'] < 3]                              # 불리언 인덱싱은 행 단위로 적용됨
A B C D
x 1 4 7 10
y 2 5 8 11
# 행 인덱싱과 슬라이싱5
df = pd.DataFrame({
    'A': [1, 2, 3],
    'B': [4, 5, 6],
    'C': [7, 8, 9],
    'D': [10, 11, 12]})

df.loc[0:1]                                  # 명시적 인덱스(label) 사용 - 마지막 인덱스 포함
df.iloc[0:1]                                 # 암묵적 인덱스(integer) 사용 - 마지막 인덱스 제외
df[0:1]                                      # 암묵적 인덱스(integer) 사용, iloc와 동일
A B C D
0 1 4 7 10


4.2.3. 데이터프레임 연산

4.2.3.1. 데이터프레임 기본 연산

  • NumPy와 동일하게 벡터화, 브로드캐스팅 모두 지원
  • 단항 연산: 인덱스와 열 레이블 보존
  • 이항 연산: 인덱스와 열 레이블을 기준으로 자동 정렬되어 연산
    • 위치가 아닌 동일한 레이블끼리 연산하며, 일치하지 않는 항목은 NaN으로 처리
# 단항 연산1 : Series
sr = pd.Series([1, 2, 3, 4, 5])
print(sr + 2)
0    3
1    4
2    5
3    6
4    7
dtype: int64
# 단항 연산2 : DataFrame
df = pd.DataFrame({
    'x': [1, 2, 3, 4, 5],
    'y': [11, 12, 13, 14, 15] 
})
print(np.log(df))
          x         y
0  0.000000  2.397895
1  0.693147  2.484907
2  1.098612  2.564949
3  1.386294  2.639057
4  1.609438  2.708050
# 이항 연산1 : Series
# 두 시리즈에 존재하는 모든 행 인덱스를 포함한 결과 생성 (합집합처럼 동작)
math = pd.Series({'Alice': 85, 'Bob': 90, 'Charlie': 78})
english = pd.Series({'Alice': 95, 'Charlie': 80, 'David': 88})
print(math + english)
Alice      180.0
Bob          NaN
Charlie    158.0
David        NaN
dtype: float64
# 이항 연산2: DataFrame
midterm = pd.DataFrame({
    'math': [85, 90, 78],
    'science': [88, 92, 79]
}, index=['Alice', 'Bob', 'Charlie'])

final = pd.DataFrame({
    'math': [95, 88, 82],
    'english': [95, 80, 88]
}, index=['Alice', 'Charlie', 'David'])

print(midterm + final)
         english   math  science
Alice        NaN  180.0      NaN
Bob          NaN    NaN      NaN
Charlie      NaN  166.0      NaN
David        NaN    NaN      NaN


4.2.3.2. 연산자 메소드

  • 인덱스와 열 레이블이 일치하지 않아도 연산 가능
  • 예외 처리가 필요한 경우 기본 연산자보다 안정적임
    • fill_value 인수를 사용하여 NaN를 다른 값으로 처리 가능
연산자 연산자 메소드
+ add()
- sub()
* mul()
/ divide()
// floordiv()
% mod()
** pow()
# 연산자 메소드1
# midterm + final과 동일한 결과
print(midterm.add(final))
         english   math  science
Alice        NaN  180.0      NaN
Bob          NaN    NaN      NaN
Charlie      NaN  166.0      NaN
David        NaN    NaN      NaN
# 연산자 메소드2
# 하나의 데이터프레임에만 값이 있는 경우 0으로 처리하고, 둘 다 값이 없으면 NaN 반환
print(midterm.add(final, fill_value = 0))
         english   math  science
Alice       95.0  180.0     88.0
Bob          NaN   90.0     92.0
Charlie     80.0  166.0     79.0
David       88.0   82.0      NaN


4.2.3.3. 새로운 열 생성

함수 설명
assign() 기존 데이터프레임을 수정하지 않고, 새로운 열을 추가한 복사본을 반환
eval() 수식을 문자열로 작성하여 열 간 계산을 간결하게 수행(직관적인 수식 표현)
where() 조건에 따라 서로 다른 값을 선택하여 새로운 열 생성(NumPy 메소드)
df = pd.DataFrame({
    'product': ['A', 'B', 'C', 'D', 'E'],
    'price': [100, 200, 150, 300, 250],
    'quantity': [10, 5, 3, 7, 6]
})
print(df)
  product  price  quantity
0       A    100        10
1       B    200         5
2       C    150         3
3       D    300         7
4       E    250         6
# 새로운 열 생성1: 기본 할당
# 기본 할당은 데이터를 직접 수정하므로, 권장하지 않음
df['total_price'] = df['price'] * df['quantity']
print(df)
  product  price  quantity  total_price
0       A    100        10         1000
1       B    200         5         1000
2       C    150         3          450
3       D    300         7         2100
4       E    250         6         1500
# 새로운 열 생성2: assign() 사용
print(df.assign(total_price = df['price'] * df['quantity']))
  product  price  quantity  total_price
0       A    100        10         1000
1       B    200         5         1000
2       C    150         3          450
3       D    300         7         2100
4       E    250         6         1500
# 새로운 열 생성3: eval() 사용
# inplace=True: 기존 데이터프레임에 직접 적용(새 객체를 반환하지 않음)
df.eval('total_price = price * quantity', inplace=True)
print(df)
  product  price  quantity  total_price
0       A    100        10         1000
1       B    200         5         1000
2       C    150         3          450
3       D    300         7         2100
4       E    250         6         1500
# 새로운 열 생성4: where() 사용
# 가격이 200 이상이면 'Expensive', 아니면 'Affordable'로 구분
df['price_category'] = np.where(df['price'] >= 200, 'Expensive', 'Affordable')
print(df)
  product  price  quantity  total_price price_category
0       A    100        10         1000     Affordable
1       B    200         5         1000      Expensive
2       C    150         3          450     Affordable
3       D    300         7         2100      Expensive
4       E    250         6         1500      Expensive


4.2.3.4. 데이터프레임 집계

  • NumPy와 동일한 축 개념 사용
    • axis 0 : 행 방향 연산(열 단위로 요약)
    • axis 1 : 열 방향 연산(행 단위로 요약)
  • 수치형 데이터에 적용되며, NaN은 기본적으로 제외됨
함수 설명
count() NaN를 제외한 행의 개수
value_counts() 집단별 빈도
sum() 합계
mean() 평균
std() 표준편차
median() 중앙값
min(), max() 최소값, 최대값
cumsum(), cumprod() 누적 합계, 누적 곱
## 실습 데이터 : tips 데이터셋
import seaborn as sns
tips = sns.load_dataset('tips')
# total_bill: 식사 금액
# tip: 팁 금액
# sex: 성별
# smoker: 흡연 여부
# day: 요일
# time: 식사 시간(Lunch, Dinner)
# size: 식사 인원 수
print(tips.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 244 entries, 0 to 243
Data columns (total 7 columns):
 #   Column      Non-Null Count  Dtype   
---  ------      --------------  -----   
 0   total_bill  244 non-null    float64 
 1   tip         244 non-null    float64 
 2   sex         244 non-null    category
 3   smoker      244 non-null    category
 4   day         244 non-null    category
 5   time        244 non-null    category
 6   size        244 non-null    int64   
dtypes: category(4), float64(2), int64(1)
memory usage: 7.4 KB
None
print(tips.head())
   total_bill   tip     sex smoker  day    time  size
0       16.99  1.01  Female     No  Sun  Dinner     2
1       10.34  1.66    Male     No  Sun  Dinner     3
2       21.01  3.50    Male     No  Sun  Dinner     3
3       23.68  3.31    Male     No  Sun  Dinner     2
4       24.59  3.61  Female     No  Sun  Dinner     4
print(tips.describe())
       total_bill         tip        size
count  244.000000  244.000000  244.000000
mean    19.785943    2.998279    2.569672
std      8.902412    1.383638    0.951100
min      3.070000    1.000000    1.000000
25%     13.347500    2.000000    2.000000
50%     17.795000    2.900000    2.000000
75%     24.127500    3.562500    3.000000
max     50.810000   10.000000    6.000000
# 각 열의 유효한 데이터 수 (NaN 제외)
print(tips.count())
total_bill    244
tip           244
sex           244
smoker        244
day           244
time          244
size          244
dtype: int64
# 식사 시간별 주문 수
print(tips.value_counts('time'))
time
Dinner    176
Lunch      68
Name: count, dtype: int64
# 전체(합계) 식사 금액, 팁 금액, 식사 인원 수
print(tips[['total_bill', 'tip', 'size']].sum())
total_bill    4827.77
tip            731.58
size           627.00
dtype: float64
# 고객 1인당 평균 식사 금액
print((tips['total_bill'] / tips['size']).mean())
7.888229508196722
# 고객 1인당 평균 팁 금액
print((tips['tip'] / tips['size']).mean())
1.2127616120218578
# 전체 식사 금액에서 팁이 차지하는 비율
print(tips['tip'] / tips['total_bill'])
0      0.059447
1      0.160542
2      0.166587
3      0.139780
4      0.146808
         ...   
239    0.203927
240    0.073584
241    0.088222
242    0.098204
243    0.159744
Length: 244, dtype: float64
# 전체 식사 금액에서 팁이 차지하는 비율에 대한 요약 통계량
print((tips['tip'] / tips['total_bill']).describe())
count    244.000000
mean       0.160803
std        0.061072
min        0.035638
25%        0.129127
50%        0.154770
75%        0.191475
max        0.710345
dtype: float64
# 전체 식사 금액에서 팁이 차지하는 비율이 50% 이상인 경우
print(tips[tips['tip'] / tips['total_bill'] >= 0.5])
     total_bill   tip   sex smoker  day    time  size
172        7.25  5.15  Male    Yes  Sun  Dinner     2


4.2.4. 고급 데이터 처리

4.2.4.1. 데이터프레임 집단별 연산

  • groupby() 함수를 이용하여 특정 열을 기준으로 데이터를 집단화한 후, 다양한 연산 수행 가능
  • SQL의 GROUP BY와 유사한 기능을 제공하며, 집계, 변환, 필터링을 지원
함수 설명
aggregate() 또는 agg() 여러 집계 함수를 동시에 적용(문자열, 함수, 리스트 등 가능)
filter() 집단별 집계 결과를 조건으로 집단 자체를 필터링
transform() 집단별 연산 결과를 원래 구조와 같은 형태로 반환(원소 개수 유지)
apply() 임의의 함수를 전체 집단에 유연하게 적용(집계, 반환 모두 가능)
#  실습 데이터 : tips 데이터셋
tips = sns.load_dataset('tips')
# 요일별 평균 식사 금액
# observed=False: 모든 범주 포함, 실제 데이터가 없으면 NaN
print(tips.groupby('day', observed=False)['total_bill'].mean())
day
Thur    17.682742
Fri     17.151579
Sat     20.441379
Sun     21.410000
Name: total_bill, dtype: float64
# 식사 시간별 식사 금액, 팁 금액의 평균 및 표준편차
print(tips.groupby('time', observed=False)[['total_bill', 'tip']].agg(['mean', 'std']))
       total_bill                 tip          
             mean       std      mean       std
time                                           
Lunch   17.168676  7.713882  2.728088  1.205345
Dinner  20.797159  9.142029  3.102670  1.436243
# 식사 시간별 식사 금액의 최대값, 팁 금액의 최소값: 딕셔너리 사용
print(tips.groupby('time', observed=False).agg({'total_bill': 'max', 'tip': 'min'}).reset_index())
     time  total_bill   tip
0   Lunch       43.11  1.25
1  Dinner       50.81  1.00
# 식사 시간별 식사 금액의 최대값, 팁 금액의 최소값: 튜플 사용, named aggregation 문법
print(tips.groupby('time', observed=False).agg(total_bill_max = ('total_bill', 'max'), tip_min = ('tip', 'min')).reset_index())
     time  total_bill_max  tip_min
0   Lunch           43.11     1.25
1  Dinner           50.81     1.00


  • [참고] 람다 함수(lamdba function)
    • 함수 이름 없이 일시적으로 정의해 사용할 수 있는 익명 함수
    • def, return 없이 간단한 함수 로직을 한 줄로 표현
    • 구조는 일반 함수와 같지만, 주로 짧고 단순한 연산에 사용
    • apply(), filter() 등 함수의 인수로 전달할 때 유용하게 활용
# 함수 정의
def f(x, y):
    return x + y
f(1, 4)
5
# 람다 함수
(lambda x, y: x + y)(1, 4)
5
# 팀 금액 평균이 3달러 이상인 요일 데이터 필터링: 함수 정의
def filter_tip_avg(x):
    return x['tip'].mean() >= 3

print(tips.groupby('day', observed=False).filter(filter_tip_avg))
     total_bill   tip     sex smoker  day    time  size
0         16.99  1.01  Female     No  Sun  Dinner     2
1         10.34  1.66    Male     No  Sun  Dinner     3
2         21.01  3.50    Male     No  Sun  Dinner     3
3         23.68  3.31    Male     No  Sun  Dinner     2
4         24.59  3.61  Female     No  Sun  Dinner     4
..          ...   ...     ...    ...  ...     ...   ...
186       20.90  3.50  Female    Yes  Sun  Dinner     3
187       30.46  2.00    Male    Yes  Sun  Dinner     5
188       18.15  3.50  Female    Yes  Sun  Dinner     3
189       23.10  4.00    Male    Yes  Sun  Dinner     3
190       15.69  1.50    Male    Yes  Sun  Dinner     2

[76 rows x 7 columns]
# 팀 금액 평균이 3달러 이상인 요일 데이터 필터링: 람다 함수 사용
print(tips.groupby('day', observed=False).filter(lambda x: x['tip'].mean() >= 3))
     total_bill   tip     sex smoker  day    time  size
0         16.99  1.01  Female     No  Sun  Dinner     2
1         10.34  1.66    Male     No  Sun  Dinner     3
2         21.01  3.50    Male     No  Sun  Dinner     3
3         23.68  3.31    Male     No  Sun  Dinner     2
4         24.59  3.61  Female     No  Sun  Dinner     4
..          ...   ...     ...    ...  ...     ...   ...
186       20.90  3.50  Female    Yes  Sun  Dinner     3
187       30.46  2.00    Male    Yes  Sun  Dinner     5
188       18.15  3.50  Female    Yes  Sun  Dinner     3
189       23.10  4.00    Male    Yes  Sun  Dinner     3
190       15.69  1.50    Male    Yes  Sun  Dinner     2

[76 rows x 7 columns]
# 성별 팁 금액의 편차
tips['tip_dev'] = tips.groupby('sex', observed=False)['tip'].transform(lambda x: x - x.mean())
print(tips[['sex', 'tip', 'tip_dev']].head())
      sex   tip   tip_dev
0  Female  1.01 -1.823448
1    Male  1.66 -1.429618
2    Male  3.50  0.410382
3    Male  3.31  0.220382
4  Female  3.61  0.776552
# 식사 시간별 팁 금액의 사분위수범위: 함수 정의
def iqr(x):
    return x.quantile(0.75) - x.quantile(0.25)

print(tips.groupby('time', observed=False)['tip'].apply(iqr))
time
Lunch     1.2875
Dinner    1.6875
Name: tip, dtype: float64
# 식사 시간별 팁 금액의 사분위수범위: 람다 함수 사용
print(tips.groupby('time', observed=False)['tip'].apply(lambda x: x.quantile(0.75) - x.quantile(0.25)))
time
Lunch     1.2875
Dinner    1.6875
Name: tip, dtype: float64


4.2.4.2. 수식 및 조건식 기반 데이터 처리

  • eval(), query() 메소드를 사용하면 문자열 형태의 표현식을 통해 데이터를 보다 직관적으로 처리 가능
  • 복잡한 연산이나 조건 필터링을 간결하게 표현할 수 있어 가독성과 성능 모두에 유리함
    • 데이터프레임의 열 이름을 따옴표 없이 사용 가능
    • 외부 변수는 @ 기호를 사용하여 표현식에 표현 가능
함수 설명
eval() - 문자열로 작성한 수식을 이용하여 기존 열을 연산하거나 새로운 열을 생성
- Python 표현식을 그대로 사용할 수 있어 가독성이 높음
query() - 문자열로 작성한 조건식을 이용하여 행을 필터링
- 복잡한 불리언 인덱싱보다 간결하고 직관적인 문법 제공
- 논리 연산자는 &, \|, ~의 사용을 권장하며, 각 조건은 괄호 ()로 감싸야 함
#  실습 데이터 : tips 데이터셋
tips = sns.load_dataset('tips')
# 기존 열 연산: 식사 금액에서 세금 10%를 제외한 실수령 금액
print(tips.eval('total_bill = total_bill * 0.9'))
     total_bill   tip     sex smoker   day    time  size
0        15.291  1.01  Female     No   Sun  Dinner     2
1         9.306  1.66    Male     No   Sun  Dinner     3
2        18.909  3.50    Male     No   Sun  Dinner     3
3        21.312  3.31    Male     No   Sun  Dinner     2
4        22.131  3.61  Female     No   Sun  Dinner     4
..          ...   ...     ...    ...   ...     ...   ...
239      26.127  5.92    Male     No   Sat  Dinner     3
240      24.462  2.00  Female    Yes   Sat  Dinner     2
241      20.403  2.00    Male    Yes   Sat  Dinner     2
242      16.038  1.75    Male     No   Sat  Dinner     2
243      16.902  3.00  Female     No  Thur  Dinner     2

[244 rows x 7 columns]
# 새로운 열 생성: 전체 식사 금액 대비 팁 금액 비율
# inplace=True: 기존 데이터프레임에 직접 적용(새 객체를 반환하지 않음)
tips.eval('tip_rate = tip / total_bill', inplace=True)
print(tips)
     total_bill   tip     sex smoker   day    time  size  tip_rate
0         16.99  1.01  Female     No   Sun  Dinner     2  0.059447
1         10.34  1.66    Male     No   Sun  Dinner     3  0.160542
2         21.01  3.50    Male     No   Sun  Dinner     3  0.166587
3         23.68  3.31    Male     No   Sun  Dinner     2  0.139780
4         24.59  3.61  Female     No   Sun  Dinner     4  0.146808
..          ...   ...     ...    ...   ...     ...   ...       ...
239       29.03  5.92    Male     No   Sat  Dinner     3  0.203927
240       27.18  2.00  Female    Yes   Sat  Dinner     2  0.073584
241       22.67  2.00    Male    Yes   Sat  Dinner     2  0.088222
242       17.82  1.75    Male     No   Sat  Dinner     2  0.098204
243       18.78  3.00  Female     No  Thur  Dinner     2  0.159744

[244 rows x 8 columns]
# 외부 변수 사용: 전체 식사 금액에 따른 기본(최소) 팁 금액
tip_percentage = 0.15
tips.eval('min_tip = total_bill * @tip_percentage', inplace = True)
print(tips)
     total_bill   tip     sex smoker   day    time  size  tip_rate  min_tip
0         16.99  1.01  Female     No   Sun  Dinner     2  0.059447   2.5485
1         10.34  1.66    Male     No   Sun  Dinner     3  0.160542   1.5510
2         21.01  3.50    Male     No   Sun  Dinner     3  0.166587   3.1515
3         23.68  3.31    Male     No   Sun  Dinner     2  0.139780   3.5520
4         24.59  3.61  Female     No   Sun  Dinner     4  0.146808   3.6885
..          ...   ...     ...    ...   ...     ...   ...       ...      ...
239       29.03  5.92    Male     No   Sat  Dinner     3  0.203927   4.3545
240       27.18  2.00  Female    Yes   Sat  Dinner     2  0.073584   4.0770
241       22.67  2.00    Male    Yes   Sat  Dinner     2  0.088222   3.4005
242       17.82  1.75    Male     No   Sat  Dinner     2  0.098204   2.6730
243       18.78  3.00  Female     No  Thur  Dinner     2  0.159744   2.8170

[244 rows x 9 columns]
# 기본(최소) 팁 금액보다 적게 준 경우 'Below', 많거나 같은 경우 'Above'로 구분
# 기본(최소) 팁 금액보다 적게 준 경우는 108팀임
tips['tip_group'] = np.where(tips['tip'] < tips['min_tip'], 'Below', 'Above')
print(tips['tip_group'].value_counts())
tip_group
Above    136
Below    108
Name: count, dtype: int64
# 단일 조건 필터링: 식사 금액이 20달러 이상인 경우
print(tips.query('total_bill >= 20'))
     total_bill   tip     sex smoker  day    time  size  tip_rate  min_tip  \
2         21.01  3.50    Male     No  Sun  Dinner     3  0.166587   3.1515   
3         23.68  3.31    Male     No  Sun  Dinner     2  0.139780   3.5520   
4         24.59  3.61  Female     No  Sun  Dinner     4  0.146808   3.6885   
5         25.29  4.71    Male     No  Sun  Dinner     4  0.186240   3.7935   
7         26.88  3.12    Male     No  Sun  Dinner     4  0.116071   4.0320   
..          ...   ...     ...    ...  ...     ...   ...       ...      ...   
237       32.83  1.17    Male    Yes  Sat  Dinner     2  0.035638   4.9245   
238       35.83  4.67  Female     No  Sat  Dinner     3  0.130338   5.3745   
239       29.03  5.92    Male     No  Sat  Dinner     3  0.203927   4.3545   
240       27.18  2.00  Female    Yes  Sat  Dinner     2  0.073584   4.0770   
241       22.67  2.00    Male    Yes  Sat  Dinner     2  0.088222   3.4005   

    tip_group  
2       Above  
3       Below  
4       Below  
5       Above  
7       Below  
..        ...  
237     Below  
238     Below  
239     Above  
240     Below  
241     Below  

[97 rows x 10 columns]
# 다중 조건 필터링: 식사 인원 수가 3명 이상이고, 목요일에 방문한 경우
#print(tips.query('size >= 3 and day == "Thur"'))           # 권장하지 않음, 조건이 복잡할 경우 오류가 발생할 수 있음
print(tips.query('(size >= 3) & (day == "Thur")'))          # 권장 방식: 괄호로 각 조건을 명확히 구분
     total_bill   tip     sex smoker   day   time  size  tip_rate  min_tip  \
77        27.20  4.00    Male     No  Thur  Lunch     4  0.147059   4.0800   
85        34.83  5.17  Female     No  Thur  Lunch     4  0.148435   5.2245   
119       24.08  2.92  Female     No  Thur  Lunch     4  0.121262   3.6120   
125       29.80  4.20  Female     No  Thur  Lunch     6  0.140940   4.4700   
129       22.82  2.18    Male     No  Thur  Lunch     3  0.095530   3.4230   
141       34.30  6.70    Male     No  Thur  Lunch     6  0.195335   5.1450   
142       41.19  5.00    Male     No  Thur  Lunch     5  0.121389   6.1785   
143       27.05  5.00  Female     No  Thur  Lunch     6  0.184843   4.0575   
146       18.64  1.36  Female     No  Thur  Lunch     3  0.072961   2.7960   
197       43.11  5.00  Female    Yes  Thur  Lunch     4  0.115982   6.4665   
200       18.71  4.00    Male    Yes  Thur  Lunch     3  0.213789   2.8065   
204       20.53  4.00    Male    Yes  Thur  Lunch     4  0.194837   3.0795   
205       16.47  3.23  Female    Yes  Thur  Lunch     3  0.196114   2.4705   

    tip_group  
77      Below  
85      Below  
119     Below  
125     Below  
129     Below  
141     Above  
142     Below  
143     Above  
146     Below  
197     Below  
200     Above  
204     Above  
205     Above  
# 외부 변수 사용: 기준 팁 금액 미만인 경우
min_tip_required = 5
print(tips.query('tip < @min_tip_required'))
     total_bill   tip     sex smoker   day    time  size  tip_rate  min_tip  \
0         16.99  1.01  Female     No   Sun  Dinner     2  0.059447   2.5485   
1         10.34  1.66    Male     No   Sun  Dinner     3  0.160542   1.5510   
2         21.01  3.50    Male     No   Sun  Dinner     3  0.166587   3.1515   
3         23.68  3.31    Male     No   Sun  Dinner     2  0.139780   3.5520   
4         24.59  3.61  Female     No   Sun  Dinner     4  0.146808   3.6885   
..          ...   ...     ...    ...   ...     ...   ...       ...      ...   
238       35.83  4.67  Female     No   Sat  Dinner     3  0.130338   5.3745   
240       27.18  2.00  Female    Yes   Sat  Dinner     2  0.073584   4.0770   
241       22.67  2.00    Male    Yes   Sat  Dinner     2  0.088222   3.4005   
242       17.82  1.75    Male     No   Sat  Dinner     2  0.098204   2.6730   
243       18.78  3.00  Female     No  Thur  Dinner     2  0.159744   2.8170   

    tip_group  
0       Below  
1       Above  
2       Above  
3       Below  
4       Below  
..        ...  
238     Below  
240     Below  
241     Below  
242     Below  
243     Above  

[216 rows x 10 columns]
# 기준 팁 금액보다 적게 준 경우는 216팀임
print(tips.query('tip < @min_tip_required').shape[0])
print(len(tips.query('tip < @min_tip_required')))
216
216


4.2.5. 시계열 데이터 처리

4.2.5.1. 시계열 데이터(time series data)

  • 일정한 시간 간격으로 관측된 연속적인 데이터
  • Pandas에서는 시계열 데이터를 처리하기 위한 다양한 날짜/시간 관련 객체를 제공함
구분 설명 Pandas 객체
타임스탬프(timestampe) 특정 시점 2025-03-02 09:00 pd.TimeStamp, DatetimeIndex
시간 기간(period) 일정한 시간 구간(시작~종료) 2025년 3월 (월간) pd.Period, PeriodIndex
시간 델타(time delta) 두 시점 사이의 시간 간격 2일, 5시간 pd.Timedelta, TimedeltaIndex
print(pd.Timestamp('2025-03-02 09:00'))      # 타임스탬프
print(pd.Period('2025-03', freq='M'))        # 시간 기간
print(pd.Timedelta(days=2, hours=5))         # 시간 델타
2025-03-02 09:00:00
2025-03
2 days 05:00:00


4.2.5.2. 시계열 데이터 형식 변환

  • pd.to_datetime() 함수
    • 문자열이나 정수 등 다양한 형태의 데이터를 시계열 데이터(datetime 형식)로 변환
    • format 인자를 사용하여 날짜 형식을 명시적으로 지정 가능
    • errors='coerce' 인자를 사용하면 오류 발생 시 NaT(Not a Time)으로 처리
    • 시계열 인덱스를 설정하거나 변환할 때도 자주 사용됨
코드 설명
%Y 전체 연도 2025
%B 전체 월 이름 March
%m 0으로 시작하는 월 03
%d 0으로 시작하는 일 02
%H 0으로 시작하는 시간(24시간제) 14
%I 0으로 시작하는 시간(12시간제) 09
%p AM 또는 PM AM
%M 0으로 시작하는 분 00
%S 0으로 시작하는 초 00
# Timestampe1: 문자열 리스트 사용
pd.to_datetime(['2025-03-02', '2025-03-03', '2025-03-04'])
DatetimeIndex(['2025-03-02', '2025-03-03', '2025-03-04'], dtype='datetime64[ns]', freq=None)
# Timestampe2: 명시적 형식 지정
pd.to_datetime(['March 2, 2025', 'March 3, 2025', 'March 4, 2025'], format='%B %d, %Y')
DatetimeIndex(['2025-03-02', '2025-03-03', '2025-03-04'], dtype='datetime64[ns]', freq=None)
# Timestampe3: 오류 발생 시 NaT 처리
pd.to_datetime(["2025-03-02", "not a date"], errors="coerce")
DatetimeIndex(['2025-03-02', 'NaT'], dtype='datetime64[ns]', freq=None)
# Timestampe4: 정수 데이터프레임 사용(각 행을 날짜로 변환)
df = pd.DataFrame({
    "year": [2025, 2025],
    "month": [3, 4],
    "day": [2, 15]
})
pd.to_datetime(df)
0   2025-03-02
1   2025-04-15
dtype: datetime64[ns]
# Period: 2025년 3월 1일 ~ 31일(한 달)
pd.to_datetime('2025-03', format='%Y-%m').to_period('M')
Period('2025-03', 'M')
# Timedelta
pd.to_datetime('2025-05-13') - pd.to_datetime('2025-03-02')
Timedelta('72 days 00:00:00')


  • 정규 날짜 시퀀스 생성
    • Pandas는 일정 간격의 날짜/시간 데이터를 생성하는 기능을 제공함
    • pd.date_range(), pd.period_range(), pd.timedelta_range() 함수
      • freq 인자를 통해 날짜 간격의 빈도(frequency) 지정 가능
코드 설명
D 일(day)
W 주(week)
ME 월말(month end)
QE 분기말(quarer end)
YE 연말(year end)
h 시간(hour)
min 분(minute)
s 초(second)
# pd.date_range(): 하루씩 증가하는 날짜 시퀀스
pd.date_range('2025-03-02', periods=5, freq='D')
DatetimeIndex(['2025-03-02', '2025-03-03', '2025-03-04', '2025-03-05',
               '2025-03-06'],
              dtype='datetime64[ns]', freq='D')
# pd.date_range(): 일주일씩 증가하는 날짜 시퀀스(일요일 기준)
pd.date_range('2025-05-13', periods=5, freq='W')
DatetimeIndex(['2025-05-18', '2025-05-25', '2025-06-01', '2025-06-08',
               '2025-06-15'],
              dtype='datetime64[ns]', freq='W-SUN')
# pd.period_range(): 2025년 3월부터 시작하여 3개월 동안의 기간
pd.period_range('2025-03', periods=3, freq='M')
PeriodIndex(['2025-03', '2025-04', '2025-05'], dtype='period[M]')
# pd.timedelta_range(): 1시간 간격의 시간 델타 시퀀스
pd.timedelta_range(start='0s', periods=5, freq='h')
TimedeltaIndex(['0 days 00:00:00', '0 days 01:00:00', '0 days 02:00:00',
                '0 days 03:00:00', '0 days 04:00:00'],
               dtype='timedelta64[ns]', freq='h')


4.2.5.3. 시계열 데이터 인덱스와 필터링

  • 시계열 데이터를 인덱스로 설정하면, 시간 기반으로 데이터를 쉽게 필터링할 수 있음
    • 문자열 형태의 날짜를 인덱스 슬라이싱에 사용 가능
    • 예 : 연도(‘2025’), 연월(‘2025-01’), 연월일(‘2025-01-07’) 등
  • DatetimeIndex 객체는 다음과 같은 속성을 제공
    • .year, .month, .day, .hour, .minute, .second
    • .month_name(), .day_name(), .dayofweek(0=월요일)
  • sort_index() 메소드를 통해 날짜 인덱스를 기준으로 정렬 가능
# 시계열 데이터 인덱스 설정
date_rng = pd.date_range(start='2024-12-20', end='2025-01-10', freq='D')
df = pd.DataFrame({'value': np.random.randint(0, 100, size=len(date_rng))}, index=date_rng)
print(df)
            value
2024-12-20     93
2024-12-21     67
2024-12-22     67
2024-12-23     94
2024-12-24     29
2024-12-25     60
2024-12-26     66
2024-12-27     71
2024-12-28     43
2024-12-29     54
2024-12-30     99
2024-12-31     73
2025-01-01     82
2025-01-02     63
2025-01-03      1
2025-01-04     95
2025-01-05      1
2025-01-06      6
2025-01-07     73
2025-01-08     52
2025-01-09     42
2025-01-10     35
# 날짜 기반 필터링1: 특정 연도 데이터
print(df.loc['2025'])
            value
2025-01-01     82
2025-01-02     63
2025-01-03      1
2025-01-04     95
2025-01-05      1
2025-01-06      6
2025-01-07     73
2025-01-08     52
2025-01-09     42
2025-01-10     35
# 날짜 기반 필터링2: 특정 날짜 이후 데이터
print(df.loc['2025-01-07':])
            value
2025-01-07     73
2025-01-08     52
2025-01-09     42
2025-01-10     35
# 날짜 기반 필터링2: 특정 날짜 범위 데이터
print(df.loc['2024-12-24':'2024-12-31'])
            value
2024-12-24     29
2024-12-25     60
2024-12-26     66
2024-12-27     71
2024-12-28     43
2024-12-29     54
2024-12-30     99
2024-12-31     73
# DatetimeIndex 속성1: 월(month)
print(df.index.month)
Index([12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1],
      dtype='int32')
# DatetimeIndex 속성2: 요일 이름
print(df.index.day_name())
Index(['Friday', 'Saturday', 'Sunday', 'Monday', 'Tuesday', 'Wednesday',
       'Thursday', 'Friday', 'Saturday', 'Sunday', 'Monday', 'Tuesday',
       'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday', 'Monday',
       'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
      dtype='object')
# 날짜 인덱스 정렬: 내림차순
print(df.sort_index(ascending=False))
            value
2025-01-10     35
2025-01-09     42
2025-01-08     52
2025-01-07     73
2025-01-06      6
2025-01-05      1
2025-01-04     95
2025-01-03      1
2025-01-02     63
2025-01-01     82
2024-12-31     73
2024-12-30     99
2024-12-29     54
2024-12-28     43
2024-12-27     71
2024-12-26     66
2024-12-25     60
2024-12-24     29
2024-12-23     94
2024-12-22     67
2024-12-21     67
2024-12-20     93


4.2.5.4. 시계열 데이터 처리 및 시각화

  • 리샘플링(resampling) : 시계열 데이터의 빈도를 변경하는 방법(예 : 월별 데이터 → 연간 데이터)
  • 시간 이동(time-shift) : 시계열 데이터를 시간 축을 기준으로 이동시키는 방법으로, 이전 시점의 데이터와 비교할 때 유용함
  • 롤링 윈도우(rolling windows): 일정한 기간을 기준으로 이동 평균 등을 방법으로, 주기적인 변동성을 관할할 때 유용함
## 실습 데이터 : flights 데이터셋
flights = sns.load_dataset("flights")
print(flights.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 144 entries, 0 to 143
Data columns (total 3 columns):
 #   Column      Non-Null Count  Dtype   
---  ------      --------------  -----   
 0   year        144 non-null    int64   
 1   month       144 non-null    category
 2   passengers  144 non-null    int64   
dtypes: category(1), int64(2)
memory usage: 2.9 KB
None
print(flights.head())
   year month  passengers
0  1949   Jan         112
1  1949   Feb         118
2  1949   Mar         132
3  1949   Apr         129
4  1949   May         121
# 시계열 데이터 인덱스 설정
flights['date'] = pd.to_datetime(flights['year'].astype(str) + '-' + flights['month'].astype(str), format='%Y-%b')
flights.set_index('date', inplace=True)
flights.drop(['year', 'month'], axis=1, inplace=True)
print(flights.head())
            passengers
date                  
1949-01-01         112
1949-02-01         118
1949-03-01         132
1949-04-01         129
1949-05-01         121
# 리샘플링: 월별 데이터 → 연간 데이터(연도별 총합)
annual_passengers = flights.resample('YE').sum()
print(annual_passengers)
            passengers
date                  
1949-12-31        1520
1950-12-31        1676
1951-12-31        2042
1952-12-31        2364
1953-12-31        2700
1954-12-31        2867
1955-12-31        3408
1956-12-31        3939
1957-12-31        4421
1958-12-31        4572
1959-12-31        5140
1960-12-31        5714
import matplotlib.pyplot as plt
# 리샘플링: 연간 데이터 시각화
plt.figure(figsize=(5, 3))
plt.plot(annual_passengers['passengers'])
plt.title('Annual Total Passengers')
plt.xlabel('Year')
plt.ylabel('Number of Passengers')
plt.grid(True)
plt.show()

# 시간 이동: 직전 분기 대비 승객 수 증감률(%)
quarterly = flights['passengers'].resample('QE').sum()
previous_quarter = quarterly.shift(1)
change_rate = (quarterly - previous_quarter) / previous_quarter * 100

quarterly_df = pd.DataFrame({
    'quarterly_passengers': quarterly,
    'change_rate': change_rate
})
print(quarterly_df)
            quarterly_passengers  change_rate
date                                         
1949-03-31                   362          NaN
1949-06-30                   385     6.353591
1949-09-30                   432    12.207792
1949-12-31                   341   -21.064815
1950-03-31                   382    12.023460
1950-06-30                   409     7.068063
1950-09-30                   498    21.760391
1950-12-31                   387   -22.289157
1951-03-31                   473    22.222222
1951-06-30                   513     8.456660
1951-09-30                   582    13.450292
1951-12-31                   474   -18.556701
1952-03-31                   544    14.767932
1952-06-30                   582     6.985294
1952-09-30                   681    17.010309
1952-12-31                   557   -18.208517
1953-03-31                   628    12.746858
1953-06-30                   707    12.579618
1953-09-30                   773     9.335219
1953-12-31                   592   -23.415265
1954-03-31                   627     5.912162
1954-06-30                   725    15.629984
1954-09-30                   854    17.793103
1954-12-31                   661   -22.599532
1955-03-31                   742    12.254160
1955-06-30                   854    15.094340
1955-09-30                  1023    19.789227
1955-12-31                   789   -22.873900
1956-03-31                   878    11.280101
1956-06-30                  1005    14.464692
1956-09-30                  1173    16.716418
1956-12-31                   883   -24.722933
1957-03-31                   972    10.079275
1957-06-30                  1125    15.740741
1957-09-30                  1336    18.755556
1957-12-31                   988   -26.047904
1958-03-31                  1020     3.238866
1958-06-30                  1146    12.352941
1958-09-30                  1400    22.164049
1958-12-31                  1006   -28.142857
1959-03-31                  1108    10.139165
1959-06-30                  1288    16.245487
1959-09-30                  1570    21.894410
1959-12-31                  1174   -25.222930
1960-03-31                  1227     4.514480
1960-06-30                  1468    19.641402
1960-09-30                  1736    18.256131
1960-12-31                  1283   -26.094470
# 시프팅: 직전 분기 대비 승객 수 증감률(%) 시각화
plt.figure(figsize=(5, 3))
plt.plot(quarterly_df['change_rate'])
plt.title('Quarter-over-Quarter Change in Airline Passengers (%)')
plt.xlabel('Quarter')
plt.ylabel('Change Rate (%)')
plt.grid(True)
plt.show()

# 롤링 윈도우: 12개월 단순이동평균
flights['moving_avg_12months'] = flights['passengers'].rolling(window=12).mean()
print(flights)
            passengers  moving_avg_12months
date                                       
1949-01-01         112                  NaN
1949-02-01         118                  NaN
1949-03-01         132                  NaN
1949-04-01         129                  NaN
1949-05-01         121                  NaN
...                ...                  ...
1960-08-01         606           463.333333
1960-09-01         508           467.083333
1960-10-01         461           471.583333
1960-11-01         390           473.916667
1960-12-01         432           476.166667

[144 rows x 2 columns]
# 롤링 윈도우: 12개월 단순이동평균 시각화
plt.figure(figsize=(5, 3))
plt.plot(flights['passengers'], label='Original Data')
plt.plot(flights['moving_avg_12months'], label='12-Month Moving Average', color='orange')
plt.title('Airline Passenger Trend with 12-Month Moving Average')
plt.xlabel('Date')
plt.ylabel('Number of Passengers')
plt.legend()
plt.show()


(과제) 시계열 데이터 처리 실습

  • air_quality_no2_long.csv 데이터셋은 Pandas 공식 문서에서 제공하는 유럽 도시 대기 중 이산화질소(NO2) 농도 측정 자료이다.
    • 문제1. 날짜 정보(date.utc) 열을 datetime 형식으로 변환하고, 이를 인덱스로 설정하라.
    • 문제2. 2019년 5월에 속하는 데이터 중 월요일에 해당하는 데이터만 추출하라.
    • 문제3. 이산화질소 농도(value)의 주(week)별 최대값을 계산하라.

  • yfinance 라이브러리를 통해 2023년 1월 1일부터 12월 31일까지의 Apple(AAPL) 일별 주가 데이터를 다운로드할 수 있다.
    • 문제4. 전일 종가(Close) 대비 당일 시가(Open)의 변동률이 3% 이상인 데이터를 추출하라.
    • 문제5. 종가(Close)에 대해 5일 이동평균과 10일 이동평균을 각각 계산한 후 시각화하라.
# air_quality_no2_long.csv
air = pd.read_csv("https://raw.githubusercontent.com/pandas-dev/pandas/main/doc/data/air_quality_no2_long.csv")
# 2023년 Apple 일별 주가 데이터
# !pip install yfinance
# import yfinance as yf
# stock = yf.download("AAPL", start="2023-01-01", end="2023-12-31")
# stock.columns = stock.columns.droplevel("Ticker")