This tutorial presupposes having read the general
introduction to `POSSA`

. Here, that introduction is
extended for cases of multiple hypotheses, i.e., where each analysis (at
a given “look”) involves multiple significance tests, and hence multiple
p values.

To verify that such relatively more complex used-defined functions
were written correctly, it is useful to store some descriptive data,
such as the means and SDs of the variables, or correlations, effect
sizes, etc. Relatedly, in real cases, one should ideally test a variety of possible
scenarios: for this, `POSSA`

provides the possibility to
easily run multiple simulations with varying factors (e.g., varying
correlations, effect sizes, etc.).

Hence, recording descriptives and using varying factors will be
explained here first, after which testing for multiple hypotheses is
presented. This completes (together with the intro)
everything important that needs to be known for using
`POSSA`

.

`library('POSSA')`

Let’s take again a t-test. The sample generating function is like before.

```
= function(sampleSize) {
customSample list(
sample1 = rnorm(sampleSize, mean = 0, sd = 10),
sample2_h0 = rnorm(sampleSize, mean = 0, sd = 10),
sample2_h1 = rnorm(sampleSize, mean = 5, sd = 10)
) }
```

However, the test function will return, apart from the p values, some
additional information. For a simple example, let’s calculate the mean
difference in case of both H0 and H1, and store it as
`meanDiffH0`

and `meanDiffH1`

(but any other
custom name could just as well be given – except for the notation
reserved for p values, i.e., starting with `p_`

and ending
with `_h0`

/`_h1`

).

```
= function(sample1, sample2_h0, sample2_h1) {
customTestWithMeans c(
p_h0 = t.test(sample1, sample2_h0, 'less', var.equal = TRUE)$p.value,
p_h1 = t.test(sample1, sample2_h1, 'less', var.equal = TRUE)$p.value,
meanDiffH0 = mean(sample1 - sample2_h0),
meanDiffH1 = mean(sample1 - sample2_h1)
) }
```

See that it’s working:

```
do.call(customTestWithMeans, customSample(85))
#> p_h0 p_h1 meanDiffH0 meanDiffH1
#> 1.291100e-01 7.749566e-06 -1.659895e+00 -6.799983e+00
```

Now the simulation as before.

```
= sim(fun_obs = customSample,
dfPvalsAndMeans n_obs = c(27, 54, 81),
fun_test = customTestWithMeans)
```

The power calculation could be accessed as before via
`pow`

, e.g. `pow(dfPvalsAndMeans)`

, and the
results will be the same – but that’s not important here.

What’s important is that the `dfPvalsAndMeans`

can be
printed, and the factors will be automatically shown.

```
print(dfPvalsAndMeans)
#> POSSA sim() results (p values)
#> Sample:
#> .iter .look .n_total sample1 sample2_h p_h0 p_h1 meanDiffH0 meanDiffH1
#> 1: 1 1 54 27 27 0.57639929 0.0206001019 0.5229246 -5.886524
#> 2: 1 2 108 54 54 0.02521739 0.0001540393 -3.9918067 -6.850594
#> 3: 1 3 162 81 81 0.04303389 0.0000339881 -2.7414472 -6.031900
#> Descriptives:
#> meanDiffH0:
#> Min. 1st Qu. Median Mean 3rd Qu. Max.
#> -11.208494 -1.331260 0.003347 0.006194 1.346132 12.649740
#> meanDiffH1:
#> Min. 1st Qu. Median Mean 3rd Qu. Max.
#> -15.704 -6.350 -5.006 -5.005 -3.656 5.699
```

Note that entering a variable name, in this case
`dfPvalsAndMeans`

, in the console (or, in RStudio,
highlighting it and pressing *Ctrl*+*Enter*) is equivalent
to using the `print()`

function.

As can be seen, summary information for the additional values
returned from the testing function is automatically printed (under a
“Descriptives” title). When only p values are included, no such
information is printed. For some optional arguments, such as to change
the function used for summary information, see
`?print.possa_sim_df`

. Some examples are also given
below.

```
print(dfPvalsAndMeans, descr_cols = 'meanDiffH0')
print(dfPvalsAndMeans, descr_cols = c('meanDiffH0', '.n_total'))
print(dfPvalsAndMeans, descr_func = sd)
print(dfPvalsAndMeans, group_by = '.look')
```

Of course, one can also just check the data frame directly via other
functions, including plots. For example,
`neatStats::peek_neat(dfPvalsAndMeans, c("meanDiffH0", "meanDiffH1"), group_by = c('.look'))`

