Visualization Tutorial#

This tutorial covers NeuRodent’s comprehensive visualization capabilities for EEG analysis results.

Overview#

NeuRodent provides two main plotting classes:

  1. AnimalPlotter: Visualize data from a single animal

  2. ExperimentPlotter: Compare data across multiple animals with grouping

  3. ZeitgeberPlotter: Circadian rhythm plots using Zeitgeber Time (ZT)

Both support various plot types: time series, categorical plots, heatmaps, and more.

import sys
from pathlib import Path
import logging

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

from neurodent import core, visualization, constants

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger()

# Set plotting style
sns.set_style("whitegrid")
plt.rcParams['figure.figsize'] = (12, 6)
/home/runner/work/neurodent/neurodent/.venv/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm

1. Single Animal Visualization (AnimalPlotter)#

Setup#

# Load WAR for a single animal
war_path = Path("/path/to/war/animal_001")
war = visualization.WindowAnalysisResult.load_pickle_and_json(war_path)

# Create plotter
ap = visualization.AnimalPlotter(war)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[2], line 3
      1 # Load WAR for a single animal
      2 war_path = Path("/path/to/war/animal_001")
----> 3 war = visualization.WindowAnalysisResult.load_pickle_and_json(war_path)
      5 # Create plotter
      6 ap = visualization.AnimalPlotter(war)

File ~/work/neurodent/neurodent/src/neurodent/visualization/results.py:4043, in WindowAnalysisResult.load_pickle_and_json(cls, folder_path, pickle_name, json_name)
   4041 folder_path = Path(folder_path)
   4042 if not folder_path.exists():
-> 4043     raise ValueError(f"Folder path {folder_path} does not exist")
   4045 if pickle_name is not None:
   4046     # Handle pickle_name as either absolute path or relative to folder_path
   4047     pickle_path = Path(pickle_name)

ValueError: Folder path /path/to/war/animal_001 does not exist

Time Series Plots#

Visualize features over time:

# Plot RMS amplitude over time
fig = ap.plot_feature_over_time('rms')
plt.title("RMS Amplitude Over Time")
plt.show()

# Plot log RMS
fig = ap.plot_feature_over_time('logrms')
plt.title("Log RMS Amplitude Over Time")
plt.show()

Band Power Visualization#

Display power across frequency bands:

# Plot band powers
fig = ap.plot_psdband()
plt.title("Power Spectral Density by Band")
plt.show()

# Plot band powers over time
fig = ap.plot_psdband_over_time()
plt.title("Band Power Over Time")
plt.show()

2. Multi-Animal Comparison (ExperimentPlotter)#

Setup#

# Load multiple WARs
war_paths = [
    Path("/path/to/war/animal_001"),
    Path("/path/to/war/animal_002"),
    Path("/path/to/war/animal_003"),
]

wars = [
    visualization.WindowAnalysisResult.load_pickle_and_json(path)
    for path in war_paths
]

# Create experiment plotter
ep = visualization.ExperimentPlotter(
    wars,
    exclude=['nspike', 'lognspike']  # Exclude features
)

Categorical Plots#

Compare features across groups using box plots, violin plots, swarm plots, etc.

# Box plot grouped by genotype
g = ep.plot_catplot(
    'rms',
    groupby='genotype',
    kind='box',
    catplot_params={'showfliers': False}
)
plt.suptitle("RMS by Genotype")
plt.show()

# Violin plot with day/night comparison
g = ep.plot_catplot(
    'rms',
    groupby=['genotype', 'isday'],
    x='genotype',
    col='isday',
    kind='violin'
)
plt.suptitle("RMS by Genotype and Time of Day")
plt.show()

Band Power Comparisons#

# Box plot of band powers by genotype
g = ep.plot_catplot(
    'psdband',
    groupby='genotype',
    x='genotype',
    hue='band',
    kind='box',
    collapse_channels=True,
    catplot_params={'showfliers': False}
)
plt.suptitle("Band Power by Genotype")
plt.show()

# With day/night split
g = ep.plot_catplot(
    'psdband',
    groupby=['genotype', 'isday'],
    x='genotype',
    col='isday',
    hue='band',
    kind='box',
    collapse_channels=True,
    catplot_params={'showfliers': False}
)
plt.suptitle("Band Power by Genotype and Time")
plt.show()

Swarm and Point Plots#

Show individual data points with averages:

# Swarm plot with animal-level averaging
g = ep.plot_catplot(
    'rms',
    groupby=['animal', 'genotype'],
    x='genotype',
    hue='channel',
    kind='swarm',
    average_groupby=True,
    collapse_channels=False,
    catplot_params={'dodge': True, 'errorbar': 'ci'}
)
plt.suptitle("RMS by Genotype (Animal Averages)")
plt.show()

# Point plot with confidence intervals
g = ep.plot_catplot(
    'rms',
    groupby=['animal', 'genotype'],
    x='genotype',
    kind='point',
    average_groupby=True,
    collapse_channels=True,
    catplot_params={'errorbar': 'ci'}
)
plt.suptitle("RMS by Genotype (Mean ± CI)")
plt.show()

3. Connectivity Visualization#

Heatmaps for Coherence and Correlation#

# Coherence heatmap by genotype
g = ep.plot_heatmap(
    'cohere',
    groupby='genotype'
)
plt.suptitle("Coherence by Genotype")
plt.show()

