feat(matlab): add Satterthwaite DF + honest random-slope test to LME reports
Every fitlme-based report (lme_*, paper_*, phase_*, and the variations' analyze.m) now shows, per effect: residual-DF p, Satterthwaite-DF p, and -- for the interaction -- an HONEST test from a per-animal random-SLOPE model (day|rat), whose Satterthwaite DF collapses toward the animal count. New: tdcs_random_slope_interaction.m (shared helper). Wired into tdcs_lme, tdcs_paper_lme, tdcs_phase_lme, variation_analyze; SUMMARY.csv gains interaction_p_satt / interaction_p_rs. Regenerated all results/, variations/, matched-effort outputs. Key point this surfaces: Satterthwaite ~= residual on the random-INTERCEPT model (the slope's error is at session level), so it does NOT fix pseudoreplication; the random-slope model does. Effect: full-range mergeA2 interaction 0.009 -> 0.75 (collapses); unmerge_d0_5 0.015 -> 0.13 (n.s.); the pooled-control early windows survive honestly (naive_a2_d0_5 0.001 -> 0.028; naive_boxa_d0_5 0.003 -> 0.036). Suite 42/42. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -32,24 +32,32 @@ T.tDCS = double(T.group == cfg.anchorHigh); % Box-B2 = 1, Box-A2 = 0
|
||||
lme = fitlme(T, 'success ~ day*tDCS + (1|subject)');
|
||||
C = lme.Coefficients;
|
||||
A = anova(lme);
|
||||
As = anova(lme, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
||||
|
||||
L.lme = lme;
|
||||
L.nSubjects = numel(unique(T.subject));
|
||||
L.nObs = height(T);
|
||||
L.maxDayA = max(T.day(T.group == cfg.anchorLow)); % last day Box-A2 has data
|
||||
L.maxDayB = max(T.day(T.group == cfg.anchorHigh)); % last day Box-B2 has data
|
||||
L.interaction = localTerm(C, A, 'day:tDCS');
|
||||
L.day = localTerm(C, A, 'day');
|
||||
L.tDCS = localTerm(C, A, 'tDCS');
|
||||
L.interaction = localTerm(C, A, As, 'day:tDCS');
|
||||
L.day = localTerm(C, A, As, 'day');
|
||||
L.tDCS = localTerm(C, A, As, 'tDCS');
|
||||
% Honest test: refit with a per-subject random SLOPE (see
|
||||
% tdcs_random_slope_interaction) so the interaction DF collapses toward n.
|
||||
L.interRS = tdcs_random_slope_interaction(T, ...
|
||||
'success ~ day*tDCS + (day|subject)', 'day:tDCS');
|
||||
|
||||
end
|
||||
|
||||
function e = localTerm(C, A, name)
|
||||
%LOCALTERM Pull one term's coefficient (t/df/p) and ANOVA (F/df/p) stats.
|
||||
function e = localTerm(C, A, As, name)
|
||||
%LOCALTERM Pull one term's coefficient (t/df/p), residual-DF ANOVA (F/df/p),
|
||||
% and Satterthwaite denominator DF + p (.dfSatt, .pSatt).
|
||||
ci = strcmp(C.Name, name);
|
||||
ai = strcmp(A.Term, name);
|
||||
si = strcmp(As.Term, name);
|
||||
e = struct( ...
|
||||
'estimate', C.Estimate(ci), 'se', C.SE(ci), ...
|
||||
't', C.tStat(ci), 'df', C.DF(ci), 'p', C.pValue(ci), ...
|
||||
'F', A.FStat(ai), 'df1', A.DF1(ai), 'df2', A.DF2(ai), 'Fp', A.pValue(ai));
|
||||
'F', A.FStat(ai), 'df1', A.DF1(ai), 'df2', A.DF2(ai), 'Fp', A.pValue(ai), ...
|
||||
'dfSatt', As.DF2(si), 'pSatt', As.pValue(si));
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user