Helmut
★★★
avatar
Homepage
Vienna, Austria,
2022-06-12 13:37
(234 d 06:57 ago)

Posting: # 23051
Views: 1,377
 

 ‘Two-at-a-Time’ approach with >3 treat­ments [Design Issues]

Dear all,

the EMA1–3 recommends for higher-order crossovers the ‘Two-at-a-Time’ approach, i.e., to exclude all but two treatments, leading to Incomplete Block Designs.4 That’s fine, since the ‘All at Once’ approach (an ANOVA of all data) may lead to biased estimates and an inflated Type I Error.5,6

So far I had to deal only with three treatments and planned pairwise comparisons \(\small{\text{A}\;vs\;\text{C}}\) and \(\small{\text{B}\;vs\;\text{C}}\). For a six-sequence Williams’ design we get$$\small{\begin{array}{c|ccc}
s/p & \text{I} & \text{II} & \text{III} \\\hline
1 & \text{A} & \text{B} & \text{C}\\
2 & \text{A} & \text{C} & \text{B}\\
3 & \text{B} & \text{A} & \text{C}\\
4 & \text{B} & \text{C} & \text{A}\\
5 & \text{C} & \text{A} & \text{B}\\
6 & \text{C} & \text{B} & \text{A}\\
\end{array}{\color{Blue}\mapsto}
\begin{array}{c|ccc}
s/p & \text{I} & \text{II} & \text{III} \\\hline
1 & \text{A} & {\color{Red}\bullet} & \text{C}\\
2 & \text{A} & \text{C} & {\color{Red}\bullet}\\
3 & {\color{Red}\bullet} & \text{A} & \text{C}\\
4 & {\color{Red}\bullet} & \text{C} & \text{A}\\
5 & \text{C} & \text{A} & {\color{Red}\bullet}\\
6 & \text{C} & {\color{Red}\bullet} & \text{A}\\
\end{array}{\color{Blue}\wedge}
\begin{array}{c|ccc}
s/p & \text{I} & \text{II} & \text{III} \\\hline
1 & {\color{Red}\bullet} & \text{B} & \text{C}\\
2 & {\color{Red}\bullet} & \text{C} & \text{B}\\
3 & \text{B} & {\color{Red}\bullet} & \text{C}\\
4 & \text{B} & \text{C} & {\color{Red}\bullet}\\
5 & \text{C} & {\color{Red}\bullet} & \text{B}\\
6 & \text{C} & \text{B} & {\color{Red}\bullet}\\
\end{array}}$$ where \(\small{{\color{Red}\bullet}}\) denotes an excluded treatment. Both IBDs are balanced. Excellent.

What about four treatments in a Williams’ design with pairwise comparisons \(\small{\text{A}\;vs\;\text{D}}\), \(\small{\text{B}\;vs\;\text{D}}\), and \(\small{\text{C}\;vs\;\text{D}}\)? $$\small{\begin{array}{c|cccc}
s/p & \text{I} & \text{II} & \text{III} & \text{IV}\\\hline
1 & \text{A} & \text{B} & \text{C} & \text{D}\\
2 & \text{B} & \text{D} & \text{A} & \text{C}\\
3 & \text{C} & \text{A} & \text{B} & \text{D}\\
4 & \text{D} & \text{C} & \text{B} & \text{A}\\
\end{array}{\color{Blue}\mapsto}
\begin{array}{c|cccc}
s/p & \text{I} & \text{II} & \text{III} & \text{IV} \\\hline
1 & \text{A} & {\color{Red}\bullet} & {\color{Red}\bullet} & \text{D}\\
2 & {\color{Red}\bullet} & \text{D} & \text{A} & {\color{Red}\bullet}\\
3 & {\color{Red}\bullet} & \text{A} & {\color{Red}\bullet} & \text{D}\\
4 & \text{D} & {\color{Red}\bullet} & {\color{Red}\bullet} & \text{A}\\
\end{array}{\color{Blue}\wedge}
\begin{array}{c|cccc}
s/p & \text{I} & \text{II} & \text{III} & \text{IV}\\\hline
1 & {\color{Red}\bullet} & \text{B} & {\color{Red}\bullet} & \text{D}\\
2 & \text{B} & \text{D} & {\color{Red}\bullet} & {\color{Red}\bullet}\\
3 & {\color{Red}\bullet} & {\color{Red}\bullet} & \text{B} & \text{D}\\
4 & \text{D} & {\color{Red}\bullet} & \text{B} & {\color{Red}\bullet}\\
\end{array}{\color{Blue}\wedge}
\begin{array}{c|cccc}
s/p & \text{I} & \text{II} & \text{III} & \text{IV}\\\hline
1 & {\color{Red}\bullet} & {\color{Red}\bullet} & \text{C} & \text{D}\\
2 & {\color{Red}\bullet} & \text{D} & {\color{Red}\bullet} & \text{C}\\
3 & \text{C} & {\color{Red}\bullet} & {\color{Red}\bullet} & \text{D}\\
4 & \text{D} & \text{C} & {\color{Red}\bullet} & {\color{Red}\bullet}\\
\end{array}}$$ Now the IBDs are imbalanced and hence, lacking period effects have to be assumed. Nasty.
Solution: Use one of the 24 arrangements given by Senn.6 Here the first as an example:
$$\small{\begin{array}{c|cccc}
s/p & \text{I} & \text{II} & \text{III} & \text{IV}\\\hline
1 & \text{A} & \text{B} & \text{C} & \text{D}\\
2 & \text{B} & \text{A} & \text{D} & \text{C}\\
3 & \text{C} & \text{D} & \text{A} & \text{B}\\
4 & \text{D} & \text{C} & \text{B} & \text{A}\\
\end{array}{\color{Blue}\mapsto}
\begin{array}{c|cccc}
s/p & \text{I} & \text{II} & \text{III} & \text{IV} \\\hline
1 & \text{A} & {\color{Red}\bullet} & {\color{Red}\bullet} & \text{D}\\
2 & {\color{Red}\bullet} & \text{A} & \text{D} & {\color{Red}\bullet}\\
3 & {\color{Red}\bullet} & \text{D} & \text{A} & {\color{Red}\bullet}\\
4 & \text{D} & {\color{Red}\bullet} & {\color{Red}\bullet} & \text{A}\\
\end{array}{\color{Blue}\wedge}
\begin{array}{c|cccc}
s/p & \text{I} & \text{II} & \text{III} & \text{IV}\\\hline
1 & {\color{Red}\bullet} & \text{B} & {\color{Red}\bullet} & \text{D}\\
2 & \text{B} & {\color{Red}\bullet} & \text{D} & {\color{Red}\bullet}\\
3 & {\color{Red}\bullet} & \text{D} & {\color{Red}\bullet} & \text{B}\\
4 & \text{D} & {\color{Red}\bullet} & \text{B} & {\color{Red}\bullet}\\
\end{array}{\color{Blue}\wedge}
\begin{array}{c|cccc}
s/p & \text{I} & \text{II} & \text{III} & \text{IV}\\\hline
1 & {\color{Red}\bullet} & {\color{Red}\bullet} & \text{C} & \text{D}\\
2 & {\color{Red}\bullet} & {\color{Red}\bullet} & \text{D} & \text{C}\\
3 & \text{C} & \text{D} & {\color{Red}\bullet} & {\color{Red}\bullet}\\
4 & \text{D} & \text{C} & {\color{Red}\bullet} & {\color{Red}\bullet}\\
\end{array}}$$ All is good. An [image]-script at the end.
BTW, the common Latin Square (\(\small{\text{ABCD}\,|\,\text{BCDA}\,|\,\text{CDAB}\,|\,\text{DABC}}\)) would do as well.


  1. EMA, CHMP. Guideline on the Investigation of Bioequivalence. CPMP/EWP/QWP/1401/98 Rev. 1/Corr **. London. 20 January 2010. Online.
  2. Brown D. Presentation at the 3rd EGA Symposium on Bioequivalence. London. June 2010. Slides.
  3. EGA. Revised EMA Bioequivalence Guideline. Questions & Answers. Online.
  4. Cochran WG, Cox GM. Experimental Designs. New York: Wiley; 2nd ed. 1957. Chapter 9, p. 376–95.
  5. Schuirmann DJ. Two at a Time? Or All at Once? International Biometric Society, Eastern North American Region, Spring Meeting. Pittsburgh, PA. March 28–31, 2004. Abstract.
  6. D’Angelo P. Testing for Bioequivalence in Higher‐Order Crossover Designs: Two‐at‐a‐Time Principle Versus Pooled ANOVA. 2nd Conference of the Global Bioequivalence Harmonisation Initiative. Rockville, MD. 15–16 Sep­tem­ber, 2016.
  7. Senn S. Cross-over Trials in Clinical Research. Chichester: Wiley; 2nd ed. 2002. Table 5.1. p. 163.

