*! version 1.1.4 24may2013 /* History 24may2013: version 1.1.4 - bug in plot option corrected (the idea in the old lines 103-108 of version 1.1.2 is recovered in the new lines 132-141) - more intensive use of the abbreviations - parentheses in the conditions of 'if' and 'while' removed (not in the mata part) 08apr2013: version 1.1.3 - beta option added - theta option added - print function modified - lines 103-108 of version 1.1.2 (calling tempvar, mat coln and svmat in relation with boundalpha) deleted 16feb2013: version 1.1.2 - when computing asymmetric boundaries, different alpha spending functions may be used on each side - rho may be a numlist of two numbers; now there is no default value for rho - internally, methods are recoded in methodmat: obf -> 1, poc -> 2, pow -> 3 - tempvar used for creating the names of variables derived from boundalpha matrix - tempnames are not necessary for matrices; they are not used anymore - names of t and alpha columns of boundalpha matrix substituted with time and cumalpha - plot option added - in print function, "boundaries" instead of "bounds" and other minor changes - changes in the values returned in r() 07feb2013: version 1.1.1 */ pr landemets, rclass version 12.1 syntax [, /// t(numlist sort >0 <=1) /// interim monitoring times Alpha(numlist >0 <1 max=2) /// type I error(s) Method(string) /// alpha spending function(s) Rho(numlist >0 max=2) /// parameter(s) of alpha spending function(s) in the power family Onesided /// compute one-sided boundaries (default is twosided boundaries) Beta(numlist sort >0 <1 max=5) /// type II error THeta(numlist sort >0 max=5) /// standardized treatment difference Plot /// plot the boundaries ] capt mata mata which mm_root() if _rc { di as err "mm_root() from -moremata- is required; type -ssc install moremata- to install it" exit 499 } if "`t'" == "" loc t "1" loc dimt: word count `t' mat tmat = J(`dimt', 1, .) loc i 1 foreach val of loc t { mat tmat[`i', 1] = `val' loc ++i } if "`alpha'" == "" loc alpha "0.05" loc dimalpha: word count `alpha' mat alphamat = J(`dimalpha', 1, .) loc i 1 foreach val of loc alpha { mat alphamat[`i', 1] = `val' loc ++i } if "`method'" == "" loc method "obf" loc dimmethod: word count `method' mat methodmat = J(`dimmethod', 1, .) loc i 1 foreach val of loc method { if "`val'" == "obf" mat methodmat[`i', 1] = 1 else { if "`val'" == "poc" mat methodmat[`i', 1] = 2 else { if "`val'" == "pow" mat methodmat[`i', 1] = 3 } } loc ++i } loc dimrho: word count `rho' mat rhomat = J(`dimmethod', 1, .) loc i 1 loc found 0 foreach val of loc rho { while `i' <= `dimmethod' & !`found' { if methodmat[`i', 1] == 3 { mat rhomat[`i', 1] = `val' loc ++found } loc ++i } if `found' loc --found } if `dimalpha' != `dimmethod' { di as err "alpha and method options do not have the same length" exit 198 } if (`dimmethod' == 1 & methodmat[1, 1] == .) | (`dimmethod' == 2 & (methodmat[1, 1] == . | methodmat[2, 1] == .)) { di as err "Unknown alpha spending function or incorrect syntax. Valid values:" _newline "a) obf (for O'Brien-Fleming type), poc (for Pocock type), pow (for power family) or" _newline "b) any two of the values in a) separated by one or more blank spaces" exit 198 } if (`dimmethod' == 1 & methodmat[1, 1] == 3 & rhomat[1, 1] == .) | (`dimmethod' == 2 & ((methodmat[1, 1] == 3 & rhomat[1, 1] == .) | (methodmat[2, 1] == 3 & rhomat[2, 1] == .))) { di as err "rho parameter(s) not entered" exit 198 } if (`dimmethod' == 1 & methodmat[1, 1] != 3 & `dimrho' == 1) | /// (`dimmethod' == 2 & (methodmat[1, 1] != 3 | methodmat[2, 1] != 3) & `dimrho' == 2) | /// (`dimmethod' == 2 & methodmat[1, 1] != 3 & methodmat[2, 1] != 3 & `dimrho' == 1) { di as err "unused rho parameter(s)" exit 198 } loc bilateral = 1 if "`onesided'" != "" loc bilateral = 0 if `bilateral' == 0 & `dimalpha' == 2 { di as err "onesided option used with alpha of length 2 (which implies two-sided boundaries)" exit 198 } loc dimbeta: word count `beta' loc dimtheta: word count `theta' if `dimbeta' > 0 & `dimtheta' > 0 { di as err "only one of the beta and theta options can be used at the same time" exit 198 } if `dimbeta' > 0 mat betamat = J(`dimbeta', 1, .) else mat betamat = (.) loc i 1 foreach val of loc beta { mat betamat[`i', 1] = `val' loc ++i } if `dimtheta' > 0 mat thetamat = J(`dimtheta', 1, .) else mat thetamat = (.) loc i 1 foreach val of loc theta { mat thetamat[`i', 1] = `val' loc ++i } mata: landemetsmata("alphamat", `dimalpha', "methodmat", "rhomat", "tmat", `dimt', `bilateral', "betamat", `dimbeta', "thetamat", `dimtheta') if "`plot'" != "" { if `bilateral' == 1 { mat boundalphaplot = boundalpha[1..., 1..3] tempvar time lower upper mat coln boundalphaplot = `time' `lower' `upper' } else { mat boundalphaplot = boundalpha[1..., 1..2] tempvar time upper mat coln boundalphaplot = `time' `upper' } qui svmat boundalphaplot, names(col) loc limsup = ceil(`upper'[1]) if `dimalpha' == 1 { loc methodtitle = methodlong loc alphatitle "`alpha'" if `bilateral' { loc liminf = -`limsup' graph twoway connected `upper' `lower' `time', mcolor(red red) lcolor(blue blue) legend(off) /// xtitle("Time") xlabel(`t') ytitle("{it:Z}-value") ylabel(`liminf'(1)`limsup') /// title("Alpha spending function: `methodtitle'" /// "{it:{&alpha}} = `alphatitle'.", size(*0.8)) } else { loc liminf = 0 graph twoway connected `upper' `time', mcolor(red) lcolor(blue) legend(off) /// xtitle("Time") xlabel(`t') ytitle("{it:Z}-value") ylabel(`liminf'(1)`limsup') /// title("Alpha spending function: `methodtitle'" /// "{it:{&alpha}} = `alphatitle'.", size(*0.8)) } } else { loc liminf = floor(`lower'[1]) loc methodlowertitle = methodlonglower loc methoduppertitle = methodlongupper loc alphalowertitle = alphamat[1, 1] loc alphauppertitle = alphamat[2, 1] graph twoway connected `upper' `lower' `time', mcolor(red red) lcolor(blue blue) legend(off) /// xtitle("Time") xlabel(`t') ytitle("{it:Z}-value") ylabel(`liminf'(1)`limsup') /// title("Alpha spending function: lower, `methodlowertitle'; upper, `methoduppertitle'" /// "{it:{&alpha}}: lower, `alphalowertitle'; upper, `alphauppertitle'", size(*0.7)) } } ret sca K = `dimt' if `dimbeta' > 0 | `dimtheta' > 0 { mat coln betatheta = beta theta ret mat beta_theta betatheta } if `bilateral' == 1 mat coln boundalpha = time lower upper cumalpha diffalpha else mat coln boundalpha = time upper cumalpha diffalpha ret mat bound_alpha boundalpha if `dimalpha' == 1 { ret loc method = methodlong ret sca alpha = `alpha' } else { ret loc method_lower = methodlonglower ret loc method_upper = methodlongupper ret sca alpha_lower = alphamat[1, 1] ret sca alpha_upper = alphamat[2, 1] } ret loc bound_type = boundtype end version 12 mata: function landemetsmata(alphamat, dimalpha, methodmat, rhomat, tmat, dimt, bilateral, betamat, dimbeta, thetamat, dimtheta) { n = 251 symorone = 2 - dimalpha t = st_matrix(tmat) alpha = st_matrix(alphamat) methodmat = st_matrix(methodmat) rhomat = st_matrix(rhomat) betamat = st_matrix(betamat) thetamat = st_matrix(thetamat) cs = J(dimt, dimalpha, .) if (symorone) { alphat = alphafun(t, alpha, methodmat, rhomat, bilateral, symorone) find_c(n, alphat, t, dimt, cs, bilateral, symorone) } else { alphatlow = alphafun(t, alpha[1], methodmat[1], rhomat[1], bilateral, symorone) alphatupp = alphafun(t, alpha[2], methodmat[2], rhomat[2], bilateral, symorone) alphatasym = (alphatlow, alphatupp) alphat = alphatasym[., 1] + alphatasym[., 2] find_c(n, alphatasym, t, dimt, cs, bilateral, symorone) } diffalphat = J(dimt, 1, alphat[1]) for (i = 2; i <= dimt; i++) { diffalphat[i] = alphat[i] - alphat[i-1] } if (bilateral & symorone) resultmat = (t, -cs, cs, alphat, diffalphat) else resultmat = (t, cs, alphat, diffalphat) st_matrix("boundalpha", resultmat) bound = bound_name(bilateral, symorone) st_strscalar("boundtype", bound) if (dimalpha == 1) st_strscalar("methodlong", methodn2name(methodmat, rhomat)) else { st_strscalar("methodlonglower", methodn2name(methodmat[1], rhomat[1])) st_strscalar("methodlongupper", methodn2name(methodmat[2], rhomat[2])) } betatheta = J(max((dimbeta, dimtheta)), 2, .) if (dimbeta > 0) { betatheta[., 1] = betamat for (i = 1; i <= dimbeta; i++) { betatheta[i, 2] = find_betatheta(n, betamat[i], thetamat, t, dimt, cs :* sqrt(t), bilateral, symorone) } } else if (dimtheta > 0) { betatheta[., 2] = thetamat for (i = 1; i <= dimtheta; i++) { betatheta[i, 1] = find_betatheta(n, betamat, thetamat[i], t, dimt, cs :* sqrt(t), bilateral, symorone) } } st_matrix("betatheta", betatheta) print(bound, alpha, dimalpha, t, dimt, cs, alphat, diffalphat, methodmat, rhomat, bilateral, betatheta, dimbeta, dimtheta) } function alphafun(t, alpha, methodmat, rhomat, bilateral, symorone) { if (methodmat == 1) return(((1 + bilateral*symorone) * 2) :* (1 :- normal(invnormal(1 - alpha / 2 / (1 + bilateral*symorone)) :/ sqrt(t)))) // for two-sided symorone bounds one must multiply and divide by 2 else { if (methodmat == 2) return(alpha :* log(1 :+ (exp(1) :-1) :* t)) else { if (methodmat == 3) return(alpha :* t:^rhomat) } } } function find_c(n, alphat, t, dimt, cs, bilateral, symorone) { if (symorone) { cs[1] = sqrt(t[1]) * invnormal(1 - alphat[1] / (1 + bilateral)) if(cs[1] >= .) cs[1] = 8.209536152 * sqrt(t[1]) // 8.209536152 is apparently the largest value returned by invnormal() if (bilateral) x = rangen(-cs[1], cs[1], n) else x = rangen(-8.209536152, cs[1], n) } else { cs[1, 1] = sqrt(t[1]) * invnormal(alphat[1, 1]) if(-cs[1, 1] >= .) cs[1, 1] = -8.209536152 * sqrt(t[1]) cs[1, 2] = sqrt(t[1]) * invnormal(1- alphat[1, 2]) if(cs[1, 2] >= .) cs[1, 2] = 8.209536152 * sqrt(t[1]) x = rangen(cs[1, 1], cs[1, 2], n) } y = normalden(x, 0, sqrt(t[1])) ynext = J(n, 1, .) for (i = 2; i <= dimt; i++) { if(symorone) { rc = mm_root(z = ., &objective_c2(), 0.001, 10, 0, 1000, n, alphat[i] - alphat[i-1], sqrt(t[i] - t[i-1]), x, y, cs[i-1], bilateral, symorone) if (rc != 0) z = . cs[i] = z } else { rc = mm_root(z = ., &objective_c2(), -10, -0.001, 0, 1000, n, alphat[i, 1] - alphat[i-1, 1], sqrt(t[i] - t[i-1]), x, y, cs[i-1, 2] - cs[i-1, 1], bilateral, symorone) if (rc != 0) z = . cs[i, 1] = z rc = mm_root(z = ., &objective_c2(), 0.001, 10, 0, 1000, n, alphat[i, 2] - alphat[i-1, 2], sqrt(t[i] - t[i-1]), x, y, cs[i-1, 2] - cs[i-1, 1], bilateral, symorone) if (rc != 0) z = . cs[i, 2] = z } if (symorone) { if (bilateral) { xnext = rangen(-cs[i], cs[i], n) step = 2 * cs[i-1] / (n - 1) } else { xnext = rangen(-8.209536152, cs[i], n) step = (cs[i-1] - (-8.209536152)) / (n - 1) } } else { xnext = rangen(cs[i, 1], cs[i, 2], n) step = (cs[i - 1, 2] - cs[i - 1, 1]) / (n - 1) } for (j = 1; j <= n; j++) { ynext[j] = simpson(y :* normalden(xnext[j] :- x, 0, sqrt(t[i] - t[i-1])), n, step) } x = xnext y = ynext } cs = cs :/ sqrt(t) } function objective_c2(c, n, alpha, t, x, y, cprev, bilateral, symorone) { if(c < 0) integrand = y :* normal((c :- x) :/ t) else integrand = y :* (1 :- normal((c :- x) :/ t)) if (bilateral) step = (1 + symorone) * cprev / (n - 1) else step = (cprev - (-8.209536152)) / (n - 1) return((1 + bilateral * symorone) * simpson(integrand, n, step) - alpha) } function simpson(y, n, step) { temp = y[1] - y[n]; for (i = 1; i <= (n-1)/2; i++) { temp = temp + 4 * y[2*i] + 2 * y[2*i + 1] } return(step * temp / 3) } function bound_name(bilateral, symorone) { if (bilateral) { if (symorone) return("Two-sided") else return("Asymmetric") } else return("One-sided") } function find_betatheta(n, beta, theta, t, dimt, cs, bilateral, symorone){ rc = mm_root(z = ., &objective_betatheta(), 0.001, 10, 0, 1000, n, beta, theta, t, dimt, cs, bilateral, symorone) if (rc != 0) z = . return(z) } function objective_betatheta(v, n, beta, theta, t, dimt, cs, bilateral, symorone){ if (beta < .) { v1 = v v2 = beta } else if (theta < .) { v1 = theta v2 = v } if (symorone) { if (bilateral) x = rangen(-cs[1], cs[1], n) else x = rangen(-8.209536152 * sqrt(t[1]), cs[1], n) y = normalden(x :- (v1 * t[1]), 0, sqrt(t[1])) ynext = J(n, 1, .) for (i = 2; i <= dimt; i++) { if (bilateral){ xnext = rangen(-cs[i], cs[i], n) step = 2 * cs[i-1] / (n - 1) } else { xnext = rangen(-8.209536152 * sqrt(t[i] - t[i-1]), cs[i], n) // pensar si esta bien multiplicado por sqrt() step = (cs[i-1] - (-8.209536152 * sqrt(t[i] - t[i-1])))/ (n - 1) } for (j = 1; j <= n; j++) { ynext[j] = simpson(y :* normalden(xnext[j] :- x :- (v1 * (t[i] - t[i-1])), 0, sqrt(t[i] - t[i-1])), n, step) } x = xnext y = ynext } if (bilateral) step = 2 * cs[dimt] / (n - 1) else step = (cs[dimt] - (-8.209536152 * sqrt(t[dimt] - t[dimt-1]))) / (n - 1) } else { x = rangen(cs[1, 1], cs[1, 2], n) y = normalden(x :- (v1 * t[1]), 0, sqrt(t[1])) ynext = J(n, 1, .) for (i = 2; i <= dimt; i++) { xnext = rangen(cs[i, 1], cs[i, 2], n) step = (cs[i-1, 2] - cs[i-1, 1]) / (n - 1) for (j = 1; j <= n; j++) { ynext[j] = simpson(y :* normalden(xnext[j] :- x :- (v1 * (t[i] - t[i-1])), 0, sqrt(t[i] - t[i-1])), n, step) } x = xnext y = ynext } step = (cs[dimt, 2] - cs[dimt, 1]) / (n - 1) } return(simpson(y, n, step) - v2) } function print(bound, alpha, dimalpha, t, dimt, cs, alphat, diffalphat, methodmat, rhomat, bilateral, betatheta, dimbeta, dimtheta) { printf("\n") printf("{res}{space 1}%s {txt}boundaries\n", bound) printf("\n") if (dimalpha == 1) { printf("{txt}{space 1}Alpha spending function: {res}%10s \n", methodn2name(methodmat, rhomat)) printf("\n") printf("{txt}{space 1}Alpha: {res}%5.4g \n", alpha[1]) } else { printf("{txt}{space 1}Alpha spending function:\n") printf("{txt}{space 4}Lower boundary: {res}%s \n", methodn2name(methodmat[1], rhomat[1])) printf("{txt}{space 4}Upper boundary: {res}%s \n", methodn2name(methodmat[2], rhomat[2])) printf("\n") printf("{txt}{space 1}Alpha:\n") printf("{txt}{space 4}Lower boundary: {res}%5.4g \n", alpha[1]) printf("{txt}{space 4}Upper boundary: {res}%5.4g \n", alpha[2]) } printf("\n") printf("{txt}{space 1}Number of interim analyses: {res}%2.0f \n", dimt) printf("\n") if (bilateral) { printf("{space 1}{hline 70}\n") printf("{txt}{space 18}boundaries \n") printf("{space 14}{hline 18}\n") printf("{space 2}i{space 5}t_i{space 5}lower{space 5}upper{space 4}alpha(t_i){space 2}alpha(t_i)-alpha(t_i-1)\n") printf("{space 1}{hline 70}\n") if (dimalpha == 1) { for (i = 1; i <= dimt; i++) { printf("{res}{space 1}%2.0f %7.4g %9.5g %9.5g %13.4g %24.4g \n", i, t[i], -cs[i], cs[i], alphat[i], diffalphat[i]) } } else { for (i = 1; i <= dimt; i++) { printf("{res}{space 1}%2.0f %7.4g %9.5g %9.5g %13.4g %24.4g \n", i, t[i], cs[i, 1], cs[i, 2], alphat[i], diffalphat[i]) } } printf("{space 1}{hline 70}\n") } else { printf("{space 1}{hline 67}\n") printf("{txt}{space 2}i{space 5}t_i{space 3}upper boundary{space 4}alpha(t_i){space 2}alpha(t_i)-alpha(t_i-1)\n") printf("{space 1}{hline 67}\n") for (i = 1; i <= dimt; i++) { printf("{res}{space 1}%2.0f %7.4g %16.5g %13.4g %24.4g \n", i, t[i], cs[i], alphat[i], diffalphat[i]) } printf("{space 1}{hline 67}\n") } if (dimbeta > 0 | dimtheta > 0) { printf("\n{space 1}{hline 17}\n") if (dimbeta > 0) { printf("{txt}{space 2}beta{space 6}theta\n") printf("{space 1}{hline 17}\n") for (i = 1; i <= dimbeta; i++) { printf("{res}{space 1}%5.4g %10.5g \n", betatheta[i,1], betatheta[i,2]) } } else { printf("{txt}{space 2}theta{space 6}beta\n") printf("{space 1}{hline 17}\n") for (i = 1; i <= dimtheta; i++) { printf("{res}{space 1}%5.4g %10.5g \n", betatheta[i,2], betatheta[i,1]) } } printf("{space 1}{hline 17}\n") } } function methodn2name(methodn, rhomat) { if (methodn == 1) return("O'Brien-Fleming type") else { if (methodn == 2) return("Pocock type") else { if (methodn == 3) return(invtokens(("power family (rho ", strofreal(rhomat), ")"), "")) } } } end