block by timelyportfolio 9672236

rCharts d3.js Interactive Look at bfast breaks

Full Screen

Interactively analyze the effect of h on the R package bfast breakpoints with a d3.js visualization using rCharts.

Code to replicate and more

Original R bfast code from blog post

For a better understanding of bfast please see the following paper.

Verbesselt J, Hyndman R, Newnham G, Culvenor D (2010)
Detecting Trend and Seasonal Changes in Satellite Image Time Series.
Remote Sensing of Environment, 114(1), 106–115.
http://dx.doi.org/10.1016/j.rse.2009.08.014

S&P 500 data from Yahoo! Finance - used for personal education purposes

code.R

#sample bfast code from blog post
#http://timelyportfolio.blogspot.com/2012/04/structural-breaks-bull-or-bear.html
#analyze breakpoints with the R package bfast
#please read the paper 
#Verbesselt J, Hyndman R, Newnham G, Culvenor D (2010)
#Detecting Trend and Seasonal Changes in Satellite Image Time Series.
#Remote Sensing of Environment, 114(1), 106–115.
#http://dx.doi.org/10.1016/j.rse.2009.08.014

require(bfast)
require(quantmod)

GSPC <- getSymbols("^GSPC",from="1950-01-01", auto.assign=F)
#convert to log price
GSPC.monthly <- log(to.monthly(GSPC)[,4])
#get monthly returns for the close price
#not necessary, leave in price form
#GSPC.return <- monthlyReturn(GSPC[,4])
#need ts representation so do some brute force conversion
GSPC.ts <- ts(as.vector(GSPC.monthly["1951-01::"]),start=c(1951,1),frequency=12)
#look at the stl Seasonal-Trend decomposition procedure already in R
GSPC.stl <- stl(GSPC.ts,s.window="periodic")
plot(GSPC.stl,main="STL Decomposition of S&P 500")


#get the results from bfast
#adjusting h lower will result in more breakpoints
GSPC.bfast <- bfast(GSPC.ts,h=0.2,max.iter=1,season="none")

plot(GSPC.bfast,type="components",ylim=c(3,max(GSPC.monthly)+1),main="S&P 500 with bfast Breakpoints and Components")
plot(GSPC.bfast,type="trend",ylim=c(3,max(GSPC.monthly)+1),main="S&P 500 with bfast Trend Breakpoints")


#see everything with type="all" but in bfast calculation set seasonal to "none"
#play away with this
#plot(GSPC.bfast,type="all")

#do some additional plotting
#[[1]] is used since for speed I only did one iteration
#could plot each iteration if I did multiple
plot(GSPC.bfast$Yt/GSPC.bfast$output[[1]]$Tt-1,
     main="bfast Remainder as % of S&P 500 Price",
     xlab=NA, ylab="remainder (% of price)",bty="l")
#add vertical line for the breakpoints
abline(v=breakdates(GSPC.bfast$output[[1]]$bp.Vt),col="gray70")
#add horizontal line at 0
abline(h=0,col="black",lty=2)
text(x=breakdates(GSPC.bfast$output[[1]]$bp.Vt),y=par("usr")[3]+.01,
     labels=breakdates(GSPC.bfast$output[[1]]$bp.Vt,format.times=TRUE),
     srt=90,pos=4,cex=0.75)



require(rCharts)
options(viewer=NULL)

bfast.df <- data.frame(
  date = format(index(as.xts(GSPC.bfast$output[[1]]$Tt)),"%Y-%m-%d")#as.Date(index(as.xts(GSPC.bfast$output[[1]]$Tt)))
  ,bfast = as.numeric(GSPC.bfast$output[[1]]$Tt)
)
#just use dimple to start
dBfast <- dPlot(
  price ~ date,
  data = data.frame(
    date = format(as.Date(index(GSPC.monthly)),"%Y-%m-%d"),#as.numeric(as.Date(index(GSPC.monthly)))
    price = as.numeric(GSPC.monthly)
  ),
  type = "line"
)
#set up a time based x axis
dBfast$xAxis(
  #type = "addCategoryAxis",
  type = "addTimeAxis"
  ,inputFormat = "%Y-%m-%d"
  ,outputFormat = "%Y"
)
dBfast$yAxis(
  overrideMin = 0,
  overrideMax = 8
)
dBfast
#add the bfast layer
#with new dataset for layer
dBfast$templates$script = "../rCharts/inst/libraries/dimple/layouts/chart.html"
dBfast$layer(
  x = "date",
  y = "bfast",
  data = bfast.df,
  type = "line"
)
dBfast

# various cleanup experiments
# what should be in dimple/rCharts by default
# or made easier
dBfast$setTemplate(
  afterScript = '
    <script>
      myChart.series[1].shapes
        .transition().duration(10).delay(1000)
          .style("stroke","#AD5277")
          .style("stroke-dasharray",[10,5])
      //delete some of the ticks
      myChart.svg.select(".axis").selectAll(".tick")[0].forEach(function(d,i){
          if (!(+d3.time.format("%Y")(new Date(+d3.select(d).datum())) % 10 == 0)) {
            d.remove()
          }
        })
      myChart.svg.select(".axis").selectAll(".tick text")
        .attr("transform","none")
        .style("text-anchor","middle");
    </script>
  '
)
dBfast



