Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

power analysis example #12

Merged
merged 1 commit into from
Sep 12, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions live_coding_sessions/power_analysis.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using DataFrames
using MixedModels
using MixedModelsSim
using Random
using Statistics
using SMLP2024: dataset

fm1 = fit(MixedModel,
@formula(reaction ~ 1 + days + (1 + days|subj)),
dataset(:sleepstudy))

parametricbootstrap(MersenneTwister(42), 1000, fm1)

slpsim = DataFrame(dataset(:sleepstudy); copycols=true)

slpsim[:, :reaction] .= 0.0

slpsimmod = LinearMixedModel(@formula(reaction ~ 1 + days + (1 + days|subj)), slpsim)

# coefnames(slpsimmod)
simulate!(MersenneTwister(42), slpsimmod; β=[500, 50], σ=250)
response(slpsimmod)
fit!(slpsimmod)

slpsimpow = parametricbootstrap(MersenneTwister(42), 1000, slpsimmod; β=[500, 50], σ=250)

combine(groupby(DataFrame(slpsimpow.coefpvalues), :coefname),
:p => (p -> mean(<(0.05), p)) => :power)

# now let's do random effects!
# these are expressed _relative_ to the residual standard deviation
# so we devide by 250, which is what we set as the residual standard deviation

Check warning on line 32 in live_coding_sessions/power_analysis.jl

View workflow job for this annotation

GitHub Actions / Spell Check with Typos

"devide" should be "divide" or "device".
# TODO: add a named argument "relative=true"
subj_re = create_re(100/250, 20/250)
update!(slpsimmod; subj=subj_re)

simulate!(MersenneTwister(42), slpsimmod; β=[500, 50], σ=250, θ=slpsimmod.θ)
fit!(slpsimmod)

slpsimpow = parametricbootstrap(MersenneTwister(42), 1000, slpsimmod; β=[500, 50], σ=250)

combine(groupby(DataFrame(slpsimpow.coefpvalues), :coefname),
:p => (p -> mean(<(0.05), p)) => :power)


# big questions:
# where do we get these numbers from?

# holding the RE and residual and FE-intercept constant, what is the smallest
# days-effect that I could detect with the given sample size?

to_consider = [10, 20, 30, 50]
# initialize an empty array of dataframes
considerations = DataFrame[]
for eff_size in to_consider
slpsimpow = parametricbootstrap(MersenneTwister(42), 1000, slpsimmod;
β=[500, eff_size], σ=250)
power_at_size = combine(groupby(DataFrame(slpsimpow.coefpvalues), :coefname),
:p => (p -> mean(<(0.05), p)) => :power)
power_at_size[:, :eff_size] .= eff_size
push!(considerations, power_at_size)
end

# put it all together in one dataframe
power = reduce(vcat, considerations)

# for a given sample size, now we know what size effects we can detect
# what happens if we have an effect size and what to know what sample size we need?

# for this study, there are no items
# and the number of observations per subject is given by the design
# so we only have one n to worry about
days = 0:9
rng = MersenneTwister(42)
subj_re = create_re(100/250, 20/250)

considerations = DataFrame[]
for n_subj in [10, 20, 30, 40]
subj_ids = "S" .* lpad.(string.(1:n_subj), 3, '0')

subj_data = DataFrame[]
for subj in subj_ids
sdat = DataFrame(; subj, days)
push!(subj_data, sdat)
end
simdat = reduce(vcat, subj_data)
# initialize the response with normally distributed noise
simdat[:, :reaction] .= randn(nrow(simdat))

slpsimmod = LinearMixedModel(@formula(reaction ~ 1 + days + (1 + days|subj)), simdat)

update!(slpsimmod; subj=subj_re)
slpsimpow = parametricbootstrap(rng, 1000, slpsimmod; β=[500, 10], σ=250)
power_at_size = combine(groupby(DataFrame(slpsimpow.coefpvalues), :coefname),
:p => (p -> mean(<(0.05), p)) => :power)
power_at_size[:, :n_subj] .= n_subj
push!(considerations, power_at_size)
end

power = reduce(vcat, considerations)
Loading