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×120198 Matrix{Float64}:
 17.5872    7.98078   8.95192  11.3478   …  13.6962    6.47239  11.3838
  1.98739  11.2074    2.4076    3.24325     11.1043   11.9759   13.3204
 20.2716    7.2268   17.7165   19.0147       6.43297  14.3635   15.0468
  7.78103   8.71343   9.4522    6.64546      8.01423   1.27141  16.071
  4.5458   17.8417    5.58468   6.13369      5.79027   3.45208  17.4985

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))
Example block output

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


This page was generated using Literate.jl.