library(PowerTOST)
library(randomizeBE)
# sample size for evaluation by ‘Two at a Time’
# note that the design has to specified as "2x2"

n    <- sampleN.TOST(CV = 0.20, theta0 = 0.95, targetpower = 0.8,
                     design = "2x2", print = FALSE)[["Sample size"]]
# specify the sequences and get the randomization list
seqs <- c("ABCD", "BADC", "CDAB", "DCBA")
rl   <- RL4(nsubj = n, seqs = seqs)$rl
rl   <- cbind(rl, IBD.1 = NA, IBD.2 = NA, IBD.3 = NA)
# extract pairwise comparisons (A vs D, B vs D, C vs D) and build the IBDs
for (j in 1:nrow(rl)) {
  rl$IBD.1[j] <- gsub("[B, C]", "•", rl$sequence[j]) # keep A and D
  rl$IBD.2[j] <- gsub("[C, A]", "•", rl$sequence[j]) # keep B and D
  rl$IBD.3[j] <- gsub("[A, B]", "•", rl$sequence[j]) # keep C and D
}
rl   <- rl[, -2] # remove seqno
# check IBDs for balance
c.1 <- c.2 <- c.3 <- NA
for (j in 1:4) {
  c.1[j] <- sum(substr(rl$IBD.1, j, j) == "A") == sum(substr(rl$IBD.1, j, j) == "D")
  c.2[j] <- sum(substr(rl$IBD.2, j, j) == "B") == sum(substr(rl$IBD.2, j, j) == "D")
  c.3[j] <- sum(substr(rl$IBD.3, j, j) == "C") == sum(substr(rl$IBD.3, j, j) == "D")
}
chk  <- setNames(c(sum(c.1), sum(c.2), sum(c.3)), c("IBD.1", "IBD.2", "IBD.3"))
txt  <- paste("Specified sequences:", paste(seqs, collapse = ", "))
if (length(unique(chk)) == 1) {
  txt <- paste(txt,
               "\nPassed check       : All IBDs are balanced.\n")
 
} else {
  txt <- paste(txt,
              "\nFailed check       : IBDs are not balanced:",
              "Examine the specified sequences.\n")
}
print(rl, row.names = FALSE)
cat(txt)


Gives
 subject sequence IBD.1 IBD.2 IBD.3
       1     DCBA  D••A  D•B•  DC••
       2     CDAB  •DA•  •D•B  CD••
       3     CDAB  •DA•  •D•B  CD••
       4     BADC  •AD•  B•D•  ••DC
       5     BADC  •AD•  B•D•  ••DC
       6     ABCD  A••D  •B•D  ••CD
       7     DCBA  D••A  D•B•  DC••
       8     ABCD  A••D  •B•D  ••CD
       9     CDAB  •DA•  •D•B  CD••
      10     BADC  •AD•  B•D•  ••DC
      11     BADC  •AD•  B•D•  ••DC
      12     ABCD  A••D  •B•D  ••CD
      13     DCBA  D••A  D•B•  DC••
      14     DCBA  D••A  D•B•  DC••
      15     CDAB  •DA•  •D•B  CD••
      16     ABCD  A••D  •B•D  ••CD
      17     DCBA  D••A  D•B•  DC••
      18     CDAB  •DA•  •D•B  CD••
      19     ABCD  A••D  •B•D  ••CD
      20     BADC  •AD•  B•D•  ••DC
