A Python port of the MATLAB demixed Principal Component Analysis package
(Kobak, Brendel et al., eLife 2016), installable as matlab_dpca. The port
preserves the original architecture and computations one-to-one; only surface
details change to fit NumPy/SciPy idioms (0-based indexing, vectorized
marginalization, Axes-based plotting callbacks).
It is a derivative of the MIT-licensed upstream package
machenslab/dPCA and is itself released
under the MIT License (see License and Citation).
The Machens lab ships two implementations: the MATLAB package and a
separate, sklearn-style Python package (pip install dPCA, last released 2020).
matlab_dpca is a faithful port of the MATLAB code — numerically validated
to match it to < 1e-6 — and is not the PyPI dPCA. Key differences:
matlab_dpca (this package) |
original dPCA (PyPI) |
|
|---|---|---|
| Lineage | port of the MATLAB code, validated to match it (< 1e-6) |
independent Python implementation |
| API | functional, MATLAB-style: dpca(X, k, …) -> (W, V, which_marg); you project Z = W.T @ X yourself |
sklearn-style class: dPCA(...).fit_transform(X) returns transformed data per marginalization (D/P as attributes) |
| Marginalizations | combined_params (1-based nested lists, as in MATLAB) |
labels string + join dict + protect |
| Regularization | explicit noise covariance Cnoise and ridge lambda, with a separate optimize_lambda |
ridge regularizer='auto' (no noise-covariance term) |
| Decoding / stats | classification_accuracy, classification_shuffled, signif_components, plus time_splits, per_marginalization, dpca_pinv |
significance_analysis only; no time-splitting |
| Plotting | matplotlib ports of the MATLAB figures (dpca_plot, …) |
none (demo notebook only) |
| Dependencies | numpy, scipy, matplotlib |
numpy, scipy, scikit-learn, numexpr, numba |
Use matlab_dpca when you want results and options identical to the MATLAB
package (noise-covariance regularization, time splits, the decoding/plotting
suite); use the original dPCA if you prefer its scikit-learn-style transformer API.
pip install -r requirements.txt # numpy, scipy, matplotlib
# or, as a package (installs `matlab_dpca`):
pip install -e .All data arrays are neurons-first: X has shape (N, *param_dims) where
axis 0 is neurons and axes 1 … D are the task parameters (e.g. stimulus,
decision, time). Single-trial arrays carry an extra trailing trial axis:
(N, *param_dims, max_trials), with missing trials filled with NaN.
Parameter labels used in combined_params, time_parameter, not_to_split
and decoding_classes are 1-based (1 = first parameter = axis 1), exactly
as in the MATLAB package. Returned which_marg marginalization indices are
0-based (Pythonic).
import numpy as np
from matlab_dpca import dpca, explained_variance, get_noise_covariance, optimize_lambda
combined_params = [[[1], [1, 3]], [[2], [2, 3]], [[3]], [[1, 2], [1, 2, 3]]]
# firing_rates_average: (N, S, D, T) trial-averaged
# firing_rates: (N, S, D, T, E) single trials (NaN for missing)
# trial_num: (N, S, D) trials per neuron/condition
W, V, which_marg = dpca(firing_rates_average, 20, combined_params=combined_params)
ev = explained_variance(firing_rates_average, W, V, combined_params=combined_params)
# with regularization + noise covariance
opt_lambda, _ = optimize_lambda(firing_rates_average, firing_rates, trial_num,
combined_params=combined_params)
Cnoise = get_noise_covariance(firing_rates_average, firing_rates, trial_num)
W, V, which_marg = dpca(firing_rates_average, 20, combined_params=combined_params,
lambda_=opt_lambda, Cnoise=Cnoise)See demo.py for the full pipeline (a direct port of
dpca_demo.m): run python demo.py to display figures, or
python demo.py --save to write them to ./demo_figures.
| MATLAB function | Python |
|---|---|
dpca |
matlab_dpca.dpca |
dpca_marginalize |
matlab_dpca.marginalize |
dpca_getNoiseCovariance |
matlab_dpca.get_noise_covariance |
dpca_getTestTrials |
matlab_dpca.get_test_trials |
dpca_explainedVariance |
matlab_dpca.explained_variance → ExplainedVariance |
dpca_optimizeLambda |
matlab_dpca.optimize_lambda |
dpca_classificationAccuracy |
matlab_dpca.classification_accuracy |
dpca_classificationShuffled |
matlab_dpca.classification_shuffled |
dpca_signifComponents |
matlab_dpca.signif_components |
dpca_perMarginalization |
matlab_dpca.per_marginalization / plot_per_marginalization |
dpca_pinv |
matlab_dpca.dpca_pinv |
dpca_plot |
matlab_dpca.dpca_plot |
dpca_plot_default |
matlab_dpca.plot_default |
dpca_classificationPlot |
matlab_dpca.classification_plot |
Keyword arguments follow the MATLAB option names in snake_case
(combinedParams → combined_params, numComps → num_comps, …). The
lambda option is lambda_ (since lambda is a Python keyword). String
toggles ('yes'/'no') become booleans (order=True, scale=False, …).
- The
explained_variance"new approach" signal-variance estimate keeps the MATLAB "dirty hack" that assumes exactly three parameters(S, Q, T). dpca_pinvin MATLAB mixes its computation with a broken plotting call (undefinedplotFunction); the Python version keeps only the well-defined computation (per-marginalization PCA encoders + pseudo-inverse decoders).- Random routines (
optimize_lambda,classification_*,get_test_trials) accept an optionalrng(numpy.random.Generator) for reproducibility. Their results match MATLAB in expectation but not per-seed, since NumPy and MATLAB use different RNG streams. scikit-learnis intentionally not used: every computation is bespoke linear algebra (top-keigenvectors ofM Mᵀ, a regularized linear solve, economy SVDs) whose conventions are validated against MATLAB; substitutingsklearn.decomposition.PCAwould risk diverging from the reference output.
python -m pytest tests/tests/test_properties.py— structural invariants and sanity checks that run standalone (no MATLAB needed).tests/test_vs_matlab.py— exact numerical comparison (< 1e-6) against MATLAB reference outputs for every deterministic computation (marginalization, noise covariance, decoder/encoder matrices, explained and signal variance).
To (re)generate the MATLAB reference:
python tests/generate_data.py # writes synthetic.mat / .npz
matlab -batch run_matlab_reference # writes matlab_reference.mat
python -m pytest tests/test_vs_matlab.pyThe MATLAB-comparison tests skip automatically if matlab_reference.mat is
absent.
MIT License — see LICENSE.
matlab_dpca is a Python port and derivative work of the MATLAB
machenslab/dPCA package, which is itself
MIT-licensed (Copyright © 2016 Machens Lab). That upstream copyright and
permission notice is retained in LICENSE and NOTICE, as
the MIT License requires. The port is Copyright © 2026 Anton Sobinov.
If you use this software in academic work, please cite the dPCA paper:
Kobak D*, Brendel W*, Constantinidis C, Feierstein CE, Kepecs A, Mainen ZF, Qi X-L, Romo R, Uchida N, Machens CK. "Demixed principal component analysis of neural population data." eLife 2016;5:e10989. https://doi.org/10.7554/eLife.10989 (* equal contribution)