BiotunerGroup (BETA)#

Warning

🧪 BETA Feature

The BiotunerGroup module is currently in beta. The API may change in future releases. We welcome feedback and contributions to help stabilize this feature.

BiotunerGroup: Group-level analysis for multiple time series

This module provides a clean, elegant interface for running biotuner analysis on multiple time series (trials, electrodes, etc.) with automatic aggregation, comparison, and visualization capabilities.

Author: Biotuner Team Date: December 2025

class BiotunerGroup(data: ndarray, sf: int, axis_labels: List[str] | None = None, metadata: Dict[str, List] | None = None, store_objects: bool = True, **biotuner_kwargs)[source]#

Bases: object

Group-level biotuner analysis for multiple time series.

This class manages multiple biotuner objects, enabling batch processing, aggregation of metrics, group comparisons, and unified visualizations.

Parameters:
  • data (ndarray) – Time series data with shape: - 2D: (n_series, n_samples) - e.g., electrodes × timepoints - 3D: (n_trials, n_channels, n_samples) - e.g., trials × electrodes × timepoints

  • sf (int) – Sampling frequency in Hz

  • axis_labels (list of str, optional) – Names for each dimension, e.g., [‘trials’, ‘electrodes’] If None, uses generic labels [‘dim0’, ‘dim1’, …]

  • metadata (dict, optional) – Metadata for grouping/comparison. Keys are column names, values are lists with length matching the first dimension of data. Example: {‘condition’: [‘rest’, ‘rest’, ‘task’, ‘task’], ‘subject’: [‘S1’, ‘S2’, ‘S1’, ‘S2’]}

  • store_objects (bool, default=True) – If True, stores all individual biotuner objects. If False, only stores results to save memory (individual objects not accessible).

  • **biotuner_kwargs (dict) – Default parameters passed to all compute_biotuner objects (e.g., peaks_function=’EMD’, precision=0.1, n_harm=10)

objects#

Individual biotuner objects (if store_objects=True)

Type:

list of compute_biotuner

results#

Summary DataFrame with all computed metrics

Type:

pandas.DataFrame

shape#

Shape of the input data

Type:

tuple

n_series#

Total number of time series

Type:

int

Examples

Basic usage with 2D data:

>>> # Single electrode across trials
>>> data = np.random.randn(10, 5000)  # 10 trials, 5000 samples
>>> btg = BiotunerGroup(data, sf=1000)
>>> btg.compute_peaks(peaks_function='FOOOF', min_freq=1, max_freq=50)
>>> summary = btg.summary()
>>> print(summary)

With 3D data and metadata:

>>> # Multiple electrodes across trials
>>> data = np.random.randn(20, 64, 5000)  # 20 trials, 64 channels, 5000 samples
>>> metadata = {'condition': ['rest']*10 + ['task']*10}
>>> btg = BiotunerGroup(data, sf=1000, axis_labels=['trials', 'electrodes'], metadata=metadata)
>>> btg.compute_peaks(peaks_function='EMD')
>>> btg.compute_metrics()
>>>
>>> # Compare groups
>>> comparison = btg.compare_groups('condition', metric='harmsim')
>>>
>>> # Plot
>>> btg.plot_metric_distribution('harmsim', groupby='condition')

Memory-efficient mode for large datasets:

>>> btg = BiotunerGroup(data, sf=1000, store_objects=False)
>>> btg.compute_peaks()
>>> summary = btg.summary()  # Only summary stats kept in memory
compute_peaks(peaks_function: str | None = None, min_freq: float = 1, max_freq: float = 60, precision: float | None = None, n_peaks: int = 5, n_jobs: int = 1, verbose: bool = False, **kwargs) BiotunerGroup[source]#

Extract spectral peaks for all time series.

Parameters:
  • peaks_function (str, optional) – Peak extraction method. If None, uses value from biotuner_kwargs or ‘EMD’

  • min_freq (float, default=1) – Minimum frequency for peak extraction (Hz)

  • max_freq (float, default=60) – Maximum frequency for peak extraction (Hz)

  • precision (float, optional) – Frequency precision (Hz). If None, uses value from biotuner_kwargs or 0.1

  • n_peaks (int, default=5) – Number of peaks to extract

  • n_jobs (int, default=1) – Number of parallel jobs. -1 uses all CPUs, 1 for sequential processing. Recommended for large datasets (>50 series) or FOOOF peak detection.

  • verbose (bool, default=False) – Print progress

  • **kwargs (dict) – Additional parameters for peaks_extraction

