Skip to content
Merged
Show file tree
Hide file tree
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
44 changes: 29 additions & 15 deletions docs/literate/reference/onsettypes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -134,29 +134,36 @@ end
# ```

# ## LogNormalOnset
# The `LogNormalOnset` is based on a log-normal distribution and has four parameters: `μ`, `σ`, `offset` and `truncate_upper`.
# The `LogNormalOnset` is based on a log-normal distribution and has five parameters: \ `μ`, `σ`, `offset`, `truncate_upper` and `truncate_lower`.

# Example:
onset_lognormal = LogNormalOnset(; μ = 3, σ = 0.25, offset = 0, truncate_upper = nothing);
onset_lognormal = LogNormalOnset(;
μ = 3,
σ = 0.25,
offset = 0,
truncate_upper = nothing,
truncate_lower = nothing,
);

# The parameters `μ` and `σ` are the location and scale parameter of the log-normal distribution. However, they are not identical to its mean and standard deviation.
# If a variable $X$ is log-normally distributed then $Y = ln(X)$ is normally distributed with mean `μ` and standard deviation `σ`[^1].

# The `offset` parameter determines the minimal distance between two events and its value is added to the value sampled from the log-normal distribution i.e. it shifts the distribution.
# Its default value is `0`, i.e. no offset.

# The `truncate_upper` parameter allows to truncate the distribution at a certain sample value. Its default value is `nothing`, i.e. no truncation.
# The `truncate_upper` and `truncate_lower` parameters allow to truncate the distribution at certain sample values. The default value for both is `nothing`, i.e. no truncation.

# In the figure below, it is illustrated how the onset distribution changes when changing one of its parameters.
let # hide
f = Figure(size = (600, 800)) # hide

## Define parameter combinations # hide
parameters = [ # hide
(((3, 0.25, 0, nothing), (2.5, 0.25, 0, nothing)), "μ"), # hide
(((3, 0.25, 0, nothing), (3, 0.35, 0, nothing)), "σ"), # hide
(((3, 0.25, 0, nothing), (3, 0.25, 30, nothing)), "offset"), # hide
(((3, 0.25, 0, nothing), (3, 0.25, 0, 25)), "truncate_upper"), # hide
(((3, 0.25, 0, nothing, nothing), (2.5, 0.25, 0, nothing, nothing)), "μ"), # hide
(((3, 0.25, 0, nothing, nothing), (3, 0.35, 0, nothing, nothing)), "σ"), # hide
(((3, 0.25, 0, nothing, nothing), (3, 0.25, 30, nothing, nothing)), "offset"), # hide
(((3, 0.25, 0, nothing, nothing), (3, 0.25, 0, 25, nothing)), "truncate_upper"), # hide
(((3, 0.25, 0, nothing, nothing), (3, 0.25, 0, nothing, 25)), "truncate_lower"), # hide
] # hide

axes_list = Array{Any}(undef, length(parameters)) # hide
Expand All @@ -167,14 +174,15 @@ let # hide
axes_list[index] = ax # hide

## Go through all parameter combinations and plot a histogram of the sampled onsets # hide
for (μ, σ, offset, truncate_upper) in combinations # hide
for (μ, σ, offset, truncate_upper, truncate_lower) in combinations # hide
onsets = UnfoldSim.simulate_interonset_distances( # hide
MersenneTwister(42), # hide
LogNormalOnset(; # hide
μ = μ, # hide
σ = σ, # hide
offset = offset, # hide
truncate_upper = truncate_upper, # hide
truncate_lower = truncate_lower, # hide
), # hide
design, # hide
) # hide
Expand All @@ -183,13 +191,15 @@ let # hide
ax, # hide
onsets, # hide
bins = range(0, 100, step = 1), # hide
label = "($μ,$σ,$offset,$truncate_upper)", # hide
label = "($μ,$σ,$offset,$truncate_upper,$truncate_lower)", # hide
) # hide

if label == "offset" && offset !== 0 # hide
vlines!(offset, color = "black") # hide
elseif label == "truncate_upper" && truncate_upper !== nothing # hide
vlines!(truncate_upper, color = "black") # hide
elseif label == "truncate_lower" && truncate_lower !== nothing # hide
vlines!(truncate_lower, color = "black") # hide
end # hide
end # hide
hideydecorations!(ax) # hide
Expand Down Expand Up @@ -218,10 +228,11 @@ let

## Define parameter combinations
parameters = [
(((3, 0.25, 0, nothing), (2.5, 0.25, 0, nothing)), "μ"),
(((3, 0.25, 0, nothing), (3, 0.35, 0, nothing)), "σ"),
(((3, 0.25, 0, nothing), (3, 0.25, 30, nothing)), "offset"),
(((3, 0.25, 0, nothing), (3, 0.25, 0, 25)), "truncate_upper"),
(((3, 0.25, 0, nothing, nothing), (2.5, 0.25, 0, nothing, nothing)), "μ"),
(((3, 0.25, 0, nothing, nothing), (3, 0.35, 0, nothing, nothing)), "σ"),
(((3, 0.25, 0, nothing, nothing), (3, 0.25, 30, nothing, nothing)), "offset"),
(((3, 0.25, 0, nothing, nothing), (3, 0.25, 0, 25, nothing)), "truncate_upper"),
(((3, 0.25, 0, nothing, nothing), (3, 0.25, 0, nothing, 25)), "truncate_lower"),
]

axes_list = Array{Any}(undef, length(parameters))
Expand All @@ -232,14 +243,15 @@ let
axes_list[index] = ax

