tsibble
R에서 시계열 데이터를 처리하기 위한 전용 자료구조로 zoo
, Z’s Ordered Observations와 xts
, eXtensible Time Series가 많이 활용되었지만, tidyverse
등장이후 시계열 자료구조도 lubridate
팩키지의 등장 이후 시계열이 아닌 많이 사용되는 데이터프레임에서 표준으로 자리를 잡아가고 있으며 tsibble
이 두가지 큰 흐름의 간극을 매워가면서 새로운 지평을 열어가고 있다.
tsibble
기본 1tsibble(tbl_ts
)은 티블(tibble) tidyverse
의 시계열 자료구조를 확장한 것으로 제작되었고. ts
, zoo
, xts
와 비교하여 볼 때 시간 인덱스를 본질적인 데이터 칼럼으로 둠으로써 이질적인 자료구조 처리를 내재적으로 가능하게 제작되었고 변수 하나 혹은 그 이상으로 구성된 “Key” 개념을 도입해서 시간 흐름에 따라 관측점 단위별로 식별이 가능하게 만들었다.
tsibble
객체 생성tsibble()
함수로 tsibble
객체를 생성시킬 수 있을 뿐만 아니라 as_tsibble()
함수는 S3 메쏘드로 다른 객체를 tsibble
객체로 강제변환(coerce)시킨다. ts
, mts
객체는 as_tsibble()
함수로 tsibble
객체를 별도 다른 설정없이 강제변환이 가능한 반면, 티블 혹은 데이터프레임의 경우 인덱스(index)와 키(key) 변수를 식별하기 위해서 추가 작업이 다소 필요하다.
library(tidyverse)
library(tsibble)
library(lubridate)
weather <- nycflights13::weather %>%
select(origin, time_hour, temp, humid, precip)
weather
# A tibble: 26,115 x 5
origin time_hour temp humid precip
<chr> <dttm> <dbl> <dbl> <dbl>
1 EWR 2013-01-01 01:00:00 39.0 59.4 0
2 EWR 2013-01-01 02:00:00 39.0 61.6 0
3 EWR 2013-01-01 03:00:00 39.0 64.4 0
4 EWR 2013-01-01 04:00:00 39.9 62.2 0
5 EWR 2013-01-01 05:00:00 39.0 64.4 0
6 EWR 2013-01-01 06:00:00 37.9 67.2 0
7 EWR 2013-01-01 07:00:00 39.0 64.4 0
8 EWR 2013-01-01 08:00:00 39.9 62.2 0
9 EWR 2013-01-01 09:00:00 39.9 62.2 0
10 EWR 2013-01-01 10:00:00 41 59.6 0
# ... with 26,105 more rows
nycflights13::weather
자료형은 티블로 시간변수는 time_hour
가 되고, 3개 변수 temp
, humid
, precip
은 관측변수가 되고, origin
은 관측지점이 된다. tsibble
자료형을 설명하기 적합한 요소를 두루갖추고 있다. 티블/데이터프레임을 tsibble
객체로 변환시킬 경우 index=
인자로 시계열 정보를 담고 있는 변수를 지정하고 key=
인자를 통해 관측단위 변수를 지정시킨다. 이를 통해서 관측점(행)을 관측단위별 시계열 자료구조로 준비가 완료되었다. tsibble
객체가 생성되면서 내부적으로 키가 있는 경우 키를 기준값으로 정렬이 일차적으로 일어나고 인덱스를 기준으로 다시 한번 정렬된다. 키가 없는 경우 인덱스를 기준으로 정렬된다.
weather_tsbl <- as_tsibble(weather, index=time_hour, key=id(origin))
weather_tsbl
# A tsibble: 26,115 x 5 [1h] <America/New_York>
# Key: origin [3]
origin time_hour temp humid precip
<chr> <dttm> <dbl> <dbl> <dbl>
1 EWR 2013-01-01 01:00:00 39.0 59.4 0
2 EWR 2013-01-01 02:00:00 39.0 61.6 0
3 EWR 2013-01-01 03:00:00 39.0 64.4 0
4 EWR 2013-01-01 04:00:00 39.9 62.2 0
5 EWR 2013-01-01 05:00:00 39.0 64.4 0
6 EWR 2013-01-01 06:00:00 37.9 67.2 0
7 EWR 2013-01-01 07:00:00 39.0 64.4 0
8 EWR 2013-01-01 08:00:00 39.9 62.2 0
9 EWR 2013-01-01 09:00:00 39.9 62.2 0
10 EWR 2013-01-01 10:00:00 41 59.6 0
# ... with 26,105 more rows
tsibble
객체는 자동으로 시계열 기간에 대한 정보를 인식하여 반영시킨다.
Date
혹은 POSITct
는 월단위를 지원하지 않지만 tsibble
은 yearmonth
/yearmth
를 지원한다. weather_tsbl
객체는 시계열 단위 [1h]
와 함께 키도 origin [3]
으로 명시적으로 나타내고 있다.
tsibble
객체 데이터 조작데이터프레임 dplyr
동사 group_by()
에 대응되는 tsibble
index_by()
함수를 사용해서 lubridate
팩키지 함수를 사용해서 시간단위 인덱스 time_hour
변수를 as_date()
함수로 날짜형으로 변환시키고 나서 month()
함수를 사용해서 월단위로 시계열 단위를 변환시킨 후에 최고/최저 온도를 요약하여 정리한다.
weather_tsbl %>%
group_by(origin) %>%
index_by(month = yearmonth(time_hour)) %>%
summarise(
temp_high = max(temp, na.rm = TRUE),
temp_average = mean(temp, na.rm = TRUE),
temp_low = min(temp, na.rm = TRUE)
) %>%
DT::datatable()
데이터프레임을 tsibble
객체로 변환시킬 경우 기본디폴트 설정으로 regular=TRUE
로 되어있다. 하지만, nycflights13::flights
데이터프레임의 경우 carrier
, flight
를 키로 두고 as_tsibble()
함수를 적용시킬 경우 불규칙 시간 간격을 갖는 시계열 데이터가 생성된다.
먼저 lubridate
팩키지 make_datetime()
함수를 가지고 시계열 날짜시간 sched_dep_datetime
변수를 생성시킨다.
flights <- nycflights13::flights %>%
mutate(sched_dep_datetime = make_datetime(year, month, day, hour, minute, tz = "America/New_York")) %>%
select(carrier, flight, year, month, day, hour, minute, sched_dep_datetime, dep_delay)
flights
# A tibble: 336,776 x 9
carrier flight year month day hour minute sched_dep_datetime
<chr> <int> <int> <int> <int> <dbl> <dbl> <dttm>
1 UA 1545 2013 1 1 5 15 2013-01-01 05:15:00
2 UA 1714 2013 1 1 5 29 2013-01-01 05:29:00
3 AA 1141 2013 1 1 5 40 2013-01-01 05:40:00
4 B6 725 2013 1 1 5 45 2013-01-01 05:45:00
5 DL 461 2013 1 1 6 0 2013-01-01 06:00:00
6 UA 1696 2013 1 1 5 58 2013-01-01 05:58:00
7 B6 507 2013 1 1 6 0 2013-01-01 06:00:00
8 EV 5708 2013 1 1 6 0 2013-01-01 06:00:00
9 B6 79 2013 1 1 6 0 2013-01-01 06:00:00
10 AA 301 2013 1 1 6 0 2013-01-01 06:00:00
# ... with 336,766 more rows, and 1 more variable: dep_delay <dbl>
carrier
, flight
변수를 key=
을 설정하여 관측단위로 지정하고 make_datetime()
함수로 생성시킨 sched_dep_datetime
변수를 index=
에 지정하고 regular = FALSE
으로 불규칙 시계열 자료임을 명시적으로 선엉하여 tsibble
객체를 만든다.
flights_tsbl <- as_tsibble(flights, key=id(carrier, flight),
index=sched_dep_datetime,
regular = FALSE)
flights_tsbl
# A tsibble: 336,776 x 9 [!] <America/New_York>
# Key: carrier, flight [5,725]
carrier flight year month day hour minute sched_dep_datetime
<chr> <int> <int> <int> <int> <dbl> <dbl> <dttm>
1 9E 2900 2013 11 3 15 40 2013-11-03 15:40:00
2 9E 2900 2013 11 4 15 40 2013-11-04 15:40:00
3 9E 2900 2013 11 5 15 40 2013-11-05 15:40:00
4 9E 2900 2013 11 6 15 40 2013-11-06 15:40:00
5 9E 2900 2013 11 7 15 40 2013-11-07 15:40:00
6 9E 2900 2013 11 8 15 40 2013-11-08 15:40:00
7 9E 2900 2013 11 9 15 40 2013-11-09 15:40:00
8 9E 2900 2013 11 10 15 40 2013-11-10 15:40:00
9 9E 2900 2013 11 11 15 40 2013-11-11 15:40:00
10 9E 2900 2013 11 12 15 40 2013-11-12 15:40:00
# ... with 336,766 more rows, and 1 more variable: dep_delay <dbl>
flights_tsbl
객체는 A tsibble: 336,776 x 9 [!] <America/New_York>
에서 나타나듯이 [!]
표식을 통해 불규칙 시계열데이터임을 확인할 수 있다. 불규칙 시계열 데이터를 균등간격을 갖는 시계열 데이터로 변환시킬 경우 index_by()
+ summarize()
를 사용한다. A tsibble: 22,789 x 4 [1M]
에 나타난 것처럼 월별 [1M]
로 균등간격을 갖는 시계열로 변환이 되었다.
flights_tsbl %>%
group_by(carrier, flight) %>%
index_by(month = yearmonth(sched_dep_datetime)) %>%
summarise(delay_mean = mean(dep_delay, na.rm=TRUE)) %>%
arrange(desc(delay_mean))
# A tsibble: 22,789 x 4 [1M]
# Key: carrier, flight [5,725]
# Groups: carrier [16]
carrier flight month delay_mean
<chr> <int> <mth> <dbl>
1 UA 372 2013 7 483
2 UA 488 2013 1 379
3 DL 1091 2013 12 378.
4 EV 5017 2013 9 372
5 9E 3552 2013 3 354
6 EV 5537 2013 7 350
7 WN 521 2013 9 346
8 DL 390 2013 9 334
9 EV 4670 2013 6 322
10 EV 3261 2013 12 321
# ... with 22,779 more rows
NA
) 처리 2본격적인 시계열 데이터 분석을 위해서 결측값(NA
)에 대한 면밀한 조사가 선행되어야 한다. 이를 위해서 tsibble
팩키지는 다음 함수를 제공하고 있다.
has_gaps()
scan_gaps()
count_gaps()
fill_gaps()
pedestrian
데이터셋은 tsibble
팩키지에 내장된 tsibble
객체로 IoT 센서데이터를 담고 있는데 결측값이 상당수 포함되어 있다. Sensor
변수가 관측점별 키 역할을 하고 있으며 총 4개 센서가 있다. has_gaps()
함수를 통해 센서 4개 모두 결측값이 존재됨이 확인되었다.
pedestrian
# A tsibble: 66,037 x 5 [1h] <Australia/Melbourne>
# Key: Sensor [4]
Sensor Date_Time Date Time Count
<chr> <dttm> <date> <int> <int>
1 Birrarung Marr 2015-01-01 00:00:00 2015-01-01 0 1630
2 Birrarung Marr 2015-01-01 01:00:00 2015-01-01 1 826
3 Birrarung Marr 2015-01-01 02:00:00 2015-01-01 2 567
4 Birrarung Marr 2015-01-01 03:00:00 2015-01-01 3 264
5 Birrarung Marr 2015-01-01 04:00:00 2015-01-01 4 139
6 Birrarung Marr 2015-01-01 05:00:00 2015-01-01 5 77
7 Birrarung Marr 2015-01-01 06:00:00 2015-01-01 6 44
8 Birrarung Marr 2015-01-01 07:00:00 2015-01-01 7 56
9 Birrarung Marr 2015-01-01 08:00:00 2015-01-01 8 113
10 Birrarung Marr 2015-01-01 09:00:00 2015-01-01 9 166
# ... with 66,027 more rows
has_gaps(pedestrian)
# A tibble: 4 x 2
Sensor .gaps
<chr> <lgl>
1 Birrarung Marr TRUE
2 Bourke Street Mall (North) TRUE
3 QV Market-Elizabeth St (West) TRUE
4 Southern Cross Station TRUE
결측값이 확인되면 다음으로 관심있는 사항은 결측값이 각 센서별로 몇개나 있는지 계산해야 한다. count_gaps()
함수를 통해서 각센서별 기간별 결측값이 발생된 횟수를 파악할 수 있다.
pedestrian %>%
count_gaps(.fill=TRUE) %>%
arrange(desc(.n))
# A tibble: 18 x 4
Sensor .from .to .n
<chr> <dttm> <dttm> <int>
1 Birrarung Marr 2016-10-29 00:00:00 2016-11-28 23:00:00 744
2 Birrarung Marr 2015-10-06 00:00:00 2015-10-31 23:00:00 624
3 Birrarung Marr 2016-04-08 00:00:00 2016-05-03 23:00:00 624
4 Birrarung Marr 2015-05-07 00:00:00 2015-05-31 23:00:00 600
5 Birrarung Marr 2015-11-26 00:00:00 2015-12-04 23:00:00 216
6 Birrarung Marr 2015-11-20 00:00:00 2015-11-24 23:00:00 120
7 Birrarung Marr 2015-11-05 00:00:00 2015-11-06 23:00:00 48
8 QV Market-Elizabeth St (W~ 2015-12-31 00:00:00 2015-12-31 23:00:00 24
9 Southern Cross Station 2016-03-29 02:00:00 2016-03-29 03:00:00 2
10 Birrarung Marr 2015-04-05 02:00:00 2015-04-05 02:00:00 1
11 Birrarung Marr 2016-04-03 02:00:00 2016-04-03 02:00:00 1
12 Bourke Street Mall (North) 2015-04-05 02:00:00 2015-04-05 02:00:00 1
13 Bourke Street Mall (North) 2016-04-03 02:00:00 2016-04-03 02:00:00 1
14 QV Market-Elizabeth St (W~ 2015-04-05 02:00:00 2015-04-05 02:00:00 1
15 QV Market-Elizabeth St (W~ 2016-04-03 02:00:00 2016-04-03 02:00:00 1
16 Southern Cross Station 2015-04-05 02:00:00 2015-04-05 02:00:00 1
17 Southern Cross Station 2016-03-08 02:00:00 2016-03-08 02:00:00 1
18 Southern Cross Station 2016-04-03 02:00:00 2016-04-03 02:00:00 1
다음으로 결측값에 대한 상세한 내용을 파악하려면 scan_gaps()
함수를 통해 파악할 수 있다.
pedestrian %>%
filter(Sensor == "Bourke Street Mall (North)") %>%
scan_gaps()
# A tsibble: 2 x 2 [1h] <Australia/Melbourne>
# Key: Sensor [1]
Sensor Date_Time
<chr> <dttm>
1 Bourke Street Mall (North) 2015-04-05 02:00:00
2 Bourke Street Mall (North) 2016-04-03 02:00:00
ggplot
팩키지를 통해서 결측값을 시각화하여 파악하는 것도 가능하다. 이를 통해서 센서가 정상적으로 동작하지 않는 기간이 언제였는지 쉽게 파악할 수 있다.
pedestrian %>%
count_gaps(.fill=TRUE) %>%
ggplot(aes(x = Sensor, colour = Sensor)) +
geom_linerange(aes(ymin = .from, ymax = .to)) +
geom_point(aes(y = .from)) +
geom_point(aes(y = .to)) +
coord_flip() +
theme(legend.position = "top") +
labs(x="")
센서별로 결측값이 발생한 시간을 파악하게 되면 다음 단계로 결측값을 채워넣는다. fill_gaps()
함수를 사용한다. 시간별로 빈칸을 NA
로 채워넣거나 mean(Count)
평균값을 포함한 다른 값으로도 채워넣는 것이 가능하다.
na_g <- pedestrian %>%
fill_gaps(.full=TRUE) %>%
ggplot(aes(x=Date_Time, y=Count, color=Sensor)) +
geom_line() +
facet_wrap(~Sensor) +
theme(legend.position = "none")
fill_g <- pedestrian %>%
fill_gaps(Count = mean(Count), .full=TRUE) %>%
ggplot(aes(x=Date_Time, y=Count, color=Sensor)) +
geom_line() +
facet_wrap(~Sensor) +
theme(legend.position = "none")
cowplot::plot_grid(na_g, fill_g)
시계열 데이터는 엄격한 시간 순서를 따르기 때문에 이를 기반으로 연산작업을 수행할 수 있다. 대표적인 시계열 연산이 이동평균을 들 수 있다. 명사 tsibble
객체에 다음 세가지 유형의 시계열 동사를 이동 윈도우에 적용시킬 수 있다.
slide()
/slide2()
/pslide()
: 겹치는 시계열 관측점을 갖는 슬라이딩 윈도우tile()
/tile2()
/ptile()
: 겹치지 않는 시계열 관측점을 갖는 타일 윈도우stretch()
/stretch2()
/pstretch()
: 초기 윈도우를 고정시키고 관측점을 추가pedestrian
데이터는 호주 멜버른시 시간대별 보행자수를 담고 있는 시계열 데이터로 Sensor
가 키역할을 담당하고 Date_Time
가 시계열 인덱스를 나타내고 있다. 결측값이 포함되어 있기 때문에 fill_gaps
로 시계열 결측을 처리한다.
pedestrian_tsbl <- pedestrian %>%
fill_gaps(.full = TRUE)
pedestrian_tsbl
# A tsibble: 70,176 x 5 [1h] <Australia/Melbourne>
# Key: Sensor [4]
Sensor Date_Time Date Time Count
<chr> <dttm> <date> <int> <int>
1 Birrarung Marr 2015-01-01 00:00:00 2015-01-01 0 1630
2 Birrarung Marr 2015-01-01 01:00:00 2015-01-01 1 826
3 Birrarung Marr 2015-01-01 02:00:00 2015-01-01 2 567
4 Birrarung Marr 2015-01-01 03:00:00 2015-01-01 3 264
5 Birrarung Marr 2015-01-01 04:00:00 2015-01-01 4 139
6 Birrarung Marr 2015-01-01 05:00:00 2015-01-01 5 77
7 Birrarung Marr 2015-01-01 06:00:00 2015-01-01 6 44
8 Birrarung Marr 2015-01-01 07:00:00 2015-01-01 7 56
9 Birrarung Marr 2015-01-01 08:00:00 2015-01-01 8 113
10 Birrarung Marr 2015-01-01 09:00:00 2015-01-01 9 166
# ... with 70,166 more rows
slide_dbl()
함수를 마치 map_dbl()
와 같이 사용해서 이동평균을 구할 수 있고, .align=
인자를 통해 이동평균 적용 인덱스도 지정시킬 수 있다.
pedestrian_tsbl %>%
group_by(Sensor) %>%
mutate(Daily_MA = slide_dbl(Count, mean, na.rm = TRUE, .size = 3, .align = "center"
))
# A tsibble: 70,176 x 6 [1h] <Australia/Melbourne>
# Key: Sensor [4]
# Groups: Sensor [4]
Sensor Date_Time Date Time Count Daily_MA
<chr> <dttm> <date> <int> <int> <dbl>
1 Birrarung Marr 2015-01-01 00:00:00 2015-01-01 0 1630 NA
2 Birrarung Marr 2015-01-01 01:00:00 2015-01-01 1 826 1008.
3 Birrarung Marr 2015-01-01 02:00:00 2015-01-01 2 567 552.
4 Birrarung Marr 2015-01-01 03:00:00 2015-01-01 3 264 323.
5 Birrarung Marr 2015-01-01 04:00:00 2015-01-01 4 139 160
6 Birrarung Marr 2015-01-01 05:00:00 2015-01-01 5 77 86.7
7 Birrarung Marr 2015-01-01 06:00:00 2015-01-01 6 44 59
8 Birrarung Marr 2015-01-01 07:00:00 2015-01-01 7 56 71
9 Birrarung Marr 2015-01-01 08:00:00 2015-01-01 8 113 112.
10 Birrarung Marr 2015-01-01 09:00:00 2015-01-01 9 166 176.
# ... with 70,166 more rows
일별/주별/월별로 연산작업을 수행하고자 하는 경우 tsibble
객체에 리스트칼럼 개념을 도입할 경우 탈력적으로 작업할 수 있다. 먼저 월별로 시계열 데이터를 묶는 경우를 살펴보자. nest()
함수를 사용해서 월별로 데이터를 묶어낸다.
pedestrian_mth_tsbl <- pedestrian_tsbl %>%
mutate(YrMth = yearmonth(Date_Time)) %>%
nest(-Sensor, -YrMth)
pedestrian_mth_tsbl
# A tibble: 96 x 3
Sensor YrMth data
<chr> <mth> <list>
1 Birrarung Marr 2015 1 <tsibble [744 x 4]>
2 Birrarung Marr 2015 2 <tsibble [672 x 4]>
3 Birrarung Marr 2015 3 <tsibble [744 x 4]>
4 Birrarung Marr 2015 4 <tsibble [721 x 4]>
5 Birrarung Marr 2015 5 <tsibble [744 x 4]>
6 Birrarung Marr 2015 6 <tsibble [720 x 4]>
7 Birrarung Marr 2015 7 <tsibble [744 x 4]>
8 Birrarung Marr 2015 8 <tsibble [744 x 4]>
9 Birrarung Marr 2015 9 <tsibble [720 x 4]>
10 Birrarung Marr 2015 10 <tsibble [743 x 4]>
# ... with 86 more rows
다음으로 앞선 slide_dbl()
함수에 .size=1
로 지정하게 되면 월별 연산작업을 수행할 수 있다. 하지만, 3개월/6개월/12개월 작업을 할 경우 월별 리스트칼럼 데이터를 결합시켜야 한다. 예를 들어 분기별로 통계량을 계산하고자 할 경우 .size=3
으로 지정하고 .bind = TRUE
으로 설정한 후 3개월 이동평균을 계산하는 작업을 수행한다.
pedestrian_mth_tsbl %>%
group_by(Sensor) %>%
mutate(Monthly_MA = slide_dbl(data,
~ mean(.$Count, na.rm = TRUE), .size = 3, .align = "center", .bind = TRUE
))
# A tibble: 96 x 4
# Groups: Sensor [4]
Sensor YrMth data Monthly_MA
<chr> <mth> <list> <dbl>
1 Birrarung Marr 2015 1 <tsibble [744 x 4]> NA
2 Birrarung Marr 2015 2 <tsibble [672 x 4]> 634.
3 Birrarung Marr 2015 3 <tsibble [744 x 4]> 546.
4 Birrarung Marr 2015 4 <tsibble [721 x 4]> 554.
5 Birrarung Marr 2015 5 <tsibble [744 x 4]> 397.
6 Birrarung Marr 2015 6 <tsibble [720 x 4]> 429.
7 Birrarung Marr 2015 7 <tsibble [744 x 4]> 390.
8 Birrarung Marr 2015 8 <tsibble [744 x 4]> 398.
9 Birrarung Marr 2015 9 <tsibble [720 x 4]> 392.
10 Birrarung Marr 2015 10 <tsibble [743 x 4]> 539.
# ... with 86 more rows
filter_index()
함수로 “2015-03”월까지 추출하고 센서(Sensor
)별로 묶은 후에 앞서 정의한 fit_reg()
함수를 적용시켜 회귀분석을 수행한다.
fit_reg <- function(...) {
data <- tibble(...)
fit <- lm(Count ~ Time, data = data)
list(fitted = fitted(fit), resid = residuals(fit))
}
pedestrian_reg_tsbl <- pedestrian %>%
filter_index(~ "2015-03") %>%
nest(-Sensor) %>%
mutate(reg = purrr::map(data, ~ pslide_dfr(., fit_reg, .size = 24 * 7)))
pedestrian_reg_tsbl
# A tibble: 4 x 3
Sensor data reg
<chr> <list> <list>
1 Birrarung Marr <tsibble [2,160 x 4]> <tibble [334,825 x 2~
2 Bourke Street Mall (North) <tsibble [1,032 x 4]> <tibble [145,321 x 2~
3 QV Market-Elizabeth St (West) <tsibble [2,160 x 4]> <tibble [334,825 x 2~
4 Southern Cross Station <tsibble [2,160 x 4]> <tibble [334,825 x 2~
pedestrian_reg_tsbl$reg[[1]]
# A tibble: 334,825 x 2
fitted resid
<dbl> <dbl>
1 NA NA
2 161. 1469.
3 170. 656.
4 179. 388.
5 189. 75.3
6 198. -59.1
7 208. -131.
8 217. -173.
9 226. -170.
10 236. -123.
# ... with 334,815 more rows