capture program drop rescale
program define rescale, rclass
*!	Program -rescale- by Mead Over  Version 3.4     5Dec2022
*!	syntax varlist [if] [, None Hundreds Thousands Millions Billions Trillions Format(string)
*!		AUTOmatic KEEPlabel ALready(real) truncmax(tmaxlist) truncmin(tminlist) positive MAXvalue(real 10e14) MINvalue(real 0)]
*!	   ---  or  ---
*!	syntax varlist , undo

*	This program creates temporary versions of variables to be used in a -graph-
*	command, which are rescaled and/or truncated in order to fit on a common axis.
*	Use a single -rescale- command for all the variables on a single axis.

*	Existing variables are retained in the data by renaming them with
*	the prefix "___" added to the front of each of their variable names.

*	When the rescaled/truncated variables are no longer useful, the operation should be
*	reversed by repeating the command with the same variable list and the
*	option -undo-.

*	If the -format- option is selected, the specified format is applied to
*	all rescaled variables.  Otherwise, the format is assigned based on the
*	magnitude of the largest absolute value of any of the rescaled variables, -maxresc-.
*	maxresc < .1  ->  ndec = 3
*	maxresc <  2  ->  ndec = 2
*	maxresc <  4  ->  ndec = 1
*	maxresc >= 4  ->  ndec = 0

*	The -truncmax- and -truncmin- option allow automatic truncation of the rescaled versions
*	of the varialbes in -varlist- so that observations which exceed the maximum maximorum
*	of the variables in -tmaxlist- or are less than the minimimum minimorum of the variables
*	in -tminlist- are set to missing.  This facilitates fitting graphs of several variables
*	on the same graph axis.

*	The -positive- option is only relevant if truncmin is specified.  It forces the
*	option -truncmin- to ignore non-positive values of the variables in -tminlist-
*	when computing the minimum.  The effect is to truncate at the smallest value
*	which is greater than zero.