Specified sequences: ABCD, BADC, CDAB, DCBA
Passed check       : All IBDs are balanced.


Dif-tor heh smusma 🖖🏼 Довге життя Україна! [image]
Helmut Schütz
[image]

The quality of responses received is directly proportional to the quality of the question asked. 🚮
Science Quotes
Shuanghe
★★  

Spain,
2022-06-12 21:02
(233 d 23:31 ago)

@ Helmut
Posting: # 23053
Views: 1,102
 

 ‘Two-at-a-Time’ approach with >3 treat­ments

Dear Helmut,

I vaguelly recall that for 4x4 William design, each row (sequence) and each column (period) should contain A, B, C, and D each exactly once. I think that there's a typo in the 3rd sequence of your first William design. Your 3rd and 4th column do not seem right. So 3rd period would have 2 Bs and 4th had 2 Ds. CABD should be CADB. So It would be ABCD/BDAC/CADB/DCBA. In such case, it would be balanced for pair-wise comparisons.

$$\small{\begin{array}{c|cccc}
s/p & \text{I} & \text{II} & \text{III} & \text{IV}\\\hline
1 & \text{A} & \text{B} & \text{C} & \text{D}\\
2 & \text{B} & \text{D} & \text{A} & \text{C}\\
3 & \text{C} & \text{A} & \text{D} & \text{B}\\
4 & \text{D} & \text{C} & \text{B} & \text{A}\\
\end{array}{\color{Blue}\mapsto}
\begin{array}{c|cccc}
s/p & \text{I} & \text{II} & \text{III} & \text{IV} \\\hline
1 & \text{A} & {\color{Red}\bullet} & {\color{Red}\bullet} & \text{D}\\
2 & {\color{Red}\bullet} & \text{D} & \text{A} & {\color{Red}\bullet}\\
3 & {\color{Red}\bullet} & \text{A} & \text{D} & {\color{Red}\bullet}\\
4 & \text{D} & {\color{Red}\bullet} & {\color{Red}\bullet} & \text{A}\\
\end{array}{\color{Blue}\wedge}
\begin{array}{c|cccc}
s/p & \text{I} & \text{II} & \text{III} & \text{IV}\\\hline
1 & {\color{Red}\bullet} & \text{B} & {\color{Red}\bullet} & \text{D}\\
2 & \text{B} & \text{D} & {\color{Red}\bullet} & {\color{Red}\bullet}\\
3 & {\color{Red}\bullet} & {\color{Red}\bullet} & \text{D} & \text{B}\\
4 & \text{D} & {\color{Red}\bullet} & \text{B} & {\color{Red}\bullet}\\
\end{array}{\color{Blue}\wedge}
\begin{array}{c|cccc}
s/p & \text{I} & \text{II} & \text{III} & \text{IV}\\\hline
1 & {\color{Red}\bullet} & {\color{Red}\bullet} & \text{C} & \text{D}\\
2 & {\color{Red}\bullet} & \text{D} & {\color{Red}\bullet} & \text{C}\\
3 & \text{C} & {\color{Red}\bullet} & \text{D} & {\color{Red}\bullet}\\
4 & \text{D} & \text{C} & {\color{Red}\bullet} & {\color{Red}\bullet}\\
\end{array}}$$

All the best,
Shuanghe
Helmut
★★★
avatar
Homepage
Vienna, Austria,
2022-06-12 22:10
(233 d 22:23 ago)

@ Shuanghe
Posting: # 23054
Views: 1,000
 

 ‘Two-at-a-Time’ approach with >3 treat­ments

Hi Shuanghe,

long time, no hear!

❝ I vaguelly recall that for 4x4 William design, each row (sequence) and each column (period) should contain A, B, C, and D each exactly once.


Absolutely correct.

❝ I think that there's a typo in the 3rd sequence of your first William design.


F**k & THX! You are right.

❝ Your 3rd and 4th column do not seem right. So 3rd period would have 2 Bs and 4th had 2 Ds. CABD should be CADB. So It would be ABCD/BDAC/CADB/DCBA.


Yep.

An updated [image]-script which works for any number of treatments (tested for ≤ 8) at the end. Of course, more than five treatments in a higher-order crossover are not realistic due to the number of periods and likely a Balanced In­com­plete Block Design will be used instead. Not implemented yet.

An example for the maximum of treatments I ever came across:

 subject seqno sequence  IBD.1  IBD.2  IBD.3  IBD.4  IBD.5
       1     4   DAFCEB •AF••• ••F••B ••FC•• D•F••• ••F•E•
       2     6   FDEABC F••A•• F•••B• F••••C FD•••• F•E•••
       3     6   FDEABC F••A•• F•••B• F••••C FD•••• F•E•••
       4     3   CBAEDF ••A••F •B•••F C••••F ••••DF •••E•F
       5     2   BECFAD •••FA• B••F•• ••CF•• •••F•D •E•F••
       6     5   EFBDCA •F•••A •FB••• •F••C• •F•D•• EF••••
       7     1   ACDBFE A•••F• •••BF• •C••F• ••D•F• ••••FE
       8     1   ACDBFE A•••F• •••BF• •C••F• ••D•F• ••••FE
       9     2   BECFAD •••FA• B••F•• ••CF•• •••F•D •E•F••
      10     5   EFBDCA •F•••A •FB••• •F••C• •F•D•• EF••••
      11     3   CBAEDF ••A••F •B•••F C••••F ••••DF •••E•F
      12     4   DAFCEB •AF••• ••F••B ••FC•• D•F••• ••F•E•
      13     2   BECFAD •••FA• B••F•• ••CF•• •••F•D •E•F••
      14     6   FDEABC F••A•• F•••B• F••••C FD•••• F•E•••
      15     4   DAFCEB •AF••• ••F••B ••FC•• D•F••• ••F•E•
      16     1   ACDBFE A•••F• •••BF• •C••F• ••D•F• ••••FE
      17     5   EFBDCA •F•••A •FB••• •F••C• •F•D•• EF••••
      18     4   DAFCEB •AF••• ••F••B ••FC•• D•F••• ••F•E•
      19     1   ACDBFE A•••F• •••BF• •C••F• ••D•F• ••••FE
      20     3   CBAEDF ••A••F •B•••F C••••F ••••DF •••E•F
