BIDistances provides a common interface for calculating, comparing, and examining distances, dissimilarities, divergences, and selected similarities. The package connects four tasks that are usually treated separately:
DistanceMatrix().DistanceProperties().DistanceDistributions().p = 2.Additional domain-specific functionality includes a Gene Ontology-derived TF-IDF distance, time-series distances, nearest-neighbour distances, toroidal Euclidean distances, and mixed-data constructions.
DistanceMatrix() interface for native BIDistances methods and all predefined methods supplied by parallelDist, with optional access to philentropy and manydist.philentropy is installed. This count excludes aliases, user-defined C++ functions, direct-only functions, and the configurable mixed-data constructions in manydist.parallelDist, an internal RcppParallel multicore backend, or optional OpenCL kernels.p = 2 OpenCL route.DistanceDistributions() for comparing candidate distance distributions and identifying multimodality under the goal of finding distance-based cluster structures. Distance vectors above 72,000 values use the integrated batch dip-test backend, while smaller vectors retain diptest::dip.test().Tfidf_Distance() for deriving distances between genes from a gene-by-Gene-Ontology-term feature matrix.DistanceProperties() for documenting the mathematical properties and conditions of native methods.| Access layer | Canonical named routes | Counting rule |
|---|---|---|
Package-defined DistanceMatrix() routes |
19 | 12 native routes, six correlation routes, and squared Euclidean |
Predefined parallelDist methods |
41 | The user-defined custom route is excluded |
| Core installation | 60 | Package-defined routes plus parallelDist
|
Additional philentropy identifiers |
34 | Exact overlaps and aliases already resolved by DistanceMatrix() are excluded |
Numerical-data total with philentropy
|
94 | Canonical names only |
manydist adds preset-based and custom mixed-type distance constructions. These are not included in the total because their specifications are configurable rather than a fixed list of independent methods. Direct-only functions such as TWED_Distance() and ToroidalEuclidean_Distance() are also outside the dispatcher count.
From CRAN:
install.packages("BIDistances")
From GitHub:
install.packages("remotes")
# remotes::install_github("Mthrun/BIDistances")
install.packages("BIDistances", repos = c('https://mthrun.r-universe.dev', 'https://cloud.r-project.org'))
System requirements are R >= 3.5.0, GNU make, and a suitable C++ toolchain. An OpenCL library and the R package OpenCL are optional and are required only for GPU acceleration. Pandoc is required when rebuilding the vignette.
library(BIDistances)
data("Hepta")
Data = Hepta$Data
D_euclidean = DistanceMatrix(
Data,
method = "euclidean"
)
D_minkowski = DistanceMatrix(
Data,
method = "minkowski",
p = 3
)
D_cosine = DistanceMatrix(
Data,
method = "Cosine_Distance"
)
D_euclidean[1:5, 1:5]
DistanceMatrix() first checks for a native BIDistances route. Other method names are evaluated by parallelDist; methods unavailable there can be obtained from philentropy when it is installed. Mixed numerical and categorical data can be handled through manydist.
For finite p >= 1, Minkowski_Distance() computes
$$ d_{p,w}(x,y) = \left(\sum_k w_k |x_k-y_k|^p\right)^{1/p}. $$
Data = as.matrix(iris[1:30, 1:4])
Weights = c(1, 2, 0.5, 1)
D_parallel = Minkowski_Distance(
Data = Data,
p = 3,
Weights = Weights,
backend = "parallelDist",
threads = 2
)
D_multicore = Minkowski_Distance(
Data = Data,
p = 3,
Weights = Weights,
backend = "multicore",
threads = 2
)
max(abs(D_parallel - D_multicore))
The optional OpenCL route uses separate kernels for general p and delegates p = 2 to the established weighted Euclidean GPU implementation:
# Requires an installed OpenCL implementation and a usable device.
D_opencl = Minkowski_Distance(
Data = Data,
p = 3,
Weights = Weights,
backend = "opencl",
Mem = 2
)
Use backend = "auto" to try OpenCL and fall back to the internal multicore backend with a warning. For p = Inf, unit weights are required and the maximum or Chebyshev distance is returned. Values 0 < p < 1 are available through Fractional_Distance() but are generally not metrics.
The same weighted route is available through DistanceMatrix():
D_dispatch = DistanceMatrix(
Data,
method = "Minkowski_Distance",
p = 3,
Weights = Weights,
backend = "multicore",
threads = 2
)
The memory planner can be inspected before a large OpenCL calculation:
MemoryPlan = calculateMemoryDemandGPU(
n = 70000,
d = 784,
mem = 6
)
MemoryPlan
The Euclidean-related entry points have deliberately different roles:
| Entry point | Role |
|---|---|
DistanceMatrix(Data, method = "euclidean") |
Euclidean distance through parallelDist
|
EuclideanMulticore_Distance(Data) |
Internal unweighted multicore Euclidean implementation |
EuclideanGPU_Distance(Data, Weights, backend = ...) |
Weighted Euclidean CPU/OpenCL implementation |
Minkowski_Distance(Data, p = 2, ...) |
Unified weighted Minkowski interface using the optimized Euclidean OpenCL route |
ToroidalEuclidean_Distance(Points, Lines, Columns) |
Euclidean distance on a two-dimensional torus |
library(BIDistances)
data("Hepta")
set.seed(42)
Selection = DistanceDistributions(
Data = Hepta$Data,
DistanceMethods = c(
"euclidean",
"manhattan",
"minkowski",
"chord"
),
CosineNonParallel = TRUE,
CorrelationDist = TRUE,
PlotIt = FALSE
)
Selection$DistanceChoice
Selection$SelectionStatistics[, c(
"Distance", "DipStatistic", "DipPValue", "BimodalityAmplitude", "Status"
)]
if (!is.null(Selection$DistanceMatrix)) {
Selection$DistanceMatrix[1:5, 1:5]
}
Selection$ggobject
The workflow ranks candidate distributions using multimodality-related information under the explicit goal of finding distance-based cluster structures. SelectionStatistics reports both the Hartigan dip statistic and its p-value. For at most 72,000 pairwise values the established diptest::dip.test() path is retained; larger candidate columns are processed together by the integrated C++ batch with explicit asymptotic calibration. The result is exploratory: multimodality supports this structural goal, but it is not a universal proof of cluster validity or biological relevance.
library(BIDistances)
data("Hearingloss_N109")
Result = Tfidf_Distance(
Hearingloss_N109$FeatureMatrix_Gene2Term,
tf_fun = mean
)
Distance = Result$Distance
TfidfWeights = Result$TfidfWeights
Distance[1:5, 1:5]
TfidfWeights[1:5]
Rows represent genes and columns represent GO terms. The function returns pairwise absolute differences between scalar TF-IDF weights together with those weights. Because different annotation rows can receive the same scalar weight, the construction is a pseudometric on the original rows.
library(BIDistances)
TimeSeries = cbind(
SeriesA = c(0, 1, 2, 1, 0),
SeriesB = c(0, 1, 1.5, 1, 0),
SeriesC = c(2, 1, 0, 1, 2)
)
D_msmd = DistanceMatrix(
TimeSeries,
method = "MSMD_Distance",
ParameterC = 1
)
D_msmd
For MSMD_Distance and DTW_Distance, every column is one time series and row order is the temporal order. No separate time variable should be supplied. TWED_Distance() requires explicit timestamps and is therefore called directly rather than through DistanceMatrix().
[1:n,1:d] numerical matrix with n cases, d variables
[1:d,1:d] numerical matrix of distances
Most methods compare the rows of Data. The documented exceptions are:
DTW_Distance and MSMD_Distance, which compare time series stored in columns.EndresSchindelin_Distance, which compares columns as samples of variables.TWED_Distance, which compares two time series directly and requires explicit time vectors.The word distance is used as a broad software convention. Not every returned quantity is a mathematical metric. The complete machine-readable classification is available through:
Properties = DistanceProperties()
Properties[, c(
"Function",
"Classification",
"Conditions"
)]
Examples include:
p >= 1 with strictly positive weights and can become a pseudometric when zero weights remove distinguishing coordinates.Fractional_Distance() with 0 < p < 1 generally violates the triangle inequality.Mahalanobis_Distance() returns squared Mahalanobis dissimilarities; applying sqrt() yields the metric form.Cosine_Distance() returns 1 - cosine similarity and is not a metric in general.Tfidf_Distance() is a pseudometric on the original gene-annotation rows.Kullback_Leibler_div() returns a divergence, including when symmetrized.See ?distance-conventions and ?DistanceProperties for details. The registry documents mathematical conditions; it is not a runtime proof for arbitrary user-supplied data.
DistanceMatrix() routesCanonical package methods available through DistanceMatrix() are:
Cosine_DistanceEndresSchindelin_DistanceFractional_DistanceGini_DistanceJaccard_DistanceMahalanobis_DistanceMinkowski_DistanceSharedNeighbor_DistanceTfidf_DistanceWasserstein_DistanceDTW_DistanceMSMD_DistanceTWED_Distance() is intentionally excluded from the dispatcher because its explicit time vectors cannot be inferred from the standard matrix interface.
Some functions require suggested packages only when they are used:
OpenCL for the GPU backend.philentropy for additional probability-distribution methods.manydist for mixed numerical and categorical data.dtw and proxy for DTW_Distance().ineq for Gini_Distance().transport for Wasserstein_Distance().ScatterDensity for sample-based Endres-Schindelin calculations.The full manual is available at CRAN.
The package vignette can be opened from R with:
vignette("BIDistances")
Citation information is available with:
citation("BIDistances")
[Thrun, 2021] Thrun, M. C.: The Exploitation of Distance Distributions for Clustering, International Journal of Computational Intelligence and Applications, Vol. 20(3), 2150016, doi:10.1142/S1469026821500164, 2021.
[Stier & Thrun, 2023] Stier, Q., & Thrun, M. C.: Deriving Homogeneous Subsets from Gene Sets by Exploiting the Gene Ontology, Informatica, Vol. 34(2), pp. 357-386, doi:10.15388/23-INFOR517, 2023.