.

To get some additional information from each mean comparison, here is an extended “t-test” function that returns, apart from the p value, the mean difference, correlation, and the standardized mean difference (SMD; Cohen’s d).

```
= function(x, y) {
tTestPlus = stats::t.test(x, y, paired = TRUE, var.equal = T)
t_info = sd(x)
sdx = sd(y)
sdy = cor(x, y)
corr = sqrt((sdx ** 2 + sdy ** 2) - 2 * corr * sdx * sdy)
sd_p return(
list(
pval = t_info$p.value,
mean_diff = as.numeric(t_info$estimate),
corr = corr,
smd = as.numeric(t_info$estimate) / sd_p
)
) }
```

Now, the function for sample generation. What’s new here is that,
apart from the sample size (here: `sampSize`

), there are two
additional parameters, `meanH1`

and `corrH1`

,
which specify the mean difference and the correlation, respectively, for
the differing variable in case of H1.

```
= function(sampSize, meanH1, corrH1) {
sampFun = faux::rnorm_multi(
correlatedSamples n = sampSize,
vars = 3,
mu = c(0, 0, meanH1),
sd = 5,
r = c(corrH1, corrH1, 0)
)list(
GRP_v1 = correlatedSamples$X1,
GRP_v2_h0 = correlatedSamples$X2,
GRP_v2_h1 = correlatedSamples$X3
) }
```

And then the test function that return related additional information (using the extra t-test function): the mean differences, correlations, and SMDs, per H0 and H1.

```
= function(GRP_v1, GRP_v2_h0, GRP_v2_h1) {
testFun = tTestPlus(GRP_v2_h0, GRP_v1)
t0 = tTestPlus(GRP_v2_h1, GRP_v1)
t1 return(
c(
p_h0 = t0$pval,
meanDiffValueH0 = t0$mean_diff,
corrValueH0 = t0$corr,
smdValueH0 = t0$smd,
p_h1 = t1$pval,
meanDiffValueH1 = t1$mean_diff,
corrValueH1 = t1$corr,
smdValueH1 = t1$smd
)
) }
```

(Check via `do.call(testFun, sampFun(30, 2, 0.5))`

.)

The `sim()`

function works the same as before, except that
the varying factors `meanH1`

and `corrH1`

(the
parameters in `sampFun`

) need to be provided as part of
`fun_obs`

: instead of assigning a function as
e.g. `fun_obs = sampFun`

, a list is needed where the first
element is the function (`sampFun`

), and the rest of the
elements specify the varying factor via their names and their
corresponding values in a vector.

Hence, if one intends to see, for instance, all the combinations of
mean differences of `1.5`

, `2.5`

,
`3.5`

, and correlations of `0`

and
`0.5`

, this can be specified as
`fun_obs = list(sampFun, meanH1 = c(1.5, 2.5, 3.5), corrH1 = c(0, 0.5))`

.

(Given 2x3 = 6 combinations, each with the default 45000 iterations,
this simulation would take a while – for quick testing, one can set
e.g. `n_iter = 500`

.)

```
= sim(
dfPvalsFacts fun_obs = list(
sampFun,meanH1 = c(1.5, 2.5, 3.5),
corrH1 = c(0, 0.5)
),n_obs = c(27, 54, 81),
fun_test = testFun,
n_iter = 500
)
```

Now, simply by printing `dfPvalsFacts`

, one can check the
means, correlations, and SMDs, for each factor combination, to make sure
everything is as intended; it’s also reassuring to see that each look
has similar values (e.g., correlations) for each combination;
`print(dfPvalsFacts, group_by = c('.look', 'corrH1'), descr_cols = 'corrValueH1')`

.
Or, again, some plots can give quick insight:
`neatStats::peek_neat(dfPvalsFacts, c("corrValueH0", "corrValueH1"), group_by = c('corrH1', '.look'))`

;
`neatStats::peek_neat(dfPvalsFacts, c("smdValueH1"), group_by = c('meanH1', 'corrH1'))`

;
etc.

Finally, `POSSA::pow`

will calculate power for each factor
combination separately – otherwise behaving just the same as when no
varying factors are given.

