Files
experiments-database/analysis/matlab/variation_plot.m
T
Experiments DB Dev 8128495049 fix(matlab): rate y-grid every 0.02 (was auto-subdividing to ~0.01)
MATLAB's minor grid ignored MinorTickValues and auto-subdivided finer than
0.02. Make 0.02 the actual (major) tick spacing for rate, label only every 0.1,
and let grid on draw the fine 0.02 gridlines deterministically. Regenerated all
figures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 10:37:09 -04:00

101 lines
4.3 KiB
Matlab

% Variation learning-curve plots, in the style of the paper:
% "Lines indicate mean (and SEM) across animals in the anodal (red) and
% control (blue) groups."
% Produces TWO figures from this folder's data.csv:
% learning_curve.png # successes (count) per training day
% learning_curve_rate.png success rate (success/attempts) per training day
% anodal / treatment = stim 1 (red); control = stim 0 (blue). Per-group N is
% read from the data. Training day is 1-indexed (our day 0 = paper "Day 1") and
% the x-axis tick labels are drawn vertically.
% Run: matlab -batch "plotcurve"
% (Copy of analysis/matlab/variation_plot.m; see make_variation_plot.m.)
here = fileparts(mfilename('fullpath'));
if isempty(here); here = pwd; end
vname = regexprep(here, '.*[/\\]', '');
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
days = unique(D.day);
xd = days + 1; % plot as 1-indexed training day (paper axis)
red = [0.85 0.10 0.10];
blue = [0.10 0.30 0.85];
localPlot(D, days, xd, 'count', '# successes', ...
fullfile(here, 'learning_curve.png'), vname, red, blue);
localPlot(D, days, xd, 'rate', 'success rate', ...
fullfile(here, 'learning_curve_rate.png'), vname, red, blue);
% ------------------------------------------------------------------ helpers
function localPlot(D, days, xd, metric, ylab, outFile, vname, red, blue)
[Ma, Sa, na] = localCurve(D, 1, days, metric); % anodal / treatment
[Mc, Sc, nc] = localCurve(D, 0, days, metric); % control
fig = figure('Visible', 'off', 'Color', 'w', 'Position', [100 100 560 460]);
hold on
e1 = errorbar(xd, Ma, Sa, '-o', 'Color', red, 'MarkerFaceColor', red, 'LineWidth', 2);
e2 = errorbar(xd, Mc, Sc, '-o', 'Color', blue, 'MarkerFaceColor', blue, 'LineWidth', 2);
hold off
legend([e1 e2], {sprintf('anodal, N = %d', na), sprintf('control, N = %d', nc)}, ...
'Location', 'northwest', 'Box', 'off');
xlabel('training day'); ylh = ylabel(ylab);
title(vname, 'Interpreter', 'none');
% Compact publication layout with a tight centred plot box and ticks pointing
% out. Setting XTickLabel {} then xticklabels('auto') resets the labels cleanly
% after the Position change.
set(gca, 'XTick', xd, 'XTickLabel', {}, 'Position', [0.2 0.30 0.60 0.60], ...
'FontName', 'Arial', 'FontSize', 13, 'LineWidth', 1.5, 'Box', 'off', 'TickDir', 'out');
xticklabels('auto')
set(gca, 'XTickLabelRotation', 0); % keep x labels horizontal (no auto-rotate)
xlim([min(xd) - 0.5, max(xd) + 0.5]); % half-day padding so points aren't on the edges
if strcmp(metric, 'count') % count: tick every 5, but label only every 10
yl = ylim(gca);
yt = floor(yl(1) / 5) * 5 : 5 : ceil(yl(2) / 5) * 5;
lbl = cell(1, numel(yt));
for k = 1:numel(yt)
if mod(yt(k), 10) == 0; lbl{k} = num2str(yt(k)); else; lbl{k} = ''; end
end
set(gca, 'YTick', yt, 'YTickLabel', lbl);
else % rate: tick every 0.02, label only every 0.1
yl = ylim(gca);
yt = 0 : 0.02 : ceil(yl(2) / 0.02) * 0.02;
lbl = cell(1, numel(yt));
for k = 1:numel(yt)
if abs(yt(k) / 0.1 - round(yt(k) / 0.1)) < 1e-9 % multiple of 0.1
lbl{k} = num2str(round(yt(k), 1));
else
lbl{k} = '';
end
end
set(gca, 'YTick', yt, 'YTickLabel', lbl);
end
grid on % gridlines fall on the ticks (rate: every 0.02)
% Add breathing room between the y-axis label and the tick numbers.
set(ylh, 'Units', 'normalized');
yp = get(ylh, 'Position');
set(ylh, 'Position', [yp(1) - 0.015, yp(2), 0]);
drawnow;
exportgraphics(fig, outFile, 'Resolution', 150);
close(fig);
fprintf('%s: wrote %s (anodal N=%d, control N=%d)\n', vname, outFile, na, nc);
end
function [M, S, n] = localCurve(D, stimVal, days, metric)
%LOCALCURVE Per-day mean and SEM across the animals in a group, for a metric.
subs = unique(D.subject(D.stim == stimVal));
n = numel(subs);
X = nan(numel(days), n);
for j = 1:n
for i = 1:numel(days)
r = D.subject == subs(j) & D.day == days(i);
if ~any(r); continue; end
if strcmp(metric, 'rate')
tot = sum(D.total(r));
if tot > 0; X(i, j) = sum(D.success(r)) / tot; end
else
X(i, j) = mean(D.success(r));
end
end
end
M = mean(X, 2, 'omitnan');
S = std(X, [], 2, 'omitnan') ./ sqrt(sum(~isnan(X), 2));
end