*! difwithpar.ado            v 1.4  	Sept 26, 2012

/*

syntax:
difwithpar varlist, ID(str) RUnname(str) ABility(str) GRoups(str)
           [MULtinomial(str)  NUL(integer 1)  NUPValue(real 0.05)
           UBeta(integer 1) UBCH(real 0.10) UL(integer 0) ULPValue(real 0.05)
           minsize(integer 20) CLeanup(integer 0)
           SCale(str) CYcles(str) NQpt(str)]

	Output:	DfP_runname.log
			code_runname.psl
			data_runname.txt
			itemdata.dta
			vars_runname.txt
			DIFDetect.dta

*/

*********************************************************************************
* difwithpar, by Paul Crane, MD MPH, Laura Gibbons, PhD, Lance Jolly, MS, and
* Gerald van Belle, PhD.
*
* Copyright 2005-2006, University of Washington.
* Written by Laura Gibbons, PhD, and Lance Jolly, MS,
* under the direction of Paul Crane, MD MPH, and Gerald van Belle, PhD.
* The time of Drs. Gibbons and Crane was supported by NIH grant AG
* K08 22232, “Improving cognitive tests with modern psychometrics.”  Dr. Gibbons
* was also supported by  NIH grant 5 P50 AG05136-17.
*
* We appreciate the help of May M. Boggess, Tom Koepsell, and Rich Jones
* with sections of this program.
*
*   difwithpar Software License
*
*
* The University of Washington (UW) gives permission for you to use
* the difwithpar software package developed at UW, on the following
* conditions:
*
*     difwithpar is not published, distributed, or otherwise transferred or
*     made available except through the UW DIFdetect web site.
*
*     You agree to make improvements to difwithpar available to the UW
*     difwithpar team for consideration and deployment in future releases
*     of difwithpar. In this way, future versions of difwithpar will be
*     tested, standardized and improved through one central academic
*     site.  All improvements must come with a statement and warranty
*	  that the work is original and that the person offering the
*	  improvement has the right to grant permission to use the
*	  improvement.
*
*     You retain in difwithpar and any modifications to difwithpar the
*     copyright, trademark, or other notices pertaining to difwithpar as
*     provided by UW.
*
*     You provide the difwithpar team with feedback on the use of difwithpar
*     software in your research, and that the difwithpar team and UW are
*     permitted to use any information you provide in making changes to
*     difwithpar software.  All bug reports and technical questions shall
*     be sent to: gibbonsl@u.washington.edu
*
*     You acknowledge that UW and its licensees may develop
*     modifications to difwithpar that may be substantially similar to your
*     modifications of difwithpar, and that UW and its licensees shall not
*     be constrained in any way by you in UW's or its licensees' use or
*     management of such modifications. You acknowledge the right of
*     the UW to prepare and publish modifications to difwithpar that may
*     be substantially similar or functionally equivalent to your
*     modifications and improvements, and if you obtain patent
*     protection for any modification or improvement to difwithpar you
*     agree not to allege or enjoin infringement of your patent
*     by UW or by any of UW's licensees obtaining modifications or
*     improvements to difwithpar from UW.
*
*     Please send bibliographic citations regarding the use of difwithpar
*     to Dr. Paul Crane at pcrane@u.washington.edu so that the
*     difwithpar team can keep an up-to-date list of projects published
*     using the program.
*
*     Any risk associated with using the difwithpar software at your
*     institution is with you and your Institution/Company.
*     difwithpar is experimental in nature and is made available as a
*     research courtesy "AS IS," without obligation by UW to provide
*     accompanying services or support.  UW AND THE AUTHORS EXPRESSLY
*     DISCLAIM ANY AND ALL WARRANTIES REGARDING THE SOFTWARE, WHETHER
*     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES
*     PERTAINING TO MERCHANTABILITY, NON_INFRINGEMENT, OR FITNESS FOR
* 	  A PARTICULAR PURPOSE.
*
************************************************************************

/*--------------------------------------------------------------------------
* SUBROUTINE STRUCTURE:

	difwithpar
		difdetect03
			q_ologit &/or q_logit
			processitems
				If dif:
					makevirtualitems
						collapsecat
						writecode
				If no dif:
					collapsecat

					writecode
			dumpcode
			makevarlist
			writepardata

*------------------------------------------------------------------------*/


*----------------------------------------------------------
* START COLLAPSECAT PROGRAM
* Routine to collapse categories on a scale var to assure that
* all categories have at least a certain minimum frequency
* and that there are no more than 15 categories

