Source code for pydist2.distance

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

"""
| The following script implements some useful vectors distances calculation.

| The code has been translated from matlab and follows the same logic.
| The original `matlab code`_ belongs to `Piotr Dollar's Toolbox`_.
| Please refer to the above web pages for more explanations.
| Matlab code can also be found @ MathWorks_.
| This program and the accompanying materials are made available under the terms of the `MIT License`_.
| SPDX short identifier: MIT
| Contributors:
    Mahmoud Harmouch, mail_.

.. _matlab code: https://github.com/pdollar/toolbox/blob/master/classify/pdist2.m
.. _Piotr Dollar's Toolbox: http://vision.ucsd.edu/~pdollar/toolbox/doc/index.html
.. _MathWorks: https://www.mathworks.com/matlabcentral/mlc-downloads/downloads/submissions/29004/versions/2/previews/FPS_in_image/FPS%20in%20image/Help%20Functions/SearchingMatches/pdist2.m/index.html
.. _MIT License: https://opensource.org/licenses/MIT
.. _mail: mahmoudddharmouchhh@gmail.com
"""

from abc import ABC, abstractmethod
import itertools
import numpy as np
from .custom_typing import NumpyArray, PositiveInteger, String, Void, Bool
np.set_printoptions(precision=4)


[docs]class VectorsDistanceDescriptor(ABC): """ This descriptor constuct a blueprint for different vectors distance. It defines the methods specified in the subclasses. All of the subclasses attributes(fields) are prefixed with an underscore, since they are not intended to be accessed directly, but rather through the getter and setter methods. They are unlikely to change, but must be defiened in a distance subclass in order to be compatible with the `distance` module. Any custom methods in a subclass should not be prefixed with an underscore. All of these methods must be implemented in any subclass in order to work with. Any implementation specific logic should be handled in a subclass. """ @abstractmethod def __init__(self, metric: String) -> Void: """ Return an instance of the Distance class based on a metric. :param metric: String that represents the name of the distance method. :return: Void. """ pass @property @abstractmethod def metric(self) -> String: """ A getter method that returns the metric attribute. :param: Instance of the class. :return: String that represents the metric attribute. """ pass @metric.setter @abstractmethod def metric(self, value: String) -> Void: """ A setter method that changes the value of the metric attribute. :param value: String that specifies the name of the distance method. :return: Void. """ pass
[docs] @classmethod @abstractmethod def compute(self, P: NumpyArray, Q: NumpyArray) -> NumpyArray: """ A method that computes the distance between two vectors P and Q. :param P: NumpyArray that represents the first vector/matrix. :param Q: NumpyArray that represents the second vector/matrix. :return: The distance between the two vectors. """ pass
@abstractmethod def __repr__(self) -> String: """ A method that Return a formated string for a given instance. :param: a reference for a given instance. :return: a formated string for a given instance. """ pass
[docs]class PairwiseDistanceDescriptor(ABC): """ This descriptor construct a blueprint for different pairwise distances. It defines the methods specified in the subclasses. All of the subclasses attributes(fields) are prefixed with an underscore, since they are not intended to be accessed directly, but rather through the getter and setter methods. They are unlikely to change, but must be defiened in a distance subclass in order to be compatible with the `distance` module. Any custom methods in a subclass should not be prefixed with an underscore. All of these methods must be implemented in any subclass in order to work with. Any implementation specific logic should be handled in a subclass. """ @abstractmethod def __init__(self, metric: String) -> Void: """ Return an instance of the Distance class based on the metric. :param metric: a given name of the distance method. :return: void. """ pass @property @abstractmethod def metric(self) -> String: """ A getter method that returns the metric attribute. :param: Instance of the class. :return: metric. """ pass @metric.setter @abstractmethod def metric(self, value: String) -> Void: """ A setter method that changes the value of the metric attribute. :param value: String that specifies the name of the distance method. :return: Void. """ pass
[docs] @classmethod @abstractmethod def compute(self, P: NumpyArray) -> NumpyArray: """ A method that computes the pairwise distance of vector P. :param P: NumpyArray that represents a certain vector. :return: NumpyArray that contains the 'metric' distance between each pair of data for the vector P. """ pass
@abstractmethod def __repr__(self) -> String: """ A method that Return a formated string for a given instance. :param self: a reference for a given instance. :return: a formated string for a given instance. """ pass
[docs]class L1Distance(VectorsDistanceDescriptor): """ The L1 norm distance between two vectors. Also known as Manhattan Distance. Literature: https://en.wikipedia.org/wiki/Taxicab_geometry """ _metric = String("metric") def __init__(self, metric: String = "L1 Distance") -> Void: """Returns an instance of a L1Distance class.""" self._metric = metric @property def metric(self) -> String: """ A getter method that returns the metric attribute. :param: instance of the class. :return: metric. """ return self._metric @metric.setter def metric(self, value: String) -> Void: """ A setter method that changes the value of the metric attribute. :param value: a string that specifies the name of the distance method. :return: Void. """ self._metric = value
[docs] @classmethod def compute(self, P: NumpyArray, Q: NumpyArray) -> NumpyArray: """Compute the distance using sum(abs(P-Qi)).""" m = P.shape[0] n = Q.shape[0] mOnes = np.ones((1, m), dtype=np.float64) D = np.zeros((m, n), dtype=np.float64) for i in range(n): Qi = Q[i, :] Qi = Qi * mOnes D[:, i] = np.sum(np.abs(P - Qi), axis=1) return D
def __repr__(self) -> String: """Custom repr function.""" return f"{self.__dict__}"
[docs]class SquaredEuclideanDistance(VectorsDistanceDescriptor): """The L2 norm distance squared. Literature: https://en.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance """ _metric = String("metric") def __init__(self, metric: String = "Squared Euclidean Distance") -> Void: """Return an instance of a SquaredEuclideanDistance class.""" self._metric = metric @property def metric(self) -> String: """ A getter method that returns the metric attribute. :param: instance of the class. :return: metric. """ return self._metric @metric.setter def metric(self, value: String) -> Void: """ A setter method that changes the value of the metric attribute. :param value: a string that specifies the name of the distance method. :return: Void. """ self._metric = value
[docs] @classmethod def compute(self, P: NumpyArray, Q: NumpyArray) -> NumpyArray: """Compute the Squared Euclidean distance using (P - Q)^2.""" m = P.shape[0] n = Q.shape[1] mOnes = np.ones((m, n), dtype=np.float64) Qt = Q.transpose() PP = (np.sum(P * P, axis=1) * mOnes).transpose() QQ = np.sum(Qt * Qt, axis=0) * mOnes return PP + QQ - 2 * np.dot(P, Qt)
def __repr__(self) -> String: """Custom repr function.""" return f"{self.__dict__}"
[docs]class EuclideanDistance(SquaredEuclideanDistance): """The L2 norm distance, aka the euclidean Distance.""" def __init__(self, metric: String = "Euclidean Distance") -> Void: """Return an instance of a EuclideanDistance class.""" super().__init__(metric)
[docs] @classmethod def compute(self, P: NumpyArray, Q: NumpyArray) -> NumpyArray: """Compute the distance using sqrt(Squared Euclidean Distance).""" return np.sqrt(super().compute(P, Q))
[docs]class ChiSquaredDistance(VectorsDistanceDescriptor): """ Compute the Chi-Squared Distance using the formula below. d(P,Q) = sum((Pi - Qi)^2 / (Pi + Qi)) / 2 Literature: https://www.hindawi.com/journals/mpe/2015/352849/ """ _metric = String("metric") def __init__(self, metric: String = "Chi-Squared Distance") -> Void: """Return an instance of a Chi-Squared class.""" self._metric = metric @property def metric(self) -> String: """ A getter method that returns the metric attribute. :param: instance of the class. :return: metric. """ return self._metric @metric.setter def metric(self, value: String) -> Void: """ A setter method that changes the value of the metric attribute. :param value: a string that specifies the name of the distance method. :return: Void. """ self._metric = value
[docs] @classmethod def compute(self, P: NumpyArray, Q: NumpyArray) -> NumpyArray: """Compute the Chi-Squared Distance.""" m = P.shape[0] n = Q.shape[0] mOnes = np.ones((1, m), dtype=np.float64) D = np.zeros((m, n), dtype=np.float64) eps = np.finfo(float).eps for i in range(n): Qi = Q[i, :] QiRep = Qi * mOnes d = QiRep - P s = QiRep + P D[:, i] = np.sum((d * d) / (s + eps), axis=1) return D / 2
def __repr__(self) -> String: """Custom repr function.""" return f"{self.__dict__}"
[docs]class CosineDistance(VectorsDistanceDescriptor): """ Compute the Cosine Distance equals tp 1 - Cosine_Similarity. Literature: https://en.wikipedia.org/wiki/Cosine_similarity """ _metric = String("metric") def __init__(self, metric: String = "Cosine Distance") -> Void: """Return an instance of a CosineDistance class.""" self._metric = metric @property def metric(self) -> String: """ A getter method that returns the metric attribute. :param: instance of the class. :return: metric. """ return self._metric @metric.setter def metric(self, value: String) -> Void: """ A setter method that changes the value of the metric attribute. :param value: a string that specifies the name of the distance method. :return: Void. """ self._metric = value
[docs] @classmethod def compute(self, P: NumpyArray, Q: NumpyArray) -> NumpyArray: """Distance is defined as 1 - cosine(angle between two vectors).""" p = P.shape[1] pOnes = np.ones((1, p), dtype=np.float64) PP = (pOnes.T * np.sqrt(np.sum(P * P, axis=1))).T QQ = (pOnes.T * np.sqrt(np.sum(Q * Q, axis=1))).T P = P.astype(np.float64) Q = Q.astype(np.float64) np.divide(P, PP, out=P, where=PP != 0) np.divide(Q, QQ, out=Q, where=QQ != 0) return 1 - np.dot(P, Q.T)
def __repr__(self) -> String: """Custom repr function.""" return f"{self.__dict__}"
[docs]class EarthMoversDistance(VectorsDistanceDescriptor): """ Compute the Earth Mover's Distance between two vectors. Literature: https://en.wikipedia.org/wiki/Earth_mover%27s_distance """ _metric = String("metric") def __init__(self, metric: String = "Earth Mover's Distance") -> Void: """Return an instance of a EarthMoversDistance class.""" self._metric = metric @property def metric(self) -> String: """ A getter method that returns the metric attribute. :param: instance of the class. :return: metric. """ return self._metric @metric.setter def metric(self, value: String) -> Void: """ A setter method that changes the value of the metric attribute. :param value: a string that specifies the name of the distance method. :return: Void. """ self._metric = value
[docs] @classmethod def compute(self, P: NumpyArray, Q: NumpyArray) -> NumpyArray: """Distance is defined as cosine of the angle between two vectors.""" m = P.shape[1] n = Q.shape[1] Pcdf = np.cumsum(P, axis=1) Qcdf = np.cumsum(Q, axis=1) mOnes = np.ones((1, m), dtype=np.float64) D = np.zeros((m, n), dtype=np.float64) for i in range(n): qcdf = Qcdf[i, :] qcdfRep = qcdf * mOnes D[:, i] = np.sum(np.abs(Pcdf - qcdfRep), axis=1) return D
def __repr__(self) -> String: """Custom repr function.""" return f"{self.__dict__}"
[docs]class SQEuclidean(PairwiseDistanceDescriptor): """Pairwise Squared Euclidean distance.""" _metric = String("metric") def __init__(self, metric: String = "Pairwise Squared Euclidean Distance") -> Void: """Return an instance of a Euclidean class.""" self._metric = metric @property def metric(self) -> String: """ A getter method that returns the metric attribute. :param: instance of the class. :return: metric. """ return self._metric @metric.setter def metric(self, value: String) -> Void: """ A setter method that changes the value of the metric attribute. :param value: a string that specifies the name of the distance method. :return: Void. """ self._metric = value
[docs] @classmethod def compute(self, P: NumpyArray) -> NumpyArray: """Compute the Squared Euclidean distances between each elements of P.""" m, n = P.shape[: 2] D = np.zeros((1, int(m * (m - 1) / 2)), dtype=np.float64) k = 0 for i in range(m): dsq = np.zeros((m - i - 1, 1), dtype=np.float64) for q in range(n): dsq = dsq + (P[i, q] - P[i + 1: m, q]).reshape(-1, 1) ** 2 D[0, k: k + m - i - 1] = dsq.reshape(1, -1) k += m - i - 1 return D[0]
def __repr__(self) -> String: """Custom repr function.""" return f"{self.__dict__}"
[docs]class Euclidean(SQEuclidean): """Pairwise Euclidean distance.""" def __init__(self, metric: String = "Pairwise Euclidean Distance") -> Void: """Return an instance of a Pairwise Euclidean class.""" super().__init__(metric)
[docs] @classmethod def compute(self, P: NumpyArray) -> NumpyArray: """Compute the distance using sqrt(Squared Euclidean Distance).""" return np.sqrt(super().compute(P))
[docs]class StandardizedEuclidean(PairwiseDistanceDescriptor): """Pairwise Standardized Euclidean Distance,Weighted Euclidean distance.""" _metric = String("metric") def __init__(self, metric: String = "Pairwise Standardized Euclidean Distance") -> Void: """Return an instance of a StandardizedEuclidean class.""" self._metric = metric @property def metric(self) -> String: """ A getter method that returns the metric attribute. :param: instance of the class. :return: metric. """ return self._metric @metric.setter def metric(self, value: String) -> Void: """ A setter method that changes the value of the metric attribute. :param value: a string that specifies the name of the distance method. :return: Void. """ self._metric = value
[docs] @classmethod def compute(self, P: NumpyArray) -> NumpyArray: """Compute the Weighted Euclidean distance as: sqrt(wgts*(P_iq-P_hq)^2).""" m, n = P.shape[: 2] wgts = 1 / np.var(P, ddof=1, axis=0) D = np.zeros((1, int(m * (m - 1) / 2)), dtype=np.float64) k = 0 for i in range(m): dsq = np.zeros((m - i - 1, 1), dtype=np.float64) for q in range(n): dsq = dsq + wgts[q] * (P[i, q] - P[i + 1: m, q]).reshape(-1, 1) ** 2 D[0, k: k + m - i - 1] = np.sqrt(dsq).reshape(1, -1) k += m - i - 1 return D[0]
def __repr__(self) -> String: """Custom repr function.""" return f"{self.__dict__}"
[docs]class CityBlock(PairwiseDistanceDescriptor): """Pairwise City Block Distance.""" _metric = String("metric") def __init__(self, metric: String = "City Block Distance") -> Void: """Return an instance of a CityBlock class.""" self._metric = metric @property def metric(self) -> String: """ A getter method that returns the metric attribute. :param: instance of the class. :return: metric. """ return self._metric @metric.setter def metric(self, value: String) -> Void: """ A setter method that changes the value of the metric attribute. :param value: a string that specifies the name of the distance method. :return: Void. """ self._metric = value
[docs] @classmethod def compute(self, P: NumpyArray) -> NumpyArray: """Compute the City Block distance: sum|P_iq-P_hq|.""" m, n = P.shape[: 2] D = np.zeros((1, int(m * (m - 1) / 2)), dtype=np.float64) k = 0 for i in range(m): dsq = np.zeros((m - i - 1, 1), dtype=np.float64) for q in range(n): dsq = dsq + np.abs((P[i, q] - P[i + 1: m, q]).reshape(-1, 1)) D[0, k: k + m - i - 1] = dsq.reshape(1, -1) k += m - i - 1 return D[0]
def __repr__(self) -> String: """Custom repr function.""" return f"{self.__dict__}"
[docs]class Mahalanobis(PairwiseDistanceDescriptor): """Pairwise Mahalanobis Distance.""" _metric = String("metric") def __init__(self, metric: String = "Mahalanobis Distance") -> Void: """Return an instance of a Mahalanobis class.""" self._metric = metric @property def metric(self) -> String: """ A getter method that returns the metric attribute. :param: instance of the class. :return: metric. """ return self._metric @metric.setter def metric(self, value: String) -> Void: """ A setter method that changes the value of the metric attribute. :param value: a string that specifies the name of the distance method. :return: Void. """ self._metric = value
[docs] @staticmethod def cov(P0): """A helper method that computes the covariance for a given matrix.""" P = P0 - P0.mean(axis=0) U, s, V = np.linalg.svd(P, full_matrices=0) D = np.dot(np.dot(V.T, np.diag(s**2)), V) return D / (P0.shape[0] - 1)
[docs] @classmethod def compute(self, P: NumpyArray) -> NumpyArray: """Compute the Mahalanobis distance.""" m, n = P.shape[: 2] D = np.zeros((1, int(m * (m - 1) / 2)), dtype=np.float64) # left division of cov(P) with np.eye to get the inverse matrix. # the reason to do so is because cov(P) is a singular matrix. # so np.linalg.inv(cov(P)) would raize numpy.linalg.LinAlgError. x, _, _, _ = np.linalg.lstsq(Mahalanobis.cov(P), np.eye(3), rcond=None) k = 0 for i in range(m): del_ = P[i, :] - P[i + 1: m, :] dsq = np.sum(np.dot(del_, x) * del_, axis=1) D[0, k: k + m - i - 1] = np.sqrt(dsq).reshape(1, -1) k += m - i - 1 return D[0]
def __repr__(self) -> String: """Custom repr function.""" return f"{self.__dict__}"
[docs]class Minkowski(PairwiseDistanceDescriptor): """Pairwise Minkowski Distance.""" _metric = String("metric") def __init__(self, metric: String = "Minkowski Distance") -> Void: """Return an instance of a ManhattanDistance class.""" self._metric = metric @property def metric(self) -> String: """ A getter method that returns the metric attribute. :param: instance of the class. :return: metric. """ return self._metric @metric.setter def metric(self, value: String) -> Void: """ A setter method that changes the value of the metric attribute. :param value: a string that specifies the name of the distance method. :return: Void. """ self._metric = value
[docs] @classmethod def compute(self, P: NumpyArray, exp: PositiveInteger = 3) -> NumpyArray: """ Compute the City Block distance: (sum(P_iq-P_hq)^(exp))^(1/exp). exp = 2 ----> Euclidean distance exp = 1 ----> city-block distance """ m, n = P.shape[: 2] D = np.zeros((1, int(m * (m - 1) / 2)), dtype=np.float64) k = 0 for i in range(m): dpow = np.zeros((m - i - 1, 1), dtype=np.float64) for q in range(n): dpow = dpow + np.abs((P[i, q] - P[i + 1: m, q]).reshape(-1, 1)) ** exp D[0, k: k + m - i - 1] = dpow.reshape(1, -1) ** (1 / exp) k += m - i - 1 return D[0]
def __repr__(self) -> String: """Custom repr function.""" return f"{self.__dict__}"
[docs]class Chebychev(PairwiseDistanceDescriptor): """Pairwise Chebychev Distance.""" _metric = String("metric") def __init__(self, metric: String = "Chebychev Distance") -> Void: """Return an instance of a Chebychev class.""" self._metric = metric @property def metric(self) -> String: """ A getter method that returns the metric attribute. :param: instance of the class. :return: metric. """ return self._metric @metric.setter def metric(self, value: String) -> Void: """ A setter method that changes the value of the metric attribute. :param value: a string that specifies the name of the distance method. :return: Void. """ self._metric = value
[docs] @classmethod def compute(self, P: NumpyArray) -> NumpyArray: """Compute the Chebychev distance.""" m, n = P.shape[: 2] D = np.zeros((1, int(m * (m - 1) / 2)), dtype=np.float64) k = 0 for i in range(m): dmax = np.zeros((m - i - 1, 1), dtype=np.float64) for q in range(n): dmax = np.maximum(dmax, np.abs(P[i, q] - P[i + 1: m, q]).reshape(-1, 1)) D[0, k: k + m - i - 1] = dmax.reshape(-1) k += m - i - 1 return D[0]
def __repr__(self) -> String: """Custom repr function.""" return f"{self.__dict__}"
[docs]class Cosine(PairwiseDistanceDescriptor): """Pairwise Cosine Distance.""" _metric = String("metric") def __init__(self, metric: String = "Cosine Distance") -> Void: """Return an instance of a Cosine class.""" self._metric = metric @property def metric(self) -> String: """ A getter method that returns the metric attribute. :param: instance of the class. :return: metric. """ return self._metric @metric.setter def metric(self, value: String) -> Void: """ A setter method that changes the value of the metric attribute. :param value: a string that specifies the name of the distance method. :return: Void. """ self._metric = value
[docs] @classmethod def compute(self, P: NumpyArray) -> NumpyArray: """Compute the Cosine distance.""" m, n = P.shape[: 2] mOnes = np.ones((m, n), dtype=np.float64) Pnorm = np.sqrt(np.sum(P ** 2, axis=1)).reshape(-1, 1) * mOnes P = P / Pnorm D = np.zeros((1, int(m * (m - 1) / 2)), dtype=np.float64) k = 0 for i in range(m): d = np.zeros((m - i - 1, 1), dtype=np.float64) for q in range(n): d = d + (P[i, q] * P[i + 1: m, q]).reshape(-1, 1) d = np.where(d > 1, 1, d) D[0, k: k + m - i - 1] = (1 - d).reshape(-1) k += m - i - 1 return D[0]
def __repr__(self) -> String: """Custom repr function.""" return f"{self.__dict__}"
[docs]class Correlation(PairwiseDistanceDescriptor): """Pairwise Correlation Distance.""" _metric = String("metric") def __init__(self, metric: String = "Correlation Distance") -> Void: """Return an instance of a Correlation class.""" self._metric = metric @property def metric(self) -> String: """ A getter method that returns the metric attribute. :param: instance of the class. :return: metric. """ return self._metric @metric.setter def metric(self, value: String) -> Void: """ A setter method that changes the value of the metric attribute. :param value: a string that specifies the name of the distance method. :return: Void. """ self._metric = value
[docs] @classmethod def compute(self, P: NumpyArray) -> NumpyArray: """Compute the Correlation distance.""" m, n = P.shape[: 2] mOnes = np.ones((m, n), dtype=np.float64) Pmean = np.mean(P, axis=1).reshape(-1, 1) P = P - Pmean * mOnes Pnorm = np.sqrt(np.sum(P ** 2, axis=1)).reshape(-1, 1) * mOnes P = P / Pnorm D = np.zeros((1, int(m * (m - 1) / 2)), dtype=np.float64) k = 0 for i in range(m): d = np.zeros((m - i - 1, 1), dtype=np.float64) for q in range(n): d = d + (P[i, q] * P[i + 1: m, q]).reshape(-1, 1) d = np.where(d > 1, 1, d) D[0, k: k + m - i - 1] = (1 - d).reshape(-1) k += m - i - 1 return D[0]
def __repr__(self) -> String: """Custom repr function.""" return f"{self.__dict__}"
[docs]class SpearmanCorrelation(PairwiseDistanceDescriptor): """Spearman rank correlation Distance.""" _metric = String("metric") def __init__(self, metric: String = "Spearman Distance") -> Void: """Return an instance of SpearmanCorrelation class.""" self._metric = metric @property def metric(self) -> String: """ A getter method that returns the metric attribute. :param: instance of the class. :return: metric. """ return self._metric @metric.setter def metric(self, value: String) -> Void: """ A setter method that changes the value of the metric attribute. :param value: a string that specifies the name of the distance method. :return: Void. """ self._metric = value
[docs] @staticmethod def tiedrank(P, dim=None): """A helper method that computes the tiedrank for a given matrix.""" if dim is None: dim = np.min(np.nonzero(P.shape)[0]) Q = np.argsort(P, axis=dim) r1 = np.argsort(Q, axis=dim) Q = np.argsort(-P, axis=dim) r2 = np.argsort(Q, axis=dim) r2 = P.shape[dim] - r2 return ((r1 + r2) / 2 + 1).astype(np.uint32)
[docs] @classmethod def compute(self, P: NumpyArray) -> NumpyArray: """Compute the Spearman Correlation distance.""" m, n = P.shape[: 2] mOnes = np.ones((m, n), dtype=np.float64) P = SpearmanCorrelation.tiedrank(P.T).T P = P - (n + 1) / 2 Pnorm = np.sqrt(np.sum(P ** 2, axis=1)).reshape(-1, 1) * mOnes P = P / Pnorm D = np.zeros((1, int(m * (m - 1) / 2)), dtype=np.float64) k = 0 for i in range(m): d = np.zeros((m - i - 1, 1), dtype=np.float64) for q in range(n): d = d + (P[i, q] * P[i + 1: m, q]).reshape(-1, 1) d = np.where(d > 1, 1, d) D[0, k: k + m - i - 1] = (1 - d).reshape(-1) k += m - i - 1 return D[0]
def __repr__(self) -> String: """Custom repr function.""" return f"{self.__dict__}"
[docs]class Hamming(PairwiseDistanceDescriptor): """Pairwise Hamming Distance.""" _metric = String("metric") def __init__(self, metric: String = "Hamming Distance") -> Void: """Return an instance of a Hamming class.""" self._metric = metric @property def metric(self) -> String: """ A getter method that returns the metric attribute. :param: instance of the class. :return: metric. """ return self._metric @metric.setter def metric(self, value: String) -> Void: """ A setter method that changes the value of the metric attribute. :param value: a string that specifies the name of the distance method. :return: Void. """ self._metric = value
[docs] @classmethod def compute(self, P: NumpyArray) -> NumpyArray: """Compute the Hamming distance.""" m, n = P.shape[: 2] D = np.zeros((1, int(m * (m - 1) / 2)), dtype=np.float64) k = 0 for i in range(m): nesum = np.zeros((m - i - 1, 1), dtype=np.float64) for q in range(n): nesum = nesum + np.not_equal(P[i, q], P[i + 1: m, q]).reshape(-1, 1) D[0, k: k + m - i - 1] = (nesum / n).reshape(-1) k += m - i - 1 return D[0]
def __repr__(self) -> String: """Custom repr function.""" return f"{self.__dict__}"
[docs]class Jaccard(PairwiseDistanceDescriptor): """Pairwise Jaccard Distance.""" _metric = String("metric") def __init__(self, metric: String = "Jaccard Distance") -> Void: """Return an instance of a Jaccard class.""" self._metric = metric @property def metric(self) -> String: """ A getter method that returns the metric attribute. :param: instance of the class. :return: metric. """ return self._metric @metric.setter def metric(self, value: String) -> Void: """ A setter method that changes the value of the metric attribute. :param value: a string that specifies the name of the distance method. :return: Void. """ self._metric = value
[docs] @classmethod def compute(self, P: NumpyArray) -> NumpyArray: """Compute the Jaccard distance.""" m, n = P.shape[: 2] D = np.zeros((1, int(m * (m - 1) / 2)), dtype=np.float64) k = 0 for i in range(m): nesum = np.zeros((m - i - 1, 1), dtype=np.float64) nzsum = np.zeros((m - i - 1, 1), dtype=np.float64) for q in range(n): nz = np.logical_or(np.not_equal(P[i, q], 0), np.not_equal(P[i + 1: m, q], 0)).reshape(-1, 1) ne = np.not_equal(P[i, q], P[i + 1: m, q]).reshape(-1, 1) nzsum = nzsum + nz nesum = nesum + np.logical_and(nz, ne) D[0, k: k + m - i - 1] = (nesum / nzsum).reshape(-1) k += m - i - 1 return D[0]
def __repr__(self) -> String: """Custom repr function.""" return f"{self.__dict__}"
[docs]class pdist1(object): """ An interface for distance calculation between each pair of points. The method of computation will be chosen based on the metric. """ _metric = String("metric") _metric_distance = {'euclidean': Euclidean, 'default': Euclidean, 'sqeuclidean': SQEuclidean, 'seuclidean': StandardizedEuclidean, 'cityblock': CityBlock, 'mahalanobis': Mahalanobis, 'minkowski': Minkowski, 'chebyshev': Chebychev, 'cosine': Cosine, 'correlation': Correlation, 'spearman': SpearmanCorrelation, 'hamming': Hamming, 'jaccard': Jaccard, } @property def metric(self) -> String: """ A getter method that returns the metric attribute. :param: instance of the class. :return: metric. """ return self._metric @metric.setter def metric(self, value: String) -> Void: """ A setter method that changes the value of the metric attribute. :param value: a string that specifies the name of the distance method. :return: Void. """ self._metric = value def __new__(self, P: NumpyArray, metric: String = "default", exp: PositiveInteger = 2, matrix: Bool = False) -> NumpyArray: """Compute the pairwise distance for each two elements of P.""" if metric == 'minkowski': dist_object = Minkowski distance = dist_object().compute(P, exp) else: dist_object = self._metric_distance.get(metric, Euclidean) distance = dist_object().compute(P) if matrix is True: index = np.array(list(itertools.combinations(np.arange(P.shape[0]), 2))) a = [list(P[i, :]) for i in index[:, 0]] b = [list(P[i, :]) for i in index[:, 1]] distance = np.concatenate((distance.reshape(distance.shape[0], 1), a, b), axis=1) return distance def __repr__(self) -> String: """Custom repr function.""" return f"{self.__dict__}"
[docs]class pdist2(object): """ An interface for distance calculation between each pair of two vectors. The method of computation will be chosen based on the metric. """ _metric = String("metric") _metric_distance = {'manhattan': L1Distance, 'sqeuclidean': SquaredEuclideanDistance, 'euclidean': EuclideanDistance, 'default': EuclideanDistance, 'chi-squared': ChiSquaredDistance, 'cosine': CosineDistance, 'earthmover': EarthMoversDistance, } @property def metric(self) -> String: """ A getter method that returns the metric attribute. :param: instance of the class. :return: metric. """ return self._metric @metric.setter def metric(self, value: String) -> Void: """ A setter method that changes the value of the metric attribute. :param value: a string that specifies the name of the distance method. :return: Void. """ self._metric = value def __new__(self, P: NumpyArray, Q: NumpyArray, metric: String = "default") -> NumpyArray: """Compute the distance between P and Q based on metric.""" dist_object = self._metric_distance.get(metric, EuclideanDistance) return dist_object().compute(P, Q) def __repr__(self) -> String: """Custom repr function.""" return f"{self.__dict__}"