INTENSE Disentanglement

Mixed selectivity disentanglement analysis for INTENSE.

This module provides functions to analyze and disentangle mixed selectivity in neural responses when neurons respond to multiple, potentially correlated behavioral variables.

driada.intense.disentanglement.DEFAULT_MULTIFEATURE_MAP = {('x', 'y'): 'place', ('x', 'y', 'z'): '3d-place'}

Default multifeature mapping for common behavioral variable combinations.

Maps component tuples to their semantic names:

  • ("x", "y"): mapped to "place" (2D spatial location)

  • ("x", "y", "z"): mapped to "3d-place" (3D spatial location)

driada.intense.disentanglement.disentangle_pair(ts1, ts2, ts3, verbose=False, ds=1)[source]

Disentangle mixed selectivity between two behavioral variables for a neuron.

Determines which of two correlated behavioral variables (ts2, ts3) provides the primary information about neural activity (ts1) using interaction information and conditional mutual information analysis.

Parameters:
  • ts1 (TimeSeries) – Neural activity time series (e.g., calcium signal or spike train).

  • ts2 (TimeSeries) – First behavioral variable.

  • ts3 (TimeSeries) – Second behavioral variable.

  • verbose (bool, optional) – If True, print detailed analysis results. Default: False.

  • ds (int, optional) – Downsampling factor. Default: 1.

Returns:

Disentanglement result:

  • 0: ts2 is the primary variable (ts3 is redundant)

  • 1: ts3 is the primary variable (ts2 is redundant)

  • 0.5: Both variables contribute - undistinguishable

Return type:

float

Notes

The method uses interaction information to detect redundancy/synergy:

  • If II < 0 (redundancy), identifies the “weakest link” using criteria based on pairwise MI and conditional MI values

  • If II > 0 (synergy), uses different criteria for special cases

See docs/intense_mathematical_framework.md for theoretical background.

driada.intense.disentanglement.disentangle_all_selectivities(exp, feat_names, ds=1, multifeature_map=None, feat_feat_significance=None, cell_bunch=None, cell_feat_stats=None, feat_feat_similarity=None, n_jobs=-1, pre_filter_func=None, post_filter_func=None, filter_kwargs=None)[source]

Analyze mixed selectivity across all significant neuron-feature pairs.

For each neuron that responds to multiple features, determines which features provide primary vs redundant information using disentanglement analysis. Only analyzes feature pairs that show significant correlation in the behavioral data.

Parameters:
  • exp (Experiment) – Experiment object containing neural and behavioral data.

  • feat_names (list of str) – List of feature names to analyze. Should match features in experiment and any aggregated names from multifeature_map.

  • ds (int, optional) – Downsampling factor. Default: 1.

  • multifeature_map (dict, optional) – Mapping from multifeature tuples to aggregated names and their corresponding MultiTimeSeries. If None, uses DEFAULT_MULTIFEATURE_MAP. Example: {('x', 'y'): 'place', ('speed', 'head_direction'): 'locomotion'}.

  • feat_feat_significance (ndarray, optional) – Binary significance matrix from compute_feat_feat_significance. If provided, only feature pairs marked as significant (value=1) will be analyzed for disentanglement. Non-significant pairs are assumed to represent true mixed selectivity.

  • cell_bunch (list or None, optional) – List of cell IDs to analyze. If None, analyzes all cells. Default: None.

  • cell_feat_stats (dict or None, optional) – Pre-computed neuron-feature statistics from INTENSE analysis. Structure: stats[cell_id][feat_name][“me”] = MI value. If provided, MI(neuron, feature) values will be looked up instead of recomputed, significantly speeding up disentanglement. Default: None.

  • feat_feat_similarity (ndarray or None, optional) – Pre-computed feature-feature similarity matrix from compute_feat_feat_significance. Matrix where [i, j] = MI(feat_i, feat_j). If provided, MI(feature1, feature2) values will be looked up. Default: None.

  • n_jobs (int, optional) – Number of parallel jobs for processing neurons. -1 means use all available processors. Default: -1.

  • pre_filter_func (callable or None, optional) –

    Population-level filter function (or composed filter) to run BEFORE the parallel processing loop. The filter mutates neuron selectivities and pre-computes pair decisions for all neurons at once.

    Signature:

    def pre_filter_func(
        neuron_selectivities,    # dict: {neuron_id: [feat1, feat2, ...]} - MUTATE
        pair_decisions,          # dict: {neuron_id: {(f1, f2): 0/0.5/1}} - MUTATE
        renames,                 # dict: {neuron_id: {new_name: (old1, old2)}} - MUTATE
        cell_feat_stats,         # Pre-computed MI values (READ ONLY)
        feat_feat_significance,  # Binary matrix (READ ONLY)
        feat_names,              # List of feature names (READ ONLY)
        **kwargs,                # User-provided extra arguments
    ):
        ...
    

    Default: None (no filtering).

  • post_filter_func (callable or None, optional) –

    Population-level filter function to run AFTER parallel disentanglement. Can modify pair results (e.g., tie-breaking). Mutates per_neuron_disent and recalculates final_sels.

    Signature:

    def post_filter_func(
        per_neuron_disent,       # dict: {nid: {'pairs': {...}, ...}} - MUTATE
        cell_feat_stats,         # Pre-computed MI values (READ ONLY)
        feat_names,              # List of feature names (READ ONLY)
        **kwargs,                # User-provided extra arguments
    ):
        ...
    

    Default: None (no post-filtering).

  • filter_kwargs (dict or None, optional) – Dictionary of keyword arguments to pass to pre_filter_func and post_filter_func. Can include pre-extracted data like calcium_data, feature_data, thresholds, etc. Default: None.

