*! A.Discacciati, V.Oskarsson, N.Orsini
*! 20150617
*! v 1.6

capture program drop stphcoxrcs
program define stphcoxrcs, rclass
version 12

syntax varlist(min=1 max=1 fv), ///
	[ ///
	SPLITEVERY(string) ///
	SPLITAT(string) ///
	NKnots(integer 3) ///
	GOPTS(string) ///
	NOGraph ///
	Level(string) ///
	LNTime ///
	NOCI ///
	SAVING(string) ///
	NOYREF ///
	IC ///
	MYCHECK /// /* leave undocumented, only used to check results with stcox + tvc */
	RANGE(string) ///
	LRTest ///
	]

local myvarlist "`varlist'"

***initial checks

*data stset'ed
st_is 2 analysis

*id variable set
if "`_dta[st_id]'" == "" {
	di as err "stphcoxrcs requires that you have previously stset an id() variable"
	exit 198
}

*last command is cox
if e(cmd) != "cox" {
	di in red "last command must be stcox"
	exit 198
}
else {
	tempname oldcox
	est store `oldcox'

	local oldstcox = "`e(cmdline)'"
	local oldhr = exp(_b[`myvarlist'])

	local 0 : subinstr local oldstcox "stcox" ""
	syntax varlist(fv) [if] [in] [, * ]
	local oldvarlist "`varlist'"
	*local oldif "`if'"
	local oldoptions "`options'"
	*mac li

	if strpos("`oldoptions'", "tvc") != 0 | ///
		strpos("`oldoptions'", "texp") != 0 {
	di as err "you cannot use tvc() and/or texp() options in stcox"
	exit 198
	}
}

*split
if "`splitevery'" != "" & "`splitat'" != "" {
	di as err "please specify only splitevery() or splitat()"
	exit 198
}
if "`splitevery'" == "" & "`splitat'" == "" {
	local splitat "failures"
}

*knots between 3 and 5
if `nknots' < 3 | `nknots' > 5 {
	di as err "nknots() must be between 3 and 5"
	exit 198
}
if "`mycheck'" != "" local nknots = 2

*level
if "`level'" == "" {
	local level = c(level)
}
if `level' <10 | `level'>99 {
		di as err "level() must be between 10 and 99"
		exit 198
}

*range
if "`range'" != "" {
	local nrange : word count `range'
	if `nrange' != 2 {
		di as err "specify 2 values in the option range()"
		exit 198
	}
	else {
		tokenize "`range'"
		if "`lntime'" == "" {
			local minrange = `1'
			local maxrange = `2'
		}
		else {
			if `1' == 0 {
				di as err "lower limit in range() cannot be 0 if the option lntime is also specified"
				exit 198
				}
			local minrange = log(`1')
			local maxrange = log(`2')
		}
	}
}


***clean dataset
preserve
qui keep if e(sample)

***stsplit
if "`splitat'" != "" {
	qui stsplit, at(`splitat') nopreserve
}
else if "`splitevery'" != "" {
	tempvar split
	qui stsplit `split', every(`splitevery') nopreserve
}


***rcs transformations of time
tempvar lntimev timev
gen double `lntimev' = ln((_t + _t0) / 2)
gen double `timev' = ((_t + _t0) / 2)

if `nknots' == 3 {
	_pctile `lntimev' if _d == 1, p(10 50 90)
	local myknots "`r(r1)' `r(r2)' `r(r3)'"
	mkspline __splxx = `lntimev', cubic knots(`myknots')
}
else if `nknots' == 4 {
	_pctile `lntimev' if _d == 1, p(5 35 65 95)
	local myknots "`r(r1)' `r(r2)' `r(r3)' `r(r4)'"
	mkspline __splxx = `lntimev', cubic knots(`myknots')
}
else if `nknots' == 5 {
	_pctile `lntimev' if _d == 1, p(5 27.5 50 72.5 95)
	local myknots "`r(r1)' `r(r2)' `r(r3)' `r(r4)' `r(r5)'"
	mkspline __splxx = `lntimev', cubic knots(`myknots')
}
else if `nknots' == 2 {
	gen double __splxx1 = ln(_t)
}

return scalar N_knots = r(N_knots)

*tempname retknots
*mat `retknots' = r(knots)
*mat rownames `retknots' = timescale
*return matrix knots = `retknots'

local interactions ""
local j = 1
foreach i of numlist 1/`=`nknots'-1' {
	tempvar inter`i'
	qui gen `inter`i'' = `myvarlist' * __splxx`i'
	local interactions "`interactions' `inter`i''"
	local j++
}


***test for non PH
tempname tvccox tempcox

if "`lrtest'" == "" {
	qui stcox `oldvarlist' `interactions', `oldoptions'

	qui testparm `interactions'
	local testtype "Wald"
}
else {
	qui stcox `oldvarlist', `oldoptions'
	est store `tempcox'

	qui stcox `oldvarlist' `interactions', `oldoptions'
	est store `tvccox'

	qui lrtest `tvccox' `tempcox'
	local testtype "Likelihood ratio"
}

di in gr _newline(1) "`testtype' test of proportional-hazards assumption for `myvarlist'"

