Files
experiments-database/analysis/matlab/tdcs_random_slope_interaction.m
Experiments DB Dev 8a18c894dd 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>
2026-07-22 15:45:19 -04:00

34 lines
1.6 KiB
Matlab

function rs = tdcs_random_slope_interaction(T, formula, term)
%TDCS_RANDOM_SLOPE_INTERACTION Honest LME test of a slope-interaction TERM.
% RS = TDCS_RANDOM_SLOPE_INTERACTION(T, FORMULA, TERM) refits the linear mixed
% model FORMULA (which must include a per-subject random SLOPE, e.g.
% 'success ~ day*tDCS + (day|subject)') on table T and returns the
% Satterthwaite-DF marginal F-test of TERM (e.g. 'day:tDCS').
%
% Why this is the honest test. The paper's model has only a random INTERCEPT,
% so it assumes every animal shares one true slope and estimates the slope /
% interaction with session-level precision -- Satterthwaite DF on that model
% stays ~= residual DF and does NOT fix the pseudoreplication. Giving each
% animal its OWN slope (a random slope) lets the between-animal slope variance
% enter the standard error, and the Satterthwaite denominator DF then collapses
% toward the number of animals -- matching the per-animal (cluster-honest)
% test. RS fields: .ok (false if the richer model failed to fit), .F, .df1,
% .df2 (Satterthwaite), .p.
rs = struct('ok', false, 'F', NaN, 'df1', NaN, 'df2', NaN, 'p', NaN);
w = warning('off', 'all'); cleanup = onCleanup(@() warning(w)); %#ok<NASGU>
try
lme = fitlme(T, formula);
A = anova(lme, 'DFMethod', 'satterthwaite');
i = strcmp(A.Term, term);
if any(i)
rs = struct('ok', true, 'F', A.FStat(i), 'df1', A.DF1(i), ...
'df2', A.DF2(i), 'p', A.pValue(i));
end
catch
% Random-slope model unidentifiable / non-convergent (common in short
% windows or with very few animals): leave rs.ok = false.
end
end