*! version 2.3.2 16feb2025  Richard Williams, rwilliam@nd.edu

* 1.0.0 - Initial release 
* 1.0.1 - BETA experimental log link added
* 1.0.2 - BETA hetero/scale options added
* 1.0.3 - BETA Bug fixes
* 1.0.4 - BETA flip option added
* 1.0.5 - BETA Cauchit (Inverse Cauchy) link added
* 1.1.0 - 2nd official release
* 1.1.1 - Minor release - showeqns option added to ml display; version changed to 9.2
* 1.1.2 - Fixed problem with pweights resulting in incorrect pseudo R^2.
*	  pweights are now rescaled. (Feb 1, 2007)
* 1.1.3 - Added svyb and svyj as supported properties (May 6, 2007)
* 1.1.4 - Fixed esoteric bug with value labels.  Help revised to indicate
*         better citations & references.
* 2.0.0 - Updated for Stata 11. Supports factor variables and the margins
*         command. Those with Stata 9 or 10 should use oglm9 instead.
* 2.0.1 - Predict option tweaked to default to outcome #1 if
*         no outcome specified for pr or score. This helps the routine to 
*         work with the spost13 commands.
* 2.1.0 - Added support for more Stata display options. Added check program
*         for when svy: is used. svy option dropped; use svy: prefix instead.
* 2.1.1 - Fixed bug in or option. Undocumented Collinear option added. 
*         Added by request. Not recommended, use at your own risk.
*         Help file updated.
* 2.1.2 - Fixed a problem of perfect collinearity with rmcoll when using subsamples
*         Fixed problem with ml options not working correctly
*         renamed mlopts and diopts to be consistent with other Stata commands
* 2.2.0 - ereturns marginsdefault. In Stata 14+, this will cause the margins command 
*         to default to using all the outcomes rather than just the first.
* 2.3.0 - Added modifications provided by Ben Shear, Stanford, to specify 
*         initial values via the STARTVALS option
* 2.3.1 - ordwarm2.dta added as an ancilliary file
* 2.3.2 - e(cmdline) added to the ereturns

program oglm, eclass byable(recall) sortpreserve properties(swml svyr svyb svyj or irr rrr hr eform mi fvaddcons)
	version 11.2
	macro drop oglmby oglmx oglmh Link dv_*

// Replay results if that is all that is requested. 

	if replay() {
		if "`e(cmd)'" != "oglm"  {
			display as error "oglm was not the last estimation command"
			exit 301
		}
		if _by() {
			display as error "You cannot use the by command " ///
				"when replaying oglm results"
			exit 190
		}
		Replay `0'
		exit
	}
	else {

// This not a replay, so estimate the model.  

	// The bysample code identifies the cases currently selected by by.
	// Cases not in the by sample will later be marked out.
		tempvar bysample
		quietly generate byte `bysample' = 1
		quietly if _by() replace `bysample' = . if `_byindex'!=_byindex()
		global oglmby `bysample'
		Estimate `0'
	}
	// Drop global vars
	macro drop oglmby oglmx oglmh Link dv_*
	// ereturn cmdline
	ereturn local cmdline `"oglm `0'"'

end

************************
program Estimate, eclass
	version 11.1
	syntax [varlist(default=none fv)] [if] [in]		/// Standard Stata
		[pweight fweight iweight]  [, 			///
		ROBust CLuster(varname) 			///
		Constraints(string) 				///
		SCore(passthru) 				///
		Level(cilevel)					/// display options
		or rrr irr hr EForm LOG  			///
		LRForce STOre(name) 				/// special oglm options
		LInk(string)					/// Link fncs, e.g. logit, probit
		HETero(varlist fv) SCALE(varlist fv) eq2(varlist fv)	/// Synonyms, use one only
		flip						/// Reverse location & scale equations
		HC LS						/// Affects equation labeling
		TRUstme	FORCE					/// Override some otherwise fatal errors
		COLlinear					/// Don't check for collinearity. Affects both equations
		STARTVALS(name)					/// Name of matrix containing desired initial values; ben shear
		*   						/// ml options
		]

// Syntax checks, special oglm options

	// nolog is the default unless log is specified
	if "`log'"=="" local nolog "nolog"
	
	// make sure only one eform specified.  If so, it will
	// be local macro eform
	local eform `or' `irr' `rrr' `hr' `eform'
	if `:list sizeof eform' > 1 {
		opts_exclusive "`eform'"
	}

	// Only one of hc or ls may be specified.  If so, it will
	// be local macro eqlabel
	local eqlabel `hc' `ls'
	if `:list sizeof eqlabel' > 1 {
		opts_exclusive "`eqlabel'"
	}

	
	// if trustme/force is specified, some otherwise fatal exit commands become warnings only
	if "`trustme'"!="" | "`force'"!="" {
		local comment "* "
		local warning "Warning! "
	}