Sequences: ACDBFE, BECFAD, CBAEDF, DAFCEB, EFBDCA, FDEABC
All extracted IBDs are balanced.

Sometimes sequences are imbalanced. In this example we have four subjects in sequences DAFCEB and FDEABC
and three subjects in each of the other four sequences. However, that’s not relevant because we aim at balance of the IBDs. If you want balance, specify bal <- TRUE. In the example it would require 24 subjects instead of 20.

Although there is just one Williams’ design for three treatments, there are six possible for four treatments, twelve for five, 120 for six, and 360 for seven. The function williams() of randomizeBE will arbitrarily select one of them.
If you run the script, the chance to get the same sequences like in my example is 1/120.


Edit: Don’t use this script. Use the one at the end of this post instead.

library(PowerTOST)
library(randomizeBE)
ntmt   <- 6     # number of treatments (at least 3)
CV     <- 0.20  # assumed CV
theta0 <- 0.95  # assumed T/R-ratio
target <- 0.80  # target (desired) power
bal    <- FALSE # TRUE if the study should have balanced sequences
make.equal <- function(n, nseq) {
  return(as.integer(nseq * (n %/% nseq + as.logical(n %% nseq))))
}
# sample size for evaluation by the ‘Two at a Time’ approach
# note that the design is specified as "2x2"

n      <- sampleN.TOST(CV = CV, theta0 = theta0, targetpower = target,
                       design = "2x2", print = FALSE)[["Sample size"]]
# sequences of a Williams’ design
seqs   <- williams(ntmt)
# alternatively specify your own vector:
# treatments must have only one character and the
# reference must have the highest lexical order
# a Latin Square with four treatments will work
#   seqs <- c("ABCR", "BCRA", "CRAB", "RABC")
# but this one will not
#   seqs <- c("T1T2T3R", "T2T3RT1", "T3RT1T2", "RT1T2T3")
# eventual uprounding if balance is requested

if (bal) n <- make.equal(n, length(seqs))
s      <- paste0(seqs, collapse = "")
# the last treatment in lexical order will act as the reference
s      <- paste(sort(unlist(strsplit(s, ""))), collapse = "")
ref    <- substr(s, nchar(s), nchar(s))
# extract treatment codes
trts   <- unique(c(strsplit(split = "", seqs), recursive = TRUE))
# extract tests in increasing order
tests  <- sort(trts[!trts == ref])
# get the randomization list
rand   <- suppressWarnings( # not relevant for TaT
            RL4(nsubj = n, seqs = seqs)$rl)
# generate columns for the IBDs
for (j in seq_along(tests)) {
  rand[[paste0("IBD.", j)]] <- NA
}
# build the IBDs
for (j in 1:nrow(rand)) {
  for (k in seq_along(tests)) {
    excl           <- tests[!tests == tests[k]]
    excl           <- paste0("[", paste(excl, collapse = ", "), "]")
    rand[j, 3 + k] <- gsub(excl, "•", rand$sequence[j])
  }
}
# check IBDs for balance
checks <- rep(NA, length(tests))
for (j in seq_along(tests)) {
  checks[j] <- sum(lengths(regmatches(rand[3 + j],
                                      gregexpr(tests[j],
                                               rand[3 + j])))) ==
               sum(lengths(regmatches(rand[3 + j],
                                      gregexpr(ref,
                                               rand[3 + j]))))
}
txt    <-    paste("Sequences:", paste(seqs, collapse = ", "))
if (length(unique(checks)) == 1) { # all are TRUE
  txt <- paste(txt, "\nAll extracted IBDs are balanced.\n")
 
} else {                           # at least one of the IBDs is imbalanced
  txt <- paste(txt, "\nExtracted IBDs are imbalanced;",
               "check the sequences.\n")
}
print(rand, row.names = FALSE); cat(txt)


Dif-tor heh smusma 🖖🏼 Довге життя Україна! [image]
Helmut Schütz
[image]

The quality of responses received is directly proportional to the quality of the question asked. 🚮
Science Quotes
Shuanghe
★★  

Spain,
2022-07-10 02:05
(206 d 18:28 ago)

@ Helmut
Posting: # 23123
Views: 807
 

 ‘Two-at-a-Time’ approach with >3 treat­ments

Hi Helmut,

❝ long time, no hear!


Indeed. COVID, change of company and workload, study, ..., Pretty busy these days. Had to work day and night recently to catch everything up, so I had much less time to read the forum than before... But I'll be in Amsterdam this September for the 5th global BE harmonisation conference, so hopefully we'll have time to have a drink together :smoke: :smoke: :smoke:

