본문 바로가기
  • plotly로 바로쓰는 동적시각화 in R & 파이썬
실전에서 바로 쓰는 시계열 데이터 처리와 분석 in R/못다한 이야기

시계열 변화점 탐지

by 아참형인간 2021. 7. 17.
changepoint.knit

변화점(change point)

시계열 데이터는 시간에 따른 데이터 변화량을 기록하는 데이터로 다른 데이터와는 조금 다른 몇가지 특징을 가진다. 이 특징은 계절성, 자기상관성 등이 있지만 또 하나의 특징으로 말할 수 있는 것이 특정 시기에 발생한 외부적 요인에 따른 데이터의 급격한 변화 요인이다.

필자가 생각하는 변화점의 예로 가장 적절한 것은 미국의 9.11테러로 인한 증시에 변화량이라고 생각한다.

2001년 9월 11일 발생한 테러로 인해 뉴욕 증시 뿐만아니라 한국 증시도 패닉상태였다. 하루에 종합주가지수는 12%떨어졌는데 당시 개별 주식의 하한가가 -15%인 것을 감안하면 거의 모든 종목이 하한가였다는 것이다. 실제로 하한가 종목은 621개이고 상승종목은 15개에 불과했다.(이날 상승한 종목은 대체 뭐지???)

또한 가장 가까운 예로 2020년 2월까지 안정적 흐름으로 상승추세를 보이던 우리나라 신규 취업자의 흐름이 2020년 3월 갑자기 하락추세로 반전되었다. 코로나 19로 인해..

이와 같이 시계열 데이터는 특정 시기에 발생한 외부 충격에 의해 데이터의 추세가 갑자기 바뀌는 상황이 존재한다. 위에서 설명한 두가지 예는 데이터의 흐름을 보면 육안으로 확실히 데이터의 변화점을 알 수 있지만 육안으로 발견되지 않는 변화점은 어떻게 알 수 있을까?

R에서 이러한 시계열 데이터에 대한 변화점을 찾아내기 위해 많은 패키지가 제공되고 있지만 이 포스트에서는 changepint 패키지를 사용하여 시계열 데이터 상의 변화점을 찾아내는 방법을 알아보자.

changepoint 패키지

changepoint 패키지는 일련의 수치형 벡터에서 데이터가 변화되는 변화점을 찾아내는데 사용되는 함수를 제공한다. 사실 R에서 변화점을 찾아내는 패키지는 mcp, segmented, bcp, changepoint 등 여러가지가 제공된다.

이 포스트에서는 changepoint 패키지 위주로 변화점을 찾아보도록 한다.

우선 changepoint 패키지를 사용하기 위해서 패키지를 설치해주고 로딩한다.

if (!require(changepoint)) {
  install.packages('changepoint')
  library(changepoint)
}

changepoint 는 변화점을 찾기 위해 cpt.mean(), cpt.meanvar() , cpt.var() 의 세가지 함수를 제공한다.

cpt.mean()

cpt.mean() 은 data 매개변수에 의해 전달된 numeric vector의 변화점을 평균의 변화량을 기반으로 찾아내는 함수이다. cpt.mean() 은 변화점을 찾기 위해 여러가지 옵션을 매개변수를 통해 설정할 수 있는데 주요 옵션은 다음과 같다.

  • method : ‘AMOC’, ‘PELT’, ‘SegNeig’, ’BinSeg’의 네가지 옵션중에 하나를 선택한다. ’AMOC’는 단일 변화점(single changepoint)를 설정하고, ’PELT’와 ’SegNeig’는 여러개의 변화점(multiple changepoint), ’BinSeg’는 C에서 제공하는 Binary Segmentation Algorithm을 사용하여 변화점을 설정한다. 이 방법은 공간을 계속 2개씩 잘라가면서 변화점을 찾는 방법이다.
library(changepoint)
library(patchwork)
library(tidyverse)
## cpt.mean
cpmean.BinSeg.students <- cpt.mean(data = pull(students[, 2]), method = 'BinSeg')
cpmean.AMOC.students <- cpt.mean(data = pull(students[, 2]), method = 'AMOC')
cpmean.PELT.students <- cpt.mean(data = pull(students[, 2]), method = 'PELT')

p.BinSeg <- ggplot(students, aes(연도, 학생수계)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = pull(students[cpmean.BinSeg.students@cpts, 1]), color = 'red') + 
  ggtitle('BinSeg')

p.AMOC <- ggplot(students, aes(연도, 학생수계)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = pull(students[cpmean.AMOC.students@cpts, 1]), color = 'blue') + 
  ggtitle('AMOC')

p.PELT <- ggplot(students, aes(연도, 학생수계)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = pull(students[cpmean.PELT.students@cpts, 1]), color = 'green') + 
  ggtitle('PELT')