// Mark the estimation sample.  More marking to come.
	marksample touse

// Syntax checks, normal Stata options

	// display and maximization options
	_get_diopts diopts options, `options'
	local diopts `diopts' `eform' level(`level')
	mlopts mlopts, `options'

	
	// Routine checks for cluster, weight
	if "`cluster'" != "" {
		local clopt cluster(`cluster')
	}
	if "`weight'" != "" {
		local wgt "[`weight'`exp']"
	}
	
	// Stata commands are inconsistent in accepting colons as an alternative to /.
	// Comma as an alternative to space sometimes works, sometimes does not.
	// We therefore change colons to / and commas to spaces, which always work.
	local constraints: subinstr local constraints ":" "/", all
	local constraints: subinstr local constraints "," " ", all

// Markout other missing values from the estimation sample
// Make sure we have cases!
	markout `touse' `offset' `exposure'
	markout `touse' `cluster', strok
	// limit to bysample
	markout `touse' $oglmby
 
	quietly count if `touse' !=0
	if r(N) == 0 {
		display as error "There are no observations left!"
		display as error "Double-check your sample selection."
		exit 2000
	}

// Additional checks on the varlist specfications

	// Get Y and X variables
	gettoken y x: varlist
	// get rid of collinear explanatory variables
	_rmcoll `x' if `touse', expand `collinear'
	local x `"`r(varlist)'"'
	_fv_check_depvar `y'
	
// Link functions

	// logit is the default link if none has been specified. This
	// could be changed if someone preferred a different default. 
	if "`link'"=="" local link "logit"

	// logit link
	if "`link'"=="logit" | "`link'"=="l" {
		local link "logit"
		local link_title "Ordered Logistic Regression"
	}
	// probit is also supported
	else if "`link'"=="probit" | "`link'"=="p" | {
		local link "probit"
		local link_title "Ordered Probit Regression"
	}
	// cloglog - SPSS calls this nloglog
	else if "`link'"=="cloglog" | "`link'"=="c" |  {
		local link "cloglog"
		local link_title "Ordered Cloglog Regression"
	}
	// loglog - SPSS calls this cloglog
	else if "`link'"=="loglog" | "`link'"=="ll"  {
		local link "loglog"
		local link_title "Ordered Loglog Regression"
	}
	// cauchit
	else if "`link'"=="cauchit" | "`link'"=="ca" | "`link'"=="cau"  {
		local link "cauchit"
		local link_title "Ordered Cauchit Regression"
	}
	// log - This link is experimental, may not work right
	else if "`link'"=="log"   {
		local link "log"
		local link_title "Ordered Log Regression"
	}
	// non-supported link
	else {
		display ""
		display as error ///
			"{yellow}`link'{red} is not a legal link function"
		display ""
		exit 198
	}
	global Link `link'


// heteroskedasticity/ scale/ eq2 option

	// Can only specify one of hetero, scale, eq2
	local het_options = 0
	foreach opt in hetero scale eq2 {
		if "``opt''"!="" local het_options = `het_options' + 1
	}
	if `het_options' > 1 {
		display as error "hetero, scale & eq2 are synonyms - use only one of them"
		exit 198
	}
	
	local hetero `scale' `hetero' `eq2'

	if "`hetero'" !="" {
		_rmcoll `hetero' if `touse', expand `collinear'
		local hetero `r(varlist)'
	}
	
	// Flip the 2 equations if so requested.  This is mainly useful
	// if using the sw or nestreg prefixes.
	if "`flip'"!="" {
		local vhetero `x'
		local vx `hetero'
		local hetero `vhetero'
		local x `vx'
	}

	// oglmx = 1 if there are X vars, 0 otherwise
	local Numx: word count `x'
	global oglmx = "`x'" != ""

	// oglmh = 1 if there are hetero vars, 0 otherwise
	local Numh: word count `hetero'
	global oglmh = "`hetero'" != ""

	// labeling of equations & model
	if $oglmh {
		local link_title Heteroskedastic `link_title'
		markout `touse' `hetero'
	}
	if "`eqlabel'"=="" {
		local eq1label `y'
		local eq2label lnsigma
	}
	else if "`eqlabel'"=="hc" {
		local eq1label choice
		local eq2label variance
	}
	else if "`eqlabel'"=="ls" {
		local eq1label location
		local eq2label scale
	}

	