capture prog drop collapsecat
program define collapsecat

	args var
	version 7.0
	tempvar count

	preserve
	sort `var'

	quietly by `var': gen long `count' = _N
	quietly by `var': keep if _n == 1

	global norigcat = _N
	local endloop = $norigcat
	forvalues i = 1/`endloop' {
		if `var'[`i'] >= . {
			global norigcat = $norigcat - 1
			}
		}
	local nnewcat = $norigcat

	forvalues i = 1/$norigcat {
		local ocode`i' = `var'[`i']		/* Original codes */
		if `ocode`i''==10 {			/* deal with values > 9 */
			local nocode`i' "A"
			}
		else if `ocode`i''==11 {
			local nocode`i' "B"
			}
		else if `ocode`i''==12 {
			local nocode`i' "C"
			}
		else if `ocode`i''==13 {
			local nocode`i' "D"
			}
		else if `ocode`i''==14 {
			local nocode`i' "E"
			}
		else if `ocode`i''==15 {
			local nocode`i' "F"
			}
		else local nocode`i'= `ocode`i''
		local mapping`i' = `i'	/* Code to which each will be mapped */
		local newcat`i' = `count'[`i']	/* Vector of frequencies */
		}
	restore


	while (1) {			/* Keep looping till no more collapsing needed */

				  /* Find current minimum count among new categories */
		local mincount = .
		local whichcat = .
		forvalues i = 1/`nnewcat' {
			if `newcat`i'' < `mincount' {
				local mincount = `newcat`i''
				local whichcat = `i'
				}
			}

		/* If all new categories have at least required minimum, all done*/

		if `mincount' >= $minsize | `nnewcat' ==1 { continue, break }

		/* Find whether new category with current minimum count should be
			combined with the next-lower or the next-higher category */

		if `whichcat' == 1 { local bottomcat = 1 }
		else if `whichcat' == `nnewcat' { local bottomcat = `nnewcat'- 1 }
		else {
			local nextlower = `whichcat' - 1

			local nexthigher = `whichcat' + 1
			if `newcat`nextlower'' < `newcat`nexthigher'' {
				local bottomcat = `nextlower'
				}
			else { local bottomcat = `whichcat' }
			}

		/* Collapse two new categories into one */
		local toplimit = `nnewcat' - 1
		forvalues i = `bottomcat'/`toplimit' {
			local j = `i' + 1
			if `i' == `bottomcat' {
				local newcat`i' = `newcat`i'' + `newcat`j''
				}
			else {local newcat`i' = `newcat`j'' }
			}
		local nnewcat = `nnewcat' - 1

		/* Revise mappings to reflect smaller no. of new categories */
		forvalues i = 1/$norigcat {
			if `mapping`i'' > `bottomcat' {
				local mapping`i' = `mapping`i'' - 1
				}
			}
		}
	if `nnewcat' < 2 {

		gen __ts`var' =1
		noisily di in white as result "Warning:  `var'   has too few subjects"
		}
	if `nnewcat' >= 2 {
		gen __ts`var' =0
		}

	if $norigcat > 15 {
		noisily di in white as result "Warning:  `var'  has > 15 original categories, Parscale will reject."
		}

	/* Create PARSCALE "ORI" and "MOD" specifications */
	global ori ""
	global mod ""

	forvalues i = 1/$norigcat {
		global ori "$ori`nocode`i''"
		global mod "$mod`mapping`i''"
		if `i' < $norigcat {
			global ori "$ori,"
			global mod "$mod,"
			}
		}

	end

* END COLLAPSECAT PROGRAM
*----------------------------------------------------------


*----------------------------------------------------------
* START PROGRAM TO MAKE THE “VIRTUAL” ITEMS
* Routine to create 2+ "virtual" scale items from a single item on
*	which differential item performance has been found

capture program drop makevirtualitems
program define makevirtualitems
	args var group
	version 7.0

	tempvar count
	preserve
	sort `group'
	quietly by `group': gen long `count' = _N
	quietly by `group': keep if _n == 1
	local ngroups = _N
	if `group'[`ngroups'] == . { local ngroups = `ngroups' - 1 }


	forvalues i = 1/`ngroups' { local g`i' = `group'[`i'] }
	restore

	forvalues i = 1/`ngroups' {

		local vvarname "`var'_`group'`g`i''"
		quietly gen `vvarname' = .
		quietly replace `vvarname' = `var' if `group' == `g`i''

		collapsecat `vvarname'

		if __ts`vvarname' ~=1{
			writecode `vvarname'
			}
		}

	end

* END MAKEVIRTUALITEMS PROGRAM
*----------------------------------------------------------


*----------------------------------------------------------
* START PROGRAM TO WRITE PARSCALE CODE
* Routine to write PARSCALE code for one "block", which is
* one scale item variable


capture program drop writecode
program define writecode
	args var
	version 7.0

	global nblocks = $nblocks + 1

	global ncodelines = $ncodelines + 1
	global codeline$ncodelines = ">BLOCK$nblocks BNAme=('`var''), ORI=($ori),"
	global ncodelines = $ncodelines + 1
	global codeline$ncodelines = "  MOD=($mod), NITems=1, NCAT=$norigcat, CADjust=0.0;"
	global ncode2lines = $ncode2lines + 1
	global code2line$ncode2lines =  "`var'"
	global varlist "$varlist`var' "
	end
* END OF WRITECODE PROGRAM
*----------------------------------------------------------


*----------------------------------------------------------
* START PROCESSITEMS CODE
* Routine to go through each
* item and carry out collapsing of categories, creation of virtual items
* and writing of PARSCALE code


