Trajectory Analysis
Group trajectory differences: magnitude, orientation, and shape.
Assumptions about the measurement space
The trajectory statistics use Euclidean geometry in the supplied outcome matrix. Their interpretation therefore depends on how that matrix was built.
| Representation | What the statistics directly describe | Interpretation |
|---|---|---|
| Standardized concatenation | The complete standardized feature space | A transparent baseline, but feature weighting depends on standardization and block dimensionality |
| PCA or PLS scores | A linear projection of the feature space | Path length and direction can be related to the original features through the fitted loadings; omitted components still make the projection lossy |
| SNF spectral coordinates | A nonlinear embedding of a fused affinity graph | Euclidean length, angle, and Procrustes shape describe the embedding and are not guaranteed to preserve feature-space geometry |
delta and angle are most directly aligned with linear measurement spaces.
When using SNF, validate the intended geometric interpretation by simulation
and see Interpreting trajectory geometry in SNF space.
Graph-native diffusion, connectivity, or transition-profile statistics would
be a separate methodology and are not implemented by the current trajectory
API.
Interpreting shape
shape is a strict geometric-morphometric residual configuration statistic.
For each group trajectory, MOTCO centers the stage configuration and scales it
to unit centroid size. Pairwise shape distance is then the residual norm after
aligning one trajectory to the other over the full orthogonal group — the
alignment is unconstrained in determinant, so reflections are aligned away
rather than retained.
Under this contract, translation, positive uniform scale, and any orthogonal
transformation — proper rotation or reflection — do not produce nonzero shape
distance beyond numerical tolerance. Genuine configuration changes, such as
moving an interior stage so relative stage-to-stage distances change after size
normalization, remain positive.
The reflection policy is uniform: it does not depend on whether the stage
configuration spans the ambient space, so the statistic means the same thing at
every measurement checkpoint. (A trajectory of k stages has rank at most
k − 1, so in feature space a proper-rotation constraint would be satisfiable
for free in the null space and therefore vacuous — the previous "reflections
retained" contract bound only in a full-rank latent space.) A genuine mirror
difference is an orientation difference and is reported by angle.
Interpreting orientation and identifying feature drivers
In a shared linear PLS space, a trajectory orientation difference means that the groups progress through different relative combinations of the molecular patterns retained by PLS. Different stage-associated feature sets are one possible cause, but not the only one: the same features can change in different proportions or signs, or one group can recruit an additional correlated feature module.
For two stages, let each group's standardized feature-space transition be
d_g = mean(X | group=g, stage=1) - mean(X | group=g, stage=0)
or use covariate-adjusted LS means in place of raw means. To isolate direction from magnitude, normalize each transition and calculate
u_g = d_g / ||d_g||
q = u_B - u_A
Large positive or negative values of q[j] identify features that contribute
relatively more to one group's progression direction. With more than two
stages, calculate this contrast for each adjacent transition and for the signed
principal feature-space orientation used to summarize the complete path.
A production attribution analysis should:
- Keep the pooled preprocessing and PLS model fixed; do not fit independently aligned latent spaces for the two groups.
- Reconstruct each group's stage-to-stage PLS score displacement through the fitted X loadings, then compare the reconstructed normalized feature steps.
- Compare that loading-derived contrast with the directly observed or
LS-mean-adjusted feature contrast
q. Their agreement describes the part of the group difference captured by the retained PLS space; their residual describes feature variation that PLS omitted. - Bootstrap samples to assess feature-rank and sign stability, especially for correlated features, and summarize stable signals at module/pathway level.
- Report standardized effects alongside original-unit effects. PLS geometry is defined after preprocessing, so raw-unit contributions need not have the same ranking.
Pooled VIP scores alone are insufficient for this question: they identify features important to the pooled stage model, not features whose normalized stage association differs between groups. Orientation drivers require the group-specific transition contrast above.
For a production attribution result from a detected orientation difference, use the shared fitted PLS model and the aligned pooled-standardized matrix with Orientation Driver Attribution. The attribution API supports adjacent transitions for multi-stage data, optional covariate- adjusted mean tables, frozen-model bootstrap stability, and caller-supplied module/pathway labels. It reports standardized and optional original-unit effects separately and does not produce p-values, causal claims, or a unique inverse of the measured features.
Design matrix
motco.stats.design.get_model_matrix(X, group_col, level_col, full=True)
Build a design (model) matrix for group × level factors.
Coding scheme
- Intercept (column of ones).
- Group main effects: one-hot with drop-first for groups (G-1 columns).
- Level main effects: one-hot with drop-first for levels (L-1 columns).
- If
full=True, include all interaction terms between group and level dummies: (G-1) × (L-1) columns.
The category order is deterministic: numeric when every distinct label of a factor parses as an integer (so 10+ levels order correctly), otherwise sorted by string representation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
DataFrame
|
DataFrame containing |
required |
group_col
|
str
|
Name of the group column. |
required |
level_col
|
str
|
Name of the level/state column. |
required |
full
|
bool
|
Whether to include interaction terms. |
True
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Model matrix with intercept. |
Source code in src/motco/stats/design.py
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | |
motco.stats.design.build_ls_means(group_levels, level_levels, full=True)
Generate LS-mean rows for every group × level cell consistent with
get_model_matrix coding.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group_levels
|
Sequence[str]
|
Sorted group labels; first is baseline. |
required |
level_levels
|
Sequence[str]
|
Sorted level labels; first is baseline. |
required |
full
|
bool
|
Whether to include interaction terms. |
True
|
Returns:
| Type | Description |
|---|---|
ndarray
|
LS-mean design matrix with shape (G×L, 1 + (G-1) + (L-1) + I), where
I = (G-1)×(L-1) if |
Source code in src/motco/stats/design.py
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 | |
motco.stats.design.center_matrix(dat, group_col, level_col, feature_cols=None)
Center feature columns by per-group means.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dat
|
DataFrame
|
Original, non-centered dataframe. |
required |
group_col
|
str
|
Column in |
required |
level_col
|
str
|
Column in |
required |
feature_cols
|
Sequence[str] | None
|
Feature columns to center. If None, all numeric columns except
|
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
A copy of |
Source code in src/motco/stats/design.py
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | |
Estimation
motco.stats.trajectory.estimate_betas(X, Y)
Estimate the beta coefficients between an outcome matrix and a model matrix
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
Union[DataFrame, ndarray]
|
Model matrix with intercept. |
required |
Y
|
Union[DataFrame, ndarray]
|
Outcome matrix. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
betas |
Union[DataFrame, ndarray]
|
Beta coefficients |
Source code in src/motco/stats/trajectory.py
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | |
motco.stats.trajectory.get_observed_vectors(X, Y, group_col, level_col, full=True)
Get LS-mean vectors for each group × level cell.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
DataFrame
|
DataFrame containing factors |
required |
Y
|
Union[DataFrame, ndarray]
|
Outcome matrix (n_samples × n_features). |
required |
group_col
|
str
|
Group column name in |
required |
level_col
|
str
|
Level/state column name in |
required |
full
|
bool
|
Whether to include interactions in the model. |
True
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
LS means arranged with a MultiIndex (group, level). Columns follow |
Source code in src/motco/stats/trajectory.py
487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 | |
motco.stats.trajectory.estimate_difference(Y, model_matrix, LS_means, contrast, *, return_spectra=False, orientation_estimator='pc1')
Estimate parameters angle, delta, and shape given an outcome matrix, model matrix, and contrast to compare. This is a comparison of more than two states.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
Y
|
Union[DataFrame, ndarray]
|
Outcome matrix. |
required |
model_matrix
|
Union[DataFrame, ndarray]
|
Model matrix with intercept. |
required |
LS_means
|
Union[DataFrame, ndarray]
|
Least-squares means to estimate. |
required |
contrast
|
list[list[int]]
|
Indices indicating the groups to compare based on LS means. Each list must contain the cohorts that belong to the same group. |
required |
return_spectra
|
bool
|
When |
False
|
orientation_estimator
|
str
|
How each group's orientation is estimated for |
'pc1'
|
Returns:
| Name | Type | Description |
|---|---|---|
deltas |
ndarray
|
Symmetric matrix (n_groups x n_groups) with differences in magnitude. |
angles |
ndarray
|
Symmetric matrix (n_groups x n_groups) with differences in direction (degrees). |
shapes |
ndarray
|
Symmetric matrix (n_groups x n_groups) with Procrustes shape distances
after removing translation, uniform scale, and any orthogonal
transformation. Reflections are aligned away at every ambient
dimension; a mirror difference is an orientation difference and
surfaces in |
spectra |
dict[str, Any]
|
Only when |
Notes
See [1]_ for more information on trajectory analysis.
References
.. [1] Adams, Dean C., and Michael L. Collyer. "A general framework for the analysis of phenotypic trajectories in evolutionary studies." Evolution: International Journal of Organic Evolution 63.5 (2009): 1143-1154. https://doi.org/10.1111/j.1558-5646.2009.00649.x
Source code in src/motco/stats/trajectory.py
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 | |
motco.stats.trajectory.pair_difference(dat, group_col, level_col, groups=None, levels=None, feature_cols=None)
Estimate difference in direction (angle, degrees) and magnitude (delta) between two groups across two levels.
The change vector for a group is defined as level1 - level2 over the
selected feature columns.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dat
|
DataFrame
|
DataFrame containing features plus |
required |
group_col
|
str
|
Column with groups (between-subject factor). |
required |
level_col
|
str
|
Column with levels/states (within-subject factor). |
required |
groups
|
tuple[str, str] | None
|
Pair of group labels to compare. If None, infer and require exactly two. |
None
|
levels
|
tuple[str, str] | None
|
Pair of level labels to use for the change vector. If None, infer and require exactly two. |
None
|
feature_cols
|
Sequence[str] | None
|
Feature columns to use. If None, all numeric columns except |
None
|
Returns:
| Type | Description |
|---|---|
tuple[float, float]
|
(angle_degrees, delta_magnitude_difference) |
Notes
See [1]_ for more information on two-state comparisons.
References
.. [1] Collyer, Michael L., and Dean C. Adams. "Analysis of two‐state multivariate phenotypic change in ecological studies." Ecology 88.3 (2007): 683-692. https://doi.org/10.1890/06-0727
Source code in src/motco/stats/trajectory.py
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | |
Permutation test
motco.stats.permutation.RRPP(Y, model_full, model_reduced, LS_means, contrast, permutations=999, n_jobs=None, progress=True, seed=None, return_eigengaps=False, orientation_estimator='pc1')
Residual Randomization in a Permutation Procedure to evaluate linear models.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
Y
|
Union[DataFrame, ndarray]
|
Outcome matrix. |
required |
model_full
|
Union[DataFrame, ndarray]
|
Model matrix for full model, including intercept. |
required |
model_reduced
|
Union[DataFrame, ndarray]
|
Model matrix for reduced model, including intercept. |
required |
LS_means
|
Union[DataFrame, ndarray]
|
Least-squares means to estimate. |
required |
contrast
|
list[list[int]]
|
Indices indicating the groups to compare based on LS means. Each list must contain the cohorts that belong to the same group. |
required |
permutations
|
int
|
Number of permutations. |
999
|
n_jobs
|
Optional[int]
|
If provided and > 1, run permutations in parallel using multiple worker processes. Use -1 to use all available CPUs. When None or 1, runs single-threaded (backward-compatible default). |
None
|
seed
|
Optional[int]
|
Optional seed for reproducible residual randomization. |
None
|
return_eigengaps
|
bool
|
When |
False
|
orientation_estimator
|
str
|
Orientation estimator for every permuted |
'pc1'
|
Returns:
| Name | Type | Description |
|---|---|---|
dist_delta |
list[float]
|
Distribution of deltas. |
dist_angle |
list[float]
|
Distribution of angles. |
Source code in src/motco/stats/permutation.py
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 | |