// Generate all the new things we will need for the models

	// Get Y values from tab
	tempname Y_Values   /* Vector containing the values of the DV */
	quietly tab `y' if `touse', matrow(`Y_Values')
	local M = r(r)
	if `M' > 20 {
		di 
		di as error "`warning'`y' has `M' categories - a maximum of 20 is normally allowed"
		di as error "Make sure you are using an ordinal dependent variable"
		di
		`comment'exit 149
	}
	matrix `Y_Values' = `Y_Values''  /* Transpose the matrix */

	local  numcuts = `M' - 1
	local lastcat = `Y_Values'[1, `M']   /* last category will be base category */
	
	// ll program will use these values to determine 1rst, 2nd, 3rd, etc. values of Y
	macro drop dv_*
	forval i = 1/`M' {
		global dv_`i' = `Y_Values'[1, `i']
	}

// Build equations for full model to be estimated. There are 4 possible
// types of models, depending on whether or not Xs and hetero are specified.
	// First do for models with X's & hetero
	if $oglmx & $oglmh {
		local eqs (`eq1label': `y'=`x', noconstant) (`eq2label': `hetero', noconstant)
		forval i = 1/`numcuts' {
			local eqs "`eqs' (cut`i':)"
		}
	}
	// Next do Xs but no hetero
	else if $oglmx & !$oglmh {
		local eqs (`eq1label': `y'=`x', noconstant)
		forval i = 1/`numcuts' {
			local eqs "`eqs' (cut`i':)"
		}
	}

	// Next do hetero only, no Xs
	else if !$oglmx & $oglmh {
		local eqs (`eq2label': `y' = `hetero', noconstant)
		forval i = 1/`numcuts' {
			local eqs "`eqs' (cut`i':)"
		}
	}
	// Finally, no Xs & no hetero, i.e. cutpoints only
	else if !$oglmx & !$oglmh {
		local eqs (cut1: `y'=)
		forval i = 2/`numcuts' {
			local eqs "`eqs' (cut`i':)"
		}
	}

// Start values for each type of link will be in matrix b0
// The Start_Values subroutine will generate the start values
	tempname b0

// Estimate the constant-only model 
	Start_Values `y',  /// 
		wgt(`wgt') touse(`touse') `robust' clopt(`clopt') ///
		numcuts(`numcuts') b0(`b0')  ///
		link(`link') eqsx(`eqsx')  ///
		constraints(`constraints') ///
		mlopts(`mlopts') 
		
	local initopt `s(initopt)'
	if "`s(LL0)'"!="" local LL0 = `s(LL0)'
	
	if "`startvals'" != "" mat `b0' = `startvals'	// ben shear added this

// Estimate the final model; and add some stats

	ml model lf oglm_ll `eqs' `wgt' if `touse', 		///
		constraints(`constraints') `robust'  `clopt' 	 	///
		waldtest(-`numcuts') `initopt' title(`link_title') 	///
		collinear missing maximize nocnsnotes 			///
		`score' `nolog' `mlopts'

	ereturn repost, buildfvinfo ADDCONS
		
	// e(k_aux) tells Stata how many cutpoints there are.  If you don't
	// have this, everything gets printed out as separate equations.
	local numcuts2 `numcuts'
	ereturn scalar k_aux = `numcuts'
	ereturn scalar k_exp = `numcuts2'
		
	if "`LL0'"!="" ereturn scalar ll_0 = `LL0'

	// Compute McFadden's Pseudo R^2 if necessary info exists, i.e. it won't 
	// be possible after using svy because svy does not return it.
	// Also, Stata ml does not rescale pweights, but oglm will
	if (e(ll) < . & e(ll_0) < .) {
		if "`weight'" == "pweight" {
			local wgtvar: word 2 of `=e(wexp)'
			quietly sum `wgtvar' if e(sample)
			ereturn scalar ll = e(ll)/ r(mean)
		}
		ereturn scalar r2_p = 1 - (e(ll) / e(ll_0) )
	}

	// Stata reports a Wald rather than LR test whenever constraints have been imposed.
	// The lrforce parameter makes Stata report a LR test instead, provided
	// crittype is log likelihood.  Use this at your own risk
	local crittype = e(crittype)
	local chi2type = e(chi2type)
	if "`lrforce'"!="" & "`crittype'"=="log likelihood" & "`chi2type'"!="LR" {
		ereturn local chi2type "LR"
		ereturn scalar chi2 = -2 * (e(ll_0) - e(ll))
		ereturn scalar p = chi2tail(e(df_m),e(chi2))
	}

// Adapted from ologit.ado Stata 14 -- additional margins support for V 14
// I use some different names than ologit because I created equivalents 
// of them earlier, e.g. M instead of ncat
	forval i = 1/`M' {
			local j = `Y_Values'[1,`i']
			local mdflt `mdflt' predict(pr outcome(`j'))
	}
	ereturn local marginsdefault `"`mdflt'"'

// Return assorted values
	ereturn local xvars `x'
	ereturn local hetero `hetero'
	ereturn scalar k_cat = `M'
	ereturn scalar basecat = `lastcat'
	ereturn scalar ibasecat = `M'
	ereturn matrix cat = `Y_Values'
	ereturn scalar k_eform = 1
	ereturn scalar k_eq_model = e(k_eq) - e(k_aux)
	ereturn local link `link'

	

// Final cleanup

	macro drop oglmx oglmh Link dv_*

	ereturn local predict "oglm_p"
	ereturn local cmd "oglm"

	ereturn local marginsprop addcons
	ereturn local marginsok pr xb
	ereturn local marginsnotok sigma stdp

// display the results

	Replay, `diopts' store(`store')
	
end


************************

program Start_Values, sclass
* Gets the start values (i.e. the values of the cutpoints-only model)
	version 11.1
	syntax varname , 	///
		[robust clopt(string) ///
		wgt(string) touse(varname) b0(name) numcuts(integer -1) ///
		link(string) eqsx(string) ///
		constraints(string) ///
		mlopts(string) ]
	local numcuts = `numcuts'
	local M = `numcuts' + 1
	local y `varlist'
	
// Matrix b0 will contain the cumulative probabilities for each category
// LL0 will contain the log likelihood 0 for non-svy models
	quietly proportion `y' `wgt' if `touse' , `clopt' nolabel
	mat `b0' = e(b)
	forval j = 1/`M' {
		local LL0 = `LL0' + `e(N)' * `b0'[1, `j'] * ln(`b0'[1,`j'])
	}
	mat `b0' = `b0'[1, 1..`numcuts']
			
	// Now we convert the b's to cumulative percentages
	forval j = 2/`numcuts' {
		local i = `j' - 1
			mat `b0'[1, `j'] = `b0'[1, `j'] + `b0'[1, `i']
	}


