*! 1.2.0 Anders Alexandersson 20030116
program define ellip6, rclass
	version 6.0
	if _caller() < 6 {
		ellip5 `0'
		exit
	}
	syntax varlist(min=1 max=2 numeric) [if] [in] [, COEFS MEANS  /*
	*/ Level(int $S_level) NPoints(integer 400) Pool(int -1)      /*
	*/ Constant(str) CONNect(str) SYmbol(str) Generate(str)       /*
	*/ Add(varlist min=2 max=2 numeric) B2title(str) L1title(str) /*
	*/ T1title(str) T2title(str) YForm XForm YForm2(str) NOGRaph /*
	*/ SAving(str) EVR(real 1) XForm2(str) REPLACE *]

	* parse string in g() and c()
	if "`generat'" ~= "" {
		local wc : word count `generat'
		if `wc' ~= 2 {
			di in red "exactly TWO variables required in generate()"
			exit 198
		}
		tokenize `generat'
		args new1 new2
		if "`replace'" ~= "" {
			capture drop `new1' `new2'
		}
		confirm new var `new1'
		confirm new var `new2'
		marksample touse, novarlist	/* include missing obs */
	}
        else {
		marksample touse		/* exclude missing obs */
	}

	local old_N = _N
	if "`constant'" ~= "" {
		local wc : word count `constant'
		if `wc' >= 3 {
			di in red "max TWO arguments allowed in constant()"
			exit 198
		}
		tokenize "`constant'"
		args stat p
		* confirm names `stat'   /* Stata 7 */
		confirm existence `stat'
		if "`p'" ~= "" {
			confirm number `p'
		}
	}

	* set common defaults
	if "`nograph'" ~= "" {
		local graph ""
	}
	else {
		local graph "graph"
	}
	if "`connect'" ~= "" {
		local connect "connect(`connect')"
	}
	if "`symbol'" ~= "" {
		local symbol "symbol(`symbol')"
	}
	if "`add'" ~= "" {
		if "`connect'" == "" {
			local connect "connect(ll)"
		}
		if "`symbol'" == "" {
			local symbol "symbol(..)"
		}
	}
	else {
		if "`connect'" == "" {
			local connect "connect(l)"
		}
        	if "`symbol'" == "" {
			local symbol "symbol(.)"
		}
	}
	if "`saving'" ~= "" {
		local saving "saving(`saving')"
	}
	set textsize 125
	if "`t1title'" == "" {
		local t1title " "
	}
	if "`t2title'" == "" {
		local t2title " "
	}

	* check for errors
	if `level' < 10 | `level' > 99 {
		di in red `"level(`level') invalid"'
		di in blue "level() must be an integer between 10 and 99"
		exit 198
	}
	if `npoints' < 20 | `npoints' > 9999 {
		di in red `"npoints(`npoints') invalid"'
		di in blue "npoints() must be an integer between 20 and 9999"
		exit 198
	}
	if `evr' < 0 | `evr' > 10^36 {
		di in red `"evr(`evr') invalid"'
		di in blue "evr() must be a floating-point number between " /*
		*/ "0 and 10^36"
		exit 198
	}
	if "`means'" ~= "" & "`coefs'" ~= "" {
		di in red "may not specify both means and coefs"
		exit 198
	}
	if "`yform'" ~= "" & `"`yform2'"' ~= "" {
		di in error "may not specify both yform and yform()"
		exit 198
	}
	if `"`yform2'"' ~= "" {
		capt local tmp : display `yform2' 1
		if _rc {
			* di as err `"invalid %fmt in yform(`yform2')"'
			di in red `"invalid %fmt in yform(`yform2')"'
			exit 120
		}
	}
	if "`xform'" ~= "" & `"`xform2'"' ~= "" {
		di in error "may not specify both xform and xform()"
		exit 198
	}
	if `"`xform2'"' ~= "" {
		capt local tmp : display `xform2' 1
		if _rc {
			* di as err `"invalid %fmt in xform(`xform2')"'
			di in red `"invalid %fmt in xform(`xform2')"'
			exit 120
		}
	}
	if "`constant'" == "sd" & `level' ~= $S_level {
		di in red "may not specify both constant(sd) and level()"
		exit 198
	}
	if "`pool'" ~= "-1" {
		ChkPool "`pool'" "`add'" "`if'" "`in'" "`graph'"
	}

	* define macros, and tokenize
	tempvar t new1 new2 yy xx id tmpy tmpx iwt
	tempname n f F r a b c xbar ybar s_x s_y x_a y_a A B m1 m2 /*
	*/ var_y var_x cov_xy C D lambda1 lambda2
	tempfile new tmp
	if "`generat'" ~= "" {
		tokenize `generat'
		args new1 new2
	}
	if "`add'" ~= "" {
		tokenize `add'
		args old1 old2
	}
	if "`constant'" ~= "" {
		tokenize `constant'
		args stat p
	}
	tokenize "`varlist'"
	args y x

	* set centering-specific defaults, and calculate basic ingredients
	if "`coefs'" == "coefs" {
		if "`e(cmd)'" ~= "regress" {
			di in red "ellip ..., coefs must be used after regress"
			exit 198
		}
		if "`x'" == "" {
			local x "_cons"
		}
		else {
			local x "`2'"
			if "`e(depvar)'" == "`x'" {
				di in red "`e(depvar)' is not independent variable"
				exit 111
			}
		}
		if "`l1title'" == "" {
			local l1title "Estimated `y'"
		}
		if "`b2title'" == "" {
			local b2title "Estimated `x'"
		}
		local ybar "_b[`y']"
		local s_y "_se[`y']"
		local var_y "_se[`y']^2"
		local xbar "_b[`x']"
		local s_x "_se[`x']"
		local var_x "_se[`x']^2"
		qui cor if `touse', _coef
		scalar `n' = e(N)
	}
	else {
		if "`l1title'" == "" {
			local l1title "`y'"
		}
		if "`b2title'" == "" {
			local b2title "`x'"
		}
		qui summarize `y' if `touse'
		scalar `ybar' = r(mean)
		scalar `s_y' = r(sd)
		scalar `var_y' = r(Var)
		qui summarize `x' if `touse'
		scalar `xbar' = r(mean)
		scalar `s_x' = r(sd)
		scalar `var_x' = r(Var)
		qui corr `y' `x' if `touse'
		scalar `n' = r(N)
	}
	scalar `r' = r(rho)
	scalar `cov_xy' = `r' * `s_x' * `s_y'

	* calculate c in subroutine, and then amplitudes of arcs x and y
	DoCons "`p'" `n' `level' "`stat'" "`coefs'"
	scalar `c' = r(c)
	scalar `x_a' = `s_x' * sqrt(`c')
	scalar `y_a' = `s_y' * sqrt(`c')

	* calculate scalars to be returned
	scalar `A' = `var_y' - `evr' * `var_x'
	scalar `B' = `cov_xy'
	scalar `m1' = (`A' + sqrt((`A')^2 + 4*`evr' * (`B')^2)) / (2 * `B')
	scalar `C' = `var_y' + `var_x'
	scalar `D' = ((`C'^2) - 4*(`var_y' * `var_x' - `cov_xy'^2))^.5
	scalar `lambda1' = (`C' + `D') / 2
	scalar `lambda2' = `C' - `lambda1'  /* or: (`C' - `D') / 2 */

	* save results as scalars in r()
	ret scalar a = (1 / `lambda1')^-.5 * sqrt(`c')	/* l of semi-major */
	ret scalar b = (1 / `lambda2')^-.5 * sqrt(`c')	/* l of semi-minor */
	ret scalar c       = `c'		/* boundary constant */
	ret scalar r       = `r'		/* correlation coefficient */
	ret scalar m1      = `m1'		/* slope of major axis */
	ret scalar m2      = - 1 / `m1'		/* slope of minor axis */
	ret scalar lambda1 = `lambda1'		/* first eigenvalue of inverse V */
	ret scalar lambda2 = `lambda2'		/* second eigenvalue of inverse V */
	ret scalar mean_y  = `ybar'		/* mean of y */
	ret scalar s_y     = `s_y'		/* standard error of y */
	ret scalar mean_x  = `xbar'		/* mean of x */
	ret scalar s_x     = `s_x'		/* standard error of x */

	* bring back data, calculate ellipse, and return results
	preserve
	qui range `t' 0 2*_pi `npoints'				/* change data */
	qui gen `new2' = `xbar' + `x_a' * cos(`t')
 	qui gen `new1' = `ybar' + `y_a' * cos(`t' + acos(`r'))
	qui summarize `new1'
	ret scalar min_y = r(min)	/* min of y */
	ret scalar max_y = r(max)	/* max of y */
	qui summarize `new2'
	ret scalar min_x = r(min)	/* min of x */
	ret scalar max_x = r(max)	/* max of x */

	* create macros for readabaility: ylist, xlist
	if "`generat'" == "" {
		if "`add'" == "" {
			local ylist "`new1'"
			local xlist "`new2'"
		}
		else {
			stack `new1' `new2' `old1' `old2', into(`yy' `xx') clear
			qui gen `new1' = `yy' if _stack==1  /* (obs 001-400) */
			qui gen `old1' = `yy' if _stack==2  /* (obs 401-800) */
			keep `new1' `old1' `xx'             /*    >= 800 obs */
			local ylist "`new1' `old1'"
			local xlist "`xx'"
		}
	}
	else {							/* gen() */
		if "`add'" == "" {
			local ylist "`new1'"
			local xlist "`new2'"
		}
		else {						/* add() */
			qui save "`new'" /* save dataset to allow damage */
			stack `new1' `new2' `old1' `old2', into(`yy' `xx') clear
			qui gen `new1' = `yy' if _stack==1 /* (obs 001-400) */
			qui gen `old1' = `yy' if _stack==2 /* (obs 401-800) */
			keep `new1' `old1' `xx'            /*    >= 800 obs */
			if "`pool'" == "-1" {
				local ylist "`new1' `old1'"
				local xlist "`xx'"
			}
			else {					/* pool() */
				qui gen long `id' = _n
				sort `id'
				local ylist "`new1' `old1'"
				local xlist "`xx'"
				qui save "`tmp'"		/* save tmp dataset */
			}
		}
	}

	* format ylist and xlist
	if `"`yform2'"' ~= "" {
		format `ylist' `yform2'
	}
	else format `ylist' %9.0g /* includes yf */
	if `"`xform2'"' ~= "" {
		format `xlist' `xform2'
	}
	else format `xlist' %9.0g /* includes xf */

	* graph ellipse
	if "`generat'" ~= "" & "`add'" ~= "" & "`pool'" ~= "-1" {
		* retrieve tmp dataset, and create fractionally pooled datasets
		use "`tmp'", clear
		local i = 0
		while `i' <= `pool' {
			restore, preserve /* N >= 400 */
			qui gen double `iwt' = 1 if `touse'==1
			qui replace `iwt' = [`i']/`pool' if `touse'==0
			qui reg `e(depvar)' `varlist' [iw=`iwt'], /*
			*/ level(`level') noheader
			qui gen `tmpy' = `ybar'
			qui gen `tmpx' = `xbar'
			qui keep `iwt' `tmpy' `tmpx'
			qui keep in l/l
			tempfile wt`i'
			qui save "`wt`i''"
			local i = `i' + 1
		}

		* append into one file which contains the locus curve
		use "`wt0'", clear
		local i = 1
		while `i' <= `pool' {
			append using "`wt`i''"
			local i = `i' + 1
		}

		* save locus curve, and merge with tmp dataset
		qui gen long `id' = _n
		sort `id'
		qui save "`wt`pool''", replace

		* return matrix for locus curve, as in -corrgram-
		tempname IWT YVAR XVAR
		mkmat `iwt', matrix(`IWT')
		mkmat `tmpy', matrix(`YVAR')
		mkmat `tmpx', matrix(`XVAR')
		mat colnames `IWT' = iwt
		mat colnames `YVAR' = "`y'"
		mat colnames `XVAR' = "`x'"
		ret matrix IWT `IWT'
		ret matrix YVAR `YVAR'
		ret matrix XVAR `XVAR'
		merge `id' using "`tmp'"

		* use the -gr20- STB-34 package within gph
		gph open, `saving'
		* pen() option in graph is invalid in gphdt -> r(198)
		gra `ylist' `xlist', `connect' `symbol' /*
		*/ t1("`t1title'") t2("`t2title'") /*
		*/ b2("`b2title'") l1("`l1title'") `options'
		gphsave
		local end = `pool' + 1
		local y_b = `tmpy' in 1/1			/*  b(y;_) */
		local y_bp = `tmpy' in `end'/`end'		/* bp(y;_) */
		local x_b = `tmpx' in 1/1			/*  b(_;x) */
		local x_bp = `tmpx' in `end'/`end'		/* bp(_;x) */
		gphdt text `y_b' `x_b' 0 1 b			/* b text */
		gphdt text `y_bp' `x_bp' 0 1 bp			/* bp text */
		qui gphdt vline `tmpy' `tmpx' in 1/`end'	/* var line */
		qui gphdt vpoint `tmpy' `tmpx' in 1/`end'	/* var pts */
		gph close
		qui keep if `iwt' ~= .
		rename `iwt' iweight
		rename `tmpy' `y'
		di						/* display table */
		di in gr "The Fractionally Pooled Estimates"
		di in gr "(i.e., the dots in the locus curve):"
		if "`x'" == "_cons" {
			rename `tmpx' constant			/* _cons is system variable */
			tabdisp iweight, cell(`y' constant) center
		}
		else {
			rename `tmpx' `x'
			tabdisp iweight, cell(`y' `x') center
		}
	}
	else {  /* NOT pool() */
		if "`graph'" ~= "" {
			gra `ylist' `xlist', `connect' `symbol'  /*
			*/ t1("`t1title'") t2("`t2title'")  /*
			*/ b2("`b2title'") l1("`l1title'") `saving' `options'
		}
	}

	* make sure that dataset is not damaged
	if "`generat'" ~= "" & "`add'" ~= "" {
		use "`new'", clear
	}

	* all went well, keep any generated vars
	if "`generat'" ~= "" {
		if _N > `old_N' {
			di in blu "obs was `old_N', now " _N
		}
		restore, not
	}
