Question
I am struggling with understanding how to run a simulated power analysis for GAMMs. Though my planned studies will include more variables, the core issue I'm trying to work out is to simulate the following:
- A binary outcome
y
. However, since I don't know how to correlatey
here, I have opted to just use a Gaussian response as it is easier to model. - Two predictors, modeled with
s(x, by = group)
to estimate this for two groups. - The variables should be moderately correlated, with
x2
having a negative association withy
. - An interaction will be modeled as
ti(x1,x2,by=group)
. - Subject and item random effects (with no specific prior information).
- For Group 1,
x1
andx2
should have a larger effect ony
.
Code
The best I could come up with was something like this for a basic GAM:
#### Load Libraries ####
library(mgcv)
library(tidyverse)
library(correlation)
library(faux)
set.seed(123)
#### Perform Simulation PA ####
B <- 10 # number of simulations (10 for now so its faster)
p1 <- NULL # store p values
p2 <- NULL
p3 <- NULL
p4 <- NULL
p5 <- NULL
p6 <- NULL
for (i in 1:B) {
#### Create RE Grid ####
sub <- factor(1:50)
item <- factor(1:20)
obs <- length(sub)*length(item)
grid <- expand.grid(sub=sub,
item=item)
#### Set Up Simulation Parameters ####
n <- 50 # sample size
alpha <- 0.05 # significance level
power <- 0.8 # desired power
#### Simulate Data ####
dat <- rnorm_multi(n = n,
mu = c(20, 20, 20),
sd = c(10, 10, 10),
r = c(0.30, 0, -.30),
varnames = c("x1", "x2", "y"),
empirical = FALSE)
#### Make Grouping Factor ####
group <- factor(
rep(c(1,2),each=25),
labels = c("Group 1", "Group 2")
) # not sure how to show differences in groups
#### Merge Data ####
df <- data.frame(dat,group,grid) %>%
as_tibble()
df
#### Build Model ####
model_sim <- bam(y
~ s(x1, by = group)
+ s(x2, by = group)
+ ti(x1,x2, by = group)
+ s(sub, bs = "re")
+ s(item, bs = "re"),
data = df) # fit GAM model to simulated data
#### Store P Values ####
p1[i] <- as.vector(summary(model_sim)$s.table[1,"p-value"])
p2[i] <- as.vector(summary(model_sim)$s.table[2,"p-value"])
p3[i] <- as.vector(summary(model_sim)$s.table[3,"p-value"])
p4[i] <- as.vector(summary(model_sim)$s.table[4,"p-value"])
p5[i] <- as.vector(summary(model_sim)$s.table[5,"p-value"])
p6[i] <- as.vector(summary(model_sim)$s.table[6,"p-value"])
p.data <- data.frame(p1,p2,p3,p4,p5,p6)
}
#### Inspect P Values for Variables 1:6 ####
p.data
Problems
Running this as-is takes forever, even with the very small number of boots here $(B = 10)$ and often doesn't converge, with this error popping up:
Warning message:
In bgam.fitd(G, mf, gp, scale, nobs.extra = 0, rho = rho, coef = coef, :
algorithm did not converge
I'm assuming it has to do with the data I have simulated. I also have no way of getting the groups to do anything other than arbitrarily split, while tinkering sometimes leads to complete separation in the GAMM (which I obviously don't want). As mentioned in the Question section, one group should have a predictably larger effect on y
for each x
, so modeling this would be helpful, Finally, achieving all of this somehow with binary data seems hard, so I have no way of figuring that part out.
So to summarize, I need to solve the following:
- What is causing the slow/weird estimaton?
- How do I get the groups to have a meaningful distinction from each other?
- Is there a way to make the outcome data binary? This is not my greatest priority, but it would be a nice bonus.
Edit
(continuation of first edit)
Because I don't want to make this question verbose, I've only included my recent changes. To follow the advise in the comments (hopefully I've done that), I tried estimating $y$ in the way prescribed in the data simulation functions of mgcv and gratia. I have kept it simple and made one "positive" function that varies slightly by group and one "negative" function that varies slightly by group.
#### Load Libraries ####
library(mgcv)
library(tidyverse)
library(gratia)
library(faux)
#### N and Scale ####
set.seed(123)
n <- 100
scale <- 1
#### Create X and F ####
x1 <- runif(n, 0, 1)
x2 <- runif(n, 0, 1)
f1.x1 <- exp(3 * x1)
f2.x1 <- exp(2 * x1)
f1.x2 <- exp(3 * x2) * -1
f2.x2 <- exp(2 * x2) * -1
e <- rnorm(n, 0, scale)
#### Create Factors ####
fac <- as.factor(sample(1:2,n,replace=TRUE))
fac.1 <- as.numeric(fac==1)
fac.2 <- as.numeric(fac==2)
#### Estimate Y ####
y <- x1 + x2 + f1.x1*fac.1 + f2.x1*fac.2 + f1.x2*fac.1 + f2.x2*fac.2 + e
#### Merge Data ####
df <- data.frame(y=y, # y values
x1=x1, # x values
x2=x2,
fac=fac) # factors
#### Convert and Inspect ####
data <- df %>%
as_tibble()
data
#### Fit Model ####
fit <- gam(y
~ fac
+ s(x1, by = fac)
+ s(x2, by = fac)
+ ti(x1, x2, by = fac),
method = "REML",
data = data)
summary(fit)
draw(fit)
Which gives me this:
This gets me close to what I want for now with the main effects and their grouping (though the error seems to always be higher for the second factor). The major parts I am still missing:
- How do I code the interaction between the two main effects? I am aware of how you do this in a vanilla regression, but not GAMs. I've tried assembling functions to do this like
ti.function <- function(x1,x2){x1*x2}
intoy
, but this doesn't seem to do anything different. I also tried correlating the variables beforehand using this method, but this in turn changes the direction of the plotted function. To summarize, I need increases in $x_1$ to be associated with increases in $y$, increases in $x_2$ to be associated with decreases in $y$, and and a linear interaction between $x_1$ and $x_2$ When $x_1$ is high and $x_2$ is low, this should result in higher $y$. - I still don't know how to add in subject/item random variance in a meaningful way. The
faux
package seems promising, but so far my attempts to add REs have failed. - I'm having issues adjusting the error variance with the
scale
object in this simulation. The standard error seems to always be relatively low but I expect my actual data to have more, so I would like to have better ways to tweak this.
Another Edit...(Dec 2023)
TLDR: I have solved the perfect prediction issues and have included crossed random effects like I originally wanted, but the relationships between the predictors and outcome are still not ideal.
I have still not solved my problem after repeated attempts, so I am setting up another bounty. I realize part of my issue previously was not providing enough information about the variables. Unfortunately, there is a serious lack of a priori information for my case, so I can only fill in what I know from some work in my area. However, I can offer a lot more information based off some previous knowledge of the research and what previous answerers have suggested adding. The variables should have the following features:
- A binary dependent variable called RD for "reading success" with values $0$ for failure to read and $1$ for success.
- A group variable called LG for "language group", where the L1 group has a higher rate of reading success than the L2 group. Its unclear what this means from a logistic sense. In one analysis the L1 group scored a mean of $120/150$ items correct and the L2 group merely got $30/150$, but the L2 group came from a poor disadvantaged background. Another study only found a $15$ point difference in scores on average (with the L2 group again scoring lower), but the L2 group in this case was from a much higher SES group. The actual reading test is a scale which ranges from a total score of $[0,150]$.
- A discrete subject-level variable called OA which has a positive linear effect on RD for the L2 group and a more weak curvilinear effect for the L1 group. There does not appear to be strong differences between the groups in at least one study (L1 OA = $12/16$, L2 OA = $11/16$) but I imagine those differences also vary wildly in reality and I have a strong feeling that the differences are more extreme than what is reported in just one study. As one can see, the scores range between $[0, 16]$. However, another study uses a similar measure range between $[0,20]$ with reported scores of $11/20$ for the L2 group but no reported scores of L1 groups with this measure.
- A discrete item-level variable called GC which has a negative linear effect on reading for the L2 group and a more weak curvilinear effect for the L1 group. The mean of the GC variable should be about $115$ and has no ceiling value, so it spans the range $[0, \infty)$. A previous analysis showed that GC was linear for reading and curvilinear for writing for the L1 group, so to be careful I am just going to assume the relationship is generally curvilinear for the L1 group, but that is of course just an assumption.
- A subject x item crossed random effects design, with a starting estimate of $50$ subjects per group and $150$ reading items. However, because this is a power analysis, the items will be essentially fixed and I am only after the number of subjects required for adequate power.
- Something else to add is that the estimates are likely to be fairly noisy. In the previous simulations in my question and the generous answer by Gavin, the standard error is very narrow, but I anticipate that this will not be the case given how measures in my field are not strongly correlated with the response.
The only thing I have been able to do to modify the code so far is something like this, which at least includes the random effects and logistic regression I need (thus solving the perfect prediction issues and added in random effects like I needed before), but the simulation still doesn't model the data like I would hope. Note for simplicity I just included continuous versions of the predictors because I ran into some more issues with Poisson-distributed variables:
#### Load Libraries ####
library(tidyverse)
library(mgcv)
#### Set Seed ####
set.seed(123)
#### Define REs ####
n_subjects <- 100
n_items <- 150
#### Create Subject Data ####
subject_data <- data.frame(
subject = rep(1:n_subjects, each = n_items),
LG = rep(c("L1", "L2"), each = n_items * 50),
OA = rnorm(n_subjects * n_items, mean = 11, sd = 1)
)
#### Create Item Data ####
item_data <- data.frame(
item = rep(1:n_items, times = n_subjects),
GC = rnorm(n_subjects * n_items, mean = 100, sd = 10)
)
#### Combine Data ####
data <- cbind(subject_data, item_data)
glimpse(data)
#### Define Y Variable ####
data$RD <- with(data, rbinom(n_subjects * n_items, 1, plogis(
(LG == "L1") * (0.05 * OA - 0.0001 * GC^2) +
(LG == "L2") * (0.03 * OA - 0.0001 * GC) +
(LG == "L2") * (0.0002 * OA * GC)
)))
data.fix <- data %>%
mutate(subject = factor(subject),
item = factor(item))
#### Fit GAMM ####
fit <- bam(
RD
~ s(OA, by = LG)
+ s(GC, by = LG)
+ ti(OA, GC, by =LG)
+ s(subject, bs = "re")
+ s(item, bs = "re"),
data=data.fix,
family = binomial
)
#### Plot Effects ####
plot(fit,
pages=1)
I'm not looking for an exact answer to my problem, so the solution doesn't need to fit all of these criteria exactly. I just need a workable logistic GAMM with crossed random effects, and if it achieves most of what I am trying to get here then I will be happy.
group
if using a factor by term. If you're doing a power analysis, you probably should usete()
in place of the three separate smooths as thete()
form is a simpler model $\endgroup$setup.R
file) contain lots of examples of reference models and simulating from them. You need to specify shapes for the functions, plus write out the linear predictor Lp, and then simulate data using a suitable RNG function. If you can spell out those things, I can see about producing an answer $\endgroup$gratia
andmgcv
. That seems to give me the main effects and grouping effects I want, but I'm still struggling with coding in the interactions and random effects. I tried multiplying thex1
andx2
variables in they
object but that didn't seem to fix anything. See my edit for more deets. $\endgroup$