hpo 0.12.0

Human Phenotype Ontology Similarity
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
use std::cmp::Ordering;
use std::collections::hash_map;
use std::collections::HashMap;

use crate::set::HpoSet;
use crate::utils::Combinations;
pub mod cluster;

use cluster::Cluster;
use cluster::ClusterVec;

#[derive(Debug, Default)]
struct DistanceMatrix(HashMap<(usize, usize), f32>);

impl DistanceMatrix {
    fn iter(&'_ self) -> hash_map::Iter<'_, (usize, usize), f32> {
        self.0.iter()
    }

    fn insert(&mut self, k: (usize, usize), v: f32) -> Option<f32> {
        self.0.insert(k, v)
    }

    fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    fn retain<F>(&mut self, f: F)
    where
        F: FnMut(&(usize, usize), &mut f32) -> bool,
    {
        self.0.retain(f);
    }

    fn get(&self, k: &(usize, usize)) -> Option<&f32> {
        self.0.get(k)
    }
}

/// Linkage matrices from `HpoSet`s
///
/// Crate a linkage matrix from a list of `HpoSet`s to use in dendograms
/// or other hierarchical cluster analyses
///
/// Provided algorithms for clustering
///
/// - [`Linkage::union`](`Linkage::union`): Create a new `HpoSet` for each cluster based on the union of
///   both combined clusters. This method becomes slow with growing input data
/// - [`Linkage::single`](`Linkage::single`): The minimum distance of each cluster's nodes to the other
///   nodes is used as distance for newly formed clusters. This is also known as the Nearest Point Algorithm.
/// - [`Linkage::complete`](`Linkage::complete`): The maximum distance of each cluster's nodes to the other
///   nodes is used as distance for newly formed clusters. This is also known by the Farthest Point Algorithm
///   or Voor Hees Algorithm.
/// - [`Linkage::average`](`Linkage::average`): The mean distance of each cluster's nodes to the other
///   nodes is used as distance for newly formed clusters. This is also called the UPGMA algorithm.
///
/// # Examples
///
/// ```rust
///
/// use hpo::Ontology;
/// use hpo::HpoSet;
/// use hpo::similarity::GroupSimilarity;
/// use hpo::utils::Combinations;
/// use hpo::stats::Linkage;
///
/// // This method can and should utilize parallel processing, e.g.
/// // using rayon iterators
/// fn distance(combs: Combinations<HpoSet<'_>>) -> Vec<f32> {
///     let sim = GroupSimilarity::default();
///     combs.map(|comp| {
///         1.0 - sim.calculate(comp.0, comp.1)
///     }).collect()
/// }
///
/// let ontology = Ontology::from_binary("tests/example.hpo").unwrap();
/// let sets = vec![
///     ontology.gene_by_name("KRAS").unwrap().to_hpo_set(&ontology),
///     ontology.gene_by_name("WDR45").unwrap().to_hpo_set(&ontology),
///     ontology.gene_by_name("TP53").unwrap().to_hpo_set(&ontology),
///     ontology.gene_by_name("CLCN7").unwrap().to_hpo_set(&ontology),
/// ];
///
///
/// let mut cluster = Linkage::union(sets, distance).into_cluster();
/// let first = cluster.next().unwrap();
/// // println!("{:?}", first);
/// // Cluster { idx1: 0, idx2: 3, distance: 0.008127391, size: 2 }
/// assert_eq!(cluster.next().unwrap().len(), 2);
/// assert_eq!(cluster.next().unwrap().len(), 4);
/// assert!(cluster.next().is_none());
/// ```
pub struct Linkage<'a> {
    sets: Vec<Option<HpoSet<'a>>>,
    distance_matrix: DistanceMatrix,
    initial_len: usize,
    clusters: ClusterVec,
}

