defmodule Numerix.Distance do @moduledoc """ Distance functions between two vectors. """ use Numerix.Tensor import Numerix.LinearAlgebra alias Numerix.{Common, Correlation, Statistics} @doc """ Mean squared error, the average of the squares of the errors betwen two vectors, i.e. the difference between predicted and actual values. """ @spec mse(Common.vector(), Common.vector()) :: Common.maybe_float() def mse(x = %Tensor{}, y = %Tensor{}) do p = pow(x - y, 2) Statistics.mean(p.items) end def mse(vector1, vector2) do x = Tensor.new(vector1) y = Tensor.new(vector2) mse(x, y) end @doc """ Root mean square error of two vectors, or simply the square root of mean squared error of the same set of values. It is a measure of the differences between predicted and actual values. """ @spec rmse(Common.vector(), Common.vector()) :: Common.maybe_float() def rmse(vector1, vector2) do :math.sqrt(mse(vector1, vector2)) end @doc """ The Pearson's distance between two vectors. """ @spec pearson(Common.vector(), Common.vector()) :: Common.maybe_float() def pearson(vector1, vector2) do case Correlation.pearson(vector1, vector2) do nil -> nil correlation -> 1.0 - correlation end end @doc """ The Minkowski distance between two vectors. """ @spec minkowski(Common.vector(), Common.vector(), integer) :: Common.maybe_float() def minkowski(x, y, p \\ 3) def minkowski(x = %Tensor{}, y = %Tensor{}, p) do norm(p, x - y) end def minkowski(vector1, vector2, p) do x = Tensor.new(vector1) y = Tensor.new(vector2) minkowski(x, y, p) end @doc """ The Euclidean distance between two vectors. """ @spec euclidean(Common.vector(), Common.vector()) :: Common.maybe_float() def euclidean(x = %Tensor{}, y = %Tensor{}) do l2_norm(x - y) end def euclidean(vector1, vector2) do x = Tensor.new(vector1) y = Tensor.new(vector2) euclidean(x, y) end @doc """ The Manhattan distance between two vectors. """ @spec manhattan(Common.vector(), Common.vector()) :: Common.maybe_float() def manhattan(x = %Tensor{}, y = %Tensor{}) do l1_norm(x - y) end def manhattan(vector1, vector2) do x = Tensor.new(vector1) y = Tensor.new(vector2) manhattan(x, y) end @doc """ The Jaccard distance (1 - Jaccard index) between two vectors. """ @spec jaccard(Common.vector(), Common.vector()) :: Common.maybe_float() def jaccard(%Tensor{items: []}, %Tensor{items: []}), do: 0.0 def jaccard(%Tensor{items: []}, _), do: nil def jaccard(_, %Tensor{items: []}), do: nil def jaccard([], []), do: 0.0 def jaccard([], _), do: nil def jaccard(_, []), do: nil def jaccard(vector1, vector2) do vector1 |> Stream.zip(vector2) |> Enum.reduce({0, 0}, fn {x, y}, {intersection, union} -> case {x, y} do {x, y} when x == 0 or y == 0 -> {intersection, union} {x, y} when x == y -> {intersection + 1, union + 1} _ -> {intersection, union + 1} end end) |> to_jaccard_distance end defp to_jaccard_distance({_intersection, union}) when union == 0, do: 1.0 defp to_jaccard_distance({intersection, union}) do 1 - intersection / union end end