*** Project. Generalized CV for Varying coefficient models ** change all globals into locals ** NOW IN MATA ** last change was to modify with a flag for ealy ending capture program drop vc_bw program vc_bw, rclass sortpreserve syntax varlist(numeric fv) [if] [in], vcoeff(varname) [ bwi(real -1) weight(varname) knots(real -1) km(real 1) sample(varname) trimsample(varname) kernel(str) plot ] ** varlist will contain the dependent variable and independent variables ** weight will have the survey weights if any ** full_x has the fully observed nonparametric data ** par_x gas the partial data (which is a less detailed variable compared to full_x only for speeding up calculations ** trim_sample to define if any segment of the data will not be used for CV but used in claculations ** sample defines what data will be used in estimation (and what will not) sample>trimsample ** This sets up base information tokenize `varlist' local y `1' //get the rest of the vars macro shift local varx `*' marksample touse markout `touse' `varlist' `vcoeff' if "`sample'"!="" { qui: replace `touse'=0 if `sample'==0 } *local varx `varx' local full_x `vcoeff' *verifying if knots its a sensible number numlist "`knots'", integer range(>=-2) * This counts how many values vcoeff has tempvar vals qui:bysort `touse' `vcoeff': gen byte `vals' = (_n == 1) * `touse' sum `vals' if `touse', meanonly local vcvls=r(sum) if `vcvls'>=500 & `knots'==-1 { display "the variable `vcoeff' has more than 500 distinct values, using option knots(0) km(2)" display "To force using all distinct values use knots(-2)" local knots=0 local km=2 } if `knots'==-1 | `knots'==-2 { local par_x `vcoeff' } else { tempvar _kn egen `_kn'=vbin(`vcoeff') if `touse', knot(`knots') km(`km') local par_x `_kn' ** Get Number of Implicit knots and Implicit Bandwidth. if (`knots'==0) { sum `vcoeff' if `touse', meanonly local max=r(max) local min=r(min) local N=r(N) local _ikb=min(sqrt(`N'),10*ln(`N')/ln(10)) local _ikb=round(`_ikb'*`km') local _ibw=(`max'-`min')/(`_ikb'+1) } else { sum `vcoeff' if `touse', meanonly local max=r(max) local min=r(min) local N=r(N) local _ikb=(`knots') local _ibw=(`max'-`min')/(`knots'+1) } display "Number of used Knots: `_ikb' " display "Implicit Bandwidth : " %6.5f `_ibw' } local sweight 1 * this is undocumented. PLanned for adding sample weights *if "`weight'"!="" { * local sweight `weight' *} *DEFAULT OPTION FOR Kernel if "`kernel'"=="" { local kernel="gaussian" } display "Kernel: `kernel'" ** try adding th other options if "`kernel'"!="gaussian" & /// "`kernel'"!="biweight" & /// "`kernel'"!="cosine" & /// "`kernel'"!="epan" & /// "`kernel'"!="epan2" & /// "`kernel'"!="parzen" & /// "`kernel'"!="trian" & /// "`kernel'"!="rectan" { display "Kernel function `kernel' not allowed" exit 1 } tempvar sample2 qui:gen `sample2'=1 * This is to create a weight that has value of zero for some observations if "`trimsample'"!="" { qui:replace `sample2'=`trimsample' } ** Initial bandwidth based on lpoly if `bwi'==-1 { lpoly `y' `full_x' if `touse', nograph kernel(`kernel') degree(1) ** Initial BW adjusts for number of parameters in the explanatory variable. Better adjustments can be found. ** in principle more explanatory variables require a larger bandwidth local num : word count `varx' local bw1=(1+ln(`num'+1))*r(bwidth) local bw0=`bw1'*0.99 local bw2=`bw1'*1.01 } else { local bw1=`bwi' local bw0=`bw1'*0.99 local bw2=`bw1'*1.01 } local bwi=`bw1' ** This defines through what values we will be used for estimating the CV criteria This should adjust for TRIM factor ** if parx is equal to fullx then we use ALL values in fullx ** Best option to use Par_x which should have fewer levels than FUllx *qui: levelsof `par_x' if `touse'==1 & `sample2'==1, local(nparval) ** We need a new variable CV ** This variable will contain the Cross validation values tempvar cv qui:gen double `cv'=. ** Here is where the CV process begins preserve * Keep only observations that will be used for the analysis * Revise so we keep only variables required for the process, * This may help making it faster qui:keep if `touse'==1 tempvar delta wgt err ** initial CV for bandwidth. This are the 3 initial points of interest local k=0 foreach bw in `bw0' `bw1' `bw2' { ** initialize cv to missing qui:capture drop `cv' ** Rotates trough all the values of interestet in par_x ** This predict the LOOERR. Its a strip down version of the vc_predict qui:vc_predict `y' `varx', vcoeff(`full_x') vcoeff_par(`par_x') bw(`bw') looe(`cv') kernel(`kernel') nstat qui:replace `cv'=`cv'^2 sum `cv' [aw=`sweight'] if `touse'==1 & `sample2'==1, meanonly ** lets change the CV to log(RMSE) ** This is the same criteria used in NPREGRESS local cv`k'=ln(r(mean)) local k=`k'+1 } * We will create a matrix that has the results as needed * But will only report the Middle point capture matrix drop cvbw matrix cvbw=nullmat(cvbw)\[`bw0',`cv0']\[`bw1',`cv1']\[`bw2',`cv2'] display as result "Iteration: 0 BW: " %10.7f `bw1' " CV: " %10.7g `cv1' *** from here on we do the NR procedure *local dcv=min(`cv2',`cv0') local chg=1 local pchg=1 * while abs(`dcv'-`cv1')>epsdouble() & abs(`chg')>epsdouble() { ** Latest criteria If the proportional change of the BW is less than 0.01% local flagbw=0 ** This measures the change in the Bandiwdth pchg while abs(`pchg')>0.0001 & `flagbw'!=1 { local cc=`cc'+1 * last cv for "min point" local dcv=`cv1' local dbw=`bw1' ** Approximations for derivatives i get the updated changes with this, and use it for the NR algorithm ** See if its possible to adapt the BWALT here. THe idea. Once the MAX and MIN are known, we dont have to search beyond those points. local df=(`cv2'-`cv0')/(`bw2'-`bw0') local ddf=(`cv2'-2*`cv1'+`cv0')/(`bw2'-`bw1')^2 ** This later change seems to work. ** Look if it ever fails. Come back to here local chg=-`df'/abs(`ddf') ** auxiliar DF local df1=(`cv2'-`cv1')/(`bw2'-`bw1') local df2=(`cv1'-`cv0')/(`bw1'-`bw0') ** This is to avoid overreaction. If the change is more than 50% of current BW, we use a 20% change. ** This could be improved. Look into adaptive step /* if abs(`chg')>0.5*`bw1' { local chg=0.5*`bw1'*sign(-`df'/`ddf') }*/ ** And this to deal with non convex sets ** NP process usually finds a flat point. This corrections help to ensure the point is minimizing the function. /*if `df'<0 & `df1'<0 & `df2'<0 { local chg=abs(`df'/`ddf') } if `df'>0 & `df1'>0 & `df2'>0 { local chg=-abs(`df'/`ddf') }*/ if (`bw1'+`chg')<0 { local chg=-0.5*`bw1' } *other option USe CHG to be between certain levels ** updating numbers BW, using above changes. * This dch is to update the auxiliary points. we use a 1% gap or the estimated change. Whichever is smaller. ** We will try implement an adaptive version of this algorithm ** Following example, we will first verify that an improvement was made. If not, try again with half a step local flag_imp=0 local dch=min(0.01*`bw1',abs(`chg')) local bw1=`bw1'+`chg' tempvar ccvv ** This basically rotates until an improvement has been found local cntloop=0 while `flag_imp'==0 & `cntloop'<5{ local cntloop=`cntloop'+1 qui:capture drop `cv' qui:capture drop `ccvv' ** Rotates trough all the values of interestet in par_x qui:vc_predict `y' `varx', vcoeff(`full_x') vcoeff_par(`par_x') bw(`bw1') looe(`cv') kernel(`kernel') nstat qui:replace `cv'=`cv'^2 sum `cv' [aw=`sweight'] if `touse'==1 & `sample2'==1, meanonly ** lets change the CV to RMSE local cv1=ln(r(mean)) matrix cvbw=nullmat(cvbw)\[`bw1',`cv1'] if `cv1'<=`dcv' { local flag_imp=1 } else { local chg=0.5*`chg' local dch=min(0.01*`bw1',abs(`chg')) local bw1=`dbw'+`chg' } } * updating two points of reference local bw2=`bw1'+`dch' local bw0=`bw1'-`dch' * Restarting local. local k=0 * now restarting the 3 band CV. LEss efficient but more accurate * THe alternative was to use only 1 additional BW foreach bw in `bw0' `bw2' { ** initialize cv to missing qui:capture drop `cv' qui:capture drop `ccvv' ** Rotates trough all the values of interestet in par_x qui:vc_predict `y' `varx', vcoeff(`full_x') vcoeff_par(`par_x') bw(`bw') looe(`cv') kernel(`kernel') nstat qui:replace `cv'=`cv'^2 sum `cv' [aw=`sweight'] if `touse'==1 & `sample2'==1, meanonly ** lets change the CV to RMSE local cv`k'=ln(r(mean)) local k=`k'+2 } matrix cvbw=nullmat(cvbw)\[`bw0',`cv0']\[`bw1',`cv1']\[`bw2',`cv2'] local pchg=`chg'/`bw1' display as result "Iteration: `cc' BW: " %10.7f `bw1' " CV: " %10.7g `cv1' if `bwi'*100<`bw1' { local flagbw=1 noisily:display "BW too large" } } *local cvchg=`dcv'-`cv1' *display as text "BW 1:`bw0' " as result "2: `bw1'" as text "3: `bw2'" as result "Last Change: `chg'" *display as text "CV 1:`cv0' " as result "2: `cv1'" as text "3: `cv2'" as result "Last Change: `cvchg'" *display "DF:`df' DDF:`ddf' Change:`chg' " restore global opbw_=`bw1' global kernel_="`kernel'" global vcoeff_="`vcoeff'" display "Bandwidth stored in global \$opbw_" display "Kernel function stored in global \$kernel_" display "VC variable name stored in global \$vcoeff_" if "`plot'"!="" { tempname _cv svmat cvbw, name(`_cv') scatter `_cv'2 `_cv'1 , xtitle("bandwidth") ytitle("CV criteria") legend(off) xline(`bw1') } matrix colname cvbw= bw cv *macro drop y par_x varx full_x sweight return scalar bandwidth=`bw1' return scalar cv_criteria=`cv1' return local kernel="`kernel'" return matrix bwcv_trace=cvbw *capture mata:mata drop x y end