# tDCS reaching study — GLM methods This document explains the statistics in `tdcs_glm.py`: the models, the exact formulas, how each subject's progress is accounted for, and the caveats. ## The question Two **known** conditions anchor the performance scale and differ from each other: - **H2 (anchor check):** `Electrode-Box-B2` performs **better** than `Electrode-Box-A2`. Two **unknown** conditions are then **classified** against those anchors — for each of `Electrode-Box-A` and `Right-Electrode`, is it **A2-like** or **B2-like**? We do *not* assume either belongs to B2; each unknown is compared to *both* anchors, and the anchor it cannot be distinguished from is its likely class. There is also a **merged-assumption** mode (`--merge`): assume the two unknowns resolve as `Right-Electrode == Box-B2` and `Electrode-Box-A == Box-A2`, fold them into the anchors, and re-estimate everything with 4 subjects per group. ## Data and outcome - **Outcome:** `success` = successful reaches in a session (`analysis_summary.counts.Success`), a non-negative **count**. - **Attempts:** `total` = reach attempts in the session (`analysis_summary.total`); used as the denominator for the rate model. `success_rate == success / total`. - **Time:** `day` = the "# Days Reach" field (training day, 0..26). Analyses use days ≥ 0 (pre-training negative days and zero-attempt sessions are excluded; zero-attempt sessions are undefined for the rate model). - **Groups (subjects):** Naive (4), Box-A2 (3), Box-B2 (3), Box-A (1), Right-Electrode (1). Under `--merge`: Box-A2 (4), Box-B2 (4), Naive (4). **Provenance:** `tdcs_reach_data.csv` was reconstructed from the experiment database and verified cell-by-cell against the exported matrix (71/71 unambiguous cells on days 0–5 matched exactly). ## The three models All models use `Electrode-Box-B2` as the **reference** group, so each group term is that group's contrast *versus Box-B2*. `day_c` is the centered training day and `day_c2 = day_c²` captures the rise-then-plateau of the learning curve. ### (A) Level — count (primary) Poisson GEE on the success counts, clustered by subject: ``` success ~ C(group, Treatment('Electrode-Box-B2')) + day_c + day_c2 family = Poisson (log link) groups = subject # repeated-measures cluster cov_struct = Exchangeable # working within-subject correlation SE = robust (sandwich) ``` `exp(coef)` for a group term is an **incidence-rate ratio (IRR)**: expected successes relative to Box-B2. ### (B) Level — rate Binomial GLM on successes-out-of-attempts, with cluster-robust SEs by subject: ``` cbind(success, total - success) ~ C(group, Treatment('Electrode-Box-B2')) + day_c + day_c2 family = Binomial (logit link) cov_type = cluster (groups = subject) ``` `exp(coef)` is an **odds ratio** for a successful reach relative to Box-B2. This controls for differing numbers of attempts, so it answers "who is more accurate per attempt?" rather than "who attempts more?" ### (C) Learning rate Poisson GEE with a **group × day** interaction, to ask whether groups improve at different *rates* (not just different levels): ``` success ~ C(group, Treatment('Electrode-Box-B2')) * day_c + day_c2 ``` Each `group[T.X]:day_c` term is the difference in log-slope versus Box-B2; a joint Wald test asks whether *any* group's slope differs. Large p ⇒ parallel learning. ### Anchors-only model The A2-vs-B2 comparison, refit on **just the two anchor groups** so nothing else influences the shared day terms or the dispersion/correlation nuisance: ``` # subset to {Box-A2, Box-B2} success ~ C(group, Treatment('Electrode-Box-B2')) + day_c + day_c2 # count cbind(success, total-success) ~ C(group, ...) + day_c + day_c2 # rate ``` With B2 as reference the single group term is the A2-vs-B2 effect; H2 is a one-sided test that this coefficient is below zero. ## How each subject's progress is accounted for Two distinct pieces: 1. **Progress over training** — the `day_c + day_c2` fixed terms model the average learning curve, so groups are compared at comparable points in training rather than being confounded by *when* each was measured. 2. **Repeated measures / individual baselines** — each subject contributes many correlated sessions and has its own baseline. Handled two ways: - **Primary (GEE):** subject is the cluster; an exchangeable working correlation plus robust (sandwich) SEs give *population-average* group effects whose inference is valid under within-subject correlation and Poisson overdispersion. - **Sensitivity (mixed model):** a Poisson model with a **per-subject random intercept** (`(1 | subject)`) so each animal gets its own baseline level; the group effects are estimated after allowing for that individual variation. statsmodels has no frequentist Poisson GLMM, so this is the **MAP/Laplace** fit (`fit_vb` diverges on these large counts). Its p-values are approximate — read it as a direction/magnitude check that should agree with GEE. ## Classification logic (unknowns) For each unknown group, `diff_contrast` builds a linear contrast of that group against **each anchor** (both coded vs the B2 reference) and tests it: - indistinguishable from B2 **and** different from A2 → **B2-like** - indistinguishable from A2 **and** different from B2 → **A2-like** - indistinguishable from both → **ambiguous** (report the numerically nearer one) - different from both → **unlike both** ## Caveats - **Tiny groups.** Box-A2/B2 have 3 subjects, Naive 4, and each unknown has **n = 1 subject**. Classifying a one-subject condition is weak: "matches anchor X" means "not statistically distinguishable from X," **not** proof of equivalence. - **Single-cluster fragility.** Cluster-robust/GEE inference with one cluster in a group can produce artificially small SEs — most visibly the learning-rate interaction for the n=1 groups; do not read those p-values literally. - **Mixed-model confounding.** With a per-subject random intercept, a group made of one subject is partly confounded with that subject's random intercept, so its fixed effect is shrunk. - **Rate vs count.** "Success" alone is a count; the rate model (success/attempts) is the fairer accuracy comparison when attempt counts differ. ## Files and usage - `tdcs_glm.py` — the analysis (run it directly). - `tdcs_reach_data.csv` — the verified long-format data (`subject, group, day, success, total`). - `tdcs_learning_curves*.png` — per-group learning curves (count and rate panels). ``` python3 analysis/tdcs_glm.py # all training days, 5 groups python3 analysis/tdcs_glm.py --max-day 10 # restrict to days 0–10 python3 analysis/tdcs_glm.py --merge # assume Right==B2 and Box-A==A2 (3 groups) ``` Requires: pandas, numpy, scipy, statsmodels, matplotlib.