SE.EQ-package {SE.EQ}R Documentation

SE-Test for Equivalence

Description

Implements the SE-test for equivalence according to Hoffelder et al. (2015) <DOI:10.1080/10543406.2014.920344>. The SE-test for equivalence is a multivariate two-sample equivalence test. Distance measure of the test is the sum of standardized differences between the expected values or in other words: the sum of effect sizes (SE) of all components of the two multivariate samples. The test is an asymptotically valid test for normally distributed data (see Hoffelder et al.,2015). The function SE.EQ() implements the SE-test for equivalence according to Hoffelder et al. (2015). The function SE.EQ.dissolution.profiles() implements a variant of the SE-test for equivalence for similarity analyses of dissolution profiles as mentioned in Suarez-Sharp et al.(2020) <DOI:10.1208/s12248-020-00458-9>). The equivalence margin used in SE.EQ.dissolution.profiles() is analogically defined as for the T2EQ approach according to Hoffelder (2019) <DOI:10.1002/bimj.201700257>) by means of a systematic shift in location of 10 [% of label claim] of both dissolution profile populations. SE.EQ.dissolution.profiles() checks whether the weighted mean of the differences of the expected values of both dissolution profile populations is statistically significantly smaller than 10 [% of label claim]. The weights are built up by the inverse variances.

Details

The DESCRIPTION file:

Package: SE.EQ
Type: Package
Title: SE-Test for Equivalence
Version: 1.0
Date: 2020-10-06
Author: Thomas Hoffelder
Maintainer: Thomas Hoffelder <thomas.hoffelder@boehringer-ingelheim.com>
Description: Implements the SE-test for equivalence according to Hoffelder et al. (2015) <DOI:10.1080/10543406.2014.920344>. The SE-test for equivalence is a multivariate two-sample equivalence test. Distance measure of the test is the sum of standardized differences between the expected values or in other words: the sum of effect sizes (SE) of all components of the two multivariate samples. The test is an asymptotically valid test for normally distributed data (see Hoffelder et al.,2015). The function SE.EQ() implements the SE-test for equivalence according to Hoffelder et al. (2015). The function SE.EQ.dissolution.profiles() implements a variant of the SE-test for equivalence for similarity analyses of dissolution profiles as mentioned in Suarez-Sharp et al.(2020) <DOI:10.1208/s12248-020-00458-9>). The equivalence margin used in SE.EQ.dissolution.profiles() is analogically defined as for the T2EQ approach according to Hoffelder (2019) <DOI:10.1002/bimj.201700257>) by means of a systematic shift in location of 10 [% of label claim] of both dissolution profile populations. SE.EQ.dissolution.profiles() checks whether the weighted mean of the differences of the expected values of both dissolution profile populations is statistically significantly smaller than 10 [% of label claim]. The weights are built up by the inverse variances.
Imports: MASS
License: GPL-3

Index of help topics:

SE.EQ                   The SE-test for equivalence
SE.EQ-package           SE-Test for Equivalence
SE.EQ.dissolution.profiles
                        The SE-test for equivalence for dissolution
                        profile similarity analyses
ex_data_JoBS            Example dataset from Hoffelder et al. (2015)

Author(s)

Thomas Hoffelder

Maintainer: Thomas Hoffelder <thomas.hoffelder@boehringer-ingelheim.com>

References

EMA (2010). Guidance on the Investigation of Bioequivalence. European Medicines Agency, CHMP, London. Doc. Ref.: CPMP/EWP/QWP/1401/98 Rev. 1/ Corr **. URL: https://www.ema.europa.eu/en/documents/scientific-guideline/guideline-investigation-bioequivalence-rev1_en.pdf

FDA (1997). Guidance for Industry: Dissolution Testing of Immediate Release Solid Oral Dosage Forms. Food and Drug Administration FDA, CDER, Rockville. URL: https://www.fda.gov/media/70936/download

Hoffelder, T., Goessl, R., Wellek, S. (2015). Multivariate Equivalence Tests for Use in Pharmaceutical Development. Journal of Biopharmaceutical Statistics, 25:3, 417-437. URL: http://dx.doi.org/10.1080/10543406.2014.920344

