Skip to content

DesignMatrix

DesignMatrix(
    data: DesignMatrix
    | DataFrame
    | DataFrame
    | ndarray
    | dict
    | str
    | Path
    | None = None,
    *,
    sampling_freq: float | None = None,
    TR: float | None = None,
    run_length: int | str | None = None,
    columns: list[str] | None = None,
    convolved: list[str] | None = None,
    confounds: list[str] | None = None,
    hrf_model: str | None = "glover",
    n_rows: int | None = None,
)

Represent an experimental design for neuroimaging as a Polars-backed matrix.

Wraps a Polars DataFrame (one row per timepoint, one column per regressor) together with the metadata a GLM needs: the sampling frequency, which columns have been HRF-convolved, and which columns are nuisance/confound regressors. Transformations return new instances with that metadata preserved; DesignMatrix is composed over the DataFrame rather than subclassing it. Unknown attributes are forwarded to the underlying DataFrame, so the Polars API is available directly (dm.select(...), dm.filter(...), dm.slice(...) return a DesignMatrix). Every eager DataFrame result becomes a new DesignMatrix; Series and builder objects remain native Polars values. Metadata is retained only when the operation establishes its validity.

data accepts a Polars DataFrame (copied), a pandas DataFrame (converted), a NumPy array (named via columns), a dict of columns, another DesignMatrix (copied), None (empty), or a file path. A .tsv/.csv path is read as a BIDS events file when it has onset and duration columns — each trial_type becomes an HRF-convolved regressor, or a raw boxcar under hrf_model=None — and as a plain table otherwise (typically confounds). A .h5/.hdf5 path written by write restores the data and the metadata (sampling_freq, convolved, confounds, multi), so neither run_length nor sampling_freq is required; passing either overrides what the file recorded.

Parameters:

Name Type Description Default
data DesignMatrix | DataFrame | DataFrame | ndarray | dict | str | Path | None

Input data; see above for how each type is interpreted.

None
sampling_freq float | None

Sampling frequency in Hz (1/TR for fMRI data). Mutually exclusive with TR.

None
TR float | None

Repetition time in seconds, a convenience for sampling_freq = 1/TR. Mutually exclusive with sampling_freq.

None
run_length int | str | None

Number of TRs in the run. Required when data is a path to a text file. Pass 'infer' for tabular (confounds) files to accept whatever row count the file has; not valid for events files. Not used for .h5 inputs, which carry their own length.

None
columns list[str] | None

Column names, used with NumPy input.

None
convolved list[str] | None

Names of columns that are already HRF-convolved.

None
confounds list[str] | None

Names of nuisance/confound columns (intercept, polynomial drift, DCT cosines, motion, …).

None
hrf_model str | None

HRF model used to convolve regressors loaded from a BIDS events file — 'glover' (the default), 'glover_time', 'glover_dispersion', 'spm', 'spm_time', 'spm_dispersion', or None to keep raw boxcar regressors. A model name hands the events straight to nilearn's make_first_level_design_matrix, so the regressors are the ones a nilearn FirstLevelModel would build from the same file. Ignored for every other kind of data.

'glover'
n_rows int | None

Number of timepoints for a matrix with no columns (Polars cannot represent "n rows, 0 columns"). Rarely needed directly; set by find_spikes and by append.

None

Attributes:

Name Type Description
data DataFrame

The underlying Polars DataFrame.

sampling_freq float | None

Sampling frequency in Hz.

convolved list[str]

Names of HRF-convolved columns (read-only; managed by convolve and append).

confounds list[str]

Names of nuisance/confound columns (read-only; managed by add_poly, add_dct_basis, append, and the constructor). Skipped by convolve and kept separate per run on multi-run vertical append.

multi bool

True if the matrix was created by a multi-run vertical append.

columns list[str]

Column names.

shape tuple[int, int]

(n_rows, n_cols).

is_empty bool

True if the matrix holds no data.

Examples:

# Create from a NumPy array
dm = DesignMatrix(np.zeros((100, 2)), sampling_freq=0.5, columns=["a", "b"])

# Add a column
dm["stim"] = [0, 1, 1, 0] * 25

# Convolve with the HRF — convolved columns get a `_c0` suffix
dm_conv = dm.convolve()  # 'stim' → 'stim_c0'

# Add polynomial drift terms
dm_conv = dm_conv.add_poly(order=2)

# Multi-run concatenation separates drift terms per run
dm_run1 = DesignMatrix(run1_events, sampling_freq=0.5, run_length=100).add_poly(0)
dm_run2 = DesignMatrix(run2_events, sampling_freq=0.5, run_length=100).add_poly(0)
dm_multi = dm_run1.append(dm_run2, axis=0)  # → .nl_r0_poly_0, .nl_r1_poly_0

