[Python] 데이터 사이언스 스쿨 - 4.8 시계열 자료 다루기

Updated:

데이터 사이언스 스쿨 자료를 토대로 공부한 내용입니다.

실습과정에서 필요에 따라 내용의 누락 및 추가, 수정사항이 있습니다.


4.8 시계열 자료 다루기

DatetimeIndex 인덱스

시계열 자료는 인덱스가 날짜 혹은 시간인 데이터를 말한다.

판다스에서 시계열 자료를 생성하려면 인덱스를 DatetimeIndex 자료형으로 만들어야 한다.

DatetimeIndex는 특정한 순간에 기록된 타임스탬프(timestamp) 형식의 시계열 자료를 다루기 위한 인덱스이다.

타임스탬프 인덱스의 라벨값이 반드시 일정한 간격일 필요는 없다.

import numpy as np
import pandas as pd
# datetime index 생성 1
date_str = ["2018, 1, 1", "2018, 1, 4", "2018, 1, 5", "2018, 1, 6"]
idx = pd.to_datetime(date_str)
idx
DatetimeIndex(['2018-01-01', '2018-01-04', '2018-01-05', '2018-01-06'], dtype='datetime64[ns]', freq=None)
# datetime index 생성 2
pd.date_range("2018-4-1", "2018-4-30")
DatetimeIndex(['2018-04-01', '2018-04-02', '2018-04-03', '2018-04-04',
               '2018-04-05', '2018-04-06', '2018-04-07', '2018-04-08',
               '2018-04-09', '2018-04-10', '2018-04-11', '2018-04-12',
               '2018-04-13', '2018-04-14', '2018-04-15', '2018-04-16',
               '2018-04-17', '2018-04-18', '2018-04-19', '2018-04-20',
               '2018-04-21', '2018-04-22', '2018-04-23', '2018-04-24',
               '2018-04-25', '2018-04-26', '2018-04-27', '2018-04-28',
               '2018-04-29', '2018-04-30'],
              dtype='datetime64[ns]', freq='D')
# 갯수 입력하여 생성
pd.date_range(start="2018-4-1", periods=5)
DatetimeIndex(['2018-04-01', '2018-04-02', '2018-04-03', '2018-04-04',
               '2018-04-05'],
              dtype='datetime64[ns]', freq='D')

freq 인수

인수 설명
s
T
H 시간
D 일(day)
B 주말이 아닌 평일
W 주(일요일)
W-MON 주(월요일)
M 각 달(month)의 마지막 날
MS 각 달의 첫날
BM 주말이 아닌 평일 중에서 각 달의 마지막 날
BMS 주말이 아닌 평일 중에서 각 달의 첫날
WOM-2THU 각 달의 두번째 목요일
Q-JAN 각 분기의 첫달의 마지막 날
Q-DEC 각 분기의 마지막 달의 마지막 날
pd.date_range("2018-04-01","2018-04-30", freq="W")
DatetimeIndex(['2018-04-01', '2018-04-08', '2018-04-15', '2018-04-22',
               '2018-04-29'],
              dtype='datetime64[ns]', freq='W-SUN')

shift 연산

# datetime index를 가지는 시리즈
np.random.seed(0)
ts = pd.Series(
    np.random.randn(4),
    index=pd.date_range("2018-1-1", periods=4, freq="M")
)
ts
2018-01-31    1.764052
2018-02-28    0.400157
2018-03-31    0.978738
2018-04-30    2.240893
Freq: M, dtype: float64
# 값이 2칸씩 이동
ts.shift(2)
2018-01-31         NaN
2018-02-28         NaN
2018-03-31    1.764052
2018-04-30    0.400157
Freq: M, dtype: float64
# 값이 1칸 전으로 이동
ts.shift(-1)
2018-01-31    0.400157
2018-02-28    0.978738
2018-03-31    2.240893
2018-04-30         NaN
Freq: M, dtype: float64
# Datetimeindex를 1개 인수만큼 증가
ts.shift(1, freq="M")
2018-02-28    1.764052
2018-03-31    0.400157
2018-04-30    0.978738
2018-05-31    2.240893
Freq: M, dtype: float64

resample 연산

다운샘플링

np.random.seed(0)
ts = pd.Series(np.random.randn(100), index=pd.date_range(
    "2018-1-1", periods=100, freq="D"))