```
pow(dfPvalsFacts)
#> # POSSA pow() results #
#> GROUP: pow_1.5_0
#> N(average-total) = 81.0 (if H0 true) or 81.0 (if H1 true)
#> (p) Type I error: .10000; Power: .43000
#> Local alphas: (1) none; (2) none; (3) .05000
#> Likelihoods of significance if H0 true: (1) 0; (2) 0; (3) .10000
#> Likelihoods of significance if H1 true: (1) 0; (2) 0; (3) .43000
#> GROUP: pow_1.5_0.5
#> N(average-total) = 81.0 (if H0 true) or 81.0 (if H1 true)
#> (p) Type I error: .07000; Power: .80000
#> Local alphas: (1) none; (2) none; (3) .05000
#> Likelihoods of significance if H0 true: (1) 0; (2) 0; (3) .07000
#> Likelihoods of significance if H1 true: (1) 0; (2) 0; (3) .80000
#> GROUP: pow_2.5_0
#> N(average-total) = 81.0 (if H0 true) or 81.0 (if H1 true)
#> (p) Type I error: .03000; Power: .84000
#> Local alphas: (1) none; (2) none; (3) .05000
#> Likelihoods of significance if H0 true: (1) 0; (2) 0; (3) .03000
#> Likelihoods of significance if H1 true: (1) 0; (2) 0; (3) .84000
#> ...
```

(For brevity here, only the three first combinations are printed.)

To check any one or subset of the factor combinations, the
`dfPvalsFacts`

data frame can be subset, as, for instance,
`pow(dfPvalsFacts[dfPvalsFacts$meanH1 == 1.5 & dfPvalsFacts$corrH1 == 0,])`

.

Alternatively, any of the data frames contained in the list returned
by `pow()`

can be printed separately. E.g.,
`all_pow_data = pow(dfPvalsFacts)`

; then
`print(all_pow_data$pow_1.5_0)`

.

It is possible to perform tests for multiple hypotheses where each
test’s sample is independent from all the other tests’ samples, and this
is possible to do with `POSSA`

too. However, the outcome is
fairly straightforward in such cases: each test will have its own power
and T1ER independently from the rest, and these can be rather easily
combined into some total power or T1ER values as needed. What is more
interesting is how correlated samples’ testing affect the global
outcomes (e.g., the likelihood that any of the tests give a false
positive finding). Hence, the focus here is on correlated samples.
Nonetheless, by removing the `grp_`

notation (see below),
independent tests can just as well be used in `POSSA`

.

Now, the `GRP`

notation served to designate a single group
within the entire analysis. This could have any additions in the name,
such as `GRP_x`

, `GRP_Y`

, etc., it makes no
difference. The lowercase `grp_`

notation on the other hand
will designate variables that belong to one of several groups, each of
which requires a specific group name following this `grp_`

prefix. For instance, to designate a group “`1`

”, the
variable name can be any of the following: `grp_1`

,
`grp_1_x`

, `grp_1_YZ`

. To designate a group
“`green`

”, the variable name can be any of the following:
`grp_green`

, `grp_green_y`

,
`grp_green_Xz`

. In other words, the name must be separated by
underscores, and the first element must be `grp`

, and the
second element the group name, which allows all group members to be
categorized together.

Let’s say we have two independent groups, and we want to compare two
different variables (`A`

and `B`

), that we suspect
will be correlated within each group. For simplicity, let’s say that in
each case, the baseline has a mean of zero, and the SESOI is a mean
difference of `5`

units. Since the baseline is identical in
either case, here it’s enough to assign it to one variable. To simulate
the correlation of the two variables in the other group, which is
expected to change (in case of H1), two pairs of correlated variables
have to be generated, one pair for H0, and one pair for H1.

```
= function(sampSize) {
multiSample = faux::rnorm_multi(
corr_vars_h0 n = sampSize,
vars = 2,
mu = 0,
sd = 10,
r = 0.6
)= faux::rnorm_multi(
corr_vars_h1 n = sampSize,
vars = 2,
mu = 5,
sd = 10,
r = 0.6
)= rnorm(sampSize, mean = 0, sd = 10)
v1 list(
grp_1_myTest_base = v1,
grp_2_myTestA_h0 = corr_vars_h0$X1,
grp_2_myTestA_h1 = corr_vars_h1$X1,
grp_2_myTestB_h0 = corr_vars_h0$X2,
grp_2_myTestB_h1 = corr_vars_h1$X2
) }
```

The `myTest`

/`myTestA`

/`myTestB`

parts of the names could each be named differently, everything would
work just the same.

