analysis(matlab): add per-variation power simulation (powersim.m + power_result.txt)
Add variation_power.m (self-contained Monte-Carlo power for the paper's stim x day interaction, using each folder's own data as ground truth; scores per-animal cluster-honest power + LME power across N=[3..24] and effect multipliers 1/0.5) and make_variation_power.m, which drops powersim.m into every variations/<name>/ folder and runs it, writing power_result.txt beside the existing data.csv/analyze.m/result.txt. Named powersim (not power) to avoid shadowing the MATLAB builtin. All 28 folders processed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
% Variation power simulation -- Monte-Carlo power for the paper's stim x day
|
||||
% interaction, using THIS folder's data as the ground truth.
|
||||
%
|
||||
% 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,
|
||||
% and residual SD generate NREP synthetic datasets at each rats-per-group N and
|
||||
% each true-effect multiplier (1 = observed slope, 0.5 = half). Each dataset is
|
||||
% scored at alpha = 0.05 two ways:
|
||||
% 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)
|
||||
% Writes power_result.txt beside this script. Run: matlab -batch "powersim"
|
||||
% (Copy of analysis/matlab/variation_power.m; see make_variation_power.m.)
|
||||
|
||||
here = fileparts(mfilename('fullpath'));
|
||||
if isempty(here); here = pwd; end
|
||||
vname = regexprep(here, '.*[/\\]', '');
|
||||
|
||||
D = readtable(fullfile(here, 'data.csv'), 'TextType', 'string');
|
||||
FORMULA = 'behavior ~ stim + day + stim:day + (1|rat)';
|
||||
NS = [3 5 8 12 16 24];
|
||||
EFFMULS = [1 0.5];
|
||||
NREP = 120;
|
||||
|
||||
warnState = warning('off', 'all');
|
||||
rng(1);
|
||||
|
||||
day0 = min(D.day);
|
||||
tbl0 = table(D.success, D.day - day0, double(D.stim), categorical(D.subject), ...
|
||||
'VariableNames', {'behavior', 'day', 'stim', 'rat'});
|
||||
|
||||
nStim = numel(unique(D.subject(D.stim == 1)));
|
||||
nCtrl = numel(unique(D.subject(D.stim == 0)));
|
||||
|
||||
bar = repmat('=', 1, 78);
|
||||
s = sprintf('%s\nPOWER SIMULATION -- %s\n%s\n', bar, vname, bar);
|
||||
s = [s sprintf('model: %s (success COUNT; day within-window)\n', FORMULA)];
|
||||
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
|
||||
s = [s sprintf('\nInsufficient data for a power simulation (need >=2 animals/group and >=2 days).\n')];
|
||||
localFinish(s, here); warning(warnState); return
|
||||
end
|
||||
|
||||
lme = fitlme(tbl0, FORMULA);
|
||||
cn = lme.CoefficientNames; be = lme.fixedEffects;
|
||||
b0 = be(strcmp(cn, '(Intercept)')); bStim = be(strcmp(cn, 'stim'));
|
||||
bDay = be(strcmp(cn, 'day')); bInt = be(strcmp(cn, 'day:stim'));
|
||||
psi = covarianceParameters(lme); sRat = sqrt(psi{1}); sRes = sqrt(lme.MSE);
|
||||
days = (0:max(tbl0.day))';
|
||||
|
||||
s = [s sprintf('ground truth: stim:day=%+.2f/day, rat SD=%.2f, residual SD=%.2f, days=%d\n', ...
|
||||
bInt, sRat, sRes, numel(days))];
|
||||
|
||||
for eMul = EFFMULS
|
||||
bI = bInt * eMul;
|
||||
s = [s sprintf('\n true stim:day interaction = %+.2f (%.0f%% of observed)\n', bI, eMul * 100)]; %#ok<AGROW>
|
||||
s = [s sprintf(' %-8s | per-animal power | LME power\n', 'N/group')]; %#ok<AGROW>
|
||||
s = [s sprintf(' %s\n', repmat('-', 1, 42))]; %#ok<AGROW>
|
||||
for N = NS
|
||||
sigPA = 0; sigL = 0;
|
||||
for r = 1:NREP
|
||||
tb = localSim(N, days, b0, bStim, bDay, bI, sRat, sRes);
|
||||
sigPA = sigPA + (localPAp(tb) < 0.05);
|
||||
try
|
||||
m = fitlme(tb, FORMULA); Cm = m.Coefficients;
|
||||
sigL = sigL + (Cm.pValue(strcmp(Cm.Name, 'day:stim')) < 0.05);
|
||||
catch
|
||||
end
|
||||
end
|
||||
star = '';
|
||||
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>
|
||||
end
|
||||
end
|
||||
|
||||
s = [s sprintf(['\nRead the per-animal column as the honest power. At the observed N this study\n' ...
|
||||
'is typically underpowered; per-animal power reaches ~0.8 only at larger N.\n'])];
|
||||
|
||||
localFinish(s, here);
|
||||
warning(warnState);
|
||||
|
||||
% ---------------------------------------------------------------- helpers
|
||||
function localFinish(s, here)
|
||||
fprintf('%s', s);
|
||||
fid = fopen(fullfile(here, 'power_result.txt'), 'w');
|
||||
fprintf(fid, '%s', s); fclose(fid);
|
||||
end
|
||||
|
||||
function tbl = localSim(N, days, b0, bStim, bDay, bI, sRat, sRes)
|
||||
nd = numel(days); rows = 2 * N * nd;
|
||||
rat = strings(rows, 1); day = zeros(rows, 1); stim = zeros(rows, 1); behavior = zeros(rows, 1);
|
||||
k = 0;
|
||||
for g = 0:1
|
||||
for sIdx = 1:N
|
||||
re = sRat * randn;
|
||||
rid = sprintf('g%d_r%d', g, sIdx);
|
||||
for d = 1:nd
|
||||
k = k + 1;
|
||||
rat(k) = rid; day(k) = days(d); stim(k) = g;
|
||||
behavior(k) = b0 + bStim * g + bDay * days(d) + bI * days(d) * g + re + sRes * randn;
|
||||
end
|
||||
end
|
||||
end
|
||||
tbl = table(categorical(rat), day, stim, behavior, ...
|
||||
'VariableNames', {'rat', 'day', 'stim', 'behavior'});
|
||||
end
|
||||
|
||||
function p = localPAp(tbl)
|
||||
rats = unique(tbl.rat); sl = zeros(numel(rats), 1); gr = zeros(numel(rats), 1);
|
||||
for i = 1:numel(rats)
|
||||
r = tbl.rat == rats(i);
|
||||
c = polyfit(tbl.day(r), tbl.behavior(r), 1); sl(i) = c(1);
|
||||
gr(i) = tbl.stim(find(r, 1));
|
||||
end
|
||||
[~, p] = ttest2(sl(gr == 1), sl(gr == 0), 'Vartype', 'unequal');
|
||||
end
|
||||
Reference in New Issue
Block a user