Passing another DesignMatrix returns a copy: data, sampling_freq, convolved, confounds, and multi are carried over, and any explicit kwarg overrides the inherited value.

When data is a path to a BIDS events file, the events go to nilearn's make_first_level_design_matrix with the named hrf_model ('glover' by default): output columns are suffixed _c0 and convolved is populated. Pass hrf_model=None to load raw boxcar regressors instead — useful for FIR designs, PPI flows that build interaction terms before convolution, or teaching material that introduces convolution as a separate step. Those boxcars are sampled onto the TR grid, so convolving them afterwards with convolve is not the same as letting the constructor convolve the events: onsets that fall between TRs have already been quantized.

Methods:

Name Description
add_dct_basis

Add discrete cosine transform basis functions for high-pass filtering.

add_poly

Add Legendre polynomial drift terms.

append

Concatenate design matrices.

clean

Remove highly correlated columns.

convolve

Convolve columns with an HRF model or custom kernel.

copy

Create a deep copy of the DesignMatrix.

corr

Calculate column correlations as a similarity Adjacency.

downsample

Reduce temporal resolution using Polars-native operations.

drop

Drop specified columns.

fillna

Fill NaN/null values with specified value.

plot

Visualize the design matrix.

replace_data

Replace data columns while preserving confounds and metadata.

standardize

Standardize columns by centering them, optionally scaling to unit variance.

sum

Compute the sum along an axis.

to_numpy

Convert a DesignMatrix to a NumPy array.

upsample

Increase temporal resolution to a target frequency.

vif

Compute the variance inflation factor for each column.

with_columns

Add or replace columns via Polars expressions.

write

Write DesignMatrix to file.

Attributes

columns property writable

columns: list[str]

Column names of the design matrix as a list of strings.

confounds property writable

confounds: list[str]

Names of nuisance/confound columns (read-only).

Managed by convolve, append, add_poly, add_dct_basis, and the confounds= constructor kwarg. Direct assignment raises AttributeError — pass via the constructor or use .append(other, axis=1) (which auto-tracks confounds when other is a raw Polars DataFrame).

convolved property writable

convolved: list[str]

Names of HRF-convolved columns (read-only).

Managed by convolve and append (which merges across inputs). Direct assignment raises AttributeError — pass via the convolved= constructor kwarg if you need to set initial state.

is_empty property

is_empty: bool

True if the design matrix holds no data.

shape property

shape: tuple

The (n_rows, n_cols) shape of the matrix.

For a matrix with no regressors, n_rows comes from the height recorded at construction (Polars cannot represent "n rows, 0 columns").

Methods:

add_dct_basis

add_dct_basis(
    duration: float = 180,
    drop: int = 0,
    *,
    include_constant: bool = True,
) -> DesignMatrix

Add discrete cosine transform basis functions for high-pass filtering.

Parameters:

Name Type Description Default
duration float

Filter duration in seconds. Default: 180.

180
drop int

Number of low-frequency bases to drop. Default: 0.

0
include_constant bool

If True, also add a constant/intercept column named .nl_cosine_0 (analogous to .nl_poly_0 in add_poly). The underlying DCT basis drops the constant per SPM convention; set False to match SPM behavior. Default: True.

True

Returns:

Type Description
DesignMatrix

New DesignMatrix with DCT basis columns appended.

add_poly

add_poly(
    order: int = 0, include_lower: bool = True
) -> DesignMatrix

Add Legendre polynomial drift terms.

Parameters:

Name Type Description Default
order int

Polynomial order (0=intercept, 1=linear, 2=quadratic, ...). Default: 0.

0
include_lower bool

If True, include all orders from 0 to order. Default: True.

True

Returns:

Type Description
DesignMatrix

New DesignMatrix with polynomial columns appended.

append

append(
    data: DesignMatrix | list[DesignMatrix],
    *,
    axis: int = 0,
    keep_separate: bool = True,
    unique_cols: list[str] | None = None,
    fill_na: int | float | None = 0,
    as_confounds: bool = False,
    progress_bar: bool = False,
) -> DesignMatrix

Concatenate design matrices.

Parameters:

Name Type Description Default
data DesignMatrix or list of DesignMatrix

Design matrix/matrices to append.

required
axis int

0 for row-wise (vertical), 1 for column-wise (horizontal). Default: 0.

0
keep_separate bool

Whether to separate confound columns across runs (only applies when axis=0). Default: True.

True
unique_cols list of str

Additional columns to keep separated (supports wildcards).

None
fill_na int, float, or None

Value to fill NaN values during vertical concatenation, or None to preserve nulls. Default: 0.

0
as_confounds bool

Only applies when axis=1. If True, mark all columns from data as nuisance/confounds in the result — they get skipped by .convolve() and separated across runs on later vertical appends. Default: False.

