eegproc.featurization module

eegproc.featurization.choose_dwt_level(n_samples, fs, wavelet, min_freq)[source]

Choose a discrete wavelet transform (DWT) level given data length and a target minimum frequency.

The selected level is bounded by PyWavelets’ maximum permissible level for the given signal length and wavelet filter length, and ensures that the detail subband captures energy at or above min_freq.

Parameters:
  • n_samples (int) – Number of time-domain samples in the signal.

  • fs (float) – Sampling rate in Hz. Must be positive.

  • wavelet (str) – Name of a PyWavelets wavelet (e.g., "db4", "sym5").

  • min_freq (float) – Target minimum frequency (Hz) you still wish to capture in a detail subband. If non-positive, a tiny floor (1e-6) is used to avoid taking log2(0) and to return a conservative level.

Return type:

int

Returns:

int – The chosen DWT level (>= 1) that balances the maximum allowable level with the goal of preserving content at or above min_freq.

See also

pywt.dwt_max_level: Maximum level allowed by signal and filter length. pywt.Wavelet: Wavelet objects carrying filter lengths and properties.

eegproc.featurization.dwt_subband_ranges(fs, level)[source]

Return nominal frequency ranges (Hz) for DWT subbands up to a level.

Uses paired ranges for detail bands D{j} and the approximation band A{level}.

Parameters:
  • fs (float) – Sampling rate in Hz.

  • level (int) – Decomposition level (>= 1).

Return type:

dict[str, tuple[float, float]]

Returns:

dict[str, tuple[float, float]] – Mapping like {"D1": (f_lo, f_hi), ..., "A{level}": (0, f_c)}.

eegproc.featurization.feature_grouped_by_metadata(eeg_df, target_function=<function psd_bandpowers>, fs=128, bands={'alpha': (8.0, 13.0), 'betaH': (20.0, 30.0), 'betaL': (13.0, 20.0), 'delta': (0.5, 4.0), 'gamma': (30.0, 45.0), 'theta': (4.0, 8.0)}, channels=None, group_by_metadata_columns=None, drop_metadata_for_fn=True, verbose=False, **fn_kwargs)[source]

Group eeg_df by group_by_metadata_columns, run target_function on each group’s EEG slice, and prepend the group keys to every output row.

Parameters:
  • eeg_df (DataFrame) – Input with EEG columns (and possibly metadata columns).

  • target_function (Callable[..., DataFrame], default: <function psd_bandpowers at 0x7f56c8836480>) – Function that takes (df, fs=..., bands=..., **kwargs) and returns a pandas.DataFrame of features. Example: psd_bandpowers.

  • fs (int, default: 128) – Sampling frequency to pass to target_function.

  • bands (dict[str, tuple[float, float]], default: {'delta': (0.5, 4.0), 'theta': (4.0, 8.0), 'alpha': (8.0, 13.0), 'betaL': (13.0, 20.0), 'betaH': (20.0, 30.0), 'gamma': (30.0, 45.0)}) – Bands mapping to pass to target_function (if it uses it).

  • channels (list[str] | None, default: None) – If provided, restrict input columns to these (metadata cols are always kept for grouping).

  • group_by_metadata_columns (list[str] | None, default: None) – Columns to group by (e.g., [“patient_id”, “video_id”]). If None, process the whole df once.

  • drop_metadata_for_fn (bool, default: True) – If True, drop the group-by columns before calling target_function. Set False if your function can safely ignore extra columns.

  • verbose (bool, default: False) – Print a single-line progress display while processing groups.

  • **fn_kwargs – Extra keyword args forwarded to target_function.

Return type:

DataFrame

Returns:

pd.DataFrame – Concatenation of per-group outputs, with metadata keys as leading columns.

Examples

Band power for each (subject, trial) group of a band-filtered table:

>>> import numpy as np, pandas as pd
>>> from eegproc import (FREQUENCY_BANDS, bandpass_filter,
...                      feature_grouped_by_metadata, psd_bandpowers)
>>> fs = 128
>>> raw = pd.DataFrame({"AF3": np.random.randn(8 * fs), "F7": np.random.randn(8 * fs)})
>>> clean = bandpass_filter(raw, fs, bands=FREQUENCY_BANDS).assign(subject="P01", trial=1)
>>> feats = feature_grouped_by_metadata(
...     clean, target_function=psd_bandpowers, fs=fs,
...     group_by_metadata_columns=["subject", "trial"],
... )
>>> feats.columns[:3].tolist()
['subject', 'trial', 'AF3_delta']
eegproc.featurization.generate_all_features(raw_eeg_df, fs, bands={'alpha': (8.0, 13.0), 'betaH': (20.0, 30.0), 'betaL': (13.0, 20.0), 'delta': (0.5, 4.0), 'gamma': (30.0, 45.0), 'theta': (4.0, 8.0)}, channels=None, group_by_metadata_columns=None, verbose=False)[source]

