Analysis library for two-alternative forced-choice (2-AFC) and go/no-go categorisation data. Loads sessions from CSV via a YAML config, selects and filters trials, computes psychometric and trial-history statistics with resampling-based uncertainty, and draws the standard figures.
It knows nothing about any particular experiment: stimulus units, distributions, session types and presets all come from the config you give it. Every computation runs on one normalised stimulus axis ([-1, 1], category boundary at 0) that the loader maps your raw stimulus onto.
A pinned release:
pip install "behav_utils @ git+https://github.com/serkanshentyurk/behav_utils.git@v0.9.1"Or your own checkout, editable, to work on the library:
pip install -e <path to behav_utils>Requires Python ≥ 3.10; numpy, pandas, scipy, matplotlib, pyyaml.
from behav_utils import (load_experiment, select_sessions, filter_trials, TrialArrays,
compute_stats, PSYCHOMETRIC, compute_psychometric_curve,
compute_phase_stats, compute_delta_stat, plot_psychometric_curve)
experiment = load_experiment('config.yaml') # {animal_id: AnimalData}
animal = experiment.animals['A01']
sessions = select_sessions(animal, preset='expert') # presets defined in the config
phase = filter_trials(sessions, trial_type='non_opto') # drops aborts and laser trials
arrays = TrialArrays.from_sessions(phase) # one aligned bundle of arrays
s = compute_stats(arrays, [*PSYCHOMETRIC, 'accuracy', 'win_stay']) # pd.Series of floats
s['mu'], s['sigma']
curve = compute_psychometric_curve(arrays) # fit + bootstrap band
plot_psychometric_curve(curve) # one axes, nothing computed
r = compute_phase_stats(phase, ['accuracy', 'mu'], per_session=True) # pooled Series + tidy per-session frame
d = compute_delta_stat({'off': phase, 'on': filter_trials(sessions, trial_type='opto')},
['mu', 'accuracy'], reference='off',
n_permutations=1000) # same sessions, per-trial label → permutation p is valid
d.contrast('on').table()| package | what | entry points |
|---|---|---|
behav_utils.config |
YAML schema and loader; the raw → normalised stimulus transform | load_config, load_cohorts, TaskConfig.normalise_stimulus |
behav_utils.data |
data structures, CSV loading, session selection, trial filtering, synthetic data | load_experiment, select_sessions, filter_trials, pool_arrays, TrialArrays (with trial_key), generate_synthetic_animal, find_switches |
behav_utils.stats |
scalar statistics registry | compute_stats, list_stats, PSYCHOMETRIC, PSE_DYNAMICS |
behav_utils.readouts |
array-valued readouts as dataclasses, and the two fit engines on raw arrays (fit.py) |
compute_psychometric_curve, compute_update_matrix, compute_conditional_psychometric, compute_binned_curve, compute_sd_profile, fit_psychometric, fit_update_matrix |
behav_utils.analysis |
phase statistics, contrasts, resampling, rolling, group tests | compute_phase_stats, compute_delta_stat, compute_interaction, bootstrap_phase_stats, permute_phase_difference, compute_rolling_stats, collect_rows, compare_groups, paired_diff, bootstrap_units, rank_test |
behav_utils.plotting |
one single-panel, draw-only plot_x per result type |
plot_psychometric_curve, plot_update_matrix, plot_comparison, plot_stat_comparison, plot_interaction, plot_trajectory, plot_rolling_stats (+ *_grid layout helpers) |
Worked example, no data needed: notebooks/example_workflow.ipynb. Full details: ARCHITECTURE.md (design and contracts), docs/config_guide.md, docs/data_structures_reference.md, docs/stats_reference.md (every statistic), LLM_CONTEXT.md (orientation for an AI assistant), CONTRIBUTING.md.
- Pipeline order is fixed:
load → select_sessions → filter_trials → compute_x → plot_x. Compute functions take pre-filtered sessions or aTrialArrays; they never select or filter themselves. - Every compute returns a typed result — a
pd.Series/DataFrameor a frozen dataclass withto_rows(). No nested dicts, no shape that depends on amodeargument. - Every plotter is draw-only and single-panel:
plot_x(result, ax=None)takes the matching result and computes nothing; layout is the caller's. The only exception is the pair of*_gridhelpers, which lay out one panel per stat.
Same library, two config differences: the withheld response is coded as choice = 0 (not missing),
and the go stimuli sit on the positive side of the boundary. See Go/no-go in
docs/config_guide.md and the reading table at the top of
docs/stats_reference.md.
Out of scope here, by design. What the library provides for it is TrialArrays.trial_key — an int64
identity per row (session_idx * 10**6 + trial_number) that survives every take, valid and
resample — so a per-trial neural tensor keyed the same way can be masked to exactly the trials a
statistic used. Alignment (sync tables, binning spikes or frames per trial) belongs in the project.
From the repository folder; no config or data needed:
pytest tests -q
python docs/run_doc_examples.pyThe second checks that every code block in docs/data_structures_reference.md still runs. After changing
a statistic, regenerate its reference page:
python docs/gen_stats_reference.pySemantic. behav_utils.__version__ is stamped into snapshots and result metadata by projects that use it.
See CHANGELOG.md. A v* tag builds, tests and publishes a GitHub release automatically.
This repository is the canonical one; projects that take the library on should fork it and pin their fork's tags (see Maintenance in CONTRIBUTING.md).