Friday, August 3, 2012

Horizon Plots in Base Graphics

for background please see prior posts More on Horizon Charts, Application of Horizon Plots, Horizon Plot Already Available, and Cubism Horizon Charts in R

There are three primary graphics routes in R (base graphics, lattice, and ggplot2), and each have their zealots.  Last time in More on Horizon Charts, I used lattice and latticeExtra. This time we will build horizon plots in base graphics, and I was pleased with the result. Unfortunately, there is one small issue in that the points of change from positive to negative overlap.  Please let me know if you have a solution. Thanks to helpful readers for their comments and code changes, this is now fixed.

From TimelyPortfolio
From TimelyPortfolio
From TimelyPortfolio
From TimelyPortfolio
From TimelyPortfolio
From TimelyPortfolio
From TimelyPortfolio

Now, let’s implement a for loop and mirror the negative values.

From TimelyPortfolio

With horizon chart functionality in base graphics, hopefully we can add this type now to other packages.  Here is a potential example using quantmod.

From TimelyPortfolio

 

R code in GIST (do raw for copy/paste)

require(RColorBrewer)
require(quantmod)
require(PerformanceAnalytics)
data(managers)
#let's do managers from 2002 to 2004 to get positive and negative
x <- cumprod(1+managers["2002::2004"])[,1] - 1
#get some decent colors from RColorBrewer
#we will use colors on the edges so 2:4 for red and 7:9 for blue
col.brew <- brewer.pal(name="RdBu",n=10)
#get this to ease using it later
n<-nrow(x)
#set scale to be 10%
horizonscale=0.1
#remove space around chart
par(mar=c(2,1,1,1))
plot(index(x), coredata(x), type="n", bty="n", las=1, yaxt="n", xlab=NA, ylab=NA, ylim=c(-horizonscale,horizonscale))
#thanks http://stackoverflow.com/questions/9630014/polygon-for-xts-objects
#draw first positive band
polygon(
index(x)[c(1,1:n,n)],
c(0,ifelse(coredata(x) > 0,coredata(x), 0),0),
col=col.brew[7]
)
#draw first negative band
polygon(
index(x)[c(1,1:n,n)],
c(0,ifelse(coredata(x) < 0 ,coredata(x), 0),0),
col=col.brew[4]
)
#overlay second positive band
polygon(
index(x)[c(1,1:n,n)],
c(0,ifelse(coredata(x) > 0.1,coredata(x) - 0.1, 0),0),
col=col.brew[8]
)
#overlay second negative band
polygon(
index(x)[c(1,1:n,n)],
c(0,ifelse(coredata(x) < -0.1 ,coredata(x) + 0.1, 0),0),
col=col.brew[3]
)
#overlay third positive band
polygon(
index(x)[c(1,1:n,n)],
c(0,ifelse(coredata(x) > 0.2 ,coredata(x) - 0.2, 0),0),
col=col.brew[9]
)
#overlay third negative band
polygon(
index(x)[c(1,1:n,n)],
c(0,ifelse(coredata(x) < -0.2 ,coredata(x) + 0.2, 0),0),
col=col.brew[2]
)
#little touch up to get a line at extending left to right
abline(h=0,col="black")
#add a line at the bottom of the chart
abline(h=par("usr")[3],col="black")
#now let's do it with a loop and flip the negative up
nbands = ceiling(max(abs(coredata(x)))/horizonscale)
plot(index(x), abs(coredata(x)), type="n", bty="n", las=1, yaxt="n", xaxt="n", xlab=NA, ylab=NA, ylim=c(0,horizonscale))
#thanks to helpful reader A. Zolot
par(usr=c(index(x)[1],index(x)[n],0,horizonscale),mar=c(2,1,1,1)) # 0-margines
for (i in 1:nbands) {
#draw positive
polygon(
c(index(x)[1], index(x), index(x)[n]),
c(0, coredata(x) - (i-1) * horizonscale,0),
col=col.brew[length(col.brew)-nbands+i-1],
border=NA
)
#draw negative
polygon(
c(index(x)[1], index(x), index(x)[n]),
c(0, -coredata(x) - (i-1) * horizonscale, 0),
col=col.brew[nbands-i+1],
border=NA
)
}
abline(h=0,col="black")
axis.Date(side=1,x=index(x),pos=0)

2 comments:

  1. You key issue in how you defined the polygons. In the data you have, for example:

    2002-05-30 as 0.010353965
    and
    2002-06-29 as -0.013995566

    However in creating the positive band you forced the second value to be zero instead of -0.013... and in creating the negative band you forced the first value to be zero instead of 0.0103...

    So your polygons do not match the data. To get the graph working right you will have to determine the crossover point - which is not the value of x of either of the two dates but for some point between those two values. Then use that point to define the polygons.

    The effect of the difference is easy to see if you insert the code

    #draw Actual polygon
    polygon(
    index(x)[c(1,1:n,n)],
    c(0,coredata(x),0),
    col="red")

    after you make the first plot with the positive and negative polygons.

    ReplyDelete
  2. simpler and no overlap:

    horplot=function(x, nBrakes= 6, border = NA) {
    nBrakes= 2 * floor(nBrakes/2) # nBrakes should be even

    palette(adjustcolor(colorRampPalette(c("blue",'red'))(nBrakes+1), alpha.f = .6))

    h= hist(x, br= nBrakes, plot= T)
    dx= diff(h$breaks)[1]

    n= NROW(x)

    plot(index(x), coredata(x), ylim=c(0,dx), type="n")
    par(usr=c(index(x)[1],index(x)[n],0,dx)) # 0-margines

    for(i in 1:(nBrakes/2)){
    polygon(
    c(index(x)[1], index(x), index(x)[n]),
    c(0, x - h$breaks[i+ nBrakes/2-1], 0),
    col= i + nBrakes/2, border = border
    )
    polygon(
    c(index(x)[1], index(x), index(x)[n]),
    c(0,-x + h$breaks[i], 0),
    col= i , border = border
    )
    }
    }

    horplot(x, 6)
    horplot(x, 6, border =T)

    ReplyDelete