program define cf2, rclass
*!	-cf2.ado-          Version 2.5                      by Mead Over 8Aug2023
*	Extended version of STATA's -cf- command to compare variables between -master- and -using- data
	version 13

	syntax [varlist] using/ , Sortvars(varlist) [Verbose all MAXRows(real 20) listpct(int 0) Continue]

	if strpos("`using'",".dta")>0 {
		local usingdta `using'
	}
	else {
		local usingdta `using'.dta
	}
	confirm file "`usingdta'"

	cap isid `sortvars'
		if _rc > 0 {
			di as err "The variable(s) specified by the option -sortvars(`sortvars')-" ///
				_n "do not uniquely identify the observations in the -master- data."  ///
				_n "See help for {stata help isid:isid}."
			exit 459
		}
	cap isid `sortvars' using "`using'"
		if _rc > 0 {
			di as err "The variable(s) specified by the option -sortvars(`sortvars')-"  ///
				_n "do not uniquely identify the observations in the -using- data."  ///
				_n "See help for {stata help isid:isid}."
			exit 459
		}

	if `listpct' < 0 | `listpct' > 100  {
		di as err "The option -listpct()-, if specified, must be an integer between 0 and 100"
		exit 198
	}

	return clear
	preserve
	//  Since Stata reserves the word "using", substitute the word "subaltern"
	tempfile master subaltern mrgid

	if "`varlist'"=="" {
		local varlist _all
	}

	local nsvars : word count `sortvars'
		local firstsvar : word 1 of `sortvars'
		local scndsvar  : word 2 of `sortvars'

	sort `sortvars'
		cap drop _merge
		cap drop _mtchobs
		qui save `master'

	qui use "`using'", clear
		cap drop _merge
		cap drop _mtchobs
		sort `sortvars'
		qui save `subaltern'

	qui use `master', clear

	cap merge 1:1 `sortvars' using `subaltern', sorted gen(_mtchobs)
	if _rc > 0 {
		di as err "Initial merge fails with return code " _rc " .  This error should not occur."
		cap noi merge 1:1 `sortvars' using `subaltern', sorted gen(_mtchobs)
		exit 198
	}

		lab var _mtchobs "Obs matching between -master- & -using-"
		label define _mtchobs 1 "MasterOnly" 2 "UsingOnly" 3 "Both"
		label value _mtchobs _mtchobs

	tab _mtchobs, mi

	qui keep `sortvars' `varlist' _mtchobs
		order `sortvars' `varlist' _mtchobs
		sort `sortvars'
		local varswosort : list varlist - sortvars

		qui count if _mtchobs==3
			local N_both = r(N)
			local N_both : di %20.0gc `N_both'
			local N_both = trim("`N_both'")
		qui drop if _mtchobs==3
		label data "Saved sort, compare and _merge variables for unmatched observations"
	qui save `mrgid'

	//  Describe and optionally list observations that appear only in the -master- data
	qui count if _mtchobs==1
		local N_master_only = r(N)
	if `N_master_only'>0 & `N_master_only'<. {
		local N_master_only : di %20.0gc `N_master_only'
		local N_master_only = trim("`N_master_only'")
		if "`verbose'" ~="" {
			di as txt _n "Descriptive statistics on the "  ///
				as res "`N_master_only'" as txt " observation(s) only in the -master- data"
			sum `sortvars' `varswosort' if _mtchobs==1

			cap tab `firstsvar' `scndsvar' if _mtchobs==1, mi
			if `nsvars'>1 & _rc==0 & r(r) < r(N) & r(r) < `maxrows' {
				di _n as txt "Observations only in the -master- data file, tabulated by the first two sort variables"
				tab `firstsvar' `scndsvar' if _mtchobs==1, mi
			}
		}

		if `listpct' > 0 {
			tempvar mrandom
			qui gen `mrandom' = uniform() if _mtchobs==1
			qui count if `mrandom' <= `listpct'/100
			if r(N) > 0 {
				di _n as txt "Listing of `listpct'% of the observations which appear only in the -master- data"
				list `sortvars' `varswosort' if _mtchobs==1 & `mrandom' <= `listpct'/100
			}
		}
	}

	//  Describe and optionally list observations that appear only in the -using- data
	qui count if _mtchobs==2
		local N_using_only = r(N)
	if `N_using_only'>0 & `N_using_only'<. {
		local N_using_only : di %20.0gc `N_using_only'
		local N_using_only = trim("`N_using_only'")
		if "`verbose'" ~="" {
			di as txt _n "Descriptive statistics on the "  ///
				as res "`N_using_only'" as txt " observation(s) only in the -using- data"
			sum `sortvars' `varswosort' if _mtchobs==2

			cap tab `firstsvar' `scndsvar' if _mtchobs==2, mi
			if `nsvars'>1 & _rc==0 & r(r) < r(N) & r(r) < `maxrows' {
				di _n as txt "Observations only in the -using- data file, tabulated by the first two sort variables"
				tab `firstsvar' `scndsvar' if _mtchobs==2, mi
			}
		}

		if `listpct' > 0 {
			tempvar urandom
			qui gen `urandom' = uniform() if _mtchobs==2
			qui count if `urandom' <= `listpct'/100
			if r(N) > 0 {
				di _n as txt "Listing of `listpct'% of the observations which appear only in the -using- data"
				list `sortvars' `varswosort' if _mtchobs==2 & `urandom' <= `listpct'/100
			}
		}
	}

	//  Purge the -master- data of unmatched observations
	//  (Actual master data is safe behind the -preserve- command)
	qui use `master', clear
	qui merge 1:1 `sortvars' using `subaltern', sorted keep(match) keepusing(`sortvars')
		qui drop _merge
		sort `sortvars'
	qui save `master', replace

	//  Purge the -using- data of unmatched observations
	qui use `subaltern', clear
	qui merge 1:1 `sortvars' using `master', sorted keep(match) keepusing(`sortvars')
		qui drop _merge
	sort `sortvars'
	qui save `subaltern', replace

	qui use `master', clear

	di _n in gr "Executing STATA's " in ye "-cf-" ///
		in gr " command on only the " ///
		in ye "`N_both'" in gr " observations in both files:"

	if "`continue'"~="" {
		local continue "capture noisily "
	}

	di as inp `"->  `continue'cf `varlist' using "`using'", `verbose' `all'"' _n
	`continue'cf `varlist' using `"`subaltern'"', `verbose' `all'

	return scalar N_both = real("`N_both'")
	return scalar N_using_only = real("`N_using_only'")
	return scalar N_master_only = real("`N_master_only'")
	return scalar N_mismatchs = real(r(Nsum))
	return local  Nsum `r(Nsum)'

	if `listpct'~=0 {
		return scalar listpct = `listpct'
	}

end
*	1.0 06April2009                     by Mead Over
*	1.1 5Dec2018  Move to version 13, use new -merge- syntax
*		Add -tab- of first two `sortvars' not merging.
*		Silence the second, uninformmative -merge-
*	2.0 6Feb2021
*		Use -merge-'s option -keep(match)- to fix bug
*		 which failed to detect variables absent from -using- file.
*		Add the -listpct- option to suppress excessive listing
*		Use -isid- to trap the error when -sortvars()- are insufficient
*		Only tabulate the values of the sort variables
*		when the -tab- command does not fail
*		 and when there are at least two sort variables
*		 and when the number of rows r(r) is less than r(N)
*		 and the number of rows is less than -maxrows- defaulting to 20.
*		Display the -summarize- results for all Master-only and
*		 Using-only non-matching observations.
*		Add the -continue- option
*	2.1 8Feb2021 Accommodate files that already have the _merge variable
*	2.2 15Feb2021 Fix bug in line 122 display command
*	2.3 20May2021 Trap nonexistent file specified by -using- option
*	2.4 25May2021 Convert from -using- to -using/-
*	2.5 8Aug2023 Add -.dta- extension to filename if it is absent

*  Possible enhancements:
*	1.  The -listpct- option produces unreadable output on a large file even when the percentage is set to 1.
*		How about adding an option -listvars- which could limit the variables listed by -listpct-.
*	2.	When the variable names differ only by a letter or two, -cf2-,
*		like -cf- says the variable in the master file is not found in the using file.
*		Would be helpful if variables which differ by only a few characters
*		could still be compared.  Perhaps invoke -compuse-???