Compute the full feature set for an EEG table, optionally grouped by metadata.

Runs the whole featurization pipeline per group and concatenates the result: band-pass with notch, then PSD bandpowers, Shannon entropy, Hjorth parameters, wavelet band energy and entropy, and IMF band energy and entropy.

Parameters:
  • raw_eeg_df (DataFrame) – Raw EEG, one column per electrode, plus any metadata columns.

  • fs (int) – Sampling rate in Hz.

  • bands (dict[str, tuple[float, float]] | None, default: {'delta': (0.5, 4.0), 'theta': (4.0, 8.0), 'alpha': (8.0, 13.0), 'betaL': (13.0, 20.0), 'betaH': (20.0, 30.0), 'gamma': (30.0, 45.0)}) – Band vocabulary; defaults to FREQUENCY_BANDS.

  • channels (list[str] | None, default: None) – Restrict processing to these electrode columns. Grouping columns are always retained.

  • group_by_metadata_columns (list[str] | None, default: None) – Columns identifying a group, e.g. ["subject", "trial"]. When omitted, the whole table is one group.

  • verbose (bool, default: False) – Print a single-line progress display while processing groups.

Return type:

DataFrame

Returns:

pandas.DataFrame – One row per (group, window): the metadata columns followed by every feature column.

Examples

>>> feats = generate_all_features(
...     raw_df, fs=128, group_by_metadata_columns=["subject", "trial"],
... )
eegproc.featurization.hjorth_params(df, fs, window_sec=4.0, overlap=0.5, detrend='constant', eps=1e-300)[source]

Compute Hjorth parameters (activity, mobility, complexity) per channel over windows.

Expects columns named {channel}_{band} where each band is a key in bands (bandpass_filter) may be used to achieve the expected table. For each numeric column, the function computes: - Activity: variance of the signal - Mobility: sqrt(var(Δx) / var(x)) - Complexity: mobility(Δx) / mobility(x)

Parameters:
  • df (DataFrame) – Bandpass Filtered EEG dataframe. Numeric columns must be named like {channel}_{band} (e.g., AF3_alpha).

  • fs (float) – Sampling rate in Hz.

  • window_sec (float, default: 4.0) – Window length in seconds.

  • overlap (float, default: 0.5) – Fractional overlap in [0, 1) between windows.

  • detrend (str | None, default: 'constant') – Detrending applied per column prior to differencing.

  • eps (float, default: 1e-300) – Numerical guard to prevent division by zero.

Return type:

DataFrame

Returns:

pandas.DataFrame – One row per window, with columns: {channel}_activity, {channel}_mobility, {channel}_complexity.

Raises:

ValueError – If window is too small (needs >= 3 samples) or overlap is invalid.

eegproc.featurization.imf_band_energy(df, fs, imf_to_band=['gamma', 'betaH', 'betaL', 'alpha', 'theta', 'delta'], window_sec=4.0, overlap=0.5, EMD_kwargs={})[source]

Compute Intrinsic Mode Function (IMF) band-energy distributions per channel using Empirical Mode Decomposition (EMD).

Expects columns named {channel} where each row is a reading of raw EEG data. For each column, applies EMD decomposition to find mean m(t) of envelopes (cubic splines fit to extremas) and takes difference between raw data point and m(t) [d(t) = x(t) - m(t)]. For each window and channel, the IMF band-energy is based on the cumulative sums of the IMFs returned by the EMD function.

Parameters:
  • df (DataFrame) – Raw data EEG dataframe. Numeric columns.

  • fs (float) – Sampling rate in Hz (used only for window sizing).

  • imf_to_band (list[str], default=["gamma", "betaH", "betaL", "alpha", "theta", "delta"]) – Labels to assign to IMFs 1..K (K = number of labels).

  • window_sec (float, default: 4.0) – Window length in seconds.

  • overlap (float, default: 0.5) – Fractional overlap in [0, 1).

  • EMD_kwargs (dict, default: {}) – Extra keyword arguments passed to PyEMD.EMD (e.g., max_imf is internally set).

Return type:

DataFrame

Returns:

pandas.DataFrame – One row per window; columns: {channel}_{band}_imfenergy for each band label.

Raises:

ValueError – If window too small, overlap invalid, or window longer than available samples.

Notes

  • IMF indices are assigned in order to the names in imf_to_band; if fewer IMFs are present than labels, missing energies are filled with 0.0.

  • The EMD is computed once per full signal and reused for all windows via cumulative sums.