capture program drop processitems
program define processitems
	version 7.0
	tempfile postdata


	/* Initializations */

		local ntypes = _N		/* No. of item/group/ability combinations */
		global ncodelines = $nheaderlines
		global ncode2lines = 0
		global nblocks = 0

	/* Main loop through types */

	local itype = 1
	while (`itype' <= `ntypes') {

		local ivar = item[`itype']
		tokenize "$DB_items"
		local var "``ivar''"	/* Set var = item variable name */
		*display "Processing `var'"
		if nonunif[`itype'] == 0 & unif[`itype'] == 0 {
										/* If no DIF */


			quietly save "`postdata'", replace
			use "itemdata", replace
			collapsecat `var'		/* Collapse categories on var */
			if __ts`var' ~=1{
				writecode `var'		/* Write parscale code */
				}
			quietly save "itemdata", replace
			use "`postdata'", replace
			}
		else {			    		 /* If there IS DIF */

			local igroup = group[`itype']
			tokenize "$DB_groups"
			local gp "``igroup''"  /* Set gp = group variable name */
			quietly save "`postdata'", replace
			use "itemdata", replace
		  	qui tab `var'
		  	if r(r) > 1{
				makevirtualitems `var' `gp'
				quietly save "itemdata", replace
				use "`postdata'", replace
				}

			else {
				di in red "`var' dropped, all one level"
				}
			}
		local itype = `itype' + 1		/* Go to next type */
	}

	end

* END PROCESS ITEMS PROGRAM
*----------------------------------------------------------


*----------------------------------------------------------
* START DUMPCODE PROGRAM
* Routine to dump PARSCALE code to a text file *

capture program drop dumpcode
program define dumpcode
	version 7.0

	drop _all
	local x = $ncodelines + $ntrailerlines		/* Allow for trailer */
	quietly set obs `x'
	quietly gen str80 code = ""


	/* Code for header is here */
	local blocks=($ncodelines-$nheaderlines)/2
	local lines=int((`blocks'+9)/80)+1
	if `lines' == 2 {
		local r1=`blocks'- 70
		}
	if `lines' == 3 {
		local r1=`blocks'- 150
		}
	quietly replace code = "This run has `lines' line(s) of data per subject." if _n == 1
	quietly replace code = "" if _n == 2
	quietly replace code = ">COMMENT" if _n == 3
	quietly replace code = ">FILES DFNAME='data_$DB_runname.txt'," if _n == 4
	quietly replace code = "NFNAME='data_$DB_runname.txt', SAVE;" if _n == 5
	quietly replace code = ">SAVE PARM='$DB_runname.par'," if _n == 6
	quietly replace code = "FIT='$DB_runname.fit'," if _n == 7
	quietly replace code = "SCORE='$DB_runname.sco';" if _n == 8
	quietly replace code = ">INPUT NTEST=1, LENGTH=`blocks', NID=10, NTO=`blocks';" if _n == 9
	quietly replace code = "(10A1, `blocks'A1)" if _n == 10
	quietly replace code = ">TEST1 TNAME='$DB_runname', items=(1(1)`blocks'), NBL=`blocks', SLOPE;" if _n == 11

	/* body code */
		local s = $nheaderlines+1
		forvalues i = `s'/$ncodelines {
		local x = "\$codeline`i'"
		quietly replace code = "`x'" if _n == `i'
		}

	/* Code for trailer is here */
	local end = $nheaderlines+2*`blocks'+2
	quietly replace code = ">CALIB GRADED, LOGISTIC, SCALE=1.7, NQPT=$nqpt, CYCLES=$cycles, CRIT=.001;" if _n == `end'-1
	quietly replace code = ">SCORE EAP;" if _n == `end'

	capture outsheet code using "code_$DB_runname.psl", noquote nolabel nonames replace
	end


* END DUMPCODE PROGRAM
*----------------------------------------------------------






*----------------------------------------------------------
* START THETAIN PROGRAM
* merges Parscale theta scores back into the original Stata data set

version 7.0

capture program drop thetain
program define thetain

clear
tempfile temp
infix 2 lines  1: long parscaleid 2-11  2: theta$DB_runname 58-65 setheta$DB_runname 70-78 using "$DB_runname.SCO"

sort parscaleid
capture save "`temp'", replace
clear

use "$origdata"
capture drop _merge
sort parscaleid

merge parscaleid using "`temp'"
assert _merge==3 | _merge==1

save "$origdata", replace
end

* END THETAIN PROGRAM
*----------------------------------------------------------


*----------------------------------------------------------
* START MAKEVARLIST PROGRAM
* Routine to make a text file of the old and new variables *

capture program drop makevarlist
program define makevarlist
	version 7.0

	drop _all
	local x = $ncode2lines
	quietly set obs `x'
	quietly gen str80 code = ""

	forvalues j = 1/$ncode2lines {
		local x = "\$code2line`j'"
		quietly replace code = "`x'" if _n == `j'
		}

	capture outsheet code using "vars_$DB_runname.txt", noquote nolabel nonames replace
	end

* END MAKEVARLIST
*----------------------------------------------------------


*----------------------------------------------------------
* START WRITEPARDATA
* Program to make the .txt dataset for Parscale

capture program drop writepardata
program define writepardata
	version 8.0
***runtogether option only in version 8
***can run in version 7 with noquote instead of runtogether and then edit out the spaces

clear
tempfile temp1


use "itemdata"
*this first step is just to keep from converting and merging huge data sets
keep parscaleid $DB_groups $DB_abilities $varlist
capture save "itemdata", replace

*makes the list all string variables so they'll allow conversion to X and A-G
quietly {
	tostring _all, replace force
	foreach var in    $varlist	 {
	replace `var'="X" if `var'=="."
	replace `var'="X" if `var'==".x"

	replace `var'="A" if `var'=="10"
	replace `var'="B" if `var'=="11"
	replace `var'="C" if `var'=="12"
	replace `var'="D" if `var'=="13"
	replace `var'="E" if `var'=="14"
	replace `var'="F" if `var'=="15"
	}
	}
capture save "`temp1'", replace

clear
tempfile temp2
quietly set obs 1
gen parscaleid="NPKY      "
foreach var in    $varlist	 {


	gen `var'="X"
	}
capture save "`temp2'", replace

append using "`temp1'"
capture save "`temp1'", replace

*outputs the data in a nice, tidy rectangle
capture outfile parscaleid $varlist using "data_$DB_runname.txt", replace runtogether
end

* END WRITEPARDATA
*----------------------------------------------------------

*----------------------------------------------------------
* BEGIN Q_OLOGIT
* specialized ologit module to output the “completely determined”
* error message that would occur if data are too sparse and the models
* were not run quietly.

capture program drop q_ologit
program define q_ologit, rclass
        version 8.2
preserve

	syntax anything [if], *

	ologit `0'
	capture brant
	if _rc==999 {
		local bnp=0
		return scalar bnp=`bnp'
        	}
	else if _rc~=999 {
		local pb = r(p)
		return scalar pb=`pb'
	      }
      local exp=""
	ologit `0'
	local levels=e(k_cat)
      forvalues k=1/`levels'{
      	predict double ppp`k', p outcome(#`k')
      	local exp   "`exp' & (ppp`k'<1e-6 | (1-ppp`k')<1e-6)"
        	}
      local exp : subinstr local exp "&" ""
      count if `exp'
      local cd=r(N)
	return scalar cd=`cd'
end

* END Q_OLOGIT
*----------------------------------------------------------

*----------------------------------------------------------
* BEGIN Q_LOGIT
* specialized logit module to output the “completely determined”
* error message that would occur if data are too sparse and the models
* were not run quietly.


capture program drop q_logit
program define q_logit, rclass
        version 8.2
preserve

	syntax anything [if], *
	logit `0'
	lfit, group(10)
	local phls = 1-chi2(r(df),r(chi2))
	predict double ppp,rules
	count if (ppp<1e-6 | (1-ppp)<1e-6)
      local cd=r(N)
	return scalar cd=`cd'
	return scalar phls=`phls'
end

* END OLOGIT
*----------------------------------------------------------

*----------------------------------------------------------
* BEGIN DIFdetect03

* main program for DIF detection

capture program drop DIFdetect03
program define DIFdetect03
	version 7.0
preserve

global DB_noncat
global DB_non

global ncat : word count $DB_categorical

noisily di in yellow "There are " in white _N in yellow " observations."
if $DB_ival==1{
	noisily di in yellow "The $DB_ival item of interest:  $DB_items."
	}
else {
	noisily di in yellow "The $DB_ival items of interest:  $DB_items."
	}
if $DB_DIFtypesval==1{
	noisily di in yellow "The $DB_DIFtypesval group of interest:  $DB_groups."
	if $ncat==0 {
		noisily di in yellow "The group $DB_groups is either dichotomous or ordinally grouped."
		}
	else {
		noisily di in yellow "The group $DB_categorical is labeled categorical."
		}
	}
else {
	noisily di in yellow "The $DB_DIFtypesval groups of interest:  $DB_groups."
	if $ncat==0 {
		noisily di in yellow "None of the groups is categorical."
		}
	else {
		noisily di in yellow "Of these groups, the following are categorical:  $DB_categorical."
		}
	}
if $DB_abilval ==1 {
	noisily di in yellow "The $DB_abilval ability of interest:  $DB_abilities."
	}
else {
	noisily di in yellow "The $DB_abilval abilities of interest: $DB_abilities."
	}

noisily di ""
noisily di "_______________________________________________________________"
noisily di ""

foreach y of global DB_groups {
        tempname sumcat
        scalar `sumcat'=0
        *makes the suffixes for the categorical comparisons

     	  foreach z of global DB_categorical{
                if "`y'"=="`z'" {
                        tempvar c`z' max2`z'
                        egen `c`z''=group(`y')
                        egen `max2`z''=max(`c`z'')
                        local max`z' `max2`z''
                        scalar `sumcat'=`sumcat'+1
                }
        }

        if `sumcat'==0 {
        global DB_noncat $DB_non
        global DB_non "\$DB_noncat `y'"
        }
}
global DB_cat1