## Go through all parameter combinations and plot a histogram of the sampled onsets
for (μ, σ, offset, truncate_upper) in combinations
for (μ, σ, offset, truncate_upper, truncate_lower) in combinations
onsets = UnfoldSim.simulate_interonset_distances(
MersenneTwister(42),
LogNormalOnset(;
μ = μ,
σ = σ,
offset = offset,
truncate_upper = truncate_upper,
truncate_lower = truncate_lower,
),
design,
)
Expand All @@ -248,13 +260,15 @@ let
ax,
onsets,
bins = range(0, 100, step = 1),
label = "($μ,$σ,$offset,$truncate_upper)",
label = "($μ,$σ,$offset,$truncate_upper,$truncate_lower)",
)

if label == "offset" && offset !== 0
vlines!(offset, color = "black")
elseif label == "truncate_upper" && truncate_upper !== nothing
vlines!(truncate_upper, color = "black")
elseif label == "truncate_lower" && truncate_lower !== nothing
vlines!(truncate_lower, color = "black")
end
end
hideydecorations!(ax)
Expand Down
11 changes: 10 additions & 1 deletion src/onset.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ See also [`UniformOnset`](@ref UnfoldSim.UniformOnset), [`NoOnset`](@ref).
σ::Any # variance
offset = 0 # additional offset
truncate_upper = nothing # truncate at some sample?
truncate_lower = nothing # truncate at some lower sample?
end

"""
Expand Down Expand Up @@ -173,6 +174,9 @@ function simulate_interonset_distances(rng, onset::LogNormalOnset, design::Abstr
if !isnothing(onset.truncate_upper)
fun = truncated(fun; upper = onset.truncate_upper)
end
if !isnothing(onset.truncate_lower)
fun = truncated(fun; lower = onset.truncate_lower)
end
return Int.(round.(onset.offset .+ rand(deepcopy(rng), fun, s)))
end

Expand Down Expand Up @@ -358,6 +362,7 @@ offset: The minimal distance between events - aka a shift of the LogNormal distr
- `offset_β::Vector = [0] ` (optional): Choose a `Vector` of betas. The number of betas needs to fit the formula chosen.
- `offset_contrasts::Dict = Dict()` (optional): Choose a contrasts-`Dict`ionary according to the StatsModels specifications.
- `truncate_upper::nothing` (optional): Upper limit (in samples) at which the distribution is truncated (formula for truncation currently not implemented)
- `truncate_lower::nothing` (optional): Lower limit (in samples) at which the distribution is truncated (formula for truncation currently not implemented)

# Combined with [ShiftOnsetByOne](@ref)

Expand Down Expand Up @@ -387,6 +392,7 @@ See also [`LogNormalOnset`](@ref UnfoldSim.LogNormalOnset) for a simplified vers
offset_β::Vector = [0]
offset_contrasts::Dict = Dict()
truncate_upper = nothing # truncate at some sample?
truncate_lower = nothing
end

function simulate_interonset_distances(
Expand All @@ -408,6 +414,9 @@ function simulate_interonset_distances(
if !isnothing(o.truncate_upper)
funs = truncated.(funs; upper = o.truncate_upper)
end
#@debug reduce(hcat, rand.(deepcopy(rng), funs, 1))
if !isnothing(o.truncate_lower)
funs = truncated.(funs; lower = o.truncate_lower)
end

return Int.(round.(offsets .+ reduce(vcat, rand.(deepcopy(rng), funs, 1))))
end
1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[deps]
Automa = "67c07d97-cdcb-5c2c-af73-a7f9c32a568b"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
MixedModelsSim = "d5ae56c5-23ca-4a1f-b505-9fc4796fc1fe"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Expand Down
19 changes: 15 additions & 4 deletions test/onset.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,25 @@
@test minimum(rand_vec) > 100

# test Truncated
lognormal_onset = LogNormalOnset(; μ = 4, σ = 1, truncate_upper = 100)
lognormal_onset =
LogNormalOnset(; μ = 4, σ = 1, truncate_lower = 10, truncate_upper = 100)
rand_vec = UnfoldSim.simulate_interonset_distances(
StableRNG(1),
lognormal_onset,
dummydesign,
)
@test maximum(rand_vec) <= 100
@test minimum(rand_vec) >= 0
@test minimum(rand_vec) >= 10
end

@testset "truncated_upper_lower_bound" begin
fun = LogNormal(3, 0.5)
# Test that truncating a distribution twice in a row with one bound is equal to truncating it with two bounds in one call
v1 = rand(StableRNG(1), truncated(truncated(fun; upper = 20); lower = 10), 100)
v2 = rand(StableRNG(1), truncated(fun; upper = 20, lower = 10), 100)
@test v1 == v2
end

@testset "sim_onsets" begin
uniform_onset = UniformOnset(; offset = 0, width = 50)

Expand Down Expand Up @@ -92,12 +102,13 @@
μ_formula = @formula(0 ~ 1 + cond),
μ_β = [1, 1],
σ_β = [1],
truncate_lower = 5,
)
events = generate_events(design)
onsets = UnfoldSim.simulate_interonset_distances(StableRNG(1), o, design)
@test minimum(onsets[1:2:end]) == 0
@test minimum(onsets[1:2:end]) >= 5
@test maximum(onsets[1:2:end]) < 150
@test minimum(onsets[2:2:end]) == 0
@test minimum(onsets[2:2:end]) >= 5
@test maximum(onsets[2:2:end]) > 300


Expand Down
1 change: 1 addition & 0 deletions test/setup.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ using Statistics
using LinearAlgebra
using MixedModelsSim
using DataFrames
using Distributions # For LogNormal function

function gen_debug_design(; n_subjects = 20, n_item = 100)
# define design parameters
Expand Down
Loading