p.BinSeg / p.AMOC / p.PELT + 
  plot_annotation(title = '연도별 학생수 변화점 - cpt.mean', 
                  theme = theme(plot.title = element_text(size = 16, hjust = 0.5)))

## cpt.mean
cpmean.BinSeg.employees <- cpt.mean(data = employees[, 2], method = 'BinSeg')
cpmean.AMOC.employees <- cpt.mean(data = employees[, 2], method = 'AMOC')
cpmean.PELT.employees <- cpt.mean(data = employees[, 2], method = 'PELT')

p.BinSeg <- ggplot(employees, aes(time, total)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = employees[cpmean.BinSeg.employees@cpts, 1], color = 'red') + 
  ggtitle('BinSeg')

p.AMOC <- ggplot(employees, aes(time, total)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = employees[cpmean.AMOC.employees@cpts, 1], color = 'blue') + 
  ggtitle('AMOC')

p.PELT <- ggplot(employees, aes(time, total)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = employees[cpmean.PELT.employees@cpts, 1], color = 'green') + 
  ggtitle('PELT')


p.BinSeg / p.AMOC / p.PELT + 
  plot_annotation(title = '월별 취업자수 변화점 - cpt.mean', 
                  theme = theme(plot.title = element_text(size = 16, hjust = 0.5)))

## cpt.mean
cpmean.BinSeg.covid19 <- cpt.mean(data = covid19[,3], method = 'BinSeg')
cpmean.AMOC.covid19 <- cpt.mean(data = covid19[,3], method = 'AMOC')
cpmean.PELT.covid19 <- cpt.mean(data = covid19[,3], method = 'PELT')

p.BinSeg <- ggplot(covid19, aes(date, `0-9세`)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = covid19[cpmean.BinSeg.covid19@cpts, 1], color = 'red') + 
  ggtitle('BinSeg')

p.AMOC <- ggplot(employees, aes(time, total)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = covid19[cpmean.AMOC.covid19@cpts, 1], color = 'blue') + 
  ggtitle('AMOC')

p.PELT <- ggplot(employees, aes(time, total)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = covid19[cpmean.PELT.covid19@cpts, 1], color = 'green') + 
  ggtitle('PELT')


p.BinSeg / p.AMOC / p.PELT + 
  plot_annotation(title = '일별 코로나 확진자수(0~9세) 변화점 - cpt.mean', 
                  theme = theme(plot.title = element_text(size = 16, hjust = 0.5)))

cpt.meanvar()

cpt.meanvar() 는 data 매개변수에 의해 전달된 numeric vector의 변화점을 평균과 분산의 변화량을 기반으로 찾아내는 함수이다.

library(changepoint)
library(patchwork)
## cpt.meanvar
cpmeanvar.BinSeg.students <- cpt.meanvar(data = pull(students[, 2]), method = 'BinSeg')
cpmeanvar.AMOC.students <- cpt.meanvar(data = pull(students[, 2]), method = 'AMOC')
cpmeanvar.PELT.students <- cpt.meanvar(data = pull(students[, 2]), method = 'PELT')

p.BinSeg <- ggplot(students, aes(연도, 학생수계)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = pull(students[cpmeanvar.BinSeg.students@cpts, 1]), color = 'red') + 
  ggtitle('BinSeg')

p.AMOC <- ggplot(students, aes(연도, 학생수계)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = pull(students[cpmeanvar.AMOC.students@cpts, 1]), color = 'blue') + 
  ggtitle('AMOC')

p.PELT <- ggplot(students, aes(연도, 학생수계)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = pull(students[cpmeanvar.PELT.students@cpts, 1]), color = 'green') + 
  ggtitle('PELT')

p.BinSeg / p.AMOC / p.PELT + 
  plot_annotation(title = '연도별 학생수 변화점 - cpt.meanvar', 
                  theme = theme(plot.title = element_text(size = 16, hjust = 0.5)))

## cpt.meanvar
cpmeanvar.BinSeg.employees <- cpt.meanvar(data = employees[, 2], method = 'BinSeg')
cpmeanvar.AMOC.employees <- cpt.meanvar(data = employees[, 2], method = 'AMOC')
cpmeanvar.PELT.employees <- cpt.meanvar(data = employees[, 2], method = 'PELT')

p.BinSeg <- ggplot(employees, aes(time, total)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = employees[cpmeanvar.BinSeg.employees@cpts, 1], color = 'red') + 
  ggtitle('BinSeg')

p.AMOC <- ggplot(employees, aes(time, total)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = employees[cpmeanvar.AMOC.employees@cpts, 1], color = 'blue') + 
  ggtitle('AMOC')

p.PELT <- ggplot(employees, aes(time, total)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = employees[cpmeanvar.PELT.employees@cpts, 1], color = 'green') + 
  ggtitle('PELT')


p.BinSeg / p.AMOC / p.PELT + 
  plot_annotation(title = '월별 취업자수 변화점 - cpt.meanvar', 
                  theme = theme(plot.title = element_text(size = 16, hjust = 0.5)))

