analysis(matlab): rate + count outputs, variation batch 5
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@@ -1,30 +1,58 @@
|
|||||||
% Variation analysis -- the paper's linear mixed model on the successful-reach
|
% Variation analysis -- the paper's linear mixed model on this folder's data,
|
||||||
% COUNT, fit on this folder's curated data subset.
|
% for BOTH metrics:
|
||||||
|
% metric = count : behavior = # successes -> result.txt
|
||||||
|
% metric = rate : behavior = success / attempts -> result_rate.txt
|
||||||
|
% (rate uses only sessions with attempts > 0)
|
||||||
%
|
%
|
||||||
% model: behavior ~ stim + day + stim:day + (1|rat)
|
% model: behavior ~ stim + day + stim:day + (1|rat)
|
||||||
% behavior = successful reaches (count per session)
|
% stim = 1 treatment / 0 control; day = training day within window (0 =
|
||||||
% stim = 1 for the treatment group(s), 0 for the control group(s)
|
% first analyzed day); rat = subject (random intercept).
|
||||||
% day = training day within this window (0 = first analyzed day)
|
% For the interaction we report residual DF, Satterthwaite DF, and the honest
|
||||||
% rat = subject (random intercept)
|
% per-animal random-slope test. Self-contained: reads data.csv beside this
|
||||||
%
|
% script. Run headless with: matlab -batch "analyze"
|
||||||
% Self-contained: reads data.csv beside this script and writes result.txt.
|
% (Copy of analysis/matlab/variation_analyze.m; see make_variations.m.)
|
||||||
% 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'));
|
here = fileparts(mfilename('fullpath'));
|
||||||
if isempty(here); here = pwd; end
|
if isempty(here); here = pwd; end
|
||||||
vname = regexprep(here, '.*[/\\]', ''); % folder name = variation id
|
vname = regexprep(here, '.*[/\\]', '');
|
||||||
|
|
||||||
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
||||||
|
|
||||||
tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject), ...
|
Rc = localAnalyze(D, 'count', here, vname);
|
||||||
|
Rr = localAnalyze(D, 'rate', here, vname);
|
||||||
|
|
||||||
|
% Machine-readable handoff for SUMMARY.csv (count drives it; rate appended).
|
||||||
|
VARRESULT = struct('name', vname, 'nRats', Rc.nRats, 'nObs', Rc.nObs, ...
|
||||||
|
'interP', Rc.interP, 'interEst', Rc.interEst, ...
|
||||||
|
'interPsatt', Rc.interPsatt, 'interPrs', Rc.interPrs, ...
|
||||||
|
'stimP', Rc.stimP, 'dayP', Rc.dayP, 'covEqual', Rc.covEqual, ...
|
||||||
|
'interPrate', Rr.interP, 'interEstRate', Rr.interEst, 'interPrsRate', Rr.interPrs);
|
||||||
|
|
||||||
|
% ------------------------------------------------------------------ helper
|
||||||
|
function R = localAnalyze(D, metric, here, vname)
|
||||||
|
if strcmp(metric, 'rate')
|
||||||
|
D = D(D.total > 0, :);
|
||||||
|
beh = D.success ./ D.total;
|
||||||
|
mlabel = 'success RATE (success/attempts)'; suffix = '_rate';
|
||||||
|
else
|
||||||
|
beh = D.success;
|
||||||
|
mlabel = 'success COUNT'; suffix = '';
|
||||||
|
end
|
||||||
|
R = struct('interP', NaN, 'interEst', NaN, 'interPsatt', NaN, 'interPrs', NaN, ...
|
||||||
|
'stimP', NaN, 'dayP', NaN, 'nRats', numel(unique(D.subject)), ...
|
||||||
|
'nObs', height(D), 'covEqual', false);
|
||||||
|
if numel(unique(D.stim)) < 2 || numel(unique(D.day)) < 2
|
||||||
|
localWrite(sprintf('VARIATION: %s [metric: %s]\nInsufficient data for this metric.\n', ...
|
||||||
|
vname, mlabel), here, suffix);
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
tbl = table(beh, D.day - min(D.day), double(D.stim), categorical(D.subject), ...
|
||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
As = anova(m, 'DFMethod', 'satterthwaite');
|
||||||
|
|
||||||
% 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;
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
wst = warning('off', 'all');
|
wst = warning('off', 'all');
|
||||||
try
|
try
|
||||||
@@ -43,6 +71,7 @@ row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f
|
|||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
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)));
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
|
ii = gi('day:stim'); pI = C.pValue(ii); eI = C.Estimate(ii);
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
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));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
if abs(maxT - maxC) > 2
|
if abs(maxT - maxC) > 2
|
||||||
@@ -50,20 +79,14 @@ if abs(maxT - maxC) > 2
|
|||||||
else
|
else
|
||||||
cov = '(equal day coverage over this window)';
|
cov = '(equal day coverage over this window)';
|
||||||
end
|
end
|
||||||
|
if pI >= 0.05; verdict = 'n.s. -- slopes parallel (no differential learning rate)';
|
||||||
ii = gi('day:stim'); pI = C.pValue(ii); eI = C.Estimate(ii);
|
elseif eI > 0; verdict = 'SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates)';
|
||||||
if pI >= 0.05
|
else; verdict = 'SIGNIFICANT negative -- treatment improves SLOWER (groups converge)'; end
|
||||||
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);
|
bar = repmat('=', 1, 78);
|
||||||
raw = regexprep(evalc('disp(m)'), '</?strong>', '');
|
raw = regexprep(evalc('disp(m)'), '</?strong>', '');
|
||||||
s = sprintf('%s\nVARIATION: %s\n%s\n', bar, vname, bar);
|
s = sprintf('%s\nVARIATION: %s [metric: %s]\n%s\n', bar, vname, mlabel, bar);
|
||||||
s = [s sprintf('model: behavior ~ stim + day + stim:day + (1|rat) (behavior = success COUNT)\n')];
|
s = [s sprintf('model: behavior ~ stim + day + stim:day + (1|rat) (behavior = %s)\n', mlabel)];
|
||||||
s = [s sprintf('day = training day within window (0 = first analyzed day)\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('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('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim == 0))), ', '))];
|
||||||
@@ -74,7 +97,7 @@ s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (res
|
|||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.4g, %+.4g]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
if rsOk
|
if rsOk
|
||||||
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
else
|
else
|
||||||
@@ -82,17 +105,18 @@ else
|
|||||||
end
|
end
|
||||||
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
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'])];
|
' 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('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.4g)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24, count): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
|
localWrite(s, here, suffix);
|
||||||
|
|
||||||
|
R = struct('interP', pI, 'interEst', eI, 'interPsatt', As.pValue(gs('day:stim')), ...
|
||||||
|
'interPrs', rsP, 'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
|
'nRats', numel(unique(D.subject)), 'nObs', height(D), 'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
end
|
||||||
|
|
||||||
|
function localWrite(s, here, suffix)
|
||||||
fprintf('%s', s);
|
fprintf('%s', s);
|
||||||
fid = fopen(fullfile(here, 'result.txt'), 'w');
|
fid = fopen(fullfile(here, ['result' suffix '.txt']), 'w');
|
||||||
fprintf(fid, '%s', s);
|
fprintf(fid, '%s', s); fclose(fid);
|
||||||
fclose(fid);
|
end
|
||||||
|
|
||||||
% 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);
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 41 KiB |
@@ -1,7 +1,7 @@
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
LOG-DAY MODEL + COHEN'S f + POWER -- unmerge_d0_13
|
LOG-DAY MODEL + COHEN'S f + POWER -- unmerge_d0_13 [metric: # successes (count)]
|
||||||
==============================================================================
|
==============================================================================
|
||||||
model: behavior ~ stim + log(day) + stim:log(day) + (1|rat) (success COUNT)
|
model: behavior ~ stim + log(day) + stim:log(day) + (1|rat) (behavior = # successes (count))
|
||||||
log(day) uses 1-indexed training day (our day 0 = paper "Day 1")
|
log(day) uses 1-indexed training day (our day 0 = paper "Day 1")
|
||||||
observed groups: stim n=3, control n=3 nrep=120, alpha=0.05
|
observed groups: stim n=3, control n=3 nrep=120, alpha=0.05
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ stim x log(day) interaction: F(1,66)=8.712 p(resid)=0.004373 p(Satt)=0.004357
|
|||||||
honest per-animal random slope (log-day): F(1,12.9)=6.96 p=0.02062
|
honest per-animal random slope (log-day): F(1,12.9)=6.96 p=0.02062
|
||||||
Cohen's f (interaction, partial eta^2=0.015) = 0.124 (small-medium; f: .10 small, .25 medium, .40 large)
|
Cohen's f (interaction, partial eta^2=0.015) = 0.124 (small-medium; f: .10 small, .25 medium, .40 large)
|
||||||
|
|
||||||
--- power simulation (log-day ground truth: stim:logday=+11.06, ratSD=5.68, resSD=11.36) ---
|
--- power simulation (log-day ground truth: stim:logday=+11.06, ratSD=5.676, resSD=11.36) ---
|
||||||
|
|
||||||
true stim:log(day) = +11.06 (100% of observed)
|
true stim:log(day) = +11.06 (100% of observed)
|
||||||
N/group | per-animal power | LME power
|
N/group | per-animal power | LME power
|
||||||
@@ -22,7 +22,7 @@ Cohen's f (interaction, partial eta^2=0.015) = 0.124 (small-medium; f: .10 smal
|
|||||||
16 | 1.00 | 1.00
|
16 | 1.00 | 1.00
|
||||||
24 | 1.00 | 1.00
|
24 | 1.00 | 1.00
|
||||||
|
|
||||||
true stim:log(day) = +5.53 (50% of observed)
|
true stim:log(day) = +5.528 (50% of observed)
|
||||||
N/group | per-animal power | LME power
|
N/group | per-animal power | LME power
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
3 | 0.18 | 0.38 <- observed
|
3 | 0.18 | 0.38 <- observed
|
||||||
@@ -32,5 +32,4 @@ Cohen's f (interaction, partial eta^2=0.015) = 0.124 (small-medium; f: .10 smal
|
|||||||
16 | 0.97 | 0.98
|
16 | 0.97 | 0.98
|
||||||
24 | 0.98 | 0.98
|
24 | 0.98 | 0.98
|
||||||
|
|
||||||
Read the per-animal column as the honest power; the LME column matches the
|
Read per-animal as the honest power; LME matches the paper's power code (optimistic).
|
||||||
paper's power code (anova interaction p, observation-level DF) and is optimistic.
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
==============================================================================
|
||||||
|
LOG-DAY MODEL + COHEN'S f + POWER -- unmerge_d0_13 [metric: success RATE]
|
||||||
|
==============================================================================
|
||||||
|
model: behavior ~ stim + log(day) + stim:log(day) + (1|rat) (behavior = success RATE)
|
||||||
|
log(day) uses 1-indexed training day (our day 0 = paper "Day 1")
|
||||||
|
observed groups: stim n=3, control n=3 nrep=120, alpha=0.05
|
||||||
|
|
||||||
|
--- fitted on real data ---
|
||||||
|
stim x log(day) interaction: F(1,66)=12.243 p(resid)=0.0008416 p(Satt)=0.0008388 (df=66)
|
||||||
|
honest per-animal random slope (log-day): F(1,29.1)=11.46 p=0.00205
|
||||||
|
Cohen's f (interaction, partial eta^2=0.030) = 0.176 (small-medium; f: .10 small, .25 medium, .40 large)
|
||||||
|
|
||||||
|
--- power simulation (log-day ground truth: stim:logday=+0.08873, ratSD=0.04364, resSD=0.07673) ---
|
||||||
|
|
||||||
|
true stim:log(day) = +0.08873 (100% of observed)
|
||||||
|
N/group | per-animal power | LME power
|
||||||
|
------------------------------------------
|
||||||
|
3 | 0.71 | 0.97 <- observed
|
||||||
|
5 | 1.00 | 1.00
|
||||||
|
8 | 1.00 | 1.00
|
||||||
|
12 | 1.00 | 1.00
|
||||||
|
16 | 1.00 | 1.00
|
||||||
|
24 | 1.00 | 1.00
|
||||||
|
|
||||||
|
true stim:log(day) = +0.04437 (50% of observed)
|
||||||
|
N/group | per-animal power | LME power
|
||||||
|
------------------------------------------
|
||||||
|
3 | 0.26 | 0.52 <- observed
|
||||||
|
5 | 0.57 | 0.72
|
||||||
|
8 | 0.85 | 0.90
|
||||||
|
12 | 0.95 | 0.96
|
||||||
|
16 | 1.00 | 1.00
|
||||||
|
24 | 1.00 | 1.00
|
||||||
|
|
||||||
|
Read per-animal as the honest power; LME matches the paper's power code (optimistic).
|
||||||
@@ -1,50 +1,52 @@
|
|||||||
% Variation log-day analysis + Cohen's f + power simulation.
|
% Variation log-day analysis + Cohen's f + power simulation, for BOTH metrics:
|
||||||
|
% metric = count : behavior = # successes -> logpower_result.txt
|
||||||
|
% metric = rate : behavior = success / attempts -> logpower_result_rate.txt
|
||||||
%
|
%
|
||||||
% The paper's power code models behavior against LOG training day, not raw day:
|
% The paper's power code models behavior against LOG training day:
|
||||||
% behavior ~ stim + log(day) + stim:log(day) + (1|rat).
|
% behavior ~ stim + log(day) + stim:log(day) + (1|rat).
|
||||||
% Their day is 1-indexed (1..10); our data.csv day is 0-indexed (day 0 = paper
|
% Their day is 1-indexed; our data.csv day is 0-indexed, so log(day + 1)
|
||||||
% "Day 1"), so log(day + 1) reproduces their transform exactly.
|
% reproduces their transform (our day 0 = paper "Day 1"). For each metric this
|
||||||
%
|
% refits that model, reports the interaction (residual / Satterthwaite / honest
|
||||||
% This script (a) refits that log-day model on data.csv, (b) reports the
|
% per-animal random-slope DF) and Cohen's f (partial-eta^2 effect size), then
|
||||||
% interaction (residual DF, Satterthwaite DF, and the honest per-animal
|
% runs the Monte-Carlo power sim (per-animal cluster-honest + LME power).
|
||||||
% random-slope test) and Cohen's f -- the partial-eta^2 effect size of the
|
% Run: matlab -batch "logpowersim"
|
||||||
% interaction, var(fitted_full) - var(fitted_no_interaction) over var(behavior)
|
|
||||||
% -- and (c) runs the Monte-Carlo power simulation on the log-day model,
|
|
||||||
% scoring per-animal (cluster-honest) and LME power across N.
|
|
||||||
% Writes logpower_result.txt. Run: matlab -batch "logpowersim"
|
|
||||||
% (Copy of analysis/matlab/variation_logpower.m; see make_variation_logpower.m.)
|
% (Copy of analysis/matlab/variation_logpower.m; see make_variation_logpower.m.)
|
||||||
|
|
||||||
here = fileparts(mfilename('fullpath'));
|
here = fileparts(mfilename('fullpath'));
|
||||||
if isempty(here); here = pwd; end
|
if isempty(here); here = pwd; end
|
||||||
vname = regexprep(here, '.*[/\\]', '');
|
vname = regexprep(here, '.*[/\\]', '');
|
||||||
|
|
||||||
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
||||||
FORMULA = 'behavior ~ stim + day + stim:day + (1|rat)'; % 'day' column = log(day+1)
|
|
||||||
NS = [3 5 8 12 16 24];
|
|
||||||
EFFMULS = [1 0.5];
|
|
||||||
NREP = 120;
|
|
||||||
|
|
||||||
warnState = warning('off', 'all');
|
localLogPower(D, 'count', here, vname);
|
||||||
rng(1);
|
localLogPower(D, 'rate', here, vname);
|
||||||
|
|
||||||
logday = log(D.day + 1); % 0-indexed day -> their log(1-indexed day)
|
% ---------------------------------------------------------------- per metric
|
||||||
tbl0 = table(D.success, logday, double(D.stim), categorical(D.subject), ...
|
function localLogPower(D, metric, here, vname)
|
||||||
|
NS = [3 5 8 12 16 24]; EFFMULS = [1 0.5]; NREP = 120;
|
||||||
|
FORMULA = 'behavior ~ stim + day + stim:day + (1|rat)'; % 'day' = log(day+1)
|
||||||
|
if strcmp(metric, 'rate')
|
||||||
|
D = D(D.total > 0, :); beh = D.success ./ D.total; mlabel = 'success RATE'; suffix = '_rate';
|
||||||
|
else
|
||||||
|
beh = D.success; mlabel = '# successes (count)'; suffix = '';
|
||||||
|
end
|
||||||
|
warnState = warning('off', 'all'); rng(1);
|
||||||
|
logday = log(D.day + 1);
|
||||||
|
tbl0 = table(beh, logday, double(D.stim), categorical(D.subject), ...
|
||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
nStim = numel(unique(D.subject(D.stim == 1)));
|
nStim = numel(unique(D.subject(D.stim == 1)));
|
||||||
nCtrl = numel(unique(D.subject(D.stim == 0)));
|
nCtrl = numel(unique(D.subject(D.stim == 0)));
|
||||||
|
|
||||||
bar = repmat('=', 1, 78);
|
bar = repmat('=', 1, 78);
|
||||||
s = sprintf('%s\nLOG-DAY MODEL + COHEN''S f + POWER -- %s\n%s\n', bar, vname, bar);
|
s = sprintf('%s\nLOG-DAY MODEL + COHEN''S f + POWER -- %s [metric: %s]\n%s\n', bar, vname, mlabel, bar);
|
||||||
s = [s sprintf('model: behavior ~ stim + log(day) + stim:log(day) + (1|rat) (success COUNT)\n')];
|
s = [s sprintf('model: behavior ~ stim + log(day) + stim:log(day) + (1|rat) (behavior = %s)\n', mlabel)];
|
||||||
s = [s sprintf('log(day) uses 1-indexed training day (our day 0 = paper "Day 1")\n')];
|
s = [s sprintf('log(day) uses 1-indexed training day (our day 0 = paper "Day 1")\n')];
|
||||||
s = [s sprintf('observed groups: stim n=%d, control n=%d nrep=%d, alpha=0.05\n', nStim, nCtrl, NREP)];
|
s = [s sprintf('observed groups: stim n=%d, control n=%d nrep=%d, alpha=0.05\n', nStim, nCtrl, NREP)];
|
||||||
|
|
||||||
if nStim < 2 || nCtrl < 2 || numel(unique(tbl0.day)) < 2
|
if nStim < 2 || nCtrl < 2 || numel(unique(tbl0.day)) < 2
|
||||||
s = [s sprintf('\nInsufficient data for this analysis (need >=2 animals/group and >=2 days).\n')];
|
s = [s sprintf('\nInsufficient data for this analysis.\n')];
|
||||||
localFinish(s, here); warning(warnState); return
|
localFinish(s, here, suffix); warning(warnState); return
|
||||||
end
|
end
|
||||||
|
|
||||||
% ---- fitted model on the real data ----
|
|
||||||
full = fitlme(tbl0, FORMULA);
|
full = fitlme(tbl0, FORMULA);
|
||||||
An = anova(full); Asatt = anova(full, 'DFMethod', 'satterthwaite');
|
An = anova(full); Asatt = anova(full, 'DFMethod', 'satterthwaite');
|
||||||
ii = strcmp(An.Term, 'day:stim'); is = strcmp(Asatt.Term, 'day:stim');
|
ii = strcmp(An.Term, 'day:stim'); is = strcmp(Asatt.Term, 'day:stim');
|
||||||
@@ -52,7 +54,6 @@ reduced = fitlme(tbl0, 'behavior ~ stim + day + (1|rat)');
|
|||||||
eta2part = max((var(fitted(full)) - var(fitted(reduced))) / var(tbl0.behavior), 0);
|
eta2part = max((var(fitted(full)) - var(fitted(reduced))) / var(tbl0.behavior), 0);
|
||||||
cohenf = sqrt(eta2part / (1 - eta2part));
|
cohenf = sqrt(eta2part / (1 - eta2part));
|
||||||
|
|
||||||
% honest per-animal random-slope interaction
|
|
||||||
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
try
|
try
|
||||||
mr = fitlme(tbl0, 'behavior ~ stim + day + stim:day + (day|rat)');
|
mr = fitlme(tbl0, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
@@ -76,19 +77,18 @@ end
|
|||||||
s = [s sprintf('Cohen''s f (interaction, partial eta^2=%.3f) = %.3f (%s; f: .10 small, .25 medium, .40 large)\n', ...
|
s = [s sprintf('Cohen''s f (interaction, partial eta^2=%.3f) = %.3f (%s; f: .10 small, .25 medium, .40 large)\n', ...
|
||||||
eta2part, cohenf, mag)];
|
eta2part, cohenf, mag)];
|
||||||
|
|
||||||
% ---- power simulation on the log-day ground truth ----
|
|
||||||
cn = full.CoefficientNames; be = full.fixedEffects;
|
cn = full.CoefficientNames; be = full.fixedEffects;
|
||||||
b0 = be(strcmp(cn, '(Intercept)')); bStim = be(strcmp(cn, 'stim'));
|
b0 = be(strcmp(cn, '(Intercept)')); bStim = be(strcmp(cn, 'stim'));
|
||||||
bDay = be(strcmp(cn, 'day')); bInt = be(strcmp(cn, 'day:stim'));
|
bDay = be(strcmp(cn, 'day')); bInt = be(strcmp(cn, 'day:stim'));
|
||||||
psi = covarianceParameters(full); sRat = sqrt(psi{1}); sRes = sqrt(full.MSE);
|
psi = covarianceParameters(full); sRat = sqrt(psi{1}); sRes = sqrt(full.MSE);
|
||||||
days = unique(tbl0.day); % the log(day) grid
|
days = unique(tbl0.day);
|
||||||
|
|
||||||
s = [s sprintf('\n--- power simulation (log-day ground truth: stim:logday=%+.2f, ratSD=%.2f, resSD=%.2f) ---\n', ...
|
s = [s sprintf('\n--- power simulation (log-day ground truth: stim:logday=%+.4g, ratSD=%.4g, resSD=%.4g) ---\n', ...
|
||||||
bInt, sRat, sRes)];
|
bInt, sRat, sRes)];
|
||||||
for eMul = EFFMULS
|
for eMul = EFFMULS
|
||||||
bI = bInt * eMul;
|
bI = bInt * eMul;
|
||||||
s = [s sprintf('\n true stim:log(day) = %+.2f (%.0f%% of observed)\n', bI, eMul * 100)]; %#ok<AGROW>
|
s = [s sprintf('\n true stim:log(day) = %+.4g (%.0f%% of observed)\n', bI, eMul * 100)];
|
||||||
s = [s sprintf(' %-8s | per-animal power | LME power\n %s\n', 'N/group', repmat('-', 1, 42))]; %#ok<AGROW>
|
s = [s sprintf(' %-8s | per-animal power | LME power\n %s\n', 'N/group', repmat('-', 1, 42))];
|
||||||
for N = NS
|
for N = NS
|
||||||
sigPA = 0; sigL = 0;
|
sigPA = 0; sigL = 0;
|
||||||
for r = 1:NREP
|
for r = 1:NREP
|
||||||
@@ -102,19 +102,18 @@ for eMul = EFFMULS
|
|||||||
end
|
end
|
||||||
star = '';
|
star = '';
|
||||||
if N == nStim || N == nCtrl; star = ' <- observed'; end
|
if N == nStim || N == nCtrl; star = ' <- observed'; end
|
||||||
s = [s sprintf(' %-8d | %5.2f | %5.2f%s\n', N, sigPA / NREP, sigL / NREP, star)]; %#ok<AGROW>
|
s = [s sprintf(' %-8d | %5.2f | %5.2f%s\n', N, sigPA / NREP, sigL / NREP, star)];
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
s = [s sprintf(['\nRead the per-animal column as the honest power; the LME column matches the\n' ...
|
s = [s sprintf('\nRead per-animal as the honest power; LME matches the paper''s power code (optimistic).\n')];
|
||||||
'paper''s power code (anova interaction p, observation-level DF) and is optimistic.\n'])];
|
localFinish(s, here, suffix);
|
||||||
|
|
||||||
localFinish(s, here);
|
|
||||||
warning(warnState);
|
warning(warnState);
|
||||||
|
end
|
||||||
|
|
||||||
% ---------------------------------------------------------------- helpers
|
% ---------------------------------------------------------------- helpers
|
||||||
function localFinish(s, here)
|
function localFinish(s, here, suffix)
|
||||||
fprintf('%s', s);
|
fprintf('%s', s);
|
||||||
fid = fopen(fullfile(here, 'logpower_result.txt'), 'w');
|
fid = fopen(fullfile(here, ['logpower_result' suffix '.txt']), 'w');
|
||||||
fprintf(fid, '%s', s); fclose(fid);
|
fprintf(fid, '%s', s); fclose(fid);
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
% Variation learning-curve plot, in the style of the paper:
|
% Variation learning-curve plots, in the style of the paper:
|
||||||
% "Lines indicate mean (and SEM) across animals in the anodal (red) and
|
% "Lines indicate mean (and SEM) across animals in the anodal (red) and
|
||||||
% control (blue) groups."
|
% control (blue) groups."
|
||||||
% Plots mean +/- SEM successful reaches per training day for the treatment /
|
% Produces TWO figures from this folder's data.csv:
|
||||||
% anodal group (stim = 1, red) and the control group (stim = 0, blue), reading
|
% learning_curve.png # successes (count) per training day
|
||||||
% this folder's data.csv and saving learning_curve.png. The per-group N is read
|
% learning_curve_rate.png success rate (success/attempts) per training day
|
||||||
% from the data (each variation pools different groups), so the legend shows the
|
% anodal / treatment = stim 1 (red); control = stim 0 (blue). Per-group N is
|
||||||
% actual counts. Training day is 1-indexed (our day 0 = the paper's "Day 1").
|
% 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"
|
% Run: matlab -batch "plotcurve"
|
||||||
% (Copy of analysis/matlab/variation_plot.m; see make_variation_plot.m.)
|
% (Copy of analysis/matlab/variation_plot.m; see make_variation_plot.m.)
|
||||||
|
|
||||||
@@ -14,15 +15,20 @@ if isempty(here); here = pwd; end
|
|||||||
vname = regexprep(here, '.*[/\\]', '');
|
vname = regexprep(here, '.*[/\\]', '');
|
||||||
|
|
||||||
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
||||||
days = unique(D.day); % 0-indexed
|
days = unique(D.day);
|
||||||
xd = days + 1; % plot as 1-indexed training day (paper axis)
|
xd = days + 1; % plot as 1-indexed training day (paper axis)
|
||||||
|
|
||||||
red = [0.85 0.10 0.10];
|
red = [0.85 0.10 0.10];
|
||||||
blue = [0.10 0.30 0.85];
|
blue = [0.10 0.30 0.85];
|
||||||
|
|
||||||
[Ma, Sa, na] = localCurve(D, 1, days); % anodal / treatment (stim = 1)
|
localPlot(D, days, xd, 'count', '# successes', ...
|
||||||
[Mc, Sc, nc] = localCurve(D, 0, days); % control (stim = 0)
|
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]);
|
fig = figure('Visible', 'off', 'Color', 'w', 'Position', [100 100 560 460]);
|
||||||
hold on
|
hold on
|
||||||
e1 = errorbar(xd, Ma, Sa, '-o', 'Color', red, 'MarkerFaceColor', red, 'LineWidth', 2);
|
e1 = errorbar(xd, Ma, Sa, '-o', 'Color', red, 'MarkerFaceColor', red, 'LineWidth', 2);
|
||||||
@@ -30,26 +36,30 @@ e2 = errorbar(xd, Mc, Sc, '-o', 'Color', blue, 'MarkerFaceColor', blue, 'LineWid
|
|||||||
hold off
|
hold off
|
||||||
legend([e1 e2], {sprintf('anodal, N = %d', na), sprintf('control, N = %d', nc)}, ...
|
legend([e1 e2], {sprintf('anodal, N = %d', na), sprintf('control, N = %d', nc)}, ...
|
||||||
'Location', 'northwest', 'Box', 'off');
|
'Location', 'northwest', 'Box', 'off');
|
||||||
xlabel('training day');
|
xlabel('training day'); ylabel(ylab);
|
||||||
ylabel('# successes');
|
|
||||||
title(vname, 'Interpreter', 'none');
|
title(vname, 'Interpreter', 'none');
|
||||||
set(gca, 'XTick', xd, 'FontName', 'Arial', 'FontSize', 13, 'LineWidth', 1.5, 'Box', 'off');
|
set(gca, 'XTick', xd, 'FontName', 'Arial', 'FontSize', 13, 'LineWidth', 1.5, 'Box', 'off');
|
||||||
|
xtickangle(90); % vertical x-axis tick labels
|
||||||
outFile = fullfile(here, 'learning_curve.png');
|
|
||||||
exportgraphics(fig, outFile, 'Resolution', 150);
|
exportgraphics(fig, outFile, 'Resolution', 150);
|
||||||
close(fig);
|
close(fig);
|
||||||
fprintf('%s: wrote learning_curve.png (anodal N=%d, control N=%d)\n', vname, na, nc);
|
fprintf('%s: wrote %s (anodal N=%d, control N=%d)\n', vname, outFile, na, nc);
|
||||||
|
end
|
||||||
|
|
||||||
% ------------------------------------------------------------------ helper
|
function [M, S, n] = localCurve(D, stimVal, days, metric)
|
||||||
function [M, S, n] = localCurve(D, stimVal, days)
|
%LOCALCURVE Per-day mean and SEM across the animals in a group, for a metric.
|
||||||
%LOCALCURVE Per-day mean and SEM of successes across the animals in a group.
|
|
||||||
subs = unique(D.subject(D.stim == stimVal));
|
subs = unique(D.subject(D.stim == stimVal));
|
||||||
n = numel(subs);
|
n = numel(subs);
|
||||||
X = nan(numel(days), n);
|
X = nan(numel(days), n);
|
||||||
for j = 1:n
|
for j = 1:n
|
||||||
for i = 1:numel(days)
|
for i = 1:numel(days)
|
||||||
r = D.subject == subs(j) & D.day == days(i);
|
r = D.subject == subs(j) & D.day == days(i);
|
||||||
if any(r); X(i, j) = mean(D.success(r)); end
|
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
|
||||||
end
|
end
|
||||||
M = mean(X, 2, 'omitnan');
|
M = mean(X, 2, 'omitnan');
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
POWER SIMULATION -- unmerge_d0_13
|
POWER SIMULATION -- unmerge_d0_13 [metric: # successes (count)]
|
||||||
==============================================================================
|
==============================================================================
|
||||||
model: behavior ~ stim + day + stim:day + (1|rat) (success COUNT; day within-window)
|
model: behavior ~ stim + day + stim:day + (1|rat) (behavior = # successes (count); day within-window)
|
||||||
observed groups: stim n=3, control n=3 nrep=120, alpha=0.05
|
observed groups: stim n=3, control n=3 nrep=120, alpha=0.05
|
||||||
ground truth: stim:day=+1.55/day, rat SD=0.00, residual SD=14.68, days=14
|
ground truth: stim:day=+1.547/day, rat SD=0, residual SD=14.68, days=14
|
||||||
|
|
||||||
true stim:day interaction = +1.55 (100% of observed)
|
true stim:day interaction = +1.547 (100% of observed)
|
||||||
N/group | per-animal power | LME power
|
N/group | per-animal power | LME power
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
3 | 0.22 | 0.56 <- observed
|
3 | 0.22 | 0.56 <- observed
|
||||||
@@ -15,7 +15,7 @@ ground truth: stim:day=+1.55/day, rat SD=0.00, residual SD=14.68, days=14
|
|||||||
16 | 0.98 | 0.98
|
16 | 0.98 | 0.98
|
||||||
24 | 1.00 | 1.00
|
24 | 1.00 | 1.00
|
||||||
|
|
||||||
true stim:day interaction = +0.77 (50% of observed)
|
true stim:day interaction = +0.7733 (50% of observed)
|
||||||
N/group | per-animal power | LME power
|
N/group | per-animal power | LME power
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
3 | 0.12 | 0.22 <- observed
|
3 | 0.12 | 0.22 <- observed
|
||||||
@@ -25,5 +25,4 @@ ground truth: stim:day=+1.55/day, rat SD=0.00, residual SD=14.68, days=14
|
|||||||
16 | 0.64 | 0.71
|
16 | 0.64 | 0.71
|
||||||
24 | 0.78 | 0.74
|
24 | 0.78 | 0.74
|
||||||
|
|
||||||
Read the per-animal column as the honest power. At the observed N this study
|
Read the per-animal column as the honest power; LME is optimistic (obs-level DF).
|
||||||
is typically underpowered; per-animal power reaches ~0.8 only at larger N.
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
==============================================================================
|
||||||
|
POWER SIMULATION -- unmerge_d0_13 [metric: success RATE]
|
||||||
|
==============================================================================
|
||||||
|
model: behavior ~ stim + day + stim:day + (1|rat) (behavior = success RATE; day within-window)
|
||||||
|
observed groups: stim n=3, control n=3 nrep=120, alpha=0.05
|
||||||
|
ground truth: stim:day=+0.01125/day, rat SD=0.03898, residual SD=0.0934, days=14
|
||||||
|
|
||||||
|
true stim:day interaction = +0.01125 (100% of observed)
|
||||||
|
N/group | per-animal power | LME power
|
||||||
|
------------------------------------------
|
||||||
|
3 | 0.28 | 0.66 <- observed
|
||||||
|
5 | 0.67 | 0.78
|
||||||
|
8 | 0.92 | 0.94
|
||||||
|
12 | 1.00 | 1.00
|
||||||
|
16 | 1.00 | 1.00
|
||||||
|
24 | 1.00 | 1.00
|
||||||
|
|
||||||
|
true stim:day interaction = +0.005624 (50% of observed)
|
||||||
|
N/group | per-animal power | LME power
|
||||||
|
------------------------------------------
|
||||||
|
3 | 0.15 | 0.25 <- observed
|
||||||
|
5 | 0.23 | 0.25
|
||||||
|
8 | 0.48 | 0.52
|
||||||
|
12 | 0.56 | 0.60
|
||||||
|
16 | 0.75 | 0.79
|
||||||
|
24 | 0.87 | 0.83
|
||||||
|
|
||||||
|
Read the per-animal column as the honest power; LME is optimistic (obs-level DF).
|
||||||
@@ -1,45 +1,49 @@
|
|||||||
% Variation power simulation -- Monte-Carlo power for the paper's stim x day
|
% Variation power simulation -- Monte-Carlo power for the paper's stim x day
|
||||||
% interaction, using THIS folder's data as the ground truth.
|
% interaction, using THIS folder's data as the ground truth, for BOTH metrics:
|
||||||
|
% metric = count : behavior = # successes -> power_result.txt
|
||||||
|
% metric = rate : behavior = success / attempts -> power_result_rate.txt
|
||||||
%
|
%
|
||||||
% Ground truth: fitlme(behavior ~ stim + day + stim:day + (1|rat)) on data.csv
|
% Ground truth: fitlme(behavior ~ stim + day + stim:day + (1|rat)) on data.csv
|
||||||
% (success COUNT; day within-window). Its fixed effects, per-rat intercept SD,
|
% (day within-window). Its fixed effects, per-rat intercept SD, and residual SD
|
||||||
% and residual SD generate NREP synthetic datasets at each rats-per-group N and
|
% generate NREP synthetic datasets at each rats-per-group N and each true-effect
|
||||||
% each true-effect multiplier (1 = observed slope, 0.5 = half). Each dataset is
|
% multiplier (1 = observed, 0.5 = half). Each is scored at alpha=0.05 by:
|
||||||
% scored at alpha = 0.05 two ways:
|
% per-animal : Welch t on per-rat behavior~day slopes (cluster-honest power)
|
||||||
% per-animal : Welch t on per-rat behavior~day slopes (cluster-honest -- the
|
|
||||||
% honest power, matching the random-slope / per-animal inference)
|
|
||||||
% LME : the fitlme stim:day p (observation-level DF -- optimistic)
|
% LME : the fitlme stim:day p (observation-level DF -- optimistic)
|
||||||
% Writes power_result.txt beside this script. Run: matlab -batch "powersim"
|
% Writes power_result[_rate].txt. Run: matlab -batch "powersim"
|
||||||
% (Copy of analysis/matlab/variation_power.m; see make_variation_power.m.)
|
% (Copy of analysis/matlab/variation_power.m; see make_variation_power.m.)
|
||||||
|
|
||||||
here = fileparts(mfilename('fullpath'));
|
here = fileparts(mfilename('fullpath'));
|
||||||
if isempty(here); here = pwd; end
|
if isempty(here); here = pwd; end
|
||||||
vname = regexprep(here, '.*[/\\]', '');
|
vname = regexprep(here, '.*[/\\]', '');
|
||||||
|
|
||||||
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
||||||
|
|
||||||
|
localPower(D, 'count', here, vname);
|
||||||
|
localPower(D, 'rate', here, vname);
|
||||||
|
|
||||||
|
% ---------------------------------------------------------------- per metric
|
||||||
|
function localPower(D, metric, here, vname)
|
||||||
|
NS = [3 5 8 12 16 24]; EFFMULS = [1 0.5]; NREP = 120;
|
||||||
FORMULA = 'behavior ~ stim + day + stim:day + (1|rat)';
|
FORMULA = 'behavior ~ stim + day + stim:day + (1|rat)';
|
||||||
NS = [3 5 8 12 16 24];
|
if strcmp(metric, 'rate')
|
||||||
EFFMULS = [1 0.5];
|
D = D(D.total > 0, :); beh = D.success ./ D.total; mlabel = 'success RATE'; suffix = '_rate';
|
||||||
NREP = 120;
|
else
|
||||||
|
beh = D.success; mlabel = '# successes (count)'; suffix = '';
|
||||||
warnState = warning('off', 'all');
|
end
|
||||||
rng(1);
|
warnState = warning('off', 'all'); rng(1);
|
||||||
|
|
||||||
day0 = min(D.day);
|
day0 = min(D.day);
|
||||||
tbl0 = table(D.success, D.day - day0, double(D.stim), categorical(D.subject), ...
|
tbl0 = table(beh, D.day - day0, double(D.stim), categorical(D.subject), ...
|
||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
|
|
||||||
nStim = numel(unique(D.subject(D.stim == 1)));
|
nStim = numel(unique(D.subject(D.stim == 1)));
|
||||||
nCtrl = numel(unique(D.subject(D.stim == 0)));
|
nCtrl = numel(unique(D.subject(D.stim == 0)));
|
||||||
|
|
||||||
bar = repmat('=', 1, 78);
|
bar = repmat('=', 1, 78);
|
||||||
s = sprintf('%s\nPOWER SIMULATION -- %s\n%s\n', bar, vname, bar);
|
s = sprintf('%s\nPOWER SIMULATION -- %s [metric: %s]\n%s\n', bar, vname, mlabel, bar);
|
||||||
s = [s sprintf('model: %s (success COUNT; day within-window)\n', FORMULA)];
|
s = [s sprintf('model: %s (behavior = %s; day within-window)\n', FORMULA, mlabel)];
|
||||||
s = [s sprintf('observed groups: stim n=%d, control n=%d nrep=%d, alpha=0.05\n', nStim, nCtrl, NREP)];
|
s = [s sprintf('observed groups: stim n=%d, control n=%d nrep=%d, alpha=0.05\n', nStim, nCtrl, NREP)];
|
||||||
|
|
||||||
if nStim < 2 || nCtrl < 2 || numel(unique(tbl0.day)) < 2
|
if nStim < 2 || nCtrl < 2 || numel(unique(tbl0.day)) < 2
|
||||||
s = [s sprintf('\nInsufficient data for a power simulation (need >=2 animals/group and >=2 days).\n')];
|
s = [s sprintf('\nInsufficient data for a power simulation.\n')];
|
||||||
localFinish(s, here); warning(warnState); return
|
localFinish(s, here, suffix); warning(warnState); return
|
||||||
end
|
end
|
||||||
|
|
||||||
lme = fitlme(tbl0, FORMULA);
|
lme = fitlme(tbl0, FORMULA);
|
||||||
@@ -49,14 +53,13 @@ bDay = be(strcmp(cn, 'day')); bInt = be(strcmp(cn, 'day:stim'));
|
|||||||
psi = covarianceParameters(lme); sRat = sqrt(psi{1}); sRes = sqrt(lme.MSE);
|
psi = covarianceParameters(lme); sRat = sqrt(psi{1}); sRes = sqrt(lme.MSE);
|
||||||
days = (0:max(tbl0.day))';
|
days = (0:max(tbl0.day))';
|
||||||
|
|
||||||
s = [s sprintf('ground truth: stim:day=%+.2f/day, rat SD=%.2f, residual SD=%.2f, days=%d\n', ...
|
s = [s sprintf('ground truth: stim:day=%+.4g/day, rat SD=%.4g, residual SD=%.4g, days=%d\n', ...
|
||||||
bInt, sRat, sRes, numel(days))];
|
bInt, sRat, sRes, numel(days))];
|
||||||
|
|
||||||
for eMul = EFFMULS
|
for eMul = EFFMULS
|
||||||
bI = bInt * eMul;
|
bI = bInt * eMul;
|
||||||
s = [s sprintf('\n true stim:day interaction = %+.2f (%.0f%% of observed)\n', bI, eMul * 100)]; %#ok<AGROW>
|
s = [s sprintf('\n true stim:day interaction = %+.4g (%.0f%% of observed)\n', bI, eMul * 100)];
|
||||||
s = [s sprintf(' %-8s | per-animal power | LME power\n', 'N/group')]; %#ok<AGROW>
|
s = [s sprintf(' %-8s | per-animal power | LME power\n %s\n', 'N/group', repmat('-', 1, 42))];
|
||||||
s = [s sprintf(' %s\n', repmat('-', 1, 42))]; %#ok<AGROW>
|
|
||||||
for N = NS
|
for N = NS
|
||||||
sigPA = 0; sigL = 0;
|
sigPA = 0; sigL = 0;
|
||||||
for r = 1:NREP
|
for r = 1:NREP
|
||||||
@@ -70,20 +73,18 @@ for eMul = EFFMULS
|
|||||||
end
|
end
|
||||||
star = '';
|
star = '';
|
||||||
if N == nStim || N == nCtrl; star = ' <- observed'; end
|
if N == nStim || N == nCtrl; star = ' <- observed'; end
|
||||||
s = [s sprintf(' %-8d | %5.2f | %5.2f%s\n', N, sigPA / NREP, sigL / NREP, star)]; %#ok<AGROW>
|
s = [s sprintf(' %-8d | %5.2f | %5.2f%s\n', N, sigPA / NREP, sigL / NREP, star)];
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
s = [s sprintf('\nRead the per-animal column as the honest power; LME is optimistic (obs-level DF).\n')];
|
||||||
s = [s sprintf(['\nRead the per-animal column as the honest power. At the observed N this study\n' ...
|
localFinish(s, here, suffix);
|
||||||
'is typically underpowered; per-animal power reaches ~0.8 only at larger N.\n'])];
|
|
||||||
|
|
||||||
localFinish(s, here);
|
|
||||||
warning(warnState);
|
warning(warnState);
|
||||||
|
end
|
||||||
|
|
||||||
% ---------------------------------------------------------------- helpers
|
% ---------------------------------------------------------------- helpers
|
||||||
function localFinish(s, here)
|
function localFinish(s, here, suffix)
|
||||||
fprintf('%s', s);
|
fprintf('%s', s);
|
||||||
fid = fopen(fullfile(here, 'power_result.txt'), 'w');
|
fid = fopen(fullfile(here, ['power_result' suffix '.txt']), 'w');
|
||||||
fprintf(fid, '%s', s); fclose(fid);
|
fprintf(fid, '%s', s); fclose(fid);
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
VARIATION: unmerge_d0_13
|
VARIATION: unmerge_d0_13 [metric: success COUNT]
|
||||||
==============================================================================
|
==============================================================================
|
||||||
model: behavior ~ stim + day + stim:day + (1|rat) (behavior = success COUNT)
|
model: behavior ~ stim + day + stim:day + (1|rat) (behavior = success COUNT)
|
||||||
day = training day within window (0 = first analyzed day)
|
day = training day within window (0 = first analyzed day)
|
||||||
@@ -60,9 +60,9 @@ effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
|||||||
stim x day (interaction) t(66)= 1.65 F(1)= 2.721 p=0.1038 p=0.1035 (df=70)
|
stim x day (interaction) t(66)= 1.65 F(1)= 2.721 p=0.1038 p=0.1035 (df=70)
|
||||||
day (learning) t(66)= 9.09 F(1)= 82.593 p=3.035e-13 p=1.823e-13 (df=70)
|
day (learning) t(66)= 9.09 F(1)= 82.593 p=3.035e-13 p=1.823e-13 (df=70)
|
||||||
stim (main, window start) t(66)= 0.95 F(1)= 0.910 p=0.3436 p=0.3434 (df=70)
|
stim (main, window start) t(66)= 0.95 F(1)= 0.910 p=0.3436 p=0.3434 (df=70)
|
||||||
interaction 95% CI: [-0.33, +3.42]
|
interaction 95% CI: [-0.3252, +3.419]
|
||||||
HONEST LME (per-animal random slope, day|rat): interaction F(1,12.8)=1.64, p=0.2227
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,12.8)=1.64, p=0.2227
|
||||||
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
(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.)
|
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.1038, slope diff=+1.55)
|
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.1038, slope diff=+1.547)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24, count): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
==============================================================================
|
||||||
|
VARIATION: unmerge_d0_13 [metric: success RATE (success/attempts)]
|
||||||
|
==============================================================================
|
||||||
|
model: behavior ~ stim + day + stim:day + (1|rat) (behavior = success RATE (success/attempts))
|
||||||
|
day = training day within window (0 = first analyzed day)
|
||||||
|
treatment (stim=1): Electrode-Box-B2
|
||||||
|
control (stim=0): Electrode-Box-A2
|
||||||
|
N = 6 rats, 70 sessions raw day coverage: treat 0..13, control 0..13
|
||||||
|
(equal day coverage over this window)
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
FULL MODEL SUMMARY -- fitlme
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
Linear mixed-effects model fit by ML
|
||||||
|
|
||||||
|
Model information:
|
||||||
|
Number of observations 70
|
||||||
|
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
|
||||||
|
-114.71 -101.22 63.353 -126.71
|
||||||
|
|
||||||
|
Fixed effects coefficients (95% CIs):
|
||||||
|
Name Estimate SE tStat DF pValue
|
||||||
|
{'(Intercept)'} 0.25633 0.036798 6.9659 66 1.8623e-09
|
||||||
|
{'day' } 0.032331 0.0047766 6.7686 66 4.1691e-09
|
||||||
|
{'stim' } 0.03233 0.051391 0.6291 66 0.53146
|
||||||
|
{'day:stim' } 0.011248 0.0061725 1.8223 66 0.07294
|
||||||
|
|
||||||
|
|
||||||
|
Lower Upper
|
||||||
|
0.18286 0.3298
|
||||||
|
0.022794 0.041868
|
||||||
|
-0.070275 0.13493
|
||||||
|
-0.0010756 0.023572
|
||||||
|
|
||||||
|
Random effects covariance parameters (95% CIs):
|
||||||
|
Group: rat (6 Levels)
|
||||||
|
Name1 Name2 Type Estimate
|
||||||
|
{'(Intercept)'} {'(Intercept)'} {'std'} 0.038979
|
||||||
|
|
||||||
|
|
||||||
|
Lower Upper
|
||||||
|
0.016492 0.092125
|
||||||
|
|
||||||
|
Group: Error
|
||||||
|
Name Estimate Lower Upper
|
||||||
|
{'Res Std'} 0.093402 0.07855 0.11106
|
||||||
|
|
||||||
|
|
||||||
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
stim x day (interaction) t(66)= 1.82 F(1)= 3.321 p=0.07294 p=0.07278 (df=68)
|
||||||
|
day (learning) t(66)= 6.77 F(1)= 45.814 p=4.169e-09 p=3.37e-09 (df=70)
|
||||||
|
stim (main, window start) t(66)= 0.63 F(1)= 0.396 p=0.5315 p=0.5377 (df=17)
|
||||||
|
interaction 95% CI: [-0.001076, +0.02357]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,41.8)=3.20, p=0.08108
|
||||||
|
(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.07294, slope diff=+0.01125)
|
||||||
|
Paper (N=24, count): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
@@ -1,30 +1,58 @@
|
|||||||
% Variation analysis -- the paper's linear mixed model on the successful-reach
|
% Variation analysis -- the paper's linear mixed model on this folder's data,
|
||||||
% COUNT, fit on this folder's curated data subset.
|
% for BOTH metrics:
|
||||||
|
% metric = count : behavior = # successes -> result.txt
|
||||||
|
% metric = rate : behavior = success / attempts -> result_rate.txt
|
||||||
|
% (rate uses only sessions with attempts > 0)
|
||||||
%
|
%
|
||||||
% model: behavior ~ stim + day + stim:day + (1|rat)
|
% model: behavior ~ stim + day + stim:day + (1|rat)
|
||||||
% behavior = successful reaches (count per session)
|
% stim = 1 treatment / 0 control; day = training day within window (0 =
|
||||||
% stim = 1 for the treatment group(s), 0 for the control group(s)
|
% first analyzed day); rat = subject (random intercept).
|
||||||
% day = training day within this window (0 = first analyzed day)
|
% For the interaction we report residual DF, Satterthwaite DF, and the honest
|
||||||
% rat = subject (random intercept)
|
% per-animal random-slope test. Self-contained: reads data.csv beside this
|
||||||
%
|
% script. Run headless with: matlab -batch "analyze"
|
||||||
% Self-contained: reads data.csv beside this script and writes result.txt.
|
% (Copy of analysis/matlab/variation_analyze.m; see make_variations.m.)
|
||||||
% 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'));
|
here = fileparts(mfilename('fullpath'));
|
||||||
if isempty(here); here = pwd; end
|
if isempty(here); here = pwd; end
|
||||||
vname = regexprep(here, '.*[/\\]', ''); % folder name = variation id
|
vname = regexprep(here, '.*[/\\]', '');
|
||||||
|
|
||||||
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
||||||
|
|
||||||
tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject), ...
|
Rc = localAnalyze(D, 'count', here, vname);
|
||||||
|
Rr = localAnalyze(D, 'rate', here, vname);
|
||||||
|
|
||||||
|
% Machine-readable handoff for SUMMARY.csv (count drives it; rate appended).
|
||||||
|
VARRESULT = struct('name', vname, 'nRats', Rc.nRats, 'nObs', Rc.nObs, ...
|
||||||
|
'interP', Rc.interP, 'interEst', Rc.interEst, ...
|
||||||
|
'interPsatt', Rc.interPsatt, 'interPrs', Rc.interPrs, ...
|
||||||
|
'stimP', Rc.stimP, 'dayP', Rc.dayP, 'covEqual', Rc.covEqual, ...
|
||||||
|
'interPrate', Rr.interP, 'interEstRate', Rr.interEst, 'interPrsRate', Rr.interPrs);
|
||||||
|
|
||||||
|
% ------------------------------------------------------------------ helper
|
||||||
|
function R = localAnalyze(D, metric, here, vname)
|
||||||
|
if strcmp(metric, 'rate')
|
||||||
|
D = D(D.total > 0, :);
|
||||||
|
beh = D.success ./ D.total;
|
||||||
|
mlabel = 'success RATE (success/attempts)'; suffix = '_rate';
|
||||||
|
else
|
||||||
|
beh = D.success;
|
||||||
|
mlabel = 'success COUNT'; suffix = '';
|
||||||
|
end
|
||||||
|
R = struct('interP', NaN, 'interEst', NaN, 'interPsatt', NaN, 'interPrs', NaN, ...
|
||||||
|
'stimP', NaN, 'dayP', NaN, 'nRats', numel(unique(D.subject)), ...
|
||||||
|
'nObs', height(D), 'covEqual', false);
|
||||||
|
if numel(unique(D.stim)) < 2 || numel(unique(D.day)) < 2
|
||||||
|
localWrite(sprintf('VARIATION: %s [metric: %s]\nInsufficient data for this metric.\n', ...
|
||||||
|
vname, mlabel), here, suffix);
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
tbl = table(beh, D.day - min(D.day), double(D.stim), categorical(D.subject), ...
|
||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
As = anova(m, 'DFMethod', 'satterthwaite');
|
||||||
|
|
||||||
% 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;
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
wst = warning('off', 'all');
|
wst = warning('off', 'all');
|
||||||
try
|
try
|
||||||
@@ -43,6 +71,7 @@ row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f
|
|||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
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)));
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
|
ii = gi('day:stim'); pI = C.pValue(ii); eI = C.Estimate(ii);
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
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));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
if abs(maxT - maxC) > 2
|
if abs(maxT - maxC) > 2
|
||||||
@@ -50,20 +79,14 @@ if abs(maxT - maxC) > 2
|
|||||||
else
|
else
|
||||||
cov = '(equal day coverage over this window)';
|
cov = '(equal day coverage over this window)';
|
||||||
end
|
end
|
||||||
|
if pI >= 0.05; verdict = 'n.s. -- slopes parallel (no differential learning rate)';
|
||||||
ii = gi('day:stim'); pI = C.pValue(ii); eI = C.Estimate(ii);
|
elseif eI > 0; verdict = 'SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates)';
|
||||||
if pI >= 0.05
|
else; verdict = 'SIGNIFICANT negative -- treatment improves SLOWER (groups converge)'; end
|
||||||
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);
|
bar = repmat('=', 1, 78);
|
||||||
raw = regexprep(evalc('disp(m)'), '</?strong>', '');
|
raw = regexprep(evalc('disp(m)'), '</?strong>', '');
|
||||||
s = sprintf('%s\nVARIATION: %s\n%s\n', bar, vname, bar);
|
s = sprintf('%s\nVARIATION: %s [metric: %s]\n%s\n', bar, vname, mlabel, bar);
|
||||||
s = [s sprintf('model: behavior ~ stim + day + stim:day + (1|rat) (behavior = success COUNT)\n')];
|
s = [s sprintf('model: behavior ~ stim + day + stim:day + (1|rat) (behavior = %s)\n', mlabel)];
|
||||||
s = [s sprintf('day = training day within window (0 = first analyzed day)\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('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('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim == 0))), ', '))];
|
||||||
@@ -74,7 +97,7 @@ s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (res
|
|||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.4g, %+.4g]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
if rsOk
|
if rsOk
|
||||||
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
else
|
else
|
||||||
@@ -82,17 +105,18 @@ else
|
|||||||
end
|
end
|
||||||
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
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'])];
|
' 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('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.4g)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24, count): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
|
localWrite(s, here, suffix);
|
||||||
|
|
||||||
|
R = struct('interP', pI, 'interEst', eI, 'interPsatt', As.pValue(gs('day:stim')), ...
|
||||||
|
'interPrs', rsP, 'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
|
'nRats', numel(unique(D.subject)), 'nObs', height(D), 'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
end
|
||||||
|
|
||||||
|
function localWrite(s, here, suffix)
|
||||||
fprintf('%s', s);
|
fprintf('%s', s);
|
||||||
fid = fopen(fullfile(here, 'result.txt'), 'w');
|
fid = fopen(fullfile(here, ['result' suffix '.txt']), 'w');
|
||||||
fprintf(fid, '%s', s);
|
fprintf(fid, '%s', s); fclose(fid);
|
||||||
fclose(fid);
|
end
|
||||||
|
|
||||||
% 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);
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 37 KiB |
@@ -1,7 +1,7 @@
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
LOG-DAY MODEL + COHEN'S f + POWER -- unmerge_d0_5
|
LOG-DAY MODEL + COHEN'S f + POWER -- unmerge_d0_5 [metric: # successes (count)]
|
||||||
==============================================================================
|
==============================================================================
|
||||||
model: behavior ~ stim + log(day) + stim:log(day) + (1|rat) (success COUNT)
|
model: behavior ~ stim + log(day) + stim:log(day) + (1|rat) (behavior = # successes (count))
|
||||||
log(day) uses 1-indexed training day (our day 0 = paper "Day 1")
|
log(day) uses 1-indexed training day (our day 0 = paper "Day 1")
|
||||||
observed groups: stim n=3, control n=3 nrep=120, alpha=0.05
|
observed groups: stim n=3, control n=3 nrep=120, alpha=0.05
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ stim x log(day) interaction: F(1,32)=4.849 p(resid)=0.03498 p(Satt)=0.03414 (d
|
|||||||
honest per-animal random slope (log-day): F(1,7.2)=3.65 p=0.09636
|
honest per-animal random slope (log-day): F(1,7.2)=3.65 p=0.09636
|
||||||
Cohen's f (interaction, partial eta^2=0.039) = 0.200 (small-medium; f: .10 small, .25 medium, .40 large)
|
Cohen's f (interaction, partial eta^2=0.039) = 0.200 (small-medium; f: .10 small, .25 medium, .40 large)
|
||||||
|
|
||||||
--- power simulation (log-day ground truth: stim:logday=+16.48, ratSD=0.00, resSD=13.58) ---
|
--- power simulation (log-day ground truth: stim:logday=+16.48, ratSD=0, resSD=13.58) ---
|
||||||
|
|
||||||
true stim:log(day) = +16.48 (100% of observed)
|
true stim:log(day) = +16.48 (100% of observed)
|
||||||
N/group | per-animal power | LME power
|
N/group | per-animal power | LME power
|
||||||
@@ -22,7 +22,7 @@ Cohen's f (interaction, partial eta^2=0.039) = 0.200 (small-medium; f: .10 smal
|
|||||||
16 | 1.00 | 1.00
|
16 | 1.00 | 1.00
|
||||||
24 | 1.00 | 1.00
|
24 | 1.00 | 1.00
|
||||||
|
|
||||||
true stim:log(day) = +8.24 (50% of observed)
|
true stim:log(day) = +8.239 (50% of observed)
|
||||||
N/group | per-animal power | LME power
|
N/group | per-animal power | LME power
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
3 | 0.09 | 0.24 <- observed
|
3 | 0.09 | 0.24 <- observed
|
||||||
@@ -32,5 +32,4 @@ Cohen's f (interaction, partial eta^2=0.039) = 0.200 (small-medium; f: .10 smal
|
|||||||
16 | 0.69 | 0.69
|
16 | 0.69 | 0.69
|
||||||
24 | 0.90 | 0.92
|
24 | 0.90 | 0.92
|
||||||
|
|
||||||
Read the per-animal column as the honest power; the LME column matches the
|
Read per-animal as the honest power; LME matches the paper's power code (optimistic).
|
||||||
paper's power code (anova interaction p, observation-level DF) and is optimistic.
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
==============================================================================
|
||||||
|
LOG-DAY MODEL + COHEN'S f + POWER -- unmerge_d0_5 [metric: success RATE]
|
||||||
|
==============================================================================
|
||||||
|
model: behavior ~ stim + log(day) + stim:log(day) + (1|rat) (behavior = success RATE)
|
||||||
|
log(day) uses 1-indexed training day (our day 0 = paper "Day 1")
|
||||||
|
observed groups: stim n=3, control n=3 nrep=120, alpha=0.05
|
||||||
|
|
||||||
|
--- fitted on real data ---
|
||||||
|
stim x log(day) interaction: F(1,32)=10.951 p(resid)=0.002322 p(Satt)=0.00244 (df=30)
|
||||||
|
honest per-animal random slope (log-day): F(1,8.5)=8.07 p=0.02043
|
||||||
|
Cohen's f (interaction, partial eta^2=0.109) = 0.350 (medium; f: .10 small, .25 medium, .40 large)
|
||||||
|
|
||||||
|
--- power simulation (log-day ground truth: stim:logday=+0.1553, ratSD=0.04445, resSD=0.08516) ---
|
||||||
|
|
||||||
|
true stim:log(day) = +0.1553 (100% of observed)
|
||||||
|
N/group | per-animal power | LME power
|
||||||
|
------------------------------------------
|
||||||
|
3 | 0.54 | 0.90 <- observed
|
||||||
|
5 | 0.93 | 0.97
|
||||||
|
8 | 1.00 | 1.00
|
||||||
|
12 | 1.00 | 1.00
|
||||||
|
16 | 1.00 | 1.00
|
||||||
|
24 | 1.00 | 1.00
|
||||||
|
|
||||||
|
true stim:log(day) = +0.07765 (50% of observed)
|
||||||
|
N/group | per-animal power | LME power
|
||||||
|
------------------------------------------
|
||||||
|
3 | 0.13 | 0.38 <- observed
|
||||||
|
5 | 0.44 | 0.54
|
||||||
|
8 | 0.72 | 0.73
|
||||||
|
12 | 0.81 | 0.88
|
||||||
|
16 | 0.94 | 0.97
|
||||||
|
24 | 1.00 | 1.00
|
||||||
|
|
||||||
|
Read per-animal as the honest power; LME matches the paper's power code (optimistic).
|
||||||
@@ -1,50 +1,52 @@
|
|||||||
% Variation log-day analysis + Cohen's f + power simulation.
|
% Variation log-day analysis + Cohen's f + power simulation, for BOTH metrics:
|
||||||
|
% metric = count : behavior = # successes -> logpower_result.txt
|
||||||
|
% metric = rate : behavior = success / attempts -> logpower_result_rate.txt
|
||||||
%
|
%
|
||||||
% The paper's power code models behavior against LOG training day, not raw day:
|
% The paper's power code models behavior against LOG training day:
|
||||||
% behavior ~ stim + log(day) + stim:log(day) + (1|rat).
|
% behavior ~ stim + log(day) + stim:log(day) + (1|rat).
|
||||||
% Their day is 1-indexed (1..10); our data.csv day is 0-indexed (day 0 = paper
|
% Their day is 1-indexed; our data.csv day is 0-indexed, so log(day + 1)
|
||||||
% "Day 1"), so log(day + 1) reproduces their transform exactly.
|
% reproduces their transform (our day 0 = paper "Day 1"). For each metric this
|
||||||
%
|
% refits that model, reports the interaction (residual / Satterthwaite / honest
|
||||||
% This script (a) refits that log-day model on data.csv, (b) reports the
|
% per-animal random-slope DF) and Cohen's f (partial-eta^2 effect size), then
|
||||||
% interaction (residual DF, Satterthwaite DF, and the honest per-animal
|
% runs the Monte-Carlo power sim (per-animal cluster-honest + LME power).
|
||||||
% random-slope test) and Cohen's f -- the partial-eta^2 effect size of the
|
% Run: matlab -batch "logpowersim"
|
||||||
% interaction, var(fitted_full) - var(fitted_no_interaction) over var(behavior)
|
|
||||||
% -- and (c) runs the Monte-Carlo power simulation on the log-day model,
|
|
||||||
% scoring per-animal (cluster-honest) and LME power across N.
|
|
||||||
% Writes logpower_result.txt. Run: matlab -batch "logpowersim"
|
|
||||||
% (Copy of analysis/matlab/variation_logpower.m; see make_variation_logpower.m.)
|
% (Copy of analysis/matlab/variation_logpower.m; see make_variation_logpower.m.)
|
||||||
|
|
||||||
here = fileparts(mfilename('fullpath'));
|
here = fileparts(mfilename('fullpath'));
|
||||||
if isempty(here); here = pwd; end
|
if isempty(here); here = pwd; end
|
||||||
vname = regexprep(here, '.*[/\\]', '');
|
vname = regexprep(here, '.*[/\\]', '');
|
||||||
|
|
||||||
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
||||||
FORMULA = 'behavior ~ stim + day + stim:day + (1|rat)'; % 'day' column = log(day+1)
|
|
||||||
NS = [3 5 8 12 16 24];
|
|
||||||
EFFMULS = [1 0.5];
|
|
||||||
NREP = 120;
|
|
||||||
|
|
||||||
warnState = warning('off', 'all');
|
localLogPower(D, 'count', here, vname);
|
||||||
rng(1);
|
localLogPower(D, 'rate', here, vname);
|
||||||
|
|
||||||
logday = log(D.day + 1); % 0-indexed day -> their log(1-indexed day)
|
% ---------------------------------------------------------------- per metric
|
||||||
tbl0 = table(D.success, logday, double(D.stim), categorical(D.subject), ...
|
function localLogPower(D, metric, here, vname)
|
||||||
|
NS = [3 5 8 12 16 24]; EFFMULS = [1 0.5]; NREP = 120;
|
||||||
|
FORMULA = 'behavior ~ stim + day + stim:day + (1|rat)'; % 'day' = log(day+1)
|
||||||
|
if strcmp(metric, 'rate')
|
||||||
|
D = D(D.total > 0, :); beh = D.success ./ D.total; mlabel = 'success RATE'; suffix = '_rate';
|
||||||
|
else
|
||||||
|
beh = D.success; mlabel = '# successes (count)'; suffix = '';
|
||||||
|
end
|
||||||
|
warnState = warning('off', 'all'); rng(1);
|
||||||
|
logday = log(D.day + 1);
|
||||||
|
tbl0 = table(beh, logday, double(D.stim), categorical(D.subject), ...
|
||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
nStim = numel(unique(D.subject(D.stim == 1)));
|
nStim = numel(unique(D.subject(D.stim == 1)));
|
||||||
nCtrl = numel(unique(D.subject(D.stim == 0)));
|
nCtrl = numel(unique(D.subject(D.stim == 0)));
|
||||||
|
|
||||||
bar = repmat('=', 1, 78);
|
bar = repmat('=', 1, 78);
|
||||||
s = sprintf('%s\nLOG-DAY MODEL + COHEN''S f + POWER -- %s\n%s\n', bar, vname, bar);
|
s = sprintf('%s\nLOG-DAY MODEL + COHEN''S f + POWER -- %s [metric: %s]\n%s\n', bar, vname, mlabel, bar);
|
||||||
s = [s sprintf('model: behavior ~ stim + log(day) + stim:log(day) + (1|rat) (success COUNT)\n')];
|
s = [s sprintf('model: behavior ~ stim + log(day) + stim:log(day) + (1|rat) (behavior = %s)\n', mlabel)];
|
||||||
s = [s sprintf('log(day) uses 1-indexed training day (our day 0 = paper "Day 1")\n')];
|
s = [s sprintf('log(day) uses 1-indexed training day (our day 0 = paper "Day 1")\n')];
|
||||||
s = [s sprintf('observed groups: stim n=%d, control n=%d nrep=%d, alpha=0.05\n', nStim, nCtrl, NREP)];
|
s = [s sprintf('observed groups: stim n=%d, control n=%d nrep=%d, alpha=0.05\n', nStim, nCtrl, NREP)];
|
||||||
|
|
||||||
if nStim < 2 || nCtrl < 2 || numel(unique(tbl0.day)) < 2
|
if nStim < 2 || nCtrl < 2 || numel(unique(tbl0.day)) < 2
|
||||||
s = [s sprintf('\nInsufficient data for this analysis (need >=2 animals/group and >=2 days).\n')];
|
s = [s sprintf('\nInsufficient data for this analysis.\n')];
|
||||||
localFinish(s, here); warning(warnState); return
|
localFinish(s, here, suffix); warning(warnState); return
|
||||||
end
|
end
|
||||||
|
|
||||||
% ---- fitted model on the real data ----
|
|
||||||
full = fitlme(tbl0, FORMULA);
|
full = fitlme(tbl0, FORMULA);
|
||||||
An = anova(full); Asatt = anova(full, 'DFMethod', 'satterthwaite');
|
An = anova(full); Asatt = anova(full, 'DFMethod', 'satterthwaite');
|
||||||
ii = strcmp(An.Term, 'day:stim'); is = strcmp(Asatt.Term, 'day:stim');
|
ii = strcmp(An.Term, 'day:stim'); is = strcmp(Asatt.Term, 'day:stim');
|
||||||
@@ -52,7 +54,6 @@ reduced = fitlme(tbl0, 'behavior ~ stim + day + (1|rat)');
|
|||||||
eta2part = max((var(fitted(full)) - var(fitted(reduced))) / var(tbl0.behavior), 0);
|
eta2part = max((var(fitted(full)) - var(fitted(reduced))) / var(tbl0.behavior), 0);
|
||||||
cohenf = sqrt(eta2part / (1 - eta2part));
|
cohenf = sqrt(eta2part / (1 - eta2part));
|
||||||
|
|
||||||
% honest per-animal random-slope interaction
|
|
||||||
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
try
|
try
|
||||||
mr = fitlme(tbl0, 'behavior ~ stim + day + stim:day + (day|rat)');
|
mr = fitlme(tbl0, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
@@ -76,19 +77,18 @@ end
|
|||||||
s = [s sprintf('Cohen''s f (interaction, partial eta^2=%.3f) = %.3f (%s; f: .10 small, .25 medium, .40 large)\n', ...
|
s = [s sprintf('Cohen''s f (interaction, partial eta^2=%.3f) = %.3f (%s; f: .10 small, .25 medium, .40 large)\n', ...
|
||||||
eta2part, cohenf, mag)];
|
eta2part, cohenf, mag)];
|
||||||
|
|
||||||
% ---- power simulation on the log-day ground truth ----
|
|
||||||
cn = full.CoefficientNames; be = full.fixedEffects;
|
cn = full.CoefficientNames; be = full.fixedEffects;
|
||||||
b0 = be(strcmp(cn, '(Intercept)')); bStim = be(strcmp(cn, 'stim'));
|
b0 = be(strcmp(cn, '(Intercept)')); bStim = be(strcmp(cn, 'stim'));
|
||||||
bDay = be(strcmp(cn, 'day')); bInt = be(strcmp(cn, 'day:stim'));
|
bDay = be(strcmp(cn, 'day')); bInt = be(strcmp(cn, 'day:stim'));
|
||||||
psi = covarianceParameters(full); sRat = sqrt(psi{1}); sRes = sqrt(full.MSE);
|
psi = covarianceParameters(full); sRat = sqrt(psi{1}); sRes = sqrt(full.MSE);
|
||||||
days = unique(tbl0.day); % the log(day) grid
|
days = unique(tbl0.day);
|
||||||
|
|
||||||
s = [s sprintf('\n--- power simulation (log-day ground truth: stim:logday=%+.2f, ratSD=%.2f, resSD=%.2f) ---\n', ...
|
s = [s sprintf('\n--- power simulation (log-day ground truth: stim:logday=%+.4g, ratSD=%.4g, resSD=%.4g) ---\n', ...
|
||||||
bInt, sRat, sRes)];
|
bInt, sRat, sRes)];
|
||||||
for eMul = EFFMULS
|
for eMul = EFFMULS
|
||||||
bI = bInt * eMul;
|
bI = bInt * eMul;
|
||||||
s = [s sprintf('\n true stim:log(day) = %+.2f (%.0f%% of observed)\n', bI, eMul * 100)]; %#ok<AGROW>
|
s = [s sprintf('\n true stim:log(day) = %+.4g (%.0f%% of observed)\n', bI, eMul * 100)];
|
||||||
s = [s sprintf(' %-8s | per-animal power | LME power\n %s\n', 'N/group', repmat('-', 1, 42))]; %#ok<AGROW>
|
s = [s sprintf(' %-8s | per-animal power | LME power\n %s\n', 'N/group', repmat('-', 1, 42))];
|
||||||
for N = NS
|
for N = NS
|
||||||
sigPA = 0; sigL = 0;
|
sigPA = 0; sigL = 0;
|
||||||
for r = 1:NREP
|
for r = 1:NREP
|
||||||
@@ -102,19 +102,18 @@ for eMul = EFFMULS
|
|||||||
end
|
end
|
||||||
star = '';
|
star = '';
|
||||||
if N == nStim || N == nCtrl; star = ' <- observed'; end
|
if N == nStim || N == nCtrl; star = ' <- observed'; end
|
||||||
s = [s sprintf(' %-8d | %5.2f | %5.2f%s\n', N, sigPA / NREP, sigL / NREP, star)]; %#ok<AGROW>
|
s = [s sprintf(' %-8d | %5.2f | %5.2f%s\n', N, sigPA / NREP, sigL / NREP, star)];
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
s = [s sprintf(['\nRead the per-animal column as the honest power; the LME column matches the\n' ...
|
s = [s sprintf('\nRead per-animal as the honest power; LME matches the paper''s power code (optimistic).\n')];
|
||||||
'paper''s power code (anova interaction p, observation-level DF) and is optimistic.\n'])];
|
localFinish(s, here, suffix);
|
||||||
|
|
||||||
localFinish(s, here);
|
|
||||||
warning(warnState);
|
warning(warnState);
|
||||||
|
end
|
||||||
|
|
||||||
% ---------------------------------------------------------------- helpers
|
% ---------------------------------------------------------------- helpers
|
||||||
function localFinish(s, here)
|
function localFinish(s, here, suffix)
|
||||||
fprintf('%s', s);
|
fprintf('%s', s);
|
||||||
fid = fopen(fullfile(here, 'logpower_result.txt'), 'w');
|
fid = fopen(fullfile(here, ['logpower_result' suffix '.txt']), 'w');
|
||||||
fprintf(fid, '%s', s); fclose(fid);
|
fprintf(fid, '%s', s); fclose(fid);
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
% Variation learning-curve plot, in the style of the paper:
|
% Variation learning-curve plots, in the style of the paper:
|
||||||
% "Lines indicate mean (and SEM) across animals in the anodal (red) and
|
% "Lines indicate mean (and SEM) across animals in the anodal (red) and
|
||||||
% control (blue) groups."
|
% control (blue) groups."
|
||||||
% Plots mean +/- SEM successful reaches per training day for the treatment /
|
% Produces TWO figures from this folder's data.csv:
|
||||||
% anodal group (stim = 1, red) and the control group (stim = 0, blue), reading
|
% learning_curve.png # successes (count) per training day
|
||||||
% this folder's data.csv and saving learning_curve.png. The per-group N is read
|
% learning_curve_rate.png success rate (success/attempts) per training day
|
||||||
% from the data (each variation pools different groups), so the legend shows the
|
% anodal / treatment = stim 1 (red); control = stim 0 (blue). Per-group N is
|
||||||
% actual counts. Training day is 1-indexed (our day 0 = the paper's "Day 1").
|
% 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"
|
% Run: matlab -batch "plotcurve"
|
||||||
% (Copy of analysis/matlab/variation_plot.m; see make_variation_plot.m.)
|
% (Copy of analysis/matlab/variation_plot.m; see make_variation_plot.m.)
|
||||||
|
|
||||||
@@ -14,15 +15,20 @@ if isempty(here); here = pwd; end
|
|||||||
vname = regexprep(here, '.*[/\\]', '');
|
vname = regexprep(here, '.*[/\\]', '');
|
||||||
|
|
||||||
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
||||||
days = unique(D.day); % 0-indexed
|
days = unique(D.day);
|
||||||
xd = days + 1; % plot as 1-indexed training day (paper axis)
|
xd = days + 1; % plot as 1-indexed training day (paper axis)
|
||||||
|
|
||||||
red = [0.85 0.10 0.10];
|
red = [0.85 0.10 0.10];
|
||||||
blue = [0.10 0.30 0.85];
|
blue = [0.10 0.30 0.85];
|
||||||
|
|
||||||
[Ma, Sa, na] = localCurve(D, 1, days); % anodal / treatment (stim = 1)
|
localPlot(D, days, xd, 'count', '# successes', ...
|
||||||
[Mc, Sc, nc] = localCurve(D, 0, days); % control (stim = 0)
|
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]);
|
fig = figure('Visible', 'off', 'Color', 'w', 'Position', [100 100 560 460]);
|
||||||
hold on
|
hold on
|
||||||
e1 = errorbar(xd, Ma, Sa, '-o', 'Color', red, 'MarkerFaceColor', red, 'LineWidth', 2);
|
e1 = errorbar(xd, Ma, Sa, '-o', 'Color', red, 'MarkerFaceColor', red, 'LineWidth', 2);
|
||||||
@@ -30,26 +36,30 @@ e2 = errorbar(xd, Mc, Sc, '-o', 'Color', blue, 'MarkerFaceColor', blue, 'LineWid
|
|||||||
hold off
|
hold off
|
||||||
legend([e1 e2], {sprintf('anodal, N = %d', na), sprintf('control, N = %d', nc)}, ...
|
legend([e1 e2], {sprintf('anodal, N = %d', na), sprintf('control, N = %d', nc)}, ...
|
||||||
'Location', 'northwest', 'Box', 'off');
|
'Location', 'northwest', 'Box', 'off');
|
||||||
xlabel('training day');
|
xlabel('training day'); ylabel(ylab);
|
||||||
ylabel('# successes');
|
|
||||||
title(vname, 'Interpreter', 'none');
|
title(vname, 'Interpreter', 'none');
|
||||||
set(gca, 'XTick', xd, 'FontName', 'Arial', 'FontSize', 13, 'LineWidth', 1.5, 'Box', 'off');
|
set(gca, 'XTick', xd, 'FontName', 'Arial', 'FontSize', 13, 'LineWidth', 1.5, 'Box', 'off');
|
||||||
|
xtickangle(90); % vertical x-axis tick labels
|
||||||
outFile = fullfile(here, 'learning_curve.png');
|
|
||||||
exportgraphics(fig, outFile, 'Resolution', 150);
|
exportgraphics(fig, outFile, 'Resolution', 150);
|
||||||
close(fig);
|
close(fig);
|
||||||
fprintf('%s: wrote learning_curve.png (anodal N=%d, control N=%d)\n', vname, na, nc);
|
fprintf('%s: wrote %s (anodal N=%d, control N=%d)\n', vname, outFile, na, nc);
|
||||||
|
end
|
||||||
|
|
||||||
% ------------------------------------------------------------------ helper
|
function [M, S, n] = localCurve(D, stimVal, days, metric)
|
||||||
function [M, S, n] = localCurve(D, stimVal, days)
|
%LOCALCURVE Per-day mean and SEM across the animals in a group, for a metric.
|
||||||
%LOCALCURVE Per-day mean and SEM of successes across the animals in a group.
|
|
||||||
subs = unique(D.subject(D.stim == stimVal));
|
subs = unique(D.subject(D.stim == stimVal));
|
||||||
n = numel(subs);
|
n = numel(subs);
|
||||||
X = nan(numel(days), n);
|
X = nan(numel(days), n);
|
||||||
for j = 1:n
|
for j = 1:n
|
||||||
for i = 1:numel(days)
|
for i = 1:numel(days)
|
||||||
r = D.subject == subs(j) & D.day == days(i);
|
r = D.subject == subs(j) & D.day == days(i);
|
||||||
if any(r); X(i, j) = mean(D.success(r)); end
|
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
|
||||||
end
|
end
|
||||||
M = mean(X, 2, 'omitnan');
|
M = mean(X, 2, 'omitnan');
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
POWER SIMULATION -- unmerge_d0_5
|
POWER SIMULATION -- unmerge_d0_5 [metric: # successes (count)]
|
||||||
==============================================================================
|
==============================================================================
|
||||||
model: behavior ~ stim + day + stim:day + (1|rat) (success COUNT; day within-window)
|
model: behavior ~ stim + day + stim:day + (1|rat) (behavior = # successes (count); day within-window)
|
||||||
observed groups: stim n=3, control n=3 nrep=120, alpha=0.05
|
observed groups: stim n=3, control n=3 nrep=120, alpha=0.05
|
||||||
ground truth: stim:day=+5.36/day, rat SD=4.85, residual SD=10.70, days=6
|
ground truth: stim:day=+5.362/day, rat SD=4.853, residual SD=10.7, days=6
|
||||||
|
|
||||||
true stim:day interaction = +5.36 (100% of observed)
|
true stim:day interaction = +5.362 (100% of observed)
|
||||||
N/group | per-animal power | LME power
|
N/group | per-animal power | LME power
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
3 | 0.35 | 0.73 <- observed
|
3 | 0.35 | 0.73 <- observed
|
||||||
@@ -15,7 +15,7 @@ ground truth: stim:day=+5.36/day, rat SD=4.85, residual SD=10.70, days=6
|
|||||||
16 | 1.00 | 1.00
|
16 | 1.00 | 1.00
|
||||||
24 | 1.00 | 1.00
|
24 | 1.00 | 1.00
|
||||||
|
|
||||||
true stim:day interaction = +2.68 (50% of observed)
|
true stim:day interaction = +2.681 (50% of observed)
|
||||||
N/group | per-animal power | LME power
|
N/group | per-animal power | LME power
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
3 | 0.11 | 0.24 <- observed
|
3 | 0.11 | 0.24 <- observed
|
||||||
@@ -25,5 +25,4 @@ ground truth: stim:day=+5.36/day, rat SD=4.85, residual SD=10.70, days=6
|
|||||||
16 | 0.79 | 0.81
|
16 | 0.79 | 0.81
|
||||||
24 | 0.95 | 0.94
|
24 | 0.95 | 0.94
|
||||||
|
|
||||||
Read the per-animal column as the honest power. At the observed N this study
|
Read the per-animal column as the honest power; LME is optimistic (obs-level DF).
|
||||||
is typically underpowered; per-animal power reaches ~0.8 only at larger N.
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
==============================================================================
|
||||||
|
POWER SIMULATION -- unmerge_d0_5 [metric: success RATE]
|
||||||
|
==============================================================================
|
||||||
|
model: behavior ~ stim + day + stim:day + (1|rat) (behavior = success RATE; day within-window)
|
||||||
|
observed groups: stim n=3, control n=3 nrep=120, alpha=0.05
|
||||||
|
ground truth: stim:day=+0.04902/day, rat SD=0.04642, residual SD=0.07858, days=6
|
||||||
|
|
||||||
|
true stim:day interaction = +0.04902 (100% of observed)
|
||||||
|
N/group | per-animal power | LME power
|
||||||
|
------------------------------------------
|
||||||
|
3 | 0.52 | 0.89 <- observed
|
||||||
|
5 | 0.92 | 0.97
|
||||||
|
8 | 1.00 | 1.00
|
||||||
|
12 | 1.00 | 1.00
|
||||||
|
16 | 1.00 | 1.00
|
||||||
|
24 | 1.00 | 1.00
|
||||||
|
|
||||||
|
true stim:day interaction = +0.02451 (50% of observed)
|
||||||
|
N/group | per-animal power | LME power
|
||||||
|
------------------------------------------
|
||||||
|
3 | 0.12 | 0.34 <- observed
|
||||||
|
5 | 0.40 | 0.48
|
||||||
|
8 | 0.62 | 0.72
|
||||||
|
12 | 0.82 | 0.87
|
||||||
|
16 | 0.94 | 0.95
|
||||||
|
24 | 0.99 | 0.99
|
||||||
|
|
||||||
|
Read the per-animal column as the honest power; LME is optimistic (obs-level DF).
|
||||||
@@ -1,45 +1,49 @@
|
|||||||
% Variation power simulation -- Monte-Carlo power for the paper's stim x day
|
% Variation power simulation -- Monte-Carlo power for the paper's stim x day
|
||||||
% interaction, using THIS folder's data as the ground truth.
|
% interaction, using THIS folder's data as the ground truth, for BOTH metrics:
|
||||||
|
% metric = count : behavior = # successes -> power_result.txt
|
||||||
|
% metric = rate : behavior = success / attempts -> power_result_rate.txt
|
||||||
%
|
%
|
||||||
% Ground truth: fitlme(behavior ~ stim + day + stim:day + (1|rat)) on data.csv
|
% Ground truth: fitlme(behavior ~ stim + day + stim:day + (1|rat)) on data.csv
|
||||||
% (success COUNT; day within-window). Its fixed effects, per-rat intercept SD,
|
% (day within-window). Its fixed effects, per-rat intercept SD, and residual SD
|
||||||
% and residual SD generate NREP synthetic datasets at each rats-per-group N and
|
% generate NREP synthetic datasets at each rats-per-group N and each true-effect
|
||||||
% each true-effect multiplier (1 = observed slope, 0.5 = half). Each dataset is
|
% multiplier (1 = observed, 0.5 = half). Each is scored at alpha=0.05 by:
|
||||||
% scored at alpha = 0.05 two ways:
|
% per-animal : Welch t on per-rat behavior~day slopes (cluster-honest power)
|
||||||
% per-animal : Welch t on per-rat behavior~day slopes (cluster-honest -- the
|
|
||||||
% honest power, matching the random-slope / per-animal inference)
|
|
||||||
% LME : the fitlme stim:day p (observation-level DF -- optimistic)
|
% LME : the fitlme stim:day p (observation-level DF -- optimistic)
|
||||||
% Writes power_result.txt beside this script. Run: matlab -batch "powersim"
|
% Writes power_result[_rate].txt. Run: matlab -batch "powersim"
|
||||||
% (Copy of analysis/matlab/variation_power.m; see make_variation_power.m.)
|
% (Copy of analysis/matlab/variation_power.m; see make_variation_power.m.)
|
||||||
|
|
||||||
here = fileparts(mfilename('fullpath'));
|
here = fileparts(mfilename('fullpath'));
|
||||||
if isempty(here); here = pwd; end
|
if isempty(here); here = pwd; end
|
||||||
vname = regexprep(here, '.*[/\\]', '');
|
vname = regexprep(here, '.*[/\\]', '');
|
||||||
|
|
||||||
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
||||||
|
|
||||||
|
localPower(D, 'count', here, vname);
|
||||||
|
localPower(D, 'rate', here, vname);
|
||||||
|
|
||||||
|
% ---------------------------------------------------------------- per metric
|
||||||
|
function localPower(D, metric, here, vname)
|
||||||
|
NS = [3 5 8 12 16 24]; EFFMULS = [1 0.5]; NREP = 120;
|
||||||
FORMULA = 'behavior ~ stim + day + stim:day + (1|rat)';
|
FORMULA = 'behavior ~ stim + day + stim:day + (1|rat)';
|
||||||
NS = [3 5 8 12 16 24];
|
if strcmp(metric, 'rate')
|
||||||
EFFMULS = [1 0.5];
|
D = D(D.total > 0, :); beh = D.success ./ D.total; mlabel = 'success RATE'; suffix = '_rate';
|
||||||
NREP = 120;
|
else
|
||||||
|
beh = D.success; mlabel = '# successes (count)'; suffix = '';
|
||||||
warnState = warning('off', 'all');
|
end
|
||||||
rng(1);
|
warnState = warning('off', 'all'); rng(1);
|
||||||
|
|
||||||
day0 = min(D.day);
|
day0 = min(D.day);
|
||||||
tbl0 = table(D.success, D.day - day0, double(D.stim), categorical(D.subject), ...
|
tbl0 = table(beh, D.day - day0, double(D.stim), categorical(D.subject), ...
|
||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
|
|
||||||
nStim = numel(unique(D.subject(D.stim == 1)));
|
nStim = numel(unique(D.subject(D.stim == 1)));
|
||||||
nCtrl = numel(unique(D.subject(D.stim == 0)));
|
nCtrl = numel(unique(D.subject(D.stim == 0)));
|
||||||
|
|
||||||
bar = repmat('=', 1, 78);
|
bar = repmat('=', 1, 78);
|
||||||
s = sprintf('%s\nPOWER SIMULATION -- %s\n%s\n', bar, vname, bar);
|
s = sprintf('%s\nPOWER SIMULATION -- %s [metric: %s]\n%s\n', bar, vname, mlabel, bar);
|
||||||
s = [s sprintf('model: %s (success COUNT; day within-window)\n', FORMULA)];
|
s = [s sprintf('model: %s (behavior = %s; day within-window)\n', FORMULA, mlabel)];
|
||||||
s = [s sprintf('observed groups: stim n=%d, control n=%d nrep=%d, alpha=0.05\n', nStim, nCtrl, NREP)];
|
s = [s sprintf('observed groups: stim n=%d, control n=%d nrep=%d, alpha=0.05\n', nStim, nCtrl, NREP)];
|
||||||
|
|
||||||
if nStim < 2 || nCtrl < 2 || numel(unique(tbl0.day)) < 2
|
if nStim < 2 || nCtrl < 2 || numel(unique(tbl0.day)) < 2
|
||||||
s = [s sprintf('\nInsufficient data for a power simulation (need >=2 animals/group and >=2 days).\n')];
|
s = [s sprintf('\nInsufficient data for a power simulation.\n')];
|
||||||
localFinish(s, here); warning(warnState); return
|
localFinish(s, here, suffix); warning(warnState); return
|
||||||
end
|
end
|
||||||
|
|
||||||
lme = fitlme(tbl0, FORMULA);
|
lme = fitlme(tbl0, FORMULA);
|
||||||
@@ -49,14 +53,13 @@ bDay = be(strcmp(cn, 'day')); bInt = be(strcmp(cn, 'day:stim'));
|
|||||||
psi = covarianceParameters(lme); sRat = sqrt(psi{1}); sRes = sqrt(lme.MSE);
|
psi = covarianceParameters(lme); sRat = sqrt(psi{1}); sRes = sqrt(lme.MSE);
|
||||||
days = (0:max(tbl0.day))';
|
days = (0:max(tbl0.day))';
|
||||||
|
|
||||||
s = [s sprintf('ground truth: stim:day=%+.2f/day, rat SD=%.2f, residual SD=%.2f, days=%d\n', ...
|
s = [s sprintf('ground truth: stim:day=%+.4g/day, rat SD=%.4g, residual SD=%.4g, days=%d\n', ...
|
||||||
bInt, sRat, sRes, numel(days))];
|
bInt, sRat, sRes, numel(days))];
|
||||||
|
|
||||||
for eMul = EFFMULS
|
for eMul = EFFMULS
|
||||||
bI = bInt * eMul;
|
bI = bInt * eMul;
|
||||||
s = [s sprintf('\n true stim:day interaction = %+.2f (%.0f%% of observed)\n', bI, eMul * 100)]; %#ok<AGROW>
|
s = [s sprintf('\n true stim:day interaction = %+.4g (%.0f%% of observed)\n', bI, eMul * 100)];
|
||||||
s = [s sprintf(' %-8s | per-animal power | LME power\n', 'N/group')]; %#ok<AGROW>
|
s = [s sprintf(' %-8s | per-animal power | LME power\n %s\n', 'N/group', repmat('-', 1, 42))];
|
||||||
s = [s sprintf(' %s\n', repmat('-', 1, 42))]; %#ok<AGROW>
|
|
||||||
for N = NS
|
for N = NS
|
||||||
sigPA = 0; sigL = 0;
|
sigPA = 0; sigL = 0;
|
||||||
for r = 1:NREP
|
for r = 1:NREP
|
||||||
@@ -70,20 +73,18 @@ for eMul = EFFMULS
|
|||||||
end
|
end
|
||||||
star = '';
|
star = '';
|
||||||
if N == nStim || N == nCtrl; star = ' <- observed'; end
|
if N == nStim || N == nCtrl; star = ' <- observed'; end
|
||||||
s = [s sprintf(' %-8d | %5.2f | %5.2f%s\n', N, sigPA / NREP, sigL / NREP, star)]; %#ok<AGROW>
|
s = [s sprintf(' %-8d | %5.2f | %5.2f%s\n', N, sigPA / NREP, sigL / NREP, star)];
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
s = [s sprintf('\nRead the per-animal column as the honest power; LME is optimistic (obs-level DF).\n')];
|
||||||
s = [s sprintf(['\nRead the per-animal column as the honest power. At the observed N this study\n' ...
|
localFinish(s, here, suffix);
|
||||||
'is typically underpowered; per-animal power reaches ~0.8 only at larger N.\n'])];
|
|
||||||
|
|
||||||
localFinish(s, here);
|
|
||||||
warning(warnState);
|
warning(warnState);
|
||||||
|
end
|
||||||
|
|
||||||
% ---------------------------------------------------------------- helpers
|
% ---------------------------------------------------------------- helpers
|
||||||
function localFinish(s, here)
|
function localFinish(s, here, suffix)
|
||||||
fprintf('%s', s);
|
fprintf('%s', s);
|
||||||
fid = fopen(fullfile(here, 'power_result.txt'), 'w');
|
fid = fopen(fullfile(here, ['power_result' suffix '.txt']), 'w');
|
||||||
fprintf(fid, '%s', s); fclose(fid);
|
fprintf(fid, '%s', s); fclose(fid);
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
VARIATION: unmerge_d0_5
|
VARIATION: unmerge_d0_5 [metric: success COUNT]
|
||||||
==============================================================================
|
==============================================================================
|
||||||
model: behavior ~ stim + day + stim:day + (1|rat) (behavior = success COUNT)
|
model: behavior ~ stim + day + stim:day + (1|rat) (behavior = success COUNT)
|
||||||
day = training day within window (0 = first analyzed day)
|
day = training day within window (0 = first analyzed day)
|
||||||
@@ -60,9 +60,9 @@ effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
|||||||
stim x day (interaction) t(32)= 2.57 F(1)= 6.588 p=0.01515 p=0.0155 (df=30)
|
stim x day (interaction) t(32)= 2.57 F(1)= 6.588 p=0.01515 p=0.0155 (df=30)
|
||||||
day (learning) t(32)= 6.67 F(1)= 44.528 p=1.571e-07 p=2.162e-07 (df=30)
|
day (learning) t(32)= 6.67 F(1)= 44.528 p=1.571e-07 p=2.162e-07 (df=30)
|
||||||
stim (main, window start) t(32)= -0.40 F(1)= 0.163 p=0.6888 p=0.6906 (df=19)
|
stim (main, window start) t(32)= -0.40 F(1)= 0.163 p=0.6888 p=0.6906 (df=19)
|
||||||
interaction 95% CI: [+1.11, +9.62]
|
interaction 95% CI: [+1.107, +9.617]
|
||||||
HONEST LME (per-animal random slope, day|rat): interaction F(1,6.0)=3.02, p=0.1331
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,6.0)=3.02, p=0.1331
|
||||||
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
(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.)
|
model above is the honest learning-rate test -- DF collapses toward the animal count.)
|
||||||
INTERPRETATION: stim x day interaction SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates) (p=0.01515, slope diff=+5.36)
|
INTERPRETATION: stim x day interaction SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates) (p=0.01515, slope diff=+5.362)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24, count): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
==============================================================================
|
||||||
|
VARIATION: unmerge_d0_5 [metric: success RATE (success/attempts)]
|
||||||
|
==============================================================================
|
||||||
|
model: behavior ~ stim + day + stim:day + (1|rat) (behavior = success RATE (success/attempts))
|
||||||
|
day = training day within window (0 = first analyzed day)
|
||||||
|
treatment (stim=1): Electrode-Box-B2
|
||||||
|
control (stim=0): Electrode-Box-A2
|
||||||
|
N = 6 rats, 36 sessions raw day coverage: treat 0..5, control 0..5
|
||||||
|
(equal day coverage over this window)
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
FULL MODEL SUMMARY -- fitlme
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
Linear mixed-effects model fit by ML
|
||||||
|
|
||||||
|
Model information:
|
||||||
|
Number of observations 36
|
||||||
|
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
|
||||||
|
-62.201 -52.7 37.1 -74.201
|
||||||
|
|
||||||
|
Fixed effects coefficients (95% CIs):
|
||||||
|
Name Estimate SE tStat DF pValue
|
||||||
|
{'(Intercept)'} 0.2186 0.042385 5.1574 32 1.2572e-05
|
||||||
|
{'day' } 0.043366 0.010845 3.9986 32 0.0003516
|
||||||
|
{'stim' } -0.053758 0.059942 -0.89683 32 0.37651
|
||||||
|
{'day:stim' } 0.049022 0.015337 3.1962 32 0.0031273
|
||||||
|
|
||||||
|
|
||||||
|
Lower Upper
|
||||||
|
0.13226 0.30494
|
||||||
|
0.021275 0.065457
|
||||||
|
-0.17586 0.06834
|
||||||
|
0.01778 0.080263
|
||||||
|
|
||||||
|
Random effects covariance parameters (95% CIs):
|
||||||
|
Group: rat (6 Levels)
|
||||||
|
Name1 Name2 Type Estimate
|
||||||
|
{'(Intercept)'} {'(Intercept)'} {'std'} 0.046423
|
||||||
|
|
||||||
|
|
||||||
|
Lower Upper
|
||||||
|
0.019948 0.10804
|
||||||
|
|
||||||
|
Group: Error
|
||||||
|
Name Estimate Lower Upper
|
||||||
|
{'Res Std'} 0.078581 0.061014 0.10121
|
||||||
|
|
||||||
|
|
||||||
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
stim x day (interaction) t(32)= 3.20 F(1)= 10.216 p=0.003127 p=0.00327 (df=30)
|
||||||
|
day (learning) t(32)= 4.00 F(1)= 15.989 p=0.0003516 p=0.0003833 (df=30)
|
||||||
|
stim (main, window start) t(32)= -0.90 F(1)= 0.804 p=0.3765 p=0.3834 (df=16)
|
||||||
|
interaction 95% CI: [+0.01778, +0.08026]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,7.1)=6.49, p=0.0379
|
||||||
|
(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 SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates) (p=0.003127, slope diff=+0.04902)
|
||||||
|
Paper (N=24, count): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
@@ -1,30 +1,58 @@
|
|||||||
% Variation analysis -- the paper's linear mixed model on the successful-reach
|
% Variation analysis -- the paper's linear mixed model on this folder's data,
|
||||||
% COUNT, fit on this folder's curated data subset.
|
% for BOTH metrics:
|
||||||
|
% metric = count : behavior = # successes -> result.txt
|
||||||
|
% metric = rate : behavior = success / attempts -> result_rate.txt
|
||||||
|
% (rate uses only sessions with attempts > 0)
|
||||||
%
|
%
|
||||||
% model: behavior ~ stim + day + stim:day + (1|rat)
|
% model: behavior ~ stim + day + stim:day + (1|rat)
|
||||||
% behavior = successful reaches (count per session)
|
% stim = 1 treatment / 0 control; day = training day within window (0 =
|
||||||
% stim = 1 for the treatment group(s), 0 for the control group(s)
|
% first analyzed day); rat = subject (random intercept).
|
||||||
% day = training day within this window (0 = first analyzed day)
|
% For the interaction we report residual DF, Satterthwaite DF, and the honest
|
||||||
% rat = subject (random intercept)
|
% per-animal random-slope test. Self-contained: reads data.csv beside this
|
||||||
%
|
% script. Run headless with: matlab -batch "analyze"
|
||||||
% Self-contained: reads data.csv beside this script and writes result.txt.
|
% (Copy of analysis/matlab/variation_analyze.m; see make_variations.m.)
|
||||||
% 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'));
|
here = fileparts(mfilename('fullpath'));
|
||||||
if isempty(here); here = pwd; end
|
if isempty(here); here = pwd; end
|
||||||
vname = regexprep(here, '.*[/\\]', ''); % folder name = variation id
|
vname = regexprep(here, '.*[/\\]', '');
|
||||||
|
|
||||||
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
||||||
|
|
||||||
tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject), ...
|
Rc = localAnalyze(D, 'count', here, vname);
|
||||||
|
Rr = localAnalyze(D, 'rate', here, vname);
|
||||||
|
|
||||||
|
% Machine-readable handoff for SUMMARY.csv (count drives it; rate appended).
|
||||||
|
VARRESULT = struct('name', vname, 'nRats', Rc.nRats, 'nObs', Rc.nObs, ...
|
||||||
|
'interP', Rc.interP, 'interEst', Rc.interEst, ...
|
||||||
|
'interPsatt', Rc.interPsatt, 'interPrs', Rc.interPrs, ...
|
||||||
|
'stimP', Rc.stimP, 'dayP', Rc.dayP, 'covEqual', Rc.covEqual, ...
|
||||||
|
'interPrate', Rr.interP, 'interEstRate', Rr.interEst, 'interPrsRate', Rr.interPrs);
|
||||||
|
|
||||||
|
% ------------------------------------------------------------------ helper
|
||||||
|
function R = localAnalyze(D, metric, here, vname)
|
||||||
|
if strcmp(metric, 'rate')
|
||||||
|
D = D(D.total > 0, :);
|
||||||
|
beh = D.success ./ D.total;
|
||||||
|
mlabel = 'success RATE (success/attempts)'; suffix = '_rate';
|
||||||
|
else
|
||||||
|
beh = D.success;
|
||||||
|
mlabel = 'success COUNT'; suffix = '';
|
||||||
|
end
|
||||||
|
R = struct('interP', NaN, 'interEst', NaN, 'interPsatt', NaN, 'interPrs', NaN, ...
|
||||||
|
'stimP', NaN, 'dayP', NaN, 'nRats', numel(unique(D.subject)), ...
|
||||||
|
'nObs', height(D), 'covEqual', false);
|
||||||
|
if numel(unique(D.stim)) < 2 || numel(unique(D.day)) < 2
|
||||||
|
localWrite(sprintf('VARIATION: %s [metric: %s]\nInsufficient data for this metric.\n', ...
|
||||||
|
vname, mlabel), here, suffix);
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
tbl = table(beh, D.day - min(D.day), double(D.stim), categorical(D.subject), ...
|
||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
As = anova(m, 'DFMethod', 'satterthwaite');
|
||||||
|
|
||||||
% 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;
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
wst = warning('off', 'all');
|
wst = warning('off', 'all');
|
||||||
try
|
try
|
||||||
@@ -43,6 +71,7 @@ row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f
|
|||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
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)));
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
|
ii = gi('day:stim'); pI = C.pValue(ii); eI = C.Estimate(ii);
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
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));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
if abs(maxT - maxC) > 2
|
if abs(maxT - maxC) > 2
|
||||||
@@ -50,20 +79,14 @@ if abs(maxT - maxC) > 2
|
|||||||
else
|
else
|
||||||
cov = '(equal day coverage over this window)';
|
cov = '(equal day coverage over this window)';
|
||||||
end
|
end
|
||||||
|
if pI >= 0.05; verdict = 'n.s. -- slopes parallel (no differential learning rate)';
|
||||||
ii = gi('day:stim'); pI = C.pValue(ii); eI = C.Estimate(ii);
|
elseif eI > 0; verdict = 'SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates)';
|
||||||
if pI >= 0.05
|
else; verdict = 'SIGNIFICANT negative -- treatment improves SLOWER (groups converge)'; end
|
||||||
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);
|
bar = repmat('=', 1, 78);
|
||||||
raw = regexprep(evalc('disp(m)'), '</?strong>', '');
|
raw = regexprep(evalc('disp(m)'), '</?strong>', '');
|
||||||
s = sprintf('%s\nVARIATION: %s\n%s\n', bar, vname, bar);
|
s = sprintf('%s\nVARIATION: %s [metric: %s]\n%s\n', bar, vname, mlabel, bar);
|
||||||
s = [s sprintf('model: behavior ~ stim + day + stim:day + (1|rat) (behavior = success COUNT)\n')];
|
s = [s sprintf('model: behavior ~ stim + day + stim:day + (1|rat) (behavior = %s)\n', mlabel)];
|
||||||
s = [s sprintf('day = training day within window (0 = first analyzed day)\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('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('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim == 0))), ', '))];
|
||||||
@@ -74,7 +97,7 @@ s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (res
|
|||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.4g, %+.4g]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
if rsOk
|
if rsOk
|
||||||
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
else
|
else
|
||||||
@@ -82,17 +105,18 @@ else
|
|||||||
end
|
end
|
||||||
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
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'])];
|
' 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('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.4g)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24, count): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
|
localWrite(s, here, suffix);
|
||||||
|
|
||||||
|
R = struct('interP', pI, 'interEst', eI, 'interPsatt', As.pValue(gs('day:stim')), ...
|
||||||
|
'interPrs', rsP, 'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
|
'nRats', numel(unique(D.subject)), 'nObs', height(D), 'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
end
|
||||||
|
|
||||||
|
function localWrite(s, here, suffix)
|
||||||
fprintf('%s', s);
|
fprintf('%s', s);
|
||||||
fid = fopen(fullfile(here, 'result.txt'), 'w');
|
fid = fopen(fullfile(here, ['result' suffix '.txt']), 'w');
|
||||||
fprintf(fid, '%s', s);
|
fprintf(fid, '%s', s); fclose(fid);
|
||||||
fclose(fid);
|
end
|
||||||
|
|
||||||
% 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);
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 36 KiB |
@@ -1,7 +1,7 @@
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
LOG-DAY MODEL + COHEN'S f + POWER -- unmerge_d6_10
|
LOG-DAY MODEL + COHEN'S f + POWER -- unmerge_d6_10 [metric: # successes (count)]
|
||||||
==============================================================================
|
==============================================================================
|
||||||
model: behavior ~ stim + log(day) + stim:log(day) + (1|rat) (success COUNT)
|
model: behavior ~ stim + log(day) + stim:log(day) + (1|rat) (behavior = # successes (count))
|
||||||
log(day) uses 1-indexed training day (our day 0 = paper "Day 1")
|
log(day) uses 1-indexed training day (our day 0 = paper "Day 1")
|
||||||
observed groups: stim n=3, control n=2 nrep=120, alpha=0.05
|
observed groups: stim n=3, control n=2 nrep=120, alpha=0.05
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ stim x log(day) interaction: F(1,21)=0.581 p(resid)=0.4545 p(Satt)=0.4549 (df=
|
|||||||
honest per-animal random slope (log-day): F(1,5.0)=0.44 p=0.5382
|
honest per-animal random slope (log-day): F(1,5.0)=0.44 p=0.5382
|
||||||
Cohen's f (interaction, partial eta^2=0.011) = 0.106 (small-medium; f: .10 small, .25 medium, .40 large)
|
Cohen's f (interaction, partial eta^2=0.011) = 0.106 (small-medium; f: .10 small, .25 medium, .40 large)
|
||||||
|
|
||||||
--- power simulation (log-day ground truth: stim:logday=+16.98, ratSD=6.08, resSD=8.72) ---
|
--- power simulation (log-day ground truth: stim:logday=+16.98, ratSD=6.081, resSD=8.718) ---
|
||||||
|
|
||||||
true stim:log(day) = +16.98 (100% of observed)
|
true stim:log(day) = +16.98 (100% of observed)
|
||||||
N/group | per-animal power | LME power
|
N/group | per-animal power | LME power
|
||||||
@@ -22,7 +22,7 @@ Cohen's f (interaction, partial eta^2=0.011) = 0.106 (small-medium; f: .10 smal
|
|||||||
16 | 0.47 | 0.50
|
16 | 0.47 | 0.50
|
||||||
24 | 0.63 | 0.66
|
24 | 0.63 | 0.66
|
||||||
|
|
||||||
true stim:log(day) = +8.49 (50% of observed)
|
true stim:log(day) = +8.489 (50% of observed)
|
||||||
N/group | per-animal power | LME power
|
N/group | per-animal power | LME power
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
3 | 0.07 | 0.11 <- observed
|
3 | 0.07 | 0.11 <- observed
|
||||||
@@ -32,5 +32,4 @@ Cohen's f (interaction, partial eta^2=0.011) = 0.106 (small-medium; f: .10 smal
|
|||||||
16 | 0.17 | 0.17
|
16 | 0.17 | 0.17
|
||||||
24 | 0.26 | 0.26
|
24 | 0.26 | 0.26
|
||||||
|
|
||||||
Read the per-animal column as the honest power; the LME column matches the
|
Read per-animal as the honest power; LME matches the paper's power code (optimistic).
|
||||||
paper's power code (anova interaction p, observation-level DF) and is optimistic.
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
==============================================================================
|
||||||
|
LOG-DAY MODEL + COHEN'S f + POWER -- unmerge_d6_10 [metric: success RATE]
|
||||||
|
==============================================================================
|
||||||
|
model: behavior ~ stim + log(day) + stim:log(day) + (1|rat) (behavior = success RATE)
|
||||||
|
log(day) uses 1-indexed training day (our day 0 = paper "Day 1")
|
||||||
|
observed groups: stim n=3, control n=2 nrep=120, alpha=0.05
|
||||||
|
|
||||||
|
--- fitted on real data ---
|
||||||
|
stim x log(day) interaction: F(1,21)=0.027 p(resid)=0.8704 p(Satt)=0.8705 (df=20)
|
||||||
|
honest per-animal random slope (log-day): F(1,5.0)=0.01 p=0.9117
|
||||||
|
Cohen's f (interaction, partial eta^2=0.001) = 0.023 (small; f: .10 small, .25 medium, .40 large)
|
||||||
|
|
||||||
|
--- power simulation (log-day ground truth: stim:logday=+0.02357, ratSD=0.03992, resSD=0.05586) ---
|
||||||
|
|
||||||
|
true stim:log(day) = +0.02357 (100% of observed)
|
||||||
|
N/group | per-animal power | LME power
|
||||||
|
------------------------------------------
|
||||||
|
3 | 0.03 | 0.03 <- observed
|
||||||
|
5 | 0.03 | 0.06
|
||||||
|
8 | 0.06 | 0.07
|
||||||
|
12 | 0.08 | 0.09
|
||||||
|
16 | 0.05 | 0.06
|
||||||
|
24 | 0.08 | 0.06
|
||||||
|
|
||||||
|
true stim:log(day) = +0.01179 (50% of observed)
|
||||||
|
N/group | per-animal power | LME power
|
||||||
|
------------------------------------------
|
||||||
|
3 | 0.03 | 0.08 <- observed
|
||||||
|
5 | 0.07 | 0.07
|
||||||
|
8 | 0.05 | 0.05
|
||||||
|
12 | 0.07 | 0.07
|
||||||
|
16 | 0.07 | 0.07
|
||||||
|
24 | 0.06 | 0.04
|
||||||
|
|
||||||
|
Read per-animal as the honest power; LME matches the paper's power code (optimistic).
|
||||||
@@ -1,50 +1,52 @@
|
|||||||
% Variation log-day analysis + Cohen's f + power simulation.
|
% Variation log-day analysis + Cohen's f + power simulation, for BOTH metrics:
|
||||||
|
% metric = count : behavior = # successes -> logpower_result.txt
|
||||||
|
% metric = rate : behavior = success / attempts -> logpower_result_rate.txt
|
||||||
%
|
%
|
||||||
% The paper's power code models behavior against LOG training day, not raw day:
|
% The paper's power code models behavior against LOG training day:
|
||||||
% behavior ~ stim + log(day) + stim:log(day) + (1|rat).
|
% behavior ~ stim + log(day) + stim:log(day) + (1|rat).
|
||||||
% Their day is 1-indexed (1..10); our data.csv day is 0-indexed (day 0 = paper
|
% Their day is 1-indexed; our data.csv day is 0-indexed, so log(day + 1)
|
||||||
% "Day 1"), so log(day + 1) reproduces their transform exactly.
|
% reproduces their transform (our day 0 = paper "Day 1"). For each metric this
|
||||||
%
|
% refits that model, reports the interaction (residual / Satterthwaite / honest
|
||||||
% This script (a) refits that log-day model on data.csv, (b) reports the
|
% per-animal random-slope DF) and Cohen's f (partial-eta^2 effect size), then
|
||||||
% interaction (residual DF, Satterthwaite DF, and the honest per-animal
|
% runs the Monte-Carlo power sim (per-animal cluster-honest + LME power).
|
||||||
% random-slope test) and Cohen's f -- the partial-eta^2 effect size of the
|
% Run: matlab -batch "logpowersim"
|
||||||
% interaction, var(fitted_full) - var(fitted_no_interaction) over var(behavior)
|
|
||||||
% -- and (c) runs the Monte-Carlo power simulation on the log-day model,
|
|
||||||
% scoring per-animal (cluster-honest) and LME power across N.
|
|
||||||
% Writes logpower_result.txt. Run: matlab -batch "logpowersim"
|
|
||||||
% (Copy of analysis/matlab/variation_logpower.m; see make_variation_logpower.m.)
|
% (Copy of analysis/matlab/variation_logpower.m; see make_variation_logpower.m.)
|
||||||
|
|
||||||
here = fileparts(mfilename('fullpath'));
|
here = fileparts(mfilename('fullpath'));
|
||||||
if isempty(here); here = pwd; end
|
if isempty(here); here = pwd; end
|
||||||
vname = regexprep(here, '.*[/\\]', '');
|
vname = regexprep(here, '.*[/\\]', '');
|
||||||
|
|
||||||
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
||||||
FORMULA = 'behavior ~ stim + day + stim:day + (1|rat)'; % 'day' column = log(day+1)
|
|
||||||
NS = [3 5 8 12 16 24];
|
|
||||||
EFFMULS = [1 0.5];
|
|
||||||
NREP = 120;
|
|
||||||
|
|
||||||
warnState = warning('off', 'all');
|
localLogPower(D, 'count', here, vname);
|
||||||
rng(1);
|
localLogPower(D, 'rate', here, vname);
|
||||||
|
|
||||||
logday = log(D.day + 1); % 0-indexed day -> their log(1-indexed day)
|
% ---------------------------------------------------------------- per metric
|
||||||
tbl0 = table(D.success, logday, double(D.stim), categorical(D.subject), ...
|
function localLogPower(D, metric, here, vname)
|
||||||
|
NS = [3 5 8 12 16 24]; EFFMULS = [1 0.5]; NREP = 120;
|
||||||
|
FORMULA = 'behavior ~ stim + day + stim:day + (1|rat)'; % 'day' = log(day+1)
|
||||||
|
if strcmp(metric, 'rate')
|
||||||
|
D = D(D.total > 0, :); beh = D.success ./ D.total; mlabel = 'success RATE'; suffix = '_rate';
|
||||||
|
else
|
||||||
|
beh = D.success; mlabel = '# successes (count)'; suffix = '';
|
||||||
|
end
|
||||||
|
warnState = warning('off', 'all'); rng(1);
|
||||||
|
logday = log(D.day + 1);
|
||||||
|
tbl0 = table(beh, logday, double(D.stim), categorical(D.subject), ...
|
||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
nStim = numel(unique(D.subject(D.stim == 1)));
|
nStim = numel(unique(D.subject(D.stim == 1)));
|
||||||
nCtrl = numel(unique(D.subject(D.stim == 0)));
|
nCtrl = numel(unique(D.subject(D.stim == 0)));
|
||||||
|
|
||||||
bar = repmat('=', 1, 78);
|
bar = repmat('=', 1, 78);
|
||||||
s = sprintf('%s\nLOG-DAY MODEL + COHEN''S f + POWER -- %s\n%s\n', bar, vname, bar);
|
s = sprintf('%s\nLOG-DAY MODEL + COHEN''S f + POWER -- %s [metric: %s]\n%s\n', bar, vname, mlabel, bar);
|
||||||
s = [s sprintf('model: behavior ~ stim + log(day) + stim:log(day) + (1|rat) (success COUNT)\n')];
|
s = [s sprintf('model: behavior ~ stim + log(day) + stim:log(day) + (1|rat) (behavior = %s)\n', mlabel)];
|
||||||
s = [s sprintf('log(day) uses 1-indexed training day (our day 0 = paper "Day 1")\n')];
|
s = [s sprintf('log(day) uses 1-indexed training day (our day 0 = paper "Day 1")\n')];
|
||||||
s = [s sprintf('observed groups: stim n=%d, control n=%d nrep=%d, alpha=0.05\n', nStim, nCtrl, NREP)];
|
s = [s sprintf('observed groups: stim n=%d, control n=%d nrep=%d, alpha=0.05\n', nStim, nCtrl, NREP)];
|
||||||
|
|
||||||
if nStim < 2 || nCtrl < 2 || numel(unique(tbl0.day)) < 2
|
if nStim < 2 || nCtrl < 2 || numel(unique(tbl0.day)) < 2
|
||||||
s = [s sprintf('\nInsufficient data for this analysis (need >=2 animals/group and >=2 days).\n')];
|
s = [s sprintf('\nInsufficient data for this analysis.\n')];
|
||||||
localFinish(s, here); warning(warnState); return
|
localFinish(s, here, suffix); warning(warnState); return
|
||||||
end
|
end
|
||||||
|
|
||||||
% ---- fitted model on the real data ----
|
|
||||||
full = fitlme(tbl0, FORMULA);
|
full = fitlme(tbl0, FORMULA);
|
||||||
An = anova(full); Asatt = anova(full, 'DFMethod', 'satterthwaite');
|
An = anova(full); Asatt = anova(full, 'DFMethod', 'satterthwaite');
|
||||||
ii = strcmp(An.Term, 'day:stim'); is = strcmp(Asatt.Term, 'day:stim');
|
ii = strcmp(An.Term, 'day:stim'); is = strcmp(Asatt.Term, 'day:stim');
|
||||||
@@ -52,7 +54,6 @@ reduced = fitlme(tbl0, 'behavior ~ stim + day + (1|rat)');
|
|||||||
eta2part = max((var(fitted(full)) - var(fitted(reduced))) / var(tbl0.behavior), 0);
|
eta2part = max((var(fitted(full)) - var(fitted(reduced))) / var(tbl0.behavior), 0);
|
||||||
cohenf = sqrt(eta2part / (1 - eta2part));
|
cohenf = sqrt(eta2part / (1 - eta2part));
|
||||||
|
|
||||||
% honest per-animal random-slope interaction
|
|
||||||
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
try
|
try
|
||||||
mr = fitlme(tbl0, 'behavior ~ stim + day + stim:day + (day|rat)');
|
mr = fitlme(tbl0, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
@@ -76,19 +77,18 @@ end
|
|||||||
s = [s sprintf('Cohen''s f (interaction, partial eta^2=%.3f) = %.3f (%s; f: .10 small, .25 medium, .40 large)\n', ...
|
s = [s sprintf('Cohen''s f (interaction, partial eta^2=%.3f) = %.3f (%s; f: .10 small, .25 medium, .40 large)\n', ...
|
||||||
eta2part, cohenf, mag)];
|
eta2part, cohenf, mag)];
|
||||||
|
|
||||||
% ---- power simulation on the log-day ground truth ----
|
|
||||||
cn = full.CoefficientNames; be = full.fixedEffects;
|
cn = full.CoefficientNames; be = full.fixedEffects;
|
||||||
b0 = be(strcmp(cn, '(Intercept)')); bStim = be(strcmp(cn, 'stim'));
|
b0 = be(strcmp(cn, '(Intercept)')); bStim = be(strcmp(cn, 'stim'));
|
||||||
bDay = be(strcmp(cn, 'day')); bInt = be(strcmp(cn, 'day:stim'));
|
bDay = be(strcmp(cn, 'day')); bInt = be(strcmp(cn, 'day:stim'));
|
||||||
psi = covarianceParameters(full); sRat = sqrt(psi{1}); sRes = sqrt(full.MSE);
|
psi = covarianceParameters(full); sRat = sqrt(psi{1}); sRes = sqrt(full.MSE);
|
||||||
days = unique(tbl0.day); % the log(day) grid
|
days = unique(tbl0.day);
|
||||||
|
|
||||||
s = [s sprintf('\n--- power simulation (log-day ground truth: stim:logday=%+.2f, ratSD=%.2f, resSD=%.2f) ---\n', ...
|
s = [s sprintf('\n--- power simulation (log-day ground truth: stim:logday=%+.4g, ratSD=%.4g, resSD=%.4g) ---\n', ...
|
||||||
bInt, sRat, sRes)];
|
bInt, sRat, sRes)];
|
||||||
for eMul = EFFMULS
|
for eMul = EFFMULS
|
||||||
bI = bInt * eMul;
|
bI = bInt * eMul;
|
||||||
s = [s sprintf('\n true stim:log(day) = %+.2f (%.0f%% of observed)\n', bI, eMul * 100)]; %#ok<AGROW>
|
s = [s sprintf('\n true stim:log(day) = %+.4g (%.0f%% of observed)\n', bI, eMul * 100)];
|
||||||
s = [s sprintf(' %-8s | per-animal power | LME power\n %s\n', 'N/group', repmat('-', 1, 42))]; %#ok<AGROW>
|
s = [s sprintf(' %-8s | per-animal power | LME power\n %s\n', 'N/group', repmat('-', 1, 42))];
|
||||||
for N = NS
|
for N = NS
|
||||||
sigPA = 0; sigL = 0;
|
sigPA = 0; sigL = 0;
|
||||||
for r = 1:NREP
|
for r = 1:NREP
|
||||||
@@ -102,19 +102,18 @@ for eMul = EFFMULS
|
|||||||
end
|
end
|
||||||
star = '';
|
star = '';
|
||||||
if N == nStim || N == nCtrl; star = ' <- observed'; end
|
if N == nStim || N == nCtrl; star = ' <- observed'; end
|
||||||
s = [s sprintf(' %-8d | %5.2f | %5.2f%s\n', N, sigPA / NREP, sigL / NREP, star)]; %#ok<AGROW>
|
s = [s sprintf(' %-8d | %5.2f | %5.2f%s\n', N, sigPA / NREP, sigL / NREP, star)];
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
s = [s sprintf(['\nRead the per-animal column as the honest power; the LME column matches the\n' ...
|
s = [s sprintf('\nRead per-animal as the honest power; LME matches the paper''s power code (optimistic).\n')];
|
||||||
'paper''s power code (anova interaction p, observation-level DF) and is optimistic.\n'])];
|
localFinish(s, here, suffix);
|
||||||
|
|
||||||
localFinish(s, here);
|
|
||||||
warning(warnState);
|
warning(warnState);
|
||||||
|
end
|
||||||
|
|
||||||
% ---------------------------------------------------------------- helpers
|
% ---------------------------------------------------------------- helpers
|
||||||
function localFinish(s, here)
|
function localFinish(s, here, suffix)
|
||||||
fprintf('%s', s);
|
fprintf('%s', s);
|
||||||
fid = fopen(fullfile(here, 'logpower_result.txt'), 'w');
|
fid = fopen(fullfile(here, ['logpower_result' suffix '.txt']), 'w');
|
||||||
fprintf(fid, '%s', s); fclose(fid);
|
fprintf(fid, '%s', s); fclose(fid);
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
% Variation learning-curve plot, in the style of the paper:
|
% Variation learning-curve plots, in the style of the paper:
|
||||||
% "Lines indicate mean (and SEM) across animals in the anodal (red) and
|
% "Lines indicate mean (and SEM) across animals in the anodal (red) and
|
||||||
% control (blue) groups."
|
% control (blue) groups."
|
||||||
% Plots mean +/- SEM successful reaches per training day for the treatment /
|
% Produces TWO figures from this folder's data.csv:
|
||||||
% anodal group (stim = 1, red) and the control group (stim = 0, blue), reading
|
% learning_curve.png # successes (count) per training day
|
||||||
% this folder's data.csv and saving learning_curve.png. The per-group N is read
|
% learning_curve_rate.png success rate (success/attempts) per training day
|
||||||
% from the data (each variation pools different groups), so the legend shows the
|
% anodal / treatment = stim 1 (red); control = stim 0 (blue). Per-group N is
|
||||||
% actual counts. Training day is 1-indexed (our day 0 = the paper's "Day 1").
|
% 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"
|
% Run: matlab -batch "plotcurve"
|
||||||
% (Copy of analysis/matlab/variation_plot.m; see make_variation_plot.m.)
|
% (Copy of analysis/matlab/variation_plot.m; see make_variation_plot.m.)
|
||||||
|
|
||||||
@@ -14,15 +15,20 @@ if isempty(here); here = pwd; end
|
|||||||
vname = regexprep(here, '.*[/\\]', '');
|
vname = regexprep(here, '.*[/\\]', '');
|
||||||
|
|
||||||
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
||||||
days = unique(D.day); % 0-indexed
|
days = unique(D.day);
|
||||||
xd = days + 1; % plot as 1-indexed training day (paper axis)
|
xd = days + 1; % plot as 1-indexed training day (paper axis)
|
||||||
|
|
||||||
red = [0.85 0.10 0.10];
|
red = [0.85 0.10 0.10];
|
||||||
blue = [0.10 0.30 0.85];
|
blue = [0.10 0.30 0.85];
|
||||||
|
|
||||||
[Ma, Sa, na] = localCurve(D, 1, days); % anodal / treatment (stim = 1)
|
localPlot(D, days, xd, 'count', '# successes', ...
|
||||||
[Mc, Sc, nc] = localCurve(D, 0, days); % control (stim = 0)
|
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]);
|
fig = figure('Visible', 'off', 'Color', 'w', 'Position', [100 100 560 460]);
|
||||||
hold on
|
hold on
|
||||||
e1 = errorbar(xd, Ma, Sa, '-o', 'Color', red, 'MarkerFaceColor', red, 'LineWidth', 2);
|
e1 = errorbar(xd, Ma, Sa, '-o', 'Color', red, 'MarkerFaceColor', red, 'LineWidth', 2);
|
||||||
@@ -30,26 +36,30 @@ e2 = errorbar(xd, Mc, Sc, '-o', 'Color', blue, 'MarkerFaceColor', blue, 'LineWid
|
|||||||
hold off
|
hold off
|
||||||
legend([e1 e2], {sprintf('anodal, N = %d', na), sprintf('control, N = %d', nc)}, ...
|
legend([e1 e2], {sprintf('anodal, N = %d', na), sprintf('control, N = %d', nc)}, ...
|
||||||
'Location', 'northwest', 'Box', 'off');
|
'Location', 'northwest', 'Box', 'off');
|
||||||
xlabel('training day');
|
xlabel('training day'); ylabel(ylab);
|
||||||
ylabel('# successes');
|
|
||||||
title(vname, 'Interpreter', 'none');
|
title(vname, 'Interpreter', 'none');
|
||||||
set(gca, 'XTick', xd, 'FontName', 'Arial', 'FontSize', 13, 'LineWidth', 1.5, 'Box', 'off');
|
set(gca, 'XTick', xd, 'FontName', 'Arial', 'FontSize', 13, 'LineWidth', 1.5, 'Box', 'off');
|
||||||
|
xtickangle(90); % vertical x-axis tick labels
|
||||||
outFile = fullfile(here, 'learning_curve.png');
|
|
||||||
exportgraphics(fig, outFile, 'Resolution', 150);
|
exportgraphics(fig, outFile, 'Resolution', 150);
|
||||||
close(fig);
|
close(fig);
|
||||||
fprintf('%s: wrote learning_curve.png (anodal N=%d, control N=%d)\n', vname, na, nc);
|
fprintf('%s: wrote %s (anodal N=%d, control N=%d)\n', vname, outFile, na, nc);
|
||||||
|
end
|
||||||
|
|
||||||
% ------------------------------------------------------------------ helper
|
function [M, S, n] = localCurve(D, stimVal, days, metric)
|
||||||
function [M, S, n] = localCurve(D, stimVal, days)
|
%LOCALCURVE Per-day mean and SEM across the animals in a group, for a metric.
|
||||||
%LOCALCURVE Per-day mean and SEM of successes across the animals in a group.
|
|
||||||
subs = unique(D.subject(D.stim == stimVal));
|
subs = unique(D.subject(D.stim == stimVal));
|
||||||
n = numel(subs);
|
n = numel(subs);
|
||||||
X = nan(numel(days), n);
|
X = nan(numel(days), n);
|
||||||
for j = 1:n
|
for j = 1:n
|
||||||
for i = 1:numel(days)
|
for i = 1:numel(days)
|
||||||
r = D.subject == subs(j) & D.day == days(i);
|
r = D.subject == subs(j) & D.day == days(i);
|
||||||
if any(r); X(i, j) = mean(D.success(r)); end
|
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
|
||||||
end
|
end
|
||||||
M = mean(X, 2, 'omitnan');
|
M = mean(X, 2, 'omitnan');
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
POWER SIMULATION -- unmerge_d6_10
|
POWER SIMULATION -- unmerge_d6_10 [metric: # successes (count)]
|
||||||
==============================================================================
|
==============================================================================
|
||||||
model: behavior ~ stim + day + stim:day + (1|rat) (success COUNT; day within-window)
|
model: behavior ~ stim + day + stim:day + (1|rat) (behavior = # successes (count); day within-window)
|
||||||
observed groups: stim n=3, control n=2 nrep=120, alpha=0.05
|
observed groups: stim n=3, control n=2 nrep=120, alpha=0.05
|
||||||
ground truth: stim:day=+1.77/day, rat SD=6.11, residual SD=8.63, days=5
|
ground truth: stim:day=+1.767/day, rat SD=6.107, residual SD=8.629, days=5
|
||||||
|
|
||||||
true stim:day interaction = +1.77 (100% of observed)
|
true stim:day interaction = +1.767 (100% of observed)
|
||||||
N/group | per-animal power | LME power
|
N/group | per-animal power | LME power
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
3 | 0.05 | 0.10 <- observed
|
3 | 0.05 | 0.10 <- observed
|
||||||
@@ -15,7 +15,7 @@ ground truth: stim:day=+1.77/day, rat SD=6.11, residual SD=8.63, days=5
|
|||||||
16 | 0.44 | 0.42
|
16 | 0.44 | 0.42
|
||||||
24 | 0.57 | 0.58
|
24 | 0.57 | 0.58
|
||||||
|
|
||||||
true stim:day interaction = +0.88 (50% of observed)
|
true stim:day interaction = +0.8833 (50% of observed)
|
||||||
N/group | per-animal power | LME power
|
N/group | per-animal power | LME power
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
3 | 0.07 | 0.11 <- observed
|
3 | 0.07 | 0.11 <- observed
|
||||||
@@ -25,5 +25,4 @@ ground truth: stim:day=+1.77/day, rat SD=6.11, residual SD=8.63, days=5
|
|||||||
16 | 0.13 | 0.17
|
16 | 0.13 | 0.17
|
||||||
24 | 0.22 | 0.23
|
24 | 0.22 | 0.23
|
||||||
|
|
||||||
Read the per-animal column as the honest power. At the observed N this study
|
Read the per-animal column as the honest power; LME is optimistic (obs-level DF).
|
||||||
is typically underpowered; per-animal power reaches ~0.8 only at larger N.
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
==============================================================================
|
||||||
|
POWER SIMULATION -- unmerge_d6_10 [metric: success RATE]
|
||||||
|
==============================================================================
|
||||||
|
model: behavior ~ stim + day + stim:day + (1|rat) (behavior = success RATE; day within-window)
|
||||||
|
observed groups: stim n=3, control n=2 nrep=120, alpha=0.05
|
||||||
|
ground truth: stim:day=+0.002261/day, rat SD=0.03997, residual SD=0.0557, days=5
|
||||||
|
|
||||||
|
true stim:day interaction = +0.002261 (100% of observed)
|
||||||
|
N/group | per-animal power | LME power
|
||||||
|
------------------------------------------
|
||||||
|
3 | 0.03 | 0.04 <- observed
|
||||||
|
5 | 0.03 | 0.06
|
||||||
|
8 | 0.05 | 0.05
|
||||||
|
12 | 0.08 | 0.07
|
||||||
|
16 | 0.05 | 0.07
|
||||||
|
24 | 0.06 | 0.06
|
||||||
|
|
||||||
|
true stim:day interaction = +0.00113 (50% of observed)
|
||||||
|
N/group | per-animal power | LME power
|
||||||
|
------------------------------------------
|
||||||
|
3 | 0.03 | 0.08 <- observed
|
||||||
|
5 | 0.07 | 0.07
|
||||||
|
8 | 0.05 | 0.04
|
||||||
|
12 | 0.06 | 0.07
|
||||||
|
16 | 0.07 | 0.07
|
||||||
|
24 | 0.06 | 0.04
|
||||||
|
|
||||||
|
Read the per-animal column as the honest power; LME is optimistic (obs-level DF).
|
||||||
@@ -1,45 +1,49 @@
|
|||||||
% Variation power simulation -- Monte-Carlo power for the paper's stim x day
|
% Variation power simulation -- Monte-Carlo power for the paper's stim x day
|
||||||
% interaction, using THIS folder's data as the ground truth.
|
% interaction, using THIS folder's data as the ground truth, for BOTH metrics:
|
||||||
|
% metric = count : behavior = # successes -> power_result.txt
|
||||||
|
% metric = rate : behavior = success / attempts -> power_result_rate.txt
|
||||||
%
|
%
|
||||||
% Ground truth: fitlme(behavior ~ stim + day + stim:day + (1|rat)) on data.csv
|
% Ground truth: fitlme(behavior ~ stim + day + stim:day + (1|rat)) on data.csv
|
||||||
% (success COUNT; day within-window). Its fixed effects, per-rat intercept SD,
|
% (day within-window). Its fixed effects, per-rat intercept SD, and residual SD
|
||||||
% and residual SD generate NREP synthetic datasets at each rats-per-group N and
|
% generate NREP synthetic datasets at each rats-per-group N and each true-effect
|
||||||
% each true-effect multiplier (1 = observed slope, 0.5 = half). Each dataset is
|
% multiplier (1 = observed, 0.5 = half). Each is scored at alpha=0.05 by:
|
||||||
% scored at alpha = 0.05 two ways:
|
% per-animal : Welch t on per-rat behavior~day slopes (cluster-honest power)
|
||||||
% per-animal : Welch t on per-rat behavior~day slopes (cluster-honest -- the
|
|
||||||
% honest power, matching the random-slope / per-animal inference)
|
|
||||||
% LME : the fitlme stim:day p (observation-level DF -- optimistic)
|
% LME : the fitlme stim:day p (observation-level DF -- optimistic)
|
||||||
% Writes power_result.txt beside this script. Run: matlab -batch "powersim"
|
% Writes power_result[_rate].txt. Run: matlab -batch "powersim"
|
||||||
% (Copy of analysis/matlab/variation_power.m; see make_variation_power.m.)
|
% (Copy of analysis/matlab/variation_power.m; see make_variation_power.m.)
|
||||||
|
|
||||||
here = fileparts(mfilename('fullpath'));
|
here = fileparts(mfilename('fullpath'));
|
||||||
if isempty(here); here = pwd; end
|
if isempty(here); here = pwd; end
|
||||||
vname = regexprep(here, '.*[/\\]', '');
|
vname = regexprep(here, '.*[/\\]', '');
|
||||||
|
|
||||||
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
||||||
|
|
||||||
|
localPower(D, 'count', here, vname);
|
||||||
|
localPower(D, 'rate', here, vname);
|
||||||
|
|
||||||
|
% ---------------------------------------------------------------- per metric
|
||||||
|
function localPower(D, metric, here, vname)
|
||||||
|
NS = [3 5 8 12 16 24]; EFFMULS = [1 0.5]; NREP = 120;
|
||||||
FORMULA = 'behavior ~ stim + day + stim:day + (1|rat)';
|
FORMULA = 'behavior ~ stim + day + stim:day + (1|rat)';
|
||||||
NS = [3 5 8 12 16 24];
|
if strcmp(metric, 'rate')
|
||||||
EFFMULS = [1 0.5];
|
D = D(D.total > 0, :); beh = D.success ./ D.total; mlabel = 'success RATE'; suffix = '_rate';
|
||||||
NREP = 120;
|
else
|
||||||
|
beh = D.success; mlabel = '# successes (count)'; suffix = '';
|
||||||
warnState = warning('off', 'all');
|
end
|
||||||
rng(1);
|
warnState = warning('off', 'all'); rng(1);
|
||||||
|
|
||||||
day0 = min(D.day);
|
day0 = min(D.day);
|
||||||
tbl0 = table(D.success, D.day - day0, double(D.stim), categorical(D.subject), ...
|
tbl0 = table(beh, D.day - day0, double(D.stim), categorical(D.subject), ...
|
||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
|
|
||||||
nStim = numel(unique(D.subject(D.stim == 1)));
|
nStim = numel(unique(D.subject(D.stim == 1)));
|
||||||
nCtrl = numel(unique(D.subject(D.stim == 0)));
|
nCtrl = numel(unique(D.subject(D.stim == 0)));
|
||||||
|
|
||||||
bar = repmat('=', 1, 78);
|
bar = repmat('=', 1, 78);
|
||||||
s = sprintf('%s\nPOWER SIMULATION -- %s\n%s\n', bar, vname, bar);
|
s = sprintf('%s\nPOWER SIMULATION -- %s [metric: %s]\n%s\n', bar, vname, mlabel, bar);
|
||||||
s = [s sprintf('model: %s (success COUNT; day within-window)\n', FORMULA)];
|
s = [s sprintf('model: %s (behavior = %s; day within-window)\n', FORMULA, mlabel)];
|
||||||
s = [s sprintf('observed groups: stim n=%d, control n=%d nrep=%d, alpha=0.05\n', nStim, nCtrl, NREP)];
|
s = [s sprintf('observed groups: stim n=%d, control n=%d nrep=%d, alpha=0.05\n', nStim, nCtrl, NREP)];
|
||||||
|
|
||||||
if nStim < 2 || nCtrl < 2 || numel(unique(tbl0.day)) < 2
|
if nStim < 2 || nCtrl < 2 || numel(unique(tbl0.day)) < 2
|
||||||
s = [s sprintf('\nInsufficient data for a power simulation (need >=2 animals/group and >=2 days).\n')];
|
s = [s sprintf('\nInsufficient data for a power simulation.\n')];
|
||||||
localFinish(s, here); warning(warnState); return
|
localFinish(s, here, suffix); warning(warnState); return
|
||||||
end
|
end
|
||||||
|
|
||||||
lme = fitlme(tbl0, FORMULA);
|
lme = fitlme(tbl0, FORMULA);
|
||||||
@@ -49,14 +53,13 @@ bDay = be(strcmp(cn, 'day')); bInt = be(strcmp(cn, 'day:stim'));
|
|||||||
psi = covarianceParameters(lme); sRat = sqrt(psi{1}); sRes = sqrt(lme.MSE);
|
psi = covarianceParameters(lme); sRat = sqrt(psi{1}); sRes = sqrt(lme.MSE);
|
||||||
days = (0:max(tbl0.day))';
|
days = (0:max(tbl0.day))';
|
||||||
|
|
||||||
s = [s sprintf('ground truth: stim:day=%+.2f/day, rat SD=%.2f, residual SD=%.2f, days=%d\n', ...
|
s = [s sprintf('ground truth: stim:day=%+.4g/day, rat SD=%.4g, residual SD=%.4g, days=%d\n', ...
|
||||||
bInt, sRat, sRes, numel(days))];
|
bInt, sRat, sRes, numel(days))];
|
||||||
|
|
||||||
for eMul = EFFMULS
|
for eMul = EFFMULS
|
||||||
bI = bInt * eMul;
|
bI = bInt * eMul;
|
||||||
s = [s sprintf('\n true stim:day interaction = %+.2f (%.0f%% of observed)\n', bI, eMul * 100)]; %#ok<AGROW>
|
s = [s sprintf('\n true stim:day interaction = %+.4g (%.0f%% of observed)\n', bI, eMul * 100)];
|
||||||
s = [s sprintf(' %-8s | per-animal power | LME power\n', 'N/group')]; %#ok<AGROW>
|
s = [s sprintf(' %-8s | per-animal power | LME power\n %s\n', 'N/group', repmat('-', 1, 42))];
|
||||||
s = [s sprintf(' %s\n', repmat('-', 1, 42))]; %#ok<AGROW>
|
|
||||||
for N = NS
|
for N = NS
|
||||||
sigPA = 0; sigL = 0;
|
sigPA = 0; sigL = 0;
|
||||||
for r = 1:NREP
|
for r = 1:NREP
|
||||||
@@ -70,20 +73,18 @@ for eMul = EFFMULS
|
|||||||
end
|
end
|
||||||
star = '';
|
star = '';
|
||||||
if N == nStim || N == nCtrl; star = ' <- observed'; end
|
if N == nStim || N == nCtrl; star = ' <- observed'; end
|
||||||
s = [s sprintf(' %-8d | %5.2f | %5.2f%s\n', N, sigPA / NREP, sigL / NREP, star)]; %#ok<AGROW>
|
s = [s sprintf(' %-8d | %5.2f | %5.2f%s\n', N, sigPA / NREP, sigL / NREP, star)];
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
s = [s sprintf('\nRead the per-animal column as the honest power; LME is optimistic (obs-level DF).\n')];
|
||||||
s = [s sprintf(['\nRead the per-animal column as the honest power. At the observed N this study\n' ...
|
localFinish(s, here, suffix);
|
||||||
'is typically underpowered; per-animal power reaches ~0.8 only at larger N.\n'])];
|
|
||||||
|
|
||||||
localFinish(s, here);
|
|
||||||
warning(warnState);
|
warning(warnState);
|
||||||
|
end
|
||||||
|
|
||||||
% ---------------------------------------------------------------- helpers
|
% ---------------------------------------------------------------- helpers
|
||||||
function localFinish(s, here)
|
function localFinish(s, here, suffix)
|
||||||
fprintf('%s', s);
|
fprintf('%s', s);
|
||||||
fid = fopen(fullfile(here, 'power_result.txt'), 'w');
|
fid = fopen(fullfile(here, ['power_result' suffix '.txt']), 'w');
|
||||||
fprintf(fid, '%s', s); fclose(fid);
|
fprintf(fid, '%s', s); fclose(fid);
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
VARIATION: unmerge_d6_10
|
VARIATION: unmerge_d6_10 [metric: success COUNT]
|
||||||
==============================================================================
|
==============================================================================
|
||||||
model: behavior ~ stim + day + stim:day + (1|rat) (behavior = success COUNT)
|
model: behavior ~ stim + day + stim:day + (1|rat) (behavior = success COUNT)
|
||||||
day = training day within window (0 = first analyzed day)
|
day = training day within window (0 = first analyzed day)
|
||||||
@@ -60,9 +60,9 @@ effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
|||||||
stim x day (interaction) t(21)= 0.71 F(1)= 0.503 p=0.486 p=0.4864 (df=20)
|
stim x day (interaction) t(21)= 0.71 F(1)= 0.503 p=0.486 p=0.4864 (df=20)
|
||||||
day (learning) t(21)= 1.24 F(1)= 1.547 p=0.2273 p=0.2279 (df=20)
|
day (learning) t(21)= 1.24 F(1)= 1.547 p=0.2273 p=0.2279 (df=20)
|
||||||
stim (main, window start) t(21)= 1.73 F(1)= 3.008 p=0.09752 p=0.1098 (df=11)
|
stim (main, window start) t(21)= 1.73 F(1)= 3.008 p=0.09752 p=0.1098 (df=11)
|
||||||
interaction 95% CI: [-3.41, +6.95]
|
interaction 95% CI: [-3.414, +6.947]
|
||||||
HONEST LME (per-animal random slope, day|rat): interaction F(1,5.0)=0.40, p=0.5549
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,5.0)=0.40, p=0.5549
|
||||||
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
(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.)
|
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.486, slope diff=+1.77)
|
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.486, slope diff=+1.767)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24, count): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
==============================================================================
|
||||||
|
VARIATION: unmerge_d6_10 [metric: success RATE (success/attempts)]
|
||||||
|
==============================================================================
|
||||||
|
model: behavior ~ stim + day + stim:day + (1|rat) (behavior = success RATE (success/attempts))
|
||||||
|
day = training day within window (0 = first analyzed day)
|
||||||
|
treatment (stim=1): Electrode-Box-B2
|
||||||
|
control (stim=0): Electrode-Box-A2
|
||||||
|
N = 5 rats, 25 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 25
|
||||||
|
Fixed effects coefficients 4
|
||||||
|
Random effects coefficients 5
|
||||||
|
Covariance parameters 2
|
||||||
|
|
||||||
|
Formula:
|
||||||
|
behavior ~ 1 + day*stim + (1 | rat)
|
||||||
|
|
||||||
|
Model fit statistics:
|
||||||
|
AIC BIC LogLikelihood Deviance
|
||||||
|
-55.071 -47.758 33.535 -67.071
|
||||||
|
|
||||||
|
Fixed effects coefficients (95% CIs):
|
||||||
|
Name Estimate SE tStat DF pValue
|
||||||
|
{'(Intercept)'} 0.52619 0.041587 12.653 21 2.7251e-11
|
||||||
|
{'day' } 0.0099391 0.012456 0.79797 21 0.43382
|
||||||
|
{'stim' } 0.11742 0.053689 2.187 21 0.040203
|
||||||
|
{'day:stim' } 0.0022606 0.01608 0.14058 21 0.88954
|
||||||
|
|
||||||
|
|
||||||
|
Lower Upper
|
||||||
|
0.4397 0.61267
|
||||||
|
-0.015964 0.035842
|
||||||
|
0.0057634 0.22907
|
||||||
|
-0.03118 0.035701
|
||||||
|
|
||||||
|
Random effects covariance parameters (95% CIs):
|
||||||
|
Group: rat (5 Levels)
|
||||||
|
Name1 Name2 Type Estimate
|
||||||
|
{'(Intercept)'} {'(Intercept)'} {'std'} 0.039966
|
||||||
|
|
||||||
|
|
||||||
|
Lower Upper
|
||||||
|
0.016761 0.095298
|
||||||
|
|
||||||
|
Group: Error
|
||||||
|
Name Estimate Lower Upper
|
||||||
|
{'Res Std'} 0.055703 0.040859 0.075939
|
||||||
|
|
||||||
|
|
||||||
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
stim x day (interaction) t(21)= 0.14 F(1)= 0.020 p=0.8895 p=0.8896 (df=20)
|
||||||
|
day (learning) t(21)= 0.80 F(1)= 0.637 p=0.4338 p=0.4343 (df=20)
|
||||||
|
stim (main, window start) t(21)= 2.19 F(1)= 4.783 p=0.0402 p=0.05066 (df=11)
|
||||||
|
interaction 95% CI: [-0.03118, +0.0357]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,5.0)=0.01, p=0.9219
|
||||||
|
(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.8895, slope diff=+0.002261)
|
||||||
|
Paper (N=24, count): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
@@ -1,30 +1,58 @@
|
|||||||
% Variation analysis -- the paper's linear mixed model on the successful-reach
|
% Variation analysis -- the paper's linear mixed model on this folder's data,
|
||||||
% COUNT, fit on this folder's curated data subset.
|
% for BOTH metrics:
|
||||||
|
% metric = count : behavior = # successes -> result.txt
|
||||||
|
% metric = rate : behavior = success / attempts -> result_rate.txt
|
||||||
|
% (rate uses only sessions with attempts > 0)
|
||||||
%
|
%
|
||||||
% model: behavior ~ stim + day + stim:day + (1|rat)
|
% model: behavior ~ stim + day + stim:day + (1|rat)
|
||||||
% behavior = successful reaches (count per session)
|
% stim = 1 treatment / 0 control; day = training day within window (0 =
|
||||||
% stim = 1 for the treatment group(s), 0 for the control group(s)
|
% first analyzed day); rat = subject (random intercept).
|
||||||
% day = training day within this window (0 = first analyzed day)
|
% For the interaction we report residual DF, Satterthwaite DF, and the honest
|
||||||
% rat = subject (random intercept)
|
% per-animal random-slope test. Self-contained: reads data.csv beside this
|
||||||
%
|
% script. Run headless with: matlab -batch "analyze"
|
||||||
% Self-contained: reads data.csv beside this script and writes result.txt.
|
% (Copy of analysis/matlab/variation_analyze.m; see make_variations.m.)
|
||||||
% 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'));
|
here = fileparts(mfilename('fullpath'));
|
||||||
if isempty(here); here = pwd; end
|
if isempty(here); here = pwd; end
|
||||||
vname = regexprep(here, '.*[/\\]', ''); % folder name = variation id
|
vname = regexprep(here, '.*[/\\]', '');
|
||||||
|
|
||||||
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
||||||
|
|
||||||
tbl = table(D.success, D.day - min(D.day), double(D.stim), categorical(D.subject), ...
|
Rc = localAnalyze(D, 'count', here, vname);
|
||||||
|
Rr = localAnalyze(D, 'rate', here, vname);
|
||||||
|
|
||||||
|
% Machine-readable handoff for SUMMARY.csv (count drives it; rate appended).
|
||||||
|
VARRESULT = struct('name', vname, 'nRats', Rc.nRats, 'nObs', Rc.nObs, ...
|
||||||
|
'interP', Rc.interP, 'interEst', Rc.interEst, ...
|
||||||
|
'interPsatt', Rc.interPsatt, 'interPrs', Rc.interPrs, ...
|
||||||
|
'stimP', Rc.stimP, 'dayP', Rc.dayP, 'covEqual', Rc.covEqual, ...
|
||||||
|
'interPrate', Rr.interP, 'interEstRate', Rr.interEst, 'interPrsRate', Rr.interPrs);
|
||||||
|
|
||||||
|
% ------------------------------------------------------------------ helper
|
||||||
|
function R = localAnalyze(D, metric, here, vname)
|
||||||
|
if strcmp(metric, 'rate')
|
||||||
|
D = D(D.total > 0, :);
|
||||||
|
beh = D.success ./ D.total;
|
||||||
|
mlabel = 'success RATE (success/attempts)'; suffix = '_rate';
|
||||||
|
else
|
||||||
|
beh = D.success;
|
||||||
|
mlabel = 'success COUNT'; suffix = '';
|
||||||
|
end
|
||||||
|
R = struct('interP', NaN, 'interEst', NaN, 'interPsatt', NaN, 'interPrs', NaN, ...
|
||||||
|
'stimP', NaN, 'dayP', NaN, 'nRats', numel(unique(D.subject)), ...
|
||||||
|
'nObs', height(D), 'covEqual', false);
|
||||||
|
if numel(unique(D.stim)) < 2 || numel(unique(D.day)) < 2
|
||||||
|
localWrite(sprintf('VARIATION: %s [metric: %s]\nInsufficient data for this metric.\n', ...
|
||||||
|
vname, mlabel), here, suffix);
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
tbl = table(beh, D.day - min(D.day), double(D.stim), categorical(D.subject), ...
|
||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
m = fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)');
|
||||||
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
C = m.Coefficients; A = anova(m); ci = coefCI(m);
|
||||||
As = anova(m, 'DFMethod', 'satterthwaite'); % Satterthwaite denominator DF
|
As = anova(m, 'DFMethod', 'satterthwaite');
|
||||||
|
|
||||||
% 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;
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
wst = warning('off', 'all');
|
wst = warning('off', 'all');
|
||||||
try
|
try
|
||||||
@@ -43,6 +71,7 @@ row = @(nm, t) sprintf('%-26s t(%d)=%6.2f F(%d)=%7.3f p=%.4g p=%.4g (df=%.0f
|
|||||||
C.DF(gi(t)), C.tStat(gi(t)), A.DF1(ga(t)), A.FStat(ga(t)), C.pValue(gi(t)), ...
|
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)));
|
As.pValue(gs(t)), As.DF2(gs(t)));
|
||||||
|
|
||||||
|
ii = gi('day:stim'); pI = C.pValue(ii); eI = C.Estimate(ii);
|
||||||
maxT = max(D.day(D.stim == 1)); minT = min(D.day(D.stim == 1));
|
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));
|
maxC = max(D.day(D.stim == 0)); minC = min(D.day(D.stim == 0));
|
||||||
if abs(maxT - maxC) > 2
|
if abs(maxT - maxC) > 2
|
||||||
@@ -50,20 +79,14 @@ if abs(maxT - maxC) > 2
|
|||||||
else
|
else
|
||||||
cov = '(equal day coverage over this window)';
|
cov = '(equal day coverage over this window)';
|
||||||
end
|
end
|
||||||
|
if pI >= 0.05; verdict = 'n.s. -- slopes parallel (no differential learning rate)';
|
||||||
ii = gi('day:stim'); pI = C.pValue(ii); eI = C.Estimate(ii);
|
elseif eI > 0; verdict = 'SIGNIFICANT positive -- treatment improves FASTER (benefit accumulates)';
|
||||||
if pI >= 0.05
|
else; verdict = 'SIGNIFICANT negative -- treatment improves SLOWER (groups converge)'; end
|
||||||
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);
|
bar = repmat('=', 1, 78);
|
||||||
raw = regexprep(evalc('disp(m)'), '</?strong>', '');
|
raw = regexprep(evalc('disp(m)'), '</?strong>', '');
|
||||||
s = sprintf('%s\nVARIATION: %s\n%s\n', bar, vname, bar);
|
s = sprintf('%s\nVARIATION: %s [metric: %s]\n%s\n', bar, vname, mlabel, bar);
|
||||||
s = [s sprintf('model: behavior ~ stim + day + stim:day + (1|rat) (behavior = success COUNT)\n')];
|
s = [s sprintf('model: behavior ~ stim + day + stim:day + (1|rat) (behavior = %s)\n', mlabel)];
|
||||||
s = [s sprintf('day = training day within window (0 = first analyzed day)\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('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('control (stim=0): %s\n', strjoin(cellstr(unique(D.group(D.stim == 0))), ', '))];
|
||||||
@@ -74,7 +97,7 @@ s = [s sprintf('%-26s %-18s %-12s %s\n%s\n', 'effect', 't(df) / F(df1)', 'p (res
|
|||||||
s = [s row('stim x day (interaction)', 'day:stim')];
|
s = [s row('stim x day (interaction)', 'day:stim')];
|
||||||
s = [s row('day (learning)', 'day')];
|
s = [s row('day (learning)', 'day')];
|
||||||
s = [s row('stim (main, window start)', 'stim')];
|
s = [s row('stim (main, window start)', 'stim')];
|
||||||
s = [s sprintf('interaction 95%% CI: [%+.2f, %+.2f]\n', ci(ii, 1), ci(ii, 2))];
|
s = [s sprintf('interaction 95%% CI: [%+.4g, %+.4g]\n', ci(ii, 1), ci(ii, 2))];
|
||||||
if rsOk
|
if rsOk
|
||||||
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
s = [s sprintf('HONEST LME (per-animal random slope, day|rat): interaction F(1,%.1f)=%.2f, p=%.4g\n', rsDf, rsF, rsP)];
|
||||||
else
|
else
|
||||||
@@ -82,17 +105,18 @@ else
|
|||||||
end
|
end
|
||||||
s = [s sprintf([' (Satterthwaite DF ~= residual on this random-intercept model; the random-slope\n' ...
|
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'])];
|
' 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('INTERPRETATION: stim x day interaction %s (p=%.4g, slope diff=%+.4g)\n', verdict, pI, eI)];
|
||||||
s = [s sprintf('Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
s = [s sprintf('Paper (N=24, count): interaction t(227)=2.68, F(1)=7.12, p=0.008.\n')];
|
||||||
|
|
||||||
|
localWrite(s, here, suffix);
|
||||||
|
|
||||||
|
R = struct('interP', pI, 'interEst', eI, 'interPsatt', As.pValue(gs('day:stim')), ...
|
||||||
|
'interPrs', rsP, 'stimP', C.pValue(gi('stim')), 'dayP', C.pValue(gi('day')), ...
|
||||||
|
'nRats', numel(unique(D.subject)), 'nObs', height(D), 'covEqual', abs(maxT - maxC) <= 2);
|
||||||
|
end
|
||||||
|
|
||||||
|
function localWrite(s, here, suffix)
|
||||||
fprintf('%s', s);
|
fprintf('%s', s);
|
||||||
fid = fopen(fullfile(here, 'result.txt'), 'w');
|
fid = fopen(fullfile(here, ['result' suffix '.txt']), 'w');
|
||||||
fprintf(fid, '%s', s);
|
fprintf(fid, '%s', s); fclose(fid);
|
||||||
fclose(fid);
|
end
|
||||||
|
|
||||||
% 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);
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 39 KiB |
@@ -1,7 +1,7 @@
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
LOG-DAY MODEL + COHEN'S f + POWER -- unmerge_d6_13
|
LOG-DAY MODEL + COHEN'S f + POWER -- unmerge_d6_13 [metric: # successes (count)]
|
||||||
==============================================================================
|
==============================================================================
|
||||||
model: behavior ~ stim + log(day) + stim:log(day) + (1|rat) (success COUNT)
|
model: behavior ~ stim + log(day) + stim:log(day) + (1|rat) (behavior = # successes (count))
|
||||||
log(day) uses 1-indexed training day (our day 0 = paper "Day 1")
|
log(day) uses 1-indexed training day (our day 0 = paper "Day 1")
|
||||||
observed groups: stim n=3, control n=2 nrep=120, alpha=0.05
|
observed groups: stim n=3, control n=2 nrep=120, alpha=0.05
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ stim x log(day) interaction: F(1,30)=3.007 p(resid)=0.09315 p(Satt)=0.0931 (df
|
|||||||
honest per-animal random slope (log-day): F(1,12.8)=2.47 p=0.1406
|
honest per-animal random slope (log-day): F(1,12.8)=2.47 p=0.1406
|
||||||
Cohen's f (interaction, partial eta^2=0.025) = 0.161 (small-medium; f: .10 small, .25 medium, .40 large)
|
Cohen's f (interaction, partial eta^2=0.025) = 0.161 (small-medium; f: .10 small, .25 medium, .40 large)
|
||||||
|
|
||||||
--- power simulation (log-day ground truth: stim:logday=+22.97, ratSD=7.14, resSD=7.81) ---
|
--- power simulation (log-day ground truth: stim:logday=+22.97, ratSD=7.136, resSD=7.808) ---
|
||||||
|
|
||||||
true stim:log(day) = +22.97 (100% of observed)
|
true stim:log(day) = +22.97 (100% of observed)
|
||||||
N/group | per-animal power | LME power
|
N/group | per-animal power | LME power
|
||||||
@@ -32,5 +32,4 @@ Cohen's f (interaction, partial eta^2=0.025) = 0.161 (small-medium; f: .10 smal
|
|||||||
16 | 0.71 | 0.74
|
16 | 0.71 | 0.74
|
||||||
24 | 0.90 | 0.93
|
24 | 0.90 | 0.93
|
||||||
|
|
||||||
Read the per-animal column as the honest power; the LME column matches the
|
Read per-animal as the honest power; LME matches the paper's power code (optimistic).
|
||||||
paper's power code (anova interaction p, observation-level DF) and is optimistic.
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
==============================================================================
|
||||||
|
LOG-DAY MODEL + COHEN'S f + POWER -- unmerge_d6_13 [metric: success RATE]
|
||||||
|
==============================================================================
|
||||||
|
model: behavior ~ stim + log(day) + stim:log(day) + (1|rat) (behavior = success RATE)
|
||||||
|
log(day) uses 1-indexed training day (our day 0 = paper "Day 1")
|
||||||
|
observed groups: stim n=3, control n=2 nrep=120, alpha=0.05
|
||||||
|
|
||||||
|
--- fitted on real data ---
|
||||||
|
stim x log(day) interaction: F(1,30)=1.838 p(resid)=0.1853 p(Satt)=0.1853 (df=30)
|
||||||
|
honest per-animal random slope (log-day): F(1,3.6)=0.01 p=0.9233
|
||||||
|
Cohen's f (interaction, partial eta^2=0.016) = 0.129 (small-medium; f: .10 small, .25 medium, .40 large)
|
||||||
|
|
||||||
|
--- power simulation (log-day ground truth: stim:logday=+0.1165, ratSD=0.04832, resSD=0.05061) ---
|
||||||
|
|
||||||
|
true stim:log(day) = +0.1165 (100% of observed)
|
||||||
|
N/group | per-animal power | LME power
|
||||||
|
------------------------------------------
|
||||||
|
3 | 0.28 | 0.46 <- observed
|
||||||
|
5 | 0.58 | 0.70
|
||||||
|
8 | 0.78 | 0.83
|
||||||
|
12 | 0.90 | 0.89
|
||||||
|
16 | 0.99 | 1.00
|
||||||
|
24 | 1.00 | 1.00
|
||||||
|
|
||||||
|
true stim:log(day) = +0.05823 (50% of observed)
|
||||||
|
N/group | per-animal power | LME power
|
||||||
|
------------------------------------------
|
||||||
|
3 | 0.08 | 0.17 <- observed
|
||||||
|
5 | 0.18 | 0.27
|
||||||
|
8 | 0.23 | 0.32
|
||||||
|
12 | 0.34 | 0.38
|
||||||
|
16 | 0.47 | 0.51
|
||||||
|
24 | 0.79 | 0.82
|
||||||
|
|
||||||
|
Read per-animal as the honest power; LME matches the paper's power code (optimistic).
|
||||||
@@ -1,50 +1,52 @@
|
|||||||
% Variation log-day analysis + Cohen's f + power simulation.
|
% Variation log-day analysis + Cohen's f + power simulation, for BOTH metrics:
|
||||||
|
% metric = count : behavior = # successes -> logpower_result.txt
|
||||||
|
% metric = rate : behavior = success / attempts -> logpower_result_rate.txt
|
||||||
%
|
%
|
||||||
% The paper's power code models behavior against LOG training day, not raw day:
|
% The paper's power code models behavior against LOG training day:
|
||||||
% behavior ~ stim + log(day) + stim:log(day) + (1|rat).
|
% behavior ~ stim + log(day) + stim:log(day) + (1|rat).
|
||||||
% Their day is 1-indexed (1..10); our data.csv day is 0-indexed (day 0 = paper
|
% Their day is 1-indexed; our data.csv day is 0-indexed, so log(day + 1)
|
||||||
% "Day 1"), so log(day + 1) reproduces their transform exactly.
|
% reproduces their transform (our day 0 = paper "Day 1"). For each metric this
|
||||||
%
|
% refits that model, reports the interaction (residual / Satterthwaite / honest
|
||||||
% This script (a) refits that log-day model on data.csv, (b) reports the
|
% per-animal random-slope DF) and Cohen's f (partial-eta^2 effect size), then
|
||||||
% interaction (residual DF, Satterthwaite DF, and the honest per-animal
|
% runs the Monte-Carlo power sim (per-animal cluster-honest + LME power).
|
||||||
% random-slope test) and Cohen's f -- the partial-eta^2 effect size of the
|
% Run: matlab -batch "logpowersim"
|
||||||
% interaction, var(fitted_full) - var(fitted_no_interaction) over var(behavior)
|
|
||||||
% -- and (c) runs the Monte-Carlo power simulation on the log-day model,
|
|
||||||
% scoring per-animal (cluster-honest) and LME power across N.
|
|
||||||
% Writes logpower_result.txt. Run: matlab -batch "logpowersim"
|
|
||||||
% (Copy of analysis/matlab/variation_logpower.m; see make_variation_logpower.m.)
|
% (Copy of analysis/matlab/variation_logpower.m; see make_variation_logpower.m.)
|
||||||
|
|
||||||
here = fileparts(mfilename('fullpath'));
|
here = fileparts(mfilename('fullpath'));
|
||||||
if isempty(here); here = pwd; end
|
if isempty(here); here = pwd; end
|
||||||
vname = regexprep(here, '.*[/\\]', '');
|
vname = regexprep(here, '.*[/\\]', '');
|
||||||
|
|
||||||
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
||||||
FORMULA = 'behavior ~ stim + day + stim:day + (1|rat)'; % 'day' column = log(day+1)
|
|
||||||
NS = [3 5 8 12 16 24];
|
|
||||||
EFFMULS = [1 0.5];
|
|
||||||
NREP = 120;
|
|
||||||
|
|
||||||
warnState = warning('off', 'all');
|
localLogPower(D, 'count', here, vname);
|
||||||
rng(1);
|
localLogPower(D, 'rate', here, vname);
|
||||||
|
|
||||||
logday = log(D.day + 1); % 0-indexed day -> their log(1-indexed day)
|
% ---------------------------------------------------------------- per metric
|
||||||
tbl0 = table(D.success, logday, double(D.stim), categorical(D.subject), ...
|
function localLogPower(D, metric, here, vname)
|
||||||
|
NS = [3 5 8 12 16 24]; EFFMULS = [1 0.5]; NREP = 120;
|
||||||
|
FORMULA = 'behavior ~ stim + day + stim:day + (1|rat)'; % 'day' = log(day+1)
|
||||||
|
if strcmp(metric, 'rate')
|
||||||
|
D = D(D.total > 0, :); beh = D.success ./ D.total; mlabel = 'success RATE'; suffix = '_rate';
|
||||||
|
else
|
||||||
|
beh = D.success; mlabel = '# successes (count)'; suffix = '';
|
||||||
|
end
|
||||||
|
warnState = warning('off', 'all'); rng(1);
|
||||||
|
logday = log(D.day + 1);
|
||||||
|
tbl0 = table(beh, logday, double(D.stim), categorical(D.subject), ...
|
||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
nStim = numel(unique(D.subject(D.stim == 1)));
|
nStim = numel(unique(D.subject(D.stim == 1)));
|
||||||
nCtrl = numel(unique(D.subject(D.stim == 0)));
|
nCtrl = numel(unique(D.subject(D.stim == 0)));
|
||||||
|
|
||||||
bar = repmat('=', 1, 78);
|
bar = repmat('=', 1, 78);
|
||||||
s = sprintf('%s\nLOG-DAY MODEL + COHEN''S f + POWER -- %s\n%s\n', bar, vname, bar);
|
s = sprintf('%s\nLOG-DAY MODEL + COHEN''S f + POWER -- %s [metric: %s]\n%s\n', bar, vname, mlabel, bar);
|
||||||
s = [s sprintf('model: behavior ~ stim + log(day) + stim:log(day) + (1|rat) (success COUNT)\n')];
|
s = [s sprintf('model: behavior ~ stim + log(day) + stim:log(day) + (1|rat) (behavior = %s)\n', mlabel)];
|
||||||
s = [s sprintf('log(day) uses 1-indexed training day (our day 0 = paper "Day 1")\n')];
|
s = [s sprintf('log(day) uses 1-indexed training day (our day 0 = paper "Day 1")\n')];
|
||||||
s = [s sprintf('observed groups: stim n=%d, control n=%d nrep=%d, alpha=0.05\n', nStim, nCtrl, NREP)];
|
s = [s sprintf('observed groups: stim n=%d, control n=%d nrep=%d, alpha=0.05\n', nStim, nCtrl, NREP)];
|
||||||
|
|
||||||
if nStim < 2 || nCtrl < 2 || numel(unique(tbl0.day)) < 2
|
if nStim < 2 || nCtrl < 2 || numel(unique(tbl0.day)) < 2
|
||||||
s = [s sprintf('\nInsufficient data for this analysis (need >=2 animals/group and >=2 days).\n')];
|
s = [s sprintf('\nInsufficient data for this analysis.\n')];
|
||||||
localFinish(s, here); warning(warnState); return
|
localFinish(s, here, suffix); warning(warnState); return
|
||||||
end
|
end
|
||||||
|
|
||||||
% ---- fitted model on the real data ----
|
|
||||||
full = fitlme(tbl0, FORMULA);
|
full = fitlme(tbl0, FORMULA);
|
||||||
An = anova(full); Asatt = anova(full, 'DFMethod', 'satterthwaite');
|
An = anova(full); Asatt = anova(full, 'DFMethod', 'satterthwaite');
|
||||||
ii = strcmp(An.Term, 'day:stim'); is = strcmp(Asatt.Term, 'day:stim');
|
ii = strcmp(An.Term, 'day:stim'); is = strcmp(Asatt.Term, 'day:stim');
|
||||||
@@ -52,7 +54,6 @@ reduced = fitlme(tbl0, 'behavior ~ stim + day + (1|rat)');
|
|||||||
eta2part = max((var(fitted(full)) - var(fitted(reduced))) / var(tbl0.behavior), 0);
|
eta2part = max((var(fitted(full)) - var(fitted(reduced))) / var(tbl0.behavior), 0);
|
||||||
cohenf = sqrt(eta2part / (1 - eta2part));
|
cohenf = sqrt(eta2part / (1 - eta2part));
|
||||||
|
|
||||||
% honest per-animal random-slope interaction
|
|
||||||
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
rsP = NaN; rsDf = NaN; rsF = NaN; rsOk = false;
|
||||||
try
|
try
|
||||||
mr = fitlme(tbl0, 'behavior ~ stim + day + stim:day + (day|rat)');
|
mr = fitlme(tbl0, 'behavior ~ stim + day + stim:day + (day|rat)');
|
||||||
@@ -76,19 +77,18 @@ end
|
|||||||
s = [s sprintf('Cohen''s f (interaction, partial eta^2=%.3f) = %.3f (%s; f: .10 small, .25 medium, .40 large)\n', ...
|
s = [s sprintf('Cohen''s f (interaction, partial eta^2=%.3f) = %.3f (%s; f: .10 small, .25 medium, .40 large)\n', ...
|
||||||
eta2part, cohenf, mag)];
|
eta2part, cohenf, mag)];
|
||||||
|
|
||||||
% ---- power simulation on the log-day ground truth ----
|
|
||||||
cn = full.CoefficientNames; be = full.fixedEffects;
|
cn = full.CoefficientNames; be = full.fixedEffects;
|
||||||
b0 = be(strcmp(cn, '(Intercept)')); bStim = be(strcmp(cn, 'stim'));
|
b0 = be(strcmp(cn, '(Intercept)')); bStim = be(strcmp(cn, 'stim'));
|
||||||
bDay = be(strcmp(cn, 'day')); bInt = be(strcmp(cn, 'day:stim'));
|
bDay = be(strcmp(cn, 'day')); bInt = be(strcmp(cn, 'day:stim'));
|
||||||
psi = covarianceParameters(full); sRat = sqrt(psi{1}); sRes = sqrt(full.MSE);
|
psi = covarianceParameters(full); sRat = sqrt(psi{1}); sRes = sqrt(full.MSE);
|
||||||
days = unique(tbl0.day); % the log(day) grid
|
days = unique(tbl0.day);
|
||||||
|
|
||||||
s = [s sprintf('\n--- power simulation (log-day ground truth: stim:logday=%+.2f, ratSD=%.2f, resSD=%.2f) ---\n', ...
|
s = [s sprintf('\n--- power simulation (log-day ground truth: stim:logday=%+.4g, ratSD=%.4g, resSD=%.4g) ---\n', ...
|
||||||
bInt, sRat, sRes)];
|
bInt, sRat, sRes)];
|
||||||
for eMul = EFFMULS
|
for eMul = EFFMULS
|
||||||
bI = bInt * eMul;
|
bI = bInt * eMul;
|
||||||
s = [s sprintf('\n true stim:log(day) = %+.2f (%.0f%% of observed)\n', bI, eMul * 100)]; %#ok<AGROW>
|
s = [s sprintf('\n true stim:log(day) = %+.4g (%.0f%% of observed)\n', bI, eMul * 100)];
|
||||||
s = [s sprintf(' %-8s | per-animal power | LME power\n %s\n', 'N/group', repmat('-', 1, 42))]; %#ok<AGROW>
|
s = [s sprintf(' %-8s | per-animal power | LME power\n %s\n', 'N/group', repmat('-', 1, 42))];
|
||||||
for N = NS
|
for N = NS
|
||||||
sigPA = 0; sigL = 0;
|
sigPA = 0; sigL = 0;
|
||||||
for r = 1:NREP
|
for r = 1:NREP
|
||||||
@@ -102,19 +102,18 @@ for eMul = EFFMULS
|
|||||||
end
|
end
|
||||||
star = '';
|
star = '';
|
||||||
if N == nStim || N == nCtrl; star = ' <- observed'; end
|
if N == nStim || N == nCtrl; star = ' <- observed'; end
|
||||||
s = [s sprintf(' %-8d | %5.2f | %5.2f%s\n', N, sigPA / NREP, sigL / NREP, star)]; %#ok<AGROW>
|
s = [s sprintf(' %-8d | %5.2f | %5.2f%s\n', N, sigPA / NREP, sigL / NREP, star)];
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
s = [s sprintf(['\nRead the per-animal column as the honest power; the LME column matches the\n' ...
|
s = [s sprintf('\nRead per-animal as the honest power; LME matches the paper''s power code (optimistic).\n')];
|
||||||
'paper''s power code (anova interaction p, observation-level DF) and is optimistic.\n'])];
|
localFinish(s, here, suffix);
|
||||||
|
|
||||||
localFinish(s, here);
|
|
||||||
warning(warnState);
|
warning(warnState);
|
||||||
|
end
|
||||||
|
|
||||||
% ---------------------------------------------------------------- helpers
|
% ---------------------------------------------------------------- helpers
|
||||||
function localFinish(s, here)
|
function localFinish(s, here, suffix)
|
||||||
fprintf('%s', s);
|
fprintf('%s', s);
|
||||||
fid = fopen(fullfile(here, 'logpower_result.txt'), 'w');
|
fid = fopen(fullfile(here, ['logpower_result' suffix '.txt']), 'w');
|
||||||
fprintf(fid, '%s', s); fclose(fid);
|
fprintf(fid, '%s', s); fclose(fid);
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
% Variation learning-curve plot, in the style of the paper:
|
% Variation learning-curve plots, in the style of the paper:
|
||||||
% "Lines indicate mean (and SEM) across animals in the anodal (red) and
|
% "Lines indicate mean (and SEM) across animals in the anodal (red) and
|
||||||
% control (blue) groups."
|
% control (blue) groups."
|
||||||
% Plots mean +/- SEM successful reaches per training day for the treatment /
|
% Produces TWO figures from this folder's data.csv:
|
||||||
% anodal group (stim = 1, red) and the control group (stim = 0, blue), reading
|
% learning_curve.png # successes (count) per training day
|
||||||
% this folder's data.csv and saving learning_curve.png. The per-group N is read
|
% learning_curve_rate.png success rate (success/attempts) per training day
|
||||||
% from the data (each variation pools different groups), so the legend shows the
|
% anodal / treatment = stim 1 (red); control = stim 0 (blue). Per-group N is
|
||||||
% actual counts. Training day is 1-indexed (our day 0 = the paper's "Day 1").
|
% 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"
|
% Run: matlab -batch "plotcurve"
|
||||||
% (Copy of analysis/matlab/variation_plot.m; see make_variation_plot.m.)
|
% (Copy of analysis/matlab/variation_plot.m; see make_variation_plot.m.)
|
||||||
|
|
||||||
@@ -14,15 +15,20 @@ if isempty(here); here = pwd; end
|
|||||||
vname = regexprep(here, '.*[/\\]', '');
|
vname = regexprep(here, '.*[/\\]', '');
|
||||||
|
|
||||||
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
||||||
days = unique(D.day); % 0-indexed
|
days = unique(D.day);
|
||||||
xd = days + 1; % plot as 1-indexed training day (paper axis)
|
xd = days + 1; % plot as 1-indexed training day (paper axis)
|
||||||
|
|
||||||
red = [0.85 0.10 0.10];
|
red = [0.85 0.10 0.10];
|
||||||
blue = [0.10 0.30 0.85];
|
blue = [0.10 0.30 0.85];
|
||||||
|
|
||||||
[Ma, Sa, na] = localCurve(D, 1, days); % anodal / treatment (stim = 1)
|
localPlot(D, days, xd, 'count', '# successes', ...
|
||||||
[Mc, Sc, nc] = localCurve(D, 0, days); % control (stim = 0)
|
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]);
|
fig = figure('Visible', 'off', 'Color', 'w', 'Position', [100 100 560 460]);
|
||||||
hold on
|
hold on
|
||||||
e1 = errorbar(xd, Ma, Sa, '-o', 'Color', red, 'MarkerFaceColor', red, 'LineWidth', 2);
|
e1 = errorbar(xd, Ma, Sa, '-o', 'Color', red, 'MarkerFaceColor', red, 'LineWidth', 2);
|
||||||
@@ -30,26 +36,30 @@ e2 = errorbar(xd, Mc, Sc, '-o', 'Color', blue, 'MarkerFaceColor', blue, 'LineWid
|
|||||||
hold off
|
hold off
|
||||||
legend([e1 e2], {sprintf('anodal, N = %d', na), sprintf('control, N = %d', nc)}, ...
|
legend([e1 e2], {sprintf('anodal, N = %d', na), sprintf('control, N = %d', nc)}, ...
|
||||||
'Location', 'northwest', 'Box', 'off');
|
'Location', 'northwest', 'Box', 'off');
|
||||||
xlabel('training day');
|
xlabel('training day'); ylabel(ylab);
|
||||||
ylabel('# successes');
|
|
||||||
title(vname, 'Interpreter', 'none');
|
title(vname, 'Interpreter', 'none');
|
||||||
set(gca, 'XTick', xd, 'FontName', 'Arial', 'FontSize', 13, 'LineWidth', 1.5, 'Box', 'off');
|
set(gca, 'XTick', xd, 'FontName', 'Arial', 'FontSize', 13, 'LineWidth', 1.5, 'Box', 'off');
|
||||||
|
xtickangle(90); % vertical x-axis tick labels
|
||||||
outFile = fullfile(here, 'learning_curve.png');
|
|
||||||
exportgraphics(fig, outFile, 'Resolution', 150);
|
exportgraphics(fig, outFile, 'Resolution', 150);
|
||||||
close(fig);
|
close(fig);
|
||||||
fprintf('%s: wrote learning_curve.png (anodal N=%d, control N=%d)\n', vname, na, nc);
|
fprintf('%s: wrote %s (anodal N=%d, control N=%d)\n', vname, outFile, na, nc);
|
||||||
|
end
|
||||||
|
|
||||||
% ------------------------------------------------------------------ helper
|
function [M, S, n] = localCurve(D, stimVal, days, metric)
|
||||||
function [M, S, n] = localCurve(D, stimVal, days)
|
%LOCALCURVE Per-day mean and SEM across the animals in a group, for a metric.
|
||||||
%LOCALCURVE Per-day mean and SEM of successes across the animals in a group.
|
|
||||||
subs = unique(D.subject(D.stim == stimVal));
|
subs = unique(D.subject(D.stim == stimVal));
|
||||||
n = numel(subs);
|
n = numel(subs);
|
||||||
X = nan(numel(days), n);
|
X = nan(numel(days), n);
|
||||||
for j = 1:n
|
for j = 1:n
|
||||||
for i = 1:numel(days)
|
for i = 1:numel(days)
|
||||||
r = D.subject == subs(j) & D.day == days(i);
|
r = D.subject == subs(j) & D.day == days(i);
|
||||||
if any(r); X(i, j) = mean(D.success(r)); end
|
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
|
||||||
end
|
end
|
||||||
M = mean(X, 2, 'omitnan');
|
M = mean(X, 2, 'omitnan');
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
POWER SIMULATION -- unmerge_d6_13
|
POWER SIMULATION -- unmerge_d6_13 [metric: # successes (count)]
|
||||||
==============================================================================
|
==============================================================================
|
||||||
model: behavior ~ stim + day + stim:day + (1|rat) (success COUNT; day within-window)
|
model: behavior ~ stim + day + stim:day + (1|rat) (behavior = # successes (count); day within-window)
|
||||||
observed groups: stim n=3, control n=2 nrep=120, alpha=0.05
|
observed groups: stim n=3, control n=2 nrep=120, alpha=0.05
|
||||||
ground truth: stim:day=+2.27/day, rat SD=7.12, residual SD=7.73, days=8
|
ground truth: stim:day=+2.267/day, rat SD=7.117, residual SD=7.733, days=8
|
||||||
|
|
||||||
true stim:day interaction = +2.27 (100% of observed)
|
true stim:day interaction = +2.267 (100% of observed)
|
||||||
N/group | per-animal power | LME power
|
N/group | per-animal power | LME power
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
3 | 0.46 | 0.62 <- observed
|
3 | 0.46 | 0.62 <- observed
|
||||||
@@ -15,7 +15,7 @@ ground truth: stim:day=+2.27/day, rat SD=7.12, residual SD=7.73, days=8
|
|||||||
16 | 1.00 | 1.00
|
16 | 1.00 | 1.00
|
||||||
24 | 1.00 | 1.00
|
24 | 1.00 | 1.00
|
||||||
|
|
||||||
true stim:day interaction = +1.13 (50% of observed)
|
true stim:day interaction = +1.133 (50% of observed)
|
||||||
N/group | per-animal power | LME power
|
N/group | per-animal power | LME power
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
3 | 0.12 | 0.20 <- observed
|
3 | 0.12 | 0.20 <- observed
|
||||||
@@ -25,5 +25,4 @@ ground truth: stim:day=+2.27/day, rat SD=7.12, residual SD=7.73, days=8
|
|||||||
16 | 0.72 | 0.74
|
16 | 0.72 | 0.74
|
||||||
24 | 0.91 | 0.93
|
24 | 0.91 | 0.93
|
||||||
|
|
||||||
Read the per-animal column as the honest power. At the observed N this study
|
Read the per-animal column as the honest power; LME is optimistic (obs-level DF).
|
||||||
is typically underpowered; per-animal power reaches ~0.8 only at larger N.
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
==============================================================================
|
||||||
|
POWER SIMULATION -- unmerge_d6_13 [metric: success RATE]
|
||||||
|
==============================================================================
|
||||||
|
model: behavior ~ stim + day + stim:day + (1|rat) (behavior = success RATE; day within-window)
|
||||||
|
observed groups: stim n=3, control n=2 nrep=120, alpha=0.05
|
||||||
|
ground truth: stim:day=+0.01216/day, rat SD=0.04815, residual SD=0.05028, days=8
|
||||||
|
|
||||||
|
true stim:day interaction = +0.01216 (100% of observed)
|
||||||
|
N/group | per-animal power | LME power
|
||||||
|
------------------------------------------
|
||||||
|
3 | 0.32 | 0.47 <- observed
|
||||||
|
5 | 0.63 | 0.76
|
||||||
|
8 | 0.84 | 0.88
|
||||||
|
12 | 0.91 | 0.96
|
||||||
|
16 | 1.00 | 1.00
|
||||||
|
24 | 1.00 | 1.00
|
||||||
|
|
||||||
|
true stim:day interaction = +0.006078 (50% of observed)
|
||||||
|
N/group | per-animal power | LME power
|
||||||
|
------------------------------------------
|
||||||
|
3 | 0.07 | 0.17 <- observed
|
||||||
|
5 | 0.23 | 0.28
|
||||||
|
8 | 0.27 | 0.35
|
||||||
|
12 | 0.39 | 0.40
|
||||||
|
16 | 0.48 | 0.58
|
||||||
|
24 | 0.83 | 0.84
|
||||||
|
|
||||||
|
Read the per-animal column as the honest power; LME is optimistic (obs-level DF).
|
||||||
@@ -1,45 +1,49 @@
|
|||||||
% Variation power simulation -- Monte-Carlo power for the paper's stim x day
|
% Variation power simulation -- Monte-Carlo power for the paper's stim x day
|
||||||
% interaction, using THIS folder's data as the ground truth.
|
% interaction, using THIS folder's data as the ground truth, for BOTH metrics:
|
||||||
|
% metric = count : behavior = # successes -> power_result.txt
|
||||||
|
% metric = rate : behavior = success / attempts -> power_result_rate.txt
|
||||||
%
|
%
|
||||||
% Ground truth: fitlme(behavior ~ stim + day + stim:day + (1|rat)) on data.csv
|
% Ground truth: fitlme(behavior ~ stim + day + stim:day + (1|rat)) on data.csv
|
||||||
% (success COUNT; day within-window). Its fixed effects, per-rat intercept SD,
|
% (day within-window). Its fixed effects, per-rat intercept SD, and residual SD
|
||||||
% and residual SD generate NREP synthetic datasets at each rats-per-group N and
|
% generate NREP synthetic datasets at each rats-per-group N and each true-effect
|
||||||
% each true-effect multiplier (1 = observed slope, 0.5 = half). Each dataset is
|
% multiplier (1 = observed, 0.5 = half). Each is scored at alpha=0.05 by:
|
||||||
% scored at alpha = 0.05 two ways:
|
% per-animal : Welch t on per-rat behavior~day slopes (cluster-honest power)
|
||||||
% per-animal : Welch t on per-rat behavior~day slopes (cluster-honest -- the
|
|
||||||
% honest power, matching the random-slope / per-animal inference)
|
|
||||||
% LME : the fitlme stim:day p (observation-level DF -- optimistic)
|
% LME : the fitlme stim:day p (observation-level DF -- optimistic)
|
||||||
% Writes power_result.txt beside this script. Run: matlab -batch "powersim"
|
% Writes power_result[_rate].txt. Run: matlab -batch "powersim"
|
||||||
% (Copy of analysis/matlab/variation_power.m; see make_variation_power.m.)
|
% (Copy of analysis/matlab/variation_power.m; see make_variation_power.m.)
|
||||||
|
|
||||||
here = fileparts(mfilename('fullpath'));
|
here = fileparts(mfilename('fullpath'));
|
||||||
if isempty(here); here = pwd; end
|
if isempty(here); here = pwd; end
|
||||||
vname = regexprep(here, '.*[/\\]', '');
|
vname = regexprep(here, '.*[/\\]', '');
|
||||||
|
|
||||||
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
||||||
|
|
||||||
|
localPower(D, 'count', here, vname);
|
||||||
|
localPower(D, 'rate', here, vname);
|
||||||
|
|
||||||
|
% ---------------------------------------------------------------- per metric
|
||||||
|
function localPower(D, metric, here, vname)
|
||||||
|
NS = [3 5 8 12 16 24]; EFFMULS = [1 0.5]; NREP = 120;
|
||||||
FORMULA = 'behavior ~ stim + day + stim:day + (1|rat)';
|
FORMULA = 'behavior ~ stim + day + stim:day + (1|rat)';
|
||||||
NS = [3 5 8 12 16 24];
|
if strcmp(metric, 'rate')
|
||||||
EFFMULS = [1 0.5];
|
D = D(D.total > 0, :); beh = D.success ./ D.total; mlabel = 'success RATE'; suffix = '_rate';
|
||||||
NREP = 120;
|
else
|
||||||
|
beh = D.success; mlabel = '# successes (count)'; suffix = '';
|
||||||
warnState = warning('off', 'all');
|
end
|
||||||
rng(1);
|
warnState = warning('off', 'all'); rng(1);
|
||||||
|
|
||||||
day0 = min(D.day);
|
day0 = min(D.day);
|
||||||
tbl0 = table(D.success, D.day - day0, double(D.stim), categorical(D.subject), ...
|
tbl0 = table(beh, D.day - day0, double(D.stim), categorical(D.subject), ...
|
||||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||||
|
|
||||||
nStim = numel(unique(D.subject(D.stim == 1)));
|
nStim = numel(unique(D.subject(D.stim == 1)));
|
||||||
nCtrl = numel(unique(D.subject(D.stim == 0)));
|
nCtrl = numel(unique(D.subject(D.stim == 0)));
|
||||||
|
|
||||||
bar = repmat('=', 1, 78);
|
bar = repmat('=', 1, 78);
|
||||||
s = sprintf('%s\nPOWER SIMULATION -- %s\n%s\n', bar, vname, bar);
|
s = sprintf('%s\nPOWER SIMULATION -- %s [metric: %s]\n%s\n', bar, vname, mlabel, bar);
|
||||||
s = [s sprintf('model: %s (success COUNT; day within-window)\n', FORMULA)];
|
s = [s sprintf('model: %s (behavior = %s; day within-window)\n', FORMULA, mlabel)];
|
||||||
s = [s sprintf('observed groups: stim n=%d, control n=%d nrep=%d, alpha=0.05\n', nStim, nCtrl, NREP)];
|
s = [s sprintf('observed groups: stim n=%d, control n=%d nrep=%d, alpha=0.05\n', nStim, nCtrl, NREP)];
|
||||||
|
|
||||||
if nStim < 2 || nCtrl < 2 || numel(unique(tbl0.day)) < 2
|
if nStim < 2 || nCtrl < 2 || numel(unique(tbl0.day)) < 2
|
||||||
s = [s sprintf('\nInsufficient data for a power simulation (need >=2 animals/group and >=2 days).\n')];
|
s = [s sprintf('\nInsufficient data for a power simulation.\n')];
|
||||||
localFinish(s, here); warning(warnState); return
|
localFinish(s, here, suffix); warning(warnState); return
|
||||||
end
|
end
|
||||||
|
|
||||||
lme = fitlme(tbl0, FORMULA);
|
lme = fitlme(tbl0, FORMULA);
|
||||||
@@ -49,14 +53,13 @@ bDay = be(strcmp(cn, 'day')); bInt = be(strcmp(cn, 'day:stim'));
|
|||||||
psi = covarianceParameters(lme); sRat = sqrt(psi{1}); sRes = sqrt(lme.MSE);
|
psi = covarianceParameters(lme); sRat = sqrt(psi{1}); sRes = sqrt(lme.MSE);
|
||||||
days = (0:max(tbl0.day))';
|
days = (0:max(tbl0.day))';
|
||||||
|
|
||||||
s = [s sprintf('ground truth: stim:day=%+.2f/day, rat SD=%.2f, residual SD=%.2f, days=%d\n', ...
|
s = [s sprintf('ground truth: stim:day=%+.4g/day, rat SD=%.4g, residual SD=%.4g, days=%d\n', ...
|
||||||
bInt, sRat, sRes, numel(days))];
|
bInt, sRat, sRes, numel(days))];
|
||||||
|
|
||||||
for eMul = EFFMULS
|
for eMul = EFFMULS
|
||||||
bI = bInt * eMul;
|
bI = bInt * eMul;
|
||||||
s = [s sprintf('\n true stim:day interaction = %+.2f (%.0f%% of observed)\n', bI, eMul * 100)]; %#ok<AGROW>
|
s = [s sprintf('\n true stim:day interaction = %+.4g (%.0f%% of observed)\n', bI, eMul * 100)];
|
||||||
s = [s sprintf(' %-8s | per-animal power | LME power\n', 'N/group')]; %#ok<AGROW>
|
s = [s sprintf(' %-8s | per-animal power | LME power\n %s\n', 'N/group', repmat('-', 1, 42))];
|
||||||
s = [s sprintf(' %s\n', repmat('-', 1, 42))]; %#ok<AGROW>
|
|
||||||
for N = NS
|
for N = NS
|
||||||
sigPA = 0; sigL = 0;
|
sigPA = 0; sigL = 0;
|
||||||
for r = 1:NREP
|
for r = 1:NREP
|
||||||
@@ -70,20 +73,18 @@ for eMul = EFFMULS
|
|||||||
end
|
end
|
||||||
star = '';
|
star = '';
|
||||||
if N == nStim || N == nCtrl; star = ' <- observed'; end
|
if N == nStim || N == nCtrl; star = ' <- observed'; end
|
||||||
s = [s sprintf(' %-8d | %5.2f | %5.2f%s\n', N, sigPA / NREP, sigL / NREP, star)]; %#ok<AGROW>
|
s = [s sprintf(' %-8d | %5.2f | %5.2f%s\n', N, sigPA / NREP, sigL / NREP, star)];
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
s = [s sprintf('\nRead the per-animal column as the honest power; LME is optimistic (obs-level DF).\n')];
|
||||||
s = [s sprintf(['\nRead the per-animal column as the honest power. At the observed N this study\n' ...
|
localFinish(s, here, suffix);
|
||||||
'is typically underpowered; per-animal power reaches ~0.8 only at larger N.\n'])];
|
|
||||||
|
|
||||||
localFinish(s, here);
|
|
||||||
warning(warnState);
|
warning(warnState);
|
||||||
|
end
|
||||||
|
|
||||||
% ---------------------------------------------------------------- helpers
|
% ---------------------------------------------------------------- helpers
|
||||||
function localFinish(s, here)
|
function localFinish(s, here, suffix)
|
||||||
fprintf('%s', s);
|
fprintf('%s', s);
|
||||||
fid = fopen(fullfile(here, 'power_result.txt'), 'w');
|
fid = fopen(fullfile(here, ['power_result' suffix '.txt']), 'w');
|
||||||
fprintf(fid, '%s', s); fclose(fid);
|
fprintf(fid, '%s', s); fclose(fid);
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
VARIATION: unmerge_d6_13
|
VARIATION: unmerge_d6_13 [metric: success COUNT]
|
||||||
==============================================================================
|
==============================================================================
|
||||||
model: behavior ~ stim + day + stim:day + (1|rat) (behavior = success COUNT)
|
model: behavior ~ stim + day + stim:day + (1|rat) (behavior = success COUNT)
|
||||||
day = training day within window (0 = first analyzed day)
|
day = training day within window (0 = first analyzed day)
|
||||||
@@ -60,9 +60,9 @@ effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
|||||||
stim x day (interaction) t(30)= 1.71 F(1)= 2.933 p=0.0971 p=0.09701 (df=30)
|
stim x day (interaction) t(30)= 1.71 F(1)= 2.933 p=0.0971 p=0.09701 (df=30)
|
||||||
day (learning) t(30)= 1.61 F(1)= 2.598 p=0.1175 p=0.1174 (df=30)
|
day (learning) t(30)= 1.61 F(1)= 2.598 p=0.1175 p=0.1174 (df=30)
|
||||||
stim (main, window start) t(30)= 1.69 F(1)= 2.856 p=0.1014 p=0.1269 (df=9)
|
stim (main, window start) t(30)= 1.69 F(1)= 2.856 p=0.1014 p=0.1269 (df=9)
|
||||||
interaction 95% CI: [-0.44, +4.97]
|
interaction 95% CI: [-0.4363, +4.97]
|
||||||
HONEST LME (per-animal random slope, day|rat): interaction F(1,13.9)=2.48, p=0.1382
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,13.9)=2.48, p=0.1382
|
||||||
(Satterthwaite DF ~= residual on this random-intercept model; the random-slope
|
(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.)
|
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.0971, slope diff=+2.27)
|
INTERPRETATION: stim x day interaction n.s. -- slopes parallel (no differential learning rate) (p=0.0971, slope diff=+2.267)
|
||||||
Paper (N=24): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
Paper (N=24, count): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
==============================================================================
|
||||||
|
VARIATION: unmerge_d6_13 [metric: success RATE (success/attempts)]
|
||||||
|
==============================================================================
|
||||||
|
model: behavior ~ stim + day + stim:day + (1|rat) (behavior = success RATE (success/attempts))
|
||||||
|
day = training day within window (0 = first analyzed day)
|
||||||
|
treatment (stim=1): Electrode-Box-B2
|
||||||
|
control (stim=0): Electrode-Box-A2
|
||||||
|
N = 5 rats, 34 sessions raw day coverage: treat 6..13, control 6..13
|
||||||
|
(equal day coverage over this window)
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
FULL MODEL SUMMARY -- fitlme
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
Linear mixed-effects model fit by ML
|
||||||
|
|
||||||
|
Model information:
|
||||||
|
Number of observations 34
|
||||||
|
Fixed effects coefficients 4
|
||||||
|
Random effects coefficients 5
|
||||||
|
Covariance parameters 2
|
||||||
|
|
||||||
|
Formula:
|
||||||
|
behavior ~ 1 + day*stim + (1 | rat)
|
||||||
|
|
||||||
|
Model fit statistics:
|
||||||
|
AIC BIC LogLikelihood Deviance
|
||||||
|
-85.035 -75.877 48.518 -97.035
|
||||||
|
|
||||||
|
Fixed effects coefficients (95% CIs):
|
||||||
|
Name Estimate SE tStat DF pValue
|
||||||
|
{'(Intercept)'} 0.53562 0.0416 12.875 30 9.415e-14
|
||||||
|
{'day' } 0.0032833 0.0069042 0.47554 30 0.63784
|
||||||
|
{'stim' } 0.10269 0.053584 1.9164 30 0.06488
|
||||||
|
{'day:stim' } 0.012156 0.0086128 1.4114 30 0.16841
|
||||||
|
|
||||||
|
|
||||||
|
Lower Upper
|
||||||
|
0.45066 0.62058
|
||||||
|
-0.010817 0.017384
|
||||||
|
-0.0067429 0.21212
|
||||||
|
-0.0054334 0.029746
|
||||||
|
|
||||||
|
Random effects covariance parameters (95% CIs):
|
||||||
|
Group: rat (5 Levels)
|
||||||
|
Name1 Name2 Type Estimate
|
||||||
|
{'(Intercept)'} {'(Intercept)'} {'std'} 0.048154
|
||||||
|
|
||||||
|
|
||||||
|
Lower Upper
|
||||||
|
0.023654 0.098032
|
||||||
|
|
||||||
|
Group: Error
|
||||||
|
Name Estimate Lower Upper
|
||||||
|
{'Res Std'} 0.050284 0.038906 0.064989
|
||||||
|
|
||||||
|
|
||||||
|
effect t(df) / F(df1) p (resid) Satterthwaite: p (df)
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
stim x day (interaction) t(30)= 1.41 F(1)= 1.992 p=0.1684 p=0.1684 (df=30)
|
||||||
|
day (learning) t(30)= 0.48 F(1)= 0.226 p=0.6378 p=0.6378 (df=30)
|
||||||
|
stim (main, window start) t(30)= 1.92 F(1)= 3.673 p=0.06488 p=0.09052 (df=8)
|
||||||
|
interaction 95% CI: [-0.005433, +0.02975]
|
||||||
|
HONEST LME (per-animal random slope, day|rat): interaction F(1,2.3)=0.03, p=0.8749
|
||||||
|
(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.1684, slope diff=+0.01216)
|
||||||
|
Paper (N=24, count): interaction t(227)=2.68, F(1)=7.12, p=0.008.
|
||||||