*! version 1.1.7  26Jun2017
*! Directly standardized rates with improved confidence intervals
program define distrate, rclass
	version 9.0
	syntax varlist(min=2 max=2) [if] [in] using/ , STANDstrata(varlist) ///
                 [ BY(varlist) REFrate(integer 1) POPSTAND(name) FAY DOBSON MULT(numlist integer max=1) Format(string) ///
                   LIst(namelist) SAving(string asis) Level(integer $S_level) FORMATN(integer 0) SEPBY(varlist) PREfix(string) POSTfix(string) ]

/* Relevant sample */
	marksample touse
	markout `touse' `by' `standstrata', strok
	tokenize `varlist'
	local death `1'
	local studypop `2'
	cap assert `death' <= `studypop'
	if _rc {
		di as err "Number events in `death' cannot be greater than population specified in `studypop'."
		exit 459
	}

*	confirm file `"`using'.dta"'
	if index("`using'", ".") == 0 {
		local using = `"`using'"' + ".dta"
	}
	confirm file `"`using'"'

/* Check that format is valid */
	if "`format'" != "" {
		if index("`format'",",") local format = subinstr("`format'", "," , "." , 1) /* european numeric format */
		local fmt = substr("`format'",index("`format'",".")-1,3)
		capture {
			assert substr("`format'",1,1)=="%" & substr("`format'",2,1)!="d" ///
				& substr("`format'",2,1)!="t" & index("`format'","s")==0
			confirm number `fmt'
		}
		if _rc {
			di as err "invalid format. format has been set to default %6.4f"
		}
	}

	if "`saving'" != "" {
		gettoken stfile saving : saving, parse(",")
		gettoken comma saving  : saving, parse(",")
		if `"`comma'"' == "," {
			gettoken outrgro saving : saving, parse(" ,")
			gettoken comma saving : saving, parse(" ,")
			if `"`outrgro'"' != "replace" | `"`comma'"'!="" {
				di as err "option saving() invalid"
			exit 198
			}
		}
		else if `"`comma'"' != "" {
			di as err "option saving() invalid"
			exit 198
		}
		else 	confirm new file `"`stfile'.dta"'
	}
	preserve
	qui keep if `touse'
	qui keep `death' `studypop' `standstrata' `by'
	if "`by'"== "" {
		tempvar by
		g byte `by' = 1
		local nby y
	}

	local n_vby = wordcount("`by'")
	if `n_vby'>1 {
		local bylast = word("`by'",-1)
		local byref : list by - bylast
		local byref "bysort `byref' (`bylast') :"
	}

	if "`mult'"!="" {
		local per "(per `mult')"
		local mult "*`mult'"
	}
	if "`sepby'"!="" local sepby "sepby(`sepby')"

/* Data must be in appropriate aggregate form */
	collapse (sum) `death' `studypop', by(`standstrata' `by')
	tempvar ckstr rate sumpop

/* Check that all standstrata strata are present in each by level */
	bysort `by' : g `ckstr' = _N
	cap assert `ckstr' == `ckstr'[_n-1] if _n>1
	if _rc {
		di in red "Some strata across which to average the stratum-specific rates are not present in some `by' level."
		exit 459
	}
	local J = `ckstr'

/* Define popstand */
	if "`popstand'"=="" | "`popstand'"=="`studypop'"{
		local popstand `studypop'
		tempvar stpop
		g `stpop' = `studypop'
		drop `studypop'
		local studypop `stpop'
	}

	g double `rate' = `death'/`studypop'
	sort `standstrata'
	qui merge `standstrata' using "`using'"

