Skip to content

Commit ff6b365

Browse files
authored
Merge pull request #14 from unfoldtoolbox/bug-#13_1x3
Fix 1x3 categorical condensing
2 parents a0dea7b + 9653216 commit ff6b365

3 files changed

Lines changed: 90 additions & 23 deletions

File tree

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "UnfoldMixedModels"
22
uuid = "019ae9e0-8363-565c-86e5-97a5a2fe84f4"
33
authors = ["Benedikt Ehinger <benedikt.ehinger@vis.uni-stuttgart.de>"]
4-
version = "0.1.1"
4+
version = "0.1.2"
55

66
[deps]
77
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"

src/condense.jl

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,26 @@ end
1010
MixedModels.tidyβ(m::Union{UnfoldLinearMixedModel,UnfoldLinearMixedModelContinuousTime}) =
1111
MixedModels.tidyβ(modelfit(m))
1212

13+
14+
"""
15+
helper function because `coefnames` returns an array only if number of coefs is larger than 1
16+
"""
17+
function _coefnames(t)
18+
c = coefnames(t)
19+
return isa(c, Vector) ? c : [c]
20+
end
21+
22+
23+
1324
"""
1425
random_effect_groupings(t::MixedModels.AbstractReTerm)
1526
Returns the random effect grouping term (rhs), similar to coefnames, which returns the left hand sides
1627
"""
17-
random_effect_groupings(t::AbstractTerm) = repeat([nothing], length(t.terms))
28+
random_effect_groupings(t::AbstractTerm) = repeat([nothing], length(_coefnames(t.terms)))
1829
random_effect_groupings(t::Unfold.TimeExpandedTerm) =
1930
repeat(random_effect_groupings(t.term), length(Unfold.colnames(t.basisfunction)))
2031
random_effect_groupings(t::MixedModels.AbstractReTerm) =
21-
repeat([t.rhs.sym], length(t.lhs.terms))
32+
repeat([t.rhs.sym], length(_coefnames(t.lhs)))
2233

2334
random_effect_groupings(f::FormulaTerm) = vcat(random_effect_groupings.(f.rhs)...)
2435
random_effect_groupings(t::Vector) = vcat(random_effect_groupings.(t)...)
@@ -32,16 +43,18 @@ function reorder_tidyσs(t, f)
3243
#@debug typeof(f)
3344
# get the order from the formula, this is the target
3445
f_order = random_effect_groupings(f) # formula order
35-
#@debug f_order
46+
@debug f_order
3647
f_order = vcat(f_order...)
37-
#@debug f_order
48+
@debug f_order
3849

3950
# find the fixefs
4051
fixef_ix = isnothing.(f_order)
4152

4253

4354

4455
f_order = string.(f_order[.!fixef_ix])
56+
@debug fixef_ix
57+
@debug coefnames(f)
4558

4659
f_name = vcat(coefnames(f)...)[.!fixef_ix]
4760

@@ -50,8 +63,8 @@ function reorder_tidyσs(t, f)
5063
t_name = [string(i.column) for i in t if i.iter == 1]
5164

5265
# combine for formula and tidy output the group + the coefname
53-
#@debug f_order
54-
#@debug f_name
66+
@debug "f" f_order f_name
67+
@debug "t" t_order t_name
5568
f_comb = f_order .* f_name
5669
t_comb = t_order .* t_name
5770

@@ -68,7 +81,7 @@ function reorder_tidyσs(t, f)
6881
# repeat and build the index for all timepoints
6982
reorder_ix_all = repeat(reorder_ix, length(t) ÷ length(reorder_ix))
7083
for k = 1:length(reorder_ix):length(t)
71-
reorder_ix_all[k:k+length(reorder_ix)-1] .+= (k - 1)
84+
reorder_ix_all[k:(k+length(reorder_ix)-1)] .+= (k - 1)
7285
end
7386

7487
return t[reorder_ix_all]

test/test-fit.jl

Lines changed: 69 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ end
2424
subj_idx = [parse(Int, split(string(s), 'S')[2]) for s in evts.subject]
2525
evts.latency .+= size(data, 1) .* (subj_idx .- 1)
2626

27-
27+
evts.C = rand(StableRNG(1), ["a", "b", "c"], size(evts, 1))
2828
data = reshape(data, 1, :)
2929
#append!(data, zeros(1000))
3030
data = vcat(data, data)
@@ -62,7 +62,10 @@ end
6262
)
6363
df = Unfold.coeftable(m_mum)
6464
@test isapprox(
65-
df[(df.channel.==1).&(df.coefname.=="A: a_small").&(df.time.==0.0), :estimate],
65+
df[
66+
(df.channel .== 1) .& (df.coefname .== "A: a_small") .& (df.time .== 0.0),
67+
:estimate,
68+
],
6669
[-0.02, 0.054],
6770
atol = 0.01,
6871
)
@@ -81,7 +84,10 @@ end
8184
)
8285
df = coeftable(m_mum)
8386
@test isapprox(
84-
df[(df.channel.==1).&(df.coefname.=="A: a_small").&(df.time.==0.0), :estimate],
87+
df[
88+
(df.channel .== 1) .& (df.coefname .== "A: a_small") .& (df.time .== 0.0),
89+
:estimate,
90+
],
8591
[0.031, 0.05],
8692
atol = 0.1,
8793
)
@@ -101,7 +107,10 @@ end
101107
)
102108
df = coeftable(m_tum)
103109
@test isapprox(
104-
df[(df.channel.==1).&(df.coefname.=="A: a_small").&(df.time.==0.0), :estimate],
110+
df[
111+
(df.channel .== 1) .& (df.coefname .== "A: a_small") .& (df.time .== 0.0),
112+
:estimate,
113+
],
105114
[-0.03, 0.064],
106115
atol = 0.1,
107116
)
@@ -120,13 +129,12 @@ end
120129

