*! anymatch 1.0.0 13Sep2009 roywada@hotmail.com
*! distance matching based on any metric

prog define anymatch, sortpreserve

version 8.0

syntax [varlist(default=none)], id(string) [METric(string) dist(string) NEARest(string) SUFfix(string) /*
	*/ MAXimum(numlist max=1 >=0) MINimum(numlist max=1) count COUNT1(string) PREfix(string) NOIsily /*
	*/ DONor(string) RECipient(string)]

qui {

if "`noisily'"=="noisily" {
	local noi noi
}
if "`dist'"=="" {
	local dist "dist"
}
if "`suffix'"=="" {
	local _near ""
}
if "`prefix'"=="" {
	local pre "_"
}
if "`maximum'"=="" {
	local maximum .
}
if "`minimum'"=="" {
	local minimum -1
}
if `maximum'<`minimum' {
	noi di in red `"invalid range: {opt maximum( )} needs to be greaer than or equal to {opt minimum( )}"'
	exit 198
}
if "`count'"=="" & "`count1'"=="" {
	local nocount nocount
}
if "`count'"~="" & "`count1'"=="" {
	local count1 "count"
}

* check id
tempvar test group
egen `group'=group(`id')
local wcount: word count `group'
forval num=1/`wcount' {
	local this : word `num' of `id'
	cap replace `group'=. if `this'==""
}
bys `group': gen `test'=_N if `group'~=.
sum `test', meanonly
if `r(max)'>1 {
	noi di in red `"observations not uniquely identified by "`id'""'
	exit 198
}
local N=_N
count if `id'~=.
if `r(N)'~=`N' {
	noi di in red `"`id' is missing for some observations"'
	exit 198
}

*** how many neighbors to search for
if "`nearest'"=="" {
	local nearest 5
}
else if "`nearest'"=="." {
	sum `group', meanonly
	local nearest `r(max)'
}

* donor & recipient
tempvar don rec
gen `don'=1
gen `rec'=1
if "`donor'"~="" {
	replace `don'=. if `donor'~=1
}
if "`recipient'"~="" {
	replace `rec'=. if `recipient'~=1
}

sort `don', stable
replace `don'=_n if `don'==1
sum `don', mean
if r(N)==0 {
	noi di in red "at least one `donor' needs to be 1"
	exit 198
}
local donN=r(N)
local donMin=r(min)
local donMax=r(max)

sum `rec', mean
if r(N)==0 {
	noi di in red  "at least one `recipient' needs to be 1"
	exit 198
}

* does it sideways by creating variables
foreach var in `id' {
	forval num=1/`nearest' {
		gen `pre'`var'`_near'`num'=`var'
		cap replace `pre'`var'`_near'`num'=""
		cap replace `pre'`var'`_near'`num'=.
		label var `pre'`var'`_near'`num' "nearest `var' `num'"
	}
}
foreach var in `dist' {
	forval num=1/`nearest' {
		gen `pre'`var'`_near'`num'=.
		label var `pre'`var'`_near'`num' "nearest `var' `num'"
	}
}
foreach var in `group' {
	forval num=1/`nearest' {
		gen `var'`_near'`num'=`var'
		cap replace `var'`_near'`num'=""
		cap replace `var'`_near'`num'=.
		label var `var'`_near'`num' "nearest `var' `num'"
	}
}
foreach var in `varlist' {
	forval num=1/`nearest' {
		gen `pre'`var'`_near'`num'=`var'
		cap replace `pre'`var'`_near'`num'=""
		cap replace `pre'`var'`_near'`num'=.
		label var `pre'`var'`_near'`num' "nearest `var' `num'"
	}
}

/*
*** flip if one group is larger than the other
sum `group', meanonly
local N=_N
if r(max)<`N'/2 {

*/

* place holders
foreach var in `group' `id' `varlist' `metric' {
	tempvar `var'New
	gen ``var'New'=`var'
}
foreach var in `dist' {
	tempvar `var'New
	gen double ``dist'New'=.
}
tempvar reject
gen `reject'=0
local N=_N


*tempvar loncut latcut

* cycle `don'
forval candidate=1/`nearest' {
	noi `di'
	noi di ""
	noi di in white "nearest `candidate'/`nearest'" _c
	`noi' di " from `donMax' potential donors :" _c

	local di di

	*** grid search
	*if "`grid'"~="" {
	*	noi di in red "grid search"
	*	local gridN 10
	*	egen `loncut'=cut(`lon') if `rec'~=., group(`gridN')
	*	egen `latcut'=cut(`lat') if `rec'~=., group(`gridN')
	*}

	forval place=`donMin'/`donMax' {
		* comarison values
		foreach var in `group' `id' `varlist' `metric' {
			replace ``var'New'=`var'[`place']
		}

		if "`noi'"~="" {
			`noi' di in yel " `place'" _c
		}

		foreach var in `metric' {
			replace ``dist'New'= abs(`metric'-``metric'New')

			replace ``dist'New'=. if ``dist'New'>`maximum' | ``dist'New'<`minimum'
		}

		replace `reject'=0
		if `candidate'~=1 {
			forval num=2/`candidate' {
				replace `reject'=1 if `group'`_near'`=`num'-1'==``group'New' & `rec'==1
			}
		}

		foreach var in `group' {
			* take the value if conditions are met (exclude the same place)
			replace `var'`_near'`candidate'=``var'New' if (``dist'New'<`pre'`dist'`_near'`candidate') & `group'~=``group'New' & `reject'==0 & `rec'==1
		}
		set trace off
		foreach var in `id' `varlist' {
			* take the value if conditions are met (exclude the same place)
			replace `pre'`var'`_near'`candidate'=``var'New' if (``dist'New'<`pre'`dist'`_near'`candidate') & `group'~=``group'New' & `reject'==0 & `rec'==1
		}

		* do this last:
		foreach var in `dist' {
			* take the value if conditions are met (exclude the same place)
			replace `pre'`dist'`_near'`candidate'=``dist'New' if (``dist'New'<`pre'`dist'`_near'`candidate') & `group'~=``group'New' & `reject'==0 & `rec'==1
		}
	}

	local max_candidate `=`candidate'-1'

	* break the loop if no neighbor found
	sum `group'`_near'`candidate', meanonly
	if `r(N)'==0 {
		noi di in white " - does not exist, the maximum number of neighbor is `=`candidate'-1'"
		local max_candidate `=`candidate'-1'
		foreach var in `id' `varlist' `dist' {
			drop `pre'`var'`_near'`candidate'-`pre'`var'`_near'`nearest'
		}
		continue, break
	}
}

if "`count1'"~="" & "`nocount'"~="nocount" {
	cap egen long `pre'`count1'=rownonmiss(`pre'`id'`_near'1-`pre'`id'`_near'`max_candidate')
	if _rc~=0 | `max_candidate'==0 {
		gen long `pre'`count1'=0
	}
	label var `pre'`count1' "the number of neighbors meeting the condition"
}

*/



di
} /* qui */

end
exit



donate except one's self