Metrics Embedding#

embedding #

FUNCTION DESCRIPTION
trustworthiness

Compute trustworthiness of low-dimensional embedding.

continuity

Compute continuity of low-dimensional embedding Z.

local_structure_preservation

Measure preservation of local pairwise distance structure.

Functions#

trustworthiness(X: torch.Tensor, Z: torch.Tensor, k_neighbor: int = 5) -> torch.Tensor #

Compute trustworthiness of low-dimensional embedding.

Trustworthiness measures how well the local neighborhood structure from high-dimensional space is preserved in low-dimensional space. It penalizes points that appear as neighbors in Z but are far apart in X [1].

Higher values (closer to 1) indicate better preservation of local neighborhoods from high to low dimensional space.

PARAMETER DESCRIPTION
X

High-dimensional data, shape (n_samples, n_features).

TYPE: Tensor

Z

Low-dimensional embedding, shape (n_samples, m_features).

TYPE: Tensor

k_neighbor

Number of neighbors.

TYPE: int, optional, by default 5 DEFAULT: 5

RETURNS DESCRIPTION
Tensor

Trustworthiness score.

Source code in spectre/metrics/embedding.py
def trustworthiness(
    X: torch.Tensor,
    Z: torch.Tensor,
    k_neighbor: int = 5,
) -> torch.Tensor:
    """
    Compute trustworthiness of low-dimensional embedding.

    Trustworthiness measures how well the local neighborhood structure
    from high-dimensional space is preserved in low-dimensional space.
    It penalizes points that appear as neighbors in Z but are
    far apart in X [@venna2006local].

    Higher values (closer to 1) indicate better preservation of local
    neighborhoods from high to low dimensional space.

    Parameters
    ----------
    X : torch.Tensor
        High-dimensional data, shape (n_samples, n_features).

    Z : torch.Tensor
        Low-dimensional embedding, shape (n_samples, m_features).

    k_neighbor : int, optional, by default 5
        Number of neighbors.

    Returns
    -------
    torch.Tensor
        Trustworthiness score.
    """
    check_2d(X)
    check_2d(Z)
    check_same_len(X, Z)
    check_same_device(X, Z)

    n_samples = X.shape[0]

    if not isinstance(k_neighbor, int):
        raise TypeError(
            f"Expected `k_neighbor` to be an integer, got {type(k_neighbor).__name__}."
        )
    check_in_interval(k_neighbor, f"[1, {n_samples})")

    x_pdist = pairwise_distance_euclidean(X)
    z_pdist = pairwise_distance_euclidean(Z)

    x_neighbors = _compute_knn_indices(x_pdist, k=k_neighbor)
    z_neighbors = _compute_knn_indices(z_pdist, k=k_neighbor)

    # Create binary masks for neighbors (n_samples, n_samples)
    x_neighbors_mask = torch.zeros(
        (n_samples, n_samples), dtype=torch.bool, device=X.device
    )
    z_neighbors_mask = torch.zeros(
        (n_samples, n_samples), dtype=torch.bool, device=X.device
    )

    # Fill masks: x_neighbors_mask[i, j] = True if j is a neighbor of i in X
    row_indices = (
        torch.arange(n_samples, device=X.device).unsqueeze(1).expand_as(x_neighbors)
    )
    x_neighbors_mask[row_indices, x_neighbors] = True
    z_neighbors_mask[row_indices, z_neighbors] = True

    # Find intruders: in Z neighbors but not in X neighbors
    intruders_mask = z_neighbors_mask & ~x_neighbors_mask  # (n_samples, n_samples)

    # Compute ranks for all points: rank[i, j] = rank of j in distances from i
    # Broadcasting: x_pdist[i, :] < x_pdist[i, j] for all i, j
    # Shape: (n_samples, n_samples, n_samples) -> (n_samples, n_samples)
    ranks = (x_pdist.unsqueeze(2) < x_pdist.unsqueeze(1)).sum(dim=1) + 1

    # Compute penalties only for intruders: (rank - k) where intruders_mask is True
    penalties = (ranks - k_neighbor) * intruders_mask
    penalty = penalties.sum()

    trustworthiness_score = (
        1.0
        - 2.0
        / (n_samples * k_neighbor * (2 * n_samples - 3 * k_neighbor - 1))
        * penalty
    )

    # Clamp to [0, 1] to handle edge cases
    return torch.clamp(trustworthiness_score, 0.0, 1.0)

continuity(X: torch.Tensor, Z: torch.Tensor, k_neighbor: int = 5) -> torch.Tensor #

Compute continuity of low-dimensional embedding Z.

Continuity measures how well the local neighborhood structure from low-dimensional space is preserved when mapping back to high-dimensional space. It penalizes points that appear as neighbors in X but are far apart in Z [1].

Higher values (closer to 1) indicate better preservation of local neighborhoods from low to high dimensional space.

PARAMETER DESCRIPTION
X

High-dimensional data, shape (n_samples, n_features).

TYPE: Tensor

Z

