Since this thing is on the innerwebs I suppose it should have a
[python_utils.git] / math_utils.py
index 188d3234986c5f6dd5b0474512402c9adc98cbf1..dec34f049aa0382823768c53efc2060ae8c69409 100644 (file)
@@ -1,15 +1,21 @@
 #!/usr/bin/env python3
 
+# © Copyright 2021-2022, Scott Gasch
+
 """Mathematical helpers."""
 
+import collections
 import functools
 import math
 from heapq import heappop, heappush
-from typing import List, Optional
+from typing import Dict, List, Optional, Tuple
+
+import dict_utils
 
 
 class NumericPopulation(object):
-    """A running median computer.
+    """A numeric population with some statistics such as median, mean, pN,
+    stdev, etc...
 
     >>> pop = NumericPopulation()
     >>> pop.add_number(1)
@@ -24,11 +30,12 @@ class NumericPopulation(object):
     >>> pop.get_mean()
     5.2
     >>> round(pop.get_stdev(), 2)
-    6.99
+    1.75
     >>> pop.get_percentile(20)
     3
     >>> pop.get_percentile(60)
     7
+
     """
 
     def __init__(self):
@@ -68,6 +75,16 @@ class NumericPopulation(object):
         count = len(self.lowers) + len(self.highers)
         return self.aggregate / count
 
+    def get_mode(self) -> Tuple[float, int]:
+        """Returns the mode (most common member)."""
+
+        count: Dict[float, int] = collections.defaultdict(int)
+        for n in self.lowers:
+            count[-n] += 1
+        for n in self.highers:
+            count[n] += 1
+        return dict_utils.item_with_max_value(count)
+
     def get_stdev(self) -> float:
         """Returns the stdev so far in O(n) time."""
 
@@ -78,7 +95,8 @@ class NumericPopulation(object):
             variance += (n - mean) ** 2
         for n in self.highers:
             variance += (n - mean) ** 2
-        return math.sqrt(variance)
+        count = len(self.lowers) + len(self.highers) - 1
+        return math.sqrt(variance) / count
 
     def get_percentile(self, n: float) -> float:
         """Returns the number at approximately pn% (i.e. the nth percentile)