*! version 1.0.4 PR 05jul2013.
program define artpep, rclass
version 10.0
/*
	pts() = list of numbers of patients accrued up to end of current period, P;
		length of this list defines P
	epts() = list of numbers of patients expected to be accrued in periods P+1, P+2, ...
	eperiods() = number of periods to project over, starting at P+1
	stoprecruit() = period number at end of which recruitment is to stop. If given as 0
			(the default), continue recruiting according to schedule in epts()
	startperiod() = period to start forward projections from.
			Default startperiod is final period defined by pts().
	* (other options) = options for artsurv.
	n() (if specified) = number of patients at end of recruitment to current period.
		This makes recruitment figures into weights rather than absolute numbers of patients.
	tunit() = time units, essentially as in -artsurv-
*/

syntax [using/], [ PTS(numlist) EPTs(numlist) EPEriods(int 1) N(integer 0) REPLACE ///
 STArtperiod(int 0) STOPrecruit(int 0) TUnit(int 1) DATEstart(string) * ]

if "`datestart'" != "" {
	// `datestart' corresponds to the first calendar day of the first period
	local sdate = date("`datestart'", "DMY")
	if missing(`sdate') {
		di as err "invalid date, `datestart'"
		exit 198
	}
	if `tunit' == 7 {
		di as err "calendar dates not applicable to tunit(7)"
		exit 198
	}
	// Lengths in days of the various time units
	local length1 365.25
	local length2 = `length1' / 2
	local length3 = `length1' / 4
	local length4 = `length1' / 12
	local length5 = `length1' / 52
	local length6 1
}

// Check for $S_ARTPEP in command, if so display whole command
capture local result: subinstr local 0 "$S_ARTPEP" "", count(local nat)
if _rc==0 {
	if `nat'>0 noisily window push artpep `0'
}

if "`pts'"=="" {
	di as err "option pts() required"
	exit 198
}
if `tunit'<1 | `tunit'>7 {
	di as err "Invalid time-unit code"
	exit 198
}
tokenize `""year" "6 months" "quarter" "month" "week" "day" "period""'
local lperiod ``tunit''

* pts is a numlist of numbers of patients recruited.
* It also defines the number of recruitment periods.
local recperiod: word count `pts'
local endperiod=`recperiod'+`eperiods'

if `startperiod'>0 {
	if `startperiod'>`recperiod' {
		di as err "startperiod() must be less than or equal to the number of periods implied by pts()"
		exit 198
	}
}
else local startperiod 0
if `stoprecruit'!=0 {
	if `stoprecruit'<`recperiod' | `stoprecruit'>`endperiod' {
		di as err "invalid stoprecruit(), must be between `recperiod' and `endperiod'"
		exit 198
	}
}
else local stoprecruit `endperiod'

if `"`using'"'!="" {
	if substr(`"`using'"',-4,.)!=".dta" {
		local using `using'.dta
	}
	if "`replace'"=="" {
		confirm new file `"`using'"'
	}
	tempname tmp
	local postvl period patients ctrl_events tot_events power
	if "`datestart'" != "" local postvl date `postvl'
	postfile `tmp' `postvl' using `using', `replace'
}

* Compute numpats = total #pts recruited from period 1 to recperiod inclusive
local numpats 0
tokenize `pts'
while "`1'"!="" {
	local numpats=`numpats'+`1'
	mac shift
}

/*
	Force sample size at end of recruitment to n if `n' given.
	Recruitment values given by pts() are then weights, not absolute numbers.
	Future recruitment values are then suitably scaled.
	UNDER CONSTRUCTION.
*/
if `n'>0 {
	local factor=`n'/`numpats'
}
else local factor 1

