analysis(matlab): Box-A pooling variations (b2 vs a2+a, b2+a vs a2)

Add make_boxa_variations.m producing 6 variation folders (data.csv + analyze.m
+ result.txt) for the paper LME over windows 0-5, 6-10, 0-10:
  boxa_a2  B2         vs A2 + Box-A   (b2 vs a2+a)
  boxa_b2  B2 + Box-A vs A2           (b2+a vs a2)
plus variations/boxa_summary.csv (residual / Satterthwaite / random-slope
interaction p). Early window (0-5) is obs-level significant (res p~0.03-0.04)
but n.s. under the honest random-slope test (rs p~0.13); later windows n.s.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Experiments DB Dev
2026-07-23 13:39:01 -04:00
parent b03549982e
commit e1af1af7f4
20 changed files with 1370 additions and 0 deletions
@@ -0,0 +1,98 @@
% Variation analysis -- the paper's linear mixed model on the successful-reach
% COUNT, fit on this folder's curated data subset.
%
% model: behavior ~ stim + day + stim:day + (1|rat)
% behavior = successful reaches (count per session)
% stim = 1 for the treatment group(s), 0 for the control group(s)
% day = training day within this window (0 = first analyzed day)
% rat = subject (random intercept)
%
% Self-contained: reads data.csv beside this script and writes result.txt.
% Run headless from this folder with: matlab -batch "analyze"
% (This is a copy of analysis/matlab/variation_analyze.m; see make_variations.m.)
here = fileparts(mfilename('fullpath'));
if isempty(here); here = pwd; end
vname = regexprep(here, '.*[/\\]', ''); % folder name = variation id
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject), ...
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
C = m.Coefficients; A = anova(m); ci = coefCI(m);
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
% Honest test: refit with a per-animal random SLOPE so the interaction DF
% collapses toward the animal count (guarded -- may not converge in short windows).
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
wst = warning('off', 'all');
try
mr = fitlme(tbl, 'behavior ~ stim + day + stim:day + (day|rat)');
Ar = anova(mr, 'DFMethod', 'satterthwaite');
ri = strcmp(Ar.Term, 'day:stim');
rsF = Ar.FStat(ri); rsDf = Ar.DF2(ri); rsP = Ar.pValue(ri); rsOk = true;
catch
end
warning(wst);
gi = @(t) find(strcmp(C.Name, t), 1);
ga = @(t) find(strcmp(A.Term, t), 1);
gs = @(t) find(strcmp(As.Term, t), 1);
row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f)\n', nm, ...
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
As.pValue(gs(t)), As.DF2(gs(t)));
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
if abs(maxT - maxC) > 2
cov = '** WARNING: unequal day coverage -- interaction may be confounded. **';
else
cov = '(equal day coverage over this window)';
end
ii = gi('day:stim'); pI = C.pValue(ii); eI = C.Estimate(ii);
if pI >= 0.05
verdict = 'n.s. -- slopes parallel (no differential learning rate)';
elseif eI > 0
verdict = 'SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates)';
else
verdict = 'SIGNIFICANT negative -- treatment improves SLOWER (groups converge)';
end
bar = repmat('=', 1, 78);
raw = regexprep(evalc('disp(m)'), '</?strong>', '');
s = sprintf('%s\nVARIATION: %s\n%s\n', bar, vname, bar);
s = [s sprintf('model: behavior ~ stim + day + stim:day + (1|rat) (behavior = success COUNT)\n')];
s = [s sprintf('day = training day within window (0 = first analyzed day)\n')];
s = [s sprintf('treatment (stim=1): %s\n', strjoin(cellstr(unique(D.group(D.stim == 1))), ', '))];
s = [s sprintf('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim == 0))), ', '))];
s = [s sprintf('N = %d rats, %d sessions raw day coverage: treat %d..%d, control %d..%d\n', ...
numel(unique(D.subject)), height(D), minT, maxT, minC, maxC)];
s = [s sprintf('%s\n\n%s\nFULL MODEL SUMMARY -- fitlme\n%s\n%s\n', cov, bar, bar, raw)];
s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (resid)', 'Satterthwaite: p (df)', repmat('-', 1, 76))];
s = [s row('stim x day (interaction)', 'day:stim')];
s = [s row('day (learning)', 'day')];
s = [s row('stim (main, window start)', 'stim')];
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
if rsOk
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
else
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): did not converge for this window.\n')];
end
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
' model above is the honest learning-rate test -- DF collapses toward the animal count.)\n'])];
s = [s sprintf('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.2f)\n', verdict, pI, eI)];
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
fprintf('%s', s);
fid = fopen(fullfile(here, 'result.txt'), 'w');
fprintf(fid, '%s', s);
fclose(fid);
% Machine-readable handoff for the summary table (see make_variations.m).
VARRESULT = struct('name', vname, 'nRats', numel(unique(D.subject)), ...
'nObs', height(D), 'interP', pI, 'interEst', eI, ...
'interPsatt', As.pValue(gs('day:stim')), 'interPrs', rsP, ...
'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
'covEqual', abs(maxT - maxC) <= 2);
@@ -0,0 +1,31 @@
subject,group,day,success,total,stim
Banh-mi-1,Electrode-Box-B2,6,90,131,1
Banh-mi-1,Electrode-Box-B2,7,109,148,1
Banh-mi-1,Electrode-Box-B2,8,104,137,1
Banh-mi-1,Electrode-Box-B2,9,119,150,1
Banh-mi-1,Electrode-Box-B2,10,121,158,1
Banh-mi-2,Electrode-Box-A2,6,74,146,0
Banh-mi-2,Electrode-Box-A2,7,70,148,0
Banh-mi-2,Electrode-Box-A2,8,65,130,0
Banh-mi-2,Electrode-Box-A2,9,79,151,0
Banh-mi-2,Electrode-Box-A2,10,93,152,0
Egg-tart-1,Electrode-Box-B2,6,71,142,1
Egg-tart-1,Electrode-Box-B2,7,79,138,1
Egg-tart-1,Electrode-Box-B2,8,98,142,1
Egg-tart-1,Electrode-Box-B2,9,89,139,1
Egg-tart-1,Electrode-Box-B2,10,96,143,1
Egg-tart-2,Electrode-Box-A2,6,85,145,0
Egg-tart-2,Electrode-Box-A2,7,79,143,0
Egg-tart-2,Electrode-Box-A2,8,76,131,0
Egg-tart-2,Electrode-Box-A2,9,88,149,0
Egg-tart-2,Electrode-Box-A2,10,81,151,0
Khoai-tay-1,Electrode-Box-A,6,81,137,0
Khoai-tay-1,Electrode-Box-A,7,78,147,0
Khoai-tay-1,Electrode-Box-A,8,91,132,0
Khoai-tay-1,Electrode-Box-A,9,99,146,0
Khoai-tay-1,Electrode-Box-A,10,94,143,0
Root-beer-1,Electrode-Box-B2,6,104,139,1
Root-beer-1,Electrode-Box-B2,7,98,148,1
Root-beer-1,Electrode-Box-B2,8,81,145,1
Root-beer-1,Electrode-Box-B2,9,89,156,1
Root-beer-1,Electrode-Box-B2,10,105,158,1
1 subject group day success total stim
2 Banh-mi-1 Electrode-Box-B2 6 90 131 1
3 Banh-mi-1 Electrode-Box-B2 7 109 148 1
4 Banh-mi-1 Electrode-Box-B2 8 104 137 1
5 Banh-mi-1 Electrode-Box-B2 9 119 150 1
6 Banh-mi-1 Electrode-Box-B2 10 121 158 1
7 Banh-mi-2 Electrode-Box-A2 6 74 146 0
8 Banh-mi-2 Electrode-Box-A2 7 70 148 0
9 Banh-mi-2 Electrode-Box-A2 8 65 130 0
10 Banh-mi-2 Electrode-Box-A2 9 79 151 0
11 Banh-mi-2 Electrode-Box-A2 10 93 152 0
12 Egg-tart-1 Electrode-Box-B2 6 71 142 1
13 Egg-tart-1 Electrode-Box-B2 7 79 138 1
14 Egg-tart-1 Electrode-Box-B2 8 98 142 1
15 Egg-tart-1 Electrode-Box-B2 9 89 139 1
16 Egg-tart-1 Electrode-Box-B2 10 96 143 1
17 Egg-tart-2 Electrode-Box-A2 6 85 145 0
18 Egg-tart-2 Electrode-Box-A2 7 79 143 0
19 Egg-tart-2 Electrode-Box-A2 8 76 131 0
20 Egg-tart-2 Electrode-Box-A2 9 88 149 0
21 Egg-tart-2 Electrode-Box-A2 10 81 151 0
22 Khoai-tay-1 Electrode-Box-A 6 81 137 0
23 Khoai-tay-1 Electrode-Box-A 7 78 147 0
24 Khoai-tay-1 Electrode-Box-A 8 91 132 0
25 Khoai-tay-1 Electrode-Box-A 9 99 146 0
26 Khoai-tay-1 Electrode-Box-A 10 94 143 0
27 Root-beer-1 Electrode-Box-B2 6 104 139 1
28 Root-beer-1 Electrode-Box-B2 7 98 148 1
29 Root-beer-1 Electrode-Box-B2 8 81 145 1
30 Root-beer-1 Electrode-Box-B2 9 89 156 1
31 Root-beer-1 Electrode-Box-B2 10 105 158 1
@@ -0,0 +1,68 @@
==============================================================================
VARIATION: boxa_a2_d6_10
==============================================================================
model: behavior ~ stim + day + stim:day + (1|rat) (behavior = success COUNT)
day = training day within window (0 = first analyzed day)
treatment (stim=1): Electrode-Box-B2
control (stim=0): Electrode-Box-A, Electrode-Box-A2
N = 6 rats, 30 sessions raw day coverage: treat 6..10, control 6..10
(equal day coverage over this window)
==============================================================================
FULL MODEL SUMMARY -- fitlme
==============================================================================
Linear mixed-effects model fit by ML
Model information:
Number of observations 30
Fixed effects coefficients 4
Random effects coefficients 6
Covariance parameters 2
Formula:
behavior ~ 1 + day*stim + (1 | rat)
Model fit statistics:
AIC BIC LogLikelihood Deviance
231.74 240.15 -109.87 219.74
Fixed effects coefficients (95% CIs):
Name Estimate SE tStat DF pValue
{'(Intercept)'} 75.867 5.1856 14.63 26 4.6141e-14
{'day' } 3.1667 1.4985 2.1132 26 0.044334
{'stim' } 12.667 7.3335 1.7272 26 0.095989
{'day:stim' } 1 2.1192 0.47188 26 0.64095
Lower Upper
65.208 86.526
0.086483 6.2469
-2.4075 27.741
-3.356 5.356
Random effects covariance parameters (95% CIs):
Group: rat (6 Levels)
Name1 Name2 Type Estimate
{'(Intercept)'} {'(Intercept)'} {'std'} 6.3444
Lower Upper
2.9639 13.581
Group: Error
Name Estimate Lower Upper
{'Res Std'} 8.2076 6.1852 10.891
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
----------------------------------------------------------------------------
stim x day (interaction) t(26)= 0.47 F(1)= 0.223 p=0.6409 p=0.6413 (df=24)
day (learning) t(26)= 2.11 F(1)= 4.466 p=0.04433 p=0.04517 (df=24)
stim (main, window start) t(26)= 1.73 F(1)= 2.983 p=0.09599 p=0.1083 (df=13)
interaction 95% CI: [-3.36, +5.36]
HONEST LME (per-animal random slope, day|rat): interaction F(1,6.0)=0.18, p=0.6872
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
model above is the honest learning-rate test -- DF collapses toward the animal count.)
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.6409, slope diff=+1.00)
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.