using UnfoldDecode
using UnfoldSim
using UnfoldMakie
using CairoMakie
using Unfold

Overlap-corrected decoding

We will try to introduce as many fancy features as possible Please read the "tutorial" first

Simulation

multi-event

dat, evt = UnfoldSim.predef_eeg()
evt.event = rand(["eventA", "eventB"], size(evt, 1)) # add random events
dat = repeat(dat', 5)
dat .= dat .+ 20 .* rand(size(dat)...)
5×120199 Matrix{Float64}:
 17.9053   8.87049  10.7419   6.66026  …  11.1886    2.38817  13.3819
 14.3933  15.454    11.7659   4.8754       3.18727   1.37389  19.7361
 12.4731  18.9506   13.8243  20.5309       8.4603    9.88742   8.92185
 19.1302  16.534    13.4568  19.2871       7.97039  18.6621    9.28247
  2.0325   8.48175  15.1328  13.0128      17.7138    8.29587   5.18685

Overlap-model Definition

We have two basis functions now, with two different timewindows. Let's see if it works!

des = [
    "eventA" => (@formula(0 ~ 1 + condition + continuous), firbasis((-0.1, 1.0), 100)),
    "eventB" => (@formula(0 ~ 1 + continuous), firbasis((-0.3, 0.5), 100)),
]
2-element Vector{Pair{String, Tuple{StatsModels.FormulaTerm{StatsModels.ConstantTerm{Int64}}, Unfold.FIRBasis}}}: "eventA" => (0 ~ 1 + condition + continuous, ╭──────────────────────────────────────────────────────────────────────────────╮111×111 SparseArrays.SparseMatrixCSC with 111 stored entries: │ ::BasisFunction │⎡⠑⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎤ │ name:  │⎢⠀⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥ │ kerneltype: Unfold.FIRBasis │⎢⠀⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥ │ width: 111 │⎢⠀⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥ │ height: 111 │⎢⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥ │ colnames: [-0.1, -0.09 ... 1.0] │⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⠀⎥ │ times: [-0.1, -0.09 ... 1.0] │⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⠀⎥ │ collabel: time │⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⠀⎥ │ shift_onset: -10 │⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⠀⎥ │ │⎣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⎦ ╰──────────────────────────────────────────────────────────────────────────────╯ ) "eventB" => (0 ~ 1 + continuous, ╭──────────────────────────────────────────────────────────────────────────────╮81×81 SparseArrays.SparseMatrixCSC with 81 stored entries: │ ::BasisFunction │⎡⠑⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎤ │ name:  │⎢⠀⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥ │ kerneltype: Unfold.FIRBasis │⎢⠀⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥ │ width: 81 │⎢⠀⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥ │ height: 81 │⎢⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥ │ colnames: [-0.3, -0.29 ... 0.5] │⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⠀⎥ │ times: [-0.3, -0.29 ... 0.5] │⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⠀⎥ │ collabel: time │⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⠀⎥ │ shift_onset: -30 │⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⠀⎥ │ │⎣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⎦ ╰──────────────────────────────────────────────────────────────────────────────╯ )

To show that it is possible, we explicitly specify the solver

customsolver = (x, y) -> Unfold.solver_default(x, y)
uf = Unfold.fit(UnfoldModel, des, evt, dat[1, :]; solver = customsolver);
plot_erp(coeftable(uf); mapping = (; col = :eventname))
Example block output

Fitting the Overlap-corrected LDA model

using MLJ, MultivariateStats, MLJMultivariateStatsInterface
LDA = @load LDA pkg = MultivariateStats
MLJMultivariateStatsInterface.LDA

you could use other parameters, check out ?LDA

ldaModel = LDA(
    method = :whiten,
    cov_w = SimpleCovariance(),
    cov_b = SimpleCovariance(),
    regcoef = 1e-3,
)

uf_lda = UnfoldDecode.fit(
    UnfoldDecodingModel,
    des,
    evt,
    dat,
    ldaModel,
    "eventA" => :condition;
    nfolds = 2,# only 2 folds to speed up computation
    unfold_fit_options = (; solver = customsolver), #customer solver for fun
    eventcolumn = :event, # actually the default, but maybe your event dataframe has a different name?
    multithreading = false,
) # who needs speed anyway :shrug:

plot_erp(coeftable(uf_lda))

Voila, the model classified the correct period at the correct event


This page was generated using Literate.jl.