The test function is then straightforward. (The p values do not have
to have the same mid-part names as the sample names,
e.g. `myTestA`

/`myTestB`

here, but it does help
clarity.)

```
= function(grp_1_myTest_base,
multiTest
grp_2_myTestA_h0,
grp_2_myTestA_h1,
grp_2_myTestB_h0,
grp_2_myTestB_h1) {c(
p_myTestA_h0 = t.test(grp_1_myTest_base, grp_2_myTestA_h0, 'less', var.equal = TRUE)$p.value,
p_myTestA_h1 = t.test(grp_1_myTest_base, grp_2_myTestA_h1, 'less', var.equal = TRUE)$p.value,
p_myTestB_h0 = t.test(grp_1_myTest_base, grp_2_myTestB_h0, 'less', var.equal = TRUE)$p.value,
p_myTestB_h1 = t.test(grp_1_myTest_base, grp_2_myTestB_h1, 'less', var.equal = TRUE)$p.value
) }
```

The `sim`

and `pow`

functions also essentially
work the same; only `pow`

will return separate results for
each test (each p value), as well as a “combined” result. This combined
result, by default, uses the `any`

option, meaning that, in
each set of analysis (performing the two t-tests), if *any* (in
this case: either) of the t-tests is significant (i.e., p value below
the specified alpha), this will be counted as a global “combined”
significant finding. Hence, for instance, in case of a single look
(fixed design), if either (or both) of the t-tests gives a significant
result in case of H0, the result of that analysis is counted as a type 1
error, increasing the combined T1ER. Similarly, either test’s
significance in case of H1 will mean that the analysis set is counted as
a correct finding, increasing combined power. (Hardly needs saying, the
combined T1ER and power greatly depends on the strength of the
correlation; e.g. here the rather high correlation lowers both.)

```
= sim(
dfPvalsMulti fun_obs = multiSample,
n_obs = c(27, 54, 81),
fun_test = multiTest
)
#> Note: Observation numbers groupped as "grp_1" for grp_1_myTest_base.
#> Note: Observation numbers groupped as "grp_2" for grp_2_myTestA_h0, grp_2_myTestA_h1, grp_2_myTestB_h0, grp_2_myTestB_h1.
pow(dfPvalsMulti)
#> # POSSA pow() results #
#> N(average-total) = 162.0 (if H0 true) or 162.0 (if H1 true)
#> (p_myTestA) Type I error: .05122; Power: .93807
#> Local alphas: (1) none; (2) none; (3) .05000
#> Likelihoods of significance if H0 true: (1) 0; (2) 0; (3) .05122
#> Likelihoods of significance if H1 true: (1) 0; (2) 0; (3) .93807
#> (p_myTestB) Type I error: .05056; Power: .93556
#> Local alphas: (1) none; (2) none; (3) .05000
#> Likelihoods of significance if H0 true: (1) 0; (2) 0; (3) .05056
#> Likelihoods of significance if H1 true: (1) 0; (2) 0; (3) .93556
#> Global ("combined significance") type I error: .07604 (included: p_myTestA, p_myTestB; power for reaching the "combined significance": .96751)
#> Likelihoods of stopping for (combined) significance if H0 true: (1) 0; (2) 0; (3) .07604
#> Likelihoods of stopping for (combined) significance if H1 true: (1) 0; (2) 0; (3) .96751
```

The method for combined results can be changed via
`multi_logic_global`

. For instance,
`pow(dfPvalsMulti, multi_logic_global = 'all')`

lowers both
global combined T1ER and power, because each evaluation is based on all
(here: both) tests being significant to count the set of analysis (two
t-tests) as a significant finding.

In case of a sequential design, the stopping logic is in principle
similar to the calculation of combined T1ER and power, although the
default here is `all`

: data collection is stopped only when
all included tests have significant results (i.e., p value below the
given local alpha).