impl<'a> Linkage<'a> {
    /// Performs union-based hierarchical clustering of `HpoSet`s
    ///
    /// In each iteration, `HpoSet`s are compared to each other based on the
    /// provided `distance` function. `Cluster`s are formed by combining the
    /// 2 closest `HpoSet`s into a single set (forming the union).
    ///
    /// This method becomes exponentially slower with larger lists of sets,
    /// because it merges sets and calculates pairwise similarities
    /// for each term in each set.
    ///
    /// # Examples
    ///
    /// ```rust
    ///
    /// use hpo::Ontology;
    /// use hpo::HpoSet;
    /// use hpo::similarity::GroupSimilarity;
    /// use hpo::utils::Combinations;
    /// use hpo::stats::Linkage;
    ///
    /// // This method can and should utilize parallel processing, e.g.
    /// // using rayon iterators
    /// fn distance(combs: Combinations<HpoSet<'_>>) -> Vec<f32> {
    ///     let sim = GroupSimilarity::default();
    ///     combs.map(|comp| {
    ///         1.0 - sim.calculate(comp.0, comp.1)
    ///     }).collect()
    /// }
    ///
    /// let ontology = Ontology::from_binary("tests/example.hpo").unwrap();
    /// let sets = vec![
    ///     ontology.gene_by_name("KRAS").unwrap().to_hpo_set(&ontology),
    ///     ontology.gene_by_name("WDR45").unwrap().to_hpo_set(&ontology),
    ///     ontology.gene_by_name("TP53").unwrap().to_hpo_set(&ontology),
    ///     ontology.gene_by_name("CLCN7").unwrap().to_hpo_set(&ontology),
    /// ];
    ///
    ///
    /// let mut cluster = Linkage::union(sets, distance).into_cluster();
    /// let first = cluster.next().unwrap();
    /// println!("{:?}", first);
    /// // Cluster { idx1: 0, idx2: 3, distance: 0.16666663, size: 2 }
    /// assert_eq!(cluster.next().unwrap().len(), 2);
    /// assert_eq!(cluster.next().unwrap().len(), 4);
    /// assert!(cluster.next().is_none());
    /// ```
    pub fn union<T, F>(sets: T, distance: F) -> Self
    where
        T: IntoIterator<Item = HpoSet<'a>>,
        F: Fn(Combinations<HpoSet<'_>>) -> Vec<f32>,
    {
        let mut s = Self::new(sets, &distance);
        s.cluster_set_unions(&distance);
        s
    }

    /// Performs single-hierarchical clustering of `HpoSet`s
    ///
    /// `HpoSet`s are compared to each other based on the
    /// provided `distance` function. `Cluster`s are formed by using the minimum
    /// distance of each encompassing set to the comparison set.
    /// This is also known as the Nearest Point Algorithm.
    ///
    /// # Examples
    ///
    /// ```rust
    ///
    /// use hpo::Ontology;
    /// use hpo::HpoSet;
    /// use hpo::similarity::GroupSimilarity;
    /// use hpo::utils::Combinations;
    /// use hpo::stats::Linkage;
    ///
    /// // This method can and should utilize parallel processing, e.g.
    /// // using rayon iterators
    /// fn distance(combs: Combinations<HpoSet<'_>>) -> Vec<f32> {
    ///     let sim = GroupSimilarity::default();
    ///     combs.map(|comp| {
    ///         1.0 - sim.calculate(comp.0, comp.1)
    ///     }).collect()
    /// }
    ///
    /// let ontology = Ontology::from_binary("tests/example.hpo").unwrap();
    /// let sets = vec![
    ///     ontology.gene_by_name("KRAS").unwrap().to_hpo_set(&ontology),
    ///     ontology.gene_by_name("WDR45").unwrap().to_hpo_set(&ontology),
    ///     ontology.gene_by_name("TP53").unwrap().to_hpo_set(&ontology),
    ///     ontology.gene_by_name("CLCN7").unwrap().to_hpo_set(&ontology),
    /// ];
    ///
    ///
    /// let mut cluster = Linkage::single(sets, distance).into_cluster();
    /// let first = cluster.next().unwrap();
    /// println!("{:?}", first);
    /// // Cluster { idx1: 0, idx2: 3, distance: 0.16666663, size: 2 }
    /// assert_eq!(cluster.next().unwrap().len(), 2);
    /// assert_eq!(cluster.next().unwrap().len(), 4);
    /// assert!(cluster.next().is_none());
    /// ```
    pub fn single<T, F>(sets: T, distance: F) -> Self
    where
        T: IntoIterator<Item = HpoSet<'a>>,
        F: Fn(Combinations<HpoSet<'_>>) -> Vec<f32>,
    {
        fn f32_min(v1: Option<&f32>, v2: Option<&f32>) -> f32 {
            if v1.expect("v1 must be `Some`") < v2.expect("v2 must be `Some`") {
                *v1.expect("v1 must be `Some`")
            } else {
                *v2.expect("v2 must be `Some`")
            }
        }

        let mut linkage = Self::new(sets, &distance);
        linkage.arithmetic_cluster(f32_min);
        linkage
    }

    /// Performs complete-hierarchical clustering of `HpoSet`s
    ///
    /// `HpoSet`s are compared to each other based on the
    /// provided `distance` function. `Cluster`s are formed by using the maximum
    /// distance of each encompassing set to the comparison set.
    /// This is also known by the Farthest Point Algorithm or Voor Hees Algorithm.
    pub fn complete<T, F>(sets: T, distance: F) -> Self
    where
        T: IntoIterator<Item = HpoSet<'a>>,
        F: Fn(Combinations<HpoSet<'_>>) -> Vec<f32>,
    {
        fn f32_max(v1: Option<&f32>, v2: Option<&f32>) -> f32 {
            if v1.expect("v1 must be `Some`") > v2.expect("v2 must be `Some`") {
                *v1.expect("v1 must be `Some`")
            } else {
                *v2.expect("v2 must be `Some`")
            }
        }

        let mut linkage = Self::new(sets, &distance);
        linkage.arithmetic_cluster(f32_max);
        linkage
    }

    /// Performs average-hierarchical clustering of `HpoSet`s
    ///
    /// `HpoSet`s are compared to each other based on the
    /// provided `distance` function. `Cluster`s are formed by using the average
    /// distance of both encompassing sets to the comparison set.
    /// This is also called the UPGMA algorithm.
    ///
    /// # Note
    /// This method is not implemented completely correct. Instead of calculating
    /// the average of all distances, it only uses the mean distance of all direct
    /// cluster nodes.
    pub fn average<T, F>(sets: T, distance: F) -> Self
    where
        T: IntoIterator<Item = HpoSet<'a>>,
        F: Fn(Combinations<HpoSet<'_>>) -> Vec<f32>,
    {
        fn mean(v1: Option<&f32>, v2: Option<&f32>) -> f32 {
            (v1.expect("v1 must be `Some`") + v2.expect("v2 must be `Some`")) / 2.0
        }

        let mut linkage = Self::new(sets, &distance);
        linkage.arithmetic_cluster(mean);
        linkage
    }

    /// Returns an Iterator of [`Cluster`] references
    pub fn cluster(&'_ self) -> cluster::Iter<'_> {
        self.clusters.iter()
    }

    /// Returns an Iterator of owned [`Cluster`]
    pub fn into_cluster(self) -> cluster::IntoIter {
        self.clusters.into_iter()
    }

    /// Returns the order of the input set items in the final cluster
    ///
    /// # Examples
    ///
    /// ```rust
    ///
    /// use hpo::Ontology;
    /// use hpo::HpoSet;
    /// use hpo::similarity::GroupSimilarity;
    /// use hpo::utils::Combinations;
    /// use hpo::stats::Linkage;
    ///
    /// // This method can and should utilize parallel processing, e.g.
    /// // using rayon iterators
    /// fn distance(combs: Combinations<HpoSet<'_>>) -> Vec<f32> {
    ///     let sim = GroupSimilarity::default();
    ///     combs.map(|comp| {
    ///         1.0 - sim.calculate(comp.0, comp.1)
    ///     }).collect()
    /// }
    ///
    /// let ontology = Ontology::from_binary("tests/example.hpo").unwrap();
    ///
    /// let genes = vec!["KRAS", "WDR45", "ZNRF3", "CLCN7"];
    /// let sets = genes.iter().map(|gene| ontology.gene_by_name(gene).unwrap().to_hpo_set(&ontology));
    ///
    /// let linkage = Linkage::single(sets, distance);
    /// let indicies = linkage.indicies();
    ///
    /// // Similarities:
    /// // CLCN7 WDR45  0.8333334
    /// // CLCN7 ZNRF3  0.66666
    /// // KRAS WDR45   0.5
    /// // KRAS CLCN7   0.4166667
    /// // KRAS ZNRF3   0
    /// // WDR45 ZNRF3  0
    ///
    /// assert_eq!(indicies, vec![1usize, 3usize, 2usize, 0usize]);
    /// for idx in indicies {
    ///    print!("{} ", genes[idx]);
    /// }
    /// // "KRAS ZNRF3 WDR45 CLCN7"
    /// ```
    pub fn indicies(&self) -> Vec<usize> {
        let mut res = Vec::with_capacity(self.initial_len);
        for cluster in &self.clusters {
            if cluster.lhs() < self.initial_len {
                res.push(cluster.lhs());
            }
            if cluster.rhs() < self.initial_len {
                res.push(cluster.rhs());
            }
        }
        res
    }

    fn new<T, F>(sets: T, distance: F) -> Self
    where
        T: IntoIterator<Item = HpoSet<'a>>,
        F: Fn(Combinations<HpoSet<'_>>) -> Vec<f32>,
    {
        let sets: Vec<Option<HpoSet<'a>>> = sets.into_iter().map(Some).collect();
        let len = sets.len();
        let mut s = Self {
            sets,
            distance_matrix: DistanceMatrix::default(),
            initial_len: len,
            clusters: ClusterVec::with_capacity(len),
        };
        // initial calculation of all distances
        s.calculate_initial_distances(distance);
        s
    }

    /// Creates a `DistanceMatrix` with the distances of all `Combinations`
    ///
    /// Note that the `func` return value should be `Distance`, not `Similarity`!
    fn calculate_initial_distances<F: Fn(Combinations<HpoSet<'a>>) -> Vec<f32>>(
        &mut self,
        func: F,
    ) {
        let similarities = func(Combinations::new(&self.sets));

        let index: Vec<Option<usize>> = (0..self.sets.len()).map(Some).collect();
        for ((idx1, idx2), sim) in Combinations::new(&index).zip(similarities.into_iter()) {
            self.distance_matrix.insert((*idx1, *idx2), sim);
        }
    }

    /// Gets the closest two clusters and returns their indicies and their distance
    fn closest_clusters(&self) -> ((usize, usize), f32) {
        self.distance_matrix
            .iter()
            .reduce(|max, elmt| if elmt.1 < max.1 { elmt } else { max })
            .map(|elmt| (*elmt.0, *elmt.1))
            .expect("distance matrix is not empty")
    }

    /// Adds a new cluster
    fn new_cluster(&mut self, key: (usize, usize), dist: f32) {
        self.clusters.push(Cluster::new(
            key.0,
            key.1,
            dist,
            self.size_of_cluster(key.0, key.1),
        ));
    }

    /// Iteratively clusters all sets in the `Linkage` until none are left
    ///
    /// - Finds the 2 clusters/sets with smallest distance
    /// - inactivates them and creates a new, merged set
    /// - calculates the distance between the new set and all other active sets
    /// - appends the `DistanceMatrix` with new distances
    fn cluster_set_unions<F>(&mut self, func: F)
    where
        F: Fn(Combinations<HpoSet<'a>>) -> Vec<f32>,
    {
        loop {
            if self.distance_matrix.is_empty() {
                // All sets are clustered, only one cluster remains
                return;
            }

            // get the indicies of the 2 sets with the smallest distance
            let (key, dist) = self.closest_clusters();

            // create a new cluster with the 2 sets
            self.new_cluster(key, dist);

            // merge the 2 sets and remove them from `self.sets`
            let mut newset = self.sets[key.0]
                .take()
                .expect("set is part of distance matrix and must exist");
            let set2 = self.sets[key.1]
                .take()
                .expect("set is part of distance matrix and must exist");
            newset.extend(&set2);
            self.sets.push(Some(newset));

            // remove all distance scores that include one of the 2 sets
            self.distance_matrix.retain(|(idx1, idx2), _| {
                idx1 != &key.0 && idx1 != &key.1 && idx2 != &key.0 && idx2 != &key.1
            });

            // calculate the distance scores from the new set to all other active sets
            let mut new_combinations = Combinations::new(&self.sets);
            new_combinations.set_to_last();
            let mut distances = func(new_combinations).into_iter();

            // the distance is only calculated for active sets, inactive ones are skipped.
            // due to this, we don't have the proper mapping of index to distance score.
            // to account for this, we're looping through all sets, checking if they are active
            // and then getting the next distance score.
            let last_index = self.sets.len() - 1;
            for (idx, set) in self.sets[..last_index].iter().enumerate() {
                if set.is_some() {
                    self.distance_matrix.insert(
                        (idx, last_index),
                        distances.next().expect("distance score must be present"),
                    );
                }
            }
        }
    }

    /// Iteratively clusters all sets in the `Linkage` until none are left
    ///
    /// - Finds the 2 clusters/sets with smallest distance
    /// - inactivates them and adds a new phantom set at the end
    /// - calculates the distance between the nodes of the new cluster and all
    ///   other clusters based on `func`
    /// - appends the `DistanceMatrix` with new distances
    fn arithmetic_cluster<F>(&mut self, func: F)
    where
        F: Fn(Option<&f32>, Option<&f32>) -> f32,
    {
        loop {
            if self.distance_matrix.is_empty() {
                // All sets are clustered, only one cluster remains
                return;
            }

            // get the indicies of the 2 sets with the smallest distance
            let (key, dist) = self.closest_clusters();

            // create a new cluster with the 2 sets
            self.new_cluster(key, dist);

            // Remove sets
            // For simplicity reasons, add one set to the end as new phantom set
            // (it's pushed to `sets` at the end of the loop)
            let x = self.sets[key.0].take();
            self.sets[key.1].take();

            // add new distance measures to the matrix for
            // each distance between new cluster vs all existing clusters
            // - iterate all existing cluster
            // - compare to new-cluster-1
            // - compare to new-cluster-2
            // - add lower value to distance-matrix
            let new_idx = self.sets.len();
            for (idx, set) in self.sets.iter().enumerate() {
                if idx == key.0 || idx == key.1 {
                    continue;
                }
                if set.is_some() {
                    let distance = match (idx.cmp(&key.0), idx.cmp(&key.1)) {
                        (Ordering::Less, Ordering::Less) => func(
                            self.distance_matrix.get(&(idx, key.0)),
                            self.distance_matrix.get(&(idx, key.1)),
                        ),
                        (Ordering::Less, Ordering::Greater) => func(
                            self.distance_matrix.get(&(idx, key.0)),
                            self.distance_matrix.get(&(key.1, idx)),
                        ),
                        (Ordering::Greater, Ordering::Less) => func(
                            self.distance_matrix.get(&(key.0, idx)),
                            self.distance_matrix.get(&(idx, key.1)),
                        ),
                        (Ordering::Greater, Ordering::Greater) => func(
                            self.distance_matrix.get(&(key.0, idx)),
                            self.distance_matrix.get(&(key.1, idx)),
                        ),
                        (Ordering::Equal, _) | (_, Ordering::Equal) => {
                            unreachable!("Cannot be reached")
                        }
                    };
                    self.distance_matrix.insert((idx, new_idx), distance);
                }
            }
            // remove all distance scores that include one of the 2 sets
            self.distance_matrix.retain(|(idx1, idx2), _| {
                idx1 != &key.0 && idx1 != &key.1 && idx2 != &key.0 && idx2 != &key.1
            });

            self.sets.push(x);
        }
    }

    /// Returns the size of the cluster, the sum of sizes of both nodes
    fn size_of_cluster(&self, idx1: usize, idx2: usize) -> usize {
        (if idx1 < self.initial_len {
            1
        } else {
            self.clusters
                .get(idx1 - self.initial_len)
                .expect("idx is guaranteed to be in cluster")
                .len()
        }) + (if idx2 < self.initial_len {
            1
        } else {
            self.clusters
                .get(idx2 - self.initial_len)
                .expect("idx is guaranteed to be in cluster")
                .len()
        })
    }

    /// Returns an Iterator of [`Cluster`] references
    pub fn iter(&'_ self) -> cluster::Iter<'_> {
        self.cluster()
    }
}

impl<'a> IntoIterator for &'a Linkage<'a> {
    type Item = &'a Cluster;
    type IntoIter = cluster::Iter<'a>;
    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

impl IntoIterator for Linkage<'_> {
    type Item = Cluster;
    type IntoIter = cluster::IntoIter;
    fn into_iter(self) -> Self::IntoIter {
        self.into_cluster()
    }
}