Group-level statistical analysis toolkit for source-localized EEG. It is the middle stage of a three-package pipeline:
source-localization ──► source-analytics ──► source-lightbox
(reconstruct sources) (stats + figures) (render the gallery)
source-localization turns raw EEG into per-subject
source reconstructions (ROI timeseries, whole-brain source estimates).
📖 Methods documentation:
docs/— the decisions behind the numbers, with primary-literature citations. Start with the aperiodic fit window and connectivity metrics. Build the site locally withuv run --no-project --with "mkdocs-material>=9.5,<10" mkdocs serve. source-analytics reads those reconstructions, runs group-level analyses (spectral, connectivity, cross-frequency, directed, …), and writes publication-quality statistics tables and figures.source-lightboxthen reads the same study config and the stat tables to build a browsable gallery.
Python handles orchestration, signal processing, and I/O. R handles the
linear-mixed-model statistics (lme4/lmerTest/emmeans) and ggplot2 figures. Vertex-
and sensor-map modules do their statistics in Python (cluster permutation) and use
R only for the markdown report. Python calls Rscript automatically — there is no
manual R step.
- Quickstart (after source-localization)
- Installation
- Core concepts — the mental model
- Input: the source-localization handoff
- Study configuration
- The CLI
- Analysis catalog — what exists
- Hypothesis testing
- Selecting metrics, bands & hypotheses
- Output structure
- Running a full study, in order
- Architecture
- Extending: add an analysis
- Reference documents
You have run source-localization and have a derivatives tree of per-subject
reconstructions. Three commands take you from there to results:
# 1. Scaffold a study config from the reconstruction directory.
# (--groups-from reuses the group mapping from the source-localization config.)
source-analytics init /path/to/localization/rest_roi \
--name "My Study" \
--groups-from /path/to/localization/study_config.yaml > study.yaml
# 2. Edit study.yaml — declare groups, bands, the design/hypotheses, and which
# analyses to run under each paradigm (see "Study configuration").
# 3. Sanity-check config + subject discovery before any long run.
source-analytics validate --study study.yaml
# 4. Run an analysis. --paradigm picks the block under `paradigms:` in the config.
source-analytics run --study study.yaml --paradigm resting --analysis roi_psdEach analysis writes a self-contained output directory with a data/, tables/,
figures/, and an ANALYSIS_SUMMARY.md (see Output structure).
source-analytics list shows every analysis you can run.
pip install -e . # or: uv pip install -e .Core dependencies: numpy, scipy, pandas, pyyaml, scikit-learn, networkx, matplotlib, mne.
uv users: run the CLI with
uv run --no-sync source-analytics …. Plainuv runcan trip on the lockfile;--no-syncavoids the re-resolve.
specparam has no final 2.0 release — the index carries 2.0.0rc7 at the
latest, and PEP 440 sorts every release candidate below 2.0. A declaration
of specparam>=2.0 therefore resolves to nothing at all, which is what v0.4.0
shipped with. The floor is now >=2.0.0rc6.
That makes the package installable, but it does not make a result reproducible:
the aperiodic numbers depend on the exact specparam build, and mne is
unpinned. Install against a lockfile that pins both, then add the package
without letting it re-resolve:
uv venv .venv
uv pip install --python .venv -r <lockfile>
uv pip install --python .venv --no-deps "source-analytics @ git+https://github.com/alexedmon1/source-analytics.git@<tag>"⚠ v0.4.0 is a scientific pin, not merely an old version. It hardcodes
freq_range=(2, 50) for aperiodic fitting; later releases resolve the window
dynamically and adopt 14–45 Hz, which changes every aperiodic number. Do not
"upgrade" it to reproduce work that cites it.
Statistics and most figures are R. Install once:
install.packages(c(
"ggplot2", "dplyr", "tidyr", "readr", "stringr", "forcats",
"lme4", "lmerTest", "effectsize", "emmeans",
"yaml", "argparse", "patchwork", "scales"
))Five ideas explain the whole toolkit.
1. Levels × Domains. Every analysis sits at one level (the data it reads) and in one domain (what it measures).
- Level — ROI (atlas ROIs: 32 for Allen32, 46 for the legacy Antwerp atlas), vertex (whole-brain source vertices), or electrode (raw scalp channels, for validation / source-vs-sensor comparison).
- Domain — Spectral, Connectivity, Cross-frequency, Directed, Sensor-level, or Evoked.
ANALYSIS_METADATA in core.py is the single source of truth for this map;
source-lightbox reads it to group the gallery.
2. Paradigms. A study config groups analyses under paradigms: keys (e.g.
resting, vertex, evoked). A paradigm names where the reconstruction data
lives and which analyses run on it — it is not the same as level. In the
canonical FORGE setup the resting paradigm holds the ROI + electrode analyses
(they read rest_roi/derivatives) and the vertex paradigm holds the vertex
analyses (they read rest_shell/derivatives, a different reconstruction).
--paradigm selects the block; the analysis must also be listed in that block's
analyses:.
3. Primary vs supplementary. Most analyses are primary — they read
reconstructions directly. A few are supplementary: they consume another
analysis's output and must run after it. The graph-theory modules are the main
case — roi_graph/roi_nbs need roi_connectivity; vertex_graph/vertex_nbs
need vertex_connectivity; electrode_comparison needs electrode_psd. The
toolkit does not auto-run dependencies — run the primary first or you get a
"missing edges CSV" error. The dependency is recorded as supplements in
ANALYSIS_METADATA.
4. Two statistics adapters, one declaration. Inference runs through the shared
hypothesis layer. You declare hypotheses once (in design:/hypotheses:) and
each module tests them with whichever adapter matches how it computes its statistic:
- emmeans (R LMM modules —
roi_psd,roi_aperiodic,roi_cross_freq,roi_directed,electrode_psd,electrode_aperiodic): a tabular result — per-cell estimate / CI / p, effect size, declarative-scope FDR. - permutation (vertex & sensor map modules —
vertex_cluster,vertex_connectivity,vertex_directed,vertex_specparam,electrode_connectivity): a map + clusters result — per-unit statistic map with cluster extent/mass and cluster-p (max-stat or TFCE).
Because the declaration is shared, vertex_connectivity (source FCD) and
electrode_connectivity (sensor FCD) test the same hypothesis with the same
cluster adapter — a clean source-vs-sensor comparison.
5. Declare once, run by name. Nothing auto-fires. You declare the hypotheses,
then run them one (or a few) at a time with --hypothesis NAME. There is no gating
and no "run-everything-and-adjudicate"; the scientific judgment (which post-hoc
follows an omnibus, whether a band/region matters) stays with you. See
Hypothesis testing.
source-analytics reads the per-subject files written by source-localization.
What it looks for depends on the level:
ROI-level (roi_psd, roi_aperiodic, roi_connectivity, roi_cross_freq,
roi_directed, roi_evoked):
| File | Format | Contents |
|---|---|---|
step6_roi_timeseries_magnitude.pkl |
pickle | Dict[str, ndarray] — ROI timeseries, unsigned (PSD/aperiodic) |
step6_roi_timeseries_signed.pkl |
pickle | Dict[str, ndarray] — ROI timeseries, signed (connectivity/PAC/directed need phase) |
roi_timeseries_magnitude.set |
EEGLAB | same data + metadata (sfreq) |
Vertex-level (vertex_cluster, vertex_connectivity, vertex_cross_freq,
vertex_directed, vertex_specparam, vertex_mvpa, vertex_evoked):
| File | Format | Contents |
|---|---|---|
step5_stc_signed.pkl |
pickle | MNE SourceEstimate (n_vertices, n_times), signed (falls back to step5_stc_magnitude.pkl, legacy step5_stc.pkl) |
step3_source_coords_mm.npy |
NumPy | source coordinates (n_vertices, 3) in mm |
Electrode-level (electrode_psd, electrode_aperiodic,
electrode_connectivity, electrode_comparison, electrode_evoked):
| File | Format | Contents |
|---|---|---|
*.set / *.fdt |
EEGLAB | raw scalp EEG (channels × timepoints) |
These need a subject_roster.csv (subject_id, group, eeg_filename, eeg_dir) set
via electrode.subject_roster in the config.
Expected discovery layout (group folders → subject folders → data/):
data_dir/
Group_A/Subject_001/data/…
Group_A/Subject_002/data/…
Group_B/Subject_003/data/…
One YAML drives the whole study — and the same file is read by
source-lightbox. Study-design keys (groups, design:/hypotheses:, bands) are
global; the per-paradigm analyses: block gives each analysis its data
location and parameters. Minimal shape:
name: "My Study"
# ── Study design (global) ──────────────────────────────────────────
groups: # raw group id → display label
WT_VEH: "WT Vehicle"
KO_VEH: "KO Vehicle"
group_order: [WT_VEH, KO_VEH] # plot / x-axis order
group_colors: {WT_VEH: "#3498DB", KO_VEH: "#E74C3C"}
# Declarative hypotheses — the `hypothesis` layer. Tested one at a time by name
# (--hypothesis NAME); nothing auto-fires. See "Hypothesis testing".
design:
factor: group # the categorical factor tests are taken over
reference: WT_VEH # reference level (effect orientation)
levels: [WT_VEH, KO_VEH] # explicit level order (optional)
fdr: { scope: band, method: BH } # FDR family scope (optional; default scope=hypothesis)
hypotheses:
- name: group_omnibus # "do any groups differ?" (ANOVA / permutation-F)
kind: omnibus
role: phenotype
- name: disease_effect # name is used in table/file names
kind: contrast # a linear comparison of group means
label: "Disease effect (KO vs WT)"
weights: { KO_VEH: 1, WT_VEH: -1 } # KO − WT (sign from the weights)
role: phenotype # display/grouping tag only — no gating
bands: # name → [fmin, fmax] Hz
Delta: [1, 4]
Theta: [4, 10]
Alpha: [10, 13]
Beta: [13, 30]
Low Gamma: [30, 55]
High Gamma: [65, 80]
circos_metrics: [imag_coherence, dwpli, pli, aec, coherence] # gallery circos chords
# ── Random epoch sampling (global default; per-analysis override below) ──
epoch_sampling:
enabled: true
epoch_duration_sec: 2.0
n_epochs: 80
n_bootstrap: 500 # 0 = use the full timeseries, no sampling
# ── Output locations (shared with source-lightbox) ─────────────────
paths:
analytics: ./analytics # working dir: ANALYSIS_SUMMARY.md + data/
results: ./results # published tables/ + figures/ (gallery reads this)
# ── Paradigms: where the data is + which analyses to run ───────────
paradigms:
resting:
data_dir: ./localization/rest_roi/derivatives # reconstruction output root
data_subdir: pipeline/data
analyses:
roi_psd: {}
roi_aperiodic: {}
roi_connectivity:
epoch_sampling: {n_bootstrap: 0} # per-analysis override
roi_graph: {connectivity_metrics: [imag_coherence, dwpli, pli, aec, coherence]}
roi_nbs: {nbs_threshold: 2.5, nbs_permutations: 5000}
roi_cross_freq: {}
roi_directed: {}
electrode_psd: {}
electrode_comparison: {}
vertex:
data_dir: ./localization/rest_shell/derivatives
data_subdir: pipeline/data
analyses:
vertex_connectivity: # PRIMARY for the vertex graph theory
vertex_filter: {z_min: 0.0}
metrics: [imag_coherence, dwpli, pli, aec, coherence] # all share one STFT pass
vertex_graph: {connectivity_metrics: [imag_coherence, dwpli, pli, aec, coherence]}
vertex_nbs: {nbs_threshold: 3.0, nbs_permutations: 5000}
vertex_cluster: {}
vertex_specparam: {}| Key | Consumed by | Purpose |
|---|---|---|
groups, group_order, group_colors |
all analyses | group identity, plot order/colour |
design {factor, reference, levels, covariates, fdr} |
hypothesis layer | the factor + design tests are taken over; FDR family scope |
hypotheses[] {name, kind, weights/groups/predictor} |
hypothesis layer | the declarative tests, run by name via --hypothesis |
hypotheses[] {label, role} |
figures, gallery | readable labels + grouping tag (no gating) |
bands |
all spectral/connectivity | frequency bands analysed |
epoch_sampling |
spectral/connectivity | random-epoch resampling (n_bootstrap: 0 = full timeseries) |
paths.{analytics, results} |
I/O + gallery | working vs published output trees |
paradigms.<p>.data_dir / data_subdir |
discovery | where subject reconstructions live |
paradigms.<p>.analyses.<a> |
that analysis | enables it + sets its parameters |
The per-analysis block is merged into config.raw[<analysis>] by
config.for_paradigm_analysis(), so any analysis-specific key (connectivity_metrics,
nbs_permutations, vertex_filter, …) must live under
paradigms.<paradigm>.analyses.<analysis>, not at the top level.
Connectivity metrics. Graph/NBS supplements run on every metric in their
connectivity_metrics. At the vertex level, setvertex_connectivity.metricsto the same list so the primary precomputes all of them in one shared-STFT pass;vertex_graph/vertex_nbsthen load them per metric instead of recomputing.aecis computed outside the shared STFT and is the slow one — drop it if runtime matters more than completeness.
Everything runs through one entry point with five subcommands.
| Subcommand | Purpose |
|---|---|
run |
run an analysis (the workhorse) |
validate |
check config + subject discovery without running |
list |
list available analyses (+ selectable dims; paradigm-aware with --study) |
figure |
regenerate on-demand summary figures from existing tables |
init |
scaffold a study config from a reconstruction directory |
source-analytics run --study study.yaml --paradigm resting --analysis roi_psd [options]| Flag | Meaning |
|---|---|
--study PATH |
study YAML (required) |
--paradigm NAME |
paradigm block under paradigms: (required for multi-paradigm configs) |
--analysis NAME |
analysis to run (see catalog) |
--steps a,b,… |
lifecycle steps to run. Valid: setup, process, aggregate, statistics, figures, summary |
--metric m,… |
restrict a module's metrics (shorthand for --select metric=…) |
--band b,… |
restrict bands, case/format-insensitive (shorthand for --select band=…) |
--hypothesis n,… |
test only these declared hypotheses (shorthand for --select hypothesis=…) |
--select DIM=v,… |
generic sub-output selection, repeatable (see list for a module's dims) |
--force |
overwrite the output directory if it exists |
--strict-output |
error if the output directory exists (unless --force) |
Lifecycle steps. A run is setup → process → aggregate → statistics → figures → summary. --steps re-runs a subset against existing on-disk data — e.g. recompute
only the statistics and report after a config change, without reprocessing subjects:
source-analytics run --study study.yaml --paradigm resting --analysis roi_psd \
--steps statistics,summary,figuressource-analytics validate --study study.yaml [--paradigm resting]
source-analytics list [--study study.yaml] # paradigm-aware when --study given
source-analytics figure --study study.yaml --paradigm resting --analysis roi_psd --list
source-analytics figure --study study.yaml --paradigm resting --analysis roi_psd \
--type effect_heatmap [--contrast disease_effect --band low_gamma]
source-analytics init /path/to/reconstruction_dir --name "Study" --groups-from sl_config.yamlGrouped by domain (what they measure). Levels: ROI / vertex (vtx) /
electrode (elec). Supplementary analyses are indented under their primary and
must run after it. Method provenance for the connectivity / cross-frequency /
directed families is tracked, equation-checked, in
docs/methods/CONNECTIVITY_METHODS.md.
| Analysis | Level | Computes | Reference |
|---|---|---|---|
roi_psd, electrode_psd |
ROI, elec | band power (Welch PSD: absolute/relative/dB) | Welch 1967 |
roi_aperiodic, electrode_aperiodic, vertex_specparam |
ROI, elec, vtx | 1/f aperiodic (offset, exponent) + oscillatory peaks | Donoghue 2020 (specparam) |
vertex_cluster |
vtx | per-vertex band power / fALFF / slope / peak-α, cluster-corrected maps | Maris & Oostenveld 2007 |
vertex_mvpa |
vtx | whole-brain pattern decoding (linear SVM, LOOCV, permutation) | — (linear SVM) |
vertex_spatial (RETIRED) |
vtx | was: spatial-covariance GLS robustness check | — |
vertex_spatialis retired (it produced a spatial-covariance robustness table, never a manuscript result, and did not survive the design-spec migration). Spatially-resolved vertex inference isvertex_cluster(glass-brain clusters) +vertex_nbs(network-based statistic). The module exits cleanly with empty frames + a note.
| Analysis | Level | Computes | Reference |
|---|---|---|---|
roi_connectivity, vertex_connectivity |
ROI, vtx | FC-six + more: coherence, imaginary coherence, PLI, wPLI, dwPLI, dPLI, AEC, partial correlation | Nolte 2004; Stam 2007; Vinck 2011; Stam & van Straaten 2012; Hipp 2012; Marrelec 2006 |
↳ roi_graph, vertex_graph (suppl.) |
ROI, vtx | graph-theoretic nodal metrics (degree/clustering/betweenness; vtx: multi-density AUC) | Rubinov & Sporns 2010 |
↳ roi_nbs, vertex_nbs (suppl.) |
ROI, vtx | Network-Based Statistic (sub-network test) | Zalesky 2010 |
electrode_connectivity |
elec | FC-six all-pairs + per-channel FCD — the source-vs-sensor comparator | as above |
roi_network/vertex_networkare combined aliases that run graph + NBS together; the split modules (*_graph,*_nbs) are preferred for the gallery.dpliis directed and is auto-excluded from the undirected graph/NBS layer.
| Analysis | Level | Computes | Reference |
|---|---|---|---|
roi_cross_freq, vertex_cross_freq |
ROI, vtx | PAC (Modulation Index, surrogate-z); cross-frequency AAC; n:m PPC | Tort 2010; Bruns 2000 / Masimore 2004; Tass 1998 / Palva 2005 |
| Analysis | Level | Computes | Reference |
|---|---|---|---|
roi_directed |
ROI | transfer entropy (te, net_te); DTF (dtf, ridge-MVAR) |
Schreiber 2000; Kamiński & Blinowska 1991 |
vertex_directed |
vtx | DTF outflow / inflow / netflow (ridge-MVAR), cluster-corrected | Kamiński & Blinowska 1991 |
Source ROIs/vertices are strongly collinear (mean inter-node |corr| ≈ 0.64), so DTF uses a ridge-regularized MVAR — plain LS-MVAR is non-stationary; the module warns if a fit is unstable.
| Analysis | Level | Computes |
|---|---|---|
electrode_comparison (suppl. of electrode_psd) |
elec | source-vs-electrode effect-size validation |
| Analysis | Level | Computes |
|---|---|---|
roi_evoked, vertex_evoked, electrode_evoked |
ROI, vtx, elec | ITC, ERSP, single-trial power |
Renames (2026-06). roi_pac → roi_cross_freq (now also AAC + PPC);
roi_transfer_entropy → roi_directed. Old names still work as deprecated aliases
(psd/aperiodic/pac/mvpa/wholebrain/… also map to the canonical names).
The hypothesis layer is a shared inference engine (peer to R/stats_utils.R and
src/source_analytics/stats/, not a registry module) that turns the declarative
design:/hypotheses: blocks into tests. Full reference:
docs/methods/HYPOTHESIS.md; design rationale: docs/methods/DESIGN_SPEC.md.
Four kinds. A hypothesis carries a kind and the payload it needs:
| kind | question | payload | effect size |
|---|---|---|---|
omnibus |
do these groups differ at all? | groups (default: all levels) |
partial ω² |
contrast |
a specific linear comparison (post-hoc) | weights: {level: w} |
Hedges g |
regression |
slope of a continuous predictor | predictor (+ optional by) |
standardized β |
equivalence |
is a contrast within a margin? (TOST) | weights + margin |
— |
The legacy group_a/group_b form is still accepted as sugar for a pairwise
weights map.
FDR family scope (declarative). Multiple-comparison correction happens within a
single hypothesis run's cells (band × spatial). The family is declarative via an
fdr: block — study-level under design: and/or per-hypothesis:
design:
fdr: { scope: hypothesis, method: BH } # default = the whole band×spatial grid
hypotheses:
- name: disease_effect
fdr: { scope: band } # per-band/freq-pair family (override)scope is hypothesis (one family over the whole grid — most conservative,
default), band (a family per band/freq-pair — the principled choice when bands
are pre-specified independent hypotheses, e.g. PAC), spatial, or none. method
is BH (default), BY, holm, bonferroni, or none. The permutation/map
adapter uses cluster-extent correction, so fdr: is a no-op there. Aggressiveness
is driven by family size, not just the method — declaring the family in the
spec keeps it pre-registered.
Running. --hypothesis NAME[,NAME] runs one (or a few) by name; with no flag a
module runs all declared hypotheses. It composes with --metric / --band /
--select:
source-analytics run --study study.yaml --paradigm resting \
--analysis roi_psd --hypothesis disease_effectOutput is additive: a <module>_hypotheses.csv written alongside the module's
other tables, with one tidy row per band × spatial cell (estimate, SE, CI, stat, p,
q, effect size, fdr_family, plus legacy-named aliases for existing figure
consumers). Modules with multiple spatial tiers emit one table per tier — e.g.
roi_directed writes …_global_hypotheses.csv, …_directed_edges_hypotheses.csv,
and …_region_hypotheses.csv.
Multi-output analyses honour a sub-output filter, so you compute exactly what you want without losing the shared STFT/Hilbert compute pass:
# two connectivity metrics only
source-analytics run … --analysis vertex_connectivity --metric dwpli,wpli
# one band, one cross-frequency measure
source-analytics run … --analysis vertex_cross_freq --metric ppc --band low_gamma
# one declared hypothesis
source-analytics run … --analysis roi_psd --hypothesis disease_effect
# generic form (repeatable)
source-analytics run … --select metric=pli --select band=beta,low_gammaSelectable dimensions vary by module (metric / band / hypothesis /
measure). source-analytics list tags each analysis with its dimensions, e.g.
roi_psd [--select: band, hypothesis] — that listing is the source of truth.
Each analysis writes a self-contained directory under paths.analytics
(working tree). The published tables/ + figures/ are mirrored to
paths.results, which source-lightbox reads.
<analytics>/<analysis>/
ANALYSIS_SUMMARY.md # methods + results narrative (markdown)
data/
<analysis>_*.csv # the computed per-subject measures (the inputs to stats)
study_config.yaml # the resolved config snapshot used for this run
tables/
<analysis>_hypotheses.csv # the hypothesis-layer result (one row per band×cell)
… # any module-specific diagnostic tables
figures/
*.png # ggplot2 / glass-brain / matplotlib figures
The <analysis>_hypotheses.csv is the canonical statistical contract across all
emmeans/permutation modules; figure and gallery consumers read it (plus legacy
column aliases during the migration).
Figures are render-on-demand — they are not auto-regenerated when data changes. Re-run the
figuresstep (orsource-analytics figure …) before rebuilding a manuscript/gallery from updated tables.
A study run is just the analyses invoked in dependency order (primaries before
their supplements). The canonical recipes live in scripts/; the essential order:
SA="source-analytics run --study study.yaml --paradigm"
# Resting paradigm — ROI + electrode
$SA resting --analysis roi_psd
$SA resting --analysis roi_aperiodic
$SA resting --analysis roi_connectivity # PRIMARY
$SA resting --analysis roi_graph # ↳ after roi_connectivity
$SA resting --analysis roi_nbs # ↳ after roi_connectivity
$SA resting --analysis roi_cross_freq # PAC + AAC + PPC (--metric to pick one)
$SA resting --analysis roi_directed # transfer entropy + DTF (--metric te|dtf)
$SA resting --analysis electrode_psd # PRIMARY
$SA resting --analysis electrode_aperiodic
$SA resting --analysis electrode_comparison # ↳ after electrode_psd
$SA resting --analysis electrode_connectivity # sensor FC comparator
# Vertex paradigm — whole-brain
$SA vertex --analysis vertex_connectivity # PRIMARY (slow; computes matrices)
$SA vertex --analysis vertex_graph # ↳ after vertex_connectivity
$SA vertex --analysis vertex_nbs # ↳ after vertex_connectivity
$SA vertex --analysis vertex_cluster
$SA vertex --analysis vertex_specparam
$SA vertex --analysis vertex_mvpa
$SA vertex --analysis vertex_cross_freq # local PAC + AAC + PPC
$SA vertex --analysis vertex_directed # vertex DTF (outflow/inflow/netflow)
# Evoked paradigm (trial-based data only)
$SA evoked --analysis roi_evoked
$SA evoked --analysis vertex_evoked
$SA evoked --analysis electrode_evokedPython R
────────────────────────────────────── ──────────────────────────────
1. Load YAML config, discover subjects
2. Load reconstructions (pickle/.set/.npy)
3. Signal processing (scipy/mne/sklearn)
4. Export per-subject CSVs ───────────────► 5. Read CSVs + config
(vertex/sensor: also do cluster-perm 6. LMMs (lme4/lmerTest), emmeans
stats + glass-brain figures in Python) 7. Hypothesis layer: effect sizes, FDR
8. ggplot2 figures
9. Markdown ANALYSIS_SUMMARY.md
Python calls Rscript automatically. ROI/electrode LMM modules delegate stats +
figures to R; vertex/sensor map modules do statistics and glass-brain figures in
Python and use R only for the report.
- Create
src/source_analytics/analyses/my_analysis.pysubclassingBaseAnalysis. - Implement the lifecycle hooks:
setup → process_subject → aggregate → statistics → figures → summary(any subset; the base no-ops the rest). - If it does LMM stats, add
R/my_analysis.R,source()R/hypothesis.R, and emit<module>_hypotheses.csvviawrite_module_hypotheses()(orwrite_module_directed_edges()for asymmetric directed edges). If it does map stats, use the Python permutation adapter (write_module_hypotheses_perm). - Register the class in
ANALYSIS_REGISTRYand add anANALYSIS_METADATAentry (level,domain, optionalsupplements) incore.py. - Declare a
SELECTABLEdict on the class for any--metric/--band/hypothesissub-output dimensions. - Add it to the catalog above.
| Doc | What it covers |
|---|---|
docs/ |
methods documentation site (MkDocs) — start here |
docs/methods/APERIODIC_FIT_WINDOW.md |
why the 1/f fit range is 12–45 Hz; overriding it; offset_centered |
docs/methods/HYPOTHESIS.md |
the hypothesis layer — kinds, adapters, usage |
docs/methods/DESIGN_SPEC.md |
design rationale for design:/hypotheses: + FDR scope |
docs/methods/CONNECTIVITY_METHODS.md |
equation-checked provenance for every connectivity / coupling / directed metric |
CHANGELOG.md |
version history |
docs/archive/ |
completed/superseded planning docs — not current |
MIT