*	The -if- modifier allows the rescaled variables to apply to a subset of observations.
*	The rescaled version of the variables are set to missing for other observations.
*	This option facilitates exclusion of observations outside the boundaries of a graph.

	version 9.2

	syntax varlist [if] [, None Hundreds THousands Millions Billions TRillions Undo Format(passthru) /*
		*/ AUTOmatic KEEPlabel ALready(real 1) truncmax(varlist) truncmin(varlist) POSitive MAXvalue(real 10e14) MINvalue(real -10e14)]

	if "`undo'"=="" {   /* Select new unit to which variables are to be rescaled */

		preserve

		if `already' ~= 1 {
			if `already' < 0 | `already' > 1000000000  | mod(`already',10^round(log10(`already'),1)) ~= 0 {
				di _n in re "Option Already( " in ye `already' in re " ) specified incorrectly."
				di    in re "Specified constant must be a positive power of 10 and less than 10^9"
				exit 198
			}
		}

		if "`truncmax'" ~= "" {
			local tmax = -10^12
			foreach X in `truncmax' {
				sum `X' `if', meanonly
				if `r(N)'==0 {
					di in re "Variable " in ye "`X' `if'" in re " has no observations."
					exit 198
				}
				local tmax = max(abs(`r(max)'), `tmax')
			}
		}

		if "`truncmin'" ~= "" {
			local tmin = .
			foreach X in `truncmin' {
				tempvar XX
				qui gen float `XX' = `X'
				if"`positive'"~="" {
					qui replace `XX' = . if `X'<=0
				}
				sum `XX' `if', meanonly
				local tmin = min(    `r(min)' , `tmin')  // Version 2.4  12/2/11
				drop `XX'
			}
		}
		else {
			if "`positive'"~="" {
				di in re "Option -positive- can only be specified with option -truncmin(tminlist)-"
				exit 198
			}
		}

		local nsetmiss = 0
		foreach X in `varlist' {  /* First generate, label & format temporary variables */
			capture confirm numeric variable `X'
			if !_rc~=1 {
				di _n in re "Only numeric variables can be rescaled."
				di    in re "The variable " in ye "`X'" in re " is not numeric."
				error 198
			}
			local vlngth = length("`X'")
			if `vlngth' > 32 - 3 {
				di _n in re "Variable names to be rescaled must be no more than 29 characters long"
				di    in re "The variable " in ye "`X'" in re " is " in ye `vlngth' in re " characters long"
				error 198
			}
			local vallbl : value label `X'
			local varlbl : variable label `X'

			rename `X' ___`X'

			qui gen double `X' = ___`X' * `already'
			if "`truncmax'" ~= "" {
				qui count if ___`X' > `tmax' & ___`X'~=.
					local nsetmiss = `nsetmiss' + r(N)
				qui replace `X' = . if ___`X' > `tmax' & ___`X'~=.
			}
			if `maxvalue' < 10e14  {
				qui count if ___`X' > `maxvalue' & ___`X'~=.
					local nsetmiss = `nsetmiss' + r(N)
				qui replace `X' = . if ___`X' > `maxvalue' & ___`X'~=.
			}
			if "`truncmin'" ~= "" {
				qui count if ___`X' < `tmin' & ___`X'~=.
					local nsetmiss = `nsetmiss' + r(N)
				qui replace `X' = . if ___`X' < `tmin' & ___`X'~=.
			}
			if `minvalue' > -10e14 {
				qui count if ___`X' < `minvalue' & ___`X'~=.
					local nsetmiss = `nsetmiss' + r(N)
				qui replace `X' = . if ___`X' < `minvalue' & ___`X'~=.
			}
			label variable `X' "`varlbl'"
			if "`vallbl'"~="" {
				di _n in re "Although the original variable, " in ye "`X'" in re ", has the value label named: " in ye "`vallbl'"
				di    in re "no value label is assigned to its rescaled version."
			}
			local oldvars `oldvars' ___`X'
		}

		if `"`if'"'~= "" {
			fixif `varlist', iftxt(`"`if'"')
				local if `"`r(iftxt)'"'
		}

        tempname unit
		local set = 0
		if "`none'"~="" {
			scalar `unit' = 1
			local unitlbl = "No rescaling"
			local set = 1
		}
		if "`hundreds'"~="" {
			scalar `unit' = 100
			local unitlbl = "Hundreds"
			local set = 1
		}
		if "`thousands'"~="" {
			scalar `unit' = 1000
			local unitlbl = "Thousands"
			local set = 1 + `set'
		}
		if "`millions'"~="" {
			scalar `unit' = 1000000
			local unitlbl = "Millions"
			local set = 1 + `set'
		}
		if "`billions'"~="" {
			scalar `unit' = 1000000000
			local unitlbl = "Billions"
			local set = 1 + `set'
		}
		if "`trillions'"~="" {
			scalar `unit' = 1000000000000
			local unitlbl = "Trillions"
			local set = 1 + `set'
		}
		if "`automatic'"~="" {
			local max = -999999999999
			local min =  999999999999
			foreach X in `varlist' {
				sum `X' `if' , meanonly
				local max = max(`r(max)', `max')  /* In Stata, max(.,5) is 5 */
				local min = min(`r(min)', `min')  /* In Stata, min(.,-3) is -3 */
			}
			local tenpwr = max(int(log10(abs(`max'))),int(log10(abs(`min'))))
			if `tenpwr' > 11 {
				scalar `unit' = 1000000000000
				local unitlbl = "Trillions"
			}
			if `tenpwr' > 8 & `tenpwr' <= 11 {
				scalar `unit' = 1000000000
				local unitlbl = "Billions"
			}
			if `tenpwr' > 5 & `tenpwr' <= 8 {
				scalar `unit' = 1000000
				local unitlbl = "Millions"
			}
			if `tenpwr' > 2 & `tenpwr' <= 5 {
				scalar `unit' = 1000
				local unitlbl = "Thousands"
			}
			if `tenpwr' == 2 {
				scalar `unit' = 100
				local unitlbl = "Hundreds"
			}
			if `tenpwr' < 2  {
				di _n as txt "Executing program -rescale- on the variables:" ///
					_n as res "   `varlist'"
				di    as txt "Automatic option invoked.  Since the maximum absolute value"  ///
					_n as txt "of any of the variables in the -varlist- is: " as res `max' as txt ","  ///
					_n as txt "the variables are not rescaled."
				return local nochanges nochanges
				exit  /* Exit without doing anything */
			}
			local set = 1 + `set'
		}
		if `set'~=1 {
			di _n in re "Specify one and only one of the following options:"
			di    in re "   none, hundreds, thousands, millions, billions, trillions, automatic"
			di _n
			error 198
			exit
		}

		if "`none'" == "" {
			foreach X in `varlist' {  /* Now perform rescaling */
				local varlbl : variable label `X'

				qui replace `X' = `X'/`unit'

				if "`if'"~="" {
					tempvar not_if
					qui gen byte `not_if' = 0 `if'
						qui replace `not_if' = 1 if `not_if'==.
					qui replace `X' = . if `not_if'
				}

				if "`keeplabel'"=="" {
					label variable `X' "`varlbl' (`unitlbl')"
				}
				else {
					label variable `X' "`varlbl'"
				}
			}
		}
		reformat `varlist' , `format'  //  Give all variables in -varlist- the same format
		return local maxabs `r(maxabs)'  // Maximimum of the absolute values of -varlist-
		return local format `r(format)'
		return local unitlbl `unitlbl'
		return scalar unit = `unit'
		return local  oldvars = "`oldvars'"
		return local  varlist = "`varlist'"
		return local nsetmiss = `nsetmiss'

		restore, not

	}
	else {
		if "`none'"~="" | "`hundreds'"~="" | "`thousands'"~="" | "`millions'"~="" | "`billions'"~="" | "`trillions'"~="" | "`auto'"~="" | "`truncmax'"~="" | "`truncmin'"~="" {
			di _n in re "Option -undo- is intended to reverse the effects of a previous -rescale- command."
			di _n in re "Therefore, the option -undo- cannot be specified with any of the following options:"
			di    in re "   none, hundreds, thousands, millions, billions, trillions, auto, already"
			di _n
			error 198
			exit
		}
		foreach X in `varlist' {
			confirm variable `X'
			capture confirm variable ___`X'
			if _rc~=0 {
				di _n in re "Rescale of the variable " in ye "`X'" in re " cannot be undone, because "
				di    in re "the original version of the variable, which was renamed " in ye "___`X',"
				di    in re "is not in the data."
				di _n
				confirm variable ___`X'
				exit 198
			}
			drop `X'
			rename ___`X' `X'
			local oldvars `oldvars' ___`X'
		}
		return local  oldvars = "`oldvars'"
		return local  varlist = "`varlist'"
		capture confirm variable ___
		if !_rc==1 {
			di _n in re "Warning! Rescaling of the following variables is not undone."
			des ___
		}
	}
end  /* End of program rescale */

program define reformat, rclass
*	Revised for version 3.1
	syntax varlist [, format(string) ]
	if "`format'"=="" {
		local maxabs = 0
		tempvar absX
		qui gen `absX' = .
		foreach X in `varlist' {
			qui replace `absX' = abs(`X')
			sum `absX', meanonly
				if "`r(max)'"=="" {
					di as err "The variable -" as res "`X'"   ///
						as err "- has no observations or maximum value and thus cannot be rescaled."
					exit 198
				}
				local maxabs = max(`r(max)', `maxabs')  /* In Stata, max(.,5) is 5 */
		}

		local ndec = cond(`maxabs'<.1,3,cond(`maxabs'<2,2,cond(`maxabs'<4,1,0)))
		foreach X in `varlist' {
			format `X' %9.`ndec'fc
			return local format %9.`ndec'fc
			return local maxabs `maxabs'
		}
	}
	else {
		foreach X in `varlist' {
			format `X' `format'
			return local format `format'
		}
	}
end  /* End of program reformat */

program define fixif, rclass
	syntax namelist, iftxt(string)
	foreach X in `namelist' {
		local iftxt = subinstr(`"`iftxt'"'," `X' "," ___`X' ",.)
	}
	return local iftxt = `"`iftxt'"'