By the way, the Article section is new, am I right? (Don't tell me I missed this section all those years...) I am supprised that no one mentioned it in the forum. I don't have time to read the lengthy ones yet but I really enjoy the reading among the few short ones that I've picked.

❝ All extracted IBDs are balanced.


What I understood about balanced IBD here is that, e.g., the number for A vs. F comparison should be same as number of F vs. A. So there should be 10 for each comparison for 20 in total.

If that is a correct understanding, it seems that for B, C and D, yes, but for A and E, not... There're 11 AF (FE) but 9 FA (EF). Shouldn't it be 10 and 10 like B/C/D vs F?

❝ If you run the script, the chance to get the same sequences like in my example is 1/120.


I tried the code and obviously I cannot reproduce your result as you said, but among the few runs I had, there is a run with 8 EF and 12 FE, 11 FA (BF) and 9 AF(FB)...

All the best,
Shuanghe
Helmut
★★★
avatar
Homepage
Vienna, Austria,
2022-07-10 18:02
(206 d 02:32 ago)

@ Shuanghe
Posting: # 23126
Views: 877
 

 ‘Two-at-a-Time’ approach; ≥3 treat­ments: new 🇷 script

Hi Shuanghe,

❝ I'll be in Amsterdam this September for the 5th global BE harmonisation conference, so hopefully we'll have time to have a drink together :smoke: :smoke: :smoke:


Great! BioBridges (22/23 September) as well?

❝ By the way, the Article section is new, am I right? (Don't tell me I missed this section all those years...)


<1½ years.

❝ I am supprised that no one mentioned it in the forum.


Well, I do. Actually I was bored of answering similar questions over and over again.
Now I reply only with: :blahblah:, for details see article XYZ.”

❝ I don't have time to read the lengthy ones yet but I really enjoy the reading among the few short ones that I've picked.


THX! :flower:

❝ What I understood about balanced IBD here is that, e.g., the number for A vs. F comparison should be same as number of F vs. A. So there should be 10 for each comparison for 20 in total.

❝ If that is a correct understanding, …


Of course, it is.

❝ … it seems that for B, C and D, yes, but for A and E, not... There're 11 AF (FE) but 9 FA (EF). Shouldn't it be 10 and 10 like B/C/D vs F?


[image]:thumb up: THX for discovering such a nasty bug!

If debugging is the process of removing bugs,
then programming must be the process of putting them in.
    [image] Edsger W. Dijkstra


New (lengthy) script at the end. More flexible (theta0, theta1, theta2, target, do.rate, and a vector of references ref can be specified).
Call the function with e.g., make.ibds(ntmt = 6, CV = 0.2):

 subject seqno sequence  IBD 1  IBD 2  IBD 3  IBD 4  IBD 5
       1     5   ECFABD ••FA•• ••F•B• •CF••• ••F••D E•F•••
       2     6   FEBCDA F••••A F•B••• F••C•• F•••D• FE••••
       3     5   ECFABD ••FA•• ••F•B• •CF••• ••F••D E•F•••
       4     6   FEBCDA F••••A F•B••• F••C•• F•••D• FE••••
       5     3   CAEDFB •A••F• ••••FB C•••F• •••DF• ••E•F•
       6     1   ADCBEF A••••F •••B•F ••C••F •D•••F ••••EF
       7     2   BFDEAC •F••A• BF•••• •F•••C •FD••• •F•E••
       8     4   DBAFCE ••AF•• •B•F•• •••FC• D••F•• •••F•E
       9     3   CAEDFB •A••F• ••••FB C•••F• •••DF• ••E•F•
      10     2   BFDEAC •F••A• BF•••• •F•••C •FD••• •F•E••
      11     1   ADCBEF A••••F •••B•F ••C••F •D•••F ••••EF
      12     4   DBAFCE ••AF•• •B•F•• •••FC• D••F•• •••F•E
      13     4   DBAFCE ••AF•• •B•F•• •••FC• D••F•• •••F•E
      14     3   CAEDFB •A••F• ••••FB C•••F• •••DF• ••E•F•
      15     1   ADCBEF A••••F •••B•F ••C••F •D•••F ••••EF
      16     2   BFDEAC •F••A• BF•••• •F•••C •FD••• •F•E••
      17     5   ECFABD ••FA•• ••F•B• •CF••• ••F••D E•F•••
      18     2   BFDEAC •F••A• BF•••• •F•••C •FD••• •F•E••
      19     6   FEBCDA F••••A F•B••• F••C•• F•••D• FE••••
      20     3   CAEDFB •A••F• ••••FB C•••F• •••DF• ••E•F•

Reference             : F
Tests                 : A, B, C, D, E
Sequences             : ADCBEF, BFDEAC, CAEDFB, DBAFCE, ECFABD, FEBCDA
Subjects per sequence : 3 | 4 | 4 | 3 | 3 | 3 (imbalanced)
Estimated sample size : 20
Achieved power        : 0.8347
Randomized            : 2022-07-13 22:55:28 CEST
Seed                  : 820697
Runtime               : 30 ms (1 call for balance of IBDs)

Since we are working in a regulated environment, perhaps you will be asked by an inspector how you performed the randomization. You need to provide the sample size, all sequences and the seed to reproduce it. Try:

library(randomizeBE)
RL4(n = 20, seqs = c("ADCBEF", "BFDEAC", "CAEDFB", "DBAFCE", "ECFABD", "FEBCDA"),
    seed = 820697)

Note that in this case you get at the end:

Warning message:
Unbalanced design! # of subj. in sequences  ADCBEF  BFDEAC  CAEDFB  DBAFCE  ECFABD  FEBCDA:  3  4  4  3  3  3

Is that important? Although we are interested in balanced IBDs – which they are – they are not balanced for period effects. Let’s check how often the treatments are administered in each period:

  p1 p2 p3 p4 p5 p6
A  3  4  3  3  4  3
B  4  3  3  3  3  4
C  4  3  3  3  3  4
D  3  3  4  4  3  3
E  3  3  4  4  3  3
F  3  4  3  3  4  3

If you want period-balance (which is a desirable property), use bal = TRUE in the call:

tmp <- make.ibds(ntmt = 6, CV = 0.2, print = FALSE, bal = TRUE)
cat(tmp$txt, "\n")
Reference                : F
Tests                    : A, B, C, D, E
Sequences                : AEBDFC, BAFECD, CFDBEA, DCEFAB, EDACBF, FBCADE
Subjects per sequence    : 4 | 4 | 4 | 4 | 4 | 4 (balanced)
Estimated sample size    : 20
Achieved power           : 0.8347
Adjustment to obtain period-balance
 Adjusted sample size    : 24
 Achieved power          : 0.8960
Randomized               : 2022-07-15 11:13:50 CEST
Seed                     : 9503537
Runtime                  : 20 ms (1 call for balance of IBDs)

There is no free lunch; we need 24 subjects instead of 20.

If you are interested in the performance:

calls <- et <- integer(1000)
for (j in seq_along(calls)) {
  tmp      <- make.ibds(ntmt = 6, CV = 0.2, print = FALSE)
  calls[j] <- as.integer(tmp$calls)
  et[j]    <- as.integer(tmp$et)
}
probs <- c(0, 0.025, 0.25, 0.5, 0.75, 0.975, 1)
perf  <- matrix(data = c(quantile(calls, probs = probs),
                         round(quantile(et, probs = probs))),
                         nrow = 2, ncol = length(probs), byrow = TRUE,
                dimnames = list(c("calls", "runtimes (ms)"),
                                paste0(100 * probs, "%")))
print(perf)

On my aged machine:

              0% 2.5% 25% 50% 75% 97.5% 100%
calls          1    1   3   7  14    35   59
runtimes (ms)  0   10  30  70 140   360  610


More arguments (NTID, T/R-ratio 0.975, power ≥90%, adjusting for anticipated dro­pout-rate 5%, runtime suppressed):

make.ibds(ntmt = 4, CV = 0.1, theta0 = 0.975, theta1 = 0.9, target = 0.9,
          do.rate = 0.05, sep = "–", details = FALSE)
 subject seqno sequence IBD 1 IBD 2 IBD 3
       1     2     BDAC  –DA–  BD––  –D–C
       2     3     CADB  –AD–  ––DB  C–D–
       3     1     ABCD  A––D  –B–D  ––CD
       4     4     DCBA  D––A  D–B–  DC––
       5     3     CADB  –AD–  ––DB  C–D–
       6     4     DCBA  D––A  D–B–  DC––
       7     1     ABCD  A––D  –B–D  ––CD
       8     2     BDAC  –DA–  BD––  –D–C
       9     1     ABCD  A––D  –B–D  ––CD
      10     4     DCBA  D––A  D–B–  DC––
      11     3     CADB  –AD–  ––DB  C–D–
      12     3     CADB  –AD–  ––DB  C–D–
      13     4     DCBA  D––A  D–B–  DC––
      14     1     ABCD  A––D  –B–D  ––CD
      15     2     BDAC  –DA–  BD––  –D–C
      16     2     BDAC  –DA–  BD––  –D–C
      17     2     BDAC  –DA–  BD––  –D–C
      18     2     BDAC  –DA–  BD––  –D–C
      19     4     DCBA  D––A  D–B–  DC––
      20     1     ABCD  A––D  –B–D  ––CD
      21     4     DCBA  D––A  D–B–  DC––
      22     1     ABCD  A––D  –B–D  ––CD
      23     3     CADB  –AD–  ––DB  C–D–
      24     3     CADB  –AD–  ––DB  C–D–
      25     3     CADB  –AD–  ––DB  C–D–
      26     1     ABCD  A––D  –B–D  ––CD
      27     2     BDAC  –DA–  BD––  –D–C
      28     1     ABCD  A––D  –B–D  ––CD
      29     4     DCBA  D––A  D–B–  DC––
      30     4     DCBA  D––A  D–B–  DC––
      31     3     CADB  –AD–  ––DB  C–D–
      32     2     BDAC  –DA–  BD––  –D–C

Reference                : D
Tests                    : A, B, C
Sequences                : ABCD, BDAC, CADB, DCBA
Subjects per sequence    : 8 | 8 | 8 | 8 (balanced)
Estimated sample size    : 30
Achieved power           : 0.9167
 Anticipated dropout-rate: 5%
 Adjusted sample size    : 32
 Achieved power          : 0.9318
Randomized               : 2022-07-13 23:02:13 CEST
Seed                     : 874461


You can also assign the stuff to a list-variable for checking. No counting any more. ;-)

tmp <- make.ibds(ntmt = 6, CV = 0.2)
tmp[2:6]
$n
[1] 20

$sequences
[1] "ACFDEB" "BEDFCA" "CDABFE" "DBCEAF" "EFBADC" "FAECBD"

$n.seq
[1] 4 4 3 3 3 3

$info
[1] "imbalanced"

$ibds
      seq  n seq  n
IBD 1  AF 10  FA 10
IBD 2  BF 10  FB 10
IBD 3  CF 10  FC 10
IBD 4  DF 10  FD 10
IBD 5  EF 10  FE 10


Comparison of one test with two references:

make.ibds(ntmt = 3, CV = 0.15, ref = c("B", "C"), sep = "–")
 subject seqno sequence IBD 1 IBD 2
       1     3      BAC   BA–   –AC
       2     3      BAC   BA–   –AC
       3     1      ABC   AB–   A–C
       4     6      CBA   –BA   C–A
       5     1      ABC   AB–   A–C
       6     2      ACB   A–B   AC–
       7     6      CBA   –BA   C–A
       8     5      CAB   –AB   CA–
       9     4      BCA   B–A   –CA
      10     4      BCA   B–A   –CA
      11     5      CAB   –AB   CA–
      12     2      ACB   A–B   AC–

References            : B, C
Test                  : A
Sequences             : ABC, ACB, BAC, BCA, CAB, CBA
Subjects per sequence : 2 | 2 | 2 | 2 | 2 | 2 (balanced)
Estimated sample size : 12
Achieved power        : 0.8305
Randomized            : 2022-07-13 23:04:24 CEST
Seed                  : 9664508
Runtime               : 20 ms (1 call for balance of IBDs)


If all pairwise comparisons are required (i.e., additionally between the tests), the number rises quickly:
$$N_\text{comp}=\frac{n!}{2\,(n-2)!}$$ For six treatments we have already 15 pairwise comparisons.


make.ibds <- function(ntmt = 3, CV, alpha = 0.05, theta0 = 0.95,
                      theta1, theta2, target = 0.80, ref, do.rate = 0,
                      bal = FALSE, sep = "•", print = TRUE, details = TRUE) {
  ##################################################################
  # Estimates the sample size of a Higher-Order (Williams’) design #
  # to be evaluated by the ‘Two at a Time’ approach, i.e., with    #
  # extracted Incomplete Block Designs. All IBDs will be balanced. #
  # Note: Balanced IBDs cannot be extracted from Latin Squares.    #
  # Optionally the estimated sample size can be adjusted for an    #
  # anticipated dropout-rate and/or uprounded to obtain balanced   #
  # sequences.                                                     #
  # -------------------------------------------------------------- #
  # TODO: All pairwise comparisons, i.e., additionally between the #
  #       tests.                                                   #
  ##################################################################

  if (ntmt < 3) stop("ntmt must be at least 3.")
  if (missing(CV)) stop("CV must be given.")
  if (missing(theta1) & missing(theta2)) theta1 <- 0.80
  if (missing(theta1)) theta1 <- 1/theta2
  if (missing(theta2)) theta2 <- 1/theta1
  if (theta0 < theta1 | theta0 > theta2) stop("theta0 must lie within BE limits.")
  if (target <= 0.5) stop("target ", target, " does not make sense.")
  if (alpha > 0.5) stop("alpha ", alpha, " does not make sense.")
  make.equal <- function(n, ns) {
    return(as.integer(ns * (n %/% ns + as.logical(n %% ns))))
  }
  nadj <- function(n, do.rate, ns) {
    return(as.integer(make.equal(n / (1 - do.rate), ns)))
  }
  suppressMessages(require(PowerTOST))
  suppressMessages(require(randomizeBE))
  st     <- proc.time()[[3]]
  # Sample size for evaluation by ‘Two at a Time’ approach.
  # Note that we are using the default design = "2x2".

  smpl   <- sampleN.TOST(alpha = alpha, CV = CV, theta0 = theta0,
                         theta1 = theta1, theta2 = theta2,
                         targetpower = target, print = FALSE)
  n.orig <- smpl[["Sample size"]]
  p.orig <- smpl[["Achieved power"]]
  # Get sequences of a Williams’ design.
  seqs   <- williams(ntmt)
  ns     <- 2 # because 2x2!
  # Adjust for dropout-rate.
  n      <- nadj(n.orig, do.rate, ns)
  # Eventual uprounding if balance is requested.
  if (bal) n <- make.equal(n, length(seqs))
  pwr    <- power.TOST(alpha = alpha, CV = CV, theta0 = theta0,
                       theta1 = theta1, theta2 = theta2, n = n)
  # Extract treatment codes.
  trts   <- sort(
              unique(
                unlist(
                  strsplit(Reduce(function(x, y) paste0(x, y), seqs), ""))))
  if (missing(ref)) { # The last treatment in lexical order is the reference.
    s   <- Reduce(function(x, y) paste0(x, y), trts)
    ref <- substr(s, nchar(s), nchar(s))
  }
  if (sum(ref %in% trts) == 0)
    stop("ref \'", ref, "\'not in trts \'", trts, "\'")
  # Extract test(s).
  tests  <- trts[!trts %in% ref]
  calls  <- 0
  repeat { # Call randomizations until all IBDs are balanced.
    # Get a randomization list.
    calls <- calls + 1
    tmp   <- suppressWarnings( # Not relevant for TaT.
               RL4(nsubj = n, seqs = seqs, randctrl = FALSE))
    rand  <- tmp$rl
    # Generate columns for the IBDs.
    n.ibd <-  length(tests) * length(ref)
    for (j in 1:n.ibd) {
      rand[[paste0("IBD.", j)]] <- NA
    }
    # Build the IBDs.
    for (j in 1:nrow(rand)) {
      c <- 3
      for (k in seq_along(ref)) {
        for (m in seq_along(tests)) {
          c          <- c  + 1
          excl       <- tests[!tests == tests[m]]
          excl       <- c(excl, ref[!ref == ref[k]])
          excl       <- paste0("[", paste(excl, collapse = ", "), "]")
          rand[j, c] <- gsub(excl, sep, rand$sequence[j])
        }
      }
    }
    # Check the IBDs for balance.
    checks  <- NA
    ibd.seq <- as.data.frame(
                 matrix(data = NA,
                        nrow = n.ibd,
                        ncol = 4, byrow = TRUE,
                        dimnames = list(names(rand)[4:ncol(rand)],
                                        paste0(rep(c("seq.", "n."), 2),
                                               rep(1:2, each = 2)))))
    for (j in 1:n.ibd) {
      ibd                 <- gsub("[^[:alnum:], ]", "", rand[3 + j])
      ibd                 <- gsub("c", "", ibd)
      ibd                 <- gsub(" ", "", unlist(strsplit(ibd, ",")))
      ibd.seq[j, c(1, 3)] <- sort(unique(ibd))
      ibd.seq[j, c(2, 4)] <- c(length(ibd[ibd == sort(unique(ibd))[1]]),
                               length(ibd[ibd == sort(unique(ibd))[2]]))
      checks[j]           <- ibd.seq[j, 2] == ibd.seq[j, 4]
    }
    if (sum(checks) == length(trts) - 1) break # Success!
  }
  ifelse (do.rate == 0, f <- "\n%-22s: ", f <- "\n%-25s: ") # Cosmetics.
  if (bal) f <- "\n%-25s: "
  ifelse (length(ref) == 1,
    txt <- paste0(sprintf(f, "Reference"), ref),
    txt <- paste0(sprintf(f, "References"), paste(ref, collapse = ", ")))
  ifelse (length(tests) == 1,
    txt <- paste0(txt, sprintf(f, "Test"), tests),
    txt <- paste0(txt, sprintf(f, "Tests"), paste(tests, collapse = ", ")))
  txt    <- paste0(txt, sprintf(f, "Sequences"), paste(seqs, collapse = ", "))
  txt    <- paste0(txt, sprintf(f, "Subjects per sequence"),
                   paste(tmp$ninseqs, collapse = " | "))
  ifelse (length(unique(tmp$ninseqs)) == 1,
    info <- "balanced", info <- "imbalanced")
  txt    <- paste0(txt, " (", info, ")")
  txt    <- paste0(txt, sprintf(f, "Estimated sample size"), n.orig,
                   sprintf(f, "Achieved power"), sprintf("%.4f", p.orig))
  if (!n == n.orig) {
    if (bal) {
      txt <- paste0(txt, "\nAdjustment to obtain period-balance")
    }
    if (do.rate > 0) {
      txt <- paste0(txt, sprintf("\n %-24s: ", "Anticipated dropout-rate"),
                    sprintf("%.3g%%", 100 * do.rate))
    }
    txt <- paste0(txt, sprintf("\n %-24s: ", "Adjusted sample size"), n,
                  sprintf("\n %-24s: ", "Achieved power"), sprintf("%.4f", pwr))
  }
  txt    <- paste0(txt, sprintf(f, "Randomized"), format(tmp$date, usetz = TRUE),
                   sprintf(f, "Seed"), tmp$seed)
  ibd    <- names(rand)[4:ncol(rand)]
  names(rand)[4:ncol(rand)] <- gsub("\\.", " ", ibd)
  row.names(ibd.seq)        <- names(rand)[4:ncol(rand)]
  names(ibd.seq)            <- rep(c("seq", "n"), 2)
  et     <- signif(1000 * (proc.time()[[3]] - st), 3)
  if (details) {          # Only if requested.
    ifelse (et < 10, rt <- "<10 ms", rt <- paste(et, "ms"))
    txt <- paste0(txt, sprintf(f, "Runtime"), rt, " (", calls, " call")
    if (calls > 1) txt <- paste0(txt, "s")
    txt <- paste(txt, "for balance of IBDs)")
  }
  if (print) {            # To the console.
    print(rand, row.names = FALSE)
    txt <- paste0(txt, "\n")
    cat(txt)
  }
  # Results as a list.
  return(invisible(list(rand = rand, n = n, sequences = seqs,
                        n.seq = tmp$ninseqs, info = info,
                        ibds = ibd.seq, seed = tmp$seed,
                        calls = calls, et = et,
                        txt = substr(txt, 2, nchar(txt)))))
}


Dif-tor heh smusma 🖖🏼 Довге життя Україна! [image]
Helmut Schütz
[image]

The quality of responses received is directly proportional to the quality of the question asked. 🚮
Science Quotes
Shuanghe
★★  

Spain,
2022-07-13 17:35
(203 d 02:58 ago)

@ Helmut
Posting: # 23142
Views: 675
 

 ‘Two-at-a-Time’ approach; ≥3 treat­ments: new 🇷 script

Hi Helmut,

BioBridges (22/23 September) as well?


Sure! It would be very nice to see all the familiar faces. Actually I searched the forum several time before but couldn't find any information on BioBridges this year. You used to have a post called eventlist each year and BioBridges is usually on it but it seems this year is an exception.

[image]:thumb up: THX for discovering such a nasty bug!

:-D

If debugging is the process of removing bugs,

then programming must be the process of putting them in.    [image] Edsger W. Dijkstra


Damn, looking at my own code, I must be a bug farmer then :lol3:...


make.ibds <- function(ntmt = 3, CV, theta0 = 0.95, theta1, theta2,

                      target = 0.80, do.rate = 0, bal = FALSE,

                      sep = "•", print = TRUE, details = TRUE) {... }


Thanks. I'll save the code to read it later.

All the best,
Shuanghe
Helmut
★★★
avatar
Homepage
Vienna, Austria,
2022-07-13 18:46
(203 d 01:47 ago)

@ Shuanghe
Posting: # 23143
Views: 729
 

 You’re a NE🇷D!

Hi Shuanghe,

❝ […] I searched the forum several time before but couldn't find any information on BioBridges this year. You used to have a post called eventlist each year and BioBridges is usually on it but it seems this year is an exception.


Sorry, too busy with crazy stuff.

❝ Damn, looking at my own code, I must be a bug farmer then :lol3:...


Welcome to the club!

❝ I'll save the code to read it later.


Reading code? Proves that you are real NE?D!
Don’t get obstipation of too much Spaghetti viennese (i.e., my coding style).

I have stopped reading Stephen King novels.
Now I just read C code instead.
      Richard A. O’Keefe



PS: I edited the script. Use the new one. See also there: Scroll down and click Show/hide details.

Dif-tor heh smusma 🖖🏼 Довге життя Україна! [image]
Helmut Schütz
[image]

The quality of responses received is directly proportional to the quality of the question asked. 🚮
Science Quotes
UA Flag
Activity
 Admin contact
22,478 posts in 4,708 threads, 1,603 registered users;
14 visitors (0 registered, 14 guests [including 8 identified bots]).
Forum time: 19:34 CET (Europe/Vienna)

I think it is much more interesting to live with uncertainty
than to live with answers that might be wrong.    Richard Feynman

The Bioequivalence and Bioavailability Forum is hosted by
BEBAC Ing. Helmut Schütz
HTML5