Returns:

self (BiotunerGroup) – Returns self for method chaining

compute_extension(method: str = 'consonant_harmonic_fit', n_harm: int | None = None, n_jobs: int = 1, verbose: bool = False, **kwargs) BiotunerGroup[source]#

Compute peak extensions for all time series.

Parameters:
  • method (str, default=’consonant_harmonic_fit’) – Extension method: ‘harmonic_fit’, ‘consonant’, ‘multi_consonant’, ‘consonant_harmonic_fit’, ‘multi_consonant_harmonic_fit’

  • n_harm (int, optional) – Number of harmonics. If None, uses value from biotuner_kwargs or 10

  • n_jobs (int, default=1) – Number of parallel jobs. -1 uses all CPUs, 1 for sequential processing.

  • verbose (bool, default=False) – Print progress

  • **kwargs (dict) – Additional parameters for peaks_extension

Returns:

self (BiotunerGroup) – Returns self for method chaining

compute_metrics(n_harm: int | None = None, delta_lim: int = 20, n_jobs: int = 1, verbose: bool = False, **kwargs) BiotunerGroup[source]#

Compute peak metrics for all time series.

Parameters:
  • n_harm (int, optional) – Number of harmonics. If None, uses value from biotuner_kwargs or 10

  • delta_lim (int, default=20) – Delta limit for subharmonic tension calculation

  • n_jobs (int, default=1) – Number of parallel jobs. -1 uses all CPUs, 1 for sequential processing.

  • verbose (bool, default=False) – Print progress

  • **kwargs (dict) – Additional parameters for compute_peaks_metrics

Returns:

self (BiotunerGroup) – Returns self for method chaining

compute_diss_curve(input_type: str = 'peaks', denom: int = 1000, max_ratio: float = 2, n_jobs: int = 1, verbose: bool = False, **kwargs) BiotunerGroup[source]#

Compute dissonance curves for all time series.

Parameters:
  • input_type (str, default=’peaks’) – Input type: ‘peaks’ or ‘extended_peaks’

  • denom (int, default=1000) – Denominator for dissonance curve resolution

  • max_ratio (float, default=2) – Maximum ratio (typically octave = 2)

  • n_jobs (int, default=1) – Number of parallel jobs. -1 uses all CPUs, 1 for sequential processing.

  • verbose (bool, default=False) – Print progress

  • **kwargs (dict) – Additional parameters for compute_diss_curve

Returns:

self (BiotunerGroup) – Returns self for method chaining

compute_harmonic_entropy(input_type: str = 'peaks', res: float = 0.001, spread: float = 0.01, octave: float = 2, verbose: bool = False, **kwargs) BiotunerGroup[source]#

Compute harmonic entropy for all time series.

Parameters:
  • input_type (str, default=’peaks’) – Input type: ‘peaks’ or ‘extended_peaks’

  • res (float, default=0.001) – Resolution of ratio steps

  • spread (float, default=0.01) – Spread of normal distribution

  • octave (float, default=2) – Octave value

  • verbose (bool, default=False) – Print progress

  • **kwargs (dict) – Additional parameters for compute_harmonic_entropy

Returns:

self (BiotunerGroup) – Returns self for method chaining

compute_euler_fokker(method: str = 'peaks', octave: float = 2, verbose: bool = False) BiotunerGroup[source]#

Compute Euler-Fokker scales for all time series.

Parameters:
  • method (str, default=’peaks’) – Method: ‘peaks’ or ‘extended_peaks’

  • octave (float, default=2) – Period interval

  • verbose (bool, default=False) – Print progress

Returns:

self (BiotunerGroup) – Returns self for method chaining

compute_harmonic_tuning(list_harmonics: List[int] | None = None, octave: float = 2, min_ratio: float = 1, max_ratio: float = 2, verbose: bool = False) BiotunerGroup[source]#

Compute harmonic tunings for all time series.

Parameters:
  • list_harmonics (list of int, optional) – Harmonic positions for scale construction

  • octave (float, default=2) – Period reference

  • min_ratio (float, default=1) – Unison value

  • max_ratio (float, default=2) – Octave value

  • verbose (bool, default=False) – Print progress

