Skip to content

Commit 4f4c90b

Browse files
author
NightlordTW
committed
Revise examples hierarchical testing
1 parent 100192d commit 4f4c90b

File tree

9 files changed

+101
-109
lines changed

9 files changed

+101
-109
lines changed

R/RcppExports.R

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -35,30 +35,24 @@ ptvdf <- function(x, df, lower) {
3535
.Call(`_SimTOST_ptvdf`, x, df, lower)
3636
}
3737

38-
#' @title Check Equivalence
38+
#' @title Check Equivalence for Multiple Endpoints
3939
#'
4040
#' @description
41-
#' This function evaluates whether equivalence criteria are met.
42-
#' It first checks whether all primary endpoints satisfy
43-
#' equivalence (if sequential testing is enabled). Then, it determines whether the
44-
#' required number of endpoints (`k`) meet the equivalence threshold.
45-
#' The function returns a structured matrix that includes equivalence decisions,
46-
#' test results, mean estimates, and standard deviations.
41+
#' This function evaluates whether equivalence criteria are met based on a predefined set of endpoints.
42+
#' It first checks whether all primary endpoints satisfy equivalence (if sequential testing is enabled).
43+
#' Then, it determines whether the required number of endpoints (`k`) meet the equivalence threshold.
44+
#' The function returns a binary matrix indicating whether equivalence is established.
4745
#'
4846
#' @param typey An unsigned integer vector (`arma::uvec`) indicating the type of each endpoint:
4947
#' - `1` = Primary endpoint
5048
#' - `2` = Secondary endpoint
5149
#' @param adseq A boolean flag (`TRUE` if sequential testing is enabled).
5250
#' - If `TRUE`, all primary endpoints must pass equivalence for secondary endpoints to be evaluated.
5351
#' - If `FALSE`, primary and secondary endpoints are evaluated independently.
54-
#' @param tbioq A matrix (`arma::mat`) indicating the equivalence test results:
52+
#' @param tbioq A matrix (`arma::mat`) containing the equivalence test results for each endpoint:
5553
#' - `1` = Equivalence met
5654
#' - `0` = Equivalence not met
5755
#' @param k An integer specifying the minimum number of endpoints required to establish equivalence.
58-
#' @param mu0 A matrix (`arma::mat`) containing the estimated means for the reference treatment group.
59-
#' @param mu1 A matrix (`arma::mat`) containing the estimated means for the treatment under evaluation.
60-
#' @param sd0 A matrix (`arma::mat`) containing the standard deviations for the reference treatment group.
61-
#' @param sd1 A matrix (`arma::mat`) containing the standard deviations for the treatment under evaluation.
6256
#'
6357
#' @details
6458
#' - **Sequential Adjustment (`adseq = TRUE`)**:
@@ -70,14 +64,13 @@ ptvdf <- function(x, df, lower) {
7064
#' - `0` otherwise.
7165
#'
7266
#' @return
73-
#' An `arma::mat` containing the final equivalence decision along with test statistics:
74-
#' - `totaly` (1 × 1 matrix): Binary indicator (1 = equivalence established, 0 = not established).
75-
#' - `tbioq` (m × n matrix): Equivalence test results for each endpoint.
76-
#' - `mu0, mu1` (m × n matrices): Mean estimates for the reference and treatment groups.
77-
#' - `sd0, sd1` (m × n matrices): Standard deviations for the reference and treatment groups.
78-
#' @expor
79-
check_equivalence <- function(typey, adseq, tbioq, k, mu0, mu1, sd0, sd1) {
80-
.Call(`_SimTOST_check_equivalence`, typey, adseq, tbioq, k, mu0, mu1, sd0, sd1)
67+
#' An `arma::mat` (1 × 1 matrix) containing a binary equivalence decision:
68+
#' - `1` = Equivalence established.
69+
#' - `0` = Equivalence not established.
70+
#'
71+
#' @export
72+
check_equivalence <- function(typey, adseq, tbioq, k) {
73+
.Call(`_SimTOST_check_equivalence`, typey, adseq, tbioq, k)
8174
}
8275

8376
#' @title Simulate a 2x2 Crossover Design and Compute Difference of Means (DOM)

R/SampleSize.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ sampleSize <- function(mu_list, varcov_list = NA, sigma_list = NA, cor_mat = NA,
235235

236236
names(type_y) <- uynames
237237

238+
238239
weight <- 1/table(type_y)
239240
weight_seq <- type_y
240241

R/utils.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ test_studies <- function(nsim, n, comp, param, param.d, arm_seed, ncores){
193193
if (adjust=="bon") {alpha <- rep(alphau/(m),m)}
194194
if (adjust=="sid") {alpha <- rep(1-(1-alphau)^{1/m},m)}
195195
if (adjust=="k") { alpha <- rep(k*alphau/(m),m)}
196-
if (adjust=="seq"){
196+
if (adjust == "seq"){
197197
alpha <- alphau*param$weight_seq[endp]
198198
}
199199

man/check_equivalence.Rd

Lines changed: 10 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/RcppExports.cpp

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,16 @@ BEGIN_RCPP
3838
END_RCPP
3939
}
4040
// check_equivalence
41-
arma::mat check_equivalence(const arma::uvec& typey, bool adseq, const arma::mat& tbioq, int k, const arma::mat& mu0, const arma::mat& mu1, const arma::mat& sd0, const arma::mat& sd1);
42-
RcppExport SEXP _SimTOST_check_equivalence(SEXP typeySEXP, SEXP adseqSEXP, SEXP tbioqSEXP, SEXP kSEXP, SEXP mu0SEXP, SEXP mu1SEXP, SEXP sd0SEXP, SEXP sd1SEXP) {
41+
arma::mat check_equivalence(const arma::uvec& typey, bool adseq, const arma::mat& tbioq, int k);
42+
RcppExport SEXP _SimTOST_check_equivalence(SEXP typeySEXP, SEXP adseqSEXP, SEXP tbioqSEXP, SEXP kSEXP) {
4343
BEGIN_RCPP
4444
Rcpp::RObject rcpp_result_gen;
4545
Rcpp::RNGScope rcpp_rngScope_gen;
4646
Rcpp::traits::input_parameter< const arma::uvec& >::type typey(typeySEXP);
4747
Rcpp::traits::input_parameter< bool >::type adseq(adseqSEXP);
4848
Rcpp::traits::input_parameter< const arma::mat& >::type tbioq(tbioqSEXP);
4949
Rcpp::traits::input_parameter< int >::type k(kSEXP);
50-
Rcpp::traits::input_parameter< const arma::mat& >::type mu0(mu0SEXP);
51-
Rcpp::traits::input_parameter< const arma::mat& >::type mu1(mu1SEXP);
52-
Rcpp::traits::input_parameter< const arma::mat& >::type sd0(sd0SEXP);
53-
Rcpp::traits::input_parameter< const arma::mat& >::type sd1(sd1SEXP);
54-
rcpp_result_gen = Rcpp::wrap(check_equivalence(typey, adseq, tbioq, k, mu0, mu1, sd0, sd1));
50+
rcpp_result_gen = Rcpp::wrap(check_equivalence(typey, adseq, tbioq, k));
5551
return rcpp_result_gen;
5652
END_RCPP
5753
}
@@ -165,7 +161,7 @@ RcppExport SEXP _rcpp_module_boot_test();
165161
static const R_CallMethodDef CallEntries[] = {
166162
{"_SimTOST_ptv", (DL_FUNC) &_SimTOST_ptv, 3},
167163
{"_SimTOST_ptvdf", (DL_FUNC) &_SimTOST_ptvdf, 3},
168-
{"_SimTOST_check_equivalence", (DL_FUNC) &_SimTOST_check_equivalence, 8},
164+
{"_SimTOST_check_equivalence", (DL_FUNC) &_SimTOST_check_equivalence, 4},
169165
{"_SimTOST_test_2x2_dom", (DL_FUNC) &_SimTOST_test_2x2_dom, 15},
170166
{"_SimTOST_test_2x2_rom", (DL_FUNC) &_SimTOST_test_2x2_rom, 15},
171167
{"_SimTOST_test_par_dom", (DL_FUNC) &_SimTOST_test_par_dom, 17},

src/module.cpp

Lines changed: 35 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -60,30 +60,24 @@ arma::mat ptvdf(arma::mat x, arma::mat df, bool lower) {
6060
return arma::reshape(y, 1, n);
6161
}
6262

63-
//' @title Check Equivalence
63+
//' @title Check Equivalence for Multiple Endpoints
6464
//'
6565
//' @description
66-
//' This function evaluates whether equivalence criteria are met.
67-
//' It first checks whether all primary endpoints satisfy
68-
//' equivalence (if sequential testing is enabled). Then, it determines whether the
69-
//' required number of endpoints (`k`) meet the equivalence threshold.
70-
//' The function returns a structured matrix that includes equivalence decisions,
71-
//' test results, mean estimates, and standard deviations.
66+
//' This function evaluates whether equivalence criteria are met based on a predefined set of endpoints.
67+
//' It first checks whether all primary endpoints satisfy equivalence (if sequential testing is enabled).
68+
//' Then, it determines whether the required number of endpoints (`k`) meet the equivalence threshold.
69+
//' The function returns a binary matrix indicating whether equivalence is established.
7270
//'
7371
//' @param typey An unsigned integer vector (`arma::uvec`) indicating the type of each endpoint:
7472
//' - `1` = Primary endpoint
7573
//' - `2` = Secondary endpoint
7674
//' @param adseq A boolean flag (`TRUE` if sequential testing is enabled).
7775
//' - If `TRUE`, all primary endpoints must pass equivalence for secondary endpoints to be evaluated.
7876
//' - If `FALSE`, primary and secondary endpoints are evaluated independently.
79-
//' @param tbioq A matrix (`arma::mat`) indicating the equivalence test results:
77+
//' @param tbioq A matrix (`arma::mat`) containing the equivalence test results for each endpoint:
8078
//' - `1` = Equivalence met
8179
//' - `0` = Equivalence not met
8280
//' @param k An integer specifying the minimum number of endpoints required to establish equivalence.
83-
//' @param mu0 A matrix (`arma::mat`) containing the estimated means for the reference treatment group.
84-
//' @param mu1 A matrix (`arma::mat`) containing the estimated means for the treatment under evaluation.
85-
//' @param sd0 A matrix (`arma::mat`) containing the standard deviations for the reference treatment group.
86-
//' @param sd1 A matrix (`arma::mat`) containing the standard deviations for the treatment under evaluation.
8781
//'
8882
//' @details
8983
//' - **Sequential Adjustment (`adseq = TRUE`)**:
@@ -95,36 +89,25 @@ arma::mat ptvdf(arma::mat x, arma::mat df, bool lower) {
9589
//' - `0` otherwise.
9690
//'
9791
//' @return
98-
//' An `arma::mat` containing the final equivalence decision along with test statistics:
99-
//' - `totaly` (1 × 1 matrix): Binary indicator (1 = equivalence established, 0 = not established).
100-
//' - `tbioq` (m × n matrix): Equivalence test results for each endpoint.
101-
//' - `mu0, mu1` (m × n matrices): Mean estimates for the reference and treatment groups.
102-
//' - `sd0, sd1` (m × n matrices): Standard deviations for the reference and treatment groups.
92+
//' An `arma::mat` (1 × 1 matrix) containing a binary equivalence decision:
93+
//' - `1` = Equivalence established.
94+
//' - `0` = Equivalence not established.
95+
//'
10396
//' @export
10497
// [[Rcpp::export]]
10598
arma::mat check_equivalence(const arma::uvec& typey, bool adseq,
106-
const arma::mat& tbioq, int k,
107-
const arma::mat& mu0, const arma::mat& mu1,
108-
const arma::mat& sd0, const arma::mat& sd1) {
99+
const arma::mat& tbioq, int k) {
109100
// primary endpoints in case of sequencial adjustment
110-
int sumtypey;
101+
int sumtypey = 1;
111102
// in case no primary endpoint is added.
112-
if( accu(typey) >= 0) {
103+
if (!typey.empty() && all(typey >= 0)) {
113104
sumtypey = accu(tbioq.cols(typey)); // sum of primary endpoint rejected
114-
}else{
115-
sumtypey = 1;
116105
}
117106

118107
// Total number of primary endpoints
119108
int lentypey = typey.n_elem;
120109

121-
// Determine if all primary endpoints meet the equivalence criteria under sequential adjustment.
122-
//
123-
// If `adseq` (sequential testing) is disabled (`false`), equivalence is not required for all primary endpoints,
124-
// so `sumpe` is automatically set to `true`.
125-
//
126-
// If `adseq` is enabled (`true`), `sumpe` is set to `true` only if all primary endpoints (`sumtypey`) meet
127-
// the required equivalence criteria (`lentypey`), meaning all must pass.
110+
// Determine if all primary endpoints meet the equivalence criteria under sequential adjustment
128111
bool sumpe = !adseq || (sumtypey == lentypey);
129112

130113
// Check if at least `k` endpoints meet equivalence criteria
@@ -134,13 +117,7 @@ arma::mat check_equivalence(const arma::uvec& typey, bool adseq,
134117
arma::mat totaly(1,1);
135118
totaly(0, 0) = (sumt && sumpe) ? 1 : 0;
136119

137-
// Combine results into a response matrix
138-
arma::mat response0 = join_rows(totaly, tbioq);
139-
arma::mat response1 = join_rows(mu0, mu1);
140-
arma::mat response2 = join_rows(sd0, sd1);
141-
arma::mat response3 = join_rows(response0, response1);
142-
143-
return join_rows(response3, response2);
120+
return totaly;
144121
}
145122

146123

@@ -569,8 +546,16 @@ arma::mat test_par_dom(int n, arma::vec muT, arma::vec muR,
569546
mat alpha0 = conv_to<mat>::from(alpha);
570547
mat tbioq = conv_to<mat>::from((ptost < alpha0));
571548

572-
// Call the check_equivalence function
573-
return check_equivalence(typey, adseq, tbioq, k, mu0, mu1, sd0, sd1);
549+
// Call the check_equivalence function to determine if equivalence is established
550+
arma::mat totaly = check_equivalence(typey, adseq, tbioq, k);
551+
552+
// Combine results into a response matrix
553+
arma::mat response0 = join_rows(totaly, tbioq);
554+
arma::mat response1 = join_rows(mu0, mu1);
555+
arma::mat response2 = join_rows(sd0, sd1);
556+
arma::mat response3 = join_rows(response0, response1);
557+
558+
return join_rows(response3, response2);
574559
}
575560

576561
//' @title Simulate a Parallel Design and Compute Ratio of Means (ROM)
@@ -673,8 +658,16 @@ arma::mat test_par_rom(int n, arma::vec muT, arma::vec muR,
673658
mat alpha0 = conv_to<mat>::from(alpha);
674659
mat tbioq = conv_to<mat>::from((ptost < alpha0));
675660

676-
// Call the check_equivalence function
677-
return check_equivalence(typey, adseq, tbioq, k, mu0, mu1, sd0, sd1);
661+
// Call the check_equivalence function to determine if equivalence is established
662+
arma::mat totaly = check_equivalence(typey, adseq, tbioq, k);
663+
664+
// Combine results into a response matrix
665+
arma::mat response0 = join_rows(totaly, tbioq);
666+
arma::mat response1 = join_rows(mu0, mu1);
667+
arma::mat response2 = join_rows(sd0, sd1);
668+
arma::mat response3 = join_rows(response0, response1);
669+
670+
return join_rows(response3, response2);
678671
}
679672

680673

vignettes/intropkg.Rmd

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -150,20 +150,19 @@ $$\alpha_k= \frac{k*\alpha}{m}$$
150150
where $k$ is the number of endpoints required for equivalence, and $m$ is the total number of endpoints evaluated.
151151

152152
## Hierarchical testing of multiple endpoints
153-
Hierarchical testing is an approach to multiple endpoint testing where endpoints are tested in a predefined order, typically based on their clinical or regulatory importance. A fallback testing strategy is applied, allowing sequential hypothesis testing. If a hypothesis earlier in the sequence fails to be rejected, testing stops, and subsequent hypotheses are not evaluated.
153+
Hierarchical testing is an approach to multiple endpoint testing where endpoints are tested in a predefined order, typically based on their clinical or regulatory importance. A fallback testing strategy is applied, allowing sequential hypothesis testing. If a hypothesis earlier in the sequence fails to be rejected, testing stops, and subsequent hypotheses are not evaluated. [@chowdhry_finding_2024]
154154

155-
To implement hierarchical testing in SimTOST, the user defines primary and secondary endpoints using the `type_y` vector argument. The significance level ($\alpha$) is adjusted separately for each endpoint group:
155+
To implement hierarchical testing in SimTOST, the user specifies `adjust = "seq"` in the [sampleSize()](../reference/sampleSize.html) function and defines primary and secondary endpoints using the `type_y` vector argument. The significance level ($\alpha$) is adjusted separately for each group of endpoints, ensuring strong control of the Family-Wise Error Rate (FWER) while maintaining interpretability.
156156

157157
1. **Evaluate primary endpoints**
158158
- Testing begins with the pre-specified primary endpoints.
159-
- The adjusted significance level for a primary endpoint $p$ is: $$\alpha_H= \frac{\alpha}{\text{Number of primary endpoints}}$$
160-
- If any primary hypothesis fails to be rejected, testing stops, and secondary endpoints are not tested.
159+
- The adjusted significance level for a primary endpoint $p$ is: $$\alpha_p= \frac{\alpha}{\text{Number of primary endpoints}}$$
160+
- If any primary endpoint fails to meet the equivalence criteria, testing stops, and secondary endpoints are not tested.
161161
2. **Proceed to secondary endpoints (if applicable)**
162-
- If all primary endpoints demonstrate equivalence, testing continues to the secondary endpoints.
163-
- A Bonferroni adjustment is applied again, based on the number of secondary endpoints.
164-
165-
The adjusted significance level is determined using a weighting approach, ensuring that primary and secondary endpoints are tested sequentially while maintaining strong control over the Family-Wise Error Rate (FWER). The sequential adjustment correction can be implemented by specifying `adjust = "seq"` in the [sampleSize()](../reference/sampleSize.html) function.
166-
162+
- If all primary endpoints meet the equivalence criteria, testing proceeds to the secondary endpoints.
163+
- The significance level for a secondary endpoint $s$ is adjusted as: $$\alpha_s= \frac{\alpha}{\text{Number of secondary endpoints}}$$
164+
3. **Final Decision**
165+
- The test is considered successful if at least `k` endpoints meet equivalence and all primary endpoints pass.
167166

168167
An example of hierarchical testing can be found in [this vignette](sampleSize_parallel_2A3E.html#hierarchical-testing).
169168

vignettes/references.bib

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,18 @@ @incollection{PASSch685
179179
publisher = {NCSS Statistical Software},
180180
url = {https://www.ncss.com/wp-content/themes/ncss/pdf/Procedures/PASS/Biosimilarity_Tests_for_the_Difference_Between_Means_using_a_Parallel_Two-Group_Design.pdf}
181181
}
182+
183+
@article{chowdhry_finding_2024,
184+
title = {Finding {{Multiple Signals}} in the {{Noise}}: {{Handling Multiplicity}} in {{Clinical Trials}}},
185+
shorttitle = {Finding {{Multiple Signals}} in the {{Noise}}},
186+
author = {Chowdhry, Amit K. and Park, John and Kang, John and Sakthivel, Gukan and Pugh, Stephanie},
187+
year = {2024},
188+
month = jul,
189+
journal = {International Journal of Radiation Oncology*Biology*Physics},
190+
volume = {119},
191+
number = {3},
192+
pages = {750--755},
193+
issn = {03603016},
194+
doi = {10.1016/j.ijrobp.2023.12.007}
195+
}
196+

0 commit comments

Comments
 (0)