Returns:

Dictionary containing:

  • ’disent_matrix’: ndarray where element [i,j] indicates how many times feature i was primary when paired with feature j across all neurons.

  • ’count_matrix’: ndarray where element [i,j] indicates how many neuron-feature pairs were tested for features i and j.

  • ’per_neuron_disent’: dict mapping neuron_id to detailed results with keys ‘pairs’, ‘renames’, ‘final_sels’, and ‘errors’.

Return type:

dict

Notes

The analysis is performed only on neurons with significant selectivity to at least 2 features. If feat_feat_significance is provided, only behaviorally correlated feature pairs are analyzed for redundancy. Non-significant pairs indicate true mixed selectivity.

When cell_feat_stats and feat_feat_similarity are provided, 3 out of 5 pairwise MI computations per disentangle_pair call are skipped by using lookups, providing ~60% reduction in MI computation overhead.

The neuron loop is parallelized using joblib, providing significant speedup when analyzing many neurons. Each neuron is processed independently and results are merged at the end.

Feature names in feat_names must use _2d-substituted names for circular features (e.g., headdirection_2d not headdirection), matching the names used in cell_feat_stats.

If any per-neuron disentanglement encounters errors (e.g., missing feature data), a summary warning is emitted via warnings.warn after all neurons are processed.

Filter chain execution:

  1. Filters run at population level BEFORE the parallel loop

  2. Filters mutate neuron_selectivities, pair_decisions, and renames in place

  3. Workers receive pre-computed decisions (lightweight serialization)

  4. Each filter in the chain can override decisions from earlier filters

Raises:
  • ValueError – If a feature name is not found in the experiment or feat_names.

  • AttributeError – If required attributes are missing from the experiment.

  • KeyError – If expected keys are missing from data structures.

driada.intense.disentanglement.create_multifeature_map(exp, mapping_dict)[source]

Create a multifeature mapping with validation.

Parameters:
  • exp (Experiment) – Experiment object to validate feature existence.

  • mapping_dict (dict) – Dictionary mapping tuples of features to aggregated names. Example: {(‘x’, ‘y’): ‘place’, (‘speed’, ‘head_direction’): ‘locomotion’}

Returns:

Validated multifeature mapping.

Return type:

dict

Raises:

ValueError – If any component features don’t exist in the experiment.

driada.intense.disentanglement.get_disentanglement_summary(disent_matrix, count_matrix, feat_names, feat_feat_significance=None, per_neuron_disent=None)[source]

Generate a summary of disentanglement results.

Parameters:
  • disent_matrix (ndarray) – Disentanglement result matrix from disentangle_all_selectivities.

  • count_matrix (ndarray) – Count matrix from disentangle_all_selectivities.

  • feat_names (list of str) – Feature names corresponding to matrix indices.

  • feat_feat_significance (ndarray, optional) – Binary significance matrix indicating which feature pairs were analyzed for disentanglement.

  • per_neuron_disent (dict, optional) – Per-neuron disentanglement results from disentangle_all_selectivities. When provided, redundancy/undistinguishable counts are computed exactly from per-neuron pair decisions instead of the aggregate matrices.

Returns:

Summary statistics including: - Primary feature percentages for each pair - Total counts for each pair - Overall redundancy vs independence rates - Breakdown by significant vs non-significant feature pairs

Return type:

dict

Notes

The calculation distinguishes between: - Redundant cases: One feature is primary (disentangle result 0 or 1) - Undistinguishable cases: Both features contribute (disentangle result 0.5)

