Plot 06

##############################################################

### Copyright (c) Richard Creamer 2019 - All Rights Reserved

### Inquiries: email 2to32minus1@gmail.com

##############################################################


### Globals

fontScale = 1.2    # Global font size variable

lf        = 1.2    # Enlarge font of plot labels

verbose   = FALSE  # Verbose printing

lw        = 2      # Line width

xMargin   = 1      # X-axis margin for plots

yMargin   = 1      # Y-axis margin for plots

de        = 0.01   # Small constant for estimating derivatives numerically

smlPts    = 1      # Small  data point/circle

medPts    = 1.5    # Medium data point/circle

lrgPts    = 2.0    # Large  data point/circle


### Run this function to generate this plot

runFunc = function() {

dw = 1000

dh = 750

path = "D:/Code/R/LeastSquaresPlay/PlotsAndAssocCode/Plot6/"

savePngVerbose( paste(path, "Plot6.png", sep=""), 

plotNthOrderPolyToLinearData, w=dw, h=dw, un="px", N=11, doPlot=TRUE, nPts=15 )

}


### Fit Nth order polynomial to linear data

plotNthOrderPolyToLinearData = function( nPts=15, N=1, modelM=1.3, modelB=2.7, noiseSigma=1.5, doPlot=FALSE ) {

set.seed( 3 )

if ( N < 1 || N > 11 ) { print( sprintf( "Error: N (%d) outside range 1:11", N) ); return() }

if ( nPts < (N + 1) )  { print( sprintf( "Error: nPts (%d) must be > N (%d)", nPts, N ) ); return() }

coeff = c( modelB, modelM )

x = 1:nPts

y = evalPoly( x, coeff ) + rnorm( length(x), sd=noiseSigma )

# Build appropriate formula, example: "y ~ x + I(x^2) + I(x^3) + ..."

polyFormula = makePolyFormula( polyOrder=N )

lmFit = lm( as.formula(polyFormula) )

e = compPolyFitError( x, y, lmFit$coefficients )

if ( doPlot ) {

plotTitle = sprintf( "Fit Polynomial of Order %d to Linear Data\nNoise Added", N );

plot( x, y, cex.lab=lf, xlab="", ylab="", main=plotTitle, cex=lrgPts, lwd=lw+1, cex.main=1.3*fontScale )

addAxisLabels()

grid()

box()

leftX = gpl()$xLim[1]

rightX = gpl()$xLim[2]

predX = seq( leftX, rightX, length.out=200 )

predY = evalPoly( predX, lmFit$coefficients )

lines( predX, predY, col=4, lwd=lw )

segments( x, y, x, predict(lmFit), col = "red", lwd=lw )  # Note: predict(lmFit) same as evalPoly(x, lmFit$coefficients)

if ( N > 9 ) {

addLegend( coeff, lmFit$coefficients, meanAbsErr=e$meanAbsError, meanSsdErr=e$meanSsdError, wrapLimit=4 )

}

else {

addLegend( coeff, lmFit$coefficients, meanAbsErr=e$meanAbsError, meanSsdErr=e$meanSsdError )

}

}

return( list(nPts=nPts, x=x, y=y, lmFit=lmFit, meanAbsErr=e$meanAbsError, meanSsdErr=e$meanSsdError) )

}


### Get Plot Limits : gpl()$xLim[1] --> left x-coord

gpl = function() {

u = par( "usr" )

return( list( xLim=u[1:2], yLim=u[3:4] ) )

}


### Return string suitable for plot titles with actual exponents (not '^')

### Examples: [ "1.23", "1.23-2.21x", "1.23-2.21x+4.98x^2" ]

prettyPoly = function( coeff, nSigFigs=3, wrapLimit=3 ) {

rc = signif( coeff, nSigFigs )

numTerms = length( coeff )

terms = c()

for ( termNum in 1:numTerms ) {

if ( termNum == 1 ) {

s = paste( rc[termNum], sep="" )

} else {

s = paste( abs(rc[termNum]), sep="" )

}

if ( termNum > 1 )

s = paste( s, "*x", sep="" )

if ( termNum > 2 )

s = paste( s, "^", (termNum-1), sep="" )

terms[ length(terms) + 1 ] = s

}

wrapLines = list()

catStr = ""

for ( termNum in 1:numTerms ) {

if ( termNum > 1 && ( termNum - 1 ) %% wrapLimit == 0 ) {

wrapLines[length(wrapLines) + 1] = catStr

catStr = ""

}

if ( termNum == 1 ) {

catStr = paste( catStr, terms[termNum], sep="" )

} else {

if ( rc[termNum] < 0 ) {

catStr = paste( catStr, " - ", terms[termNum], sep="" )

} else {

catStr = paste( catStr, " + ", terms[termNum], sep="" )

}

}

}

if ( catStr != "" )

wrapLines[length(wrapLines) + 1] = catStr

return( wrapLines )

}