di as text _newline(1) _col(5) "chi2(" r(df) ") = " as result %5.2f r(chi2)
di as text _col(5) "Prob > chi2 = " as result %6.4f chi2tail(r(df), r(chi2))
di as text _newline(1) _col(5) ///
	"Note: time scale modeled using Restricted Cubic Splines with `nknots' knots"

return scalar p = chi2tail(r(df), r(chi2))
return scalar df = r(df)
return scalar chi2 = r(chi2)

`=cond("`ic'"=="", "qui", "")' estat ic
tempname icmat
mat `icmat' = r(S)

return matrix S  = `icmat'


***graph
if "`nograph'" != "nograph" | "`saving'" != "" {
	tempvar loghr hr lo hi lb ub tag

	local mypredictnl ""
	foreach i of numlist 1/`=`nknots'-1' {
		if `i' == 1 {
			local plus ""
		}
		else {
			local plus = "+"
		}
		local brick "`plus' _b[`inter`i'']*(__splxx`i')"
		local mypredictnl "`mypredictnl' `brick'"
	}

	qui egen `tag' = tag(_t)

	qui predictnl `loghr' = _b[`myvarlist'] + `mypredictnl' ///
		if `tag', ci(`lo' `hi') level(`level')
	qui gen `hr' = exp(`loghr')
	qui gen `lb' = exp(`lo')
	qui gen `ub' = exp(`hi')
}

if "`range'" != "" {
	qui su `=cond("`lntime'"=="", "`timev'", "`lntimev'")' ///
		if inrange(`=cond("`lntime'"=="", "`timev'", "`lntimev'")', `minrange', `maxrange'), meanonly
	local tmin = r(min)
	local tmax = r(max)
}
else {
	qui su `=cond("`lntime'"=="", "`timev'", "`lntimev'")', meanonly
	local tmin = r(min)
	local tmax = r(max)
}

if "`nograph'" != "nograph" {
	tw (function y = `oldhr', range(`tmin' `tmax') lc(gs10) lw(medium) n(2)) /// /* referent line time-fixed HR */
	`=cond("`noyref'"!="", "", "(function y = 1 , range(`tmin' `tmax') lc(gs10) lw(medium) n(2) lp(_))")' /// /* referent line at HR=1 */
	(line `hr' `=cond("`noci'"=="", "`ub' `lb' ", " ")' /// /* y variables */
	 `=cond("`lntime'"=="", "`timev'", "`lntimev'")' /// /* x variable */
	 `=cond("`range'"!= "", "if inrange(`=cond("`lntime'"=="", "`timev'", "`lntimev'")', `minrange', `maxrange')", "")', /// /* range opt */
	 sort lp(l `=cond("`noci'"=="", "- -", " ")') lc(black `=cond("`noci'"=="", "black black", "")') /// /* lines options */
		lw(medthick `=cond("`noci'"=="", "medthick medthick", "")')), /// /* lines options */
	 yscale(log) legend(order(`=cond("`noyref'"=="", "3", "2")' "Time-varying Hazard Ratio" ///
	 1 "Time-fixed Hazard Ratio") rows(2)) ///
	 scheme(s1mono)  ytitle("Hazard Ratio") xtitle("Time scale") ///
	 plotregion(style(none)) ///
	 ylabel(#5 `oldhr' `=cond("`noyref'"!="", "", "1")', ///
	 angle(horiz) format(%4.2fc)) ///
	 xlabel(#7) ///
	 `gopts'
}

if "`saving'" != "" {
	ParseSaving `saving'

	qui keep if `tag'

	qui gen _hr = `hr'
	qui gen _lb_`level'ci = `lb'
	qui gen _ub_`level'ci = `ub'
	qui gen _timescale = `timev'
	if "`lntime'" != "" {
		qui gen _lntimescale = `lntimev'
	}

	label variable _hr "Hazard Ratio"
	label variable _lb_`level'ci "Lower bound `level'% CI"
	label variable _ub_`level'ci "Upper bound `level'% CI"
	label variable _timescale "Time scale"
	if "`lntime'" != "" {
		label variable _lntimescale "ln(Time scale)"
	}

	qui keep _hr _lb_* _ub_* _timescale `=cond("`lntime'"!="", "_lntimescale", "")'

	qui saveold "`s(fn)'", `s(replace)'
}
restore

qui est restore `oldcox'
end


capture program drop ParseSaving
program define ParseSaving, sclass
        * fn[,replace]

        sret clear
        if `"`0'"' == "" {
                exit
        }
        gettoken fn      0 : 0, parse(",")
        gettoken comma   0 : 0
        if length("`comma'") > 1 {
		local 0 = substr("`comma'",2,.) + "`0'"
 		local comma = substr("`comma'", 1,1)
	}
        gettoken replace 0 : 0

	local fn = trim(`"`fn'"')
        local 0 = trim(`"`0'"')
        if `"`fn'"'!="" & `"`0'"'=="" {
                if `"`comma'"'=="" | (`"`comma'"'=="," & `"`replace'"'=="") {
                        sret local fn `"`fn'"'
                        exit
                }
                if `"`comma'"'=="," & `"`replace'"'=="replace" {
                        sret local fn `"`fn'"'
                        sret local replace "replace"
                        exit
                }
        }
        di as err "option saving() misspecified"
        exit 198
end
exit