end

*	Version 1.1 adds the -automatic- and -keeplabel- options
*	Version 1.2 adds trillions and fixes bug in the automatic option
*	Version 1.3 improves default formatting using the -reformat- subroutine
*	Version 1.4 provides option -already- for use when the variables are already rescaled
*	Version 1.5 fixes bugs in KEEPlabel and ALready
*	Version 1.6 adds the truncmax and truncmin options and the [if] modifier
*	Version 1.7 adds the -positive- option to modify the behavior of -truncmin-
*		and the `touse' variable to assure that all variables are missing if any is.
*	Version 1.8 Removes the `touse' variable as imposing casewise deletion
*	Version 1.9 Assures fixif is only called if [if] is specified
*	Version 2.0 Changes prefix temporarily assigned to rescaled variables from __ to ___
*	Version 2.1 Enables -auto- option to rescale non=positive variables (line 173)
*	Version 2.3 Defines -unit- as a scalar rather than as a local macro
*	Version 2.4 Allows -truncmin- without the -positive option, to truncate at a negative number
*	Version 2.5 Adds the -none- option so that -rescale- can be used to truncate data without
*		actually rescaling it.  Also adds the -maxvalue- and the -minvalue- options.
*	Version 2.6 Increase default value of the new option -maxvalue- to 10e14
*	Version 2.7 Fix bug in format option and -if- execution
*	Version 2.8 21Feb2014: Add -preserve- and -restore, not- commands to assure that a syntax error leaves data unchanged
*	Version 2.9 maxvaue and minvalue options set the given values to missing rather than to the max and minvalues.
*	Version 3.0 Add an error trap in line 298 for attempts to rescale a variable with no observations.
*	Version 3.1 Expand format definition to additional decimals.
*	Version 3.2 Add returned macro -nochange- when no changes are made.
*	Version 3.3 Add the -variable list- to the -no changes- warning message.