# Pearson correlation heatmap
g = ep.plot_heatmap(
    'pcorr',
    groupby='genotype'
)
plt.suptitle("Correlation by Genotype")
plt.show()

# With day/night comparison
g = ep.plot_heatmap(
    'cohere',
    groupby=['genotype', 'isday']
)
plt.suptitle("Coherence by Genotype and Time")
plt.show()

Difference Heatmaps#

Show differences relative to a baseline condition:

# Difference from wildtype
g = ep.plot_diffheatmap(
    'cohere',
    groupby=['genotype', 'isday'],
    baseline_key='WT',
    baseline_groupby='genotype',
    remove_baseline=True
)
plt.suptitle("Coherence Difference from WT")
plt.show()

# By frequency band
g = ep.plot_diffheatmap(
    'cohere',
    groupby='genotype',
    baseline_key='WT',
    baseline_groupby='genotype',
    col='band',
    row='genotype',
    remove_baseline=True
)
plt.suptitle("Band-Specific Coherence Differences")
plt.show()

4. QQ Plots for Distribution Analysis#

# QQ plot by genotype and channel
g = ep.plot_qqplot(
    'rms',
    groupby=['genotype'],
    row='genotype',
    col='channel',
    height=3
)
plt.suptitle("RMS Distribution QQ Plot")
plt.show()

5. Customizing Plots#

Plot Parameters#

# Custom catplot parameters
custom_params = {
    'showfliers': False,
    'aspect': 2,
    'height': 5,
    'palette': 'Set2'
}

g = ep.plot_catplot(
    'rms',
    groupby='genotype',
    kind='box',
    catplot_params=custom_params
)
plt.show()

Channel Collapsing#

Average across channels:

# Without channel collapsing (show all channels)
g = ep.plot_catplot(
    'rms',
    groupby='genotype',
    kind='box',
    collapse_channels=False
)
plt.suptitle("RMS by Channel")
plt.show()

# With channel collapsing (average across channels)
g = ep.plot_catplot(
    'rms',
    groupby='genotype',
    kind='box',
    collapse_channels=True
)
plt.suptitle("RMS (Averaged Across Channels)")
plt.show()

6. Saving Figures#

Save high-quality figures for publications:

# Create output directory
output_folder = Path("./figures")
output_folder.mkdir(parents=True, exist_ok=True)

# Generate and save plot
g = ep.plot_catplot(
    'rms',
    groupby='genotype',
    kind='box',
    catplot_params={'showfliers': False}
)

# Save as PNG (high DPI for publications)
g.savefig(output_folder / 'rms_by_genotype.png', dpi=300, bbox_inches='tight')

# Save as PDF (vector format)
g.savefig(output_folder / 'rms_by_genotype.pdf', bbox_inches='tight')

print(f"Figures saved to {output_folder}")

7. Batch Figure Generation#

Generate multiple figures systematically:

# Generate plots for all linear features
for feature in constants.LINEAR_FEATURES:
    if feature not in ep.exclude:
        logger.info(f"Generating plot for {feature}")
        
        # Box plot
        g = ep.plot_catplot(
            feature,
            groupby='genotype',
            kind='box',
            collapse_channels=True,
            catplot_params={'showfliers': False}
        )
        g.savefig(
            output_folder / f'{feature}_genotype_box.png',
            dpi=300,
            bbox_inches='tight'
        )
        plt.close()

print("Batch generation complete!")

8. Advanced: Custom Plot Types#

Access underlying data for custom visualizations:

# Get aggregated data from ExperimentPlotter
df = ep.get_dataframe('rms', groupby='genotype')

# Create custom plot with matplotlib
fig, ax = plt.subplots(figsize=(10, 6))

# Plot using pandas/seaborn directly
sns.boxplot(data=df, x='genotype', y='rms', ax=ax)
ax.set_title("Custom RMS Plot")
ax.set_ylabel("RMS Amplitude")
plt.show()

9. Circadian Rhythms (ZeitgeberPlotter)#

Visualize circadian patterns using ZeitgeberPlotter. This specialized plotter handles the 48-hour duplicated time axis often used in circadian research.

# From DataFrame (workflow usage)
zp = visualization.ZeitgeberPlotter(df_zar)

# Plot single feature over 48h cycle
zp.plot_feature(
    'logrms_nobase',    # Baseline-subtracted feature
    output_path=output_folder / 'circadian_rms.png',
    figsize=(10, 6)
)
plt.show()

Summary#

This tutorial covered:

  1. Single animal visualization with AnimalPlotter

  2. Multi-animal comparisons with ExperimentPlotter

  3. Categorical plots (box, violin, swarm, point)

  4. Connectivity visualizations (heatmaps)

  5. Difference heatmaps

  6. Distribution analysis (QQ plots)

  7. Plot customization

  8. Saving figures

  9. Batch figure generation

  10. Custom plot types

Plot Type Reference#

AnimalPlotter Methods#

  • plot_feature_over_time(feature): Time series plot

  • plot_psdband(): Bar plot of band powers

  • plot_psdband_over_time(): Band powers over time

ExperimentPlotter Methods#

  • plot_catplot(feature, ...): Categorical plots (box, violin, swarm, point, strip)

  • plot_heatmap(feature, ...): Connectivity heatmaps

  • plot_diffheatmap(feature, ...): Difference heatmaps

  • plot_qqplot(feature, ...): Distribution QQ plots

Next Steps#