global DB_cat
foreach z of global DB_categorical{
local i=1
while `i'< `max`z''{
local j=`i'+1

        while `j'<=`max`z''{
                capture gen c`z'`i'`j'=1 if `c`z''==`i'
                if _rc==110 {
				noisily di in red "c`z'`i'`j' already exists, consider renaming variable."
                        tempvar c`z'`i'`j'
                        noisily di in red "the contrast variable is now called `c`z'`i'`j''."
                        gen `c`z'`i'`j''=1 if `c`z''==`i'
                        replace `c`z'`i'`j'' =0 if `c`z''==`j'
                        global DB_cat1 $DB_cat
                        global DB_cat "\$DB_cat1 `c`z'`i'`j''"
                        local j=`j'+1
                        continue
                        }
                replace c`z'`i'`j' =0 if `c`z''==`j'
                global DB_cat1 $DB_cat
                global DB_cat "\$DB_cat1 c`z'`i'`j'"
local j=`j'+1
}
local i=`i'+1
}
}
*sets up data;
global DB_groups "$DB_cat $DB_non"
local i = 1
local item = 1
local group =1
local ability =1

tempname out out1 out2 out3



tempfile results

global gpbse ""
global intbse ""
global gplist ""
global intlist ""
global postg ""
global posti ""
global postni ""
global gnm ""

if "$DB_ordinal" ~= "" {
	forvalues i3 = 2/$olevels {
		global gpbse "$gpbse gp`i3' sebgp`i3' "
		global intbse "$intbse bi`i3' sebintx`i3' "
		global gplist "$gplist gp_`i3' "
		global intlist "$intlist intx_`i3'"
		global postg "$postg (_b[gp_`i3']) (_se[gp_`i3']) "
		global posti "$posti (_b[intx_`i3']) (_se[intx_`i3']) "
		global postni "$postni (.) (.) "
		global gnm="gp_2"
		}
	}
else {
        global gpbse " bgp sebgp "
        global intbse " bi sebintx "
        global gplist " gp "
        global intlist " intx "
        global postg " (_b[gp]) (_se[gp]) "
        global posti " (_b[intx]) (_se[intx]) "
        global postni " (.) (.) "
	global gnm="gp"

	}



capture postfile `out' type item group ability ll bab sebab $gpbse $intbse pHL pBrant using "`results'"

capture gen __conv=0
capture gen coll=0


