Repository: cogent3/cogent3
Pull request #1445 (2023-05-19 02:03:26)

Change genetic distance name


Patch:
diff --git a/doc/app/user_function.rst b/doc/app/user_function.rst
index 22246fe15..4a8c91e3f 100644
--- a/doc/app/user_function.rst
+++ b/doc/app/user_function.rst
@@ -82,6 +82,6 @@ In this example, we make a function that returns a ``DistanceMatrix`` from an al
     def get_dists(aln: AlignedSeqsType, calc="hamming") -> PairwiseDistanceType:
         return aln.distance_matrix(calc=calc, show_progress=False)
 
-    percent_dist = get_dists(calc="percent")
+    percent_dist = get_dists(calc="pdist")
     result = percent_dist(aln)
     result
diff --git a/src/cogent3/app/align.py b/src/cogent3/app/align.py
index a426d8478..3b4783cda 100644
--- a/src/cogent3/app/align.py
+++ b/src/cogent3/app/align.py
@@ -410,7 +410,7 @@ class progressive_align:
         unique_guides=False,
         indel_length=1e-1,
         indel_rate=1e-10,
-        distance="percent",
+        distance="pdist",
     ):
         """
         Parameters
diff --git a/src/cogent3/app/dist.py b/src/cogent3/app/dist.py
index 9dc9f3112..7e88840d9 100644
--- a/src/cogent3/app/dist.py
+++ b/src/cogent3/app/dist.py
@@ -78,7 +78,7 @@ class fast_slow_dist:
             fast_calc = distance
             slow_calc = distance
 
-        d = {"hamming", "percent", "paralinear", "logdet"} & {slow_calc, fast_calc}
+        d = {"hamming", "pdist", "paralinear", "logdet"} & {slow_calc, fast_calc}
         if d and not self._moltype:
             raise ValueError(f"you must provide a moltype for {d}")
 
diff --git a/src/cogent3/core/alignment.py b/src/cogent3/core/alignment.py
index dbef886c1..2af6ce3e7 100644
--- a/src/cogent3/core/alignment.py
+++ b/src/cogent3/core/alignment.py
@@ -3467,7 +3467,7 @@ class AlignmentI(object):
                 moltype = self.moltype
         return klass(data=data, moltype=moltype, info=self.info, names=self.names)
 
-    def distance_matrix(self, calc="percent", show_progress=False, drop_invalid=False):
+    def distance_matrix(self, calc="pdist", show_progress=False, drop_invalid=False):
         """Returns pairwise distances between sequences.
 
         Parameters
diff --git a/src/cogent3/evolve/fast_distance.py b/src/cogent3/evolve/fast_distance.py
index 5ba912ee4..02b727300 100644
--- a/src/cogent3/evolve/fast_distance.py
+++ b/src/cogent3/evolve/fast_distance.py
@@ -8,6 +8,7 @@ from numpy.linalg import det, inv
 
 from cogent3._version import __version__
 from cogent3.core.moltype import DNA, RNA, get_moltype
+from cogent3.util import warning as c3warn
 from cogent3.util.dict_array import DictArray
 from cogent3.util.misc import get_object_provenance
 from cogent3.util.progress_display import display_wrap
@@ -300,13 +301,13 @@ def _make_stat_table(stats, names, **kwargs):
     )
 
 
-class _PairwiseDistance(object):
+class _PairwiseDistance:
     """base class for computing pairwise distances"""
 
     valid_moltypes = ()
 
     def __init__(self, moltype, invalid=-9, alignment=None, invalid_raises=False):
-        super(_PairwiseDistance, self).__init__()
+        super().__init__()
         moltype = get_moltype(moltype)
         if moltype.label not in self.valid_moltypes:
             name = self.__class__.__name__
@@ -374,10 +375,10 @@ class _PairwiseDistance(object):
         off_diag = [
             (i, j) for i in range(self._dim) for j in range(self._dim) if i != j
         ]
