*! domin version 3.5.1 1/12/2024 Joseph N. Luchman // version information at end of file **# Pre-program definition quietly include dominance.mata, adopath program define domin, eclass if `c(version)' < 15 { display "{err}As of {cmd:domin} version 3.5.0, the minimum version of Stata is 15." _newline /// "If you have an older version of Stata, most functionality of {cmd:domin} is available from" _newline /// `"{stata net install st0645:this} Stata journal article back to version 12."' exit 198 } version 15 if replay() { if ("`e(cmd)'" != "domin") error 301 if _by() error 190 Display `0' exit 0 } **# Program definition and argument checks syntax varlist(min = 1 ts) [in] [if] [aw pw iw fw] , /// [Reg(string) Fitstat(string) Sets(string) All(varlist fv ts) /// noCONditional noCOMplete EPSilon CONSmodel REVerse noESAMPleok mi miopt(string)] if strlen("`epsilon'") { local esampleok "esampleok" local conditional "conditional" local complete "complete" } if strlen("`mi'") | strlen("`mi'") { display "{err}{cmd:mi} and {opt miopt()} are depreciated. Please use " /// "{help mi_dom}." exit 198 } **# Wrapper process for -domin- processing in Mata mata: domin_2mata("`reg'", "`fitstat'", "`sets'", "`all'", /// "`conditional'", "`complete'", "`epsilon'", "`consmodel'", "`reverse'", /// "`esampleok'", "`weight'`exp'", "`in' `if'", "`varlist'") **# Ereturn processing tempname domwgts cdldom cptdom stdzd ranks if !strlen("`epsilon'") & strlen("`e(title)'") local title "`e(title)'" else if strlen("`epsilon'") & strlen("`e(title)'") local title "Epsilon-based `reg'" else local title "Custom user analysis" matrix `domwgts' = r(domwgts) ereturn post `domwgts' [`weight'`exp'], depname(`dv') obs(`=r(N)') esample(`touse') **# Ereturn scalars matrix `domwgts' = r(domwgts)*J(colsof(r(domwgts)), 1, 1) if strlen("`epsilon'") ereturn scalar fitstat_o = `=`domwgts'[1,1]' else ereturn scalar fitstat_o = `=r(fullfs) + r(allfs) + r(consfs)' if `:list sizeof all' ereturn scalar fitstat_a = `=r(allfs)' if strlen("`consmodel'") ereturn scalar fitstat_c = `=r(consfs)' if strlen("`setcount'") { ereturn hidden scalar setcount = `setcount' forvalues x = 1/`setcount' { fvunab set`x': `set`x'' ereturn local set`x' "`set`x''" } } else ereturn hidden scalar setcount = 0 **# Ereturn macros ereturn hidden local disp_title "`title'" ereturn hidden local reverse "`reverse'" if `:list sizeof all' { fvunab all: `all' ereturn local all "`all'" } if strlen("`epsilon'") ereturn local estimate "epsilon" else ereturn local estimate "dominance" if strlen("`regopts'") ereturn local regopts `"`regopts'"' ereturn local reg `"`reg'"' ereturn local fitstat "`fitstat'" ereturn local cmd `"domin"' ereturn local title `"Dominance analysis"' ereturn local cmdline `"domin `0'"' **# Ereturn matrices if !strlen("`complete'") { matrix `cptdom' = r(cptdom) mata: st_matrixcolstripe("`cptdom'", /// (J(cols(st_matrix("`cptdom'")), 1, "dominated?"), /// st_matrixcolstripe("e(b)")[,2])) mata: st_matrixrowstripe("`cptdom'", /// (J(cols(st_matrix("`cptdom'")), 1, "dominates?"), /// st_matrixcolstripe("e(b)")[,2])) ereturn matrix cptdom = `cptdom' } if !strlen("`conditional'") { matrix `cdldom' = r(cdldom) mata: st_matrixcolstripe("`cdldom'", /// (J(cols(st_matrix("`cdldom'")), 1, "#indepvars"), /// strofreal(1::cols(st_matrix("`cdldom'"))))) ereturn matrix cdldom = `cdldom' } matrix `ranks' = r(ranks) ereturn matrix ranking = `ranks' matrix `stdzd' = r(stdzd) ereturn matrix std = `stdzd' Display end /*Display program*/ program define Display version 15 tempname domwgts sdomwgts ranks matrix `domwgts' = e(b) matrix `sdomwgts' = e(std) matrix `ranks' = e(ranking) local diivs: colnames e(b) mata: st_local("cdltest", strofreal(cols(st_matrix("e(cdldom)")))) mata: st_local("cpttest", strofreal(cols(st_matrix("e(cptdom)")))) tokenize `diivs' local dv = abbrev("`e(depvar)'", 10) display _newline "{txt}General dominance statistics: `e(disp_title)'" _newline /// "{txt}Number of obs{col 27}={res}{col 40}" %12.0f e(N) display "{txt}Overall Fit Statistic{col 27}={res}{col 36}" %16.4f e(fitstat_o) if !missing(e(fitstat_a)) display "{txt}All Sub-models Fit Stat.{col 27}={res}{col 36}" %16.4f e(fitstat_a) if !missing(e(fitstat_c)) display "{txt}Constant-only Fit Stat.{col 27}={res}{col 36}" %16.4f e(fitstat_c) display _newline "{txt}{col 13}{c |}{col 20}Dominance{col 35}Standardized{col 53}Ranking" display "{txt}{lalign 9: `dv'}{col 13}{c |}{col 20}Stat.{col 35}Domin. Stat." display "{txt}{hline 12}{c +}{hline 72}" forvalues x = 1/`:list sizeof diivs' { local `x' = abbrev("``x''", 10) display "{txt}{col 2}{lalign 11:``x''}{c |}{col 14}{res}" %15.4f `domwgts'[1,`x'] /// "{col 29}" %12.4f `sdomwgts'[1,`x'] "{col 53}" %-2.0f `ranks'[1,`x'] } display "{txt}{hline 12}{c BT}{hline 72}" if `cdltest' { display "{txt}Conditional dominance statistics" _newline "{hline 85}" matrix list e(cdldom), noheader format(%12.4f) display "{txt}{hline 85}" } if `cpttest' { display "{txt}Complete dominance designation" _newline "{hline 85}" matrix list e(cptdom), noheader display "{txt}{hline 85}" } if e(estimate) == "dominance" & `=`cpttest'*`cdltest'' { display _newline "{txt}Strongest dominance designations" _newline tempname bestdom cdl gen decision if strlen("`e(reverse)'") mata: st_matrix("`bestdom'", st_matrix("e(cptdom)"):*-1) else matrix `bestdom' = e(cptdom) forvalues x = 1/`=colsof(e(cdldom))-1' { forvalues y = `=`x'+1'/`=colsof(e(cdldom))' { scalar `cdl' = 0 scalar `gen' = 0 mata: st_numscalar("`cdl'", (sum(st_matrix("e(cdldom)")[`x', .]:>st_matrix("e(cdldom)")[`y', .])):==rows(st_matrix("e(cdldom)"))) if !`cdl' mata: st_numscalar("`cdl'", -1*((sum(st_matrix("e(cdldom)")[`x', .]:st_matrix("e(b)")[1, `y']) if !`gen' mata: st_numscalar("`gen'", (st_matrix("e(b)")[1, `x'] 1 ) ivs = ivs[2..length(ivs)] else ivs = "" /*set processing*/ if ( strlen(sets) ) { /*setup parsing sets - bind on parentheses*/ parser = tokeninit(" ", "", "()") tokenset(parser, sets) /*get all sets and remove parentehses*/ iv_sets = tokengetall(parser) iv_sets = substr(iv_sets, 2, strlen(iv_sets):-2) st_local("setcount", strofreal( length(iv_sets) )) for (set=1; set<=length(iv_sets); set++) { st_local("set"+strofreal(set), iv_sets[set]) rc = _stata("fvunab waste: " + iv_sets[set], 1) if ( rc ) { display("{err}Problem with variables in set position " + strofreal(set) + ": " + iv_sets[set]) exit(111) } } /*combine to single iv vector*/ if ( length(ivs) > 1 ) ivs = (ivs, iv_sets) else ivs = iv_sets } if ( length(ivs) < 2 | ivs == "") { stata("display " + char(34) + "{err}{cmd:domin} requires at least 2 independent variables or independent variable sets." + char(34)) exit(198) } /* ~ parse regression options ~ */ parser = tokeninit(" ", ",") tokenset(parser, strtrim(reg)) reg = tokenget(parser) if ( length(tokenrest(parser)) ) regopts = substr(tokenrest(parser), 2) else regopts = "" /* ~ check primary analysis and set estimation sample ~ */ if ( !strlen(epsilon) ) { st_eclear() if (inif == " ") inif = "if _n > 0" rc = _stata(reg + " " + dv + " " + invtokens(ivs) + " " + all + " [" + weight + "] " + inif + ", " + regopts, 1) if ( rc ) { display("{err}{cmd:" + reg + "} resulted in an error.") exit(rc) } if ( !length( st_numscalar( strtrim(fitstat) ) ) ) { display("{err}{cmd:" + fitstat + "} not returned by {cmd:" + reg + "} or {cmd:" + fitstat + "} is not scalar valued. See {help return list}.") exit(198) } else full_fitstat = st_numscalar( strtrim(fitstat) ) marks = st_tempname() stata("generate byte " + marks[1] + " = e(sample)", 1) if (strmatch(weight, "fweight*") | strmatch(weight, "iweight*")) { stata("summarize " + marks[1] + " if " + marks[1] + " [" + weight + "]", 1) } else stata("count if " + marks[1], 1) obs = st_numscalar("r(N)") } else obs = 0 if (obs == 0 & strlen(esampleok) ) { marks = st_tempname(2) stata("mark " + marks[1]) stata("generate byte " + marks[2] + " = 1 " + inif, 1) stata("markout " + marks[1] + " " + marks[2] + " " + invtokens(ivs) + " " + all, 1) if (strmatch(weight, "fweight*") | strmatch(weight, "iweight*")) { stata("summarize " + marks[1] + " if " + marks[1] + " [" + weight + "]", 1) } else stata("count if " + marks[1], 1) obs = st_numscalar("r(N)") } if (obs == 0 & !strlen(esampleok)) { display("{err}{cmd:esample()} not set. Use {opt noesampleok} to avoid checking {cmd:esample()}.") exit(198) } /* ~ built-in linear regression-based model ~ */ if ( builtin ) { bltin_varlist = bltin_index = J(1, length(ivs), "") index_count = 1 for (iv = 1; iv <= length(ivs); iv++) { stata("fvexpand " + ivs[iv], 1) if ( strlen( st_macroexpand("`" + "r(fvops)" + "'") ) | strlen( st_macroexpand("`" + "r(tsops)" + "'") ) ) { bltin_index[iv] = st_macroexpand("`" + "r(varlist)" + "'") stata("fvrevar " + ivs[iv], 1) bltin_varlist[iv] = st_macroexpand("`" + "r(varlist)" + "'") if ( any( strmatch( tokens(bltin_index[iv]) , "*b.*") ) ) bltin_varlist[iv] = invtokens( select( tokens(bltin_varlist[iv]), !strmatch( tokens(bltin_index[iv]) , "*b.*") ) ) bltin_index[iv] = invtokens( strofreal((index_count..index_count+length( tokens( bltin_varlist[iv] ) )-1 ) ) ) index_count = index_count + length( tokens( bltin_varlist[iv] ) ) } else { bltin_varlist[iv] = ivs[iv] bltin_index[iv] = strofreal(index_count) index_count++ } } if ( strlen(all) ) { bltin_all = strofreal( ( index_count..index_count+length(tokens(all))-1 ) ) index_count = index_count + length( tokens(all) ) } else bltin_all = "" if (regexm(weight, "^[pi]weight=")) weight = regexr(weight, "^[pi]weight=", "aweight=") stata("correlate " + invtokens(bltin_varlist) + " " + all + " [" + weight + "] if " + marks[1], 1) bltin_corrmat = st_matrix("r(C)") stata("correlate " + dv + " " + invtokens(bltin_varlist) + " " + all + " [" + weight + "] if " + marks[1], 1) bltin_dvcor = st_matrix("r(C)")[1, 2..index_count] } /* ~ begin collecting effect sizes ~ */ all_fitstat = 0 if ( strlen(all) ) { stata(reg + " " + dv + " " + all + " [" + weight + "] if " + marks[1] + ", " + regopts, 1) all_fitstat = st_numscalar( strtrim(fitstat) ) } cons_fitstat = 0 if ( strlen(consmodel) ) { stata(reg + " " + dv + " [" + weight + "] if " + marks[1] + ", " + regopts, 1) cons_fitstat = st_numscalar( strtrim(fitstat) ) } if ( strlen(all) ) all_fitstat = all_fitstat - cons_fitstat full_fitstat = full_fitstat - all_fitstat - cons_fitstat /* ~ invoke dominance ~ */ if ( strlen(epsilon) ) { if (reg == "mvdom") stata("mvdom " + dv + " " + invtokens(ivs) + " if " + marks[1] + ", " + regopts + " epsilon", 1) else eps_ri(dv + " " + invtokens(ivs), reg, marks[1], regopts, ( length(tokens(weight, "=")) == 3 ) ? tokens(weight, "=")[3] : "") } else if ( builtin ) { model_specs.put("corrmat", bltin_corrmat) model_specs.put("dvcor", bltin_dvcor) model_specs.put("all", bltin_all) model_specs.put("all_fitstat", all_fitstat) model_specs.put("cons_fitstat", 0) dominance( model_specs, &domin_regress(), bltin_index', conditional, complete, full_fitstat ) } else { model_specs.put("reg", reg) model_specs.put("fitstat", fitstat) model_specs.put("weight", weight) model_specs.put("touse", marks[1]) model_specs.put("regopts", regopts) model_specs.put("all", all) model_specs.put("dv", dv) model_specs.put("consmodel", consmodel) model_specs.put("reverse", reverse) model_specs.put("all_fitstat", all_fitstat) model_specs.put("cons_fitstat", cons_fitstat) dominance( model_specs, &domin_call(), ivs', conditional, complete, full_fitstat ) } /* ~ return values ~ */ if ( length(iv_sets) ) ivs[(( length(ivs)-length(iv_sets)+1 )..length(ivs))] = "set":+( strofreal( (1..length(iv_sets)) ) ) st_matrixcolstripe("r(domwgts)", (J(length(ivs), 1, ""), ivs') ) if ( strlen(reverse) ) st_matrix("r(cptdom)", st_matrix("r(cptdom)"):*-1 ) if ( !strlen(conditional) ) st_matrixrowstripe("r(cdldom)", (J(length(ivs), 1, ""), ivs') ) if ( !strlen(complete) ) { st_matrixrowstripe("r(cptdom)", (J(length(ivs), 1, ""), ivs') ) st_matrixcolstripe("r(cptdom)", (J(length(ivs), 1, ""), ivs') ) } st_matrix("r(stdzd)", (st_matrix("r(domwgts)")):/(sum(st_matrix("r(domwgts)") ) ) ) st_matrixcolstripe("r(stdzd)", (J(length(ivs), 1, ""), ivs') ) st_matrix("r(ranks)", colsum( J(cols(st_matrix("r(domwgts)")), 1, st_matrix("r(domwgts)") ):<=(st_matrix("r(domwgts)")') ) ) if ( strlen(reverse) ) st_matrix("r(ranks)", colsum( J(cols(st_matrix("r(domwgts)")), 1, st_matrix("r(domwgts)") ):>=(st_matrix("r(domwgts)")') ) ) st_matrixcolstripe("r(ranks)", (J(length(ivs), 1, ""), ivs') ) st_numscalar("r(N)", obs) st_local("reg", reg) st_local("regopts", regopts) st_local("touse", marks[1]) st_local("dv", dv) st_numscalar("r(allfs)", all_fitstat) st_numscalar("r(consfs)", cons_fitstat) st_numscalar("r(fullfs)", full_fitstat) } end **# Mata function to execute 'domin-flavored' models version 15 mata: mata set matastrict on real scalar domin_call(string scalar IVs_in_model, class AssociativeArray scalar model_specs) { real scalar fitstat stata(model_specs.get("reg") + " " + model_specs.get("dv") + " " + model_specs.get("all") + " " + IVs_in_model + " [" + model_specs.get("weight") + "] if " + model_specs.get("touse") + ", " + model_specs.get("regopts"), 1) fitstat = st_numscalar(model_specs.get("fitstat")) - model_specs.get("all_fitstat") - model_specs.get("cons_fitstat") return(fitstat) } end **# Mata function to execute built-in linear regression version 15 mata: mata set matastrict on real scalar domin_regress(string scalar IVs_in_model, class AssociativeArray scalar model_specs) { real scalar fitstat real rowvector index real colvector coeffs real matrix analysis_mat index = strtoreal( tokens( invtokens( (IVs_in_model, model_specs.get("all") ) ) ) ) coeffs = qrsolve( model_specs.get("corrmat")[index, index], model_specs.get("dvcor")[index]' ) fitstat = model_specs.get("dvcor")[index]*coeffs - model_specs.get("all_fitstat") return(fitstat) } end **# Mata function to execute epsilon-based relative importance version 12 mata: mata set matastrict on void eps_ri(string scalar varlist, string scalar reg, string scalar touse, string scalar regopts, string scalar weight) { /*object declarations*/ real scalar rc real matrix X, L, R, Lm, L2, R2, orth real rowvector V, Bt, V2, glmwgts, varloc string rowvector orthnames real scalar sd_yhat, cor_yhat string scalar predmu, wgt_stmnt transmorphic view, wgt_view, mu_view /*begin processing*/ st_view(view, ., tokens(varlist), st_varindex(touse)) if ( strlen(weight) ) st_view(wgt_view, ., weight, st_varindex(touse)) else wgt_view = 1 X = correlation(view, wgt_view) //obtain correlations L = R = X[2..rows(X), 2..cols(X)] //set-up for svd() V = J(1, cols(X)-1, .) //placeholder for eigenvalues svd(X[2..rows(X), 2..cols(X)], L, V, R) //conduct singular value decomposition Lm = (L*diag( sqrt(V) )*R) //process orthogonalized predictors if (reg == "regress") Bt = invsym(Lm)*X[2..rows(X), 1] //obtain adjusted regression weights else if (reg == "glm") { //if glm-based... svd( ( view[., 2..cols(view)]:-mean( view[., 2..cols(view)] ) ):/( sqrt( diagonal( variance( view[., 2..cols(view)] ) ) )' ), L2, V2, R2) orth = L2*R2 //produce the re-constructed orthogonal predictors for use in regression orth = (orth:-mean(orth)):/sqrt( diagonal( variance(orth) ) )' //standardize the orthogonal predictors orthnames = st_tempname( cols(orth) ) varloc = st_addvar("double", orthnames) //generate some tempvars for Stata st_store(., orthnames, st_varindex(touse), orth) //put the orthogonalized variables in Stata if ( strlen(weight) ) wgt_stmnt = " [iweight=" else wgt_stmnt = " [" rc = _stata("glm " + tokens(varlist)[1] + " " + invtokens(orthnames) + wgt_stmnt + weight + "] if " + touse + ", " + regopts, 1) //conduct the analysis if ( rc ) { display("{err}{cmd:" + reg + "} failed when executing {cmd:epsilon}.") exit( rc ) } glmwgts = st_matrix("e(b)") //record the regression weights to standardize predmu = st_tempname() //generate some more tempvars for Stata sd_yhat = sqrt( variance(orth*glmwgts[1, 1..cols(glmwgts)-1]') ) //SD of linear predictor stata("predict double " + predmu + " if " + touse + ", mu", 1) //translated with link function st_view(mu_view, ., st_varindex(predmu), st_varindex(touse)) cor_yhat = correlation((view[., 1], mu_view), wgt_view) Bt = (glmwgts[1, 1..cols(glmwgts)-1]:*((cor_yhat[2, 1])/(sd_yhat)))' } else { //asked for invalid reg display("{err}{opt reg(" + reg + ")} invalid with {opt epsilon}.") exit(198) } Bt = Bt:^2 //square values of regression weights Lm = Lm:^2 //square values of orthogonalized predictors st_matrix("r(domwgts)", (Lm*Bt)') //produce proportion of variance explained and put into Stata } end /* programming notes and history - domin_se version 0.0.0 - date - mth day, year /* programming notes and history - domin version 1.0 - date - April 4, 2013 ----- - domin version 1.1 - date - April 13, 2013 -fixed incorrect e(cmd) and e(cmdline) entries -fixed markout variables for sets greater than 1 ----- - domin version 1.2 - date - April 16, 2013 -version 12.1 declared to ensure compatability with factor variables and other advertised features (thanks to Nick Cox for advice on this issue) -fixed markout problem that kept unwanted characters in markout statement (thanks to Ariel Linden for pointing this out) -analytic weights disallowed; importance weights allowed in dominance analysis consistent with underlying linear and logit-based regressions ----- - domin version 2.0 - date - Aug 25, 2013 -tuples, all subset regression, and dominance computations migrated to Mata (thanks to all individuals who pointed out the errors tuples caused when interfacing with domin) -incorporates complete and conditional dominance criteria -ranking of predictors returned as a matrix, e(ranking) -bug related to if and in qualifiers resolved -dots representing each regression replaced with a progress bar for predictors/sets >6 or >4 (for logits) -piechart dropped as option -altered adjusted domin weight computation to result in decomposition of adjusted r2's from full regression -incorporates "epsilon" or relative weights approach to general dominance (for regress only) -McFadden's pseudo-R2 used for logit-based models (for consistency with Azen & Traxel, 2009) ----- - domin version 3.0 - date - Jan 15, 2014 -R2-type metrics no longer default. Any valid model fit metric can be used. Consequently, adj R2 was also removed. -increased flexibility of estimation commands to be used by domin. Any command that follows standard syntax could potentially be used. -wrapper program mvdom and mixdom incorporated into domin package to demonstrate command's flexibility. -due to flexibility in fitstat, constant-only model adjustment incorporated (similar to Stas Kolenikov's -shapley- on SSC) -error related to reported number of observations fixed when strongly collinear variables dropped. -added multiple imputation support -greatly expanded, clarified, and updated the help file ----- - domin version 3.1 - date - Apr 14, 2015 -updated epsilon - works with glm, mvdom, and regress; also migrated to Mata (though not recommended approach - weights nixed for epslilon) -reverse option to reverse "coding" of fitstat in ranks, standardized metric and complete dominance -fixed tied ranks (used to randomly assign, - now share highest number) -added "best dominance" - Com, Cond, Gen - in display (works with "reverse") -removed unnecessary mata clear in dominance() -time series operators allowed (for commands that allow them) -tempfile error fixed for mi -tempnames used for matrices -fixed object declarations in dominance() function -returns unabbreviated each variable in set and all sets lists -added more ereturned information -fixed error where all subsets fitstat was not adjusted for the constant-only fitstat ----- - domin version 3.2 - date - Apr 8, 2016 -fixed use of total with mi to obtain N, doesn't work with tsvars and fvars, changed to regress -update predictor combination computation - use tuples' approach // 3.2.1 - Feb 15, 2021 (initiating new versioning: #major.#minor.#patch) -update to documentation for SJ article -fixed bug in -if- statements with -mi- (thanks to Annesa Flentje) --- domin version 3.3.0 - October 17, 2021 -bug fix and update to complete dominance computations - inconsistent computation of complete dominance designation -update to documentation/helpfile -clean up internal functions (naming and redundancy) -update to terminology in documentation and reporting --- domin version 3.4.0 - November 3, 2022 - reorganization of Mata code; function to function passing; Mata struct to handle input specs - lb_dominance.mlib now contains all complied Mata code for -domin- generalized for accommodating -domme- and future commands - increased precision of passed 'all' and 'constant' fit stats // 3.4.1 - January 27, 2023 - fixed Mata code (dominance.mata 0.0.1) - increase flexibility to work with -domme-; 3.4.0 makes assumptions -domme- cannot meet - does not force arguments but accepts single objects for command-specific needs - records domin options directly (doesn't call them as local macros) // 3.4.2 - March 7, 2023 - call 'dominance.mata' as opposed to using 'lb_dominance.mlib' to allow backward compatability --- domin version 3.5.0 - August 14, 2023 - most of command migrated to Mata - minimum supported version is 15 - argument check changes - new requirement that underlying command has e(sample) - remove support for -mi- make it a wrapper like fitdom() - remove dependency on -moremata- - built-in linear regression/-regress- function for speedier DA - updates to dominance.mata (to 0.1.0) - updated 'epsilon' method to use st_view() instead of st_data() - 'epsilon' method accepts weights // 3.5.1 - January 12, 2024 - fixed references to 'all subsets' - should be 'all sub-models' - fixed error in docuementation of -rforest- Example 11; needed -noesampleok- - removed redundant e(N) setting - fixed N computation with -fweight- and -iweight- --- future domin ** planned ** - depreciated by -domin2-'s bmaregress interface; eventually will be defunct and enveloped by its interface */