eegproc.featurization.imf_entropy(imf_energy_df, bands=['gamma', 'betaH', 'betaL', 'alpha', 'theta', 'delta'], normalize=True, eps=1e-300)[source]

Compute (normalized) Shannon entropy of IMF energy distributions per channel.

For each window and channel, uses the vector of _imfenergy values across bands as a distribution and computes -Σplogp. Optionally normalizes by log(K) where K = len(bands).

Parameters:
  • imf_energy_df (DataFrame) – Output from imf_band_energy() with columns {ch}_{band}_imfenergy.

  • bands (list[str], default=["gamma", "betaH", "betaL", "alpha", "theta", "delta"]) – Band labels (order defines component order).

  • normalize (bool, default: True) – If True, divide entropy by log(K) to obtain values in [0, 1].

  • eps (float, default: 1e-300) – Numerical guard for zero energies.

Return type:

DataFrame

Returns:

pandas.DataFrame – One row per window; columns {ch}_imfentropy.

eegproc.featurization.psd_bandpowers(df, fs, bands={'alpha': (8.0, 13.0), 'betaH': (20.0, 30.0), 'betaL': (13.0, 20.0), 'delta': (0.5, 4.0), 'gamma': (30.0, 45.0), 'theta': (4.0, 8.0)}, window_sec=4.0, overlap=0.5, detrend='constant')[source]

Compute Welch PSD band powers per channel-band column over selected window size.

Expects columns named {channel}_{band} where each band is a key in bands (bandpass_filter) may be used to achieve the expected table. Each row of the expected df is a wave amplitude reading of the channel-band combination. For each window, integrates the Welch PSD within each band using the trapezoid rule.

Parameters:
  • df (DataFrame) – Bandpass Filtered EEG dataframe. Numeric columns must be named like {channel}_{band} (e.g., AF3_alpha).

  • fs (float) – Sampling rate in Hz.

  • bands (dict[str, tuple[float, float]], default: {'delta': (0.5, 4.0), 'theta': (4.0, 8.0), 'alpha': (8.0, 13.0), 'betaL': (13.0, 20.0), 'betaH': (20.0, 30.0), 'gamma': (30.0, 45.0)}) – Mapping of band name to inclusive frequency bounds (Hz) (lo, hi).

  • window_sec (float, default: 4.0) – Window length in seconds used for Welch’s method (nperseg = round(fs*window_sec)).

  • overlap (float, default: 0.5) – Fractional overlap in [0, 1) between consecutive windows.

  • detrend (str | None, default: 'constant') – Detrending applied before PSD computation via apply_detrend.

Return type:

DataFrame

Returns:

pandas.DataFrame – One row per window, columns match the input band columns (e.g., AF3_alpha) and contain power spectral density-integrated band powers.

Raises:

ValueError – If no valid {channel}_{band} columns are found, if the window is too small, or if overlap is outside [0, 1).

Notes

  • Uses scipy.signal.welch with a Hann window and no overlap inside the Welch call (windowing/overlap are controlled at the outer sliding-window level).

Examples

Minimal example with synthetic data (two channels, one band):

>>> import numpy as np, pandas as pd
>>> fs = 128.0
>>> t = np.arange(int(8*fs)) / fs   # 8 seconds
>>> # Two synthetic signals with an ~10 Hz component (alpha band)
>>> af3_alpha = 0.8*np.sin(2*np.pi*10*t) + 0.1*np.random.randn(t.size)
>>> f7_alpha  = 0.6*np.sin(2*np.pi*10*t + 0.7) + 0.1*np.random.randn(t.size)
>>> df = pd.DataFrame({
...     "AF3_alpha": af3_alpha,
...     "F7_alpha":  f7_alpha,
... })
>>> bands = {"alpha": (8.0, 12.0)}
>>> out = psd_bandpowers(df, fs=fs, bands=bands, window_sec=2.0, overlap=0.5)
>>> out.head()
       AF3_alpha   F7_alpha
0       ...         ...
1       ...         ...
2       ...         ...
eegproc.featurization.shannons_entropy(psd_df, bands={'alpha': (8.0, 13.0), 'betaH': (20.0, 30.0), 'betaL': (13.0, 20.0), 'delta': (0.5, 4.0), 'gamma': (30.0, 45.0), 'theta': (4.0, 8.0)}, eps=1e-300, assume_log=False)[source]

Measure how evenly a channel’s energy is spread across its frequency bands.

Takes a PSD table (the output of psd_bandpowers()), not a raw signal. Each row is one window; for every channel the band energies in that row are normalized into a probability distribution and reduced to a single normalized Shannon entropy in [0, 1]. A value near 1 means energy is spread evenly across bands; near 0 means it is concentrated in one band.

