7b5c27df8c
Adds tdcs_paper_lme replicating the paper's exact formula/variable names (paper_* switch cases): mergeA2 reproduces the interaction F(1)=7.09 vs 7.12, p=0.009 vs 0.008, with a coverage-confound warning. Rewrites tdcs_power_sim to fit that same formula (interaction term canonicalized to day:stim). Adds a replication test. Suite 37/37. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
92 lines
4.0 KiB
Matlab
92 lines
4.0 KiB
Matlab
function L = tdcs_paper_lme(mergeKey, cfg)
|
|
%TDCS_PAPER_LME Replicate the paper's linear mixed model, verbatim formula:
|
|
% behavior ~ stim + day + stim:day + (1|rat)
|
|
% fit with fitlme on the Box-B2 (stim = 1, tDCS) vs Box-A2 (stim = 0, control)
|
|
% subset of the MERGEKEY grouping ('unmerged'|'mergeA2'|'mergeB2'), over the
|
|
% full training range. `behavior` = successful reaches, `day` = training day
|
|
% (raw; day 0 = the paper's "Day 1"), `rat` = subject. Reports the three
|
|
% effects (stim x day interaction, day, stim) as t(df) / F(1) / p with the
|
|
% interaction 95% CI, alongside the paper's reference values, and writes
|
|
% results/paper_<MERGEKEY>.txt.
|
|
%
|
|
% (This is the same model as tdcs_lme -- success ~ day*tDCS + (1|subject) --
|
|
% written with the paper's exact term order and variable names.)
|
|
%
|
|
% L fields: .model .nRats .nObs and .stim/.day/.interaction effect structs
|
|
% (.estimate .se .t .df .p from Coefficients; .F .df1 .df2 .Fp from ANOVA;
|
|
% .ci = coefficient 95% CI).
|
|
|
|
Sfull = tdcs_scenario_data([mergeKey '_full']);
|
|
A = Sfull(ismember(Sfull.group, {cfg.anchorLow, cfg.anchorHigh}), :);
|
|
|
|
tbl = table();
|
|
tbl.behavior = A.success;
|
|
tbl.day = A.day;
|
|
tbl.stim = double(A.group == cfg.anchorHigh); % Box-B2 = 1, Box-A2 = 0
|
|
tbl.rat = A.subject;
|
|
|
|
model = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
|
C = model.Coefficients; An = anova(model); ci = coefCI(model);
|
|
|
|
L.model = model;
|
|
L.nRats = numel(unique(tbl.rat));
|
|
L.nObs = height(tbl);
|
|
L.maxDayCtrl = max(tbl.day(tbl.stim == 0));
|
|
L.maxDayStim = max(tbl.day(tbl.stim == 1));
|
|
L.stim = localTerm(C, An, ci, 'stim');
|
|
L.day = localTerm(C, An, ci, 'day');
|
|
L.interaction = localTerm(C, An, ci, 'day:stim'); % MATLAB canonicalizes stim:day -> day:stim
|
|
|
|
localReport(L, mergeKey, cfg);
|
|
|
|
end
|
|
|
|
function e = localTerm(C, An, ci, name)
|
|
i = strcmp(C.Name, name);
|
|
if ~any(i)
|
|
error('tdcs_paper_lme:missingTerm', 'No "%s" coefficient (have: %s).', ...
|
|
name, strjoin(C.Name, ', '));
|
|
end
|
|
ai = strcmp(An.Term, name);
|
|
e = struct('estimate', C.Estimate(i), 'se', C.SE(i), 't', C.tStat(i), ...
|
|
'df', C.DF(i), 'p', C.pValue(i), 'F', An.FStat(ai), 'df1', An.DF1(ai), ...
|
|
'df2', An.DF2(ai), 'Fp', An.pValue(ai), 'ci', ci(i, :));
|
|
end
|
|
|
|
function localReport(L, mergeKey, cfg)
|
|
bar = repmat('=', 1, 78);
|
|
s = sprintf('%s\n', bar);
|
|
s = [s sprintf('PAPER LME REPLICATION -- %s\n', mergeKey)];
|
|
s = [s sprintf('%s\n', bar)];
|
|
s = [s sprintf('model: behavior ~ stim + day + stim:day + (1|rat) [stim: %s=1 vs %s=0]\n', ...
|
|
cfg.anchorHigh, cfg.anchorLow)];
|
|
s = [s sprintf('N = %d rats, %d sessions (day raw; day 0 = paper "Day 1")\n', L.nRats, L.nObs)];
|
|
s = [s sprintf('day coverage: stim(B2) 0..%d, control(A2) 0..%d\n', L.maxDayStim, L.maxDayCtrl)];
|
|
if abs(L.maxDayStim - L.maxDayCtrl) > 2
|
|
s = [s sprintf(['** WARNING: unequal day coverage -- the full-range stim:day interaction\n' ...
|
|
' extrapolates the control group''s line and is CONFOUNDED here (the paper''s\n' ...
|
|
' groups had equal coverage). See the _d0_13 fair-window and phased analyses. **\n'])];
|
|
end
|
|
s = [s sprintf('\n%-26s %-13s %-13s %s\n', 'effect', 't (df)', 'F (df1)', 'p')];
|
|
s = [s sprintf('%s\n', repmat('-', 1, 66))];
|
|
s = [s localRow('stim x day (interaction)', L.interaction)];
|
|
s = [s localRow('day (learning)', L.day)];
|
|
s = [s localRow('stim (main, Day 1)', L.stim)];
|
|
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', L.interaction.ci(1), L.interaction.ci(2))];
|
|
s = [s sprintf(['\nPaper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008; ' ...
|
|
'day t(227)=9.64,\n F(1)=267.64, p=1.2e-18; stim t(227)=0.23, F(1)=0.053, p=0.81.\n'])];
|
|
|
|
fprintf('%s', s);
|
|
thisDir = fileparts(mfilename('fullpath'));
|
|
resDir = fullfile(thisDir, 'results');
|
|
if ~exist(resDir, 'dir'); mkdir(resDir); end
|
|
fid = fopen(fullfile(resDir, ['paper_' mergeKey '.txt']), 'w');
|
|
if fid < 0; error('tdcs_paper_lme:fopen', 'Cannot open results file.'); end
|
|
cleanup = onCleanup(@() fclose(fid)); %#ok<NASGU>
|
|
fprintf(fid, '%s', s);
|
|
end
|
|
|
|
function r = localRow(name, e)
|
|
r = sprintf('%-26s t(%d)=%6.2f F(%d)=%8.3f p=%.4g\n', name, e.df, e.t, e.df1, e.F, e.p);
|
|
end
|