* epts is the expected recruitment from now on.
* If not given, it is taken as the current mean recruitment rate per period, rounded to nearest 1 patient.
if "`epts'"=="" {
	local rate=round(`numpats'/`recperiod', 1)
	di as text "[assuming projected rate of recruitment = " `rate' " patients per `lperiod']"
	* Project accrual forward up to `eperiods' periods
	tokenize `pts'
	while "`1'"!="" {
		local epts `epts' `rate'
		mac shift
	}
}
else {
	* Prepare string of expected patient accruals, replicating final # if it is too short
	local nepts: word count `epts'
	if `nepts'<`eperiods' {
		local lastept: word `nepts' of `epts'
		local j1=`nepts'+1
		forvalues j=`j1'/`eperiods' {
			local pt: word `j' of `pts'
			local epts `epts' `lastept'
		}
	}
}
local hcol 41
local hcol2 = `hcol' + 2
if "`datestart'" != "" {
	di as text _n "Date      {c |} " %8s "`lperiod'" " {c |}    #pats" _col(36) "#C-events" ///
	 _col(48) "#events" _col(58) "Power" _n "{hline 10}{c +}{hline 10}{c +}{hline `hcol'}"
}
else {
	di as text _n %8s "`lperiod'" " {c |}    #pats" _col(26) "#C-events" ///
	 _col(38) "#events" _col(48) "Power" _n "{hline 9}{c +}{hline `hcol2'}"
}
if `startperiod'>0 {
	* Compute and store #pts recruited from 1 to `startperiod'
	local numearlypats 0
	local earlypts
	local i 1
	tokenize `pts'
	while `i'<=`startperiod' {
		local numearlypats=`numearlypats'+``i''
		local earlypts `earlypts' ``i''
		local ++i
	}
	* compute power and #events from `startperiod' to `recperiod'-1
	local finish=`recperiod'-1
	local nall `numearlypats'
	local morepts
	forvalues j=`startperiod'/`finish' {
		* Force sample size at end of recruitment to n if `n' given.
		if `n'>0 {
			local nuse=round(`nall'*`n'/`numpats', 1)
		}
		else local nuse `nall'
		if "`datestart'" != "" {
			// compute date of end this period
			local edate = `sdate' + `j' * `length`tunit'' - 1 // automatically truncates fractions of a day
		}
		if `nuse'>0 {
			qui artsurv, recrt(`j' 0, `earlypts' `morepts', 0 ) nperiod(`j') n(`nuse') `options'
			local events = r(events)	// total number of events
			local Events: word 1 of `r(Events)' // control arm (group 1) events
			// strip trailing comma
			local c = strpos("`Events'", ",")
			if `c' > 0 local Events = substr("`Events'", 1, `c' - 1)
			if "`datestart'" != "" {
				di as text %td `edate' " {c |} " %6.0f `j' "   {c |}" as res %8.0f `nuse' ///
				 _col(33) %8.0f `Events' _col(45) %8.0f `events' _col(57) %7.5f r(power)
			}
			else di as text %6.0f `j' "   {c |}"  as res %8.0f `nuse' ///
			 _col(23) %8.0f `Events' _col(35) %8.0f `events' _col(47) %7.5f r(power)
			if `"`using'"'!="" {
				if "`datestart'" != "" post `tmp' (`edate') (`j') (`nuse') (`Events') (`events') (r(power))
				else post `tmp' (`j') (`nuse') (`Events') (`events') (r(power))
			}
		}
		else {
			if "`datestart'" != "" di as text %td `edate' " {c |} " %6.0f `j' "   {c |} (no patients)"
			else di as text %6.0f `j' "   {c |} (no patients)"
			if `"`using'"'!="" {
				if "`datestart'" != "" post `tmp' (`edate') (`j') (`nuse') (.) (.) (.)
				else post `tmp' (`j') (`nuse') (.) (.) (.)
			}
		}
		local i=`j'+1
		local pt: word `i' of `pts'
		local nall=`nall'+`pt'
		local morepts `morepts' `pt'
	}
	if `finish' >= `startperiod' {
		if "`datestart'" != "" di as text "{hline 10}{c +}{hline 10}{c +}{hline `hcol'}"
		else di as text "{hline 9}{c +}{hline `hcol2'}"
	}
}
* compute power and #events from `recperiod' to `recperiod'+`eperiods'
local accwt
local nperiod `recperiod'
local rperiod `recperiod'
local nall `numpats'
local i 0
local morepts
forvalues j=`recperiod'/`endperiod' {
	if `n'>0 {
		local nuse=round(`nall'*`n'/`numpats', 1)
	}
	else local nuse `nall'
	if "`datestart'" != "" {
		// compute date of end this period
		local edate = `sdate' + `j' * `length`tunit'' - 1
	}
	if `nuse'>0 {
		qui artsurv, recrt(`rperiod' 0, `pts' `morepts', 0 ) nperiod(`nperiod') n(`nuse') `options'
		local events = r(events)
		local Events: word 1 of `r(Events)' // control arm (group 1) events
		local c = strpos("`Events'", ",")
		if `c' > 0 local Events = substr("`Events'", 1, `c' - 1)
		if "`datestart'" != "" {
			di as text %td `edate' " {c |} " %6.0f `nperiod' "   {c |}" as res %8.0f `nuse' ///
			 _col(33) %8.0f `Events' _col(45) %8.0f `events' _col(57) %7.5f r(power)
		}
		else di as text %6.0f `nperiod' "   {c |}" as res %8.0f `nuse' ///
		 _col(23) %8.0f `Events' _col(35) %8.0f `events' _col(47) %7.5f r(power)
		if `"`using'"'!="" {
			if "`datestart'" != "" post `tmp' (`edate') (`nperiod') (`nuse') (`Events') (`events') (r(power))
			else post `tmp' (`nperiod') (`nuse') (`Events') (`events') (r(power))
		}
	}
	else {
		if "`datestart'" != "" di as text %td `edate' " {c |} " %6.0f `j' "   {c |} (no patients)"
		else di as text %6.0f `j' "   {c |} (no patients)"
		if `"`using'"'!="" {
			if "`datestart'" != "" post `tmp' (`edate') (`j') (`nuse') (.) (.) (.)
			else post `tmp' (`j') (`nuse') (.) (.) (.)
		}
	}
	if `j'==`recperiod' {
		if "`datestart'" != "" di as text "{hline 10}{c +}{hline 10}{c +}{hline `hcol'}"
		else di as text "{hline 9}{c +}{hline `hcol2'}"
	}
	local ++nperiod
	if `nperiod'<=`stoprecruit' {
		* Patients continue to be recruited
		local ++rperiod
		local ++i
		local ept: word `i' of `epts'
		local nall=`nall'+`ept'
		local morepts `morepts' `ept'
	}
}
di
if `"`using'"'!="" {
	postclose `tmp'
	preserve
	use `"`using'"', replace
	if "`datestart'" != "" {
		label var date "date of end of period"
		format date %td
	}
	label var period "`lperiod' period"
	label var patients "Cumulative patients"
	label var ctrl_events "Cumulative control-arm events"
	label var tot_events "Cumulative total events"
	label var power "Power"
	save `"`using'"', replace
	restore
}
end
exit
History
1.0.4	05jul2013	Fixed mislabelling in using file.
					Improved names of variables stored in using file.
1.0.3	19oct2009	Added datestart() option.
					Upped version to 10.0.
					Updated help file.
					Output control arm and total events.
1.0.2	14oct2006	Push command line which includes "$S_ARTPEP" to the Review window.
					This expands $S_ARTPEP.
1.0.1	---------	Fixed wordcount() bug which was truncating some macros at 80 chars