*! conjoint 1.0.0 22may2021
*! author Michael J. Frith

program conjoint, eclass
	version 16

	if replay() {
		if ("`e(cmd)'" != "conjoint") error 301
		syntax , [NOtable graph GRAPH2(integer -1)]

		local subgroup `e(subgroupvar)'
		local xvars `e(indepvars)'
		local estimate `e(estimate)'
		local depvar `e(depvar)'
		if ("`id'" != "") local id `e(clustvar)'
		if ("`estimate'" == "amce") local constraints `e(constraints)'
		if ("`estimate'" == "amce") local baselevels `e(baselevels)'
		if ("`estimate'" == "mm") local h0 `e(h0)'

		/* collects data from ereturn needed for replay */
		if "`subgroup'" != "" {
			foreach sub_label in `e(subgroup_labels)' {
				if ("`id'" != "") local N_clust `e(N_clust_`sub_label')'
				else local N_clust .
				if ("`estimate'" == "amce") matrix modelstats_`sub_label' = ///
				`e(N_`sub_label')' \ `e(df_m_`sub_label')' \ ///
				`e(df_r_`sub_label')' \ `e(r2_`sub_label')' \ ///
				`e(F_`sub_label')' \ `N_clust'
				else matrix modelstats_`sub_label' = `e(N_`sub_label')' \ . ///
				\ `e(df_r_`sub_label')' \ . \ . \ `N_clust'
				ereturn matrix modelstats_`sub_label' modelstats_`sub_label'
			}
		}
		else {
			if ("`id'" != "") local N_clust `e(N_clust)'
			else local N_clust .
			if ("`estimate'" == "amce") matrix modelstats = `e(N)' \  ///
			`e(df_m)' \ `e(df_r)' \ `e(r2)' \ `e(F)' \ `N_clust'
			else matrix modelstats = `e(N)' \ . \ `e(df_r)' \ . \ . \ `N_clust'
			ereturn matrix modelstats modelstats
		}
	}
	else {
		syntax varlist(min=2) [if] [in], ESTimate(string) [ID(varname) ///
		SUBgroup(varname) BASElevels(numlist int) CONstraints(varlist fv) ///
		h0(real -1) NOtable graph GRAPH2(integer -1)]
		marksample touse
		gettoken depvar xvars: varlist

		/* prepare and check inputs */
		conjoint_prep , xvars(`xvars') estimate(`estimate') h0(`h0') ///
		constraints(`constraints') baselevels(`baselevels') rawcmd(`0') ///
		subgroup(`subgroup') graph("`graph'") graph2("`graph2'")

		/* estimate effects */
		if "`estimate'" == "mm" {
			conjoint_est_mm, depvar(`depvar') xvars(`xvars') ///
			resmat_size(`e(resmat_size)') subgroup(`subgroup') h0(`h0') ///
			id(`id') touse(`touse')
		}
		else if "`estimate'" == "amce" {
			conjoint_est_amce, depvar(`depvar') xvars(`xvars') ///
			resmat_size(`e(resmat_size)') regress_xvars(`e(regress_xvars)') ///
			baselevels(`e(baselevels)') subgroup(`subgroup') id(`id') ///
			touse(`touse')
		}
	}

	/* display results, allowed for replay */
	conjoint_disp , subgroup(`subgroup') xvars(`xvars')	notable("`notable'") ///
	graph("`graph'") graph2("`graph2'") estimate("`estimate'") rawcmd(`0') ///
	depvar(`depvar') clustvar(`id') constraints(`constraints') ///
	baselevels(`e(baselevels)') h0(`h0')
end

program conjoint_prep, eclass
	syntax , xvars(varlist) estimate(string) [h0(real -1) ///
	constraints(string) baselevels(numlist int) rawcmd(string) ///
	subgroup(varname) graph(string) graph2(string)]

	/* if estimating amces */
	if "`estimate'"=="amce" {
	    /* check if h0 is specified as not valid for amce */
		if `h0' != -1 {
			di as error "h0 cannot be specified when estimating amce"
			exit 198
		}

		/* check constraints */
		/* error if full-factorial interaction (in constraints) */
		if strpos("`rawcmd'","##") {
			di as error "full-factorial interactions (##) not allowed in constraints"
			exit 198
		}
		/* error if spaces are found in constraints */
		if strpos("`rawcmd'"," #") | strpos("`rawcmd'","# ") {
			di as error "spaces between # and variables not allowed in constraints"
			exit 198
		}
		/* ensures constraints is suitably formatted */
		local constraints: subinstr local constraints "i." "", all
		local constraints_cln: subinstr local constraints "#" " ", all
		if strpos("`constraints_cln'",".") {
			di as error "unary operators not allowed in constraints"
			exit 198
		}
		/* checks for multiple occurrences of the same var in constraints */
		local dup_constraints : list dups constraints_cln
		if "`dup_constraints'" != "" {
			di as error "repeated variables in constraints not allowed"
			exit 198
		}
		/* find non-constrained vars, add them to constraint list */
		local missing_xvars : list xvars - constraints_cln
		local regress_xvars : list constraints | missing_xvars

		/* check and clean baselevels */
		/* if baselevels entered */
		if "`baselevels'" != "" {
			local base_count : word count `baselevels'
			local var_count : word count `xvars'
			/* if incorrect number of baselevels are specified */
			if `base_count' != `var_count' {
				di as error "incorrect number of baselevels specified"
				exit 198
			}
			/* if correct number of baselevels, check those levels exist */
			else {
				forvalues i = 1/`var_count' {
					local base : word `i' of `baselevels'
					local xvar : word `i' of `xvars'
					quietly levelsof `xvar', local(xvar_levels)
					local base_check: list base in xvar_levels
					if `base_check' != 1 {
						di as error "baselevel `base'.`xvar' not found"
						exit 198
					}
				}
			}
		}
		/* if baselevels are not entered, use first value of each variable */
		else {
			foreach xvar of local xvars {
				quietly levelsof `xvar'
				local base: word 1 of `r(levels)'
				local baselevels `baselevels' `base'
			}
		}
	}
	/* if estimating mms */
	else if "`estimate'"=="mm" {
	    /* check if constraints are specified as not valid for mm */
		if "`constraints'" != "" {
			di as error "constraints cannot be specified when estimating mm"
			exit 198
		}

		/* check if baselevels are specified as not valid for mm */
		if "`baselevels'" != "" {
			di as error "baselevels cannot be specified when estimating mm"
			exit 198
		}
	}
	/* otherwise unknown */
	else {
		di as error "unknown metric in estimate"
		exit 198
	}

	/* check for duplicate xvars */
	local dup_vars : list dups xvars
	if "`dup_vars'" != "" {
		di as error "repeated variables in varlist"
		exit 198
	}

	/* check graph settings */
	if "`graph'`graph2'" != "-1" {
		if "`subgroup'" != ""  {
			quietly levelsof `subgroup'
			local subgroup_count : word count `r(levels)'
			if `subgroup_count' < `graph2' {
				di as error "graph() value is too large for the number of subgroups"
				exit 198
			}
		}
		else if `graph2' > 0 {
			di as error "graph() value incorrect as no subgroups specified"
			exit 198
		}
	}

	/* calculate size of matrix for results */
	foreach xvar of local xvars {
		quietly levelsof `xvar', local(xvar_levels)
		local resmat_size = `resmat_size' + `r(r)'
	}

	ereturn local regress_xvars `regress_xvars'
	ereturn local baselevels `baselevels'
	ereturn scalar resmat_size = `resmat_size'

end

program conjoint_est_mm, eclass
	syntax , depvar(varname) xvars(varlist) resmat_size(int) ///
	[subgroup(varname) h0(real -1) id(varname) touse(string)]

	/* revert default value of H0 */
	if (`h0' == -1) local h0 = 0.5

	/* if subgroups, generate if conditions and result matrix names */
 	if "`subgroup'" != "" {
 	    quietly levelsof `subgroup'
 		foreach sub in `r(levels)' {
 			local if_conditions `if_conditions' `subgroup'==`sub'&`touse'
 			local subgroup_label : label (`subgroup') `sub'
			local subgroup_label = strtoname("`subgroup_label'")
 			local resmat_names `resmat_names' results_`subgroup_label'
			local modelstatsmat_names `modelstatsmat_names' modelstats_`subgroup_label'
 		}
 	}
	/* if no subgroups, use touse as if condition and use generic matrix name */
 	else {
 	    local if_conditions `touse'
		local resmat_names results
		local modelstatsmat_names modelstats
 	}

	/* main code */
	local num_models : word count `if_conditions'
	forvalues model_num = 1/`num_models' {
		local if_condition : word `model_num' of `if_conditions'
		local resmat_name : word `model_num' of `resmat_names'
		local modelstats_name : word `model_num' of `modelstatsmat_names'
		matrix `resmat_name' = J(`resmat_size',6,.)
		local matrix_rownum = 1

		/* original r code computes models seperately for each var (and sub) */
		foreach xvar of local xvars {
			quietly regress `depvar' i.`xvar' if `if_condition', cluster(`id')
			if ("`id'"!= "") local N_clust = `e(N_clust)'
			quietly margins i.`xvar', post
			mat tempres = r(table)

			local matpos 1
			quietly levelsof `xvar'
			foreach xvar_level in `r(levels)' {
				quietly lincom "(_b[`xvar_level'.`xvar'] - `h0')"
				matrix `resmat_name'[`matrix_rownum',1] = ///
				tempres[1,`matpos'], tempres[2,`matpos'], r(t), r(p),  ///
				tempres[5,`matpos'], tempres[6,`matpos']
				local ++matpos
				local ++matrix_rownum
			}
		}
		if "`id'"!= "" {
			matrix `modelstats_name' = `e(N)' \ . \ `e(df_r)' \ . \ . \ `N_clust'
		}
		else {
			matrix `modelstats_name' = `e(N)' \ . \ `e(df_r)' \ . \ . \ .
		}
	}

	ereturn clear
	forvalues model_num = 1/`num_models' {
		local resmat_name : word `model_num' of `resmat_names'
		local modelstats_name : word `model_num' of `modelstatsmat_names'
		ereturn matrix `resmat_name' `resmat_name'
		ereturn matrix `modelstats_name' `modelstats_name'
	}
end

program conjoint_est_amce, eclass
	syntax , depvar(varname) xvars(varlist) resmat_size(int) ///
	[regress_xvars(string) baselevels(numlist int) subgroup(varname) ///
	id(varname) touse(string)]

	/* if subgroups, generate if conditions and result matrix names */
 	if "`subgroup'" != "" {
 	    quietly levelsof `subgroup'
 		foreach sub in `r(levels)' {
 			local if_conditions `if_conditions' `subgroup'==`sub'&`touse'
 			local subgroup_label : label (`subgroup') `sub'
			local subgroup_label = strtoname("`subgroup_label'")
 			local resmat_names `resmat_names' results_`subgroup_label'
			local modelstatsmat_names `modelstatsmat_names' modelstats_`subgroup_label'
 		}
 	}
	/* if no subgroups, use touse as if condition and use generic matrix name */
 	else {
 	    local if_conditions `touse'
		local resmat_names results
		local modelstatsmat_names modelstats
 	}

	/* main code */
	local num_models : word count `if_conditions'
	forvalues model_num = 1/`num_models' {
		local if_condition : word `model_num' of `if_conditions'
		local resmat_name : word `model_num' of `resmat_names'
		local modelstats_name : word `model_num' of `modelstatsmat_names'
		matrix `resmat_name' = J(`resmat_size',6,.)
		local matrix_rownum = 1

		quietly regress `depvar' i.(`regress_xvars') if `if_condition', cluster(`id')
		quietly margins `regress_xvars'
		mat reg_errors = r(error)

		local xvar_count : word count `xvars'
		forvalues i = 1/`xvar_count' {
			local focal_xvar : word `i' of `xvars'
			local focal_xvar_baselevel : word `i' of `baselevels'

			/* find focal_var in regress xvar list */
			foreach regress_xvar of local regress_xvars {

				local regress_xvar: subinstr local regress_xvar "#" " ", all
				/* check if the focal var is in regress xvar */
				local found : list focal_xvar in regress_xvar

				/* found the focal xvar in the regress xvar */
				if `found' ==1 {

					/* all other xvars interacted with focal xvar */
					local other_xvars : list regress_xvar - focal_xvar

					/* iterate through all other levels of focal var */
					quietly levelsof `focal_xvar', local(focal_xvar_levels)
					foreach focal_xvar_level of local focal_xvar_levels {

						/* if that level is not the baselevel */
						if "`focal_xvar_level'" != "`focal_xvar_baselevel'" {
							local list `focal_xvar'

							/* loop through all other vars in the regress_xvar */
							foreach other_xvar of local other_xvars {

								/* create all combinations */
								quietly levelsof `other_xvar', local(other_xvar_levels)
								foreach other_xvar_level of local other_xvar_levels {
									foreach element of local list {
										local list_latest `list_latest' ///
										`element'#`other_xvar_level'.`other_xvar'
									}
								}
								local list `list_latest'
								local list_latest ""
							}
							local count 0
							local base_string 0
							local focal_string 0
							foreach element of local list {
								if reg_errors["r1","`focal_xvar_level'.`element'"] ==0 & ///
								reg_errors["r1","`focal_xvar_baselevel'.`element'"] == 0 {
									local ++count
									local focal_string ///
									"`focal_string'+`focal_xvar_level'.`element'"
									local base_string ///
									"`base_string'+`focal_xvar_baselevel'.`element'"
								}
							}

							quietly lincom "(((`focal_string')/`count')-((`base_string')/`count'))"
							matrix `resmat_name'[`matrix_rownum',1] =  ///
							r(estimate),r(se),r(t),r(p),r(lb),r(ub)
						}
						else {
							matrix `resmat_name'[`matrix_rownum',1] =  ///
							0, ., ., ., ., .
						}
						local ++matrix_rownum
					}

				}

			}
		}

		/* saves the model statistics */
		if "`id'"!= "" {
			matrix `modelstats_name' = `e(N)' \ `e(df_m)' \ `e(df_r)' \ `e(r2)' \ ///
			`e(F)' \ `e(N_clust)'
		}
		else {
			matrix `modelstats_name' = `e(N)' \ `e(df_m)' \ `e(df_r)' \ `e(r2)' \ ///
			`e(F)' \ .
		}
	}

	ereturn clear
	forvalues model_num = 1/`num_models' {
		local resmat_name : word `model_num' of `resmat_names'
		local modelstats_name : word `model_num' of `modelstatsmat_names'
		ereturn matrix `resmat_name' `resmat_name'
		ereturn matrix `modelstats_name' `modelstats_name'
	}
	ereturn local regress_xvars `regress_xvars'
	ereturn local baselevels `baselevels'
end

program conjoint_disp, eclass
	syntax , xvars(varlist) [subgroup(varname) notable(string) ///
	graph(string) graph2(string) estimate(string) rawcmd(string) ///
	depvar(varname) clustvar(varname) constraints(string) ///
	baselevels(numlist) h0(string)]

 	/* revert default value of H0 */
 	if ("`estimate'" == "mm" & "`h0'" == "-1") local h0 0.5

	//create labels for table (and graph)
	foreach var in `xvars' {
		local var_label: variable label `var'
		if ("`var_label'"=="") local var_label = "`var'"
		local mat_var_label = strtoname("`var_label'")
		local plot_var_labels "`plot_var_labels' "{bf:`var_label'}""
		quietly levelsof `var'
		foreach var_level in `r(levels)' {
			local level_label : label (`var') `var_level'
			local mat_level_label = strtoname("`level_label'")
			local mat_var_labels `mat_var_labels' `mat_var_label'
			local mat_level_labels `mat_level_labels' `mat_level_label'
			local plot_level_labels "`plot_level_labels' `mat_level_label'=	"`level_label'" "
		}
	}

	/* create correct titles and graph xline for the est */
	if "`estimate'"=="amce" {
		local tabletitle "Estimated average marginal component effects (AMCEs)"
		local plottitle "Estimated AMCEs"
		local plotxline 0
	}
	else {
		local tabletitle "Estimated marginal means (MMs)"
		local plottitle "Estimated MMs"
		local plotxline `h0'
	}


	/* convert e(matrics) for results and stats to local to avoid being cleared */
	if "`subgroup'" != "" {
 	    quietly levelsof `subgroup', local(subgroups)
 		foreach sub of local subgroups {
		    local subgroup_label : label (`subgroup') `sub'
			local subgroup_label = strtoname("`subgroup_label'")
			matrix results_`subgroup_label' = e(results_`subgroup_label')
			matrix colnames results_`subgroup_label' = "Est." "SE" "t" "P>|t|" "LCI" "UCI"
			matrix roweq results_`subgroup_label' = `mat_var_labels'
			matrix rownames results_`subgroup_label' = `mat_level_labels'
			matrix modelstats_`subgroup_label' = e(modelstats_`subgroup_label')
		}
	}
	else {
	    matrix results = e(results)
		matrix colnames results = "Est." "SE" "t" "P>|t|" "LCI" "UCI"
		matrix roweq results = `mat_var_labels'
		matrix rownames results = `mat_level_labels'
		matrix modelstats = e(modelstats)
	}

	/* display table if notable is not entered */
	if "`notable'" == "" {
	    if "`subgroup'" != "" {
			foreach sub of local subgroups {
				local subgroup_label : label (`subgroup') `sub'
				local subgroup_label = strtoname("`subgroup_label'")
				di as text "{hline}"
				di ""
				di as result "`tabletitle' "
				di as text "`subgroup' = `subgroup_label'"
				di "Number of observations = " modelstats_`subgroup_label'[1,1]
				if ("`clustvar'" != "") di "Number of respondents = " ///
				modelstats_`subgroup_label'[6,1]
				if ("`estimate'" == "mm") di as text "H0 = `h0'"
				matlist results_`subgroup_label', border(rows) ///
				rowtitle(Variable / Levels) format(%8.4f) twidth(25) underscore
				if "`estimate'" == "amce" & "`constraints'" != "" {
					local newconstraints: subinstr local constraints "i." "", all
					di "Note: constraints between `newconstraints'"
				}
				di ""
			}
		}
		else {
			di ""
			di as result "`tabletitle' "
			di as text "Number of observations = " modelstats[1,1]
			if ("`clustvar'" != "") di "Number of respondents = " modelstats[6,1]
			if ("`estimate'" == "mm") di as text "H0 = `h0'"
			matlist results, border(rows) rowtitle(Variable / Levels) ///
			format(%8.4f) twidth(25) underscore
			if "`estimate'" == "amce" & "`constraints'" != "" {
				local newconstraints: subinstr local constraints "i." "", all
				di "Note: constraints between `newconstraints'"
			}
		}
	}

	/* display graph if specified */
	if "`graph'`graph2'" != "-1" {
		/* if one plot */
		if "`graph'"=="graph" | "`graph2'"=="0" | "`subgroup'"=="" {
		    /* if one model */
			if ("`subgroup'"=="") local graph_code "(matrix(results[,1])) "
			/* multiple models (one plot) */
			else {
				foreach sub of local subgroups {
					local subgroup_label : label (`subgroup') `sub'
					local subgroup_label = strtoname("`subgroup_label'")
					local graph_code ///
					"`graph_code' (matrix(results_`subgroup_label'[,1]), label(`subgroup_label')) "
				}
			}
			/* plot the single plot */
			quietly coefplot `graph_code', ci((5 6)) keep(*:) xline(`plotxline', ///
			lpattern(-) lcolor(black)) coeflabels(`plot_level_labels') ///
			eqlabels(`plot_var_labels', asheadings) graphregion(col(white)) ///
			scale(0.7) xtitle({bf:`plottitle'})

			local graph_output_code "coefplot `graph_code', ci((5 6)) keep(*:) " ///
			"xline(`plotxline', lpattern(-) lcolor(black)) " ///
			"coeflabels("`"`plot_level_labels'"'") eqlabels("`"`plot_var_labels'"'", asheadings) " ///
			"graphregion(col(white)) scale(0.7) xtitle({bf:`plottitle'})"
		}
		/* multiple plots (and models) */
		else {
			foreach sub of local subgroups {
				local subgroup_label : label (`subgroup') `sub'
				local subgroup_label = strtoname("`subgroup_label'")
				local graph_code ///
				"`graph_code' matrix(results_`subgroup_label'[,1]), bylabel(`subgroup_label') ||"
			}
			/* plot the multiple plots */
			quietly coefplot `graph_code', ci((5 6)) keep(*:) xline(`plotxline', lpattern(-) ///
			lcolor(black)) coeflabels(`plot_level_labels') ///
			eqlabels(`plot_var_labels', asheadings) byopts(graphregion(col(white)) ///
			cols(`graph2')) subtitle(, fcolor(gs15)) scale(0.7) xtitle({bf:`plottitle'})

			local graph_output_code "coefplot `graph_code', ci((5 6)) keep(*:) " ///
			"xline(`plotxline', lpattern(-) lcolor(black)) coeflabels("`"`plot_level_labels'"'") " ///
			"eqlabels("`"`plot_var_labels'"'", asheadings) byopts(graphregion(col(white)) " ///
			"cols(`graph2')) subtitle(, fcolor(gs15)) scale(0.7) xtitle({bf:`plottitle'})"
		}
	}

	ereturn clear
	/* ereturn results */
	if "`subgroup'" != "" {
		foreach sub of local subgroups {
			local subgroup_label : label (`subgroup') `sub'
			local subgroup_label = strtoname("`subgroup_label'")
			local subgroup_labels `subgroup_labels' `subgroup_label'
			ereturn scalar N_`subgroup_label' = modelstats_`subgroup_label'[1,1]
			ereturn scalar df_r_`subgroup_label' = modelstats_`subgroup_label'[3,1]
			if "`estimate'" == "amce" {
				ereturn scalar df_m_`subgroup_label' = modelstats_`subgroup_label'[2,1]
				ereturn scalar r2_`subgroup_label' = modelstats_`subgroup_label'[4,1]
				ereturn scalar F_`subgroup_label' = modelstats_`subgroup_label'[5,1]
			}
			ereturn matrix results_`subgroup_label' results_`subgroup_label'
			/* if clustering used */
			if "`clustvar'" != "" {
				ereturn scalar N_clust_`subgroup_label' = modelstats_`subgroup_label'[6,1]
			}
		}
		ereturn local subgroupvar `subgroup'
		ereturn local subgroups `subgroups'
		ereturn local subgroup_labels `subgroup_labels'
	}
	else {
		ereturn scalar N = modelstats[1,1]
		ereturn scalar df_r = modelstats[3,1]
		ereturn matrix results results
		/* if mm are estimated, seperate models so some stats not reported */
		if "`estimate'" == "amce" {
			ereturn scalar df_m = modelstats[2,1]
			ereturn scalar r2 = modelstats[4,1]
			ereturn scalar F = modelstats[5,1]
		}
		/* if clustering used */
		if "`clustvar'" != "" {
			ereturn scalar N_clust = modelstats[6,1]
		}
	}

	ereturn local cmd "conjoint"
	ereturn local cmdline "conjoint `rawcmd'"
	ereturn local depvar `depvar'
	ereturn local title `tabletitle'
	ereturn local model "ols"
	ereturn local indepvars `xvars'
	ereturn local estimate `estimate'
	if "`clustvar'" != "" {
		ereturn local clustvar `clustvar'
		ereturn local vce "cluster"
	}
	else {
		ereturn local vce "ols"
	}
	if ("`graph'`graph2'" != "-1") ereturn local graph_code `graph_output_code'
	if ("`estimate'" == "amce") ereturn local baselevels `baselevels'
	if ("`estimate'" == "amce") ereturn local constraints `constraints'
	if ("`estimate'" == "mm") ereturn scalar h0 = `h0'
end