*! version 3.0.0 PR 19apr2012 program define mfpi, eclass version 11.0 local cmdline : copy local 0 mata: _parse_colon("hascolon", "rhscmd") if !`hascolon' error 198 _mfpi `"`0'"' `"`rhscmd'"' ereturn local cmdline `"mfpi `cmdline'"' end program define _mfpi, rclass version 11.0 args 0 statacmd gettoken cmd statacmd: statacmd if substr("`cmd'", -1, .) == "," { local cmd = substr("`cmd'", 1, length("`cmd'") - 1) local statacmd ,`statacmd' } xfrac_chk `cmd' if `s(bad)' { di as err "invalid or unrecognised command, `cmd'" exit 198 } local dist `s(dist)' local normal = (`dist'==0) global MFpdist `dist' // Determine if constant is needed (used in genf() option) if !inlist("`cmd'", "stcox", "cox", "clogit") local cons constant // Parse mfpi options from first argument /* Hidden option `adjbin' adjusts for treatment variable in adjustment model. Won't make much difference in randomised controlled trials, but may be important in observational data. To be discussed. Flexibility (flex()): 1 = least (Royston & Sauerbrei 2004), 2 = intermediate (same powers for main effect and interaction, determined for interaction), 3 = intermediate (allow different powers for main effect and interaction), 4 = most (allow different powers in each group and for main effect). */ syntax [if] [in] [aw fw pw iw] [, /// SELect(string) ADDpowers(string) ADJust(varlist fv) ADJBin all ALpha(string) CEnter(passthru) /// CENTer(varlist) DEAD(varname) DETail DF(string) FLex(int 1) FP1(varlist) FP2(varlist) /// GENF(string) GENDiff(string) LINear(string) OUTcome(string) POWers(string) noSCAling /// noCI SHOwmodel TReatment(varname fv) WITH(varname fv) ZERo(string) MFPopts(string) ] // Process mfpi options if "`with'" != "" { if "`treatment'" != "" { di as err "cannot have both treatment() and with() - they are synonyms" exit 198 } local treatment `with' } else local with `treatment' fvexpand `with' if "`r(fvops)'" == "" { local with i.`with' fvexpand `with' if "`r(fvops)'" == "" { di as err "treatment() must be a factor or categoric variable" exit 198 } di as txt "[treating `treatment' as a factor variable, `with']" } // `With' is `with' stripped of its factor prefix local With = substr("`with'", 1 + strpos("`with'", "."), .) if "`linear'`fp1'`fp2'"=="" { di as err "you must specify covariate(s) for interaction with `with'" exit 198 } if "`cmd'" == "mlogit" & "`genf'`gendiff'" != "" { if `"`outcome'"' == "" { noi di as err "you must specify outcome() with genf() and/or gendiff()" exit 198 } } // Store original lists of variables for interaction analysis with linear, FP1, FP2 models local Linear `linear' local Fp1 `fp1' local Fp2 `fp2' if "`powers'"=="" local powers -2 -1 -0.5 0 0.5 1 2 3 tokenize `powers' `addpowers' local i 1 while "``i''"!="" { local p`i' ``i'' local ++i } local np = `i' - 1 if ("`detail'" != "") local noi noi local powers powers(`powers' `addpowers') local items 0 /* overall number of interactions considered */ if "`linear'"!="" { // Sort out bound partners local lsep "(" local rsep ")" tokenize `linear', parse(" `lsep'`rsep'") local linear local lparen 0 local cat local ncat 0 while "`1'"!="" { if "`1'"=="`lsep'" { if `lparen' { noi di as err "unexpected `lsep' in linear()" exit 198 } local lparen 1 } else if "`1'"=="`rsep'" { if `lparen'==0 { noi di as err "unexpected `rsep' in linear()" exit 198 } local ++ncat fvunab iz`ncat': `cat' fvexpand `iz`ncat'' local isfactor`ncat' = ("`r(fvops)'" == "true") local linear `linear' `iz`ncat'' local ifp`ncat' 0 /* ifp=0 indicates linear term */ local cat local lparen 0 } else { if `lparen'==0 { local ++ncat fvunab iz`ncat': `1' fvexpand `iz`ncat'' local isfactor`ncat' = ("`r(fvops)'" == "true") local linear `linear' `iz`ncat'' local ifp`ncat' 0 } else local cat `cat' `1' } mac shift } if `lparen' { noi di as err "unexpected `rsep' in linear()" exit 198 } local items `ncat' } // Check validity of linear items forvalues i = 1 / `items' { if `isfactor`i'' & wordcount("`iz`i''") > 1 { noi di as err "not allowed to bind a factor variable to another variable" exit 198 } local nz: word count `iz`i'' forvalues j = 1 / `nz' { local zz: word `j' of `iz`i'' if ("`zz'" == "`With'") | ("`zz'" == "`with'") { noi di as err "cannot interact a treatment variable with itself" exit 198 } } } if "`fp1'"!="" { local nfp1: word count `fp1' forvalues i = 1/`nfp1' { local ++items local iz`items': word `i' of `fp1' if "`iz`items''" == "`With'" { noi di as err "cannot interact a treatment variable with itself" exit 198 } local ifp`items' 1 } } if "`fp2'"!="" { local nfp2: word count `fp2' forvalues i = 1/`nfp2' { local ++items local iz`items': word `i' of `fp2' if "`iz`items''" == "`With'" { noi di as err "cannot interact a treatment variable with itself" exit 198 } local ifp`items' 2 } } // Parse to extract varlist (as `xlist') from statacmd local 0 `statacmd' syntax [anything(name=xlist)] [if] [in] [aw fw pw iw] [, DEAD(str) noCONStant * ] if !missing("`dead'") local options `options' dead(`dead') if !missing("`constant'") local options `options' `constant' /* if ("`xlist'" != "") */ GetVL `xlist' marksample touse markout `touse' `yvar' $MFP_cur `with' `linear' `fp1' `fp2' `adjust' `dead' frac_wgt "`exp'" `touse' "`weight'" local wgt `r(wgt)' // [`weight'`exp'] local yvar $MFP_dv local xvars forvalues i = 1 / $MFP_n { local mfp`i' ${MFP_`i'} local w : word count `mfp`i'' if (`w' > 1) local xvars `xvars' (`mfp`i'') else local xvars `xvars' `mfp`i'' } if ("`all'" == "") local ifuse if `touse' else local restrict restrict(if `touse') // Remove `with' variable from xvars, if it was there local Xvars: list xvars - with local Xvars: list xvars - With quietly { levelsof `With', local(levels) local nlevels : word count `levels' if `nlevels' < 2 { di as err "`with' must have at least two levels" exit 198 } forvalues i = 1 / `nlevels' { local level`i' : word `i' of `levels' } count if `touse' local nobs = r(N) local nxvar: word count `Xvars' if `nxvar' == 0 { if ("`select'" != "") & ("`adjust'" != "") noi di as txt "[select() ignored]" } else { if ("`select'" == "") local select 1 // Find adjustment model for main covariates if ("`alpha'" != "") local Alpha alpha(`alpha') // Add treatment variable to adjustment model if "`adjbin'" != "" { local adjbin `with' local select `select', `adjbin':1 } // Add adjust variables as linear terms in adjustment model if "`adjust'" != "" { if ("`df'" != "") local Df df(`df',`adjust':1) else local Df df(`adjust':1) local select `select', `adjust':1 } else { if ("`df'" != "") local Df df(`df') } // Select adjustment model local Select select(`select') `noi' xmfp, `Select' `Alpha' `Df' `powers' `center' `scaling' `mfpopts' : /// `cmd' `yvar' `Xvars' `adjust' `adjbin' `wgt' if `touse', `options' if "`showmodel'"!="" { noi di as txt _n "Variables in adjustment model" _n "{hline 29}" if ("`adjust'" != "") noi di as txt "[`adjust': linear]" if ("`adjbin'" != "") noi di as txt "[`adjbin': `with']" } // Store details of selected adjustment model local nxf 0 forvalues i = 1 / `nxvar' { local p `e(fp_k`i')' local x `e(fp_x`i')' if ("`showmodel'" != "") noi di as txt %10s "`x':" _cont if "`p'" != "." { if ("`showmodel'" != "") noi di as txt " power(s) = " as res "`p'" local ++nxf local x`nxf' `x' local fp`nxf' `p' local n`nxf' `e(fp_n`i')' local sel`i' 1 } else { local sel`i' 0 if ("`showmodel'" != "") noi di as txt " not selected" } } } local d3 79 local flexmess = cond(`flex'==1, "(least flexible)", cond(`flex'==4, "(most flexible)", "(intermediate)")) noi di as txt _n "Interactions with `with' (" as res `nobs' /// as txt " observations). Flex-`flex' model `flexmess'" noi di as txt _n "{hline `d3'}" noi di as txt "Var Main Interact idf Chi2 P Deviance tdf AIC" noi di as txt "{hline `d3'}" forvalues ni = 1 / `items' { local z `iz`ni'' local nz: word count `z' /* `z' could be a varlist */ local degree `ifp`ni'' /* 0, 1 or 2; 0=linear */ /* Remove members of z from adjustment model varlist (note that `adjust' vars, if any, are not counted in nxvar) */ local xvars if `nxvar' > 0 { local nxf 0 forvalues j = 1 / `nxvar' { if `sel`j'' { local ++nxf // Check if x`nxf' is in the `z' list local in_z_list 0 forvalues i = 1 / `nz' { local zi: word `i' of `z' if "`zi'" == "`x`nxf''" { local in_z_list 1 continue, break } } if !`in_z_list' local xvars `xvars' `n`nxf'' } } } /* Deal with FP case */ if `degree' > 0 { // Determine shift in z, if needed to avoid zeros. fracgen `z' 0 if `touse', nogen `scaling' local shift = r(shift) local scale = r(scale) // Local `iz' holds the name of the possibly shifted and scaled covariate local iz _`z' cap drop `iz' gen `iz' = (`z'+`shift')/`scale' `ifuse' if `shift' == 0 { if `scale' == 1 lab var `iz' "`z'" else lab var `iz' "`z'/`scale'" } else { if `scale' == 1 lab var `iz' "`z'+`shift'" else lab var `iz' "(`z'+`shift')/`scale'" } /* Main-effects model. Determine powers for flex 1, 3 and 4 using fracpoly. Flex 2 uses main-effect powers from interaction model. */ if `flex'==1 | `flex'==3 | `flex'==4 { xfracpoly, degree(`degree') `powers' center(no) `scaling': /// `cmd' `yvar' `iz' `xvars' `adjust' `with' `wgt' if `touse', `options' local powmain `e(fp_pwrs)' if ("`powmain'" != "1") cap drop `e(fp_xp)' } /* Interaction models */ if `flex'==1 { // Use powers from main effect for main effect and interaction local powint `powmain' } else if `flex'==2 | `flex'==3 { // Determine interaction powers for flex 2 and 3. // Force powers to be the same for all levels (= `powint'). local devbest 1e30 forvalues j = 1/`np' { if `degree'==2 { forvalues j2 = `j'/`np' { fracgen `iz' `p`j'' `p`j2'' if `touse', replace `center' `scaling' `cmd' `yvar' `xvars' `adjust' `with' `with'#c.(`r(names)') `wgt' if `touse', `options' local devint = -2*e(ll) if `devint' < `devbest' { local devbest `devint' local powint `p`j'' `p`j2'' } } } else { fracgen `iz' `p`j'', replace `center' `scaling' local v `r(names)' `cmd' `yvar' `xvars' `adjust' `with' `with'#c.`v' `wgt' if `touse', `options' local devint = -2*e(ll) if `devint' < `devbest' { local devbest `devint' local powint `p`j'' } } } if (`flex' == 2) local powmain `powint' } else if `flex'==4 { local unvi // names of untransformed z at each level forvalues i = 1 / `nlevels' { tempvar z`i' // `z`i'' is `iz' for level `i' of `with', 0 otherwise gen `z`i'' = cond(`With'==`level`i'', `iz', 0) `ifuse' local unvi `unvi' `z`i'' } // Use mfp to determine possibly different powers (`powint`i'') at each level. if (`degree' == 2) local mfpdf df(1, `unvi':4) else local mfpdf df(1, `unvi':2) xmfp, select(1) alpha(1) `mfpdf' zero(`unvi') center(no) `scaling' : /// `cmd' `yvar' `unvi' `xvars' `adjust' `with' `wgt' if `touse', `options' forvalues i = 1 / `nlevels' { local powint`i' `e(fp_k`i')' // estimated power(s) at each level local dropn`i' `e(fp_n`i')' // possibly FP-transformed `iz`i'' local dropx`i' `e(fp_x`i')' // tempvar z`i' } } } else { // Linear case (includes binary and categorical). local powmain Linear local powint Linear } // Fit main-effects model if `degree' > 0 { fracgen `iz' `powmain' `ifuse', replace noscaling `restrict' `center' // ??? name(`z') local v `r(names)' } else { // Linear local v `z' } `noi' `cmd' `yvar' `xvars' `adjust' `with' `v' `wgt' if `touse', `options' local devmain = -2*e(ll) // Fit interaction model if `degree' > 0 { // Macro vi holds names of variables for interaction model; // components are vi1,...,vi`degree' local vi if `flex' <= 3 { fracgen `iz' `powint' `ifuse', replace noscaling `restrict' `center' // ??? name(`z') local vi `r(names)' forvalues j = 1 / `degree' { local vi`j' : word `j' of `vi' } } else { // flex = 4 forvalues j = 1 / `degree' { cap drop `iz'`j' gen `iz'`j' = . local vi`j' `iz'`j' local vi `vi' `vi`j'' } forvalues i = 1 / `nlevels' { fracgen `z`i'' `powint`i'' `ifuse', zero replace noscaling `restrict' `center' forvalues j = 1 / `degree' { local zij : word `j' of `r(names)' replace `iz'`j' = `zij' if `With' == `level`i'' // !! to be var labelled cap drop `zij' } cap drop `dropn`i'' cap drop `dropx`i'' } } } else local vi `z' `noi' `cmd' `yvar' `xvars' `adjust' `with' `with'#c.(`vi') `wgt' if `touse', `options' local devint = -2*e(ll) // Test interaction local k = e(k_eq_model) if (`k' == 0) | missing(`k') local k 1 local ndum = `nlevels' - 1 if `degree' > 0 { // FP local dfmain = `degree' + `k' * (`ndum' + `degree') local dfint = `k' * `ndum' * `degree' + (`flex'==4) * `ndum' * `degree' } else { // linear local dfmain = `k' * (`ndum' + `nz' ) local dfint = `k' * `ndum' * `nz' } local dftot = `dfmain' + `dfint' local totaldf`degree' `dftot' local chi = `devmain' - `devint' local P = chiprob(`dfint', `chi') /* Store details of test statistic for this variable. (Note: only details of last variable in list will be finally stored.) */ local dfd`degree' `dfint' local chi2`degree' `chi' local P`degree' `P' local dev`degree' `devint' local aic`degree' = `devint'+2*`totaldf`degree'' if (length("`z'") > 10) local showz = abbrev("`z'", 10) else local showz `z' if `degree'==0 { local term1 "Linear" local term2 "Linear" } else { local term1 "FP`degree'(`powmain')" if `flex'==4 { local starmess "* possibly more than one set of FP powers, shown for each level of `With'" local term2 "FP`degree'(`level1':`powint1')*" } else local term2 "FP`degree'(`powint')" } noi di as txt %-12s "`showz'" %-12s "`term1'" %-12s "`term2'" as res /* */ %3s "`dfint'" %8.2f `chi' %9.4f `P' %10.3f `devint' %3.0f `totaldf`degree'' %10.3f `aic`degree'' if `degree' > 0 & `flex' == 4 { forvalues i = 2 / `nlevels' { noi di as txt _col(25) "FP`degree'(`level`i'':`powint`i'')" } } if "`genf'`gendiff'"!="" { /* Note that correct prediction depends on fitting interaction model last in the lines above this. Create full-sample versions of FP of z for prediction. */ if "`cmd'" == "mlogit" { // Check for valid outcome category local eqn `"`e(eqnames)'"' if !`: list outcome in eqn' { noi di as err "`outcome' is not a valid outcome category for `yvar'" exit 198 } } if `degree' > 0 { // Create FP variables required for prediction, their names in macros FP* local Izdrop forvalues i = 1 / `nlevels' { if `flex' <= 3 { if `i' == 1 { fracgen `iz' `powint' `ifuse', replace noscaling `restrict' `center' name(Iz`i') local Izdrop `r(names)' } } else { fracgen `iz' `powint`i'' `ifuse', replace noscaling `restrict' `center' name(Iz`i') local Izdrop `Izdrop' `r(names)' } forvalues j = 1 / `degree' { local FP`i'`j' : word `j' of `r(names)' if (`i' == 1) local prefix`j' c } } local nterms `degree' } else { // Store quantities for linear covariate(s) if `isfactor`ni'' { // linear term is a factor local Z = substr("`z'", 1 + strpos("`z'", "."), .) levelsof `Z', local(Zlevels) local nZ : word count `Zlevels' forvalues j = 1 / `nZ' { local lev : word `j' of `Zlevels' forvalues i = 1 / `nlevels' { local FP`i'`j' `lev'.`Z' if (`i' == 1) { local prefix`j' `lev' local vi`j' `Z' } } } local nterms `nZ' } else { // linear term is assumed continuous forvalues i = 1 / `nlevels' { forvalues j = 1 / `nz' { local FP`i'`j' : word `j' of `z' if (`i' == 1) { local vi`j' `FP`i'`j'' local prefix`j' c } } } local nterms `nz' } } local ned = -invnormal((100 - c(level)) / 200) if "`genf'" != "" { forvalues i = 1 / `nlevels' { local lev `level`i'' // !! to check next line manually in example if ("`cons'" != "") { local predict`i' _b[_cons]+_b[`lev'.`With'] } else local predict`i' _b[`lev'.`With'] forvalues j = 1 / `nterms' { local predict`i' `predict`i'' +_b[`lev'.`With'#`prefix`j''.`vi`j'']*`FP`i'`j'' } cap drop `genf'`ni'_`i' if "`ci'" != "noci" { cap drop `genf'`ni's_`i' *noi di in red "i=`i' predict=`predict`i''" predictnl `genf'`ni'_`i' = `predict`i'' `ifuse', se(`genf'`ni's_`i') cap drop `genf'`ni'lb_`i' cap drop `genf'`ni'ub_`i' gen `genf'`ni'lb_`i' = `genf'`ni'_`i'-`ned'*`genf'`ni's_`i' gen `genf'`ni'ub_`i' = `genf'`ni'_`i'+`ned'*`genf'`ni's_`i' lab var `genf'`ni'lb_`i' "lower conf limit: `genf'`ni'_`i'" lab var `genf'`ni'ub_`i' "upper conf limit: `genf'`ni'_`i'" } else predictnl `genf'`ni'_`i' = `predict`i'' `ifuse' } } if "`gendiff'"!="" { // Predict f(level i) - f(base level) forvalues i = 2 / `nlevels' { local i1 = `i' - 1 local lev `level`i'' local predict`i' _b[`lev'.`With']-_b[`level1'.`With'] forvalues j = 1 / `nterms' { local predict`i' `predict`i'' /// +_b[`lev'.`With'#`prefix`j''.`vi`j'']*`FP`i'`j'' /// -_b[`level1'.`With'#`prefix`j''.`vi`j'']*`FP1`j'' } cap drop `gendiff'`ni'_`i1' if "`ci'" != "noci" { cap drop `gendiff'`ni's_`i1' cap drop `gendiff'`ni'lb_`i1' cap drop `gendiff'`ni'ub_`i1' predictnl `gendiff'`ni'_`i1' = `predict`i'' `ifuse', se(`gendiff'`ni's_`i1') gen `gendiff'`ni'lb_`i1' = `gendiff'`ni'_`i1'-`ned'*`gendiff'`ni's_`i1' gen `gendiff'`ni'ub_`i1' = `gendiff'`ni'_`i1'+`ned'*`gendiff'`ni's_`i1' lab var `gendiff'`ni'lb_`i1' "lower conf limit: `gendiff'`ni'_`i1'" lab var `gendiff'`ni'ub_`i1' "upper conf limit: `gendiff'`ni'_`i1'" } else predictnl `gendiff'`ni'_`i1' = `predict`i'' `ifuse' } } cap drop `Izdrop' } } } di as txt "{hline `d3'}" /// _n "idf = interaction degrees of freedom; tdf = total model degrees of freedom" if ("`starmess'" != "") di as txt "`starmess'" if "`showmodel'" != "" & "`detail'" == "" { if ("`yvar'" != "") local yvar4 " for `yvar'" local mess Last-fitted interaction model`yvar4': di as txt _n "`mess'" if ("`cmd'" == "stcox") `cmd', nohr else `cmd' } return local if `if' return local in `in' return local varlist `varlist' return local dead `dead' return local treatment `treatment' return local with `treatment' /* Store details of interaction test(s) for final variable in each list (linear, fp1, fp2) */ if "`Linear'"!="" { return local Linear `Linear' return scalar chi2lin = `chi20' return scalar Plin = `P0' return scalar devlin = `dev0' return scalar aiclin = `aic0' return scalar totdflin = `totaldf0' } if "`Fp1'"!="" { return local Fp1 `Fp1' return scalar chi2fp1 = `chi21' return scalar Pfp1 = `P1' return scalar devfp1 = `dev1' return scalar aicfp1 = `aic1' return scalar totdffp1 = `totaldf1' } if "`Fp2'"!="" { return local Fp2 `Fp2' return scalar chi2fp2 = `chi22' return scalar Pfp2 = `P2' return scalar devfp2 = `dev2' return scalar aicfp2 = `aic2' return scalar totdffp2 = `totaldf2' } if ("`gendiff'" != "") char _dta[gendiff] `gendiff' else char _dta[gendiff] char _dta[treatment] `With' return local adjust `adjust' return local z `z' if "`z'"!="" { return local powmain `powmain' if `flex'==4 { forvalues i = 1 / `nlevels' { return local powint`i' `powint`i'' } } else return local powint `powint' } return scalar nxvar = `nxvar' if `nxvar' > 0 { // save FP adjustment model details forvalues i = 1/`nxf' { return local x`i' `x`i'' return local power`i' `fp`i'' } return scalar nxf = `nxf' } end program define ChkDepvar args xlist colon spec gettoken depvar spec : spec, parse("()") match(par) if ("`par'"!="") { di as err "invalid syntax" exit 198 } fvunab depvar : `depvar' gettoken depvar rest : depvar global MFP_dv $MFP_dv `depvar' c_local `xlist' `rest' `spec' end program define GetVL /* [y1 [y2]] xvarlist [(xvarlist)] ... */ macro drop MFP_* local xlist `0' if $MFpdist != 7 { ChkDepvar xlist : `"`xlist'"' if $MFpdist == 8 { /* intreg */ ChkDepvar xlist : `"`xlist'"' } } if (`"`xlist'"'=="") { *error 102 global MFP_n 0 exit } gettoken xvar xlist : xlist, parse("()") match(par) while (`"`xvar'"'!="" & `"`xvar'"'!="[]") { fvunab xvar : `xvar' local nvar : word count `xvar' if ("`par'"!="" | `nvar'==1) { global MFP_n = $MFP_n + 1 global MFP_$MFP_n "`xvar'" global MFP_cur "$MFP_cur `xvar'" } else { tokenize `xvar' forvalues i=1/`nvar' { global MFP_n = $MFP_n + 1 global MFP_$MFP_n "``i''" global MFP_cur "$MFP_cur ``i''" } } gettoken xvar xlist : xlist, parse("()") match(par) if ("`par'"=="(" & `"`xvar'"'=="") { di as err "empty () found" exit 198 } } end exit History ------- 3.0.0 19apr2012 Major rewrite to support factor variables. Adjust() option takes over function of adjvars() option. 2.0.1 11mar2010 Changes made to mfpi_10 to preserve variables in final fitted model, allowing -predict- to work. Also, -showmodel- presents the final fitted model. Various new variables appropriately labelled. 1.0.4 26oct2009 fix problems with determining primary equation name 1.0.3 04sep2009 Rename with() option to TReatment() and adjust() option to center()