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 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