feat(matlab): learning figure + methods write-up + organized CSV data export

Adds make_figure (2-panel learning curves + per-phase slopes, colorblind-safe
Box-B2/Box-A2 palette) -> results/figure_learning.png; analysis/writeup.md
(figure + methods + results summary); and tdcs_export_all/run_export exporting
every data variation into an organized export/ tree (curated_all, scenarios/9,
anchors/3 with tDCS factor, phases/9 + MANIFEST). Adds tExport tests. Suite 36/36.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Experiments DB Dev
2026-07-20 12:11:37 -04:00
parent e730aa0020
commit 800189371b
30 changed files with 2535 additions and 0 deletions
+89
View File
@@ -0,0 +1,89 @@
%MAKE_FIGURE Two-panel learning figure (Box-B2 vs Box-A2), saved to results/.
% Panel A: mean +/- SEM success per training day over the fair 0-13 window,
% with the fast/slow phase boundary. Panel B: per-animal learning
% slope (mean +/- 95% CI) by phase. Uses the mergeA2 grouping.
% Usage: matlab -batch "make_figure"
cfg = tdcs_config();
Sfull = tdcs_scenario_data('mergeA2_full');
T = Sfull(ismember(Sfull.group, {cfg.anchorLow, cfg.anchorHigh}) & Sfull.day <= 13, :);
B2 = [42 120 214]/255; % #2a78d6 (categorical slot 1)
A2 = [0 131 0]/255; % #008300 (categorical slot 2)
groups = {cfg.anchorHigh, cfg.anchorLow};
cols = {B2, A2};
names = {'Box-B2 (tDCS)', 'Box-A2 (control)'};
mk = {'o', 's'};
ink = [0.10 0.10 0.10];
f = figure('Visible', 'off', 'Position', [100 100 1150 460], 'Color', 'w');
tl = tiledlayout(f, 1, 2, 'Padding', 'compact', 'TileSpacing', 'compact');
% ---- Panel A: learning curves ----
ax1 = nexttile(tl); hold(ax1, 'on');
days = 0:13;
hLines = gobjects(1, 2);
for gi = 1:2
G = T(T.group == groups{gi}, :);
mu = nan(size(days)); se = nan(size(days));
for d = days
v = G.success(G.day == d);
if ~isempty(v); mu(d+1) = mean(v); se(d+1) = std(v)/sqrt(numel(v)); end
end
ok = ~isnan(mu);
fill(ax1, [days(ok) fliplr(days(ok))], [mu(ok)+se(ok) fliplr(mu(ok)-se(ok))], ...
cols{gi}, 'FaceAlpha', 0.13, 'EdgeColor', 'none', 'HandleVisibility', 'off');
hLines(gi) = plot(ax1, days(ok), mu(ok), '-', 'Color', cols{gi}, 'LineWidth', 2, ...
'Marker', mk{gi}, 'MarkerFaceColor', cols{gi}, 'MarkerEdgeColor', cols{gi}, 'MarkerSize', 5);
end
yl = ylim(ax1);
xline(ax1, 5.5, '--', 'Color', [0.45 0.45 0.45], 'HandleVisibility', 'off');
text(ax1, 2.6, yl(2)*0.97, 'fast (0-5)', 'Color', ink, 'FontSize', 9, 'HorizontalAlignment', 'center');
text(ax1, 9.6, yl(2)*0.97, 'slow (6-13)', 'Color', ink, 'FontSize', 9, 'HorizontalAlignment', 'center');
xlabel(ax1, 'Training day (# Days Reach; day 0 = paper "Day 1")', 'Color', ink);
ylabel(ax1, 'Successful reaches / session', 'Color', ink);
title(ax1, 'A Learning curves (mean \pm SEM)', 'Color', ink);
legend(ax1, hLines, names, 'Location', 'northwest', 'Box', 'off', 'TextColor', ink);
grid(ax1, 'on'); ax1.GridAlpha = 0.10; ax1.XColor = ink; ax1.YColor = ink; ax1.Box = 'off';
xlim(ax1, [-0.3 13.3]);
% ---- Panel B: per-animal slope by phase ----
ax2 = nexttile(tl); hold(ax2, 'on');
phases = {[0 5], [6 13]}; phaseX = [1 2]; off = 0.14;
hB = gobjects(1, 2);
for gi = 1:2
for pi = 1:2
sl = localSlopes(T, groups{gi}, phases{pi});
m = mean(sl); ci = tinv(0.975, numel(sl)-1) * std(sl)/sqrt(numel(sl));
x = phaseX(pi) + (2*gi-3)*off; % gi=1 -> -off, gi=2 -> +off
h = errorbar(ax2, x, m, ci, 'Marker', mk{gi}, 'Color', cols{gi}, ...
'MarkerFaceColor', cols{gi}, 'MarkerEdgeColor', cols{gi}, 'MarkerSize', 8, ...
'LineWidth', 1.8, 'CapSize', 8);
if pi == 1; hB(gi) = h; end
end
end
yline(ax2, 0, ':', 'Color', [0.6 0.6 0.6], 'HandleVisibility', 'off');
xticks(ax2, [1 2]); xticklabels(ax2, {'fast (0-5)', 'slow (6-13)'});
xlim(ax2, [0.5 2.5]);
ylabel(ax2, 'Learning slope (reaches / day)', 'Color', ink);
title(ax2, 'B Per-animal learning rate by phase (mean, 95% CI)', 'Color', ink);
legend(ax2, hB, names, 'Location', 'northeast', 'Box', 'off', 'TextColor', ink);
grid(ax2, 'on'); ax2.GridAlpha = 0.10; ax2.XColor = ink; ax2.YColor = ink; ax2.Box = 'off';
thisDir = fileparts(mfilename('fullpath'));
resDir = fullfile(thisDir, 'results');
if ~exist(resDir, 'dir'); mkdir(resDir); end
outPng = fullfile(resDir, 'figure_learning.png');
exportgraphics(f, outPng, 'Resolution', 200);
fprintf('wrote %s\n', outPng);
function sl = localSlopes(T, grp, ph)
Tg = T(T.group == grp & T.day >= ph(1) & T.day <= ph(2), :);
subs = unique(Tg.subject); sl = [];
for i = 1:numel(subs)
r = Tg.subject == subs(i);
if numel(unique(Tg.day(r))) >= 2
c = polyfit(Tg.day(r), Tg.success(r), 1); sl(end+1,1) = c(1); %#ok<AGROW>
end
end
end