## cpt.meanvar
cpmeanvar.BinSeg.covid19 <- cpt.meanvar(data = covid19[,3], method = 'BinSeg')
cpmeanvar.AMOC.covid19 <- cpt.meanvar(data = covid19[,3], method = 'AMOC')
cpmeanvar.PELT.covid19 <- cpt.meanvar(data = covid19[,3], method = 'PELT')

p.BinSeg <- ggplot(covid19, aes(date, `0-9세`)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = covid19[cpmeanvar.BinSeg.covid19@cpts, 1], color = 'red') + 
  ggtitle('BinSeg')

p.AMOC <- ggplot(employees, aes(time, total)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = covid19[cpmeanvar.AMOC.covid19@cpts, 1], color = 'blue') + 
  ggtitle('AMOC')

p.PELT <- ggplot(employees, aes(time, total)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = covid19[cpmeanvar.PELT.covid19@cpts, 1], color = 'green') + 
  ggtitle('PELT')


p.BinSeg / p.AMOC / p.PELT + 
  plot_annotation(title = '일별 코로나 확진자수(0~9세) 변화점 - cpt.meanvar', 
                  theme = theme(plot.title = element_text(size = 16, hjust = 0.5)))

cpt.var()

cpt.var() 는 data 매개변수에 의해 전달된 numeric vector의 변화점을 분산의 변화량을 기반으로 찾아내는 함수이다.

library(changepoint)
library(patchwork)
## cpt.var
cpvar.BinSeg.students <- cpt.var(data = pull(students[, 2]), method = 'BinSeg')
cpvar.AMOC.students <- cpt.var(data = pull(students[, 2]), method = 'AMOC')
cpvar.PELT.students <- cpt.var(data = pull(students[, 2]), method = 'PELT')

p.BinSeg <- ggplot(students, aes(연도, 학생수계)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = pull(students[cpvar.BinSeg.students@cpts, 1]), color = 'red') + 
  ggtitle('BinSeg')

p.AMOC <- ggplot(students, aes(연도, 학생수계)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = pull(students[cpvar.AMOC.students@cpts, 1]), color = 'blue') + 
  ggtitle('AMOC')

p.PELT <- ggplot(students, aes(연도, 학생수계)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = pull(students[cpvar.PELT.students@cpts, 1]), color = 'green') + 
  ggtitle('PELT')

p.BinSeg / p.AMOC / p.PELT + 
  plot_annotation(title = '연도별 학생수 변화점 - cpt.var', 
                  theme = theme(plot.title = element_text(size = 16, hjust = 0.5)))

## cpt.var
cpvar.BinSeg.employees <- cpt.var(data = employees[, 2], method = 'BinSeg')
cpvar.AMOC.employees <- cpt.var(data = employees[, 2], method = 'AMOC')
cpvar.PELT.employees <- cpt.var(data = employees[, 2], method = 'PELT')

p.BinSeg <- ggplot(employees, aes(time, total)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = employees[cpvar.BinSeg.employees@cpts, 1], color = 'red') + 
  ggtitle('BinSeg')

p.AMOC <- ggplot(employees, aes(time, total)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = employees[cpvar.AMOC.employees@cpts, 1], color = 'blue') + 
  ggtitle('AMOC')

p.PELT <- ggplot(employees, aes(time, total)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = employees[cpvar.PELT.employees@cpts, 1], color = 'green') + 
  ggtitle('PELT')


p.BinSeg / p.AMOC / p.PELT + 
  plot_annotation(title = '월별 취업자수 변화점 - cpt.var', 
                  theme = theme(plot.title = element_text(size = 16, hjust = 0.5)))

## cpt.var
cpvar.BinSeg.covid19 <- cpt.var(data = covid19[,3], method = 'BinSeg')
cpvar.AMOC.covid19 <- cpt.var(data = covid19[,3], method = 'AMOC')
cpvar.PELT.covid19 <- cpt.var(data = covid19[,3], method = 'PELT')

p.BinSeg <- ggplot(covid19, aes(date, `0-9세`)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = covid19[cpvar.BinSeg.covid19@cpts, 1], color = 'red') + 
  ggtitle('BinSeg')

p.AMOC <- ggplot(employees, aes(time, total)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = covid19[cpvar.AMOC.covid19@cpts, 1], color = 'blue') + 
  ggtitle('AMOC')

p.PELT <- ggplot(employees, aes(time, total)) + 
  geom_line(aes(group = 1)) + 
  geom_vline(xintercept = covid19[cpvar.PELT.covid19@cpts, 1], color = 'green') + 
  ggtitle('PELT')


p.BinSeg / p.AMOC / p.PELT + 
  plot_annotation(title = '일별 코로나 확진자수(0~9세) 변화점 - cpt.var', 
                  theme = theme(plot.title = element_text(size = 16, hjust = 0.5)))

댓글