-        off_diag = tuple([tuple(a) for a in zip(*off_diag)])
+        off_diag = tuple(tuple(a) for a in zip(*off_diag))
 
         done = 0.0
-        to_do = (len(names) * len(names) - 1) / 2
+        to_do = (len(names) ** 2 - 1) / 2
         for i in range(len(names) - 1):
             if i in dupes:
                 continue
@@ -526,18 +527,18 @@ class HammingPair(_PairwiseDistance):
 
     def __init__(self, moltype="text", *args, **kwargs):
         """states: the valid sequence states"""
-        super(HammingPair, self).__init__(moltype, *args, **kwargs)
+        super().__init__(moltype, *args, **kwargs)
         self.func = _hamming
 
 
-class PercentIdentityPair(_PairwiseDistance):
-    """Percent identity distance calculator for pairwise alignments"""
+class ProportionIdenticalPair(_PairwiseDistance):
+    """Proportion sites identical distance calculator for pairwise alignments"""
 
     valid_moltypes = ("dna", "rna", "protein", "text", "bytes")
 
     def __init__(self, moltype="text", *args, **kwargs):
         """states: the valid sequence states"""
-        super(PercentIdentityPair, self).__init__(moltype, *args, **kwargs)
+        super().__init__(moltype, *args, **kwargs)
         self.func = _hamming
 
     def get_pairwise_distances(self, include_duplicates=True):
@@ -559,13 +560,23 @@ class PercentIdentityPair(_PairwiseDistance):
         return DistanceMatrix(dists)
 
 
+class PercentIdentityPair(ProportionIdenticalPair):
+    @c3warn.deprecated_callable(
+        "2023.7",
+        reason="name is inaccurate, it's a proportion identy measure",
+        new="ProportionIdenticalPair",
+    )
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+
 class _NucleicSeqPair(_PairwiseDistance):
     """base class pairwise distance calculator for nucleic acid seqs"""
 
     valid_moltypes = ("dna", "rna")
 
     def __init__(self, moltype="dna", *args, **kwargs):
-        super(_NucleicSeqPair, self).__init__(moltype, *args, **kwargs)
+        super().__init__(moltype, *args, **kwargs)
         if not _same_moltype(DNA, self.moltype) and not _same_moltype(
             RNA, self.moltype
         ):
@@ -577,7 +588,7 @@ class JC69Pair(_NucleicSeqPair):
 
     def __init__(self, moltype="dna", *args, **kwargs):
         """states: the valid sequence states"""
-        super(JC69Pair, self).__init__(moltype, *args, **kwargs)
+        super().__init__(moltype, *args, **kwargs)
         self.func = _jc69_from_matrix
 
 
@@ -586,7 +597,7 @@ class TN93Pair(_NucleicSeqPair):
 
     def __init__(self, moltype="dna", *args, **kwargs):
         """states: the valid sequence states"""
-        super(TN93Pair, self).__init__(moltype, *args, **kwargs)
+        super().__init__(moltype, *args, **kwargs)
         self._freqs = zeros(self._dim, float64)
         self.pur_indices = get_purine_indices(self.moltype)
         self.pyr_indices = get_pyrimidine_indices(self.moltype)
@@ -633,7 +644,7 @@ class LogDetPair(_PairwiseDistance):
         if use_tk_adjustment is not None:
             self._func_args = [use_tk_adjustment]
 
-        super(LogDetPair, self).run(*args, **kwargs)
+        super().run(*args, **kwargs)
 
 
 class ParalinearPair(_PairwiseDistance):
@@ -642,7 +653,7 @@ class ParalinearPair(_PairwiseDistance):
     valid_moltypes = ("dna", "rna", "protein")
 
     def __init__(self, moltype="dna", *args, **kwargs):
-        super(ParalinearPair, self).__init__(moltype, *args, **kwargs)
+        super().__init__(moltype, *args, **kwargs)
         self.func = _paralinear
 
 