### Convience method to add x/y axis labels

addAxisLabels = function( xLabel="x", yLabel="y", cexVal=1.3 ) {

mtext( text=xLabel, side=1, line=2.5, cex=cexVal )

mtext( text=yLabel, side=2, line=2.5, cex=cexVal )

}


### Add a legend to a plot suitable for this deck's polynomials/purpose

addLegend = function( coeff, fitCoeff, meanAbsErr=NULL, meanSsdErr=NULL, fontSz=1.35*fontScale, pos="topleft", wrapLimit=5 ) {

legList = list()

# Generate and append legend text lines for 'true model'

truePolyStrList = prettyPoly( coeff, nSigFigs=3, wrapLimit=wrapLimit )

legList[1] = paste( "'True model': y ==", truePolyStrList[1], sep="" )

if ( length( truePolyStrList ) > 1 ) {

for ( i in 2:length( truePolyStrList ) ) {

legList[length(legList)+1] = paste( "'     '", truePolyStrList[i], sep="" )

}

}

# Generate and append legend text lines for 'fitted model'

fitPolyStrList = prettyPoly( fitCoeff, nSigFigs=3, wrapLimit=wrapLimit )

legList[length(legList) + 1] = paste( "'Fitted model': y ==", fitPolyStrList[1], sep="" )

if ( length( fitPolyStrList ) > 1 ) {

for ( i in 2:length( fitPolyStrList ) ) {

legList[length(legList)+1] = paste( "'     '", fitPolyStrList[i], sep="" )

}

}

if ( !is.null(meanAbsErr) ) {

legError = paste( "'Mean ABS Error': ", sprintf( "%.2f", meanAbsErr ), sep="" )

legList[ length( legList ) + 1 ] = legError

}

if ( !is.null(meanSsdErr) ) {

legError = paste( "'Mean SSD Error': ", sprintf( "%.2f", meanSsdErr ), sep="" )

legList[ length( legList ) + 1 ] = legError

}

legend( pos, bty="n",inset=c(0.005,0.01),

legend=parse(text=legList), col=c("transparent","transparent"),

cex=fontSz, pch=15, y.intersp=1.1 )

}


# Compute errors, ABS and SSD, return tuple

compPolyFitError = function( x, y, coeff ) {

predY = evalPoly( x, coeff )

meanAbsError = (1/length(x)) * sum( abs(predY - y) )

meanSsdError = (1/length(x)) * sum( (predY - y)^2 )

return( list( meanAbsError=meanAbsError, meanSsdError=meanSsdError ) )

}


# Build Nth order polynomial formula string

makePolyFormula = function( indepVar="x", depVar="y", polyOrder=1 ) {

formulaStr = paste( depVar, " ~ ", indepVar, sep="" )

if ( polyOrder > 1 ) {

for ( i in 2:polyOrder )

formulaStr = paste( formulaStr, " + I(", indepVar, "^", i, ")", sep="" )

}

return( formulaStr )

}


### Compute f(x) for a polynomial and a vector of x-coordinates

evalPoly = function( x, coeff ) {

if ( length( coeff ) < 1 ) return( c(0) ) 

termSum = 0

for ( i in 1:length(coeff) ) {

termSum = termSum + coeff[i] * x^(i-1)

}

return( termSum )

}


### Save to PNG file, specify width and height

savePngVerbose = function( path, plotFunc, w=512, h=512, un="px", doCopyright=TRUE, ... ) {

png( filename = path, type="cairo", units=un, width=w, height=h, pointsize=12, res=96 )

plotFunc( ... )

if ( doCopyright )

addCopyright()

dev.off()

}


### Add copyright notice to plot via text()

addCopyright = function() {

mtext( "Copyright \uA9 2019 Richard Creamer - All Rights Reserved", side=4, line=0, adj=0, cex=1.1 )

mtext( "Email: 2to32minus1@gmail.com", side=4, line=1, adj=0, cex=1.1 )

}


runFunc()