Returns:

self (BiotunerGroup) – Returns self for method chaining

get_attribute(attr_name: str, as_array: bool = False, missing_value: any = nan) List | ndarray[source]#

Get an attribute from all biotuner objects.

Parameters:
  • attr_name (str) – Attribute name (e.g., ‘peaks’, ‘peaks_ratios’, ‘peaks_metrics’)

  • as_array (bool, default=False) – If True, attempt to stack results as numpy array. Only works if all results have the same shape.

  • missing_value (any, default=np.nan) – Value to use if attribute doesn’t exist for an object

Returns:

results (list or ndarray) – List or array of attribute values from all objects

Examples

>>> peaks_all = btg.get_attribute('peaks')
>>> amps_all = btg.get_attribute('amps')
>>> ratios_all = btg.get_attribute('peaks_ratios')
summary(metrics: List[str] | None = None, aggregation: str | List[str] = 'all', include_index: bool = True) DataFrame[source]#

Generate summary DataFrame with aggregated metrics.

Parameters:
  • metrics (list of str, optional) – Which metrics to include. If None, includes all available metrics. Examples: [‘harmsim’, ‘cons’, ‘tenney’, ‘euler’, ‘n_peaks’]

  • aggregation (str or list of str, default=’all’) – Aggregation methods to apply: - ‘mean’, ‘std’, ‘median’, ‘min’, ‘max’, ‘sem’ - ‘all’: computes all of the above - list: specific subset, e.g., [‘mean’, ‘std’]

  • include_index (bool, default=True) – Include index columns and metadata in output

Returns:

df (pandas.DataFrame) – Summary DataFrame with one row per time series

Examples

>>> # Get all statistics
>>> summary = btg.summary()
>>>
>>> # Get specific metrics and stats
>>> summary = btg.summary(metrics=['harmsim', 'cons'], aggregation=['mean', 'std'])
>>>
>>> # Compare across conditions
>>> summary = btg.summary()
>>> summary.groupby('condition').mean()
tuning_summary(include_index: bool = True) DataFrame[source]#

Generate summary DataFrame focused on tuning/scale metrics.

This is a convenience method that extracts only scale-related metrics from the full summary, making it easier to analyze tuning characteristics across time series.

Parameters:

include_index (bool, default=True) – Include index columns and metadata in output

Returns:

df (pandas.DataFrame) – Summary DataFrame with tuning metrics (scale_* columns)

Examples

>>> # Compute tunings
>>> btg.compute_diss_curve().compute_harmonic_entropy()
>>>
>>> # Get tuning metrics summary
>>> tuning_df = btg.tuning_summary()
>>>
>>> # Compare tuning metrics across conditions
>>> tuning_df.groupby('condition').mean()
get_tuning_scales(scale_type: str = 'diss_scale') List[source]#

Extract tuning scales from all biotuner objects.

Parameters:

scale_type (str, default=’diss_scale’) – Type of scale to extract: - ‘diss_scale’: Dissonance curve scale - ‘HE_scale’: Harmonic entropy scale - ‘euler_fokker’: Euler-Fokker scale - ‘harm_tuning_scale’: Harmonic tuning scale

Returns:

scales (list) – List of scales (each scale is a list of ratios)

Examples

>>> btg.compute_diss_curve()
>>> scales = btg.get_tuning_scales('diss_scale')
>>>
>>> # Analyze scale sizes
>>> scale_sizes = [len(s) for s in scales]
>>> print(f"Mean scale size: {np.mean(scale_sizes):.1f} steps")
compare_groups(groupby: str, metric: str = 'harmsim', test: str = 'ttest', alpha: float = 0.05, plot: bool = True) DataFrame[source]#

Compare metrics across metadata groups.

Parameters:
  • groupby (str) – Metadata column to group by

  • metric (str, default=’harmsim’) – Metric to compare

  • test (str, default=’ttest’) – Statistical test: ‘ttest’, ‘anova’, ‘mannwhitneyu’, ‘kruskal’

  • alpha (float, default=0.05) – Significance level

  • plot (bool, default=True) – Generate comparison plot

Returns:

results (pandas.DataFrame) – Comparison results with statistics

Examples