Hoffelder, T. (2019) Equivalence analyses of dissolution profiles with the Mahalanobis distance. Biometrical Journal, 61:5, 1120-1137. URL: https://doi.org/10.1002/bimj.201700257

Suarez-Sharp, S., Abend, A., Hoffelder, T., Leblond, D., Delvadia, P., Kovacs, E., Diaz, D.A. (2020). In Vitro Dissolution Profiles Similarity Assessment in Support of Drug Product Quality: What, How, When - Workshop Summary Report. The AAPS Journal, 22:74. URL: http://dx.doi.org/10.1208/s12248-020-00458-9

Examples

# A reproduction of the three-dimensional SE example evaluation 
# in Hoffelder et al. (2015) can be done with the following code:


data(ex_data_JoBS)
REF_JoBS <- cbind(ex_data_JoBS[ which(ex_data_JoBS$Group=='REF'), ]
                  [c("Diss_15_min","Diss_20_min","Diss_25_min")])
TEST_JoBS <- cbind(ex_data_JoBS[ which(ex_data_JoBS$Group=='TEST'), ]
                   [c("Diss_15_min","Diss_20_min","Diss_25_min")])
equivalence_margin_SE_JoBS <- 0.74^2
test_SE_JoBS <- SE.EQ(X=REF_JoBS
                      , Y=TEST_JoBS
                      , eq_margin=equivalence_margin_SE_JoBS
                      , print.results = TRUE)


# Apart from simulation errors, a recalculation of the SE results 
# of some parts (normal distribution only) of the simulation study in 
# Hoffelder et al. (2015) can be done with the following code. Please note that 
# the simulation takes approximately 20 minutes for 50.000 simulation 
# runs (number_of_simu_runs <- 50000). To shorten calculation time for 
# test users, number_of_simu_runs is set to 100 here and can/should be adapted.
# In the result of the simulation the variable empirical.size.se presents the
# simulated size obtained by function \code{SE.EQ()} whereas variable 
# empirical.size.se.disso shows the
# simulated size obtained by function \code{SE.EQ.dissolution.profiles()}. 
# A detailed analysis of the operating characteristics of the SE variant 
# implemented in \code{SE.EQ.dissolution.profiles()} is the content of 
# a future paper.

library(MASS)
number_of_simu_runs <- 100
set.seed(2020)

mu1 <- c(41,76,97)
mu2 <- mu1 - c(10,10,10)
SIGMA_1 <- matrix(data = c(537.4 , 323.8 , 91.8 ,
                           323.8 , 207.5 , 61.7 ,
                           91.8  , 61.7  , 26.1) ,ncol = 3)
SIGMA_2 <- matrix(data = c(324.1 , 233.6 , 24.5 ,
                           233.6 , 263.5 , 61.4 ,
                           24.5  , 61.4  , 32.5) ,ncol = 3)
SIGMA   <- matrix(data = c(430.7 , 278.7 , 58.1 ,
                           278.7 , 235.5 , 61.6 ,
                           58.1  , 61.6  , 29.3) ,ncol = 3)


