*! Author(s) : Michael J Grayling *! Date : 31 Jan 2019 *! Version : 0.9 /* Version history: 31/01/19 v0.9 Initial version complete. */ /* To do: - Add option for plotting. */ program define sim_ma, rclass version 15.0 syntax , [n(integer 68) tau(numlist) sd(real 1) RATio(real 1) /// ALPha(real 0.05) CORrection(string) REPlicates(integer 100000)] preserve ///// Check input variables //////////////////////////////////////////////////// local lentau:list sizeof tau if ("`correction'" == "") { local correction "dunnett" } if (`n' < 1) { di "{error}The group size in the control arm (n) must be an integer greater " di "than or equal to 1." exit(198) } if (`sd' <= 0) { di "{error}The true value of the standard deviation of the responses (sd) " di "must be a real strictly greater than 0." exit(198) } if (`ratio' <= 0) { di "{error}The allocation ratio between the experimental and control arms " di "(ratio) must be a real strictly greater than 0." exit(198) } else if (mod(`ratio'*`n', 1) != 0) { di "{error}The product of the allocation ratio between the experimental and " di "control arms (ratio) and the stage-wise group size in the control arm (n)" di " must be an integer greater than or equal to 1." exit(198) } if (("`correction'" != "benjamini") & ("`correction'" != "bonferroni") & /// ("`correction'" != "dunnett") & ("`correction'" != "holm") & /// ("`correction'" != "none") & ("`correction'" != "sidak") & /// ("`correction'" != "step_down_dunnett")) { di "{error}The multiple comparison correction must be one of benjamini, " di "bonferroni, dunnett, holm, none, sidak, or step_down_dunnett." exit(198) } if ((`alpha' <= 0) | (`alpha' >= 1)) { di "{error}The desired familywise error-rate (alpha) must be a real strictly " di "between 0 and 1." exit(198) } if (`replicates' < 1) { di "{error}The number of replicate simulations to conduct (replicates) must " di "be an integer greater than or equal to 1." exit(198) } ///// Perform main computations //////////////////////////////////////////////// if (`lentau' > 0) { local matatau "" foreach l of local tau { if "`matatau'" == "" local matatau "`l'" else local matatau "`matatau',`l'" } mat tau = (`matatau') } else { mat tau = J(1, 3, 0) } mata: sim_ma_void(`n', `sd', `ratio', "`correction'", `alpha', `replicates') ///// Output /////////////////////////////////////////////////////////////////// return scalar FDR = opchar[1, 3] return scalar P_H1 = opchar[1, 2] return scalar P_HA = opchar[1, 1] restore end ///// Mata ///////////////////////////////////////////////////////////////////// mata: void sim_ma_void(real scalar n, real scalar sd, real scalar ratio, string correction, real scalar alpha, real scalar replicates) { tau = st_matrix("tau") K = length(tau) printf("{txt}{dup 57:{c -}}\n") printf("Single-stage {inp}%g{txt}-experimental treatment MA trial ", K) printf("simulation\n") printf("{dup 57:{c -}}\n") printf("The hypotheses to be tested will be:\n\n") if (K == 1) { printf("{txt} H1: τ{inp}1{txt} = μ1 - μ0 ≤ 0.\n\n") } else if (K == 2) { printf("{txt} Hk: τk = μk - μ0 ≤ 0, k = 1, {inp}2{txt}.\n\n") } else if (K == 3) { printf("{txt} Hk: τk = μk - μ0 ≤ 0, k = 1, 2, {inp}3{txt}.\n\n") } else { printf("{txt} Hk: τk = μk - μ0 ≤ 0, k = 1, ..., {inp}%g{txt}.\n\n", K) } if (correction == "none") { printf("{inp}No multiplicity correction will be applied{txt} in order to ") printf("control the familywise\nerror-rate or false discovery rate. Each ") printf("hypothesis will be tested at significance level\nα = {inp}%g{txt}.", alpha) printf("\n\n") } else if (correction == "benjamini") { printf("{txt}The false discovery-rate will be controlled to level α = ") printf("{inp}%g{txt} using the\n{inp}Benjamini-Hochberg{txt} procedure.", alpha) printf("\n\n") } else { printf("{txt}The familywise error-rate will be controlled under the global") printf(" null hypothesis,\nHG, given by:\n\n") if (K == 1) { printf("{txt} HG: τ{inp}1{txt} = 0.\n\n") } else if (K == 2) { printf("{txt} HG: τ1 = τ{inp}2{txt} = 0.\n\n") } else if (K == 3) { printf("{txt} HG: τ1 = τ2 = τ{inp}3{txt} = 0.\n\n") } else { printf("{txt} HG: τ1 = ... = τ{inp}%g{txt} = 0.\n\n", K) } printf("If P_HA() is the power function for rejecting any null hypothesis,") if (correction == "bonferroni") { printf(" the\n{inp}Bonferroni{txt} correction ") } else if (correction == "dunnett") { printf(" the\n{inp}Dunnett{txt} correction ") } else if (correction == "holm") { printf(" the\n{inp}Bonferroni-Holm{txt} correction ") } else if (correction == "sidak") { printf(" the\n{inp}Sidak{txt} correction ") } else if (correction == "step_down_dunnett") { printf(" the\n{inp}step-down Dunnett{txt} correction ") } printf("will be used to ensure that:\n\n") printf("{txt} P_HA(HG) ≤ α = {inp}%g{txt}.\n\n", alpha) } printf("The group size in the control arm will be: n = {inp}%g{txt}.\n\n", n) printf("The allocation ratio between the experimental arms and the shared ") printf("control arm,\nratio, will be: {inp}%g{txt}:1.\n\n", ratio) printf("{txt}The trial will be simulated under the scenario where:\n\n") if (K == 1) { printf("{txt} tau = tau_{inp}1{txt} = {inp}%g{txt}.\n\n", tau) } else if (K == 2) { printf("{txt} tau = (tau_1, tau_{inp}2{txt}) = {inp}(%g, %g){txt}.\n\n", tau[1], tau[2]) } else { printf("{txt} tau = (") for (k = 1; k <= K - 1; k++) { printf("{txt}tau_%g, ", k) } printf("{txt}tau_{inp}%g{txt}) = {inp}(", K) for (k = 1; k <= K - 1; k++) { printf("%g, ", tau[k]) } printf("%g){txt}.\n\n", tau[K]) } printf("Beginning the required simulations.\n\n") rho = ratio/(ratio + 1) Lambda = J(K, K, rho) + (1 - rho)*I(K) p = p_O = . if ((correction == "benjamini") | (correction == "holm")) { p_O = alpha:/(K::1) } else if (correction == "bonferroni") { p = alpha/K } else if (correction == "dunnett") { p = 1 - normal(invmvnormal_mata(1 - alpha, J(1, K, 0), Lambda, "lower", 1000000, 0.0000001, "pmvnormal", 12, 10000)[1]) } else if (correction == "none") { p = alpha } else if (correction == "sidak") { p = 1 - (1 - alpha)^(1/K) } else if (correction == "step_down_dunnett") { rho = ratio/(ratio + 1) p_O = J(K, 1, 0) for (k = 1; k <= K; k++) { p_O[k] = 1 - normal(invmvnormal_mata(1 - alpha, J(1, K - (k - 1), 0), J(K - (k - 1), K - (k - 1), rho) + (1 - rho)*I(K - (k - 1)), "lower", 100000, 0.000001, "pmvnormal", 12, 1000)[1]) } } opchar = sim_ma_mata(n, tau', sd, ratio, correction, p, p_O, K, replicates)' printf("Completed the required simulations.\n\n") printf("The estimated operating characteristics of the trial are:\n\n") printf(" r() | Variable\n") printf(" {dup 4:{c -}} {dup 8:{c -}}\n") printf(" P_HA | P_HA(tau) = {res}%g{txt},\n", round(opchar[1], .0001)) printf(" P_H1 | P_H1(tau) = {res}%g{txt},\n", round(opchar[3], .0001)) printf(" FDR | FDR(tau) = {res}%g{txt}.\n", round(opchar[2], .0001)) st_matrix("opchar", opchar) } real colvector invmvnormal_mata(real scalar p, real vector mean, real matrix Sigma, string tail, real scalar max_iter, real scalar tolerance, string integrator, real scalar shifts, real scalar samples) { if ((p == 0) | (p == 1)) { return((. \ 0 \ 0 \ 0 \ 0)) } else { if (rows(Sigma) == 1) { if (tail == "upper") { p = 1 - p } else if (tail == "both") { p = 0.5 + 0.5*p } return((invnormal(p) + mean \ 0 \ 0 \ 0 \ 0)) } else { if (tail == "both") { a = 10^-6 } else { a = -10 } b = 10 fa = invmvnormal_mata_int(a, p, mean, Sigma, tail, integrator, shifts, samples) if (fa == 0) { return((a \ 0 \ 0 \ 0 \ 0)) } fb = invmvnormal_mata_int(b, p, mean, Sigma, tail, integrator, shifts, samples) if (fb == 0) { return((b \ 0 \ 0 \ 0 \ 0)) } if (tail == "both") { while ((fa > 0) & (a >= 10^-20)) { a = a/2 fa = invmvnormal_mata_int(a, p, mean, Sigma, tail, integrator, shifts, samples) } while ((fb < 0) & (b <= 10^6)) { b = 2*b fb = invmvnormal_mata_int(b, p, mean, Sigma, tail, integrator, shifts, samples) } } else { while ((((fa > 0) & (fb > 0)) | ((fa < 0) & (fb < 0))) & (a >= -10^6)) { a = 2*a b = 2*b fa = invmvnormal_mata_int(a, p, mean, Sigma, tail, integrator, shifts, samples) fb = invmvnormal_mata_int(b, p, mean, Sigma, tail, integrator, shifts, samples) } } if (fa == 0) { return((a \ 0 \ 0 \ 0 \ 0)) } else if (fb == 0) { return((b \ 0 \ 0 \ 0 \ 0)) } else if (((fa < 0) & (fb > 0)) | ((fa > 0) & (fb < 0))) { half_tol = 0.5*tolerance c = b fc = fb for (iter = 1; iter <= max_iter; iter++) { if (((fb > 0) & (fc > 0)) | ((fb < 0) & (fc < 0))) { c = a fc = fa d = b - a e = d } if (abs(fc) < abs(fb)) { a = b b = c c = a fa = fb fb = fc fc = fa } tol1 = 6e-8*abs(b) + half_tol xm = 0.5*(c - b) if ((abs(xm) <= tol1) | (fb == 0)) { return((b \ abs(0.5*(b - a)) \ 0 \ fb \ iter)) } if ((abs(e) >= tol1) & (abs(fa) > abs(fb))) { s = fb/fa if (a == c) { pi = 2*xm*s q = 1 - s } else { q = fa/fc r = fb/fc pi = s*(2*xm*q*(q - r) - (b - a)*(r - 1)) q = (q - 1)*(r - 1)*(s - 1) } if (pi > 0) { q = -q } pi = abs(pi) if (2*pi < min((3*xm*q - abs(tol1*q), abs(e*q)))) { e = d d = pi/q } else { e = d = xm } } else { e = d = xm } a = b fa = fb if (abs(d) > tol1) { b = b + d } else { b = b + tol1*sign(xm) } fb = invmvnormal_mata_int(b, p, mean, Sigma, tail, integrator, shifts, samples) } return((b \ abs(0.5*(b - a)) \ 1 \ fb \ iter)) } else { return((. \ . \ 2 \ . \ .)) } } } } real scalar invmvnormal_mata_int(real scalar q, real scalar p, real vector mean, real matrix Sigma, string tail, string integrator, real scalar shifts, real scalar samples) { k = rows(Sigma) if (tail == "lower") { a = J(1, k, .) b = J(1, k, q) } else if (tail == "upper") { a = J(1, k, q) b = J(1, k, .) } else { a = J(1, k, -q) b = J(1, k, q) } if (integrator != "mvnormal") { return(pmvnormal_mata(a, b, mean, Sigma, shifts, samples, 3)[1] - p) } else { for (i = 1; i <= k; i++) { if (a[i] == .) { a[i] = -8e307 } if (b[i] == .) { b[i] = 8e307 } } return(mvnormalcv(a, b, mean, vech(Sigma)') - p) } } real colvector pmvnormal_mata(real vector lower, real vector upper, real vector mean, real matrix Sigma, real scalar shifts, real scalar samples, real scalar alpha) { k = rows(Sigma) if (k == 1) { if ((lower == .) & (upper == .)) { I = 1 } else if ((lower != .) & (upper == .)) { I = 1 - normal((lower - mean)/sqrt(Sigma)) } else if ((lower == .) & (upper != .)) { I = normal((upper - mean)/sqrt(Sigma)) } else { sqrt_Sigma = sqrt(Sigma) I = normal((upper - mean)/sqrt_Sigma) - normal((lower - mean)/sqrt_Sigma) } E = 0 } else if (k == 2) { if (lower[1] == .) lower[1] = -8e307 if (lower[2] == .) lower[2] = -8e307 if (upper[1] == .) upper[1] = 8e307 if (upper[2] == .) upper[2] = 8e307 sqrt_Sigma = sqrt((Sigma[1, 1], Sigma[2, 2])) a = (lower - mean):/(sqrt_Sigma[1], sqrt_Sigma[2]) b = (upper - mean):/(sqrt_Sigma[1], sqrt_Sigma[2]) r = Sigma[1, 2]/(sqrt_Sigma[1]*sqrt_Sigma[2]) I = binormal(b[1], b[2], r) + binormal(a[1], a[2], r) - binormal(b[1], a[2], r) - binormal(a[1], b[2], r) E = 0 } else { a = lower - mean b = upper - mean C = J(k, k, 0) zero_k_min_2 = J(1, k - 2, 0) zero_k_min_1 = (zero_k_min_2, 0) y = zero_k_min_1 atilde = (a[1], zero_k_min_2) btilde = (b[1], zero_k_min_2) sqrt_Sigma11 = sqrt(Sigma[1, 1]) args = J(1, k, 1) for (j = 1; j <= k; j++) { if ((a[j] != .) & (b[j] != .)) { args[j] = normal(b[j]/sqrt_Sigma11) - normal(a[j]/sqrt_Sigma11) } else if ((a[j] == .) & (b[j] != .)) { args[j] = normal(b[j]/sqrt_Sigma11) } else if ((b[j] == .) & (a[j] != .)) { args[j] = 1 - normal(a[j]/sqrt_Sigma11) } } ii = ww = . minindex(args, 1, ii, ww) if (ii[1] != 1) { tempa = a tempb = b tempa[1] = a[ii[1]] tempa[ii[1]] = a[1] a = tempa tempb[1] = b[ii[1]] tempb[ii[1]] = b[1] b = tempb tempSigma = Sigma tempSigma[1, ] = Sigma[ii[1], ] tempSigma[ii[1], ] = Sigma[1, ] Sigma = tempSigma Sigma[, 1] = tempSigma[, ii[1]] Sigma[, ii[1]] = tempSigma[, 1] C[1, 1] = sqrt(Sigma[1, 1]) C[2::k, 1] = Sigma[2::k, 1]/C[1, 1] } else { C[, 1] = (sqrt_Sigma11 \ Sigma[2::k, 1]/sqrt_Sigma11) } if (atilde[1] != btilde[1]) { y[1] = (normalden(atilde[1]) - normalden(btilde[1]))/ (normal(btilde[1]) - normal(atilde[1])) } for (i = 2; i <= k - 1; i++) { args = J(1, k - i + 1, 1) i_vec = 1::(i - 1) for (j = 1; j <= k - i + 1; j++) { s = j + i - 1 if ((a[s] != .) & (b[s] != .)) { Cy = sum(C[s, i_vec]:*y[i_vec]) denom = sqrt(Sigma[s, s] - sum(C[s, i_vec]:^2)) args[j] = normal((b[s] - Cy)/denom) - normal((a[s] - Cy)/denom) } else if ((a[s] == .) & (b[s] != .)) { args[j] = normal((b[s] - sum(C[s, i_vec]:*y[i_vec]))/ sqrt(Sigma[s, s] - sum(C[s, i_vec]:^2))) } else if ((b[s] == .) & (a[s] != .)) { args[j] = 1 - normal((a[s] - sum(C[s, i_vec]:*y[i_vec]))/ sqrt(Sigma[s, s] - sum(C[s, i_vec]:^2))) } } ii = ww = . minindex(args, 1, ii, ww) m = i - 1 + ii[1] if (i != m) { tempa = a tempb = b tempa[i] = a[m] tempa[m] = a[i] a = tempa tempb[i] = b[m] tempb[m] = b[i] b = tempb tempSigma = Sigma tempSigma[i, ] = Sigma[m, ] tempSigma[m, ] = Sigma[i, ] Sigma = tempSigma Sigma[, i] = tempSigma[, m] Sigma[, m] = tempSigma[, i] tempC = C tempC[i, ] = C[m, ] tempC[m, ] = C[i, ] C = tempC C[, i] = tempC[, m] C[, m] = tempC[, i] } C[i, i] = sqrt(Sigma[i, i] - sum(C[i, i_vec]:^2)) i_vec2 = (i + 1)::k C[i_vec2, i] = (Sigma[i_vec2, i] - rowsum(J(k - i, 1, C[i, i_vec]):*C[i_vec2, i_vec]))/ C[i, i] Cy = sum(C[i, i_vec]:*y[i_vec]) atilde[i] = (a[i] - Cy)/C[i, i] btilde[i] = (b[i] - Cy)/C[i, i] if (atilde[i] != btilde[i]) { y[i] = (normalden(atilde[i]) - normalden(btilde[i]))/ (normal(btilde[i]) - normal(atilde[i])) } } C[k, k] = sqrt(Sigma[k, k] - sum(C[k, 1::(k - 1)]:^2)) I = V = 0 if (a[1] != .) { d = J(samples, 1, (normal(a[1]/C[1, 1]), J(1, k - 1, 0))) } else { d = J(samples, k, 0) } if (b[1] != .) { e = J(samples, 1, (normal(b[1]/C[1, 1]), J(1, k - 1, 1))) } else { e = J(samples, 1, J(1, k, 1)) } f = (e[, 1] - d[, 1], J(samples, k - 1, 0)) y = J(samples, k - 1, 0) Delta = runiform(shifts, k - 1) samples_sqrt_primes = (1::samples)*sqrt((2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541)[1::(k - 1)]) Ii = J(1, shifts, 0) for (i = 1; i <= shifts; i++) { for (l = 2; l <= k; l++) { l_vec = 1::(l - 1) y[, l - 1] = invnormal(d[, l - 1] + abs(2*mod(samples_sqrt_primes[, l - 1] :+ Delta[i, l - 1], 1) :- 1):* (e[, l - 1] - d[, l - 1])) if ((a[l] != .) & (b[l] != .)) { Cy = rowsum(J(samples, 1, C[l, l_vec]):*y[, l_vec]) d[, l] = normal((a[l] :- Cy)/C[l, l]) e[, l] = normal((b[l] :- Cy)/C[l, l]) f[, l] = (e[, l] :- d[, l]):*f[, l - 1] } else if ((a[l] != .) & (b[l] == .)) { d[, l] = normal((a[l] :- rowsum(J(samples, 1, C[l, l_vec]):*y[, l_vec]))/ C[l, l]) f[, l] = (1 :- d[, l]):*f[, l - 1] } else if ((a[l] == .) & (b[l] != .)) { e[, l] = normal((b[l] :- rowsum(J(samples, 1, C[l, l_vec]):*y[, l_vec]))/ C[l, l]) f[, l] = e[, l]:*f[, l - 1] } else { f[, l] = f[, l - 1] } } for (j = 1; j <= samples; j++) { Ii[i] = Ii[i] + (f[j, k] - Ii[i])/j } del = (Ii[i] - I)/i I = I + del V = (i - 2)*V/i + del^2 E = alpha*sqrt(V) } } return((I \ E)) } real colvector sim_ma_mata(real scalar n, real vector tau, real scalar sd, real scalar ratio, string correction, real scalar p, real vector p_O, real scalar K, real scalar replicates) { sd_sqrt_n = sd*sqrt(n) rn = ratio*n sd_sqrt_rn = sd*sqrt(rn) rn_tau = rn*tau denominator = sd*sqrt((ratio + 1)/rn) rej_any = 0 rej_vec = J(K, replicates, 0) rej_corr = rej_incorr = J(1, replicates, 0) for (i = 1; i <= replicates; i++) { pvals = 1 :- normal(((rnormal(K, 1, 0, 1)*sd_sqrt_rn :+ rn_tau)/rn - J(K, 1, rnormal(1, 1, 0, sd_sqrt_n)/n))/denominator) if ((correction == "bonferroni") | (correction == "dunnett") | (correction == "none") | (correction == "sidak")) { rej_any = rej_any + (min(pvals) < p) rej_vec[, i] = (pvals :< p) } else if ((correction == "holm") | (correction == "step_down_dunnett")) { order_pvals = order(pvals, 1) k = 1 check = 0 while ((k <= K) & (check == 0)) { if (pvals[order_pvals[k]] < p_O[k]) { rej_vec[order_pvals[k], i] = rej_vec[order_pvals[k], i] + 1 k = k + 1 } else { check = 1 } } rej_any = rej_any + sum(rej_vec[, i]) } else if (correction == "benjamini") { order_pvals = order(pvals, 1) for (k = K; k >= 1; k--) { if (pvals[order_pvals[k]] < p_O[k]) { rej_vec[1::k, i] = J(k, 1, 1) break } } } } discoveries = colsum(rej_vec) false_discoveries = colsum(rej_vec:*J(1, replicates, (tau :<= 0))) which_discoveries_0 = which_condition(discoveries, "e", 0) discoveries[which_discoveries_0] = J(1, length(which_discoveries_0), 1) return((rej_any/replicates \ mean((false_discoveries:/discoveries)') \ rowsum(rej_vec)/replicates)) } function which_condition(real vector check, string type, real scalar value) { match = . for (i = 1; i <= length(check); i++) { if (type == "ge") { if (check[i] >= value) { match = (match, i) } } else if (type == "g") { if (check[i] > value) { match = (match, i) } } else if (type == "le") { if (check[i] <= value) { match = (match, i) } } else if (type == "l") { if (check[i] < value) { match = (match, i) } } else if (type == "e") { if (check[i] == value) { match = (match, i) } } } if (length(match) > 1) { match = match[2::length(match)] if (rows(check) > 1) { return(match') } else { return(match) } } else { return(J(0, 0, .)) } } end