* 1.1 RAR 21 January 2004, with improved coding * 1.0.1 RAR 12 December 2003, with suggestions from NJC * 1.0 RAR 22 November 2003 - England won RU World Cup program cvxhull, sortpreserve version 8.0 syntax varlist(min=2 max=2) [if] [in] /// [ , Hulls(numlist int min=0 max=2 >0) MDPlot(int 8) /// GROup(varname) SELect(numlist int min=0 >0 sort) /// MEAns PREfix(string) noREPort noRETain /// SCATopt(string) noGRAph SAVing(string)] loc reporting = ("`report'" != "noreport") if "`retain'" == "noretain" & "`graph'" == "nograph" { di as err "That combination of options is silly: quitting now" exit 498 } * Nick Cox thinks this parsing is still flakey if `"`saving'"' != "" { // Check saving filename tokenize `"`saving'"', parse(",") args saving comma replace if index(`"`saving'"', ".") == 0 loc saving `"`saving'.gph"' capture confirm file `saving' if _rc == 0 { /* existing file */ if "`replace'" != "replace" { di as err "Invalid save file - did you mean to replace?" exit 198 } loc saving `"`saving',replace"' } else confirm new file `saving' // invalid file - unless new loc saving `"saving("`saving'")"' } if "`hulls'" == "" { loc hulls = 1 loc hullgap = 1 // Set default values } else { tokenize "`hulls'" loc hulls = `1' if "`2'" == "" loc hullgap = 1 else loc hullgap = `2' } marksample touse // Deal with `if', `in' & missing qui count if `touse' if r(N) == 0 error 2000 tempvar sample grp scratch count pts hullno qui gen `sample'=`touse' // & remember all points considered if "`group'" == "" { // group variable may be absent, string or numeric qui gen `grp' = 1 loc select "1" loc maxgrp = 1 } else { qui egen `grp' = group(`group') if `touse', label if "`select'" != "" { loc j = 0 foreach i in `select' { loc ++j } loc maxgrp = `j' // no of items } else { su `grp', meanonly loc maxgrp = r(max) loc select "1 / `maxgrp'" } qui { egen `scratch' = eqany(`grp'), values(`select') replace `touse' = `touse' * `scratch' } if `reporting' di as txt "Codes for groups based on " as res "`group'" label list `grp' } * Double check, in case group selection has dropped all obs qui count if `touse' if r(N) == 0 error 2000 loc retd = 1 + int((`hulls'-1)/`hullgap') // number of hulls loc hull = plural(`retd', "hull") loc gs = plural(`maxgrp', "group") if `reporting' di as txt "Up to `retd' `hull' to be saved for `maxgrp' `gs'" if "`prefix'" == "" loc prefix "_cvxh" capture drop `prefix'*l capture drop `prefix'*r capture drop `prefix'grp capture drop `prefix'hull capture drop `prefix'pts capture drop `prefix'cnt capture drop `prefix'*mindex capture drop `prefix'*maxdex capture drop `scratch' tokenize `varlist' args y x sort `grp' `x' `y' // within group, by ascending x then y qui { gen `count' = 0 gen `pts' = 0 gen `hullno' = 0 } tempname gap loc gap = `hullgap' -1 // force retain hull 1 loc maxhull = 0 ***** Main loop ****************************************** tempvar leftpath rightpath onhull // re-use scratch for points on segment qui { gen `leftpath' = . // set up outside loop to use replace gen `rightpath' = . // within loop & save RAM allocation time gen `onhull' = 0 gen `scratch' = 0 } forvalues h = 1 / `hulls' { // b1 : brackets numbered for sanity! qui count if `touse' // stop when all points peeled if r(N) > 0 { // b2 qui { replace `leftpath' = . replace `rightpath' = . replace `onhull' = 0 } loc sp = 1 // Starting point loc notstarted = 1 while `sp' <= _N { // b3 scan all observations loc curgrp = `grp'[`sp'] if !`touse'[`sp'] { loc ++sp // ignore point & increment pointer } else { // b4 - point processing ... while `grp'[`sp'] == `curgrp' { // b5 within group if `notstarted' { // b6 mark first point of current group qui { replace `leftpath' = `y' in `sp' replace `rightpath' = `y' in `sp' replace `onhull' = (_n==`sp') replace `scratch' = 0 } loc leftcur = `sp' loc rightcur = `sp' loc sp1 = `sp' loc curgrp = `grp'[`sp'] loc mindex = 1 loc notstarted = 0 loc ++sp } else { // 6 loc j = `leftcur' + 1 loc maxL = -1 loc maxD = 0 loc found = 0 while `j'<=_N & `grp'[`j']==`curgrp' { // b7 find next left */ if `touse'[`j']==1 { // b8 loc d = sqrt( (`y'[`j'] - `y'[`leftcur'])^2 + (`x'[`j'] - `x'[`leftcur'])^2 ) if float(`d') > 0 { // b9 loc cosa = (`y'[`j'] - `y'[`leftcur'])/ `d' if float(`cosa') > float(`maxL') { // b10 new leftmost direction loc maxL = `cosa' loc next = `j' qui replace `scratch' = (_n==`j') qui replace `scratch' = 1 in `sp1' loc found = 1 } else { if float(`cosa') == float(`maxL') { // collinear if float(`d') > float(`maxD') loc next = `j' qui replace `scratch' = 1 in `j' loc found = 1 } } } // 9 else { // 9 qui replace `onhull' = 1 in `j' // coincident with current point loc next = `j' } //9 } // 8 loc ++j } // 7 qui replace `onhull' = (`onhull' | `scratch') if `found' { qui { replace `leftpath' = `y' in `next' loc ++mindex loc leftcur = `next' } } loc j = `rightcur' + 1 loc maxR = 1 loc maxD = 0 loc found = 0 while `j'<=_N & `grp'[`j']==`curgrp' { // b7 find next right if `touse'[`j']==1 { //8 loc d = sqrt( (`y'[`j'] - `y'[`rightcur'])^2 + (`x'[`j'] - `x'[`rightcur'])^2 ) if float(`d') > 0 { //9 loc cosa = (`y'[`j'] - `y'[`rightcur']) / `d' if float(`cosa') < float(`maxR') { //10 loc maxR = `cosa' loc next = `j' qui replace `scratch' = (_n==`j') qui replace `scratch' = 1 in `sp1' loc found = 1 } else { if float(`cosa') == float(`maxR') { // collinear if float(`d') > float(`maxD') loc next = `j' qui replace `scratch' = 1 in `j' loc found = 1 } } } //9 else { //9 qui replace `onhull' = 1 in `j' // coincident loc next = `j' } //9 } //8 loc ++j } // 7 qui replace `onhull' = (`onhull' | `scratch') if `found' { qui { replace `rightpath' = `y' in `next' loc ++mindex loc rightcur = `next' } } * Check if found a hull, or continue loc sp = `leftcur' if `rightcur' < `leftcur' loc sp = `rightcur' if `leftcur' == `rightcur' { // hull closed qui { replace `hullno' = -`h' in `sp' replace `hullno' = `h' in `sp1' replace `count' = `mindex' - 1 in `sp1' // last point double counted qui count if `onhull' replace `count' = r(N) in `sp' replace `pts' = `h' if `onhull' replace `touse' = 0 if `onhull' } loc notstarted = 1 while `grp'[`sp'] == `curgrp' { loc ++sp } } } // e6 } // e5 found end of group if `mindex' == 1 { // special case - single point hull qui { qui count if `onhull' replace `count' = r(N) in `sp1' replace `hullno' = `h' in `sp1' loc notstarted = 1 } } } // e4 end point processing } // e3 end loop over data * Save hull if requested loc ++gap if `gap' == `hullgap' { loc ++maxhull qui { gen `prefix'`h'l = `leftpath' gen `prefix'`h'r = `rightpath' } if `maxhull' <= `mdplot' loc hulllist "`prefix'1l-`prefix'`h'r" loc gap = 0 // restart count if `reporting' di as txt " hull level `h' calculated and saved" } } // e2 do nothing if no data points } // e1 exit loop when all hulls marked ***** Optional plot if `"`graph'"' != "nograph" { if `reporting' di as txt "Graph will be plotted presently" * Build and execute graph command ... as a very long macro text! loc colours "black black blue blue dkorange dkorange magenta magenta emerald emerald khaki khaki cyan cyan red red" * Set up point plot and add line plots for each selected group * RAR's preference for default horizontal y labels loc gr `"scatter `y' `x' if `sample',yti("`y'")yla(,angle(0))`saving' `scatopt'"' if "`means'" == "means" { tempvar ymean xmean egen `ymean' = mean(`y') if `select', by(`grp') egen `xmean' = mean(`x') if `select', by(`grp') loc gr `"`gr'||scatter `ymean' `xmean',ms(T)xti("`x'")"' } foreach i of numlist `select' { loc gr `"`gr'||line `hulllist' `x' if `grp'==`i',clc(`colours')legend(off)"' } `gr' // & execute macro command } if "`retain'" != "noretain" { qui { if "`group'" != "" gen `prefix'grp = `grp' gen `prefix'hull = `hullno' gen `prefix'cnt = `count' gen `prefix'pts = `pts' } } else { capture drop `prefix'*l `prefix'*r } if `reporting' di as txt "cvxhull run" end // of cvxhull