Time Series Analysis

누락된 시계열 데이터

needmorecaffeine 2023. 3. 7. 21:56

해당 포스팅은 "실전 시계열 분석" 교재와 실습코드를 기반으로 작성되었습니다.

 

 


 

 시계열 데이터는 다른 데이터와 마찬가지로 누락된 값을 가질 수 있다.

 

오히려 비시계열 데이터와 다르게 같은 시간축의 시계열 데이터를 샘플링하는게 더 까다로울 수 있기에 누락된 값을 더 많이 가질 수 있다.

 

이런 누락된 데이터 문제를 해결하기 위해 쓰이는 일반적인 방법은 다음과 같다.

 

  • 대치법 (Imputation) : 데이터셋 전체의 관측에 기반해 누락된 데이터를 채워넣는 방법
  • 보간법 (Interpolation) : 대치법의 한 형태로 인접한 데이터를 사용하여 누락된 데이터를 추정하는 방법
  • 누락된 데이터의 기간을 아예 사용하지 않는 방법

 

1. 데이터셋

 

기본 데이터는 1948년 미국 정부가 발표한 월간 실업 자료이고 실습을 위해 임의로 누락된 데이터로 만든다.

 

누락된 데이터는 두 종류로 데이터를 임의로 누락한 버전, 실업이 가장 높은 월에서 누락된 데이터가 많도록 한 버전이다. 

이 두 데이터를 통해 대치법이 어덯게 다르게 작동하는지 알아본다.

 

## zoo : 시계열 기능 제공 패키지
require(zoo)
require(data.table)
library(readr)
unemp <- read_csv("Desktop/Time_Series/UNRATE.csv")

unemp <- data.table(unemp)
unemp[, DATE := as.Date(DATE)]
setkey(unemp, DATE)

# 임의로 누락된 데이터로 구성된 데이터셋을 생성
rand.unemp.idx <- sample(1:nrow(unemp), .1*nrow(unemp))
rand.unemp <- unemp[-rand.unemp.idx]

# 실업률이 높을 때 누락될 가능성이 더 큰 데이터로 구성된 데이터셋
hign.unemp.idx <- which(unemp$UNRATE > 8)
num.to.select <- .2 * length(high.unemp.idx)
high.unemp.idx <- sample(high.unemp.idx,)
bias.unemp <- unemp[-high.unemp.idx]

# 롤링 조인으로 시작, 종료 날짜 사이의 가용한 모든 날짜의 순서를 생성
all.dates <- seq(from = unemp$DATE[1], to = tail(unemp$DATE, 1), by = "months")
rand.unemp = rand.unemp[J(all.dates), roll = 0]
bias.unemp = bias.unemp[J(all.dates), roll = 0]

## 누락된 데이터 레이블링
rand.unemp[, rpt := is.na(UNRATE)]

 

 

2. Forward Fill

 

 Forward Fill은 누락된 값이 나타나기 직전의 값으로 누락된 값을 채우는 방법이다.

 

별도의 연산없이 간단히 수행 가능하고 시간상 누락된 지점의 가장 최근 측정값을 사용하기에 타당해 자주 사용되는 방식이다.

 

zoo 패키지의 na.locf를 이용해 쉽게 적용 가능하다.

 

> rand.unemp[, impute.ff := na.locf(UNRATE, na.rm = FALSE)]
> bias.unemp[, impute.ff := na.locf(UNRATE, na.rm = FALSE)]

> unemp[350:400, plot (DATE, UNRATE, col = 1, lwd = 2, type = 'b')]
> rand.unemp[350:400, lines(DATE, impute.ff, col = 2, lwd = 2, lty = 2)]
> rand.unemp[350:400][rpt = TRUE, points(DATE, impute.ff, col = 2, pch = 6, cex = 2)]

 

 

도식에서 역삼각형 기호가 forward fill로 채워진 부분을 의미하며 빨간색 실선이 그 시계열을 나타낸다. 

 

도식을 통해 알 수 있듯이 채워진 값은 실제값 동그라미와 그 시계열인 검은색 실선보다 많이 벗어나지 않는다는 것을 알 수 있다.

 

Backward Fill도 실행할 수 있지만 이는 look ahead의 일종으로 예측과 같은 태스크에 적합하지 않다.

 


 

3. Moving Average

 

 Moving Average, 즉 이동평균은 과거의 값으로 미래의 값을 예측한다는 점에서 forward fill과 유사하나 이동평균은 최근 과거의 여러 시간대를 입력으로 사용한다는 점에서 다르다.

 

