Native estimator migration in 0.5¶
The native VBPCA boundary consumes samples x features. It requires
scikit-learn >=1.8, stores constructor parameters unchanged and validates data
and fitted state through its native validation helpers. This follows the
scikit-learn estimator contract
and validate_data.
Native preprocessing, selection and sklearn composition use the contracts
described here. Upgrade existing applications through the explicit migration
steps below; the public estimator layout and default observation policy change
from 0.4.
| API | Data | Components | Joint training scores | Mean |
|---|---|---|---|---|
vbpca_py.VBPCA |
samples x features | (q, p) |
(n, q) |
(p,) |
vbpca_py.legacy.VBPCA |
features x samples | (p, q) |
(q, n) |
historical column layout |
Low-level pca_full |
features x samples | result A: (p, q) |
result S: (q, n) |
historical result keys |
The maintainer approved modern mode as the native estimator default.
The explicit legacy adapter and low-level solver retain their historical
defaults. Explicit compat_mode="strict_legacy" still requests historical
numerical/observation semantics; use the legacy adapter when reproducing the
pre-0.5 interface. Do not silently update a frozen study installation.
The mode comparison details observation,
epsilon and update-order differences and provides an executed migration example.
Fit, project and reconstruct¶
import numpy as np
from vbpca_py import VBPCA
X = np.random.default_rng(23).normal(size=(12, 4))
X[0, 0] = np.nan
model = VBPCA(2, maxiters=4, niter_broadprior=0, verbose=0,
random_state=23, num_cpu=1, runtime_tuning="off")
Z = model.fit_transform(X)
assert Z.shape == (12, 2)
assert model.components_.shape == (2, 4)
assert model.scores_.shape == (12, 2)
assert model.mean_.shape == (4,)
np.testing.assert_allclose(Z, model.transform(X))
new_scores = model.transform(X[:3])
assert model.inverse_transform(new_scores).shape == (3, 4)
posterior = model.transform_posterior(X[:3])
assert posterior.mean.shape == (3, 2)
assert posterior.covariance.shape == (3, 2, 2)
fit_transform(X) means fit(X).transform(X). These are conditional score
means under the fixed fitted loading, mean and noise state. VB projection
retains the full loading-covariance contribution to score precision. The joint
variational means from training remain separately available in scores_ and
can differ from conditional projections. Projection does not refit or mutate
the fitted factors. An entirely missing new row has zero latent mean and unit
prior covariance. Rank zero remains a supported mean-only model.
Projection returns float64 arrays. Ordinary transform allocates its (n,q)
output without a full (n,q,q) covariance bank. transform_posterior exposes
that bank explicitly. Its frozen/slotted ProjectionPosterior holds fresh,
writable output arrays; field immutability does not freeze the arrays. Direct
record construction borrows validated float64 arrays. Output budgets bound the
combined returned mean/covariance storage, not peak process memory.
Observations, probes and validation¶
Dense NaNs are missing and numeric zeros are observed in modern mode. Direct
calls can supply aligned entry masks using keyword arguments. An explicit
mask is consumed before fitting or projecting; it is not a row weight.
NaNs remain missing even if an explicit mask marks their positions as observed.
The legacy dense explicit-mask path could instead count such NaNs as zero
values; the adapter preserves that behavior for compatibility.
Masks and explicit xprobe tables follow the same sample-first layout as X.
Automatic probes preserve the exact remaining sparse observation set.
The standalone make_xprobe_mask helper also defaults to modern splitting;
older helper-based reproductions must explicitly request
compat_mode="strict_legacy". That historical path can discard unselected
stored zeros and sample stored NaNs. Legacy automatic fits pass their own
configured mode. See the probe policy comparison.
Rows with probe entries must retain at least one training observation. Native
fitting rejects probe-only rows because the solver removes empty training
samples and cannot evaluate their probe diagnostics. Low-level and legacy
calls also reject any observed probe entries that would be discarded by
training-axis removal, including probe-only features and samples removed by
historical sparse sum-based handling. This applies in both compatibility modes;
it replaces silently incomplete PRMS diagnostics with a clear error. Empty
training axes without probe observations can still be removed as before.
Sparse stored entries are observed, including stored zeros; absent entries
are missing. Conversions, row slicing and probe generation must retain this
storage structure. Constructing a sparse matrix from a dense table can discard
numeric zeros: construct coordinates explicitly when those zeros are observed,
or use dense NaNs/masks. Sparse explicit masks can declare observed zero values
that are absent from the input's storage. Duplicate mask entries are combined
as Boolean observations, never as repeated data values.
Native validation checks raw sparse buffers and observed infinity before
conversion can coalesce duplicate coordinates. Explicitly excluded infinities
remain unobserved; duplicate +inf/-inf cannot silently become missing data
when their location is observed. This also applies to new-row projection,
scoring, explicit probes and public component-selection inputs.
Empty training tables, entirely unobserved tables/features, observed infinity,
invalid capacities and stale feature counts are rejected. Constant observed
features and entirely missing sample rows remain supported. A failed fit/refit
clears fitted state. String feature names and order are validated when supplied
through a supported named table. Warm-start mappings retain the low-level
A: (p,q), S: (q,n) axes; the observation/probe tables use the public axes.
Unsupervised preprocessing now accepts y=None and keyword-only mask on
fit/fit_transform; pass mask=... explicitly rather than as a second
positional argument. All nine preprocessor constructors store parameters
unchanged and create learned attributes during fitting. Invalid log offsets
and winsorization quantiles are rejected by fit, so construction and cloning
can retain requested settings. transform and inverse_transform require a
fitted estimator and use scikit-learn's NotFittedError.
Refitting discards old learned arrays/plans before fitting. A failed fit,
including cancellation, clears partial state; refit successfully before using
the transformer again. Successful pickle/deepcopy round trips retain fitted
state. Fitted-state detection recognizes populated feature metadata in
compatible older preprocessor artifacts and treats old unfitted None
defaults as unfitted. Native VBPCA's separate serialized-layout rules are
described below.
All nine preprocessors now validate nonempty 2-D input, appropriate numeric or
categorical dtypes, fitted feature counts and supplied string feature names/order.
Duplicate names, observed infinity and raw complex input are rejected. Inverse
transforms validate the fitted encoded width before decoding. Wholly missing
columns retain their existing behavior, including zero-width categorical
blocks. Named inputs capture feature_names_in_; generated output names and
pandas/Polars wrapping now follow the contract below.
Sparse preprocessing accepts CSR, CSC and COO matrices/arrays with valid
structure. Stored NaNs become missing entries without eliminating stored
zeros. Duplicate coordinates are summed on owned storage. Explicit masks on
sparse encoders/scalers are rejected: scalers previously ignored them silently.
Use a leading ObservationMasker to normalize sparse observations before these
transformers. Dense inverse masks for one-hot and automatic encoding have the
original variable width, not the expanded indicator width.
Automatic encoders retain the plans learned for their fitted dense/sparse
representation; cross-representation categorical composition is not qualified
by this increment.
MissingAwareSparseOneHotEncoder now supports multiple numeric categorical
columns. Each has an independent, sorted, full-category block; categories_
keeps its flat output-column list and feature_groups_ identifies each input
variable. Single-column artifacts retain the same metadata meaning. Dense
numeric input is also supported, with every non-NaN value observed, including
zeros. Ignored unknown categories have no stored indicator. Centering changes
stored indicators without filling absent entries.
The existing configurable complex indicator dtype remains supported for
real-valued indicators with finite zero imaginary components; the encoder's
NaN-plus-NaN-imaginary missing sentinel is also retained. Inverse validation
uses real views and decoder scratch blocks; it does not enable complex-valued
raw categories or model inputs.
The default preprocessors pass all applicable generic estimator checks on the
minimum/current supported sklearn versions without skips or expected failures.
For explicit masks in a Pipeline, put ObservationMasker() before learned
preprocessing and enable sklearn metadata routing. Pass
observation_mask=mask to Pipeline/GridSearchCV, or
params={"observation_mask": mask} to cross_validate. The leading step
consumes this alias; downstream encoders and VBPCA receive dense NaNs or sparse
stored observations. CV slices training and test masks by row before feature
expansion. Do not set transform_input for entry masks or forward the raw mask
to a wider encoded table. Direct masker calls use mask=mask; every new table
needs its own aligned mask when it contains placeholders. Without explicit
masks, ordinary NaN/stored-entry Pipelines need no routing opt-in. See the
preprocessing API for sparse zero handling and the runnable
example. Output names and containers are described below.
EncodingSchema.validate(..., strict_numeric=True) now checks mixed numeric
inverse maps explicitly; default categorical-only validation remains compatible
with numeric descriptors that omit affine metadata.
Every native transformer now provides get_feature_names_out and sklearn's
set_output. Default outputs remain arrays/sparse matrices. Named pandas
output preserves the index, and qualified Polars 2.x output preserves row order,
including zero-column dense outputs. Sparse output is refused in table mode
without densification. Unnamed encoder display metadata now uses x0, x1,
... rather than col0, col1, ...; AutoEncoder names use original-variable
positions rather than repeated nested-column labels. Generated collisions
receive deterministic suffixes. In a mixed raw-data Pipeline using Polars for
numerical outputs, keep the leading masker in pandas mode. Polars input nulls
are missing observations. See the preprocessing API for the
optional extras, configuration and supported dtype boundaries. Inverse and
posterior-record output remain unchanged. The complete
Pipeline example demonstrates new-row
projection and inverse reconstruction through the fitted model/encoder portion.
Scoring and cellwise selection¶
score_samples(X) gives each row's observed-entry Gaussian marginal log density
under fitted loading means and noise, integrating unit-normal latent scores.
This plug-in score excludes posterior loading uncertainty; it is neither the
variational objective nor a fully uncertainty-integrated VB predictive score.
The implementation uses rank-sized solves without an observed-features-squared
covariance allocation. Empty rows contribute zero; a table without observations
is rejected. score(X) averages row scores; optional finite, nonnegative
sample_weight values weight rows for scoring only and require a positive sum.
They do not alter training. With metadata routing enabled, request scoring
weights explicitly through set_score_request(sample_weight=True).
Cellwise PRMS/Brier capacity selection remains a distinct target. Public
select_n_components and cross_validate_components consume samples x
features, including masks and explicit selection probes, and default to modern
mode. Requested positive-rank selected models are native estimators with
components_: (q,p), scores_: (n,q) and conditional transform(X_new);
they adopt the selected fit without refitting. Rank-zero selection retains the
existing None best-model result. Diagnostics keep their byte budgets and
use native axes. Warm starts retain their explicitly documented low-level axes.
Native capacities reject negative, Boolean, nonintegral and oversized values.
Cellwise CV constructs its own probes and refuses external probe options.
Feature groups/schema columns describe the input feature axis. Raw CV still
fits preprocessing separately within each cell fold; its public prepared
training/probe arrays and numeric_scores inputs now use samples x encoded
features. Rebuild older prepared-fold pickles, whose layout is explicitly
refused. The selection guide explains the
unchanged target/fold rules and includes the executed public example.
To preserve historical feature-first helper inputs, defaults and returned
compatibility models, import select_n_components and
cross_validate_components from vbpca_py.legacy. Existing numerical/study
characterizations remain on that explicit boundary; archived registrations
and scientific results are not rewritten.
Grouped sklearn CV and multi-metric GridSearchCV support explicitly requested
raw mask metadata and scoring-only row weights. Custom scorers must declare
their metadata requests through sklearn's public routing API. Training remains
unweighted. On supported sklearn versions, GridSearchCV.transform does not
accept metadata: use
search.best_estimator_.transform(X_new, observation_mask=new_mask) for masked
inference after search. search.score supports requested scoring metadata.
Explicit legacy API and serialized models¶
from vbpca_py.legacy import VBPCA as LegacyVBPCA
old_api = LegacyVBPCA(2, maxiters=4, niter_broadprior=0, verbose=0,
random_state=23, num_cpu=1, runtime_tuning="off")
old_scores = old_api.fit_transform(X.T)
assert old_scores.shape == (2, 12)
assert old_api.inverse_transform().shape == (4, 12)
assert old_api.compat_mode == "strict_legacy"
The adapter preserves the old training-score transform() and no-argument
inverse reconstruction. It is an explicit compatibility interface and does not
claim native Pipeline transformer semantics. Legacy characterization tests and
low-level independent numerical references remain separate from the native
estimator tests. Historical engineering examples import this adapter explicitly.
New native serialized state includes a layout version. Pre-0.5 pickles named
vbpca_py.estimators.VBPCA lack that marker and are explicitly refused by the
native class. Open those artifacts in their original environment and refit using
the native or explicit legacy API; do not guess orientation from array shapes.
New native pickle/deepcopy round trips preserve the declared layout and fitted
state without putting a serialization marker into constructor parameters.