foreach x of global DB_items {
	capture gen resp =`x'
	capture replace resp =`x'
	capture drop levels
	capture qui tab resp
	capture qui gen levels=r(r)


    foreach y of global DB_groups {
		if "$DB_ordinal" ~= "" {
			qui capture tab `y', gen(gp_)
              	*capture replace gp = `y' *ok?
			}
 	    else {
 			capture gen gp = `y'
			capture replace gp = `y'
			**not sure what the above line does
         	}
	    foreach z of global DB_abilities {
      		capture gen ab = `z'
		    capture replace ab = `z'

*interactions;

	if "$DB_ordinal" ~= "" {
		forvalues i2 = 2/$olevels {
       		quietly capture gen intx_`i2'=gp_`i2'*ab
       		quietly capture replace intx_`i2'= gp_`i2'*ab
			}
		}
      else {
		quietly capture gen intx=gp*ab
		quietly capture replace intx=gp*ab
		}
*ologit, with interaction;

	capture gen pBrant=.
	capture gen pHL=.
	capture replace pBrant=.
	capture replace pHL=.

	if levels > 2 {
	    capture quietly q_ologit resp $gplist ab $intlist if ($gnm ~=. & ab ~=.)


		if r(cd)>0 & r(cd) ~= . {
			di in red "Problem with `x' `y' `z', full model "
			di in red r(cd) " observations completely determined (or convergence not achieved)"
			di " "
		      capture replace __conv=0
      		capture replace coll=0
	        	local i = `i'+1
	        	local ability = `ability'+1
			continue
  			}
		if r(bnp)==0 {
			di in red "Brant test not possible for:"
	        	di in red "  `x' `y' `z', interaction model"
			capture replace pBrant=.
			}
		else if r(bnp)~=0 {
			capture replace pBrant=r(pb)
			}
		}

	else if levels == 2 {
		quietly sum resp
		if r(min) ~=0 {
			quietly replace resp=resp-r(min)
			*recoded for logit so 0 is minimum value
			}
	     	capture quietly q_logit resp $gplist ab $intlist if ($gnm~=. & ab ~=.)
		if r(cd)>0 {
			di in red "Problem with `x' `y' `z', interaction model"
			di in red r(cd) " observations completely determined (or convergence not achieved)"
			di " "
		      capture replace __conv=0
      		capture replace coll=0
        		local i = `i'+1
	        	local ability = `ability'+1
			continue
  			}
		capture replace pHL=r(phls)
       }

	if _rc==430 & __conv==0{
      	  noisily di in white "Convergence not achieved with:"
	        noisily di in white "  `x' `y' `z', interaction model"
      	  replace __conv=__conv+1
	        replace coll=0
      	  local i = `i'+1
	        local ability = `ability'+1
      	  continue
        	  }
	if _rc==430 & __conv~=0{
      	  noisily di "  `x' `y' `z', interaction model"
	        local i = `i'+1
	        local ability = `ability'+1
	        continue
	        }
	if _rc==908{
       	noisily di in yellow "Too many categories for `x'."
        	noisily di in yellow "Recoded temporarily into 5 groups by autocode"
        	sort resp
        	replace resp=autocode(resp,5,resp[1],resp[_N])
	      la var resp `x'
      	noisily table resp

	     	capture quietly q_ologit resp $gplist ab $intlist if ($gnm~=. & ab ~=.)
		if r(cd)>0 & r(cd) ~= . {
			di in red "Problem with `x' `y' `z', interaction model "
			di in red r(cd) " observations completely determined (or convergence not achieved)"
			di " "
		      capture replace __conv=0
 	   	  	capture replace coll=0
 	       	local i = `i'+1
 	       	local ability = `ability'+1
			continue
  			}
		if r(bnp)==0 {
			di in red "Brant test not possible for:"
      	  	di in red "  `x' `y' `z', interaction model"
			capture replace pBrant=.
			}
		else if r(bnp)~=0 {
			capture replace pBrant=r(pb)
			}

		if _rc~=0 {
        		noisily di in red "Unaccounted for error listed as " _rc
        		noisily di in red "Error applies to `x', `y', and `z' in the interaction model"
        		continue
		      }
        }
	if _rc~=0{
      	noisily di in red "Unaccounted for error listed as " _rc
        	noisily di in red "Error applies to `x', `y', and `z' in the interaction model"
	      continue
	      }

	if _rc==111 &coll==0{
		noisily di in yellow "Collinearity problems with:"
		noisily di in yellow "  `x', `y', `z', and `y'*`z'"
	     	local i = `i'+1
	     	local ability = `ability'+1
		capture replace __conv=0
 	    	replace coll=coll+1
 	    	continue
 	    	}
	if _rc==111 &coll~=0{
		noisily di in yellow "  `x', `y', `z', and `y'*`z'"
        	local i = `i'+1
        	local ability = `ability'+1
      	capture replace __conv=0
        	continue
        	}
	if _rc~=0 {

      	noisily di in red "Unaccounted for error listed as " _rc
        	noisily di in red "Error applies to `x', `y', and `z' in the interaction model"
	      local i = `i'+1
        	local ability = `ability'+1
        	capture replace __conv=0
        	continue
        	}

*adds a new observation to out;

	post `out' (`i') (`item') (`group') (`ability') (e(ll)) (_b[ab]) (_se[ab]) $postg $posti (pHL) (pBrant)


*no interaction;
	if levels > 2 {
	     	capture quietly q_ologit resp $gplist ab if ($gnm~=. & ab ~=.)

		if r(cd)>0 {
			di in red "Problem with `x' `y' `z', group+ability model "


			di in red r(cd) " observations completely determined (or convergence not achieved)"
			di " "
	      	capture replace __conv=0
      		capture replace coll=0
        		local i = `i'+1
        		local ability = `ability'+1
			continue
  			}
		if r(bnp)==0 {
			di in red "Brant test not possible for:"
        		di in red "  `x' `y' `z', group+ability model"
			capture replace pBrant=.
			}
		else if r(bnp)~=0 {
			capture replace pBrant=r(pb)
			}
		}
	else if levels == 2 {
      	capture quietly q_logit resp $gplist ab if ($gnm~=. & ab ~=.)
		if r(cd)>0 {
			di in red "Problem with `x' `y' `z', group+ability model "
			di in red r(cd) " observations completely determined (or convergence not achieved)"
			di " "
	    		capture replace __conv=0
      		capture replace coll=0
        		local i = `i'+1
        		local ability = `ability'+1
			continue
  			}
		capture replace pHL=r(phls)
       	}
	if _rc==430 & __conv==0{
      	noisily di "Convergence not achieved with:"
        	noisily di "  `x' `y' `z', main effect model"

        	replace __conv=__conv+1
        	local i = `i'+1
        	local ability = `ability'+1
        	continue
        	}
	if _rc==430 & __conv~=0{
      	noisily di "  `x' `y' `z', main effect model"
        	local i = `i'+1
        	local ability = `ability'+1
	      continue
	      }
	if _rc~=0{
	      noisily di in red "Unaccounted for error listed as " _rc
      	noisily di in red "Error applies to `x', `y', and `z' in the main effect model"
        	continue
        	}

	post `out' (`i') (`item') (`group') (`ability') (e(ll)) (_b[ab]) (_se[ab]) $postg $postni (pHL) (pBrant)


*no group;
	if levels > 2 {
	     capture quietly q_ologit resp ab if ($gnm~=. & ab ~=.)

		if r(cd)>0 {
			di in red "Problem with `x' `y' `z', ability only model "
			di in red r(cd) " observations completely determined (or convergence not achieved)"
			di " "
	      	capture replace __conv=0
      		capture replace coll=0
        		local i = `i'+1
        		local ability = `ability'+1
			continue
  			}
		if r(bnp)==0 {
			di in red "Brant test not possible for:"
        		di in red "  `x' `y' `z', ability only model"
			capture replace pBrant=.
			}
		else if r(bnp)~=0 {
			capture replace pBrant=r(pb)
			}
	        }
	else if levels == 2 {
     		capture quietly q_logit resp ab if ($gnm~=. & ab ~=.)


		if r(cd)>0 {
			di in red "Problem with `x' `y' `z', ability only model "
			di in red r(cd) " observations completely determined (or convergence not achieved)"
			di " "
		      capture replace __conv=0
   		   	capture replace coll=0
    		    	local i = `i'+1
     		   	local ability = `ability'+1
			continue
  			}
		capture replace pHL=r(phls)
      	}

	if _rc==430 & __conv==0{
      	noisily di "Convergence not achieved with:"
     		noisily di "`x' `y' `z', ability only model"
    	      replace __conv=__conv+1
        	local i = `i'+1
       	local ability = `ability'+1
        	continue
        	}
	if _rc==430 & __conv~=0{
	      noisily di "`x' `y' `z', ability only model"
      	local i = `i'+1
        	local ability = `ability'+1
        	continue
        	}
	if _rc~=0{
      	noisily di in red "Unaccounted for error listed as " _rc
        	noisily di in red "Error applies to `x', `y', and `z' in the ability only model"
        	continue
        	}

	*adds that observation to data set;

	post `out' (`i') (`item') (`group') (`ability') (e(ll)) (_b[ab]) (_se[ab]) $postni $postni (pHL) (pBrant)

	*on to the next combination;

	local i = `i'+1
	local ability = `ability'+1
		}

	local group = `group'+1
	local ability =1
	}
local item = `item'+1
local group =1
local ability=1
}

postclose `out'

capture save "itemdata", replace

quietly use "`results'", replace

local a=1

foreach x of global DB_items {
	label define itemlbl `a' `x', add
	local a=`a'+1
	}
label values item itemlbl
local a=1

foreach x of global DB_groups {
	label define gplbl `a' `x', add
	local a=`a'+1
	}

label values group gplbl
local a=1
foreach x of global DB_abilities {
	label define ablbl `a' `x', add
	local a=`a'+1
	}

label values ability ablbl

if "$DB_ordinal" == "" {
	sort type bi sebintx
	}
else {
	sort type
	}
*computes output parameters;
quietly {
	by type: gen model=_n
	by type: gen dif=(bab[2]/bab[3]-1)
	by type: gen ll1=ll[1]
	by type: gen ll2=ll[2]
	by type: gen ll3=ll[3]
	if "$DB_ordinal" == "" {
		by type: gen pb3=2*(1-norm(abs(bi[1])/sebintx[1]))
		by type: gen pb2=2*(1-norm(abs(bgp[2])/sebgp[2]))
		by type: gen ldif=chiprob(1,2*(ll1-ll2))
		by type: gen ludif=chiprob(1,2*(ll2-ll3))
		}
	else {
		by type: gen ldif=chiprob(($olevels-1),2*(ll1-ll2))
		by type: gen ludif=chiprob(($olevels-1),2*(ll2-ll3))
		gen pb3=.
		gen pb2=.
		}
	format dif %8.6f
	format ldif %8.6f
	format ludif %8.6f
		format pb3 %8.6f
		format pb2 %8.6f
	if "$DB_ordinal" == "" {
		gen str5 dirU="+" if model==2 & sign(bgp)==1
		replace dirU="-" if model==2 & sign(bgp)==-1
		gen str5 dirNU="Mixed" if model==1 & sign(bgp)==1 & sign(bi)==-1
		replace dirNU="Mixed" if model==1 & sign(bgp)==-1 & sign(bi)==1
		replace dirNU="+" if model==1 & sign(bgp)==1 & sign(bi)==1
		replace dirNU="-" if model==1 & sign(bgp)==-1 & sign(bi)==-1
		}
	****deal with nonbinary group variables later********************************
	else {
		gen str5 dirU=""
		gen str5 dirNU=""
		}
	capture save DIFdetect.dta, replace

	by type: keep if _n==1
	}	/*closes quietly*/


la var ldif "P(Dif.(LL))"
la var ludif "P(Dif.(LL))"
la var dif "Change in Est."

 *  change beta for U DIF
if $DB_unichk1==1 & $DB_unichk3==0{
		*identify dif;
		quietly gen nonunif=0
		quietly replace nonunif=1 if ldif<=$DB_pval3
		quietly gen unif=0
		quietly replace unif=1 if abs(dif)>=$DB_pchange

		la var nonunif "Non-Uniform DIF"
		la var unif "Uniform DIF"
		la de nudif 0 "no" 1 "yes"
		la val nonunif nudif

		la de udif 0 "no" 1 "yes"
		la val unif udif

		sort group type
		quietly drop if ldif==.
		noisily di as result "Non-Uniform Differential Item Functioning"
		noisily by group: tabdisp item, cell(ldif nonunif dirNU) by(ability) cen
		noisily di as result "Uniform Differential Item Functioning"
		noisily by group: tabdisp item, cell(dif unif dirU) by(ability) cen
		noisily di "Non-Uniform DIF if P(Dif.(LL)) {ul:<} $DB_pval3"
		noisily di "Uniform DIF if Change in Est. {ul:>} $DB_pchange"
		}
* both change beta and LL for U DIF
else if $DB_unichk1==1 & $DB_unichk3==1 {
		quietly gen nonunif=0
		quietly replace nonunif=1 if ldif<=$DB_pval3
		quietly gen unif=0
		quietly replace unif=1 if (abs(dif)>=$DB_pchange | ludif <=$DB_pval7)

		la var nonunif "Non-Uniform DIF"
		la var unif "Uniform DIF"
		la de nudif 0 "no" 1 "yes"
		la val nonunif nudif

		la de udif 0 "no" 1 "yes"
		la val unif udif
		sort group type
		quietly drop if ldif==.
		noisily di as result "Non-Uniform Differential Item Functioning"
		noisily by group: tabdisp item, cell(ldif nonunif dirNU) by(ability) cen
		noisily di as result "Uniform Differential Item Functioning"
		noisily by group: tabdisp item, cell(dif ludif unif dirU) by(ability) cen
		noisily di "Non-Uniform DIF if P(Dif.(LL)) {ul:<} $DB_pval3"
		noisily di "Uniform DIF if Change in Est. {ul:>} $DB_pchange or P(Dif.(LL)) {ul:<} $DB_pval7"
		}

 *  LL for U DIF
else if $DB_unichk1==0 & $DB_unichk3==1 {

		quietly gen nonunif=0
		quietly replace nonunif=1 if ldif<=$DB_pval3
		quietly gen unif=0
		quietly replace unif=1 if ludif <=$DB_pval7

		la var nonunif "Non-Uniform DIF"
		la var unif "Uniform DIF"
		la de nudif 0 "no" 1 "yes"
		la val nonunif nudif
		la de udif 0 "no" 1 "yes"
		la val unif udif
		sort group type

		quietly drop if ldif==.
		noisily di as result "Non-Uniform Differential Item Functioning"
		noisily by group: tabdisp item, cell(ldif nonunif dirNU) by(ability) cen
		noisily di as result "Uniform Differential Item Functioning"
		noisily by group: tabdisp item, cell(ludif unif dirU) by(ability) cen
		noisily di "Non-Uniform DIF if P(Dif.(LL)) {ul:<} $DB_pval3"
		noisily di "Uniform DIF if P(Dif.(LL)) {ul:<} $DB_pval7"
		}

processitems		/* Do new tasks */
dumpcode			/* Write code_$DB_runname.psl */
makevarlist			/* Write vars_$DB_runname.txt*/
writepardata		/* Write data_$DB_runname.txt*/

local blocks=($ncodelines-$nheaderlines)/2

clear
use "Difdetect"
gen str check="Check model:"
la define model 1 "Interaction" 2 "Ability + Group" 3 "Ability only"
la val model model
format se* %6.2f

if "$DB_ordinal" == "" {
	if (sebgp > 4 & sebgp ~= .) | (sebab > 4 & sebab ~= .) | (sebintx > 4 & sebintx ~= .){
		noisily di ""
		noisily di in red "Warning, the following models need checking: "
		list item group ability model sebab sebgp sebintx if (sebgp > 4 & sebgp ~= .) | (sebab > 4 & sebab ~= .) | (sebintx > 4 & sebintx ~= .),noobs label
		}
	}

clear

noisily di ""
noisily di ""
noisily di in white as result "There are now " `blocks' " items: $varlist "
noisily di ""
noisily di ""
noisily di in yellow as text "This output was produced using difwithpar version 1.3"
noisily di in yellow as text "by Paul Crane, Laura Gibbons, Lance Jolley, and Gerald van Belle"
noisily di in yellow as text "University of Washington"
noisily di in yellow as text "Copyright 2005"
noisily di ""
noisily di in white "Recommended citation:"
noisily di in white "difwithpar v. 1.3, Crane P, Gibbons LE, Jolley L, van Belle G"
noisily di in white "   Seattle, WA:  University of Washington, 2005."
end

* END DIFdetect03
*----------------------------------------------------------


*----------------------------------------------------------
* START difwithpar PROGRAM

capture prog drop difwithpar
program define difwithpar
	version 8.0

syntax varlist, ID(str) RUnname(str) ABility(str) GRoups(str) ///
       [MULtinomial(str) NUL(integer 1) NUPValue(real 0.05) ///
       UBeta(integer 1) UBCH(real 0.10) UL(integer 0) ULPValue(real 0.05) ///
       minsize(integer 20) ///
       CLeanup(integer 0) SCale(str) NQpt(str) CYcles(str)]

set more off
capture log close
capture log using "DfP_`runname'.log", replace

if "`scale'"=="" {
	global scale = 1.7
	}
else {
	global scale=`scale'
	}
if "`nqpt'"=="" {
	global nqpt = 11
	}
else {
	global nqpt=`nqpt'
	}
if "`cycles'"=="" {
	global cycles = 1000
	}
else {
	global cycles=`cycles'
	}

global origdata "$S_FN"
  qui de
  local d=r(N)

global DB_allitems "`varlist'"			/*items				*/
        *sort out items with just one level:
        global DB_items ""
        foreach x of global DB_allitems {
			capture qui tab `x'
			if r(r)==1{
				di in red "`x' dropped, all one level"
				continue
				}
			else global DB_items "$DB_items`x' "
		}
global DB_ival: word count $DB_items		/*number of items			*/
global DB_id "`id'"							/*id					*/
global DB_runname "`runname'"				/*name for output files		*/
global DB_abilities "`ability'"				/*ability measure			*/
global DB_abilval: word count $DB_abilities	/*number of ability measures	*/
	if $DB_abilval ~= 1 {
		noisily di in red "Only one ability variable can be processed at a time."
		exit
		}
global DB_groups "`groups'"					/*groups				*/
global gval: word count $DB_groups
	if $gval ~= 1 {
		noisily di in red "Only one grouping variable can be processed at a time."
		exit
		}


global DB_ordinal "`multinomial'"		/*ordinal group with > 2 levels	*/
							/*will be treated as MULTINOMIAL    */

global oval: word count $DB_ordinal
	if $oval ~= 0 {
		local tempvar __tempvar2
        bysort $DB_ordinal: gen `tempvar'=_N if $DB_ordinal < .
        egen __tempvarx=min(`tempvar')
        if __tempvarx < 20 {
			di in red "Multinomial grouping variable must have at least 20 subjects/level"
			drop  __tempvar*
			exit
        	}
		drop __tempvar*
		}
	if $oval == 0 {
		capture drop __templevg
		capture qui tab $DB_groups
		capture qui gen __templevg=r(r)
		if r(r) ~= 2 {
			noisily di in red "Grouping variable must be binary or designated as multinomial."
			drop __templevg
			exit
			}
		drop __templevg
		}
capture qui tab $DB_ordinal
global olevels=r(r)

global DB_categorical "`categorical'"	/*nominal groups with > 2 levels (not allowed here yet, will be empty)	*/
global DB_DIFtypesval: word count $DB_groups	/*number of groups; keep for compatibility with DIFd		*/

global DB_nonunichk1 = 1		/*-2LL criteria for non-uniform	*/
global DB_pval3 = `nupvalue'			/*alpha level for non-uniform		*/

global DB_unichk1 = `ubeta'			/*change in beta for uniform		*/
global DB_unichk3 = `ul'			/*-2LL for uniform			*/
global DB_pchange = `ubch'			/*percent change for beta in uniform*/

global DB_pval7 = `ulpvalue'		/*p-value for -2LL for uniform	*/
global minsize = `minsize'			/* Minimum category frequency	      */
global cleanup = `cleanup'			/*Erase parscale files when done    */

global nheaderlines = 11	/* No. of header lines in PARSCALE code file */
global ntrailerlines = 2 	/* And no. of trailer lines */
global varlist ""

capture assert $DB_nonunichk1==1
	if _rc==9 {
		noisily di "NUL must be 1 (-2LL option for non-unif DIF) until we make an effect size measure "
		exit
		}
capture assert $DB_unichk1==0 | $DB_unichk1==1
	if _rc==9 {
		noisily di "UBeta must be 0 or 1 (change in beta option for uniform DIF) "
		exit
		}
capture assert $DB_unichk3==0 | $DB_unichk3==1
	if _rc==9 {
		noisily di "UL must be 0 or 1 (-2LL option for uniform DIF) "
		exit
		}
capture assert $DB_pval3 > 0 & $DB_pval3 < 1
	if _rc==9 {
		noisily di "NUPV must be between 0 and 1"
		exit
		}
capture assert $DB_pchange > 0
	if _rc==9 {
		noisily di "UBP must be > 0"
		exit
		}
capture assert $DB_pval6 > 0 & $DB_pval6 < 1
	if _rc==9 {
		noisily di "UPPV must be between 0 and 1"
		exit
		}
capture assert $DB_pval7 > 0 & $DB_pval7 < 1
	if _rc==9 {
		noisily di "ULPV must be between 0 and 1"
		exit
		}
capture drop parscaleid

local idtype: type $DB_id
if substr("`idtype'",1,3)=="str" {
	capture encode $DB_id, gen(__numid)
	if _rc==134 {
		noisily di in yellow "The sample size limit for an ID in string (character) format"
		noisily di in yellow "is 65,536 subjects.  Please create a numeric ID and use that."
		exit
		}
	capture assert __numid < 1000000000
	if _rc==9 {
		noisily di "ID is more than 9 digits, need to modify code"
		exit
		}
	gen long parscaleid = __numid +1000000000
	drop __numid
	}

/* If ID is more than 9 digits, modify here, in dumpfile header lines (recalculate), and in writepardata (add more spaces to NPKY line)*/

else	{
	capture assert $DB_id < 1000000000
	if _rc==9 {
		noisily di "ID is more than 9 digits, need to modify code"
		exit
		}
	gen long parscaleid = $DB_id +1000000000
	}
capture save "$origdata", replace
qui drop if $DB_groups ==.
qui su $DB_groups
local m=`d'-r(N)
if `m'~=0 {
    noi di in red "Theta acounting for DIF due to $DB_groups will not be calculated"
    noi di in red "     for the `m' observations missing $DB_groups."
    }