// Convert as needed for the link function used. Remembers signs  are
// the opposite of gologit2
	if "`link'"=="logit" {
		forval j = 1/`numcuts' {
			mat `b0'[1,`j'] = logit(`b0'[1,`j'])
		}
	}
	else if "`link'"=="probit" {
		forval j = 1/`numcuts' {
			mat `b0'[1,`j'] = invnormal(`b0'[1,`j'])
		}
	}
	else if "`link'"=="cloglog" {
		forval j = 1/`numcuts' {
			mat `b0'[1,`j'] = -cloglog(1-`b0'[1,`j'])
		}
	}
	else if "`link'"=="loglog" {
		forval j = 1/`numcuts' {
			mat `b0'[1,`j'] = cloglog(`b0'[1,`j'])
		}
	}
	else if "`link'"=="cauchit" {
		forval j = 1/`numcuts' {
			mat `b0'[1,`j'] = tan(_pi * (`b0'[1,`j'] - .5))
		}
	}
	else if "`link'"=="log" {
		forval j = 1/`numcuts' {
			mat `b0'[1,`j'] = -ln(1-`b0'[1,`j'])
		}
	}


	forval i = 1/`numcuts' {
		local columnames `columnames' cut`i':_cons
	}
	matrix colnames `b0' = `columnames'

	// Below, Ben Shear added the " , skip" option which has no impact on default functioning of -oglm-
	local initopt init(`b0' , skip) lf0(`numcuts' `LL0')	
	sreturn local initopt `initopt'
	if "`LL0'"!="" sreturn local LL0 `LL0'

end

************************

program Replay, eclass
	version 11.1
	syntax [,				///
	Level(cilevel)			 	///
	or irr rrr hr EForm 			///
	STOre(name)				///
	* ]
	
	// make sure only one eform specified.  If so, it will
	// be local macro eform
	local eform `or' `irr' `rrr' `hr' `eform'
	if `:list sizeof eform' > 1 {
		opts_exclusive "`eform'"
	}

	
	// Make sure equation names show if hetero equation is being used
	if "`e(hetero)'" !="" local showeqns showeqns
	
	_get_diopts diopts , `options' 
	local diopts `diopts' `eform' level(`level') `showeqns'
	ml display , `diopts'
	
	
	// Store results if requested
	if "`store'"!="" estimates store `store'

end