특히 noise가 많으면 가장 최근 시점의 데이터 하나만을 사용하는 forward fill은 개별 데이터의 신뢰도가 떨어져 정확한 누락 데이터의 대치가 이뤄지지 않기에, 이런 경우 과거 여러 시간대의 평균으로 대치하는 이동평균을 사용하는 것이 적합하다.

 

예를 들어 성장 중인 사업의 연간 판매량 데이터의 경우, 성장 중이라는 선형적인 추세를 반영해 누락된 데이터를 채우는 것이 옳다. 이런 경우에서 이동 평균을 사용한다면 실제 추세 대비 그 판매량을 과소 평가할 수 있다.

 

반대로 선형적인 추세를 가지지 않는 데이터에 대해 선형 보간법을 사용할 경우 이는 틀린 추정이 된다.

 

 이동평균도 마찬가지로 lookahead, 즉 산술평균을 낼 때 과거 시간대만이 아닌 미래 시간대를 포함해 대치하는 것은 적절하지 않을 수 있다.

 

아래 코드는 lookahead를 하는 경우와 하지 않는 경우, 두가지 모두를 수행한 것이다.

 

## rolling mean with a lookahead
rand.unemp[, impute.rm.lookahead := rollapply(c(NA, UNRATE, NA), 3,
             function(x) {
                         if (!is.na(x[1])) x[1] else mean(x, na.rm = TRUE)
                         })]         
bias.unemp[, impute.rm.lookahead := rollapply(c(NA, UNRATE, NA), 3,
             function(x) {
                         if (!is.na(x[1])) x[1] else mean(x, na.rm = TRUE)
                         })]         

## rolling mean without a lookahead
rand.unemp[, impute.rm.nolookahead := rollapply(c(NA, NA, UNRATE), 3,
             function(x) {
                         if (!is.na(x[3])) x[3] else mean(x, na.rm = TRUE)
                         })]         
bias.unemp[, impute.rm.nolookahead := rollapply(c(NA, NA, UNRATE), 3,
             function(x) {
                         if (!is.na(x[3])) x[3] else mean(x, na.rm = TRUE)
                         })]         

## to plot a sample graph showing the flat portions
png("maplot.png")
use.idx = 150:200
unemp[use.idx, plot(DATE, UNRATE, col = 1, type = 'b')]
rand.unemp[use.idx, lines(DATE, impute.rm.nolookahead, col = 2)]
rand.unemp[use.idx][rpt == TRUE, points(DATE, impute.rm.nolookahead, col = 2, lwd = 3)]
rand.unemp[use.idx, lines(DATE, impute.rm.lookahead, col = 3)]
rand.unemp[use.idx][rpt == TRUE, points(DATE, impute.rm.lookahead, col = 3, lwd = 3)]
dev.off()

 


 

4. Interpolation

 

 Interpolation, 즉 보간법은 전체 데이터를 기하학적인 행태로 제한하여 누락된 데이터 값을 결정하는 방식이다.

 

Linear Interpolation, 선형보간법의 경우 누락된 데이터가 주변 데이터와 선형적인 일관성을 갖도록 제한하는 방식이다.

 

선형보간법은 시스템이 선형적인 패턴에 따라서 동작한다는 사실을 알고 있는 경우, 그 선형적 패턴에 부합하도록 누락값을 대치할 수 있도록 제한하는 것을 의미한다.

 

 선형보간법 뿐만 아니라 선형성이 아닌 다항식을 따르는 다항 보간법 또한 같은 방식으로 구현할 수 있다.

 

아래는 선형 보간법을 approx를 통해, 다항 보간법을 spline을 통해 구현한 코드이다.

## linear interpolation
rand.unemp[, impute.li := na.approx(UNRATE)]
bias.unemp[, impute.li := na.approx(UNRATE)]

## polynomial interpolation
rand.unemp[, impute.sp := na.spline(UNRATE)]
bias.unemp[, impute.sp := na.spline(UNRATE)]

png("splineplot.png")
use.idx = 650:680
unemp[use.idx, plot(DATE, UNRATE, col = 1, type = 'b')]
rand.unemp[use.idx, lines(DATE, impute.li, col = 2)]
rand.unemp[use.idx][rpt == TRUE, points(DATE, impute.li, col = 2, lwd = 3)]
rand.unemp[use.idx, lines(DATE, impute.sp, col = 3)]
rand.unemp[use.idx][rpt == TRUE, points(DATE, impute.sp, col = 3, lwd = 3)]

 

 


Ref

 

  • Oreilly "실전시계열 분석" 2장