Low-dimensional embedding, shape (n_samples, m_features).

TYPE: Tensor

k_neighbor

Number of neighbors to consider.

TYPE: int, optional, by default 5 DEFAULT: 5

RETURNS DESCRIPTION
Tensor

Continuity score in [0, 1].

Source code in spectre/metrics/embedding.py
def continuity(
    X: torch.Tensor,
    Z: torch.Tensor,
    k_neighbor: int = 5,
) -> torch.Tensor:
    """
    Compute continuity of low-dimensional embedding Z.

    Continuity measures how well the local neighborhood structure from
    low-dimensional space is preserved when mapping back to high-dimensional
    space. It penalizes points that appear as neighbors in X but are
    far apart in Z [@venna2006local].

    Higher values (closer to 1) indicate better preservation of local
    neighborhoods from low to high dimensional space.

    Parameters
    ----------
    X : torch.Tensor
        High-dimensional data, shape (n_samples, n_features).

    Z : torch.Tensor
        Low-dimensional embedding, shape (n_samples, m_features).

    k_neighbor : int, optional, by default 5
        Number of neighbors to consider.

    Returns
    -------
    torch.Tensor
        Continuity score in [0, 1].
    """
    # Continuity is trustworthiness with roles reversed
    return trustworthiness(Z, X, k_neighbor=k_neighbor)

local_structure_preservation(X: torch.Tensor, Z: torch.Tensor, k_neighbor: int = 5, metric: str = 'spearman') -> torch.Tensor #

Measure preservation of local pairwise distance structure.

Computes correlation between pairwise distances in high and low dimensional spaces, focusing on local neighborhoods.

PARAMETER DESCRIPTION
X

High-dimensional data, shape (n_samples, n_features).

TYPE: Tensor

Z

Low-dimensional embedding, shape (n_samples, m_features).

TYPE: Tensor

k_neighbor

Number of neighbors to consider for local structure.

TYPE: int, optional, by default 5 DEFAULT: 5

metric

Correlation metric: "spearman", "pearson".

TYPE: str, optional, by default "spearman" DEFAULT: 'spearman'

RETURNS DESCRIPTION
Tensor

Correlation coefficient in [-1, 1]. Higher values indicate better preservation of local distances.

Source code in spectre/metrics/embedding.py
def local_structure_preservation(
    X: torch.Tensor,
    Z: torch.Tensor,
    k_neighbor: int = 5,
    metric: str = "spearman",
) -> torch.Tensor:
    """
    Measure preservation of local pairwise distance structure.

    Computes correlation between pairwise distances in high and low
    dimensional spaces, focusing on local neighborhoods.

    Parameters
    ----------
    X : torch.Tensor
        High-dimensional data, shape (n_samples, n_features).

    Z : torch.Tensor
        Low-dimensional embedding, shape (n_samples, m_features).

    k_neighbor : int, optional, by default 5
        Number of neighbors to consider for local structure.

    metric : str, optional, by default "spearman"
        Correlation metric: "spearman", "pearson".

    Returns
    -------
    torch.Tensor
        Correlation coefficient in [-1, 1]. Higher values indicate better
        preservation of local distances.
    """
    check_2d(X)
    check_2d(Z)
    check_same_len(X, Z)
    check_same_device(X, Z)

    n_samples = X.shape[0]

    if not isinstance(k_neighbor, int):
        raise TypeError(
            f"Expected `k_neighbor` to be an integer, got {type(k_neighbor).__name__}."
        )
    check_in_interval(k_neighbor, f"[1, {n_samples})")

    x_pdist = pairwise_distance_euclidean(X)
    z_pdist = pairwise_distance_euclidean(Z)

    # Get k-nearest neighbors in X
    x_neighbors = _compute_knn_indices(x_pdist, k=k_neighbor)

    # Vectorized gathering of local pairwise distances
    # x_neighbors shape: (n_samples, k_neighbor)
    # Expand for advanced indexing
    row_indices = (
        torch.arange(n_samples, device=X.device).unsqueeze(1).expand_as(x_neighbors)
    )

    # Gather distances for all (i, j) pairs where j is a neighbor of i
    # Shape: (n_samples, k_neighbor) -> flatten to (n_samples * k_neighbor,)
    local_x_pdist = x_pdist[row_indices, x_neighbors].flatten()
    local_z_pdist = z_pdist[row_indices, x_neighbors].flatten()

    if metric == "pearson":
        # Pearson correlation
        corr_matrix = torch.corrcoef(torch.stack([local_x_pdist, local_z_pdist]))
        return corr_matrix[0, 1]

    elif metric == "spearman":
        # Spearman rank correlation
        rank_high = torch.argsort(torch.argsort(local_x_pdist)).float()
        rank_low = torch.argsort(torch.argsort(local_z_pdist)).float()
        corr_matrix = torch.corrcoef(torch.stack([rank_high, rank_low]))
        return corr_matrix[0, 1]

    else:
        raise ValueError(f"Unknown metric '{metric}'. Options: 'pearson', 'spearman'.")