coco_pipe.dim_reduction.evaluation ================================== .. py:module:: coco_pipe.dim_reduction.evaluation Submodules ---------- .. toctree:: :maxdepth: 1 /autoapi/coco_pipe/dim_reduction/evaluation/core/index /autoapi/coco_pipe/dim_reduction/evaluation/geometry/index /autoapi/coco_pipe/dim_reduction/evaluation/metrics/index /autoapi/coco_pipe/dim_reduction/evaluation/velocity/index Classes ------- .. autoapisummary:: coco_pipe.dim_reduction.evaluation.MethodSelector Functions --------- .. autoapisummary:: coco_pipe.dim_reduction.evaluation.moving_average coco_pipe.dim_reduction.evaluation.trajectory_acceleration coco_pipe.dim_reduction.evaluation.trajectory_curvature coco_pipe.dim_reduction.evaluation.trajectory_dispersion coco_pipe.dim_reduction.evaluation.trajectory_displacement coco_pipe.dim_reduction.evaluation.trajectory_path_length coco_pipe.dim_reduction.evaluation.trajectory_separation coco_pipe.dim_reduction.evaluation.trajectory_speed coco_pipe.dim_reduction.evaluation.trajectory_tortuosity coco_pipe.dim_reduction.evaluation.trajectory_turning_angle coco_pipe.dim_reduction.evaluation.compute_coranking_matrix coco_pipe.dim_reduction.evaluation.compute_mrre coco_pipe.dim_reduction.evaluation.continuity coco_pipe.dim_reduction.evaluation.lcmc coco_pipe.dim_reduction.evaluation.shepard_diagram_data coco_pipe.dim_reduction.evaluation.trustworthiness coco_pipe.dim_reduction.evaluation.compute_velocity_fields Package Contents ---------------- .. py:class:: MethodSelector(reducers: Union[Dict[str, coco_pipe.dim_reduction.core.DimReduction], List[coco_pipe.dim_reduction.core.DimReduction]]) Compare and rank already-scored dimensionality reduction methods. ``MethodSelector`` is intentionally post-hoc. It does not fit reducers or compute embeddings. Each reducer must already be a scored ``DimReduction`` instance with cached ``metric_records_``. :param reducers: Scored ``DimReduction`` objects to compare. Lists are converted to a method-keyed mapping using ``reducer.method``. :type reducers: dict or list of DimReduction .. attribute:: reducers Compared reductions keyed by method name. :type: dict of str to DimReduction .. attribute:: metric_records_ Cached long-form metric records populated by ``collect()``. :type: list of dict .. seealso:: :obj:`evaluate_embedding` Pure evaluator used upstream by ``DimReduction.score``. :obj:`coco_pipe.dim_reduction.core.DimReduction.score` Scores a fitted reduction and populates the records consumed here. .. rubric:: Examples >>> import numpy as np >>> from coco_pipe.dim_reduction import DimReduction >>> X = np.random.RandomState(0).randn(30, 4) >>> reducers = [ ... DimReduction("PCA", n_components=2), ... DimReduction("Isomap", n_components=2, n_neighbors=5), ... ] >>> for reducer in reducers: ... embedding = reducer.fit_transform(X) ... reducer.score(embedding, X=X, k_values=[5]) >>> selector = MethodSelector(reducers).collect() >>> frame = selector.to_frame() >>> not frame.empty True .. py:attribute:: metric_records_ :value: [] .. py:method:: from_records(records: List[Dict[str, Any]]) -> MethodSelector :classmethod: Create a selector directly from long-form metric records. .. py:method:: from_frame(frame: pandas.DataFrame) -> MethodSelector :classmethod: Create a selector directly from a metric-record DataFrame. .. py:method:: collect() -> MethodSelector Collect cached metric records from already-scored reducers. :returns: The selector populated with comparison-ready metric records. :rtype: MethodSelector :raises ValueError: If a reducer has not been scored yet. .. seealso:: :obj:`coco_pipe.dim_reduction.core.DimReduction.score` Populates the ``metric_records_`` consumed by this method. :obj:`to_frame` Materialize the collected long-form records as a DataFrame. .. rubric:: Notes ``collect()`` does not fit reducers or recompute evaluation metrics. It only gathers cached metric observations from reducers that were already scored explicitly. .. rubric:: Examples >>> import numpy as np >>> from coco_pipe.dim_reduction import DimReduction >>> X = np.random.RandomState(0).randn(20, 4) >>> reducer = DimReduction("PCA", n_components=2) >>> embedding = reducer.fit_transform(X) >>> reducer.score(embedding, X=X, k_values=[5]) >>> selector = MethodSelector([reducer]).collect() >>> len(selector.metric_records_) > 0 True .. py:method:: to_frame() -> pandas.DataFrame Return the cached long-form metric table. :returns: Tidy metric table with columns ``method``, ``metric``, ``value``, ``scope``, and ``scope_value``. :rtype: pandas.DataFrame .. rubric:: Notes This method only materializes a DataFrame at the public export boundary. Internally, ``MethodSelector`` stores metric records as plain Python dictionaries. .. seealso:: :obj:`collect` Gather cached metric records from scored reducers. :obj:`rank_methods` Rank reducers from the collected metric table. .. rubric:: Examples >>> import numpy as np >>> from coco_pipe.dim_reduction import DimReduction >>> X = np.random.RandomState(0).randn(20, 4) >>> reducer = DimReduction("PCA", n_components=2) >>> embedding = reducer.fit_transform(X) >>> reducer.score(embedding, X=X, k_values=[5]) >>> frame = MethodSelector([reducer]).collect().to_frame() >>> set(["method", "metric", "value"]).issubset(frame.columns) True .. py:method:: rank_methods(selection_metric: str, *, selection_k: Optional[int] = None, tie_breakers: Optional[Sequence[str]] = None) -> pandas.DataFrame Rank methods using one primary metric and optional tie-breakers. :param selection_metric: Metric to optimize. :type selection_metric: str :param selection_k: Neighborhood size to compare for k-scoped metrics. :type selection_k: int, optional :param tie_breakers: Additional metrics used in order when primary values tie. :type tie_breakers: sequence of str, optional :returns: Ranked comparison table. The first row is the best-scoring method under the requested ranking policy. :rtype: pandas.DataFrame :raises ValueError: If the requested metrics are unsupported, unavailable in the cached records, or missing the requested ``selection_k`` observations. .. rubric:: Notes Ranking is based on mean metric values per method. For k-scoped metrics, ``selection_k`` restricts comparison to a single neighborhood size when requested. .. seealso:: :obj:`collect` Gather cached metric observations before ranking. :obj:`to_frame` Inspect the underlying long-form metric observations directly. :obj:`coco_pipe.dim_reduction.core.DimReduction.score` Produces the metric records that feed into ranking. .. rubric:: Examples >>> import numpy as np >>> from coco_pipe.dim_reduction import DimReduction >>> X = np.random.RandomState(0).randn(20, 4) >>> reducers = [DimReduction("PCA", n_components=2)] >>> reducer = reducers[0] >>> embedding = reducer.fit_transform(X) >>> reducer.score(embedding, X=X, k_values=[5]) >>> ranked = MethodSelector(reducers).collect().rank_methods( ... "trustworthiness", ... selection_k=5, ... ) >>> ranked.iloc[0]["method"] == reducer.method True .. py:function:: moving_average(arr: numpy.ndarray, window: int) -> numpy.ndarray Smooth a one-dimensional array with a valid-mode moving average. :param arr: Input array to smooth. :type arr: np.ndarray of shape (n_samples,) :param window: Size of the smoothing window. Must be a positive integer no larger than the array length. :type window: int :returns: Smoothed array. The output length is ``n_samples - window + 1``. If ``window == 1``, a copy of the input is returned. :rtype: np.ndarray :raises ValueError: If ``arr`` is not one-dimensional, if ``window`` is not positive, or if ``window`` is larger than the input length. .. seealso:: :obj:`trajectory_speed` First-order trajectory dynamics without smoothing. :obj:`trajectory_turning_angle` Local directional changes along a trajectory. .. rubric:: Examples >>> import numpy as np >>> moving_average(np.array([1, 2, 3, 4, 5]), window=3) array([2., 3., 4.]) .. py:function:: trajectory_acceleration(traj: numpy.ndarray, dt: float = 1.0) -> numpy.ndarray Calculate instantaneous acceleration magnitude. :param traj: Trajectory array. The second-to-last axis is interpreted as time and the last axis as coordinates. :type traj: np.ndarray of shape (..., n_times, n_dims) :param dt: Uniform time step between consecutive samples. :type dt: float, default=1.0 :returns: Acceleration-magnitude timecourse aligned with the input time axis. :rtype: np.ndarray of shape (..., n_times) :raises ValueError: If ``traj`` has fewer than two dimensions, contains fewer than three time points, or if ``dt <= 0``. .. seealso:: :obj:`trajectory_speed` First-order trajectory dynamics. :obj:`trajectory_curvature` Geometric bending of a trajectory. :obj:`trajectory_turning_angle` Local directional changes between segments. .. rubric:: Examples >>> import numpy as np >>> t = np.linspace(0.0, 2.0, 3) >>> traj = np.stack([t**2, np.zeros_like(t)], axis=1) >>> trajectory_acceleration(traj, dt=1.0).shape (3,) .. py:function:: trajectory_curvature(traj: numpy.ndarray) -> numpy.ndarray Calculate geometric curvature of a trajectory. :param traj: Trajectory array. The second-to-last axis is interpreted as time and the last axis as coordinates. :type traj: np.ndarray of shape (..., n_times, n_dims) :returns: Curvature timecourse aligned with the input time axis. :rtype: np.ndarray of shape (..., n_times) :raises ValueError: If ``traj`` has fewer than two dimensions or fewer than two time points. .. rubric:: Notes For vector-valued trajectories, curvature is computed from first and second derivatives using the generalized formula ``sqrt(||v||^2 ||a||^2 - (v . a)^2) / ||v||^3``. The implementation assumes uniformly spaced samples. .. seealso:: :obj:`trajectory_turning_angle` Discrete local directional change. :obj:`trajectory_tortuosity` Path inefficiency relative to net displacement. :obj:`trajectory_speed` First-order trajectory dynamics. .. rubric:: Examples >>> import numpy as np >>> t = np.linspace(0, 2 * np.pi, 100) >>> traj = np.stack([np.cos(t), np.sin(t)], axis=1) >>> k = trajectory_curvature(traj) >>> k.shape (100,) .. py:function:: trajectory_dispersion(traj: numpy.ndarray, labels: Optional[numpy.ndarray] = None) -> Dict[str, numpy.ndarray] | numpy.ndarray Calculate within-group trajectory dispersion across time. :param traj: Trial trajectory tensor. :type traj: np.ndarray of shape (n_trials, n_times, n_dims) :param labels: Optional group label for each trial. If omitted, a single global dispersion timecourse is returned. :type labels: np.ndarray of shape (n_trials,), optional :returns: Global dispersion timecourse when ``labels`` is omitted, otherwise a mapping from label to dispersion timecourse. :rtype: np.ndarray or dict[str, np.ndarray] .. seealso:: :obj:`trajectory_separation` Unified separation entrypoint. :obj:`trajectory_separation` Use ``method="within_between_ratio"`` for normalized separation. .. rubric:: Examples >>> import numpy as np >>> traj = np.zeros((2, 3, 2)) >>> traj[1, :, 0] = 1.0 >>> trajectory_dispersion(traj) array([0.5, 0.5, 0.5]) .. py:function:: trajectory_displacement(traj: numpy.ndarray) -> numpy.ndarray Calculate displacement from the initial state across time. :param traj: Trajectory array. The second-to-last axis is interpreted as time and the last axis as coordinates. :type traj: np.ndarray of shape (..., n_times, n_dims) :returns: Euclidean displacement from the first time point at each time index. :rtype: np.ndarray of shape (..., n_times) .. seealso:: :obj:`trajectory_path_length` Total or cumulative traveled distance. :obj:`trajectory_tortuosity` Ratio of traveled distance to final displacement. .. rubric:: Examples >>> import numpy as np >>> traj = np.array([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]) >>> trajectory_displacement(traj) array([0. , 1. , 1.41421356]) .. py:function:: trajectory_path_length(traj: numpy.ndarray, *, cumulative: bool = False) -> numpy.ndarray Calculate trajectory path length. :param traj: Trajectory array. The second-to-last axis is interpreted as time and the last axis as coordinates. :type traj: np.ndarray of shape (..., n_times, n_dims) :param cumulative: If ``True``, return cumulative path length aligned with the input time axis. Otherwise return total path length for each trajectory. :type cumulative: bool, default=False :returns: Total path length with shape ``(...)`` when ``cumulative=False``, or cumulative path length with shape ``(..., n_times)`` when ``cumulative=True``. :rtype: np.ndarray .. seealso:: :obj:`trajectory_displacement` Distance from the initial state across time. :obj:`trajectory_tortuosity` Ratio of path length to net displacement. :obj:`trajectory_speed` First-order local motion magnitude. .. rubric:: Examples >>> import numpy as np >>> traj = np.array([[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]) >>> trajectory_path_length(traj) np.float64(2.0) .. py:function:: trajectory_separation(traj: numpy.ndarray, labels: numpy.ndarray, method: str = 'centroid', **kwargs) -> Dict[Tuple[str, str], numpy.ndarray] Calculate time-resolved separation between labeled trajectory groups. :param traj: Trajectory tensor containing one trajectory per trial. :type traj: np.ndarray of shape (n_trials, n_times, n_dims) :param labels: Class label for each trial. :type labels: np.ndarray of shape (n_trials,) :param method: "distributional", "margin"}, default="centroid" Separation definition to compute. :type method: {"centroid", "within_between_ratio", "mahalanobis", :param \*\*kwargs: Additional keyword arguments forwarded to the selected separation method. :type \*\*kwargs: dict :returns: Mapping from label pairs to separation timecourses of shape ``(n_times,)``. :rtype: dict[tuple[str, str], np.ndarray] :raises ValueError: If the inputs are invalid or if an unsupported separation method is requested. .. rubric:: Notes This is the high-level separation entrypoint for trajectory-group comparison. It dispatches to the more specific separation primitives in this module. Supported methods: - ``"centroid"``: Euclidean distance between label centroids. - ``"within_between_ratio"``: Between-centroid distance normalized by within-group dispersion. - ``"mahalanobis"``: Covariance-aware centroid separation. - ``"distributional"``: Energy-distance separation between trial clouds. - ``"margin"``: Nearest-cross minus nearest-within margin separation. .. seealso:: :obj:`trajectory_dispersion` Within-group spread used by some separation methods. .. rubric:: Examples >>> import numpy as np >>> traj = np.zeros((4, 5, 2)) >>> labels = np.array(["A", "A", "B", "B"]) >>> sep = trajectory_separation(traj, labels, method="centroid") >>> list(sep.keys()) [('A', 'B')] .. py:function:: trajectory_speed(traj: numpy.ndarray, dt: float = 1.0) -> numpy.ndarray Calculate instantaneous trajectory speed. :param traj: Trajectory array. The second-to-last axis is interpreted as time and the last axis as coordinates. :type traj: np.ndarray of shape (..., n_times, n_dims) :param dt: Uniform time step between consecutive samples. :type dt: float, default=1.0 :returns: Instantaneous speed timecourse. The final value is padded with the last computed speed so that the output length matches the number of time points. :rtype: np.ndarray of shape (..., n_times) :raises ValueError: If ``traj`` has fewer than two dimensions, contains fewer than two time points, or if ``dt <= 0``. .. rubric:: Notes This function computes the norm of the first difference along the time axis, divided by ``dt``. .. seealso:: :obj:`trajectory_acceleration` Second-order trajectory dynamics. :obj:`trajectory_path_length` Total or cumulative traveled distance. :obj:`trajectory_displacement` Distance from the initial state across time. .. rubric:: Examples >>> import numpy as np >>> traj = np.array([[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]) >>> trajectory_speed(traj) array([1., 1., 1.]) .. py:function:: trajectory_tortuosity(traj: numpy.ndarray, eps: float = 1e-08) -> numpy.ndarray Calculate trajectory tortuosity. Tortuosity is defined as total path length divided by net displacement from the initial to the final state. :param traj: Trajectory array. The second-to-last axis is interpreted as time and the last axis as coordinates. :type traj: np.ndarray of shape (..., n_times, n_dims) :param eps: Small constant used to identify near-zero displacement. :type eps: float, default=1e-8 :returns: Tortuosity for each trajectory. Stationary trajectories return ``1.0``; trajectories with nonzero path length but near-zero net displacement return ``np.inf``. :rtype: np.ndarray of shape (...) .. seealso:: :obj:`trajectory_path_length` Total traveled distance along the path. :obj:`trajectory_displacement` Net displacement from start to end. :obj:`trajectory_curvature` Local geometric bending. .. rubric:: Examples >>> import numpy as np >>> traj = np.array([[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]) >>> trajectory_tortuosity(traj) np.float64(1.0) .. py:function:: trajectory_turning_angle(traj: numpy.ndarray) -> numpy.ndarray Calculate local turning angles between consecutive trajectory segments. :param traj: Trajectory array. The second-to-last axis is interpreted as time and the last axis as coordinates. :type traj: np.ndarray of shape (..., n_times, n_dims) :returns: Turning-angle timecourse in radians. The first and last time points are padded with the nearest interior angle to preserve length. :rtype: np.ndarray of shape (..., n_times) .. seealso:: :obj:`trajectory_curvature` Continuous geometric bending. :obj:`trajectory_speed` Local motion magnitude. :obj:`trajectory_path_length` Total or cumulative traveled distance. .. rubric:: Examples >>> import numpy as np >>> traj = np.array([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]) >>> trajectory_turning_angle(traj) array([1.57079633, 1.57079633, 1.57079633]) .. py:function:: compute_coranking_matrix(X: numpy.ndarray, X_emb: numpy.ndarray) -> numpy.ndarray Compute the co-ranking matrix between two sample spaces. The co-ranking matrix ``Q`` counts how often each point pair appears with high-dimensional rank ``k`` and low-dimensional rank ``l``. Self-neighbors are excluded from the rank construction. :param X: Original high-dimensional data. :type X: np.ndarray of shape (n_samples, n_features) :param X_emb: Low-dimensional embedding of the same samples. :type X_emb: np.ndarray of shape (n_samples, n_components) :returns: Integer co-ranking matrix where ``Q[k, l]`` corresponds to ranks ``k + 1`` and ``l + 1`` in the original and embedded spaces. :rtype: np.ndarray of shape (n_samples - 1, n_samples - 1) :raises ValueError: If the inputs are not two-dimensional, do not share the same sample count, or contain fewer than two samples. .. seealso:: :obj:`trustworthiness` Compute intrusion-based neighborhood preservation. :obj:`continuity` Compute extrusion-based neighborhood preservation. :obj:`lcmc` Compute the local continuity meta-criterion. .. rubric:: Examples >>> import numpy as np >>> X = np.array([[0.0], [1.0], [2.0]]) >>> X_emb = np.array([[0.0], [2.0], [4.0]]) >>> Q = compute_coranking_matrix(X, X_emb) >>> Q.shape (2, 2) .. py:function:: compute_mrre(Q: numpy.ndarray, k: int) -> Tuple[float, float] Compute mean relative rank errors (MRRE). Both intrusion and extrusion MRRE are returned. These are error metrics, so lower values are better and ``0`` indicates perfect rank preservation. :param Q: Co-ranking matrix. :type Q: np.ndarray of shape (n_samples - 1, n_samples - 1) :param k: Neighborhood size. :type k: int :returns: ``(mrre_intrusion, mrre_extrusion)``. :rtype: tuple[float, float] :raises ValueError: If ``Q`` is invalid or if ``k`` falls outside the valid domain. .. seealso:: :obj:`trustworthiness` Intrusion-sensitive preservation score. :obj:`continuity` Extrusion-sensitive preservation score. .. rubric:: Examples >>> import numpy as np >>> Q = np.diag([1, 1, 1, 1]) >>> compute_mrre(Q, k=1) (0.0, 0.0) .. py:function:: continuity(Q: numpy.ndarray, k: int) -> float Compute continuity from a co-ranking matrix. Continuity penalizes extrusions, i.e. points that are among the ``k`` nearest neighbors in the original space but are pushed farther away in the embedding. :param Q: Co-ranking matrix. :type Q: np.ndarray of shape (n_samples - 1, n_samples - 1) :param k: Neighborhood size. The normalization used by continuity requires ``2 * n_samples - 3 * k - 1 > 0``. :type k: int :returns: Continuity score in ``[0, 1]``. Higher is better. :rtype: float :raises ValueError: If ``Q`` is invalid or if ``k`` falls outside the valid domain. .. seealso:: :obj:`trustworthiness` Complementary intrusion-based metric. :obj:`compute_coranking_matrix` Construct the required co-ranking matrix. .. rubric:: Examples >>> import numpy as np >>> Q = np.diag([1, 1, 1, 1]) >>> continuity(Q, k=1) 1.0 .. py:function:: lcmc(Q: numpy.ndarray, k: int) -> float Compute the local continuity meta-criterion (LCMC). :param Q: Co-ranking matrix. :type Q: np.ndarray of shape (n_samples - 1, n_samples - 1) :param k: Neighborhood size. :type k: int :returns: LCMC score. Higher is better. :rtype: float :raises ValueError: If ``Q`` is invalid or if ``k`` falls outside the valid domain. .. seealso:: :obj:`trustworthiness` Neighbor-preservation metric. :obj:`continuity` Neighbor-consistency metric. .. rubric:: Examples >>> import numpy as np >>> Q = np.diag([1, 1, 1, 1]) >>> isinstance(lcmc(Q, k=1), float) True .. py:function:: shepard_diagram_data(X: numpy.ndarray, X_embedded: numpy.ndarray, sample_size: int = 1000, random_state: Optional[int] = None) -> Tuple[numpy.ndarray, numpy.ndarray] Compute sampled pairwise distances for a Shepard diagram. :param X: Original high-dimensional data. :type X: np.ndarray of shape (n_samples, n_features) :param X_embedded: Low-dimensional embedding of the same samples. :type X_embedded: np.ndarray of shape (n_samples, n_components) :param sample_size: Number of samples to keep before computing pairwise distances. If ``sample_size`` is at least ``n_samples``, all samples are used. :type sample_size: int, default=1000 :param random_state: Random seed used when subsampling. :type random_state: int, optional :returns: Pairwise distances in the original and embedded spaces. :rtype: tuple[np.ndarray, np.ndarray] :raises ValueError: If the inputs are invalid or if ``sample_size <= 1``. .. seealso:: :obj:`compute_coranking_matrix` Rank-based global quality summary. .. rubric:: Examples >>> import numpy as np >>> X = np.random.RandomState(0).rand(10, 3) >>> X_emb = X[:, :2] >>> d_orig, d_emb = shepard_diagram_data(X, X_emb, sample_size=5, random_state=0) >>> len(d_orig) == len(d_emb) True .. py:function:: trustworthiness(Q: numpy.ndarray, k: int) -> float Compute trustworthiness from a co-ranking matrix. Trustworthiness penalizes intrusions, i.e. points that appear among the ``k`` nearest neighbors in the embedding but were farther away in the original space. :param Q: Co-ranking matrix. :type Q: np.ndarray of shape (n_samples - 1, n_samples - 1) :param k: Neighborhood size. The normalization used by trustworthiness requires ``2 * n_samples - 3 * k - 1 > 0``. :type k: int :returns: Trustworthiness score in ``[0, 1]``. Higher is better. :rtype: float :raises ValueError: If ``Q`` is invalid or if ``k`` falls outside the valid domain. .. seealso:: :obj:`continuity` Complementary extrusion-based metric. :obj:`compute_coranking_matrix` Construct the required co-ranking matrix. .. rubric:: Examples >>> import numpy as np >>> Q = np.diag([1, 1, 1, 1]) >>> trustworthiness(Q, k=1) 1.0 .. py:function:: compute_velocity_fields(X: numpy.ndarray, X_emb: numpy.ndarray, delta_t: int = 1, n_neighbors: int = 30, sigma: float = 0.1, groups: Optional[numpy.ndarray] = None, times: Optional[numpy.ndarray] = None) -> numpy.ndarray Compute velocity-like vectors in embedding space. The function estimates a forward transition direction in the original feature space, then projects that direction into the embedding by weighting low-dimensional neighbor displacements with cosine-aligned transition probabilities. :param X: High-dimensional data ordered by sequence position. :type X: np.ndarray of shape (n_samples, n_features) :param X_emb: Low-dimensional embedding aligned with ``X`` row-wise. :type X_emb: np.ndarray of shape (n_samples, n_components) :param delta_t: Forward lag in samples used to compute the high-dimensional transition vector. This is a sample lag, not a physical time unit. When ``times`` is provided, the high-dimensional transition is additionally divided by the elapsed time between the lagged observations. :type delta_t: int, default=1 :param n_neighbors: Number of non-self neighbors used for local projection. :type n_neighbors: int, default=30 :param sigma: Kernel width controlling the sharpness of transition probabilities. :type sigma: float, default=0.1 :param groups: Group labels defining independent ordered sequences. Velocity vectors are computed separately within each group to avoid cross-group transitions and cross-group neighbor mixing. :type groups: np.ndarray of shape (n_samples,), optional :param times: Optional ordering variable. When provided, samples are sorted within each group before computing forward transitions. The same ordering is also used to derive elapsed time scaling for the high-dimensional transition vector. :type times: np.ndarray of shape (n_samples,), optional :returns: Velocity vectors in embedding space. :rtype: np.ndarray of shape (n_samples, n_components) :raises ValueError: If the inputs are misaligned, not two-dimensional, contain invalid parameter values, or define non-increasing ``times`` within a sequence. .. rubric:: Notes Samples without a valid forward lagged observation keep a zero velocity vector in the output. This typically affects the final ``delta_t`` samples in each independent sequence. .. rubric:: Examples >>> import numpy as np >>> X = np.random.rand(100, 10) >>> X_emb = np.random.rand(100, 2) >>> V = compute_velocity_fields(X, X_emb, delta_t=1, n_neighbors=10) >>> V.shape (100, 2)