Repository: statsmodels/statsmodels

Issue:
handle multitests floating point precision issue #7465
- [ ] closes #7465 
- [ ] tests added / passed. 
- [ ] code/documentation is well formatted.  
- [ ] properly formatted commit message. See 
      [NumPy's guide](https://docs.scipy.org/doc/numpy-1.15.1/dev/gitwash/development_workflow.html#writing-the-commit-message). 

<details>


**Notes**:

* It is essential that you add a test when making code changes. Tests are not 
  needed for doc changes.
* When adding a new function, test values should usually be verified in another package (e.g., R/SAS/Stata).
* When fixing a bug, you must add a test that would produce the bug in main and
  then show that it is fixed with the new code.
* New code additions must be well formatted. Changes should pass flake8. If on Linux or OSX, you can
  verify you changes are well formatted by running 
  ```
  git diff upstream/main -u -- "*.py" | flake8 --diff --isolated
  ```
  assuming `flake8` is installed. This command is also available on Windows 
  using the Windows System for Linux once `flake8` is installed in the 
  local Linux environment. While passing this test is not required, it is good practice and it help 
  improve code quality in `statsmodels`.
* Docstring additions must render correctly, including escapes and LaTeX.

</details>



Discussion:

Resolving patch:
diff --git a/statsmodels/stats/multitest.py b/statsmodels/stats/multitest.py
--- a/statsmodels/stats/multitest.py
+++ b/statsmodels/stats/multitest.py
@@ -152,7 +152,7 @@ def multipletests(pvals, alpha=0.05, method='hs', is_sorted=False,
 
     elif method.lower() in ['s', 'sidak']:
         reject = pvals <= alphacSidak
-        pvals_corrected = 1 - np.power((1. - pvals), ntests)
+        pvals_corrected = -np.expm1(ntests * np.log1p(-pvals))
 
     elif method.lower() in ['hs', 'holm-sidak']:
         alphacSidak_all = 1 - np.power((1. - alphaf),
@@ -170,8 +170,11 @@ def multipletests(pvals, alpha=0.05, method='hs', is_sorted=False,
         reject = ~notreject
         del notreject
 
-        pvals_corrected_raw = 1 - np.power((1. - pvals),
-                                           np.arange(ntests, 0, -1))
+        # It's eqivalent to 1 - np.power((1. - pvals),
+        #                           np.arange(ntests, 0, -1))
+        # but prevents the issue of the floating point precision
+        pvals_corrected_raw = -np.expm1(np.arange(ntests, 0, -1) *
+                                        np.log1p(-pvals))
         pvals_corrected = np.maximum.accumulate(pvals_corrected_raw)
         del pvals_corrected_raw
 


Test patch:
diff --git a/statsmodels/stats/tests/test_multi.py b/statsmodels/stats/tests/test_multi.py
--- a/statsmodels/stats/tests/test_multi.py
+++ b/statsmodels/stats/tests/test_multi.py
@@ -343,6 +343,14 @@ def test_issorted(method):
     assert_allclose(res2[0][sortrevind], res1[0], rtol=1e-10)
 
 
+@pytest.mark.parametrize('method', sorted(multitest_methods_names))
+def test_floating_precision(method):
+    # issue #7465
+    pvals = np.full(6000, 0.99)
+    pvals[0] = 1.138569e-56
+    assert multipletests(pvals, method=method)[1][0] > 1e-60
+
+
 def test_tukeyhsd():
     # example multicomp in R p 83