```
pow(dfPvalsMulti, alpha_locals = NA)
#> # POSSA pow() results #
#> N(average-total) = 160.4 (if H0 true) or 108.1 (if H1 true)
#> (p_myTestA) Type I error: .03773; Power: .89769
#> Local alphas: (1) .02444; (2) .02444; (3) .02444
#> Likelihoods of significance if H0 true: (1) .01100; (2) .00802; (3) .01871
#> Likelihoods of significance if H1 true: (1) .33047; (2) .33673; (3) .23049
#> (p_myTestB) Type I error: .03767; Power: .89896
#> Local alphas: (1) .02444; (2) .02444; (3) .02444
#> Likelihoods of significance if H0 true: (1) .01100; (2) .00802; (3) .01864
#> Likelihoods of significance if H1 true: (1) .33047; (2) .33673; (3) .23176
#> Global ("combined significance") type I error: .05000 (included: p_myTestA, p_myTestB; power for reaching the "combined significance": .93847)
#> Likelihoods of stopping for (combined) significance if H0 true: (1) .01100; (2) .00802; (3) .03098
#> Likelihoods of stopping for (combined) significance if H1 true: (1) .33047; (2) .33673; (3) .27127
```

The stopping logic can be modified via `multi_logic_a`

.
E.g., setting `multi_logic_a = 'any'`

means that collection
is stopped whenever any of the included tests are significant; hence
stopping is more likely, the average expected observation number will
decrease, and the global combined T1ER and/or power will increase (see
`pow(dfPvalsMulti, alpha_locals = NA, multi_logic_a = 'any')`

– here, the combined T1ER remains `.05`

because of the
default adjustment procedure).

So far, `alpha_locals`

were assigned a single value (or
single vector) that was then applied for each test, which were detected
automatically. However, using a named list, one could also assign a
separate vector to each test – more specifically, to each p value, via
their root name (without the `_h0`

/`_h1`

endings).
Here, for instance, the
`p_myTestB_h0`

/`p_myTestB_h1`

can be assigned
local alphas as `p_myTestB = c(.01,.02,.04)`

; see an example
below with different local alphas for `p_myTestA`

and
`p_myTestB`

.

(To clearly show the assigned alpha values in the results, below
`adjust = FALSE`

is set, as the adjustment procedure would
otherwise multiply the all given local alphas so that the combined T1ER
would be at the specified level.)

```
pow(dfPvalsMulti,
alpha_locals = list(
p_myTestA = c(.02, .03, .03),
p_myTestB = c(.005, .02, .04)
),adjust = FALSE)
#> # POSSA pow() results #
#> N(average-total) = 161.2 (if H0 true) or 117.1 (if H1 true)
#> (p_myTestA) Type I error: .03749; Power: .90891
#> Local alphas: (1) .02000; (2) .03000; (3) .03000
#> Likelihoods of significance if H0 true: (1) .00302; (2) .00938; (3) .02509
#> Likelihoods of significance if H1 true: (1) .18542; (2) .46093; (3) .26256
#> (p_myTestB) Type I error: .04604; Power: .92553
#> Local alphas: (1) .00500; (2) .02000; (3) .04000
#> Likelihoods of significance if H0 true: (1) .00302; (2) .00938; (3) .03364
#> Likelihoods of significance if H1 true: (1) .18542; (2) .46093; (3) .27918
#> Global ("combined significance") type I error: .05958 (included: p_myTestA, p_myTestB; power for reaching the "combined significance": .95367)
#> Likelihoods of stopping for (combined) significance if H0 true: (1) .00302; (2) .00938; (3) .04718
#> Likelihoods of stopping for (combined) significance if H1 true: (1) .18542; (2) .46093; (3) .30731
```

If only a subset (here: one) of the tests are indicated in the list, the calculation will include only the selected one(s).

```
pow(dfPvalsMulti,
alpha_locals = list(p_myTestA = c(.02, .03, .03)))
#> # POSSA pow() results #
#> N(average-total) = 159.1 (if H0 true) or 101.5 (if H1 true)
#> (p_myTestA) Type I error: .05000; Power: .90522
#> Local alphas: (1) .01709; (2) .02563; (3) .02563
#> Likelihoods of significance if H0 true: (1) .01769; (2) .01862; (3) .01369
#> Likelihoods of significance if H1 true: (1) .37082; (2) .37942; (3) .15498
```

Setting futility bounds (for interim looks) works just the same.

