Source code for evomap.transform

"""
Module for transforming lower-dimensional maps post-creation, including alignment and rotation.
"""

from scipy.linalg import orthogonal_procrustes
import numpy as np

[docs] def align_maps(Xs, X_ref): """ Align a sequence of maps to a reference map using Orthogonal Procrustes Analysis. Parameters ---------- Xs : list of ndarray List of map coordinates, each of shape (n_samples, n_dims) X_ref : ndarray Reference map, shape (n_samples, n_dims) Returns ------- list of ndarray List of aligned map coordinates, each of shape (n_samples, n_dims) """ if not Xs or X_ref.size == 0: raise ValueError("Input maps and reference map must not be empty.") for X in Xs: if X.shape[1] != X_ref.shape[1]: raise ValueError("All maps must have the same number of dimensions as the reference map.") return [align_map(X, X_ref) for X in Xs]
[docs] def align_map(X, X_ref): """ Align a single map to a reference map using Orthogonal Procrustes Analysis. Parameters ---------- X : ndarray Map coordinates, shape (n_samples, n_dims) X_ref : ndarray Reference map, shape (n_samples, n_dims) Returns ------- ndarray Aligned map, shape (n_samples, n_dims) """ if X.size == 0 or X_ref.size == 0: raise ValueError("Input maps must not be empty.") if X.shape != X_ref.shape: raise ValueError("Input map and reference map must have the same shape.") X_orig = X.copy() R, _ = orthogonal_procrustes(X_orig, X_ref) return np.dot(X_orig, R)
[docs] def PCA(X, num_components): """ Perform Principal Component Analysis (PCA). Parameters ---------- X : ndarray Data matrix, shape (n_samples, n_features) num_components : int Number of principal components to retain Returns ------- ndarray Reduced dimensionality data, shape (n_samples, num_components) """ X_meaned = X - np.mean(X, axis=0) cov_mat = np.cov(X_meaned, rowvar=False) eigen_values, eigen_vectors = np.linalg.eigh(cov_mat) sorted_indices = np.argsort(eigen_values)[::-1] eigenvector_subset = eigen_vectors[:, sorted_indices[:num_components]] return np.dot(X_meaned, eigenvector_subset)
[docs] def rotate_map(Y_2D): """ Rotate a 2D map to align along the direction of maximum variance using PCA. Parameters ---------- Y_2D : ndarray 2D map, shape (n_samples, 2) Returns ------- ndarray Rotated map, shape (n_samples, 2) """ return PCA(Y_2D, num_components=2)
[docs] def rotate_maps(Y, inclusions): """ Rotate multiple maps such that the x-axis corresponds to the direction of maximum variance, controlled by an inclusion parameter which determines which elements within each map are subject to rotation. Parameters ---------- Y : list of ndarray List of maps, each map shape (n_samples, n_dims) inclusions : list of ndarray List of 0/1 vectors, each vector of length `n_samples` indicating whether the corresponding element in a map should be included in rotation. Returns ------- list of ndarray List of rotated maps, each map shape (n_samples, n_dims) Raises ------ ValueError If the length of any inclusion vector does not match the number of samples in its corresponding map. """ if len(Y) != len(inclusions): raise ValueError("The length of the inclusions list must match the number of maps.") rotated_maps = [] for map, inclusion in zip(Y, inclusions): if len(map) != len(inclusion): raise ValueError("The length of each inclusion vector must match the number of samples in its corresponding map.") Y_rotated = np.zeros_like(map) included_indices = (inclusion == 1) if np.any(included_indices): # Only perform rotation if there are included elements Y_rotated[included_indices, :] = rotate_map(map[included_indices, :]) rotated_maps.append(Y_rotated) return rotated_maps