pcntoolkit.math_functions.velocity#

Algorithms for longitudinal (velocity-centile) modelling.

This modules has the mathematical implementation of the correlation matrix, thrivelines and conditional forecasting.

Attributes#

Functions#

compute_correlation_matrix(→ xarray.DataArray)

Compute z-score correlations between visits of the same subject by age.

compute_thriveline_y(→ xarray.DataArray)

Convert thrivelines from z-scores to the response-variable Y coordinates.

compute_thriveline_z(→ tuple[xarray.DataArray, ...)

Compute thrivelines, in z-scores, from a correlation matrix.

design_matrix(→ pandas.DataFrame)

Construct the regression design matrix to fill missing correlations.

fill_missing(→ numpy.ndarray)

Fill in missing correlation values by regressing on age.

fisher_transform(→ numpy.ndarray)

Fisher z-transform.

offset_indices(max_age, bandwidth)

Yield upper-triangular (age1, age2) pairs within bandwidth.

thriveline_end_z(→ float)

Compute the z-score where one thriveline ends (Bayer et al., 2026).

thrivelines_to_dataframe(→ pandas.DataFrame)

Convert propagated thriveline arrays to a long-form table.

validate_thrivelines(→ None)

Check that a pre-computed thriveline table can be plotted.

Module Contents#

compute_correlation_matrix(data: pcntoolkit.dataio.norm_data.NormData, bandwidth: int, covariate_name: str = 'age') → xarray.DataArray#

Compute z-score correlations between visits of the same subject by age.

The covariate (typically age) is binned to integer years before computing correlations, so the resulting matrix is indexed by integer ages.

Parameters:
  • data (NormData) – Longitudinal data containing covariates (X), predicted z-scores (Z), batch effects and subject ids.

  • bandwidth (int) – Widest age gap, in years, to estimate correlations for. Correlations for wider gaps are set to 0.

  • covariate_name (str, default "age") – Covariate used to index the correlation matrix.

Returns:

Correlations of shape [n_response_vars, n_ages, n_ages] with dims (response_vars, f"{covariate_name}_1", f"{covariate_name}_2").

Return type:

xr.DataArray

Raises:

ValueError – If the data is cross-sectional, or if no pair of ages within bandwidth has at least 4 subjects scanned at both ages.

compute_thriveline_y(model: pcntoolkit.normative_model.NormativeModel, thrive_Z: xarray.DataArray | xarray.Dataset, thrive_X: xarray.DataArray | None = None, *, covariate: str | None = None, batch_effects: dict[str, str] | None = None) → xarray.DataArray#

Convert thrivelines from z-scores to the response-variable Y coordinates.

Uses the normative model to turn each point’s (age, z-score) into the value on the y-axis of the centile chart, e.g. cortical thickness in mm.

Parameters:
  • model (NormativeModel) – The fitted normative model.

  • thrive_Z (xr.DataArray or xr.Dataset) – Z-scores from compute_thriveline_z, with dimensions (segment, response_vars, offset). Can also be a Dataset holding both Z and X; thrive_X can then be left out.

  • thrive_X (xr.DataArray, optional) – Ages from compute_thriveline_z, same shape as thrive_Z.

  • covariate (str, optional) – Name of the covariate on the x-axis, e.g. "age". Only needed when the model has more than one covariate. That way it knows to put all other covariates at the middle of their range.

  • batch_effects (dict of str, optional) – Batch effect level to draw the lines for, e.g. {"site": "A"}. Defaults to the level that appears first in the model’s training data. Ignored when the model has no batch effects.

Returns:

thrive_Y – Response variable value at each point, same dimensions as thrive_Z. Lines that could not be computed stay NaN.

Return type:

xr.DataArray

compute_thriveline_z(R: xarray.DataArray, *, zgain_threshold: float = -1.96, thriveline_span: int = 1, thriveline_spacing: int = 1, centiles: list[float] | numpy.ndarray | None = None, covariate_range: tuple[int, int] | None = None) → tuple[xarray.DataArray, xarray.DataArray]#

Compute thrivelines, in z-scores, from a correlation matrix.

Parameters:
  • R (xr.DataArray) – Correlation matrix with dimensions (response_vars, <covariate>_1, <covariate>_2). Timepoints with a zero or missing correlation get no thriveline, and a warning is raised.

  • zgain_threshold (float, default -1.96) – z-gain each line follows.

  • thriveline_span (int, default 1) – How far each thriveline stretches along the x-axis.

  • thriveline_spacing (int, default 1) – Years between neighbouring thrivelines along the x-axis.

  • centiles (array-like of float, optional) – Centile curves the lines start on, between 0 and 1. Defaults to [0.05, 0.25, 0.5, 0.75, 0.95].

  • covariate_range (tuple of int, optional) – (min, max) ages to draw lines for. Defaults to every age in R.

Returns:

thrive_Z, thrive_X – Z-scores and ages of each line’s start and end point, with dimensions (segment, response_vars, offset). Lines that could not be computed are NaN.

Return type:

xr.DataArray

Raises:

ValueError – If thriveline_span is not positive, or if a centile is not strictly between 0 and 1.

design_matrix(bandwidth: int, Sigma: numpy.ndarray) → pandas.DataFrame#

Construct the regression design matrix to fill missing correlations.

Follows Buuren, S. “Evaluation and prediction of individual growth trajectories.” Ann. Hum. Biol. 50, 247-257 (2023).

Parameters:
  • bandwidth (int) – The bandwidth (max age offset) for which correlations were computed.

  • Sigma (np.ndarray) – Correlation matrix (possibly with missing values) for a single response variable. The 0th column represents an age of 0.

Returns:

Design matrix with regressors and a (possibly NaN) target column y.

Return type:

pd.DataFrame

fill_missing(bandwidth: int, cors: numpy.ndarray) → numpy.ndarray#

Fill in missing correlation values by regressing on age.

Empirical correlations can only be estimated for age pairs that have enough repeated measurements, sparsely sampled age pairs (within a chosen bandwidth) are filled in by regressing the Fisher-transformed correlations on age following Buuren (2023).

Parameters:
  • bandwidth (int) – The bandwidth within which the indices are filled in.

  • cors (np.ndarray) – Possibly incomplete correlation matrix of shape [n_response_vars, n_ages, n_ages].

Returns:

Matrix completed with predicted values.

Return type:

np.ndarray

fisher_transform(cor: numpy.ndarray) → numpy.ndarray#

Fisher z-transform.

offset_indices(max_age: int, bandwidth: int)#

Yield upper-triangular (age1, age2) pairs within bandwidth.

E.g. offset_indices(3, 2) yields (0,1), (0,2), (1,2), (1,3), (2,3).

thriveline_end_z(r: float, start_z: float, zgain_threshold: float = -1.96) → float#

Compute the z-score where one thriveline ends (Bayer et al., 2026).

The line ends at the later z-score whose z-gain is exactly zgain_threshold. Solving z_gain = (z_end − r·z_start) / sqrt(1 − r²) for z_end gives

z_end = r · z_start + sqrt(1 − r²) · zgain_threshold

Parameters:
  • r (float) – Correlation of z-scores between the start and end age of the line, between -1 and 1.

  • start_z (float) – z-score where the line starts.

  • zgain_threshold (float, default -1.96) – z-gain the line follows.

Returns:

z-score where the line ends.

Return type:

float

thrivelines_to_dataframe(thrive_Z: xarray.DataArray, thrive_X: xarray.DataArray, thrive_Y: xarray.DataArray) → pandas.DataFrame#

Convert propagated thriveline arrays to a long-form table.

Each row is one point on one segment for one response variable. Columns are segment, response_var, offset, X, Z, and Y. Segments that could not be computed (NaN) are left out.

Parameters:
  • thrive_Z (xr.DataArray) – Arrays with dimensions (segment, response_vars, offset) as returned by compute_thriveline_z and compute_thriveline_y.

  • thrive_X (xr.DataArray) – Arrays with dimensions (segment, response_vars, offset) as returned by compute_thriveline_z and compute_thriveline_y.

  • thrive_Y (xr.DataArray) – Arrays with dimensions (segment, response_vars, offset) as returned by compute_thriveline_z and compute_thriveline_y.

Returns:

Long-form thriveline table suitable for inspection, filtering, and plotting.

Return type:

pd.DataFrame

validate_thrivelines(thrivelines: pandas.DataFrame) → None#

Check that a pre-computed thriveline table can be plotted.

DEFAULT_THRIVELINE_CENTILES = [0.05, 0.25, 0.5, 0.75, 0.95]#
THRIVELINE_DF_COLUMNS = ('segment', 'response_var', 'offset', 'X', 'Z', 'Y')#