capture save "tempdata", replace

noisily DIFdetect03

*now run PARSCALE
capture confirm file C:\PROGRA~1\parscale\PSL0.EXE
if _rc~=0 {
	  di in red "This program won't execute parscale because you do not have"
	  di in red "parscale executables in the folder c:\program files\parscale"
	  di in red "either move your parscale executables or fix the path in"
	  di in red "this ado file."
	  }

foreach i of numlist 0/3 {
	  !C:\PROGRA~1\parscale\PSL`i'.EXE code_$DB_runname
	  }

thetain				/* merges in theta and setheta*/

capture erase "tempdata.dta"

if $cleanup==1 {
 	! del `runname'.*
	! del code_`runname'.*
	! del data_`runname'.*
	erase pscalscore_file
	erase pscaltotinfo_file
	erase pscalinfo_file
	erase pscalicc_file
	}

capture save "$origdata", replace

noisily di ""
noisily di ""
noisily di in yellow as text "This output was produced using difwithpar version 1.3"
noisily di in yellow as text "by Paul Crane, Laura Gibbons, Lance Jolley, and Gerald van Belle"
noisily di in yellow as text "University of Washington"
noisily di in yellow as text "Copyright 2005"
noisily di ""
noisily di in white "Recommended citation:"
noisily di in white "difwithpar v. 1.3, Crane P, Gibbons LE, Jolley L, van Belle G"
noisily di in white "   Seattle, WA:  University of Washington, 2005."
noisily di ""
noisily di ""

set more on
capture log close
end

* END difwithpar PROGRAM
*----------------------------------------------------------