121130

122131
evts.subjectB = evts.subject
123-
evts1 = evts[evts.A.=="a_small", :]
124-
evts2 = evts[evts.A.=="a_big", :]
132+
evts1 = evts[evts.A .== "a_small", :]
133+
evts2 = evts[evts.A .== "a_big", :]
125134

126-
f0_lmm = @formula 0 ~ 1 + B + (1 | subject) + (1 | subjectB)
127-
@time m_tum = coeftable(
128-
fit(UnfoldModel, f0_lmm, evts, data, basisfunction; show_progress = false),
129-
)
135+
f0_lmm = @formula 0 ~ 1 + B + (1 | subject) + (1 + C | subjectB)
136+
@time m = fit(UnfoldModel, f0_lmm, evts, data, basisfunction; show_progress = false)
137+
@time m_tum = coeftable(m)
130138

131139

132140
f1_lmm = @formula 0 ~ 1 + B + (1 | subject)
@@ -147,7 +155,10 @@ end
147155
df = coeftable(r)
148156

149157
@test isapprox(
150-
df[(df.channel.==1).&(df.coefname.=="B: b_tiny").&(df.time.==0.0), :estimate],
158+
df[
159+
(df.channel .== 1) .& (df.coefname .== "B: b_tiny") .& (df.time .== 0.0),
160+
:estimate,
161+
],
151162
[0.65, 0.69],
152163
rtol = 0.1,
153164
)
@@ -163,6 +174,35 @@ end
163174
),
164175
)
165176

177+
178+
#----
179+
# # test #13, 2x3 design
180+
181+
f = @formula 0 ~ 1 + A + C + (1 + A + C | subject)
182+
#f = @formula 0~1 + (1|subject)
183+
184+
185+
186+
# cut the data into epochs
187+
# TODO This ignores subject bounds
188+
data_e, times = Unfold.epoch(data = data, tbl = evts, τ = (-1.0, 1.9), sfreq = 10)
189+
data_missing_e, times =
190+
Unfold.epoch(data = data_missing, tbl = evts, τ = (-1.0, 1.9), sfreq = 10)
191+
evts_e, data_e = Unfold.drop_missing_epochs(copy(evts), data_e)
192+
evts_missing_e, data_missing_e = Unfold.drop_missing_epochs(copy(evts), data_missing_e)
193+
194+
######################
195+
## Mass Univariate Mixed
196+
@time m_mum = fit(
197+
UnfoldModel,
198+
f,
199+
evts_e,
200+
data_e,
201+
times,
202+
contrasts = Dict(:A => EffectsCoding(), :B => EffectsCoding()),
203+
show_progress = false,
204+
)
205+
df = Unfold.coeftable(m_mum)
166206
end
167207
## Condense check for multi channel, multi
168208
@testset "LMM multi channel, multi basisfunction" begin
@@ -198,6 +238,22 @@ end
198238

199239
@test all(last(.!isnothing.(res.group), 8))
200240
@test all(last(res.coefname, 8) .== "(Intercept)")
241+
242+
# test more complex formulas
243+
fA0 = @formula (0 ~ 1 + zerocorr(1 + C | subject))
244+
fA1 = @formula (0 ~ 1 + B + C + zerocorr(1 + C | subject2))
245+
evts.C = rand(StableRNG(1), ["a", "b", "c"], size(evts, 1))
246+
m = fit(
247+
UnfoldModel,
248+
["a_small" => (fA0, bA0), "a_big" => (fA1, bA1)],
249+
evts,
250+
data;
251+
eventcolumn = "A",
252+
show_progress = false,
253+
)
254+
255+
res = coeftable(m)
256+
201257
end
202258

203259

@@ -216,17 +272,15 @@ end
216272
[
217273
Any => (
218274
@formula(
219-
0 ~
220-
1 + A + B + zerocorr(1 + B + A | subject) + zerocorr(1 + B | item)
275+
0 ~ 1 + A + B + zerocorr(1 + B + A | subject) + zerocorr(1 + B | item)
221276
),
222277
range(0, 1, length = size(data, 1)),
223278
),
224279
],
225280
[
226281
Any => (
227282
@formula(
228-
0 ~
229-
1 + A + B + zerocorr(1 + A + B | subject) + zerocorr(1 + B | item)
283+
0 ~ 1 + A + B + zerocorr(1 + A + B | subject) + zerocorr(1 + B | item)
230284
),
231285
range(0, 1, length = size(data, 1)),
232286
),

0 commit comments

Comments
 (0)