*** Project. Generalized CV for Varying coefficient models *** Latest change. Using Mata Speed things up atleast 50% This may be as fast as i can get it for now. capture program drop vc_bwalt program vc_bwalt, rclass sortpreserve syntax varlist(numeric fv) [if], 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 } *global 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 *if "`weight'"!="" { * local sweight `weight' *} if "`kernel'"=="" { local kernel="gaussian" } display "Kernel: `kernel'" 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 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 adjustes for number of parameters in the explanatory variable. Better adjustments can be found. local num : word count `varx' local bw1=(1+ln(`num'+1))*r(bwidth) local bw0=`bw1'*0.9 local bw2=`bw1'*1.10 } else { local bw1=`bwi' local bw0=`bw1'*0.99 local bw2=`bw1'*1.01 } ** 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 ** The original program uses a NR approach to find optimal bandwidth ** This will use something similar to the Nelder–Mead method. David Drukker indicates that is closer to a bysection. ** May be slower (as convergence rate is somewhat fixed at 1/2^N. It would take about 9 iterations to find the optimal bandwidth within .1% ** To some extend this is the Zeno's Paradox. We will never Find the Optimum Bandwidth because there will always be a distance larger than zero between two candiate bandwiths. ** Unlike the Zenos' paradox, the speed of convergence is not constant, the closer we are to the Optimum bandwith, the slower we move towards it preserve * Keep only observations that will be used for the analysis * Revise so we keep only variables required for the process * Should i keep this? It only adds time qui:keep if `touse'==1 tempvar delta wgt err tempvar constant gen byte `constant'=1 *local fvarx "`full_x' `varx' c.`full_x'#c.(`varx') `constant'" *mata:y= st_data(., "`y'") *mata:x= st_data(., "`fvarx'") ** 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 interest 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 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 _c "Iteration: 0 BW: " %10.7f `bw1' " CV: " %10.7g `cv1' *** from here on we do the NM(RA) procedure This is my interpretation of the procedure. *local dbw=0.5*`bw0' local dcv=min(`cv2',`cv1',`cv0') local chg=1 local pchg=1 local bwop=`bw1' local cvop=`cv1' * while abs(`dcv'-`cv1')>epsdouble() & abs(`chg')>epsdouble() { ** Latest criteria If the proportional change of the BW is less than 0.01% ** This is to be sure max and mins are used and Flag to avoid more than one criteria local bwmin=0 local bwmax=99999999 local flag=0 while abs(`pchg')>0.0001 { local bwx0=`bwop' local cvx0=`cvop' local cc=`cc'+1 local flag=0 * This is to do a trace of the appropriate values * display "`bwmin' `bw0' `bw1' `bw2' `bwmax'" *display "`cv0' `cv1' `cv2' `flag'" ** This section will have a lot of Ifs. ** That should take into account ALL possibilities ** The problem is how to make the first Step. It can be anything... * case one. CV is declining in BW \_ if (`cv0'>=`cv1' & `cv1'>`cv2' & `flag'==0) /// | (`cv0'>`cv1' & `cv1'>=`cv2' & `flag'==0) { * This shows the PATH taken local p " Path: \_" display _c "`p'" _n local flag=1 local bwmin=`bw0' * This copies from NP to see the SIZE of the change local bwx=min(`bw2'*1.5,0.5*(`bw2'+`bwmax')) ** qui:capture drop `cv' qui:vc_predict `y' `varx', vcoeff(`full_x') vcoeff_par(`par_x') bw(`bwx') looe(`cv') kernel(`kernel') nstat qui:replace `cv'=`cv'^2 sum `cv' [aw=`sweight'] if `touse'==1 & `sample2'==1, meanonly local cvx=ln(r(mean)) matrix cvbw=nullmat(cvbw)\[`bwx',`cvx'] local cv0=`cv1' local bw0=`bw1' local cv1=`cv2' local bw1=`bw2' local cv2=`cvx' local bw2=`bwx' if `cvx'<`cv1' { local cvop=`cvx' local bwop=`bwx' } else { local cvop=`cv1' local bwop=`bw1' } *matrix cvbw=nullmat(cvbw)\[`bwx',`cvx'] } * case one. CV is increasin in BW _/ if (`cv0'<`cv1' & `cv1'<=`cv2' & `flag'==0) | /// (`cv0'<=`cv1' & `cv1'<`cv2' & `flag'==0) { local p " Path: _/" display _c "`p'" _n local flag=1 local bwmax=`bw2' local bwx=0.5*(`bw0'+`bwmin') local flag_imp=0 qui:capture drop `cv' qui:vc_predict `y' `varx', vcoeff(`full_x') vcoeff_par(`par_x') bw(`bwx') looe(`cv') kernel(`kernel') nstat qui:replace `cv'=`cv'^2 sum `cv' [aw=`sweight'] if `touse'==1 & `sample2'==1, meanonly local cvx=ln(r(mean)) matrix cvbw=nullmat(cvbw)\[`bwx',`cvx'] local cv2=`cv1' local bw2=`bw1' local cv1=`cv0' local bw1=`bw0' local cv0=`cvx' local bw0=`bwx' if `cvx'<`cv1' { local cvop=`cvx' local bwop=`bwx' } else { local cvop=`cv1' local bwop=`bw1' } *matrix cvbw=nullmat(cvbw)\[`bwx',`cvx'] } *** CV is smaller at some point in between bw2 and bw0 \_/ if (`cv0'>`cv1' & `cv1'<=`cv2'& `flag'==0) | (`cv0'>=`cv1' & `cv1'<`cv2'& `flag'==0) { local p " Path: \_/" display _c "`p'" _n *display as result "`bwmin' `bw0' `bw1' `bw2' `bwmax' " *display as result "`bwmin' `cv0' `cv1' `cv2' `bwmax' " ** Derivatives to try a faster way once we are in the concave area. Important? local flag=1 local bwmax=`bw2' local bwmin=`bw0' local bwx1=(`bwmin'+`bw1')*.5 local bwx2=(`bwmax'+`bw1')*.5 qui:capture drop `cv' qui:vc_predict `y' `varx', vcoeff(`full_x') vcoeff_par(`par_x') bw(`bwx1') looe(`cv') kernel(`kernel') nstat qui:replace `cv'=`cv'^2 qui:sum `cv' [aw=`sweight'] if `touse'==1 & `sample2'==1, meanonly local cvx1=ln(r(mean)) matrix cvbw=nullmat(cvbw)\[`bwx1',`cvx1'] if `cvx1'>`cv1' { qui:capture drop `cv' qui:vc_predict `y' `varx', vcoeff(`full_x') vcoeff_par(`par_x') bw(`bwx2') looe(`cv') kernel(`kernel') nstat qui:replace `cv'=`cv'^2 qui:sum `cv' [aw=`sweight'] if `touse'==1 & `sample2'==1, meanonly local cvx2=ln(r(mean)) matrix cvbw=nullmat(cvbw)\[`bwx2',`cvx2'] } if `cvx1'<`cv1' { local cv2=`cv1' local bw2=`bw1' local cv1=`cvx1' local bw1=`bwx1' } else if `cvx2'<`cv1' { local cv0=`cv1' local bw0=`bw1' local cv1=`cvx2' local bw1=`bwx2' } else if `cvx1'>`cv1' & `cvx2'>`cv1'{ local cv0=`cvx1' local bw0=`bwx1' local cv2=`cvx2' local bw2=`bwx2' } if `cv0'<`cv1' & `cv0'<`cv2' { local bwop=`bw0' local cvop=`cv0' } if `cv1'<`cv0' & `cv1'<`cv2' { local bwop=`bw1' local cvop=`cv1' } if `cv2'<`cv0' & `cv2'<`cv1' { local bwop=`bw2' local cvop=`cv2' } *display as result "`bwmin' `bw0' `bw1' `bw2' `bwmax' " *display as result "`bwmin' `cv0' `cv1' `cv2' `bwmax' " } ** If the function is not concave /\ if `cv0'<`cv1' & `cv1'>`cv2' & `flag'==0 { local p "---" display _c " Path:`p'" display "we encounter a not concave point" display "Nearby CV values" display "CV0: `cv0' " display "CV1: `cv1' " display "CV2: `cv2' " display "revise matrix r(bwcv_trace) to see the solution candidates" local xit="xit" } if `cv0'==`cv1' & `cv1'==`cv2' & `flag'==0 { *display "here: --- " local xit="xit" } ******************************************* display _c "Iteration: `cc' BW: " %10.7f `bwop' " CV: " %10.9g `cvop' *local pchg=(`bwx0'-`bwop')/(`bwx0'+`bwop') local pchg=(`bw2'-`bw0')/(`bw2'+`bw0')*2 if "`xit'"=="xit" { local pchg=0 } } *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_=`bwop' global kernel_="`kernel'" global vcoeff_="`vcoeff'" display _n "Bandwidth stored in global \$opbw_" display "Kernel function stored in global \$kernel_" display "VC variable name stored in global \$vcoeff_" *macro drop y par_x varx full_x sweight if "`plot'"!="" { tempname _cv svmat cvbw, name(`_cv') scatter `_cv'2 `_cv'1 , xtitle("bandwidth") ytitle("CV criteria") legend(off) xline(`bwop') } matrix colname cvbw= bw cv return matrix bwcv_trace=cvbw return scalar bandwidth=`bwop' return scalar cv_criteria=`cvop' return local kernel="`kernel'" *mata: mata drop x y end