Tools for disentangling mixed selectivity in neural populations.

Main Functions

driada.intense.disentanglement.disentangle_pair(ts1, ts2, ts3, verbose=False, ds=1)[source]

Disentangle mixed selectivity between two behavioral variables for a neuron.

Determines which of two correlated behavioral variables (ts2, ts3) provides the primary information about neural activity (ts1) using interaction information and conditional mutual information analysis.

Parameters:
  • ts1 (TimeSeries) – Neural activity time series (e.g., calcium signal or spike train).

  • ts2 (TimeSeries) – First behavioral variable.

  • ts3 (TimeSeries) – Second behavioral variable.

  • verbose (bool, optional) – If True, print detailed analysis results. Default: False.

  • ds (int, optional) – Downsampling factor. Default: 1.

Returns:

Disentanglement result:

  • 0: ts2 is the primary variable (ts3 is redundant)

  • 1: ts3 is the primary variable (ts2 is redundant)

  • 0.5: Both variables contribute - undistinguishable

Return type:

float

Notes

The method uses interaction information to detect redundancy/synergy:

  • If II < 0 (redundancy), identifies the “weakest link” using criteria based on pairwise MI and conditional MI values

  • If II > 0 (synergy), uses different criteria for special cases

See docs/intense_mathematical_framework.md for theoretical background.

driada.intense.disentanglement.disentangle_all_selectivities(exp, feat_names, ds=1, multifeature_map=None, feat_feat_significance=None, cell_bunch=None, cell_feat_stats=None, feat_feat_similarity=None, n_jobs=-1, pre_filter_func=None, post_filter_func=None, filter_kwargs=None)[source]

Analyze mixed selectivity across all significant neuron-feature pairs.

For each neuron that responds to multiple features, determines which features provide primary vs redundant information using disentanglement analysis. Only analyzes feature pairs that show significant correlation in the behavioral data.

Parameters:
  • exp (Experiment) – Experiment object containing neural and behavioral data.

  • feat_names (list of str) – List of feature names to analyze. Should match features in experiment and any aggregated names from multifeature_map.

  • ds (int, optional) – Downsampling factor. Default: 1.

  • multifeature_map (dict, optional) – Mapping from multifeature tuples to aggregated names and their corresponding MultiTimeSeries. If None, uses DEFAULT_MULTIFEATURE_MAP. Example: {('x', 'y'): 'place', ('speed', 'head_direction'): 'locomotion'}.

  • feat_feat_significance (ndarray, optional) – Binary significance matrix from compute_feat_feat_significance. If provided, only feature pairs marked as significant (value=1) will be analyzed for disentanglement. Non-significant pairs are assumed to represent true mixed selectivity.

  • cell_bunch (list or None, optional) – List of cell IDs to analyze. If None, analyzes all cells. Default: None.

  • cell_feat_stats (dict or None, optional) – Pre-computed neuron-feature statistics from INTENSE analysis. Structure: stats[cell_id][feat_name][“me”] = MI value. If provided, MI(neuron, feature) values will be looked up instead of recomputed, significantly speeding up disentanglement. Default: None.

  • feat_feat_similarity (ndarray or None, optional) – Pre-computed feature-feature similarity matrix from compute_feat_feat_significance. Matrix where [i, j] = MI(feat_i, feat_j). If provided, MI(feature1, feature2) values will be looked up. Default: None.

  • n_jobs (int, optional) – Number of parallel jobs for processing neurons. -1 means use all available processors. Default: -1.

  • pre_filter_func (callable or None, optional) –

    Population-level filter function (or composed filter) to run BEFORE the parallel processing loop. The filter mutates neuron selectivities and pre-computes pair decisions for all neurons at once.

    Signature:

    def pre_filter_func(
        neuron_selectivities,    # dict: {neuron_id: [feat1, feat2, ...]} - MUTATE
        pair_decisions,          # dict: {neuron_id: {(f1, f2): 0/0.5/1}} - MUTATE
        renames,                 # dict: {neuron_id: {new_name: (old1, old2)}} - MUTATE
        cell_feat_stats,         # Pre-computed MI values (READ ONLY)
        feat_feat_significance,  # Binary matrix (READ ONLY)
        feat_names,              # List of feature names (READ ONLY)
        **kwargs,                # User-provided extra arguments
    ):
        ...
    

    Default: None (no filtering).

  • post_filter_func (callable or None, optional) –

    Population-level filter function to run AFTER parallel disentanglement. Can modify pair results (e.g., tie-breaking). Mutates per_neuron_disent and recalculates final_sels.

    Signature:

    def post_filter_func(
        per_neuron_disent,       # dict: {nid: {'pairs': {...}, ...}} - MUTATE
        cell_feat_stats,         # Pre-computed MI values (READ ONLY)
        feat_names,              # List of feature names (READ ONLY)
        **kwargs,                # User-provided extra arguments
    ):
        ...
    

    Default: None (no post-filtering).

  • filter_kwargs (dict or None, optional) – Dictionary of keyword arguments to pass to pre_filter_func and post_filter_func. Can include pre-extracted data like calcium_data, feature_data, thresholds, etc. Default: None.

