Expand description
Hierarchical clustering by similarity, with batteries-included string metrics.
The crate exposes two layers:
-
Layer 1 (metric-agnostic):
clusterandcluster_with_candidatesrun complete-linkage hierarchical clustering over any&[T]with a user-supplied distance function. Output is aClustersof indices into the input slice. -
Layer 2 (string pipeline):
group_similaradds normalize → dedup → cluster → expand glue for string-bearing records, using aConfigthat bundles a metric (Jaro-Winkler, IDF-weighted token cosine, …) with a threshold, normalizer, and candidate-pair strategy (Blocking).
§Example: string pipeline
use group_similar::{Config, Threshold, group_similar};
use std::convert::TryInto;
#[derive(Eq, PartialEq, std::hash::Hash, Debug, Ord, PartialOrd)]
struct Merchant {
id: usize,
name: String,
}
impl AsRef<str> for Merchant {
fn as_ref(&self) -> &str { &self.name }
}
let merchants = vec![
Merchant { id: 1, name: "McDonalds 105109".to_string() },
Merchant { id: 2, name: "McDonalds 105110".to_string() },
Merchant { id: 3, name: "Target ID1244".to_string() },
Merchant { id: 4, name: "Target ID125".to_string() },
Merchant { id: 5, name: "Amazon.com TID120159120".to_string() },
Merchant { id: 6, name: "Target".to_string() },
Merchant { id: 7, name: "Target.com".to_string() },
];
// Fit IDF-weighted token cosine similarity to the corpus, with leading-
// token positional weighting (distinctive identifiers tend to lead the
// string; shared boilerplate trails).
let threshold: Threshold = 0.4_f64.try_into().unwrap();
let config = Config::token_cosine_positional(&merchants, threshold);
let results = group_similar(&merchants, &config);
// Every input appears exactly once across keys + values.
let total: usize = results.iter().map(|(_, vs)| 1 + vs.len()).sum();
assert_eq!(total, merchants.len());§Example: metric-agnostic clustering
use group_similar::{cluster, Clusters, Distance, Method, Threshold};
use std::convert::TryInto;
// Cluster floats in [0, 1] by absolute difference.
let items = vec![0.0_f32, 0.05, 0.1, 0.9, 0.95];
let threshold: Threshold = 0.2_f64.try_into().unwrap();
let clusters: Clusters = cluster(
&items,
|a: &f32, b: &f32| Distance::clamped((a - b).abs()),
threshold,
Method::Complete,
);
// Two clusters: {0.0, 0.05, 0.1} and {0.9, 0.95}.
assert_eq!(clusters.matched.len(), 2);
assert_eq!(clusters.unmatched.len(), 0);Modules§
- normalize
- Built-in string normalizers used in the dedup stage.
Structs§
- Clusters
- Result of clustering: groups of items that merged into clusters, plus the singletons that did not.
- Config
- Configuration for the string pipeline (
crate::group_similar). - Distance
- Pairwise distance in
[0.0, 1.0]. Smaller = more similar. - Threshold
Thresholdis a newtype wrapper describing how permissive comparisons are for a given comparison closure.
Enums§
- Blocking
- Candidate-pair generation strategy used by the string pipeline.
- Method
- A method for computing the dissimilarities between clusters.
Functions§
- cluster
- Run hierarchical clustering over
itemsusingdistance. - cluster_
with_ candidates - Run hierarchical clustering using a caller-supplied set of candidate pairs.
- group_
similar - Group string-bearing records via normalize → dedup → cluster → expand.
- qgram_
candidates - Generate candidate pairs for
cluster_with_candidatesusing a character trigram inverted index. - similarity_
matrix - Build a condensed pairwise distance matrix using
compare, parallelized with Rayon. Returns the upper triangle in row-major order (n*(n-1)/2entries) as rawf32s — the layout expected by kodama. The closure’sDistancereturn is unwrapped at the matrix boundary.