Source code for sensorimotor_dependencies.utils

import numpy as np

from sklearn import preprocessing
from scipy.spatial import distance

#------------------------------------------------------------
# Rotation matrix computation

[docs]def Rot(euler_angles): """ Compute rotation matrix corresponding the given Euler angle cf. https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix """ R = np.empty((3,3)) c = np.cos(euler_angles) s = np.sin(euler_angles) R[0,0] = c[0]*c[2] - c[1]*s[0]*s[2] R[0,1] = -c[0]*s[2] - c[1]*c[2]*s[0] R[0,2] = s[0]*s[1] R[1,0] = s[0]*c[2] + c[0]*c[1]*s[2] R[1,1] = c[0]*c[1]*c[2] - s[0]*s[2] R[1,2] = -c[0]*s[1] R[2,0] = s[1]*s[2] R[2,1] = -c[2]*s[1] R[2,2] = c[1] return R
#------------------------------------------------------------ # PCA to compute the degrees of freedom
[docs]def PCA(data, return_matrix=False, return_eigenvalues=False): """ Principal Component Analysis (PCA) to compute the number of degrees of freedom. Parameters ---------- data : (n, k) array Data points matrix (data points = row vectors in the matrix) return_matrix : bool If True, returns the matrix of the data points projection on the eigenvectors return_eigenvalues : bool, optional Returns the eigenvalues and their ratios \\\(λ_{i+1}/λ_i\\\) (``False`` by default). Returns ------- nb : int Number of non-zero eigenvalues of the covariance matrix, thought of as the number of degrees of freedom. The eigenvalues thereof fall into two classes (non-zero and zero eigenvalues) \\\(V_1\\\) and \\\(V_2\\\), distinguished as follows: > each \\\(λ ∈ V_1\\\) has a size closer to other the \\\(λ\\\)'s of \\\(V_1\\\) than the ones of \\\(V_2\\\), and conversely. The boundary between \\\(V_1\\\) and \\\(V_2\\\) corresponds to the largest ratio \\\(λ_{i+1}/λ_i\\\), where the \\\(λ_i\\\) are in decreasing order. Proj : (n, dim_rigid_group) array If return_matrix == True: Projection of the data points on the eigenvectors """ # Covariance matrix cov_matrix = np.cov(preprocessing.scale(data.T)) # Diagonalization of the covariance matrix eig_val, eig_vec = np.linalg.eigh(cov_matrix) # Compute the boundary index separating zero eigenvalues to non-zero ones ratios = [eig_val[i+1]/eig_val[i] for i in range(len(eig_val)-1)] max_ratio = np.argmax(ratios) if return_matrix or return_eigenvalues: if return_matrix: # Projection of the data points over the eigenvectors Proj = data.dot(eig_vec[:,max_ratio+1:]) if return_matrix and return_eigenvalues: return len(eig_val[max_ratio+1:]), Proj, [eig_val, ratios] elif return_matrix: return len(eig_val[max_ratio+1:]), Proj else: return len(eig_val[max_ratio+1:]), [eig_val, ratios] return len(eig_val[max_ratio+1:])
#------------------------------------------------------------ # Classical multidimensional scaling (MDS)
[docs]def MDS(data, return_matrix=False, return_eigenvalues=False): """ Classical multidimensional scaling (MDS) to compute the number of degrees of freedom cf. https://en.wikipedia.org/wiki/Multidimensional_scaling#Classical_multidimensional_scaling Parameters ---------- data : (n, k) array Data points matrix (data points = row vectors in the matrix) return_matrix : bool If True, returns the coordinate matrix return_eigenvalues : bool, optional Returns the eigenvalues and their ratios \\\(λ_{i+1}/λ_i\\\) (``False`` by default). Returns ------- nb : int Number of non-zero eigenvalues of $$B = X X^T$$ (where \\\(X\\\) is the coordinate matrix), thought of as the number of degrees of freedom. The eigenvalues thereof fall into two classes (non-zero and zero eigenvalues) \\\(V_1\\\) and \\\(V_2\\\), distinguished as follows: > each \\\(λ ∈ V_1\\\) has a size closer to other the \\\(λ\\\)'s of \\\(V_1\\\) than the ones of \\\(V_2\\\), and conversely. The boundary between \\\(V_1\\\) and \\\(V_2\\\) corresponds to the largest ratio \\\(λ_{i+1}/λ_i\\\), where the \\\(λ_i\\\) are in decreasing order. X : (n, dim_rigid_group) array If return_matrix == True: Coordinate matrix. """ # Number of points n = len(data) # 1. Squared Symmetric pairwise distance matrix. D_sq = distance.cdist(data, data, 'euclidean')**2 # Centering matrix J = np.eye(n)-np.ones((n, n))/n # 2. Double centering: B = X X^T B = -J.dot(D_sq).dot(J)/2 # 3. Diagonalize eig_val, eig_vec = np.linalg.eigh(B) # Compute the boundary index separating zero eigenvalues to non-zero ones ratios = [eig_val[i+1]/eig_val[i] for i in range(len(eig_val)-1)] max_ratio = np.argmax(ratios) if return_matrix or return_eigenvalues: if return_matrix: # Compute the coordinate matrix Λ_sqrt = np.diag(np.sqrt(eig_val[max_ratio+1:])) E = eig_vec[max_ratio+1:] X = E.dot(Λ_sqrt) if return_matrix and return_eigenvalues: return len(eig_val[max_ratio+1:]), X, [eig_val, ratios] elif return_matrix: return len(eig_val[max_ratio+1:]), X else: return len(eig_val[max_ratio+1:]), [eig_val, ratios] return len(eig_val[max_ratio+1:])
#------------------------------------------------------------ # Dictionary to access the dimension reduction functions in the classes later dim_reduction_dict = {'PCA': PCA, 'MDS': MDS}