SIMULATION_SIZE_SE <- function(disttype , Hom , Var , mu_1 , mu_2 
                               ,  n_per_group , n_simus ) {
  
  n_success_SE        <- 0
  n_success_SE_disso  <- 0
  if 	( Hom == "Yes" ) 	{
    COVMAT_1 <- SIGMA
    COVMAT_2 <- SIGMA
  }
  else 	   {
    COVMAT_1 <- SIGMA_1
    COVMAT_2 <- SIGMA_2
  } 
  if 	( Var == "Low" ) 	{
    COVMAT_1 <- COVMAT_1 / 4
    COVMAT_2 <- COVMAT_2 / 4
  }
  
  d <- ncol(COVMAT_1)
  Mean_diff <- mu_1 - mu_2                    # Difference of both exp. values
  vars_X      <- diag(COVMAT_1)               #  variances of first sample
  vars_Y      <- diag(COVMAT_2)               #  variances of second sample
  dist_SE     <- sum( (Mean_diff * Mean_diff) / (0.5 * (vars_X + vars_Y) ) )   
     # true SE distance and equivalence margin for SE.EQ
  
  
  if 	( n_per_group == 10 ) 	{
    cat("Expected value sample 1:",mu_1,"\n",
        "Expected value sample 2:",mu_2,"\n",
        "Covariance matrix sample 1:",COVMAT_1,"\n",
        "Covariance matrix sample 2:",COVMAT_2,"\n",
        "EM_SE:",dist_SE,"\n")
  }  
  
  for (i in 1:n_simus)  {
    if 	( disttype == "Normal" ) 	{
      REF <- mvrnorm(n = n_per_group, mu=mu_1, Sigma=COVMAT_1)
      TEST<- mvrnorm(n = n_per_group, mu=mu_2, Sigma=COVMAT_2)
    }
    n_success_SE_disso  <- n_success_SE_disso + 
                            SE.EQ.dissolution.profiles( X = REF ,
                                                        Y = TEST , 
                                                        print.results = FALSE
                                                      )$testresult.num
    n_success_SE        <- n_success_SE   + 
                            SE.EQ( X=REF , 
                                   Y=TEST , 
                                   eq_margin = dist_SE , 
                                   print.results = FALSE
                                  )$testresult.num
  }
  empirical_succ_prob_SE        <- n_success_SE / n_simus  
  empirical_succ_prob_SE_disso  <- n_success_SE_disso / n_simus  
  simuresults  <- data.frame(dist = disttype , Hom = Hom , Var = Var 
                     , dimension = d , em_se = dist_SE 
                     , sample.size = n_per_group 
                     , empirical.size.se = empirical_succ_prob_SE
                     , empirical.size.se.disso = empirical_succ_prob_SE_disso)
}

SIMULATION_LOOP_SAMPLE_SIZE <- function(disttype , Hom , Var 
                                        , mu_1 , mu_2 ,  n_simus ) {
  
  run_10  <- SIMULATION_SIZE_SE(disttype = disttype , Hom = Hom , Var = Var 
                                  , mu_1 = mu_1 , mu_2 = mu_2 
                                  , n_per_group = 10 , n_simus = n_simus) 
  run_30  <- SIMULATION_SIZE_SE(disttype = disttype , Hom = Hom , Var = Var 
                                  , mu_1 = mu_1 , mu_2 = mu_2 
                                  , n_per_group = 30 , n_simus = n_simus) 
  run_50  <- SIMULATION_SIZE_SE(disttype = disttype , Hom = Hom , Var = Var 
                                  , mu_1 = mu_1 , mu_2 = mu_2 
                                  , n_per_group = 50 , n_simus = n_simus) 
  run_100 <- SIMULATION_SIZE_SE(disttype = disttype , Hom = Hom , Var = Var 
                                  , mu_1 = mu_1 , mu_2 = mu_2 
                                  , n_per_group = 100 , n_simus = n_simus) 
  RESULT_MATRIX <- rbind(run_10 , run_30 , run_50 , run_100)
  RESULT_MATRIX                               
}

simu_1 <- SIMULATION_LOOP_SAMPLE_SIZE(disttype = "Normal", Hom = "Yes" 
                                      , Var = "High" , mu_1 = mu1  , mu_2 = mu2
                                      , n_simus = number_of_simu_runs)
simu_2 <- SIMULATION_LOOP_SAMPLE_SIZE(disttype = "Normal", Hom = "Yes" 
                                      , Var = "Low" , mu_1 = mu1  , mu_2 = mu2
                                      , n_simus = number_of_simu_runs)
simu_3 <- SIMULATION_LOOP_SAMPLE_SIZE(disttype = "Normal", Hom = "No" 
                                      , Var = "High" , mu_1 = mu1  , mu_2 = mu2
                                      , n_simus = number_of_simu_runs)
simu_4 <- SIMULATION_LOOP_SAMPLE_SIZE(disttype = "Normal", Hom = "No" 
                                      , Var = "Low" , mu_1 = mu1  , mu_2 = mu2 
                                      , n_simus = number_of_simu_runs)

FINAL_RESULT <- rbind(simu_1 , simu_2 , simu_3 , simu_4)

cat("******  Simu results n_simu_runs: ",number_of_simu_runs,"   ***** \n")
FINAL_RESULT

[Package SE.EQ version 1.0 Index]