end

program define ChkPool, rclass
        args pool add if in graph
	capture which gphdt
	if _rc ~= 0 {
		di in re "pool() must have gphdt.ado installed"
		exit _rc
	}
	capture which gphsave
	if _rc ~= 0 {
		di in re "pool() must have gphsave.ado installed"
		exit _rc
	}
	if "`add'" == "" {
		di in red "pool() must be used with add()"
		exit 198
	}
	if "`if'" == "" & "`in'" == "" {
		di in red "must use pool() with [if exp] or [in range]"
		exit 198
	}
	if "`graph'" == "" {
		di in red "may not specify both nograph and pool()"
		exit 198
	}
	if `pool' < 1 | `pool' > 100 {
		di in red `"pool(`pool') invalid"'
		di in blue "pool() must be an integer between 1 and 100"
		exit 198
	}
	capture confirm new variable _merge
	if _rc ~= 0 {
		di in red "drop _merge before running ellip with pool()"
		exit _rc
	}
end

program define DoCons, rclass	/* returns r(c) */
	args p n level stat coefs
	tempname Fadj F c Chi T2
	local means_d `"("`stat'" == "" & "`coefs'" ~= "coefs")"'
	local coefs_d `"("`stat'" == "" & "`coefs'" == "coefs")"'
	if "`stat'" ~= "sd" {
		if "`p'" == "" {
			local p "2"
		}
		else {
			local p "`p'"
		}
	}

	if "`stat'" == "fadj" {
		scalar `Fadj' = invfprob(2,(`n'-`p'),(100-`level')/100)
		ret scalar c = (2 * `Fadj')
	}
	else if "`stat'" == "f" | `coefs_d' {
		scalar `F' = invfprob(`p',(`n'-`p'),(100-`level')/100)
		ret scalar c = (2 * `F')
	}
	else if "`stat'" == "chisq" {
		ret scalar c = invchi(`p',(100-`level')/100) /* v6 */
	}
	else if "`stat'" == "chisqn" {
		scalar `Chi' = invchi(`p',(100-`level')/100) /* v6 */
		ret scalar c = (`Chi' / `n')
	}
	else if "`stat'" == "tsq" | "`stat'" == "hotel" {
		scalar `F' = invfprob(`p',(`n'-`p'),(100-`level')/100)
		scalar `T2' = `p' * (`n'-1) / (`n'-`p') * `F'
		ret scalar c = `T2'
	}
	else if "`stat'" == "tsqn" | "`stat'" == "hoteln" {
		scalar `F' = invfprob(`p',(`n'-`p'),(100-`level')/100)
		scalar `T2' = `p' * (`n'-1) / (`n'-`p') * `F'
		ret scalar c = (`T2' / `n')
	}
	else if "`stat'" == "ptsq" | "`stat'" == "photel" {
		scalar `F' = invfprob(`p',(`n'-`p'),(100-`level')/100)
		scalar `T2' = `p' * (`n'-1) / (`n'-`p') * `F'
		ret scalar c = (`T2' * (`n' + 1) / `n')
	}
	else if "`stat'" == "sd" | `means_d' {
		tempname c
		if "`p'" == "" {
			ret scalar c = 4	/* overall default */
		}
		else {
			ret scalar c = (`p')^2
		}
	}
end
exit

HISTORY
1.3.0 Anders Alexandersson 16jan2003                    SSC Archive
       - ellip7 for Stata 7; see ellip7.ado and ellip7.hlp
1.2.0 Anders Alexandersson 16jan2003                    SSC Archive
       - fixes several minor bugs such as some r() in 1.1.0
       - is tested to reproduce 20+ published confidence ellipses
       - allows more options in c()
       - uses more modular code and calls -graph- less times
       - ellip6 replaces version 1.1.0 with same requirements
1.1.0 Anders Alexandersson 12nov2001                    SSC Archive
       - allows centering around means and more types of ellipses
       - requires Stata 6, gphdt for pool()
       - version 1.0.0 renamed into ellip5.ado
1.0.0 Anders Alexandersson 7aug1998                     STB-46: gr32
       - graphs confidence ellipses around regression coefficients
       - requires Stata 5, gphdt for pool(), parsoptp