eegproc.preprocessing module

eegproc.preprocessing.apply_detrend(detrend, df)[source]

Apply interpolation and (optionally) remove a trend from each numeric column.

This is a small convenience wrapper that first ensures numeric columns are float and linearly interpolated (forward/backward) via numeric_interp(), then, depending on detrend, optionally removes a trend from each column using detrend_df().

Parameters:
  • detrend (str | None) – Which trend to remove. Accepted values are: - "constant": remove the mean from each series (demean). - "linear": remove a best-fit linear trend from each series. - None: do not detrend; only interpolate numeric columns.

  • df (DataFrame) – Input dataframe. Non-numeric columns are ignored for interpolation and detrending (they are effectively dropped by the pipeline).

Return type:

DataFrame

Returns:

pd.DataFrame – A dataframe with only numeric columns, interpolated, and optionally detrended according to detrend. Index and column names are preserved for the remaining numeric columns.

Raises:

ValueError – If detrend is not one of {"constant", "linear", None}.

See also

numeric_interp: Coerces numeric columns to float and bi-directionally interpolates. detrend_df: Applies SciPy detrending (constant or linear) column-wise.

Examples

>>> import pandas as pd, numpy as np
>>> df = pd.DataFrame({"a": [1.0, np.nan, 3.0], "b": [10.0, 11.0, np.nan], "c": ["x", "y", "z"]})
>>> apply_detrend("constant", df)  # demean numeric columns after interpolation
         a     b
0 -1.000000 -0.5
1  0.000000  0.5
2  1.000000  0.0
>>> apply_detrend(None, df)  # only numeric interpolation
     a     b
0  1.0  10.0
1  2.0  11.0
2  3.0  11.0
eegproc.preprocessing.apply_notch_once(dfin, notch_hz, notch_q, nyq)[source]

Apply single-pass IIR notch filtering (and optional 2x harmonic) to each column to remove narrow amplitude readings over selected frequencies.

Builds one or more IIR notch filters at the requested fundamental frequencies, optionally adding a 2x harmonic notch when it is safely below Nyquist, and applies zero-phase filtering (scipy.signal.filtfilt, method="gust") column-wise.

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

  • notch_hz (float | int | list | tuple | None) – Fundamental notch frequency (e.g., 50 or 60), or a list/tuple of such frequencies. If None, no notch filtering is applied.

  • notch_q (float) – Quality factor for scipy.signal.iirnotch (higher = narrower notch).

  • nyq (float) – Nyquist frequency (fs/2), used to normalize the notch frequency.

Return type:

DataFrame

Returns:

pandas.DataFrame – Copy of the input with the notch (and eligible 2x harmonic) applied to all columns with sufficient length.

Notes

  • A 2x harmonic is added for each fundamental f0 when 2*f0 < nyq - 1.0.

  • Columns shorter than max(15, 3 * max(len(a), len(b))) samples are skipped for stability.

  • Frequencies are normalized as w0 = f0 / nyq for iirnotch.

eegproc.preprocessing.bandpass_filter(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)}, low=None, high=None, *, order=4, notch_hz=[60, 120], notch_q=30.0, reref=True, detrend=True)[source]

Applies band-pass filtering over raw EEG data on each channel.

Pipeline

  1. Coerce to numeric (with interpolation).

  2. Optional common average reference (CAR) across channels (reref=True).

  3. Optional notch filtering at notch_hz (and 2x harmonics when safe).

4a) If bands is a dict: apply a per-band Butterworth band-pass (SOS) and

return columns named {channel}_{band}.

4b) Else: require (low, high) and apply a single band-pass to each channel,

returning the original channel names.

  1. Optional constant detrending per output column.

param df:

Raw data EEG dataframe. Numeric columns; NaNs are interpolated internally by numeric_interp before filtering.

type df:

DataFrame

param fs:

Sampling rate in Hz.

type fs:

float

param bands:

If provided, a mapping from band name to (low, high) in Hz. When given, one output column per {channel}_{band} is produced. If None, low and high must be provided to define a single passband.

type 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)}

param low:

Low cutoff in Hz for the single band-pass path (ignored if bands provided).

type low:

float | None, default: None

param high:

High cutoff in Hz for the single band-pass path (ignored if bands provided).

type high:

float | None, default: None

param order:

Butterworth filter order for band-pass design.

type order:

int, default: 4

param notch_hz:

Fundamental notch frequency/frequencies. None disables notch filtering.

type notch_hz:

float | int | list | tuple | None, default: [60, 120]

param notch_q:

Quality factor for the notch (higher = narrower).

type notch_q:

float, default: 30.0

param reref:

If True and there are ≥2 channels, apply common average reference (CAR).

type reref:

bool, default: True

param detrend:

If True, remove the mean (constant detrend) after filtering.

type detrend:

bool, default: True

rtype:

DataFrame

returns:

pandas.DataFrame – Filtered dataframe indexed like the input. If bands is provided, columns are {channel}_{band}; otherwise, the original channel names are preserved.

raises ValueError:
  • When bands is None and either low or high is missing. - When any cutoff does not satisfy 0 < low < high < fs/2. - When a band in bands violates Nyquist constraints.

Warning

RuntimeWarning

Emitted when reref=True but only one channel is present (CAR requires ≥2).

Notes

  • Band-pass filters are designed with scipy.signal.butter(..., output="sos") and applied with a NaN-robust zero-phase filter via _sosfiltfilt_safe().

  • Notch filtering is applied once before band-pass filtering.

  • Constant detrend uses scipy.signal.detrend(..., type="constant").

  • For stability, very short columns may be skipped inside helper routines.

eegproc.preprocessing.numeric_interp(df)[source]

Select numeric columns, cast to float, and interpolate missing values.

The function:
  1. keeps only numeric dtypes,

  2. casts them to float (to allow NaNs and interpolation),

  3. fills missing values with linear interpolation along the index, using limit_direction='both' to also fill leading/trailing gaps.

Non-numeric columns are dropped.

Parameters:

df (DataFrame) – Input dataframe containing numeric and/or non-numeric columns.

Return type:

DataFrame

Returns:

pd.DataFrame – A copy containing only the numeric columns as floats, with missing values linearly interpolated forward and backward.

Notes

  • Interpolation is performed independently per column.

  • If a column is entirely NaN, it will remain NaN.

Examples

>>> import pandas as pd, numpy as np
>>> df = pd.DataFrame({"x": [1.0, np.nan, 3.0], "y": ["a", "b", "c"]})
>>> numeric_interp(df)
     x
0  1.0
1  2.0
2  3.0
eegproc.preprocessing.sosfiltfilt_safe(sos, y)[source]

Zero-phase Second-order Section (SOS) filter, handles NaNs and short signals.

Applies scipy.signal.sosfiltfilt to a 1-D array. Linearly interpolates internal NaNs (or fills with 0.0 if too few finite samples to interpolate).

Parameters:
  • sos (array-like) – Second-order-sections filter coefficients as produced by scipy.signal.butter(..., output="sos").

  • y (array-like of shape (n_samples,)) – Input signal (1-D). May contain NaNs.

Returns:

numpy.ndarray – Filtered signal. If the input is all-NaN or too short, returns a copy of the input (with NaNs interpolated when possible).

Notes

  • NaNs at the edges are handled by linear interpolation using the nearest finite samples when available; otherwise they are set to 0.0.

  • The minimum-length guard (15) prevents filtfilt endpoint artifacts and poor padding when the window is too small.