Adds tdcs_paper_lme replicating the paper's exact formula/variable names (paper_* switch cases): mergeA2 reproduces the interaction F(1)=7.09 vs 7.12, p=0.009 vs 0.008, with a coverage-confound warning. Rewrites tdcs_power_sim to fit that same formula (interaction term canonicalized to day:stim). Adds a replication test. Suite 37/37. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7.2 KiB
tDCS reaching study — MATLAB GLM port
This directory is a MATLAB port of the Python tDCS GLM analysis
(analysis/tdcs_glm.py, documented in analysis/METHODS.md), driven by raw
app-export matrices rather than the pre-joined tdcs_reach_data.csv. It
reproduces the same three models (count level, rate level, learning rate) and
the same H2 anchor check / unknown-condition classification, but replaces the
Python's population-average GEE with a fitglme GLMM (Poisson/Binomial,
subject random intercept) — see "GLMM vs GEE" below.
Requirements
-
MATLAB R2025b + Statistics and Machine Learning Toolbox.
-
A license activated at
~/matlab/licenses/license.dat(already in place in this environment). If MATLAB ever reports it is unlicensed, activate with:~/matlab/bin/activate_matlab.sh
Layout
tdcs_config.m— curated 12-animal group map, reference group, merge maps.tdcs_load_data.m— parsesraw/raw_success.csvandraw/raw_total.csv(app-export matrix format), joins them, and applies the curated group map.tdcs_scenario_data.m— builds one of the 6 scenarios (merge map + day window), and saves the scenario table toderived/<scenario>.csv.tdcs_models.m— fits the count/rate/learning GLMEs, the per-animal Welch/Mann-Whitney tests, the H2 anchor check, and (unmerged scenarios only) the unknown-condition classification.tdcs_report.m— prints descriptives, the raw GLME fixed-effects tables, and an interpretation section (H2 verdict, per-animal p-values, classification, caveats); also writesresults/<scenario>.txt.tdcs_glm.m— the entry point:tdcs_glm(scenario)runstdcs_scenario_data→tdcs_models→tdcs_reportfor one named scenario, via an explicitswitch/caseover all 6 scenario names.run_all.m— runs all 6 scenarios in one script.run_tests.m— runs thematlab.unittestsuite intests/and exits nonzero on any failure.tests/—matlab.unittesttest classes (tLoadData,tScenarioData,tModels) with golden values cross-checked against the Python.raw/— pre-generated app-export CSVs (input; not modified by this code).derived/— scenario tables written bytdcs_scenario_data(generated).results/— report text files written bytdcs_report/tdcs_glm(generated).
Running
Run one scenario (valid names: unmerged_full, unmerged_d0_10,
mergeA2_full, mergeA2_d0_10, mergeB2_full, mergeB2_d0_10):
cd analysis/matlab
~/matlab/bin/matlab -batch "addpath(pwd); tdcs_glm('mergeB2_full')"
This prints the full report to the console and writes it to
results/mergeB2_full.txt.
Run all 6 scenarios:
~/matlab/bin/matlab -batch "addpath(pwd); run_all"
Run the test suite:
~/matlab/bin/matlab -batch "addpath(pwd); run_tests"
(run_tests calls error(...) on any failure, so the matlab -batch
process exits with a nonzero status — suitable for CI.)
Validation against the Python
Per-animal statistics (per-subject Welch t-test / one-sided Mann-Whitney U,
Box-B2 vs Box-A2) are pure stats independent of the GLM choice, and were
matched to the Python's golden values to 3 decimals across all 6 scenarios
(see docs/superpowers/plans/2026-07-20-matlab-tdcs-analysis.md, Task 3).
GLMM vs GEE (read this before comparing numbers to the Python)
The Python reference uses GEE (population-average effects, exchangeable
working correlation, robust/sandwich SEs) for the count and learning-rate
models, and a cluster-robust Binomial GLM for the rate model. This MATLAB
port instead uses GLMM (fitglme, subject-specific effects via a
(1|subject) random intercept, Laplace/PL approximation) for all three
models, because MATLAB's Statistics and Machine Learning Toolbox has no GEE
implementation.
GLMM and GEE estimate related but distinct quantities (subject-specific vs. population-average effects), so exact coefficients, ratios, and p-values will differ between the two implementations — sometimes by a fair amount for the small, unbalanced groups in this study. What should agree is the interpretation: the direction of the H2 anchor effect, which unknown condition classifies as A2-like/B2-like/ambiguous, and whether the learning rates differ across groups. Treat the MATLAB numbers as a sensitivity check on the Python's conclusions, not as a numerically-identical reproduction.
Scope vs. the Python
The MATLAB port reproduces the Python's three core models (count, rate,
learning-rate), per-animal tests, unknown-group classification, and the H2
verdict (read off the main model's A2-vs-B2 term). The Python's two auxiliary
sensitivity sections — report_anchor_only (a two-anchor-only refit) and
report_mixed (a Bayesian MAP random-intercept model) — are intentionally
not ported: the anchor-only refit is closely tracked by the main-model H2
term, and MATLAB's native fitglme already is the random-intercept model.
Phased learning analysis and power
-
Phased days × tDCS LME — the same
success ~ day*tDCS + (1|subject)model refit within learning phases (0–5, 6–10, 6–13), Box-B2 vs Box-A2, with per-phase slopes and the interaction 95% CI:matlab -batch "tdcs_glm('phase_mergeA2')" # or phase_unmerged / phase_mergeB2The Box-B2 faster-acquisition signal sits in the early (0–5) phase; late phases converge. The interaction p/CI use fitlme observation-level DF and are anticonservative at these small n (see power analysis).
-
Monte-Carlo power — power to detect the early-phase interaction, scored with the cluster-honest per-animal test and the LME test, across subjects-per-group and effect sizes:
matlab -batch "run_power" # default: mergeA2, phase 0–5Result: at our n (3–5/group) honest power is ~0.3–0.7 even at the observed effect; ~8/group reaches ~80–90% if the effect is as large as observed, ~20/group if it is half that. The LME test is anticonservative only at small n and converges to the honest test by n≈12.
Figure and data export
- Figure — two-panel learning figure (curves + per-phase slopes, Box-B2 vs Box-A2),
written to
results/figure_learning.png:matlab -batch "make_figure" - Export all data variations to CSV — organized under
export/:Producesmatlab -batch "run_export"curated_all.csv,scenarios/<merge>_<window>.csv(9),anchors/<merge>_B2vsA2.csv(3, with the binary tDCS factor), andphases/<merge>_phase_<lo>-<hi>.csv(9), plus a MANIFEST.
Paper replication (verbatim formula)
The paper's model, fitlme(tbl, 'behavior ~ stim + day + stim:day + (1|rat)'), is
replicated verbatim (behavior = successful reaches, stim = tDCS [Box-B2=1 vs Box-A2=0],
rat = subject) over the full training range:
matlab -batch "tdcs_glm('paper_mergeA2')" # or paper_unmerged / paper_mergeB2
paper_mergeA2 reproduces the paper's interaction (F(1)=7.09 vs 7.12, p=0.009 vs 0.008); it
also warns that the full-range interaction is confounded by unequal day coverage (Box-A2 ends
~day 13) — see the _d0_13 and phased analyses. The Monte-Carlo power analysis (run_power,
tdcs_power_sim) uses this same behavior ~ stim + day + stim:day + (1|rat) model.