```
pow(
dfPvalsMulti,alpha_locals = list(
p_myTestA = c(.02, .03, .03),
p_myTestB = c(.005, .02, .04)
),fut_locals = list(p_myTestA = c(.6, .5),
p_myTestB = c(.8, .4)),
adjust = FALSE
)
#> # POSSA pow() results #
#> N(average-total) = 126.5 (if H0 true) or 116.7 (if H1 true)
#> (p_myTestA) Type I error: .03711; Power: .90796
#> Local alphas: (1) .02000; (2) .03000; (3) .03000
#> Likelihoods of significance if H0 true: (1) .00302; (2) .00938; (3) .02471
#> Likelihoods of significance if H1 true: (1) .18542; (2) .46089; (3) .26164
#> Futility bounds: (1) .60000; (2) .50000
#> Likelihoods of exceeding futility alpha if H0 true: (1) .02727; (2) .18969
#> Likelihoods of exceeding futility alpha if H1 true: (1) .00253; (2) .00189
#> (p_myTestB) Type I error: .04591; Power: .92453
#> Local alphas: (1) .00500; (2) .02000; (3) .04000
#> Likelihoods of significance if H0 true: (1) .00302; (2) .00938; (3) .03351
#> Likelihoods of significance if H1 true: (1) .18542; (2) .46089; (3) .27822
#> Futility bounds: (1) .80000; (2) .40000
#> Likelihoods of exceeding futility alpha if H0 true: (1) .00993; (2) .24111
#> Likelihoods of exceeding futility alpha if H1 true: (1) .00253; (2) .00189
#> Global ("combined significance") type I error: .05911 (included: p_myTestA, p_myTestB; power for reaching the "combined significance": .95231)
#> Likelihoods of stopping for (combined) significance if H0 true: (1) .00302; (2) .00938; (3) .04671
#> Likelihoods of stopping for (combined) significance if H1 true: (1) .18542; (2) .46089; (3) .30600
#> Likelihoods of stopping for (combined) futility if H0 true: (1) .17609; (2) .28896
#> Likelihoods of stopping for (combined) futility if H1 true: (1) .00253; (2) .00189
```

Prevent any of the local alphas of any of the included tests to stop
the data collection, it can be set to zero (`0`

; so that it’s
never significant – similarly, again, futility bounds can be set to
`1`

to avoid stopping for futility at any look, for any given
test). However, one might want to know the power and T1ER for a test
that has no “stopping” alphas: for instance, there may be a
secondary/supplementary test at whose significant finding one does not
intend to stop data collection, but at the same time one would like to
know its power and T1ER – which depend on the other included tests’
stopping alphas (and the correlations between the variables, if
any).

Such “non-stopping” local alphas can be specified for any of the
included tests (p values) via the `alpha_loc_nonstop`

parameter. This parameter works the same way as
`alpha_locals`

, except that they never stop data collection,
regardless of whatever p value they have at any given look. They also do
not count towards the “combined” power or T1ER. Hence, the results of
each test with such non-stopping local alphas are reported stand-alone,
unrelated to the rest of the results.

```
pow(
dfPvalsMulti,alpha_locals = list(p_myTestA = c(.005, .01, .025)),
alpha_loc_nonstop = list(p_myTestB = c(.01, .01, .02))
)
#> # POSSA pow() results #
#> N(average-total) = 160.4 (if H0 true) or 111.2 (if H1 true)
#> (p_myTestA) Type I error: .04993; Power: .92713
#> Local alphas: (1) .00797; (2) .01595; (3) .03987
#> Likelihoods of significance if H0 true: (1) .00858; (2) .01291; (3) .02844
#> Likelihoods of significance if H1 true: (1) .26496; (2) .41029; (3) .25189
#> (non-stopper: p_myTestB) Type I error: .02256; Power: .72098
#> Local alphas (secondary): (1) .01000; (2) .01000; (3) .02000
#> Likelihoods of significance if H0 true: (1) .00318; (2) .00411; (3) .01527
#> Likelihoods of significance if H1 true: (1) .19242; (2) .30551; (3) .22304
```

(Here, there is no combined power/T1ER, since there is only one test with stopping alphas.)

Different observation numbers for each group may be specified via the
group names (analogously to specifying
observation numbers per each sample). To be able to specify the two
group’s observation numbers separately, we need separate parameters for
each, in the sample generation function, each using the
`grp_`

notation; here `grp_1`

and
`grp_2`

.

Unrelatedly, for the sake of an extended example, here there will be,
instead of two, three tests included (denoted here as `X`

,
`Y`

, `Z`

).

