Skip to main content

Robust conditional normalization of continuous markers using polynomial surface fitting over categorical and continuous covariates

Project description

covnorm

Scikit-learn compatible transformer for robust conditional Z-score normalization of a continuous marker against categorical and continuous covariates.

A single polynomial curve is fitted over mu and sigma across the entire dataset using rolling overlapping bins sorted by the continuous covariate. Mu and sigma per bin are estimated via Q-Q regression on Box-Cox transformed values, with iterative conditional Z-score outlier rejection (samples with |Z| > 3.372 against the fitted surface are dropped and the surface is refit, repeated up to n_iterations times). The Box-Cox lambda is selected by a grid search that maximises Q-Q linearity rather than marginal normality.

After fitting the shared surface, a post-hoc categorical correction is applied: the mean and standard deviation of the base Z-scores are computed within each categorical group and used to rescale final Z-scores, giving Z_corrected = (Z_base - mu_cat) / sigma_cat. This avoids overfitting independent surfaces to small groups.

Data requirement: target values must be strictly positive for Box-Cox (default). If your data contains zeros, set zero_handles="eps" (adds a small epsilon before Box-Cox) or zero_handles="yeojohnson" (switches to Yeo-Johnson transform). Negative values are not supported.

Supports up to 2 categorical covariates and up to 2 continuous covariates. With 2 continuous covariates, k-NN overlapping windows replace the 1D rolling windows; set n_bins >= 10 in that case.

Installation

pip install covnorm

Usage

import numpy as np
from covnorm import RobustConditionalNormalizer

# Separate covariates from markers before passing to the normalizer.
data = np.load("data.npy")  # shape (n_samples, 4): [sex, batch, age, marker]

sex_batch = data[:, [0, 1]]  # categorical covariates, shape (n_samples, 2)
age       = data[:, [2]]     # continuous covariate,  shape (n_samples, 1)
marker    = data[:, [3]]     # marker to normalize,   shape (n_samples, 1)

normalizer = RobustConditionalNormalizer(
    categorical_vals=sex_batch,
    continuous_vals=age,
    n_bins=6,                      # target number of rolling windows
    bin_size=120,                  # samples per rolling window
    log_transform_continuous=True, # recommended when covariates span orders of magnitude
)

marker_norm = normalizer.fit_transform(marker)

marker_norm has shape (n_samples, n_markers) and contains only the Z-scored marker columns — no covariate columns are included in the output.

It follows the scikit-learn fit / transform / fit_transform API and is compatible with Pipeline.

Inference on new samples

transform() accepts optional categorical_vals and continuous_vals overrides so a fitted normalizer can be applied to new samples with different covariate values:

marker_new_norm = normalizer.transform(
    marker_new,
    categorical_vals=sex_batch_new,
    continuous_vals=age_new,
)

Parameters

Parameter Default Description
categorical_vals Array of shape (n_samples, n_cat) or (n_samples,) with categorical covariate values (e.g. sex, batch). Pass [] when there are no categorical covariates.
continuous_vals Array of shape (n_samples, n_cont) or (n_samples,) with continuous covariate values (e.g. age, BMI). Pass [] when there are no continuous covariates.
n_bins 6 Target number of rolling windows (controls stride)
bin_size 120 Samples per rolling window (Mørkved et al. use 120)
degree 3 Polynomial degree of the mu/sigma curve
n_iterations 3 Maximum iterative conditional outlier-removal passes
log_transform_continuous False Apply log10 to the continuous covariate before fitting (recommended when it spans orders of magnitude, e.g. age in years)
zero_handles "eps" Strategy for zero values in the target: "eps" adds a small epsilon before Box-Cox; "yeojohnson" switches to Yeo-Johnson transform (supports zeros and negatives)

Plotting

from covnorm import plot_covariate_space

fig = plot_covariate_space(
    normalizer,
    data,
    covariate_labels=["age"],
    analyte_label="marker",
)
fig.savefig("normalization_surface.png", dpi=150)

plot_covariate_space visualises the fitted polynomial surface on top of the raw data:

  • 1 continuous covariate — scatter of raw values with a filled ribbon μ(x) ± n_sigma · σ(x) back-transformed to the original space.
  • 2 continuous covariates — 3-D surface plot of μ(x₁, x₂) with the per-bin estimates scattered on top.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

covnorm-0.7.1.tar.gz (18.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

covnorm-0.7.1-py3-none-any.whl (17.1 kB view details)

Uploaded Python 3

File details

Details for the file covnorm-0.7.1.tar.gz.

File metadata

  • Download URL: covnorm-0.7.1.tar.gz
  • Upload date:
  • Size: 18.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for covnorm-0.7.1.tar.gz
Algorithm Hash digest
SHA256 e4a183353d92c2cfa3f52ebed7326a51f13f8f36d3e9cd25e96f9ac4a0310a56
MD5 a62b2ebe66a52e6cdb4f78aadc4be0c7
BLAKE2b-256 a8f049af895569ecc07fdb919e1ea74c881e53011e6705978acab91bb1aa67e5

See more details on using hashes here.

Provenance

The following attestation bundles were made for covnorm-0.7.1.tar.gz:

Publisher: publish.yml on danilotat/covnorm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file covnorm-0.7.1-py3-none-any.whl.

File metadata

  • Download URL: covnorm-0.7.1-py3-none-any.whl
  • Upload date:
  • Size: 17.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for covnorm-0.7.1-py3-none-any.whl
Algorithm Hash digest
SHA256 ad95d878da0588a460d26099281efaef0e039a42443a8a242532fba974c568ba
MD5 658853eda940e91ddfc0aae603448d8f
BLAKE2b-256 89a8d7cea2219c86da2380b766294a6ef1a75dbfb3a82fbd561e0755b5b04927

See more details on using hashes here.

Provenance

The following attestation bundles were made for covnorm-0.7.1-py3-none-any.whl:

Publisher: publish.yml on danilotat/covnorm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page