/* Print a warning message if any records do not match with standard population file and exit */
	cap assert _merge==3
	if _rc {
		count if _m!=3
		di in red "`r(N)' records fail to match with standard population file " ///
			"(records who do not match are saved to _merge_error_.dta)."
		qui keep if _merge!=3
		qui save _merge_error_.dta, replace
		exit 459
	}
	drop _m

	tempvar w var vartiw wfay ratetiw vardob woldfay yl yu
	qui {
		egen `sumpop' = sum(`popstand'), by(`by')
		g double `w' = `popstand'/ `sumpop'
		g double rateadj = `rate'*`w'
		g double `wfay' = `w'/`studypop'
		g double `var' = `death' * `wfay'^2
		g double `vartiw' = (`death'+1/`J') * `wfay'^2
		g double `vardob'=`popstand'^2*`rate'/`studypop'
		collapse (sum) `death' `studypop' `popstand' rateadj `var' `vartiw' `vardob' (mean) `wfay' (max) `woldfay'=`wfay', by(`by')  // Tiwary correction -mean-
		g double crude      = `death'/`studypop' `mult'
		g double `ratetiw' = rateadj + `wfay'
		g double lb_gam = (`var'/(2*rateadj))*invchi2(2*rateadj^2/`var',1-(.5+`level'/200)) `mult'
		replace  lb_gam = 0 if `death'== 0
		g double ub_gam = (`vartiw'/(2*`ratetiw')) * ///
			invchi2((2*`ratetiw'^2)/`vartiw',.5+`level'/200) `mult'

		if "`fay'"!= "" {
			g double ub_fay = ((`var' + `woldfay'^2)/(2*(rateadj+`woldfay'))) * ///
				invchi2((2*(rateadj+`woldfay')^2)/(`var'+`woldfay'^2),.5+`level'/200) `mult'
			local ubfay ub_fay
		}
		replace `vardob' = `vardob'/`popstand'^2
		g double `yl' = .
		g double `yu' = .
		count
		local n = r(N)
		forval i = 1/`r(N)' {
			_crccip `death'[`i'] `level'
			replace `yl' = `r(lower)' if _n == `i'
			replace `yu' = `r(upper)' if _n == `i'
		}
		g double ub_dob= (rateadj+(`yu'-`death')*sqrt(`var'/`death')) `mult'
		g double lb_dob= (rateadj+(`yl'-`death')*sqrt(`var'/`death')) `mult'

		if "`nby'"=="" {
			if rateadj[`refrate']==0 {
				di in red "WARNING. Ratios of Directly Standardized Rates cannot be computed because" ///
				" the standardized rate in the denominator is 0."
				di in red "You can specify a different standardized rate in the denominator by using refrate(#) option."
			}
			`byref' g double srr = rateadj/rateadj[`refrate']
			`byref' g double lb_srr = cond(rateadj!=0,(rateadj/`ratetiw'[`refrate']) * invF(2*rateadj^2/`var', ///
				2*`ratetiw'[`refrate']^2/`vartiw'[`refrate'],1-(.5+`level'/200)),0)
			`byref' g double ub_srr = (`ratetiw'/rateadj[`refrate']) * invF(2*`ratetiw'^2/`vartiw',2*rateadj[`refrate']^2/`var'[`refrate'],.5+`level'/200)
			`byref' replace lb_srr = . if _n==`refrate'
			`byref' replace ub_srr = . if _n==`refrate'
			local stratio srr lb_srr ub_srr
		}

		replace rateadj = rateadj `mult'
	}
	if "`nby'"!="" local by