```
= function(grp_1, grp_2) {
multiSampleGroupParam = faux::rnorm_multi(
corrVarsH0 n = grp_2,
vars = 3,
mu = 0,
sd = 10,
r = c(0, 0.4, 0.8)
)= faux::rnorm_multi(
corrVarsH1 n = grp_2,
vars = 3,
mu = 4,
sd = 10,
r = c(0, 0.4, 0.8)
)= rnorm(grp_1, mean = 0, sd = 10)
v1 list(
grp_1_test_base = v1,
grp_2_testX_h0 = corrVarsH0$X1, # correlates with X3 (.4)
grp_2_testX_h1 = corrVarsH1$X1,
grp_2_testY_h0 = corrVarsH0$X2, # correlates with X3 (.8)
grp_2_testY_h1 = corrVarsH1$X2,
grp_2_testZ_h0 = corrVarsH0$X3, # correlates with X1 and X3
grp_2_testZ_h1 = corrVarsH1$X3
) }
```

The test function can then be like this.

```
= function(grp_1_test_base,
multiTest3
grp_2_testX_h0,
grp_2_testX_h1,
grp_2_testY_h0,
grp_2_testY_h1,
grp_2_testZ_h0,
grp_2_testZ_h1) {c(
p_testX_h0 = t.test(grp_1_test_base, grp_2_testX_h0, 'less', var.equal = TRUE)$p.value,
p_testX_h1 = t.test(grp_1_test_base, grp_2_testX_h1, 'less', var.equal = TRUE)$p.value,
p_testY_h0 = t.test(grp_1_test_base, grp_2_testY_h0, 'less', var.equal = TRUE)$p.value,
p_testY_h1 = t.test(grp_1_test_base, grp_2_testY_h1, 'less', var.equal = TRUE)$p.value,
p_testZ_h0 = t.test(grp_1_test_base, grp_2_testY_h0, 'less', var.equal = TRUE)$p.value,
p_testZ_h1 = t.test(grp_1_test_base, grp_2_testY_h1, 'less', var.equal = TRUE)$p.value
) }
```

(Check via
`do.call(multiTest3, multiSampleGroupParam(70, 90))`

.)

Now the observation numbers can be specified via the group names
`grp_1`

and `grp_2`

, in a list argument for
`n_obs`

.

```
= sim(
dfPvalsMultiUnequalGrps fun_obs = multiSampleGroupParam,
n_obs = list(grp_1 = c(27, 54, 81),
grp_2 = c(60, 90, 120)),
fun_test = multiTest3
)
```

Now the `pow`

function can be used as before. Here are
some examples (with output omitted here for brevity).

```
pow(dfPvalsMultiUnequalGrps,
alpha_locals = c(0.03, 0.04, 0.05))
# same with different notation
pow(dfPvalsMultiUnequalGrps,
alpha_locals = list(
p_testX = c(0.03, 0.04, 0.05),
p_testY = c(0.03, 0.04, 0.05),
p_testZ = c(0.03, 0.04, 0.05)
))
# include only two tests in the calculation
pow(dfPvalsMultiUnequalGrps,
alpha_locals = list(
p_testY = c(0.03, 0.04, 0.05),
p_testZ = c(0.03, 0.04, 0.05)
))
# include only one
pow(dfPvalsMultiUnequalGrps,
alpha_locals = list(p_testX = c(0.03, 0.04, 0.05)))
# include again one but add info for two non-stoppers
pow(
dfPvalsMultiUnequalGrps,alpha_locals = list(p_testY = c(0.03, 0.04, 0.05)),
alpha_loc_nonstop = list(
p_testX = c(0.03, 0.04, 0.05),
p_testZ = c(0.03, 0.04, 0.05)
)
)
# same but with different alphas for each
pow(
dfPvalsMultiUnequalGrps,alpha_locals = list(p_testY = c(0.03, 0.04, 0.05)),
alpha_loc_nonstop = list(
p_testX = c(0.03, 0.02, 0.01),
p_testZ = c(0.04, 0.03, 0.0)
)
)
# futility bounds for all tests
pow(dfPvalsMultiUnequalGrps,
fut_locals = c(0.6, 0.4))
# futility bounds with "any" logic
pow(
dfPvalsMultiUnequalGrps,fut_locals = c(0.6, 0.4),
multi_logic_fut = 'any'
)
# futility bounds for one test only
pow(dfPvalsMultiUnequalGrps,
fut_locals = list(p_testY = c(0.6, 0.4)))
# stopping local alphas and futility bounds together, for two tests
pow(
dfPvalsMultiUnequalGrps,alpha_locals = list(
p_testY = c(0, 0.3, 0.5),
p_testZ = c(0, 0.2, 0.5)
),fut_locals = list(p_testY = c(0.5, 0.2),
p_testZ = c(0.6, 0.3))
)
```