Returns:

Dictionary containing:

  • ’disent_matrix’: ndarray where element [i,j] indicates how many times feature i was primary when paired with feature j across all neurons.

  • ’count_matrix’: ndarray where element [i,j] indicates how many neuron-feature pairs were tested for features i and j.

  • ’per_neuron_disent’: dict mapping neuron_id to detailed results with keys ‘pairs’, ‘renames’, ‘final_sels’, and ‘errors’.

Return type:

dict

Notes

The analysis is performed only on neurons with significant selectivity to at least 2 features. If feat_feat_significance is provided, only behaviorally correlated feature pairs are analyzed for redundancy. Non-significant pairs indicate true mixed selectivity.

When cell_feat_stats and feat_feat_similarity are provided, 3 out of 5 pairwise MI computations per disentangle_pair call are skipped by using lookups, providing ~60% reduction in MI computation overhead.

The neuron loop is parallelized using joblib, providing significant speedup when analyzing many neurons. Each neuron is processed independently and results are merged at the end.

Feature names in feat_names must use _2d-substituted names for circular features (e.g., headdirection_2d not headdirection), matching the names used in cell_feat_stats.

If any per-neuron disentanglement encounters errors (e.g., missing feature data), a summary warning is emitted via warnings.warn after all neurons are processed.

Filter chain execution:

  1. Filters run at population level BEFORE the parallel loop

  2. Filters mutate neuron_selectivities, pair_decisions, and renames in place

  3. Workers receive pre-computed decisions (lightweight serialization)

  4. Each filter in the chain can override decisions from earlier filters

Raises:
  • ValueError – If a feature name is not found in the experiment or feat_names.

  • AttributeError – If required attributes are missing from the experiment.

  • KeyError – If expected keys are missing from data structures.

driada.intense.disentanglement.create_multifeature_map(exp, mapping_dict)[source]

Create a multifeature mapping with validation.

Parameters:
  • exp (Experiment) – Experiment object to validate feature existence.

  • mapping_dict (dict) – Dictionary mapping tuples of features to aggregated names. Example: {(‘x’, ‘y’): ‘place’, (‘speed’, ‘head_direction’): ‘locomotion’}

Returns:

Validated multifeature mapping.

Return type:

dict

Raises:

ValueError – If any component features don’t exist in the experiment.

driada.intense.disentanglement.get_disentanglement_summary(disent_matrix, count_matrix, feat_names, feat_feat_significance=None, per_neuron_disent=None)[source]

Generate a summary of disentanglement results.

Parameters:
  • disent_matrix (ndarray) – Disentanglement result matrix from disentangle_all_selectivities.

  • count_matrix (ndarray) – Count matrix from disentangle_all_selectivities.

  • feat_names (list of str) – Feature names corresponding to matrix indices.

  • feat_feat_significance (ndarray, optional) – Binary significance matrix indicating which feature pairs were analyzed for disentanglement.

  • per_neuron_disent (dict, optional) – Per-neuron disentanglement results from disentangle_all_selectivities. When provided, redundancy/undistinguishable counts are computed exactly from per-neuron pair decisions instead of the aggregate matrices.

Returns:

Summary statistics including: - Primary feature percentages for each pair - Total counts for each pair - Overall redundancy vs independence rates - Breakdown by significant vs non-significant feature pairs

Return type:

dict

Notes

The calculation distinguishes between: - Redundant cases: One feature is primary (disentangle result 0 or 1) - Undistinguishable cases: Both features contribute (disentangle result 0.5)

Constants

driada.intense.disentanglement.DEFAULT_MULTIFEATURE_MAP = {'xy': '2d_space', 'ad': 'allocentric', ...}

Default multifeature mapping for common behavioral variable combinations.

Maps component tuples to their semantic names:

  • ("x", "y"): mapped to "place" (2D spatial location)

  • ("x", "y", "z"): mapped to "3d-place" (3D spatial location)