#start working on an interactive view with a changing h for bfast
#this gives us each of the breakpoints and the jump to the successive breakpoint
#to get starting point just the point in time series
GSPC.bfast$Mags
#this might be an easier way to get
GSPC.bfast$output[[1]]$Tt[
  c(
    GSPC.bfast$output[[1]]$bp.Vt$breakpoints,
    GSPC.bfast$output[[1]]$bp.Vt$breakpoints+1)
]
as.xts(GSPC.bfast$output[[1]]$Tt)[
  c(
    GSPC.bfast$output[[1]]$bp.Vt$breakpoints,
    GSPC.bfast$output[[1]]$bp.Vt$breakpoints+1)
  ]
#also need to get begin and end points
head(as.xts(GSPC.bfast$output[[1]]$Tt),1)
tail(as.xts(GSPC.bfast$output[[1]]$Tt),1)
#so rbind the begin, breaks, and end
breaks <- rbind(
  head(as.xts(GSPC.bfast$output[[1]]$Tt),1)
  ,as.xts(GSPC.bfast$output[[1]]$Tt)[
    c(
      GSPC.bfast$output[[1]]$bp.Vt$breakpoints,
      GSPC.bfast$output[[1]]$bp.Vt$breakpoints+1)
    ]
  ,tail(as.xts(GSPC.bfast$output[[1]]$Tt),1)
)


#so let's try to make a function out of it to get json for linebreak
#convert bfast object to a list ready for plotting
#with rCharts and linebreak template
#should be
#key:{
#    line: timeseries data (x and y) in array of objects [{x:, y:}]
#    breaks: start and end points (x and y) in array of objects [{x:, y:}]
#}
bfast_list <- function ( bf, bfName ){
  breaks = rbind(
    head(as.xts(bf$output[[1]]$Tt),1)
    ,as.xts(bf$output[[1]]$Tt)[
      c(
        bf$output[[1]]$bp.Vt$breakpoints,
        bf$output[[1]]$bp.Vt$breakpoints+1)
      ]
    ,tail(as.xts(bf$output[[1]]$Tt),1)
  )
  breaksList <- apply(
    matrix(1:nrow(breaks),ncol=nrow(breaks)/2,byrow=F),
    MARGIN=2,
    function(x){
      return(data.frame(
        date = as.numeric(as.Date(index(breaks[x]))),
        x = breaks[x]
      ))
    }
  )

    
  dataSeries  <- list(
    list(
      line = data.frame(
        date = as.numeric(as.Date(index(as.xts(bf$Yt)))),
        x = as.xts(bf$Yt)
      ),
      breaks = unname(breaksList)
    )
  )
  names(dataSeries) <- bfName
  return ( dataSeries )
}

rCharts_linebreak <- setRefClass(
  "rCharts_linebreak",
  contains = "rCharts",
  methods = list(
    initialize = function(){
      callSuper(); 
    },
    getPayload = function(chartId){
      data = jsonlite::toJSON( params$data )
      opts = toJSON2(params[!(names(params) %in% c('data'))])
      list(chartParams = opts, data = data)
    }
  )
)

bP <- rCharts_linebreak$new()
bP$set(
  x = "date",
  y = "x",
  data = bfast_list(GSPC.bfast, "SP500")
)
bP$setLib( "." )
bP$set(
  bodyattrs = 'ng-app="myApp" ng-controller="MainCtrl"'
)
bP$setTemplate(
  chartDiv = 
    '
  <form class="well">
  <label><b>Select  series :</b></label>
  <select class="form-control" ng-model="selected" 
  ng-options="key for (key,value) in data">   
  </select><br>
  </form>
  <div id="graphic">
  </div>
  '
  ,afterScript = sprintf(
    '
  <script>
  var app = angular.module("myApp", []);
  app.controller("MainCtrl", function($scope, $window){
  $scope.data = data;
  
  $scope.drawChart = function(){
  drawChart( $scope.selected )
  }
  
  $scope.selected = $scope.data["%s"]
  
  if( $scope.selected ){
  $scope.drawChart()
  }
  
  $scope.updateChart = function(){
  updateChart( $scope.selected )
  }
  
  $scope.$watch("selected", function(){
  if( $scope.selected ){
  $scope.updateChart()
  }
  })
  });
  </script>
  ',
  names(bP$params$data)[1])
)
#manually set y in original version
#use template with y scaled by data
bP$templates$script = "./layouts/chart_flexy.html"
bP





#so now we could try lots of h in bfast like this
#and then interactively explore like this
bfastList <- sapply(
  seq(from=0.05,to=0.2,by=0.05),
  function(h){
    bfast_list(
      bf = bfast(GSPC.ts,h=h,max.iter=1,season="none"),#,hpc="foreach"), #hpc "none" default
      bfName = gsub(x=paste0("h",h),pattern="\\.",replacement="")
    )#maybe but don't think so [[1]]
  }
)
#bfastList <- lapply(1:length(bfastList),function(x){return(bfastList[[x]][1])})
#names(bfastList) <- paste0("h", seq(from=0.05,to=0.2,by=0.05))

#use previous graph and just change data to our list
bP$params$data = bfastList
bP$setTemplate(
  afterScript = gsub(
    x= bP$templates$afterScript,
    pattern = "h005",
    replacement = names(bfastList[1])
  )
)
bP