>>> comparison = btg.compare_groups('condition', metric='harmsim_mean')
>>> print(comparison)
plot_group_peaks(show_individual: bool = False, aggregate: str = 'mean', xmin: float = 1, xmax: float = 60, show_bands: bool = True, figsize: Tuple[float, float] | None = None, alpha_individual: float | None = None, **kwargs)[source]#

Plot aggregated peak spectrum across all time series.

Parameters:
  • show_individual (bool, default=False) – Show individual PSDs in background

  • aggregate (str, default=’mean’) – Aggregation method: ‘mean’, ‘median’

  • xmin (float, default=1) – Minimum frequency (Hz)

  • xmax (float, default=60) – Maximum frequency (Hz)

  • show_bands (bool, default=True) – Show frequency band labels

  • figsize (tuple, optional) – Figure size (default from plot_config)

  • alpha_individual (float, optional) – Transparency for individual traces (default from plot_config)

  • **kwargs (dict) – Additional plotting parameters

Returns:

fig, ax (matplotlib figure and axes)

plot_peak_distribution(xmin: float = 1, xmax: float = 60, bins: int | None = None, show_bands: bool = True, figsize: Tuple[float, float] | None = None, **kwargs)[source]#

Plot distribution of all detected peaks across all time series.

Parameters:
  • xmin (float, default=1) – Minimum frequency (Hz)

  • xmax (float, default=60) – Maximum frequency (Hz)

  • bins (int, optional) – Number of histogram bins (default from plot_config)

  • show_bands (bool, default=True) – Show frequency band markers

  • figsize (tuple, optional) – Figure size (default from plot_config)

  • **kwargs (dict) – Additional plotting parameters

Returns:

fig, ax (matplotlib figure and axes)

plot_metric_distribution(metric: str, groupby: str | None = None, kind: str = 'violin', figsize: Tuple[float, float] | None = None, **kwargs)[source]#

Plot distribution of a metric across time series.

Parameters:
  • metric (str) – Metric to plot (column name from summary DataFrame)

  • groupby (str, optional) – Metadata column to group by

  • kind (str, default=’violin’) – Plot type: ‘violin’, ‘box’, ‘strip’, ‘swarm’, ‘bar’

  • figsize (tuple, optional) – Figure size (default from plot_config)

  • **kwargs (dict) – Additional plotting parameters passed to seaborn

Returns:

fig, ax (matplotlib figure and axes)

plot_metric_matrix(metric: str = 'harmsim_mean', cmap: str | None = None, figsize: Tuple[float, float] | None = None, **kwargs)[source]#

Plot metric as heatmap (for 3D data: trials × electrodes).

Parameters:
  • metric (str, default=’harmsim_mean’) – Metric to plot

  • cmap (str, optional) – Colormap (default from plot_config)

  • figsize (tuple, optional) – Figure size (calculated dynamically if not provided)

  • **kwargs (dict) – Additional parameters for sns.heatmap

Returns:

fig, ax (matplotlib figure and axes)

plot_interval_histogram(scale_type: str = 'diss_scale', max_denom: int = 100, bins: int | None = None, figsize: Tuple[float, float] | None = None, show_common: bool = True, groupby: str | None = None)[source]#

Plot histogram of all intervals across time series to identify most recurrent intervals.

Parameters:
  • scale_type (str, default=’diss_scale’) – Type of scale: ‘diss_scale’, ‘HE_scale’, ‘euler_fokker’, ‘harm_tuning_scale’

  • max_denom (int, default=100) – Maximum denominator for fraction simplification

  • bins (int, default=50) – Number of bins for histogram

  • figsize (tuple, default=(14, 6)) – Figure size

  • show_common (bool, default=True) – Annotate most common intervals

  • groupby (str, optional) – Metadata column to color by groups

Returns:

fig, ax (matplotlib figure and axes)

Examples

>>> btg.compute_diss_curve()
>>> fig, ax = btg.plot_interval_histogram('diss_scale')
>>> plt.show()
plot_scale_size_distribution(scale_type: str = 'diss_scale', figsize: Tuple[float, float] | None = None, groupby: str | None = None)[source]#

Plot distribution of scale sizes (number of notes) across time series.

Parameters:
  • scale_type (str, default=’diss_scale’) – Type of scale to analyze

  • figsize (tuple, default=(12, 6)) – Figure size

  • groupby (str, optional) – Metadata column to group by

