fix(matlab): report learning rate via per-animal slope test, not anticonservative GLMM F-test
fitglme coefTest only offers observation-level DF (df2~=178), which overstates the group x day interaction significance for this few-subject design (p~=0 in every scenario). Replace the reported learning-rate result with a cluster-honest per-animal slope test (per-subject OLS slope of success vs day, Welch + MWU, B2 vs A2) -- now correctly parallel in all 6 scenarios, matching the Python GEE. The GLMM F-test is kept as a flagged reference only. Adds a regression test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -12,9 +12,18 @@ function R = tdcs_models(S, meta, cfg)
|
||||
% R.learnGLME GeneralizedLinearMixedModel, Poisson learning-rate
|
||||
% model with a group x day_c interaction:
|
||||
% success ~ group*day_c + day_c2 + (1|subject)
|
||||
% R.learn struct with the joint Wald test (via COEFTEST) that
|
||||
% all group x day_c interaction terms are zero:
|
||||
% .jointP, .jointF, .jointDF1, .jointDF2.
|
||||
% R.learn learning-rate comparison, Box-B2 vs Box-A2. The reported
|
||||
% result is a PER-ANIMAL slope test (cluster-honest, like
|
||||
% R.perAnimal): each subject's OLS slope of success on day,
|
||||
% then Welch t / two-sided Mann-Whitney across groups:
|
||||
% .slopeWelchP, .slopeMWUp, .nB, .nA, .meanSlopeB,
|
||||
% .meanSlopeA. The GLMM group x day_c joint F-test is also
|
||||
% kept for reference (.glmeJointP, .glmeJointF, .glmeJointDF1,
|
||||
% .glmeJointDF2) but is ANTICONSERVATIVE here: fitglme's
|
||||
% coefTest offers only observation-level DF, which overstates
|
||||
% significance for this few-subject design (it reads p~=0
|
||||
% even when the per-animal slopes are clearly parallel), so
|
||||
% it is NOT the basis for the conclusion.
|
||||
% R.perAnimal per-subject Box-B2 vs Box-A2 comparison (pure stats,
|
||||
% no GLME involved): .countWelchP, .countMWUp,
|
||||
% .rateWelchP, .rateMWUp, .nB, .nA. "count" = per-subject
|
||||
@@ -51,10 +60,10 @@ R.rateGLME = fitglme(Sr, countFormula, 'Distribution', 'Binomial', ...
|
||||
|
||||
learnFormula = 'success ~ group*day_c + day_c2 + (1|subject)';
|
||||
R.learnGLME = fitglme(S, learnFormula, 'Distribution', 'Poisson');
|
||||
R.learn = localJointInteractionTest(R.learnGLME);
|
||||
|
||||
R.perAnimal = localPerAnimal(S, cfg);
|
||||
R.h2 = localH2(R.countGLME, R.rateGLME, cfg);
|
||||
R.learn = localLearn(R.learnGLME, S, cfg);
|
||||
|
||||
if meta.isUnmerged
|
||||
R.classify = localClassifyAll(R.countGLME, R.rateGLME, cfg);
|
||||
@@ -200,6 +209,34 @@ end
|
||||
end
|
||||
|
||||
% -------------------------------------------------------------- learning
|
||||
function out = localLearn(learnGLME, S, cfg)
|
||||
%LOCALLEARN Learning-rate result: per-animal slope test (primary, cluster-
|
||||
% honest) plus the GLMM interaction joint F-test (reference, anticonservative).
|
||||
bSlopes = localSubjectSlopes(S(S.group == cfg.anchorHigh, :));
|
||||
aSlopes = localSubjectSlopes(S(S.group == cfg.anchorLow, :));
|
||||
[~, slopeWelchP] = ttest2(bSlopes, aSlopes, 'Vartype', 'unequal');
|
||||
slopeMWUp = ranksum(bSlopes, aSlopes); % two-sided: slope-diff direction unknown
|
||||
|
||||
j = localJointInteractionTest(learnGLME);
|
||||
out = struct( ...
|
||||
'slopeWelchP', slopeWelchP, 'slopeMWUp', slopeMWUp, ...
|
||||
'nB', numel(bSlopes), 'nA', numel(aSlopes), ...
|
||||
'meanSlopeB', mean(bSlopes), 'meanSlopeA', mean(aSlopes), ...
|
||||
'glmeJointP', j.jointP, 'glmeJointF', j.jointF, ...
|
||||
'glmeJointDF1', j.jointDF1, 'glmeJointDF2', j.jointDF2);
|
||||
end
|
||||
|
||||
function slopes = localSubjectSlopes(T)
|
||||
%LOCALSUBJECTSLOPES Per-subject OLS slope of success on day.
|
||||
subj = unique(T.subject);
|
||||
slopes = zeros(numel(subj), 1);
|
||||
for i = 1:numel(subj)
|
||||
rows = T.subject == subj(i);
|
||||
coeffs = polyfit(T.day(rows), T.success(rows), 1);
|
||||
slopes(i) = coeffs(1);
|
||||
end
|
||||
end
|
||||
|
||||
function out = localJointInteractionTest(learnGLME)
|
||||
%LOCALJOINTINTERACTIONTEST Joint Wald test that all group x day_c
|
||||
% interaction fixed effects are zero (do groups learn at different
|
||||
|
||||
Reference in New Issue
Block a user