False
progress_bar bool

Print messages about confound separation. Default: False.

False

Returns:

Type Description
DesignMatrix

Concatenated design matrix.

clean

clean(
    *,
    fill_na: int | float | None = 0,
    exclude_confounds: bool = False,
    thresh: float = 0.95,
    progress_bar: bool = False,
) -> DesignMatrix

Remove highly correlated columns.

Parameters:

Name Type Description Default
fill_na int, float, or None

Fill NaN values before checking correlations (default 0)

0
exclude_confounds bool

Skip confound/nuisance columns from correlation check

False
thresh float

Correlation threshold (drop if abs® >= thresh, default 0.95)

0.95
progress_bar bool

Print dropped column names. Default: False

False

Returns:

Type Description
DesignMatrix

Cleaned matrix with highly correlated columns removed

convolve

convolve(
    kernel: str | ndarray = "glover",
    columns: list[str] | None = None,
) -> DesignMatrix

Convolve columns with an HRF model or custom kernel.

Convolved columns are always renamed to <col>_c{i} (where i is the kernel index, 0 for a single 1-D kernel). The source columns are dropped, and self.convolved lists the post-suffix names so downstream metadata stays in sync with the dataframe.

A kernel name selects one of nilearn's HRF models: each column goes to nilearn.glm.first_level.compute_regressor as a condition, convolved at an oversampling factor of 50 and resampled onto the frame times. That is exactly what FirstLevelModel computes, so a column whose samples sit on the TR grid gives the regressor nilearn would build from the same events; sub-TR timing a column cannot represent is lost before convolution, so pass an events table to the constructor for that.

Parameters:

Name Type Description Default
kernel str or ndarray

An HRF model name — 'glover' (default), 'glover_time', 'glover_dispersion', 'spm', 'spm_time' or 'spm_dispersion' — or custom kernel(s) as a 1D array (single kernel) or 2D array (samples x kernels).

'glover'
columns list of str

Columns to convolve (default: all non-confound columns).

None

Returns:

Type Description
DesignMatrix

New DesignMatrix with convolved columns renamed.

copy

copy() -> DesignMatrix

Create a deep copy of the DesignMatrix.

Returns:

Type Description
DesignMatrix

Copy of the current DesignMatrix

corr

corr(
    *,
    metric: str = "pearson",
    columns: list[str] | None = None,
)

Calculate column correlations as a similarity Adjacency.

Parameters:

Name Type Description Default
metric str

'pearson' (default) or 'spearman'.

'pearson'
columns list of str

Subset of columns to correlate. Defaults to all columns.

None

Returns:

Type Description
Adjacency