@@ -652,7 +663,7 @@ _calculators = {
     "jc69": JC69Pair,
     "tn93": TN93Pair,
     "hamming": HammingPair,
-    "percent": PercentIdentityPair,
+    "pdist": ProportionIdenticalPair,
 }
 
 
@@ -661,6 +672,16 @@ def get_distance_calculator(name, *args, **kwargs):
 
     name is converted to lower case"""
     name = name.lower()
+    if name == "percent":
+        name = "pdist"
+        c3warn.deprecated(
+            "value",
+            old="percent",
+            new="pdist",
+            reason="name is inaccurate, it's a proportion identy measure",
+            version="2023.7",
+        )
+
     if "moltype" in kwargs and kwargs.get("moltype") is None:
         kwargs.pop("moltype")
     if name not in _calculators:
@@ -766,6 +787,9 @@ class DistanceMatrix(DictArray):
         if type(names) == str:
             names = [names]
 
+        if list(self.names) == list(names):
+            return self
+
         current_names = array(self.names)
         if negate:
             keep = [i for i, n in enumerate(current_names) if n not in names]


Test patch:
diff --git a/tests/test_app/test_dist.py b/tests/test_app/test_dist.py
index 1337a1db4..1ef7e2866 100644
--- a/tests/test_app/test_dist.py
+++ b/tests/test_app/test_dist.py
@@ -457,7 +457,7 @@ def test_approx_jc69_vals(_seqs1_collection):
 def test_symmetry_of_dists():
     """distances are symmetric"""
     seqs = load_aligned_seqs(DATADIR / "primate_brca1.fasta", moltype="dna")
-    dists = seqs.distance_matrix(calc="percent")
+    dists = seqs.distance_matrix(calc="pdist")
     app = approx_jc69()
     got = app(dists)
     assert_allclose(got.array, got.array.T)
diff --git a/tests/test_evolve/test_distance.py b/tests/test_evolve/test_distance.py
index 5f4dd03ab..f9f03f944 100644
--- a/tests/test_evolve/test_distance.py
+++ b/tests/test_evolve/test_distance.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 import os
 import warnings
 
@@ -23,7 +22,7 @@ from cogent3.evolve.fast_distance import (
     JC69Pair,
     LogDetPair,
     ParalinearPair,
-    PercentIdentityPair,
+    ProportionIdenticalPair,
     TN93Pair,
     _calculators,
     _fill_diversity_matrix,
@@ -144,9 +143,9 @@ class TestPair(TestCase):
         self.assertEqual(list(dists.keys()), list(expect.keys()))
         assert_allclose(list(dists.values()), list(expect.values()))
 
-    def test_percent_pair(self):
+    def test_prop_pair(self):
         """get distances dict"""
-        calc = PercentIdentityPair(DNA, alignment=self.alignment)
+        calc = ProportionIdenticalPair(DNA, alignment=self.alignment)
         calc.run(show_progress=False)
         dists = calc.get_pairwise_distances()
         dists = dists.to_dict()
diff --git a/tests/test_util/test_misc.py b/tests/test_util/test_misc.py
index 6f2527564..4c98fabed 100644
--- a/tests/test_util/test_misc.py
+++ b/tests/test_util/test_misc.py
@@ -1,11 +1,9 @@
-#!/usr/bin/env python
-
 """Unit tests for utility functions and classes.
 """
 
 from copy import copy, deepcopy
 from os import remove, rmdir
-from unittest import TestCase, main
+from unittest import TestCase
 
 import pytest
 
@@ -1252,7 +1250,3 @@ _body_expect = ["Notes", "-----", "body"]
 def test_docstring_to_summary_rest(foo, sum_exp, body_exp):
     summary, body = docstring_to_summary_rest(foo.__doc__)
     assert summary == sum_exp and body.split() == body_exp
-
-
-if __name__ == "__main__":
-    main()