ts.tail()
2018-04-06    0.706573
2018-04-07    0.010500
2018-04-08    1.785870
2018-04-09    0.126912
2018-04-10    0.401989
Freq: D, dtype: float64
# 1일별 데이터를 주별(일요일) 데이터로 축소
# 인덱스가 축소 되므로 그룹 함수를 사용해야함
ts.resample("W").mean()
2018-01-07    1.032030
2018-01-14    0.376722
2018-01-21   -0.146794
2018-01-28    0.207122
2018-02-04    0.045536
2018-02-11   -0.081312
2018-02-18   -0.398918
2018-02-25   -0.287513
2018-03-04   -0.353319
2018-03-11   -0.567629
2018-03-18   -0.055802
2018-03-25   -0.309877
2018-04-01    0.566217
2018-04-08    0.752381
2018-04-15    0.264451
Freq: W-SUN, dtype: float64
# 날짜가 아닌 시/분/초의 경우 첫 스타트가 0부터다
ts = pd.Series(np.random.randn(60), 
               index=pd.date_range("2018-1-1", periods=60, freq="T"))
ts.head(20)
2018-01-01 00:00:00    1.883151
2018-01-01 00:01:00   -1.347759
2018-01-01 00:02:00   -1.270485
2018-01-01 00:03:00    0.969397
2018-01-01 00:04:00   -1.173123
2018-01-01 00:05:00    1.943621
2018-01-01 00:06:00   -0.413619
2018-01-01 00:07:00   -0.747455
2018-01-01 00:08:00    1.922942
2018-01-01 00:09:00    1.480515
2018-01-01 00:10:00    1.867559
2018-01-01 00:11:00    0.906045
2018-01-01 00:12:00   -0.861226
2018-01-01 00:13:00    1.910065
2018-01-01 00:14:00   -0.268003
2018-01-01 00:15:00    0.802456
2018-01-01 00:16:00    0.947252
2018-01-01 00:17:00   -0.155010
2018-01-01 00:18:00    0.614079
2018-01-01 00:19:00    0.922207
Freq: T, dtype: float64
# 1분 간격을 10분 간격으로 축소
# 즉, 00:00:00 이상 00:10:00 미만 등으로 인식
ts.resample("10T").sum()
2018-01-01 00:00:00    3.247184
2018-01-01 00:10:00    6.685424
2018-01-01 00:20:00    2.551313
2018-01-01 00:30:00   -1.161345
2018-01-01 00:40:00    1.504165
2018-01-01 00:50:00   -2.226607
Freq: 10T, dtype: float64
# closed 옵션을 이용해서 오른쪽을 포함
# 첫 번째를 보면 23:50:00 초과 00:00:00 이하: 1.883151로서 1개의 값이 sum
# 기존 데이터의 첫 행과 같은 값임을 확인 가능
ts.resample('10T', closed="right").sum()
2017-12-31 23:50:00    1.883151
2018-01-01 00:00:00    3.231592
2018-01-01 00:10:00    5.194290
2018-01-01 00:20:00    1.404972
2018-01-01 00:30:00   -1.882686
2018-01-01 00:40:00    2.927181
2018-01-01 00:50:00   -2.158366
Freq: 10T, dtype: float64
#  ohlc 옵션을 이용해서 시,고,저,종 확인 가능
ts.resample('10T').ohlc()
open high low close
2018-01-01 00:00:00 1.883151 1.943621 -1.347759 1.480515
2018-01-01 00:10:00 1.867559 1.910065 -0.861226 0.922207
2018-01-01 00:20:00 0.376426 1.849264 -1.099401 0.407462
2018-01-01 00:30:00 -0.769916 0.676433 -1.093062 -1.093062
2018-01-01 00:40:00 -1.491258 2.383145 -1.491258 -0.461585
2018-01-01 00:50:00 -0.068242 1.713343 -1.147469 -0.437820

업 샘플링

ts = pd.Series(np.random.randn(60), index=pd.date_range(
    "2018-1-1", periods=60, freq="T"))