This is entropy across bands, one value per channel — not entropy within each band. A channel with fewer than two band columns has no distribution to measure and yields NaN.

Parameters:
  • psd_df (DataFrame) – PSD table with columns named {channel}_{band}, where band is a key of bands (e.g. AF3_alpha). Columns that do not match this pattern (metadata, indices) are ignored.

  • bands (dict[str, tuple[float, float]], default: {'delta': (0.5, 4.0), 'theta': (4.0, 8.0), 'alpha': (8.0, 13.0), 'betaL': (13.0, 20.0), 'betaH': (20.0, 30.0), 'gamma': (30.0, 45.0)}) – Band vocabulary. Only the keys are used here, to decide which columns are band columns and in what order they are read.

  • eps (float, default: 1e-300) – Numerical guard against log(0) and division by zero.

  • assume_log (bool | None, default: False) – Whether psd_df holds log-power. False treats values as linear power, True exponentiates first, and None infers it from the fraction of negative values.

Return type:

DataFrame

Returns:

pandas.DataFrame – One row per input row, indexed like psd_df. One column per channel, named {channel}_entropy.

Examples

>>> import pandas as pd
>>> from eegproc import psd_bandpowers, shannons_entropy, bandpass_filter
>>> clean = bandpass_filter(raw_df, fs=128)
>>> psd = psd_bandpowers(clean, fs=128)
>>> shannons_entropy(psd).columns.tolist()
['AF3_entropy', 'F7_entropy']

Energy spread evenly across bands is the maximum-entropy case:

>>> psd = pd.DataFrame({"AF3_delta": [1.0], "AF3_theta": [1.0],
...                     "AF3_alpha": [1.0], "AF3_betaL": [1.0],
...                     "AF3_betaH": [1.0], "AF3_gamma": [1.0]})
>>> float(shannons_entropy(psd)["AF3_entropy"].iloc[0])
1.0

Notes

  • Entropy is normalized by log2(n_bands), so results are comparable across channels only when they share the same band vocabulary.

  • Changed in v2: this function previously accepted a raw signal with fs/window_sec/overlap and emitted {channel}_{band}_entropy. It now consumes a PSD table and emits {channel}_entropy.

eegproc.featurization.wavelet_band_energy(df, fs, bands, wavelet='db4', mode='periodization', window_sec=4.0, overlap=0.5)[source]

Compute band energies by distributing DWT subband energies into target bands.

Expects columns named {channel} where each row is a reading of raw EEG data. For each window and channel, performs a multilevel DWT, computes energy in each detail/approximation subband, then proportionally assigns subband energy to user bands by frequency overlap.

Parameters:
  • df (DataFrame) – Raw data EEG dataframe. Numeric columns.

  • fs (float) – Sampling rate in Hz.

  • bands (dict[str, tuple[float, float]]) – Target bands as {name: (lo, hi)} in Hz.

  • wavelet (str, default: 'db4') – PyWavelets wavelet name for pywt.wavedec.

  • mode (str, default: 'periodization') – Signal extension mode for DWT.

  • window_sec (float, default: 4.0) – Window length in seconds.

  • overlap (float, default: 0.5) – Fractional overlap in [0, 1).

Return type:

DataFrame

Returns:

pandas.DataFrame – One row per window; columns: {channel}_{band}_wenergy for each channel in df and each band in bands.

Raises:

ValueError – If window too small, overlap invalid, or window longer than available samples.

Notes

  • The DWT level is chosen automatically via choose_dwt_level() to respect both data length and minimum target band frequency.

  • Energy is computed as the sum of squared coefficients per subband.

  • Subband energy is apportioned to target bands by fractional frequency overlap.

eegproc.featurization.wavelet_entropy(wv_band_energy_df, bands, normalize=True, eps=1e-300)[source]

Compute (optionally normalized) Shannon entropy of wavelet band-energy distributions per channel.

Expects columns {channel}_{band}_wenergy where each row is a windowed wavelet band energy. For each window and channel, treats the vector of _wenergy values across bands as a distribution, then computes -Σplogp. Optionally normalizes by log(K) where K = len(bands).

Parameters:
  • wv_band_energy_df (DataFrame) – Output of wavelet_band_energy(); columns like {ch}_{band}_wenergy.

  • bands (dict[str, tuple[float, float]]) – Same band dictionary used for energy computation (order defines component order).

  • normalize (bool, default: True) – If True, divide entropy by log(K) to obtain values in [0, 1].

  • eps (float, default: 1e-300) – Numerical guard for zero energies.

Return type:

DataFrame

Returns:

pandas.DataFrame – One row per window, columns {ch}_wentropy.

Raises:

ValueError – If no matching {channel}_{band}_wenergy columns are found.