Similarity matrix whose labels are the column names. The unit diagonal is dropped (self-correlation isn't an edge); use .plot(method='corr') for a heatmap with the diagonal restored.

downsample

downsample(
    target: float, method: str = "mean"
) -> DesignMatrix

Reduce temporal resolution using Polars-native operations.

Parameters:

Name Type Description Default
target float

Target sampling frequency in Hz (must be < current sampling_freq)

required
method str

Aggregation method - 'mean' or 'median' (default: 'mean')

'mean'

Returns:

Type Description
DesignMatrix

Downsampled DesignMatrix with updated sampling_freq

drop

drop(columns: list[str]) -> DesignMatrix

Drop specified columns.

Parameters:

Name Type Description Default
columns list of str

Column names to remove.

required

Returns:

Type Description
DesignMatrix

New DesignMatrix without the specified columns.

fillna

fillna(value: int | float) -> DesignMatrix

Fill NaN/null values with specified value.

Parameters:

Name Type Description Default
value int or float

Value to replace NaN/null entries with.

required

Returns:

Type Description
DesignMatrix

New DesignMatrix with NaN/null values replaced.

plot

plot(
    method: str = "matrix",
    *,
    columns: list[str] | None = None,
    rescale: bool = True,
    metric: str = "pearson",
    ax=None,
    figsize: tuple | None = None,
    title: str | None = None,
    cmap: str | None = None,
    save: str | None = None,
    **kwargs,
) -> Figure

Visualize the design matrix.

Dispatches over method (mirroring BrainData.plot):

  • 'matrix' (default): SPM-style heatmap (rows = TRs, columns = regressors).
  • 'timeseries': overlaid line plot of regressor time courses. Pass the same ax across calls to overlay multiple DesignMatrices (e.g. original vs. convolved).
  • 'corr': labeled correlation heatmap of the columns (reuses corr; diagonal restored to 1.0 for display).

Parameters:

Name Type Description Default
method str

One of 'matrix', 'timeseries', or 'corr'. Default: 'matrix'.

'matrix'
columns list of str

Subset of columns to plot. Defaults to all columns.

None
rescale bool

'matrix' only. Rescale each column by its L2 norm so columns with different native magnitudes are visually comparable (SPM/nilearn convention). Default: True.

True
metric str

'corr' only. 'pearson' (default) or 'spearman'.

'pearson'
ax Axes

Existing axis to draw on; a new figure is created if omitted.

None
figsize tuple

Figure size; sensible per-method default when omitted.

None
title str

Axis title.

None
cmap str

Colormap ('matrix' / 'corr').

None
save str

Path to save the figure.

None
**kwargs dict

Forwarded to the underlying plotter (seaborn.heatmap for 'matrix' / 'corr'; matplotlib.axes.Axes.plot for 'timeseries').

{}

Returns:

Type Description
Figure

The figure containing the plot.

replace_data

replace_data(
    data: ndarray, column_names: list[str] | None = None
) -> DesignMatrix

Replace data columns while preserving confounds and metadata.

Parameters:

Name Type Description Default
data ndarray

New data array (must match number of rows in current DesignMatrix)

required
column_names list of str

Names for new data columns.

None

Returns:

Type Description
DesignMatrix

New DesignMatrix with replaced data columns, preserved confounds

Raises:

Type Description
ValueError

If row count doesn't match existing data

standardize

standardize(
    *,
    method: str = "center",
    columns: list[str] | None = None,
) -> DesignMatrix

Standardize columns by centering them, optionally scaling to unit variance.

Parameters:

Name Type Description Default
method str

'center' subtracts the mean (default); 'zscore' subtracts the mean and divides by the standard deviation.

'center'
columns list[str] | None

Columns to standardize. If None, standardize all non-confound columns.

None

Returns:

Type Description
DesignMatrix

New DesignMatrix with standardized columns.

Raises:

Type Description
ValueError

If method is neither 'center' nor 'zscore'.

sum

sum(axis: int = 0) -> Series

Compute the sum along an axis.

Parameters:

Name Type Description Default
axis int

0 to sum down each column, 1 to sum across each row. Default: 0.

0

Returns:

Type Description
Series

Sums along the specified axis.

to_numpy

to_numpy() -> ndarray

Convert a DesignMatrix to a NumPy array.

Returns:

Type Description
ndarray

2D array with shape (n_samples, n_columns)

upsample

upsample(
    target: float, method: str = "linear"
) -> DesignMatrix

Increase temporal resolution to a target frequency.

Parameters:

Name Type Description Default
target float

Target sampling frequency in Hz (must be > current sampling_freq)

required
method str

Interpolation method - 'linear' or 'nearest' (default: 'linear')

'linear'

Returns:

Type Description
DesignMatrix

Upsampled DesignMatrix with updated sampling_freq

vif

vif(exclude_confounds: bool = True) -> ndarray | None

Compute the variance inflation factor for each column.

Parameters:

Name Type Description Default
exclude_confounds bool

Skip confound/nuisance columns. Default: True.

True

Returns:

Type Description
ndarray

VIF values for each included column. Returns None if the correlation matrix is singular.

with_columns

with_columns(*exprs, **named_exprs) -> DesignMatrix

Add or replace columns via Polars expressions.

Mirrors pl.DataFrame.with_columns. Named kwargs become named columns; positional pl.Expr arguments are accepted as-is (including pl.Expr.alias("name")). Returns a new DesignMatrix preserving annotations on untouched columns. Replacing a column clears its convolution annotation and retains its confound role; new columns are untagged.

For convenience, named-kwarg values that aren't pl.Expr / pl.Series are coerced: an int/float is broadcast as a scalar via pl.lit, and a list / np.ndarray is wrapped as a pl.Series.

Parameters:

Name Type Description Default
*exprs Expr

Positional Polars expressions, passed through.

()
**named_exprs Expr | Series | ndarray | list | int | float

New columns keyed by name.

{}

Returns:

Type Description
DesignMatrix

New DesignMatrix with the columns added or replaced.

Examples:

dm = dm.with_columns(motor=pl.sum_horizontal(motor_cols)).drop(motor_cols)
dm = dm.with_columns(
    vmpfc=seed_signal,
    vmpfc_motor=pl.col("vmpfc") * pl.col("motor_c0"),
)

write

write(file_name: str, sep: str | None = None) -> None

Write DesignMatrix to file.

Supports TSV, CSV, and HDF5 formats. Format is auto-detected from the file extension. Text formats carry the data only; .h5 also preserves sampling_freq, .convolved, .confounds, and .multi, so DesignMatrix(path) restores the whole object.

Parameters:

Name Type Description Default
file_name str

Output file path with a .tsv, .csv, .h5, or .hdf5 extension.

required
sep str | None

Column separator for text files. Defaults to the delimiter the extension implies (comma for .csv, tab otherwise); pass a value to override.

None