ts.head(20)
2018-01-01 00:00:00   -0.498032
2018-01-01 00:01:00    1.929532
2018-01-01 00:02:00    0.949421
2018-01-01 00:03:00    0.087551
2018-01-01 00:04:00   -1.225436
2018-01-01 00:05:00    0.844363
2018-01-01 00:06:00   -1.000215
2018-01-01 00:07:00   -1.544771
2018-01-01 00:08:00    1.188030
2018-01-01 00:09:00    0.316943
2018-01-01 00:10:00    0.920859
2018-01-01 00:11:00    0.318728
2018-01-01 00:12:00    0.856831
2018-01-01 00:13:00   -0.651026
2018-01-01 00:14:00   -1.034243
2018-01-01 00:15:00    0.681595
2018-01-01 00:16:00   -0.803410
2018-01-01 00:17:00   -0.689550
2018-01-01 00:18:00   -0.455533
2018-01-01 00:19:00    0.017479
Freq: T, dtype: float64
# 분별 데이터를 30초별 데이터로 확장
# 인덱스가 늘어나므로 없는 값을 추가해야함
# ffill: 앞에 값을 사용
ts.resample('30s').ffill().head(20)
2018-01-01 00:00:00   -0.498032
2018-01-01 00:00:30   -0.498032
2018-01-01 00:01:00    1.929532
2018-01-01 00:01:30    1.929532
2018-01-01 00:02:00    0.949421
2018-01-01 00:02:30    0.949421
2018-01-01 00:03:00    0.087551
2018-01-01 00:03:30    0.087551
2018-01-01 00:04:00   -1.225436
2018-01-01 00:04:30   -1.225436
2018-01-01 00:05:00    0.844363
2018-01-01 00:05:30    0.844363
2018-01-01 00:06:00   -1.000215
2018-01-01 00:06:30   -1.000215
2018-01-01 00:07:00   -1.544771
2018-01-01 00:07:30   -1.544771
2018-01-01 00:08:00    1.188030
2018-01-01 00:08:30    1.188030
2018-01-01 00:09:00    0.316943
2018-01-01 00:09:30    0.316943
Freq: 30S, dtype: float64
# bfill: 뒤에 값을 사용
ts.resample('30s').bfill().head(20)
2018-01-01 00:00:00   -0.498032
2018-01-01 00:00:30    1.929532
2018-01-01 00:01:00    1.929532
2018-01-01 00:01:30    0.949421
2018-01-01 00:02:00    0.949421
2018-01-01 00:02:30    0.087551
2018-01-01 00:03:00    0.087551
2018-01-01 00:03:30   -1.225436
2018-01-01 00:04:00   -1.225436
2018-01-01 00:04:30    0.844363
2018-01-01 00:05:00    0.844363
2018-01-01 00:05:30   -1.000215
2018-01-01 00:06:00   -1.000215
2018-01-01 00:06:30   -1.544771
2018-01-01 00:07:00   -1.544771
2018-01-01 00:07:30    1.188030
2018-01-01 00:08:00    1.188030
2018-01-01 00:08:30    0.316943
2018-01-01 00:09:00    0.316943
2018-01-01 00:09:30    0.920859
Freq: 30S, dtype: float64

dt 접근자

# 시리즈 생성
s = pd.Series(pd.date_range("2020-12-25", periods=100, freq="D"))
s
0    2020-12-25
1    2020-12-26
2    2020-12-27
3    2020-12-28
4    2020-12-29
        ...    
95   2021-03-30
96   2021-03-31
97   2021-04-01
98   2021-04-02
99   2021-04-03
Length: 100, dtype: datetime64[ns]
# 요일 정보
s.dt.weekday
0     4
1     5
2     6
3     0
4     1
     ..
95    1
96    2
97    3
98    4
99    5
Length: 100, dtype: int64
# resample에 사용했던 ts 데이터 프레임을 시리즈로 변경해서 정보 추출
ts.reset_index()["index"].dt.year.head(3)
0    2018
1    2018
2    2018
Name: index, dtype: int64
# strftime을 이용해서 포맷 변경
s.dt.strftime("%Y년 %m월 %d일")
0     2020년 12월 25일
1     2020년 12월 26일
2     2020년 12월 27일
3     2020년 12월 28일
4     2020년 12월 29일
          ...      
95    2021년 03월 30일
96    2021년 03월 31일
97    2021년 04월 01일
98    2021년 04월 02일
99    2021년 04월 03일
Length: 100, dtype: object

연습문제 4.8.1

다음 명령으로 만들어진 데이터프레임에 대해 월별 value의 합계를 구하라.

(힌트: groupby 메서드와 dt 접근자를 사용하라)

np.random.seed(0)
df = pd.DataFrame({
    "date": pd.date_range("2020-12-25", periods=100, freq="D"), 
    "value": np.random.randint(100, size=(100,))
})
np.random.seed(0)
df = pd.DataFrame({
    "date": pd.date_range("2020-12-25", periods=100, freq="D"), 
    "value": np.random.randint(100, size=(100,))
})
df
date value
0 2020-12-25 44
1 2020-12-26 47
2 2020-12-27 64
3 2020-12-28 67
4 2020-12-29 67
... ... ...
95 2021-03-30 23
96 2021-03-31 79
97 2021-04-01 13
98 2021-04-02 85
99 2021-04-03 48

100 rows × 2 columns

df.groupby([df.date.dt.year,df.date.dt.month]).sum()
value
date date
2020 12 381
2021 1 1811
2 985
3 1500
4 146

Leave a comment