Returns:

fig, ax (matplotlib figure and axes)

plot_common_intervals(scale_type: str = 'diss_scale', top_n: int = 15, max_denom: int = 100, figsize: Tuple[float, float] | None = None, min_occurrence: int = 2)[source]#

Plot bar chart of most common intervals across all time series.

Parameters:
  • scale_type (str, default=’diss_scale’) – Type of scale to analyze

  • top_n (int, default=15) – Number of top intervals to show

  • max_denom (int, default=100) – Maximum denominator for fractions

  • figsize (tuple, default=(12, 8)) – Figure size

  • min_occurrence (int, default=2) – Minimum number of occurrences to include

Returns:

fig, ax (matplotlib figure and axes)

plot_tuning_comparison(scale_type: str = 'diss_scale', indices: List[int] | None = None, max_denom: int = 100, figsize: Tuple[float, float] | None = None)[source]#

Compare tuning scales from multiple time series side by side.

Parameters:
  • scale_type (str, default=’diss_scale’) – Type of scale to compare

  • indices (list of int, optional) – Which time series to compare (default: first 5)

  • max_denom (int, default=100) – Maximum denominator for fractions

  • figsize (tuple, default=(14, 8)) – Figure size

Returns:

fig, ax (matplotlib figure and axes)

Overview#

BiotunerGroup is a powerful class for batch processing multiple time series with the Biotuner framework. It extends the single-object compute_biotuner to handle multi-dimensional datasets with automatic aggregation, group comparisons, and unified visualizations.

Key Features#

✅ Batch Processing - Run any biotuner method on multiple time series simultaneously ✅ Automatic Aggregation - Summary statistics computed across all series ✅ Metadata Support - Track experimental conditions, subjects, electrodes, etc. ✅ Group Comparisons - Statistical testing between conditions ✅ Unified Visualizations - Consistent, publication-ready plots ✅ Method Chaining - Clean, Pythonic API ✅ Memory Efficient - Option to store only summaries for large datasets ✅ Individual Access - Can still analyze individual biotuner objects

Quick Start#

Basic 2D Example#

import numpy as np
from biotuner import BiotunerGroup

# Your data: 10 trials × 5000 samples
data = np.random.randn(10, 5000)

# Create group object
btg = BiotunerGroup(
    data=data,
    sf=1000,  # Sampling frequency
    axis_labels=['trials']
)

# Run analysis pipeline (method chaining!)
btg.compute_peaks(peaks_function='FOOOF', min_freq=1, max_freq=50)
btg.compute_metrics(n_harm=10)

# Get summary statistics
summary = btg.summary()
print(summary.head())

3D Example with Metadata#

# Multiple electrodes across trials: 20 trials × 64 channels × 5000 samples
data = np.random.randn(20, 64, 5000)

metadata = {
    'condition': ['rest']*10 + ['task']*10
}

btg = BiotunerGroup(
    data,
    sf=1000,
    axis_labels=['trials', 'electrodes'],
    metadata=metadata
)

# Compute peaks and metrics
btg.compute_peaks(peaks_function='EMD')
btg.compute_metrics()

# Compare groups
comparison = btg.compare_groups('condition', metrics=['harm_sim', 'tenney'])
print(comparison)

Main Methods#

Data Processing#

  • compute_peaks() - Extract spectral peaks from all series

  • compute_extension() - Extend peaks to harmonic series

  • compute_metrics() - Calculate harmonicity metrics

  • compute_diss_curve() - Compute dissonance curves

  • compute_harmonic_entropy() - Calculate harmonic entropy

  • compute_euler_fokker() - Compute Euler-Fokker genus metrics

  • compute_harmonic_tuning() - Extract harmonic tuning information

Analysis & Comparison#

  • summary() - Get aggregated statistics across all series

  • tuning_summary() - Get musical tuning parameters

  • get_tuning_scales() - Extract scale information

  • compare_groups() - Statistical comparison between groups

  • get_attribute() - Access specific attributes from all objects

Visualization#

  • plot_peaks() - Visualize peaks across series

  • plot_metric_comparison() - Compare metrics between groups

  • plot_euler_fokker() - Visualize Euler-Fokker genus

  • plot_tuning_comparison() - Compare tuning across conditions

  • plot_harmonic_spectrum() - Display harmonic spectra

See Also#