Repository: cogent3/cogent3
Pull request #985 (2021-11-16 02:18:27)

DEP: cogent3.math periodicity classes method names pep8
[CHANGED] old names retained, with deprecation warnings


Patch:
diff --git a/src/cogent3/maths/period.py b/src/cogent3/maths/period.py
index e159dd992..84cc1ea1b 100644
--- a/src/cogent3/maths/period.py
+++ b/src/cogent3/maths/period.py
@@ -87,20 +87,42 @@ class _PeriodEstimator(object):
 
         self.period = period
 
-    def getNumStats(self):
+    def get_num_stats(self):
         """returns the number of statistics computed by this calculator"""
         return 1
 
+    def getNumStats(self):
+        """returns the number of statistics computed by this calculator"""
+        from cogent3.util.warning import deprecated
+
+        deprecated("method", "getNumStats", "get_num_stats", version="2022.5")
+        return self.get_num_stats()
+
 
 class AutoCorrelation(_PeriodEstimator):
+    """class for repetitive calculation of autocorrelation for series of
+    fixed length"""
+
     def __init__(self, length, llim=None, ulim=None, period=None):
-        """class for repetitive calculation of autocorrelation for series of
-        fixed length
+        """
+        Parameters
+        ----------
+        length : int
+            the signal length
+        llim : int
+            lower limit
+        ulim : int
+            upper limit
+        period : int
+            a specific period to return the IPDFT power for
+
+        Notes
+        -----
+        If ``x = [1,1,1,1]`, ``xc = [1,2,3,4,3,2,1]``
 
-        e.g. if x = [1,1,1,1], xc = [1,2,3,4,3,2,1]
         The middle element of xc corresponds to a lag (period) of 0
-        xc is always symmetric for real x
-        N is the length of x"""
+        ``xc`` is always symmetric for real ``x``
+        """
         super(AutoCorrelation, self).__init__(length, llim, ulim, period)
 
         periods = list(range(-length + 1, length))
@@ -110,7 +132,7 @@ class AutoCorrelation(_PeriodEstimator):
         self.periods = array(periods[self.min_idx : self.max_idx + 1])
         self.xc = zeros(2 * self.length - 1)
 
-    def evaluate(self, x):
+    def __call__(self, x):
         x = array(x, float64)
         self.xc.fill(0.0)
         autocorr_inner(x, self.xc, self.length)
@@ -120,8 +142,6 @@ class AutoCorrelation(_PeriodEstimator):
 
         return xc, self.periods
 
-    __call__ = evaluate
-
 
 def auto_corr(x, llim=None, ulim=None):
     """returns the autocorrelation of x
@@ -139,12 +159,18 @@ class Ipdft(_PeriodEstimator):
         """factory function for computing the integer period discrete Fourier
         transform for repeated application to signals of the same length.
 
-        Argument:
-            - length: the signal length
-            - llim: lower limit
-            - ulim: upper limit
-            - period: a specific period to return the IPDFT power for
-            - abs_ft_sig: if True, returns absolute value of signal
+        Parameters
+        ----------
+        length : int
+            the signal length
+        llim : int
+            lower limit
+        ulim : int
+            upper limit
+        period : int
+            a specific period to return the IPDFT power for
+        abs_ft_sig : bool
+            if True, returns absolute value of signal
         """
         if period is not None:
             llim = period
@@ -155,7 +181,7 @@ class Ipdft(_PeriodEstimator):
         self.X = array([0 + 0j] * self.length)
         self.abs_ft_sig = abs_ft_sig
 
-    def evaluate(self, x):
+    def __call__(self, x):
         x = array(x, float64)
         self.X.fill(0 + 0j)
         self.X = ipdft_inner(x, self.X, self.W, self.ulim, self.length)
@@ -169,22 +195,18 @@ class Ipdft(_PeriodEstimator):
 
         return array(pwr), self.periods
 
-    __call__ = evaluate
-
 
 class Goertzel(_PeriodEstimator):
     """Computes the power of a signal for a specific period"""
 
-    def __init__(self, length=None, llim=None, ulim=None, period=None, abs_ft_sig=True):
+    def __init__(self, length=None, period=None, **kwargs):
         assert period is not None, "Goertzel requires a period"
         super(Goertzel, self).__init__(length=length, period=period)
 
-    def evaluate(self, x):
+    def __call__(self, x):
         x = array(x, float64)
         return goertzel_inner(x, self.length, self.period)
 
-    __call__ = evaluate
-
 
 class Hybrid(_PeriodEstimator):
     """hybrid statistic and corresponding periods for signal x
@@ -200,23 +222,33 @@ class Hybrid(_PeriodEstimator):
         abs_ft_sig=True,
         return_all=False,
     ):
-        """Arguments:
-        - length: the length of signals to be encountered
-        - period: specified period at which to return the signal
-        - llim, ulim: the smallest, largest periods to evaluate
-        - return_all: whether to return the hybrid, ipdft, autocorr
-          statistics as a numpy array, or just the hybrid statistic
+        """
+        Parameters
+        ----------
+        length : int
+            the signal length
+        llim : int
+            lower limit
+        ulim : int
+            upper limit
+        period : int
+            a specific period to return the IPDFT power for
+        abs_ft_sig : bool
+            if True, returns absolute value of signal
+        return_all : bool
+            whether to return the hybrid, ipdft, autocorr statistics as
+            a numpy array, or just the hybrid statistic
         """
         super(Hybrid, self).__init__(length, llim, ulim, period)
         self.ipdft = Ipdft(length, llim, ulim, period, abs_ft_sig)
         self.auto = AutoCorrelation(length, llim, ulim, period)
         self._return_all = return_all
 
-    def getNumStats(self):
+    def get_num_stats(self):
         """the number of stats computed by this calculator"""
         return [1, 3][self._return_all]
 
-    def evaluate(self, x):
+    def __call__(self, x):
         if self.period is None:
             auto_sig, auto_periods = self.auto(x)
             ft_sig, ft_periods = self.ipdft(x)
@@ -227,7 +259,7 @@ class Hybrid(_PeriodEstimator):
                 result = hybrid, ft_periods
         else:
             auto_sig = self.auto(x)
-            # ft_sig = goertzel(x, period) # performance slower than ipdft!
+            # note that goertzel(x, period) performance is slower than ipdft!
             ft_sig = self.ipdft(x)
             hybrid = auto_sig * ft_sig
             if self._return_all:
@@ -236,8 +268,6 @@ class Hybrid(_PeriodEstimator):
                 result = abs(hybrid)
         return result
 
-    __call__ = evaluate
-
 
 def ipdft(x, llim=None, ulim=None, period=None):
     """returns the integer period discrete Fourier transform of the signal x
@@ -246,11 +276,12 @@ def ipdft(x, llim=None, ulim=None, period=None):
     ----------
     x
         series of symbols
-    llim
+    llim : int
         lower limit
-    ulim
+    ulim : int
         upper limit
-
+    period : int
+        a specific period to return the IPDFT power for
     """
     x = array(x, float64)
     ipdft_calc = Ipdft(len(x), llim, ulim, period)
@@ -263,9 +294,17 @@ def hybrid(x, llim=None, ulim=None, period=None, return_all=False):
 
     Parameters
     ----------
-    return_all
-        whether to return the hybrid, ipdft, autocorr
-        statistics as a numpy array, or just the hybrid statistic
+    x
+        series of symbols
+    llim : int
+        lower limit
+    ulim : int
+        upper limit
+    period : int
+        a specific period to return the IPDFT power for
+    return_all : bool
+        whether to return the hybrid, ipdft, autocorr statistics as
+        a numpy array, or just the hybrid statistic
 
     See Epps. EURASIP Journal on Bioinformatics and Systems Biology, 2009, 9
     """
@@ -287,12 +326,3 @@ def dft(x, **kwargs):
     pwr.reverse()
     periods.reverse()
     return array(pwr), array(periods)
-
-
-if __name__ == "__main__":
-    from numpy import sin
-
-    x = sin(2 * pi / 5 * arange(1, 9))
-    print(x)
-    print(goertzel(x, 4))
-    print(goertzel(x, 8))
diff --git a/src/cogent3/maths/stats/period.py b/src/cogent3/maths/stats/period.py
index ff9df5459..8ff1118c1 100644
--- a/src/cogent3/maths/stats/period.py
+++ b/src/cogent3/maths/stats/period.py
@@ -76,7 +76,8 @@ def seq_to_symbols(seq, motifs, motif_length, result=None):
         a list of sequence motifs
     motif_length
         length of first motif
-
+    result : ndarray
+        working array. Contents are reset to zero before encoding
     """
     if result is None:
         result = numpy.zeros(len(seq), numpy.uint8)
@@ -111,19 +112,26 @@ class SeqToSymbols(object):
         self.motif_length = motif_length or len(motifs[0])
         self.working = None
         if length is not None:
-            self.setResultArray(length)
+            self.set_result_array(length)
 
-    def setResultArray(self, length):
+    def set_result_array(self, length):
         """sets a result array for length"""
         self.working = numpy.zeros(length, numpy.uint8)
         self.length = length
 
+    def setResultArray(self, length):
+        """deprecated, use set_result_array()"""
+        from cogent3.util.warning import deprecated
+
+        deprecated("method", "setResultArray", "set_result_array", version="2022.5")
+        return self.set_result_array(length)
+
     def __call__(self, seq, result=None):
         if result is None and self.working is None:
-            self.setResultArray(len(seq))
+            self.set_result_array(len(seq))
         elif self.working is not None:
             if len(seq) != self.working.shape[0]:
-                self.setResultArray(len(seq))
+                self.set_result_array(len(seq))
 
         result = self.working
         result.fill(0)
@@ -136,7 +144,19 @@ class SeqToSymbols(object):
 
 
 def circular_indices(vector, start, length, num):
-    """docstring for circular_indices"""
+    """
+
+    Parameters
+    ----------
+    vector : list[int]
+        sequential integers
+    start : int
+        index to start sampling from
+    length : int
+        length of returned vector
+    num : int
+        k-mer size to support
+    """
     if start > length:
         start = start - length
 
@@ -155,7 +175,7 @@ def sampled_places(block_size, length):
     num_seg, remainder = divmod(length, block_size)
     vector = list(range(length))
     result = []
-    for seg_num in range(num_seg):
+    for _ in range(num_seg):
         i = choice(vector)
         result += circular_indices(vector, i, length, block_size)
 
@@ -194,11 +214,7 @@ def blockwise_bootstrap(
     """
     signal_length = len(signal)
 
-    if seq_to_symbols is not None:
-        dtype = "c"
-    else:
-        dtype = None  # let numpy guess
-
+    dtype = "c" if seq_to_symbols is not None else None
     signal = numpy.array(list(signal), dtype=dtype)
 
     if seq_to_symbols is not None:
@@ -208,24 +224,19 @@ def blockwise_bootstrap(
         data = signal
 
     obs_stat = calc(data)
-    if seq_to_symbols is not None:
-        if sum(symbolic) == 0:
-            p = [numpy.array([1.0, 1.0, 1.0]), 1.0][num_stats == 1]
+    if seq_to_symbols is not None and sum(data) == 0:
+        p = [numpy.array([1.0, 1.0, 1.0]), 1.0][num_stats == 1]
 
-            return obs_stat, p
+        return obs_stat, p
 
     if num_stats is None:
         try:
-            num_stats = calc.getNumStats()
+            num_stats = calc.get_num_stats()
         except AttributeError:
             num_stats = 1
 
-    if num_stats == 1:
-        count = 0
-    else:
-        count = numpy.zeros(num_stats)
-
-    for rep in range(num_reps):
+    count = 0 if num_stats == 1 else numpy.zeros(num_stats)
+    for _ in range(num_reps):
         # get sample positions
         sampled_indices = sampled_places(block_size, signal_length)
         new_signal = signal.take(sampled_indices)
@@ -242,10 +253,3 @@ def blockwise_bootstrap(
             count += 1
 
     return obs_stat, count / num_reps
-
-
-# def percrb4():
-#     """Return SNR and CRB for periodicity estimates from symbolic signals"""
-#     # TODO: complete the function according to Julien's percrb4.m
-#     pass
-#


Test patch:
diff --git a/tests/test_maths/test_stats/test_period.py b/tests/test_maths/test_stats/test_period.py
index 16c373894..8d37cd981 100644
--- a/tests/test_maths/test_stats/test_period.py
+++ b/tests/test_maths/test_stats/test_period.py
@@ -259,11 +259,11 @@ class TestPeriodStat(TestCase):
         hybrid_calc = Hybrid(150, llim=2, period=4)
         ipdft_calc = Ipdft(150, llim=2, period=4)
         autocorr_calc = AutoCorrelation(150, llim=2, period=4)
-        self.assertEqual(hybrid_calc.getNumStats(), 1)
-        self.assertEqual(ipdft_calc.getNumStats(), 1)
-        self.assertEqual(autocorr_calc.getNumStats(), 1)
+        self.assertEqual(hybrid_calc.get_num_stats(), 1)
+        self.assertEqual(ipdft_calc.get_num_stats(), 1)
+        self.assertEqual(autocorr_calc.get_num_stats(), 1)
         hybrid_calc = Hybrid(150, llim=2, period=4, return_all=True)
-        self.assertEqual(hybrid_calc.getNumStats(), 3)
+        self.assertEqual(hybrid_calc.get_num_stats(), 3)
 
     def test_permutation_skips(self):
         """permutation test correctly handles data without symbols"""