*	g double se_F = sqrt(`var')`mult'  // based on Fay and Feuer formula
	g double se_gam = sqrt(`vartiw')`mult'
	if "`format'" != "" format crude rateadj lb_g ub_g lb_dob ub_dob se_gam `stratio' `ubfay' `format'
	tempname NDeath Nobs Crude Adj Se_gam Ub_G Lb_G Ub_D Lb_D
	if "`fay'"!= "" tempname Ub_F
	if "`stratio'"!= "" tempname SRR Lb_S Ub_S
	mat `Ub_D'=J(1,`n',0)
	mat `Lb_D'=J(1,`n',0)
	mat `Ub_G'=J(1,`n',0)
	mat `Lb_G'=J(1,`n',0)
	mat `Crude'=J(1,`n',0)
	mat `Adj'=J(1,`n',0)
	mat `Se_gam'=J(1,`n',0)
	mat `NDeath'=J(1,`n',0)
	mat `Nobs'=J(1,`n',0)
	if "`fay'"!= "" 	mat `Ub_F'=J(1,`n',0)
	if "`stratio'"!= "" {
		mat `SRR'=J(1,`n',0)
		mat `Lb_S'=J(1,`n',0)
		mat `Ub_S'=J(1,`n',0)
	}
	forval i = 1 / `n' {
			mat `NDeath'[1,`i']=`death'[`i']
			mat `Nobs'[1,`i']=`studypop'[`i']
			mat `Crude'[1,`i']=  cond(crude[`i']==.,9,crude[`i'])
			mat `Adj'[1,`i']= cond(rateadj[`i']==.,9,rateadj[`i'])
			mat `Se_gam'[1,`i']= cond(`vartiw'[`i']==.,9,sqrt(`vartiw'[`i']))
			mat `Ub_G'[1,`i']= cond(ub_gam[`i']==.,9,ub_gam[`i'])
			mat `Lb_G'[1,`i']= cond(lb_gam[`i']==.,9,lb_gam[`i'])
			mat `Ub_D'[1,`i']= cond(ub_dob[`i']==.,9,ub_dob[`i'])
			mat `Lb_D'[1,`i']= cond(lb_dob[`i']==.,9,lb_dob[`i'])
			if "`fay'"!= "" 	mat `Ub_F'[1,`i']= cond(ub_fay[`i']==.,9,ub_fay[`i'])
			if "`stratio'"!= "" {
				mat `SRR'[1,`i']= cond(srr[`i']==.,9,srr[`i'])
				mat `Lb_S'[1,`i']= cond(lb_srr[`i']==.,9,lb_srr[`i'])
				mat `Ub_S'[1,`i']= cond(lb_srr[`i']==.,9,ub_srr[`i'])
			}
	}
	ret scalar k = `n'
	return mat ub_D `Ub_D'
	return mat lb_D `Lb_D'
	if "`fay'"!= ""  return mat ub_F `Ub_F'
	return mat se_gam `Se_gam'
	return mat ub_G `Ub_G'
	return mat lb_G `Lb_G'
	if "`stratio'"!= "" {
		return mat srr `SRR'
		return mat lb_srr `Lb_S'
		return mat ub_srr `Ub_S'
	}
	rename `studypop' N
	return mat adj `Adj'
	return mat crude `Crude'
	return mat NDeath `NDeath'
	if "`dobson'"!=""{
		local dd " and as proposed by Dobson et al"
		local dlist "lb_dob ub_dob"
	}
	if "`fay'"!=""	local fd ". Fay and Feuer previous upper bound (ub_fay) also displayed."

	if `formatn' > 0 {
		if `formatn'<16 format N %`formatn'.0f
	}
	di as txt "Directly standardized rates `per'"
di as txt "CI based on the gamma distribution (Fay and Feuer, 1997. Tiwari and al., 2006)`dd'`fd'"
	if "`list'"=="" list `by' `death' N crude rateadj lb_g ub_g `ubfay' se_gam `stratio' `dlist', noobs table `sepby'
	else {
		foreach name of local list {
			cap confirm var `name'
			if _rc    di as err "WARNING: `name' invalid or ambiguous in list option"
			else	{
				unab ilist: `name'
				local tolist "`tolist' `ilist'"
			}
		}
		if "`by'" != "" local tolist : list tolist - by
		list `by' `tolist', noobs table `sepby'
	}
	if "`stfile'" != "" {
		di
		keep `by' `death' N crude rateadj lb_g ub_g `ubfay' lb_dob ub_dob se_gam `stratio'
		label var `death'	"Events"
		label var N		"Study Population"
		label var crude		"Crude Rate"
		label var rateadj	"Directly Adjusted Rate"
		label var lb_g		"Lower Tiwari, Clegg and Zou bound"
		label var ub_g		"Upper Tiwari, Clegg and Zou bound"
		label var lb_dob	"Lower  DKES (Dobson et al) bound"
		label var ub_dob	"Upper DKES (Dobson et al) bound"
		label var se_gam	"Standard Error (Tiwari, Clegg and Zou)"
		if "`stratio'" != "" {
			label var srr "Ratio of Directly Adjusted Rates"
			label var lb_srr "LB of Ratio of Directly Adjusted Rates"
			label var ub_srr "UB of Ratio of Directly Adjusted Rates"
		}
		if "`fay'"!="" label var ub_fay	"Upper Fay and Feuer bound"
		label data		"Directly Standardized Rates `per'"
		order `by' `death' N crude rateadj lb_g ub_g se_gam lb_dob ub_dob
		if "`postfix'" != "" | "`prefix'" != "" {
			foreach var of varlist crude rateadj lb_g ub_g lb_dob ub_dob se_gam  `ubfay' `stratio' {
				rename `var' `prefix'`var'`postfix'
			}
		}
		save `stfile', `outrgro'
	}
end

program define _crccip, rclass
	version 6
	tempname f fp x k topk lev
* touched by kth  -- double saves in r() and S_#
* touched by jml -- fix algorithm
	scalar `k'= `1'
	scalar `lev' = (100-`2')/200
	scalar `x' = `k'
	if `x'== 0 {
		scalar `x' = .1  /* need a better starting point */
	}
	scalar `f' = 1-gammap(`k'+1,`x') - `lev'	/* Pr(k or fewer)*/
	while ((abs(`f')> 1e-8)&(`x' < .)) {
		scalar `fp'= -dgammapdx(`k'+1,`x')
		scalar `x' = `x' - `f'/`fp'
		scalar `f' = 1-gammap(`k'+1,`x') - `lev'
	}
	global S_2 : di %16.0g `x'
	ret scalar upper = `x'
	if `k'==0 {
		global S_1 0
		ret scalar lower = 0
		exit
	}
	scalar `x' = `k'
	scalar `topk'= `k'
	scalar `f' = gammap(`k',`x') - `lev'	/* Pr(k or more)	*/
	while ((abs(`f') > 1e-8)&(`x'<.)) {
		scalar `fp'=dgammapdx(`k',`x')
		scalar `x' = `x' - `f'/`fp'
		if `x'<0 {
			scalar `x' = 0
		}
		else if `x'>`topk' {
			scalar `topk' = `topk' - .1
			scalar  `x'= `topk'
		}
		scalar `f' = gammap(`k',`x') - `lev'
	}
	global S_1 : di %16.0g `x'
	ret scalar lower = `x'
end