Repository: statsmodels/statsmodels

Issue:
statsmodels.stats.rates.test_poisson_2indep has ZeroDivisionError if count2 is 0
#### Describe the bug

When running `statsmodels.stats.rates.test_poisson_2indep` on data with `count2=0`, the function raises a `ZeroDivisionError` instead of computing the test statistics.

#### Code Sample, a copy-pastable example if possible


```python
>>> from statsmodels.stats.rates import test_poisson_2indep
/home/gereon/.local/lib/python3.10/site-packages/statsmodels/compat/pandas.py:65: FutureWarning: pandas.Int64Index is deprecated and will be removed from pandas in a future version. Use pandas.Index with the appropriate dtype instead.
  from pandas import Int64Index as NumericIndex
>>> test_poisson_2indep(1, 1, 0, 4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/gereon/.local/lib/python3.10/site-packages/statsmodels/stats/rates.py", line 140, in test_poisson_2indep
    ratio = rates[0] / rates[1]
ZeroDivisionError: float division by zero
>>> test_poisson_2indep(0, 4, 1, 1)
<class 'statsmodels.stats.base.HolderTuple'>
statistic = -2.0
pvalue = 0.04550026389635839
distribution = 'normal'
method = 'score'
alternative = 'two-sided'
rates = (0.0, 1.0)
ratio = 0.0
ratio_null = 1
tuple = (-2.0, 0.04550026389635839)
```

#### Expected Output

I believe the test is symmetric, so 

```
>>> test_poisson_2indep(1, 1, 0, 4)
<class 'statsmodels.stats.base.HolderTuple'>
statistic = -2.0
pvalue = 0.04550026389635839
distribution = 'normal'
method = 'score'
alternative = 'two-sided'
rates = (1.0, 0.0)
ratio = 0.0
ratio_null = 1
tuple = (-2.0, 0.04550026389635839)

```
#### Output of ``import statsmodels.api as sm; sm.show_versions()``


INSTALLED VERSIONS
------------------
Python: 3.10.4.final.0
OS: Linux 5.10.114-1-MANJARO #1 SMP PREEMPT Mon May 9 07:52:55 UTC 2022 x86_64
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8

statsmodels
===========

Installed: 0.13.2 (/home/gereon/.local/lib/python3.10/site-packages/statsmodels)

Required Dependencies
=====================

cython: 0.29.28 (/usr/lib/python3.10/site-packages/Cython)
numpy: 1.22.3 (/usr/lib/python3.10/site-packages/numpy)
scipy: 1.8.1 (/home/gereon/.local/lib/python3.10/site-packages/scipy)
pandas: 1.4.2 (/usr/lib/python3.10/site-packages/pandas)
    dateutil: 2.8.2 (/usr/lib/python3.10/site-packages/dateutil)
patsy: 0.5.2 (/home/gereon/.local/lib/python3.10/site-packages/patsy)

Optional Dependencies
=====================

matplotlib: 3.5.2 (/usr/lib/python3.10/site-packages/matplotlib)
    backend: GTK3Agg 
cvxopt: Not installed
joblib: 1.1.0 (/usr/lib/python3.10/site-packages/joblib)

Developer Tools
================

IPython: 8.3.0 (/usr/lib/python3.10/site-packages/IPython)
    jinja2: 3.0.3 (/usr/lib/python3.10/site-packages/jinja2)
sphinx: 4.5.0 (/usr/lib/python3.10/site-packages/sphinx)
    pygments: 2.12.0 (/usr/lib/python3.10/site-packages/pygments)
pytest: 7.1.2 (/usr/lib/python3.10/site-packages/pytest)
virtualenv: 20.11.0 (/usr/lib/python3.10/site-packages/virtualenv)



Discussion:

Resolving patch:
diff --git a/statsmodels/stats/_inference_tools.py b/statsmodels/stats/_inference_tools.py
new file mode 100644
--- /dev/null
+++ b/statsmodels/stats/_inference_tools.py
@@ -0,0 +1,83 @@
+"""
+Created on Mar 30, 2022 1:21:54 PM
+
+Author: Josef Perktold
+License: BSD-3
+"""
+
+import numpy as np
+from numpy.testing import assert_allclose
+
+
+def _mover_confint(stat1, stat2, ci1, ci2, contrast="diff"):
+    """
+
+    References
+    ----------
+
+    .. [#] Krishnamoorthy, K., Jie Peng, and Dan Zhang. 2016. “Modified Large
+       Sample Confidence Intervals for Poisson Distributions: Ratio, Weighted
+       Average, and Product of Means.” Communications in Statistics - Theory
+       and Methods 45 (1): 83–97. https://doi.org/10.1080/03610926.2013.821486.
+
+
+    .. [#] Li, Yanhong, John J. Koval, Allan Donner, and G. Y. Zou. 2010.
+       “Interval Estimation for the Area under the Receiver Operating
+       Characteristic Curve When Data Are Subject to Error.” Statistics in
+       Medicine 29 (24): 2521–31. https://doi.org/10.1002/sim.4015.
+
+    .. [#] Zou, G. Y., and A. Donner. 2008. “Construction of Confidence Limits
+       about Effect Measures: A General Approach.” Statistics in Medicine 27
+       (10): 1693–1702. https://doi.org/10.1002/sim.3095.
+    """
+
+    if contrast == "diff":
+        stat = stat1 - stat2
+        low_half = np.sqrt((stat1 - ci1[0])**2 + (stat2 - ci2[1])**2)
+        upp_half = np.sqrt((stat1 - ci1[1])**2 + (stat2 - ci2[0])**2)
+        ci = (stat - low_half, stat + upp_half)
+
+    elif contrast == "sum":
+        stat = stat1 + stat2
+        low_half = np.sqrt((stat1 - ci1[0])**2 + (stat2 - ci2[0])**2)
+        upp_half = np.sqrt((stat1 - ci1[1])**2 + (stat2 - ci2[1])**2)
+        ci = (stat - low_half, stat + upp_half)
+
+    elif contrast == "ratio":
+        # stat = stat1 / stat2
+        prod = stat1 * stat2
+        term1 = stat2**2 - (ci2[1] - stat2)**2
+        term2 = stat2**2 - (ci2[0] - stat2)**2
+        low_ = (prod -
+                np.sqrt(prod**2 - term1 * (stat1**2 - (ci1[0] - stat1)**2))
+                ) / term1
+        upp_ = (prod +
+                np.sqrt(prod**2 - term2 * (stat1**2 - (ci1[1] - stat1)**2))
+                ) / term2
+
+        # method 2 Li, Tang, Wong 2014
+        low1, upp1 = ci1
+        low2, upp2 = ci2
+        term1 = upp2 * (2 * stat2 - upp2)
+        term2 = low2 * (2 * stat2 - low2)
+        low = (prod -
+               np.sqrt(prod**2 - term1 * low1 * (2 * stat1 - low1))
+               ) / term1
+        upp = (prod +
+               np.sqrt(prod**2 - term2 * upp1 * (2 * stat1 - upp1))
+               ) / term2
+
+        assert_allclose((low_, upp_), (low, upp), atol=1e-15, rtol=1e-10)
+
+        ci = (low, upp)
+
+    return ci
+
+
+def _mover_confint_sum(stat, ci):
+
+    stat_ = stat.sum(0)
+    low_half = np.sqrt(np.sum((stat_ - ci[0])**2))
+    upp_half = np.sqrt(np.sum((stat_ - ci[1])**2))
+    ci = (stat - low_half, stat + upp_half)
+    return ci
diff --git a/statsmodels/stats/rates.py b/statsmodels/stats/rates.py
--- a/statsmodels/stats/rates.py
+++ b/statsmodels/stats/rates.py
@@ -5,52 +5,675 @@
 
 '''
 
-
 import numpy as np
 import warnings
 
-from scipy import stats
+from scipy import stats, optimize
 
 from statsmodels.stats.base import HolderTuple
 from statsmodels.stats.weightstats import _zstat_generic2
+from statsmodels.stats._inference_tools import _mover_confint
+
+# shorthand
+norm = stats.norm
+
+
+method_names_poisson_1samp = {
+    "test": [
+        "wald",
+        "score",
+        "exact-c",
+        "midp-c",
+        "waldccv",
+        "sqrt-a",
+        "sqrt-v",
+        "sqrt",
+        ],
+    "confint": [
+        "wald",
+        "score",
+        "exact-c",
+        "midp-c",
+        "jeff",
+        "waldccv",
+        "sqrt-a",
+        "sqrt-v",
+        "sqrt",
+        "sqrt-cent",
+        "sqrt-centcc",
+        ]
+    }
+
+
+def test_poisson(count, nobs, value, method=None, alternative="two-sided",
+                 dispersion=1):
+    """Test for one sample poisson mean or rate
+
+    Parameters
+    ----------
+    count : array_like
+        Observed count, number of events.
+    nobs : arrat_like
+        Currently this is total exposure time of the count variable.
+        This will likely change.
+    value : float, array_like
+        This is the value of poisson rate under the null hypothesis.
+    method : str
+        Method to use for confidence interval.
+        This is required, there is currently no default method.
+        See Notes for available methods.
+    alternative : {'two-sided', 'smaller', 'larger'}
+        alternative hypothesis, which can be two-sided or either one of the
+        one-sided tests.
+    dispersion : float
+        Dispersion scale coefficient for Poisson QMLE. Default is that the
+        data follows a Poisson distribution. Dispersion different from 1
+        correspond to excess-dispersion in Poisson quasi-likelihood (GLM).
+        Dispersion coeffficient different from one is currently only used in
+        wald and score method.
 
+    Returns
+    -------
+    HolderTuple instance with test statistic, pvalue and other attributes.
+
+    Notes
+    -----
+    The implementatio of the hypothesis test is mainly based on the references
+    for the confidence interval, see confint_poisson.
 
-def test_poisson_2indep(count1, exposure1, count2, exposure2, ratio_null=1,
-                        method='score', alternative='two-sided',
-                        etest_kwds=None):
-    '''test for ratio of two sample Poisson intensities
+    Available methods are:
 
-    If the two Poisson rates are g1 and g2, then the Null hypothesis is
+    - "score" : based on score test, uses variance under null value
+    - "wald" : based on wald test, uses variance base on estimated rate.
+    - "waldccv" : based on wald test with 0.5 count added to variance
+      computation. This does not use continuity correction for the center of
+      the confidence interval.
+    - "exact-c" central confidence interval based on gamma distribution
+    - "midp-c" : based on midp correction of central exact confidence interval.
+      this uses numerical inversion of the test function. not vectorized.
+    - "sqrt" : based on square root transformed counts
+    - "sqrt-a" based on Anscombe square root transformation of counts + 3/8.
 
-    - H0: g1 / g2 = ratio_null
+    See Also
+    --------
+    confint_poisson
+
+    """
+
+    n = nobs  # short hand
+    rate = count / n
+
+    if method is None:
+        msg = "method needs to be specified, currently no default method"
+        raise ValueError(msg)
+
+    if dispersion != 1:
+        if method not in ["wald", "waldcc", "score"]:
+            msg = "excess dispersion only supported in wald and score methods"
+            raise ValueError(msg)
+
+    dist = "normal"
+
+    if method == "wald":
+        std = np.sqrt(dispersion * rate / n)
+        statistic = (rate - value) / std
+
+    elif method == "waldccv":
+        # WCC in Barker 2002
+        # add 0.5 event, not 0.5 event rate as in waldcc
+        # std = np.sqrt((rate + 0.5 / n) / n)
+        # statistic = (rate + 0.5 / n - value) / std
+        std = np.sqrt(dispersion * (rate + 0.5 / n) / n)
+        statistic = (rate - value) / std
+
+    elif method == "score":
+        std = np.sqrt(dispersion * value / n)
+        statistic = (rate - value) / std
+        pvalue = stats.norm.sf(statistic)
+
+    elif method.startswith("exact-c") or method.startswith("midp-c"):
+        pv1 = stats.poisson.cdf(count, n * value)
+        pv2 = stats.poisson.sf(count - 1, n * value)
+        if method.startswith("midp-c"):
+            pv1 = pv1 - 0.5 * stats.poisson.pmf(count, n * value)
+            pv2 = pv2 - 0.5 * stats.poisson.pmf(count, n * value)
+        if alternative == "two-sided":
+            pvalue = 2 * np.minimum(pv1, pv2)
+        elif alternative == "larger":
+            pvalue = pv2
+        elif alternative == "smaller":
+            pvalue = pv1
+        else:
+            msg = 'alternative should be "two-sided", "larger" or "smaller"'
+            raise ValueError(msg)
 
-    against one of the following alternatives
+        statistic = np.nan
+        dist = "Poisson"
 
-    - H1_2-sided: g1 / g2 != ratio_null
-    - H1_larger: g1 / g2 > ratio_null
-    - H1_smaller: g1 / g2 < ratio_null
+    elif method == "sqrt":
+        std = 0.5
+        statistic = (np.sqrt(count) - np.sqrt(n * value)) / std
+
+    elif method == "sqrt-a":
+        # anscombe, based on Swift 2009 (with transformation to rate)
+        std = 0.5
+        statistic = (np.sqrt(count + 3 / 8) - np.sqrt(n * value + 3 / 8)) / std
+
+    elif method == "sqrt-v":
+        # vandenbroucke, based on Swift 2009 (with transformation to rate)
+        std = 0.5
+        crit = stats.norm.isf(0.025)
+        statistic = (np.sqrt(count + (crit**2 + 2) / 12) -
+                     # np.sqrt(n * value + (crit**2 + 2) / 12)) / std
+                     np.sqrt(n * value)) / std
+
+    else:
+        raise ValueError("unknown method %s" % method)
+
+    if dist == 'normal':
+        statistic, pvalue = _zstat_generic2(statistic, 1, alternative)
+
+    res = HolderTuple(
+        statistic=statistic,
+        pvalue=np.clip(pvalue, 0, 1),
+        distribution=dist,
+        method=method,
+        alternative=alternative,
+        rate=rate,
+        nobs=n
+        )
+    return res
+
+
+def confint_poisson(count, exposure, method=None, alpha=0.05):
+    """Confidence interval for a Poisson mean or rate
+
+    The function is vectorized for all methods except "midp-c", which uses
+    an iterative method to invert the hypothesis test function.
+
+    All current methods are central, that is the probability of each tail is
+    smaller or equal to alpha / 2. The one-sided interval limits can be
+    obtained by doubling alpha.
+
+    Parameters
+    ----------
+    count : array_like
+        Observed count, number of events.
+    exposure : arrat_like
+        Currently this is total exposure time of the count variable.
+        This will likely change.
+    method : str
+        Method to use for confidence interval
+        This is required, there is currently no default method
+    alpha : float in (0, 1)
+        Significance level, nominal coverage of the confidence interval is
+        1 - alpha.
+
+    Returns
+    -------
+    tuple (low, upp) : confidence limits.
+
+    Notes
+    -----
+    Methods are mainly based on Barker (2002) [1]_ and Swift (2009) [3]_.
+
+    Available methods are:
+
+    - "exact-c" central confidence interval based on gamma distribution
+    - "score" : based on score test, uses variance under null value
+    - "wald" : based on wald test, uses variance base on estimated rate.
+    - "waldccv" : based on wald test with 0.5 count added to variance
+      computation. This does not use continuity correction for the center of
+      the confidence interval.
+    - "midp-c" : based on midp correction of central exact confidence interval.
+      this uses numerical inversion of the test function. not vectorized.
+    - "jeffreys" : based on Jeffreys' prior. computed using gamma distribution
+    - "sqrt" : based on square root transformed counts
+    - "sqrt-a" based on Anscombe square root transformation of counts + 3/8.
+    - "sqrt-centcc" will likely be dropped. anscombe with continuity corrected
+      center.
+      (Similar to R survival cipoisson, but without the 3/8 right shift of
+      the confidence interval).
+
+    sqrt-cent is the same as sqrt-a, using a different computation, will be
+    deleted.
+
+    sqrt-v is a corrected square root method attributed to vandenbrouke, which
+    might also be deleted.
+
+    Todo:
+
+    - missing dispersion,
+    - maybe split nobs and exposure (? needed in NB). Exposure could be used
+      to standardize rate.
+    - modified wald, switch method if count=0.
+
+    See Also
+    --------
+    test_poisson
+
+    References
+    ----------
+    .. [1] Barker, Lawrence. 2002. “A Comparison of Nine Confidence Intervals
+       for a Poisson Parameter When the Expected Number of Events Is ≤ 5.”
+       The American Statistician 56 (2): 85–89.
+       https://doi.org/10.1198/000313002317572736.
+    .. [2] Patil, VV, and HV Kulkarni. 2012. “Comparison of Confidence
+       Intervals for the Poisson Mean: Some New Aspects.”
+       REVSTAT–Statistical Journal 10(2): 211–27.
+    .. [3] Swift, Michael Bruce. 2009. “Comparison of Confidence Intervals for
+       a Poisson Mean – Further Considerations.” Communications in Statistics -
+       Theory and Methods 38 (5): 748–59.
+       https://doi.org/10.1080/03610920802255856.
+
+    """
+    n = exposure  # short hand
+    rate = count / exposure
+    alpha = alpha / 2  # two-sided
+
+    if method is None:
+        msg = "method needs to be specified, currently no default method"
+        raise ValueError(msg)
+
+    if method == "wald":
+        whalf = stats.norm.isf(alpha) * np.sqrt(rate / n)
+        ci = (rate - whalf, rate + whalf)
+
+    elif method == "waldccv":
+        # based on WCC in Barker 2002
+        # add 0.5 event, not 0.5 event rate as in BARKER waldcc
+        whalf = stats.norm.isf(alpha) * np.sqrt((rate + 0.5 / n) / n)
+        ci = (rate - whalf, rate + whalf)
+
+    elif method == "score":
+        crit = stats.norm.isf(alpha)
+        center = count + crit**2 / 2
+        whalf = crit * np.sqrt((count + crit**2 / 4))
+        ci = ((center - whalf) / n, (center + whalf) / n)
+
+    elif method == "midp-c":
+        # note local alpha above is for one tail
+        ci = _invert_test_confint(count, n, alpha=2 * alpha, method="midp-c",
+                                  method_start="exact-c")
+
+    elif method == "sqrt":
+        # drop, wrong n
+        crit = stats.norm.isf(alpha)
+        center = rate + crit**2 / (4 * n)
+        whalf = crit * np.sqrt(rate / n)
+        ci = (center - whalf, center + whalf)
+
+    elif method == "sqrt-cent":
+        crit = stats.norm.isf(alpha)
+        center = count + crit**2 / 4
+        whalf = crit * np.sqrt((count + 3 / 8))
+        ci = ((center - whalf) / n, (center + whalf) / n)
+
+    elif method == "sqrt-centcc":
+        # drop with cc, does not match cipoisson in R survival
+        crit = stats.norm.isf(alpha)
+        # avoid sqrt of negative value if count=0
+        center_low = np.sqrt(np.maximum(count + 3 / 8 - 0.5, 0))
+        center_upp = np.sqrt(count + 3 / 8 + 0.5)
+        whalf = crit / 2
+        # above is for ci of count
+        ci = (((np.maximum(center_low - whalf, 0))**2 - 3 / 8) / n,
+              ((center_upp + whalf)**2 - 3 / 8) / n)
+
+        # crit = stats.norm.isf(alpha)
+        # center = count
+        # whalf = crit * np.sqrt((count + 3 / 8 + 0.5))
+        # ci = ((center - whalf - 0.5) / n, (center + whalf + 0.5) / n)
+
+    elif method == "sqrt-a":
+        # anscombe, based on Swift 2009 (with transformation to rate)
+        crit = stats.norm.isf(alpha)
+        center = np.sqrt(count + 3 / 8)
+        whalf = crit / 2
+        # above is for ci of count
+        ci = (((np.maximum(center - whalf, 0))**2 - 3 / 8) / n,
+              ((center + whalf)**2 - 3 / 8) / n)
+
+    elif method == "sqrt-v":
+        # vandenbroucke, based on Swift 2009 (with transformation to rate)
+        crit = stats.norm.isf(alpha)
+        center = np.sqrt(count + (crit**2 + 2) / 12)
+        whalf = crit / 2
+        # above is for ci of count
+        ci = (np.maximum(center - whalf, 0))**2 / n, (center + whalf)**2 / n
+
+    elif method in ["gamma", "exact-c"]:
+        # garwood exact, gamma
+        low = stats.gamma.ppf(alpha, count) / exposure
+        upp = stats.gamma.isf(alpha, count+1) / exposure
+        if np.isnan(low).any():
+            # case with count = 0
+            if np.size(low) == 1:
+                low = 0.0
+            else:
+                low[np.isnan(low)] = 0.0
+
+        ci = (low, upp)
+
+    elif method.startswith("jeff"):
+        # jeffreys, gamma
+        countc = count + 0.5
+        ci = (stats.gamma.ppf(alpha, countc) / exposure,
+              stats.gamma.isf(alpha, countc) / exposure)
+
+    else:
+        raise ValueError("unknown method %s" % method)
+
+    ci = (np.maximum(ci[0], 0), ci[1])
+    return ci
+
+
+def tolerance_int_poisson(count, exposure, prob=0.95, exposure_new=1.,
+                          method=None, alpha=0.05,
+                          alternative="two-sided"):
+    """tolerance interval for a poisson observation
+
+    Parameters
+    ----------
+    count : array_like
+        Observed count, number of events.
+    exposure : arrat_like
+        Currently this is total exposure time of the count variable.
+    prob : float in (0, 1)
+        Probability of poisson interval, often called "content".
+        With known parameters, each tail would have at most probability
+        ``1 - prob / 2`` in the two-sided interval.
+    exposure_new : float
+        Exposure of the new or predicted observation.
+    method : str
+        Method to used for confidence interval of the estimate of the
+        poisson rate, used in `confint_poisson`.
+        This is required, there is currently no default method.
+    alpha : float in (0, 1)
+        Significance level for the confidence interval of the estimate of the
+        Poisson rate. Nominal coverage of the confidence interval is
+        1 - alpha.
+    alternative : {"two-sider", "larger", "smaller")
+        The tolerance interval can be two-sided or one-sided.
+        Alternative "larger" provides the upper bound of the confidence
+        interval, larger counts are outside the interval.
+
+    Returns
+    -------
+    tuple (low, upp) of limits of tolerance interval.
+    The tolerance interval is a closed interval, that is both ``low`` and
+    ``upp`` are in the interval.
+
+    Notes
+    -----
+    verified against R package tolerance `poistol.int`
+
+    See Also
+    --------
+    confint_poisson
+    confint_quantile_poisson
+
+    References
+    ----------
+    Hahn, Gerald J, and William Q Meeker. 2010. Statistical Intervals: A Guide
+    for Practitioners.
+
+    Hahn, Gerald J., and Ramesh Chandra. 1981. “Tolerance Intervals for Poisson
+    and Binomial Variables.” Journal of Quality Technology 13 (2): 100–110.
+    https://doi.org/10.1080/00224065.1981.11980998.
+
+    """
+    prob_tail = 1 - prob
+    alpha_ = alpha
+    if alternative != "two-sided":
+        # confint_poisson does not have one-sided alternatives
+        alpha_ = alpha * 2
+    low, upp = confint_poisson(count, exposure, method=method, alpha=alpha_)
+
+    if exposure_new != 1:
+        low *= exposure_new
+        upp *= exposure_new
+
+    if alternative == "two-sided":
+        low_pred = stats.poisson.ppf(prob_tail / 2, low)
+        upp_pred = stats.poisson.ppf(1 - prob_tail / 2, upp)
+    elif alternative == "larger":
+        low_pred = 0
+        upp_pred = stats.poisson.ppf(1 - prob_tail, upp)
+    elif alternative == "smaller":
+        low_pred = stats.poisson.ppf(prob_tail, low)
+        upp_pred = np.inf
+
+    # clip -1 of ppf(0)
+    low_pred = np.maximum(low_pred, 0)
+    return low_pred, upp_pred
+
+
+def confint_quantile_poisson(count, exposure, prob, exposure_new=1.,
+                             method=None, alpha=0.05,
+                             alternative="two-sided"):
+    """confidence interval for quantile of poisson random variable
+
+    Parameters
+    ----------
+    count : array_like
+        Observed count, number of events.
+    exposure : arrat_like
+        Currently this is total exposure time of the count variable.
+    prob : float in (0, 1)
+        Probability for the quantile, e.g. 0.95 to get the upper 95% quantile.
+        With known mean mu, the quantile would be poisson.ppf(prob, mu).
+    exposure_new : float
+        Exposure of the new or predicted observation.
+    method : str
+        Method to used for confidence interval of the estimate of the
+        poisson rate, used in `confint_poisson`.
+        This is required, there is currently no default method.
+    alpha : float in (0, 1)
+        Significance level for the confidence interval of the estimate of the
+        Poisson rate. Nominal coverage of the confidence interval is
+        1 - alpha.
+    alternative : {"two-sider", "larger", "smaller")
+        The tolerance interval can be two-sided or one-sided.
+        Alternative "larger" provides the upper bound of the confidence
+        interval, larger counts are outside the interval.
+
+    Returns
+    -------
+    tuple (low, upp) of limits of tolerance interval.
+    The confidence interval is a closed interval, that is both ``low`` and
+    ``upp`` are in the interval.
+
+    See Also
+    --------
+    confint_poisson
+    tolerance_int_poisson
+
+    References
+    ----------
+    Hahn, Gerald J, and William Q Meeker. 2010. Statistical Intervals: A Guide
+    for Practitioners.
+    """
+    alpha_ = alpha
+    if alternative != "two-sided":
+        # confint_poisson does not have one-sided alternatives
+        alpha_ = alpha * 2
+    low, upp = confint_poisson(count, exposure, method=method, alpha=alpha_)
+    if exposure_new != 1:
+        low *= exposure_new
+        upp *= exposure_new
+
+    if alternative == "two-sided":
+        low_pred = stats.poisson.ppf(prob, low)
+        upp_pred = stats.poisson.ppf(prob, upp)
+    elif alternative == "larger":
+        low_pred = 0
+        upp_pred = stats.poisson.ppf(prob, upp)
+    elif alternative == "smaller":
+        low_pred = stats.poisson.ppf(prob, low)
+        upp_pred = np.inf
+
+    # clip -1 of ppf(0)
+    low_pred = np.maximum(low_pred, 0)
+    return low_pred, upp_pred
+
+
+def _invert_test_confint(count, nobs, alpha=0.05, method="midp-c",
+                         method_start="exact-c"):
+    """invert hypothesis test to get confidence interval
+    """
+
+    def func(r):
+        v = (test_poisson(count, nobs, value=r, method=method)[1] -
+             alpha)**2
+        return v
+
+    ci = confint_poisson(count, nobs, method=method_start)
+    low = optimize.fmin(func, ci[0], xtol=1e-8, disp=False)
+    upp = optimize.fmin(func, ci[1], xtol=1e-8, disp=False)
+    assert np.size(low) == 1
+    return low[0], upp[0]
+
+
+def _invert_test_confint_2indep(
+        count1, exposure1, count2, exposure2,
+        alpha=0.05,
+        method="score",
+        compare="diff",
+        method_start="wald"
+        ):
+    """invert hypothesis test to get confidence interval for 2indep
+    """
+
+    def func(r):
+        v = (test_poisson_2indep(
+             count1, exposure1, count2, exposure2,
+             value=r, method=method, compare=compare
+             )[1] - alpha)**2
+        return v
+
+    ci = confint_poisson_2indep(count1, exposure1, count2, exposure2,
+                                method=method_start, compare=compare)
+    low = optimize.fmin(func, ci[0], xtol=1e-8, disp=False)
+    upp = optimize.fmin(func, ci[1], xtol=1e-8, disp=False)
+    assert np.size(low) == 1
+    return low[0], upp[0]
+
+
+method_names_poisson_2indep = {
+    "test": {
+        "ratio": [
+            "wald",
+            "score",
+            "score-log",
+            "wald-log",
+            "exact-cond",
+            "cond-midp",
+            "sqrt",
+            "etest-score",
+            "etest-wald"
+            ],
+        "diff": [
+            "wald",
+            "score",
+            "waldccv",
+            "etest-score",
+            "etest-wald"
+            ]
+        },
+    "confint": {
+        "ratio": [
+            "waldcc",
+            "score",
+            "score-log",
+            "wald-log",
+            "sqrtcc",
+            "mover",
+            ],
+        "diff": [
+            "wald",
+            "score",
+            "waldccv",
+            "mover"
+            ]
+        }
+    }
+
+
+def test_poisson_2indep(count1, exposure1, count2, exposure2, value=None,
+                        ratio_null=None,
+                        method=None, compare='ratio',
+                        alternative='two-sided', etest_kwds=None):
+    '''Test for comparing two sample Poisson intensity rates.
+
+    Rates are defined as expected count divided by exposure.
+
+    The Null and alternative hypothesis for the rates, rate1 and rate2, of two
+    independent Poisson samples are
+
+    for compare = 'diff'
+
+    - H0: rate1 - rate2 - value = 0
+    - H1: rate1 - rate2 - value != 0  if alternative = 'two-sided'
+    - H1: rate1 - rate2 - value > 0   if alternative = 'larger'
+    - H1: rate1 - rate2 - value < 0   if alternative = 'smaller'
+
+    for compare = 'ratio'
+
+    - H0: rate1 / rate2 - value = 0
+    - H1: rate1 / rate2 - value != 0  if alternative = 'two-sided'
+    - H1: rate1 / rate2 - value > 0   if alternative = 'larger'
+    - H1: rate1 / rate2 - value < 0   if alternative = 'smaller'
 
     Parameters
     ----------
     count1 : int
-        Number of events in first sample.
+        Number of events in first sample, treatment group.
     exposure1 : float
         Total exposure (time * subjects) in first sample.
     count2 : int
-        Number of events in second sample.
+        Number of events in second sample, control group.
     exposure2 : float
         Total exposure (time * subjects) in second sample.
-    ratio: float
-        ratio of the two Poisson rates under the Null hypothesis. Default is 1.
+    ratio_null: float
+        Ratio of the two Poisson rates under the Null hypothesis. Default is 1.
+        Deprecated, use ``value`` instead.
+    value : float
+        Value of the ratio or difference of 2 independent rates under the null
+        hypothesis. Default is equal rates, i.e. 1 for ratio and 0 for diff.
     method : string
         Method for the test statistic and the p-value. Defaults to `'score'`.
-        Current Methods are based on Gu et. al 2008.
-        Implemented are 'wald', 'score' and 'sqrt' based asymptotic normal
-        distribution, and the exact conditional test 'exact-cond', and its
-        mid-point version 'cond-midp'. method='etest' and method='etest-wald'
-        provide pvalues from `etest_poisson_2indep` using score or wald
-        statistic respectively.
         see Notes.
+
+        ratio:
+
+        - 'wald': method W1A, wald test, variance based on observed rates
+        - 'score': method W2A, score test, variance based on estimate under
+          the Null hypothesis
+        - 'wald-log': W3A, uses log-ratio, variance based on observed rates
+        - 'score-log' W4A, uses log-ratio, variance based on estimate under
+          the Null hypothesis
+        - 'sqrt': W5A, based on variance stabilizing square root transformation
+        - 'exact-cond': exact conditional test based on binomial distribution
+           This uses ``binom_test`` which is minlike in the two-sided case.
+        - 'cond-midp': midpoint-pvalue of exact conditional test
+        - 'etest' or 'etest-score: etest with score test statistic
+        - 'etest-wald': etest with wald test statistic
+
+        diff:
+
+        - 'wald',
+        - 'waldccv'
+        - 'score'
+        - 'etest-score' or 'etest: etest with score test statistic
+        - 'etest-wald': etest with wald test statistic
+
+    compare : {'diff', 'ratio'}
+        Default is "ratio".
+        If compare is `diff`, then the hypothesis test is for
+        diff = rate1 - rate2.
+        If compare is `ratio`, then the hypothesis test is for the
+        rate ratio defined by ratio = rate1 / rate2.
     alternative : string
         The alternative hypothesis, H1, has to be one of the following
 
@@ -69,20 +692,42 @@ def test_poisson_2indep(count1, exposure1, count2, exposure2, ratio_null=1,
 
     Notes
     -----
+    The hypothesis tests for compare="ratio" are based on Gu et al 2018.
+    The e-tests are also based on ...
+
     - 'wald': method W1A, wald test, variance based on separate estimates
     - 'score': method W2A, score test, variance based on estimate under Null
-    - 'wald-log': W3A
-    - 'score-log' W4A
+    - 'wald-log': W3A, wald test for log transformed ratio
+    - 'score-log' W4A, score test for log transformed ratio
     - 'sqrt': W5A, based on variance stabilizing square root transformation
     - 'exact-cond': exact conditional test based on binomial distribution
     - 'cond-midp': midpoint-pvalue of exact conditional test
     - 'etest': etest with score test statistic
     - 'etest-wald': etest with wald test statistic
 
+    The hypothesis test for compare="diff" are mainly based on Ng et al 2007
+    and ...
+
+    - wald
+    - score
+    - etest-score
+    - etest-wald
+
+    Note the etests use the constraint maximum likelihood estimate (cmle) as
+    parameters for the underlying Poisson probabilities. The constraint cmle
+    parameters are the same as in the score test.
+    The E-test in Krishnamoorty and Thomson uses a moment estimator instead of
+    the score estimator.
+
     References
     ----------
-    Gu, Ng, Tang, Schucany 2008: Testing the Ratio of Two Poisson Rates,
-    Biometrical Journal 50 (2008) 2, 2008
+    .. [1] Gu, Ng, Tang, Schucany 2008: Testing the Ratio of Two Poisson Rates,
+       Biometrical Journal 50 (2008) 2, 2008
+
+    .. [2] Ng, H. K. T., K. Gu, and M. L. Tang. 2007. “A Comparative Study of
+       Tests for the Difference of Two Poisson Means.”
+       Computational Statistics & Data Analysis 51 (6): 3085–99.
+       https://doi.org/10.1016/j.csda.2006.02.004.
 
     See Also
     --------
@@ -91,80 +736,187 @@ def test_poisson_2indep(count1, exposure1, count2, exposure2, ratio_null=1,
     '''
 
     # shortcut names
-    y1, n1, y2, n2 = count1, exposure1, count2, exposure2
+    y1, n1, y2, n2 = map(np.asarray, [count1, exposure1, count2, exposure2])
     d = n2 / n1
-    r = ratio_null
-    r_d = r / d
-
-    if method in ['score']:
-        stat = (y1 - y2 * r_d) / np.sqrt((y1 + y2) * r_d)
-        dist = 'normal'
-    elif method in ['wald']:
-        stat = (y1 - y2 * r_d) / np.sqrt(y1 + y2 * r_d**2)
-        dist = 'normal'
-    elif method in ['sqrt']:
-        stat = 2 * (np.sqrt(y1 + 3 / 8.) - np.sqrt((y2 + 3 / 8.) * r_d))
-        stat /= np.sqrt(1 + r_d)
-        dist = 'normal'
-    elif method in ['exact-cond', 'cond-midp']:
-        from statsmodels.stats import proportion
-        bp = r_d / (1 + r_d)
-        y_total = y1 + y2
-        stat = None
-        # TODO: why y2 in here and not y1, check definition of H1 "larger"
-        pvalue = proportion.binom_test(y1, y_total, prop=bp,
-                                       alternative=alternative)
-        if method in ['cond-midp']:
-            # not inplace in case we still want binom pvalue
-            pvalue = pvalue - 0.5 * stats.binom.pmf(y1, y_total, bp)
-
-        dist = 'binomial'
-    elif method.startswith('etest'):
-        if method.endswith('wald'):
-            method_etest = 'wald'
+    rate1, rate2 = y1 / n1, y2 / n2
+    rates_cmle = None
+
+    if compare == 'ratio':
+        if method is None:
+            # default method
+            method = 'score'
+
+        if ratio_null is None and value is None:
+            # default value
+            value = ratio_null = 1
+        elif ratio_null is not None:
+            warnings.warn("'ratio_null' is deprecated, use 'value' keyword",
+                          DeprecationWarning)
+            value = ratio_null
+        else:
+            # for results holder instance, it still contains ratio_null
+            ratio_null = value
+
+        r = value
+        r_d = r / d   # r1 * n1 / (r2 * n2)
+
+        if method in ['score']:
+            stat = (y1 - y2 * r_d) / np.sqrt((y1 + y2) * r_d)
+            dist = 'normal'
+        elif method in ['wald']:
+            stat = (y1 - y2 * r_d) / np.sqrt(y1 + y2 * r_d**2)
+            dist = 'normal'
+        elif method in ['score-log']:
+            stat = (np.log(y1 / y2) - np.log(r_d))
+            stat /= np.sqrt((2 + 1 / r_d + r_d) / (y1 + y2))
+            dist = 'normal'
+        elif method in ['wald-log']:
+            stat = (np.log(y1 / y2) - np.log(r_d)) / np.sqrt(1 / y1 + 1 / y2)
+            dist = 'normal'
+        elif method in ['sqrt']:
+            stat = 2 * (np.sqrt(y1 + 3 / 8.) - np.sqrt((y2 + 3 / 8.) * r_d))
+            stat /= np.sqrt(1 + r_d)
+            dist = 'normal'
+        elif method in ['exact-cond', 'cond-midp']:
+            from statsmodels.stats import proportion
+            bp = r_d / (1 + r_d)
+            y_total = y1 + y2
+            stat = np.nan
+            # TODO: why y2 in here and not y1, check definition of H1 "larger"
+            pvalue = proportion.binom_test(y1, y_total, prop=bp,
+                                           alternative=alternative)
+            if method in ['cond-midp']:
+                # not inplace in case we still want binom pvalue
+                pvalue = pvalue - 0.5 * stats.binom.pmf(y1, y_total, bp)
+
+            dist = 'binomial'
+        elif method.startswith('etest'):
+            if method.endswith('wald'):
+                method_etest = 'wald'
+            else:
+                method_etest = 'score'
+            if etest_kwds is None:
+                etest_kwds = {}
+
+            stat, pvalue = etest_poisson_2indep(
+                count1, exposure1, count2, exposure2, value=value,
+                method=method_etest, alternative=alternative, **etest_kwds)
+
+            dist = 'poisson'
         else:
-            method_etest = 'score'
-        if etest_kwds is None:
-            etest_kwds = {}
+            raise ValueError(f'method "{method}" not recognized')
+
+    elif compare == "diff":
+        if value is None:
+            value = 0
+        if method in ['wald']:
+            stat = (rate1 - rate2 - value) / np.sqrt(rate1 / n1 + rate2 / n2)
+            dist = 'normal'
+            "waldccv"
+        elif method in ['waldccv']:
+            stat = (rate1 - rate2 - value)
+            stat /= np.sqrt((count1 + 0.5) / n1**2 + (count2 + 0.5) / n2**2)
+            dist = 'normal'
+        elif method in ['score']:
+            # estimate rates with constraint MLE
+            count_pooled = y1 + y2
+            rate_pooled = count_pooled / (n1 + n2)
+            dt = rate_pooled - value
+            r2_cmle = 0.5 * (dt + np.sqrt(dt**2 + 4 * value * y2 / (n1 + n2)))
+            r1_cmle = r2_cmle + value
+
+            stat = ((rate1 - rate2 - value) /
+                    np.sqrt(r1_cmle / n1 + r2_cmle / n2))
+            rates_cmle = (r1_cmle, r2_cmle)
+            dist = 'normal'
+        elif method.startswith('etest'):
+            if method.endswith('wald'):
+                method_etest = 'wald'
+            else:
+                method_etest = 'score'
+                if method == "etest":
+                    method = method + "-score"
+
+            if etest_kwds is None:
+                etest_kwds = {}
 
-        stat, pvalue = etest_poisson_2indep(
-            count1, exposure1, count2, exposure2, ratio_null=ratio_null,
-            method=method_etest, alternative=alternative, **etest_kwds)
+            stat, pvalue = etest_poisson_2indep(
+                count1, exposure1, count2, exposure2, value=value,
+                method=method_etest, compare="diff",
+                alternative=alternative, **etest_kwds)
 
-        dist = 'poisson'
+            dist = 'poisson'
+        else:
+            raise ValueError(f'method "{method}" not recognized')
     else:
-        raise ValueError('method not recognized')
+        raise NotImplementedError('"compare" needs to be ratio or diff')
 
     if dist == 'normal':
         stat, pvalue = _zstat_generic2(stat, 1, alternative)
 
-    rates = (y1 / n1, y2 / n2)
-    ratio = rates[0] / rates[1]
+    rates = (rate1, rate2)
+    ratio = rate1 / rate2
+    diff = rate1 - rate2
     res = HolderTuple(statistic=stat,
                       pvalue=pvalue,
                       distribution=dist,
+                      compare=compare,
                       method=method,
                       alternative=alternative,
                       rates=rates,
                       ratio=ratio,
-                      ratio_null=ratio_null)
+                      diff=diff,
+                      value=value,
+                      rates_cmle=rates_cmle,
+                      ratio_null=ratio_null,
+                      )
     return res
 
 
-def etest_poisson_2indep(count1, exposure1, count2, exposure2, ratio_null=1,
-                         method='score', alternative='2-sided', ygrid=None,
+def _score_diff(y1, n1, y2, n2, value=0, return_cmle=False):
+    """score test and cmle for difference of 2 independent poisson rates
+
+    """
+    count_pooled = y1 + y2
+    rate1, rate2 = y1 / n1, y2 / n2
+    rate_pooled = count_pooled / (n1 + n2)
+    dt = rate_pooled - value
+    r2_cmle = 0.5 * (dt + np.sqrt(dt**2 + 4 * value * y2 / (n1 + n2)))
+    r1_cmle = r2_cmle + value
+    eps = 1e-20  # avoid zero division in stat_func
+    v = r1_cmle / n1 + r2_cmle / n2
+    stat = (rate1 - rate2 - value) / np.sqrt(v + eps)
+
+    if return_cmle:
+        return stat, r1_cmle, r2_cmle
+    else:
+        return stat
+
+
+def etest_poisson_2indep(count1, exposure1, count2, exposure2, ratio_null=None,
+                         value=None, method='score', compare="ratio",
+                         alternative='two-sided', ygrid=None,
                          y_grid=None):
-    """E-test for ratio of two sample Poisson rates
+    """E-test for ratio of two sample Poisson rates.
+
+        Rates are defined as expected count divided by exposure.
 
-    If the two Poisson rates are g1 and g2, then the Null hypothesis is
+    The Null and alternative hypothesis for the rates, rate1 and rate2, of two
+    independent Poisson samples are
 
-    - H0: g1 / g2 = ratio_null
+    for compare = 'diff'
 
-    against one of the following alternatives
+    - H0: rate1 - rate2 - value = 0
+    - H1: rate1 - rate2 - value != 0  if alternative = 'two-sided'
+    - H1: rate1 - rate2 - value > 0   if alternative = 'larger'
+    - H1: rate1 - rate2 - value < 0   if alternative = 'smaller'
 
-    - H1_2-sided: g1 / g2 != ratio_null
-    - H1_larger: g1 / g2 > ratio_null
-    - H1_smaller: g1 / g2 < ratio_null
+    for compare = 'ratio'
+
+    - H0: rate1 / rate2 - value = 0
+    - H1: rate1 / rate2 - value != 0  if alternative = 'two-sided'
+    - H1: rate1 / rate2 - value > 0   if alternative = 'larger'
+    - H1: rate1 / rate2 - value < 0   if alternative = 'smaller'
 
     Parameters
     ----------
@@ -176,8 +928,12 @@ def etest_poisson_2indep(count1, exposure1, count2, exposure2, ratio_null=1,
         Number of events in first sample
     exposure2 : float
         Total exposure (time * subjects) in first sample
-    ratio : float
-        ratio of the two Poisson rates under the Null hypothesis. Default is 1.
+    ratio_null: float
+        Ratio of the two Poisson rates under the Null hypothesis. Default is 1.
+        Deprecated, use ``value`` instead.
+    value : float
+        Value of the ratio or diff of 2 independent rates under the null
+        hypothesis. Default is equal rates, i.e. 1 for ratio and 0 for diff.
     method : {"score", "wald"}
         Method for the test statistic that defines the rejection region.
     alternative : string
@@ -206,37 +962,72 @@ def etest_poisson_2indep(count1, exposure1, count2, exposure2, ratio_null=1,
     ----------
     Gu, Ng, Tang, Schucany 2008: Testing the Ratio of Two Poisson Rates,
     Biometrical Journal 50 (2008) 2, 2008
+    Ng, H. K. T., K. Gu, and M. L. Tang. 2007. “A Comparative Study of Tests
+    for the Difference of Two Poisson Means.” Computational Statistics & Data
+    Analysis 51 (6): 3085–99. https://doi.org/10.1016/j.csda.2006.02.004.
 
     """
-    y1, n1, y2, n2 = count1, exposure1, count2, exposure2
+    y1, n1, y2, n2 = map(np.asarray, [count1, exposure1, count2, exposure2])
     d = n2 / n1
-    r = ratio_null
-    r_d = r / d
 
     eps = 1e-20  # avoid zero division in stat_func
 
-    if method in ['score']:
-        def stat_func(x1, x2):
-            return (x1 - x2 * r_d) / np.sqrt((x1 + x2) * r_d + eps)
-        # TODO: do I need these? return_results ?
-        # rate2_cmle = (y1 + y2) / n2 / (1 + r_d)
-        # rate1_cmle = rate2_cmle * r
-        # rate1 = rate1_cmle
-        # rate2 = rate2_cmle
-    elif method in ['wald']:
-        def stat_func(x1, x2):
-            return (x1 - x2 * r_d) / np.sqrt(x1 + x2 * r_d**2 + eps)
-        # rate2_mle = y2 / n2
-        # rate1_mle = y1 / n1
-        # rate1 = rate1_mle
-        # rate2 = rate2_mle
-    else:
-        raise ValueError('method not recognized')
+    if compare == "ratio":
+        if ratio_null is None and value is None:
+            # default value
+            value = 1
+        elif ratio_null is not None:
+            warnings.warn("'ratio_null' is deprecated, use 'value' keyword",
+                          DeprecationWarning)
+            value = ratio_null
+
+        r = value  # rate1 / rate2
+        r_d = r / d
+        rate2_cmle = (y1 + y2) / n2 / (1 + r_d)
+        rate1_cmle = rate2_cmle * r
+
+        if method in ['score']:
+            def stat_func(x1, x2):
+                return (x1 - x2 * r_d) / np.sqrt((x1 + x2) * r_d + eps)
+            # TODO: do I need these? return_results ?
+            # rate2_cmle = (y1 + y2) / n2 / (1 + r_d)
+            # rate1_cmle = rate2_cmle * r
+            # rate1 = rate1_cmle
+            # rate2 = rate2_cmle
+        elif method in ['wald']:
+            def stat_func(x1, x2):
+                return (x1 - x2 * r_d) / np.sqrt(x1 + x2 * r_d**2 + eps)
+            # rate2_mle = y2 / n2
+            # rate1_mle = y1 / n1
+            # rate1 = rate1_mle
+            # rate2 = rate2_mle
+        else:
+            raise ValueError('method not recognized')
+
+    elif compare == "diff":
+        if value is None:
+            value = 0
+        tmp = _score_diff(y1, n1, y2, n2, value=value, return_cmle=True)
+        _, rate1_cmle, rate2_cmle = tmp
+
+        if method in ['score']:
+
+            def stat_func(x1, x2):
+                return _score_diff(x1, n1, x2, n2, value=value)
+
+        elif method in ['wald']:
+
+            def stat_func(x1, x2):
+                rate1, rate2 = x1 / n1, x2 / n2
+                stat = (rate1 - rate2 - value)
+                stat /= np.sqrt(rate1 / n1 + rate2 / n2 + eps)
+                return stat
+
+        else:
+            raise ValueError('method not recognized')
 
     # The sampling distribution needs to be based on the null hypotheis
     # use constrained MLE from 'score' calculation
-    rate2_cmle = (y1 + y2) / n2 / (1 + r_d)
-    rate1_cmle = rate2_cmle * r
     rate1 = rate1_cmle
     rate2 = rate2_cmle
     mean1 = n1 * rate1
@@ -268,11 +1059,11 @@ def stat_func(x1, x2):
     eps = 1e-15   # correction for strict inequality check
 
     if alternative in ['two-sided', '2-sided', '2s']:
-        mask = np.abs(stat_space) >= np.abs(stat_sample) - eps
+        mask = np.abs(stat_space) >= (np.abs(stat_sample) - eps)
     elif alternative in ['larger', 'l']:
-        mask = stat_space >= stat_sample - eps
+        mask = stat_space >= (stat_sample - eps)
     elif alternative in ['smaller', 's']:
-        mask = stat_space <= stat_sample + eps
+        mask = stat_space <= (stat_sample + eps)
     else:
         raise ValueError('invalid alternative')
 
@@ -281,10 +1072,10 @@ def stat_func(x1, x2):
 
 
 def tost_poisson_2indep(count1, exposure1, count2, exposure2, low, upp,
-                        method='score'):
+                        method='score', compare='ratio'):
     '''Equivalence test based on two one-sided `test_proportions_2indep`
 
-    This assumes that we have two independent binomial samples.
+    This assumes that we have two independent poisson samples.
 
     The Null and alternative hypothesis for equivalence testing are
 
@@ -306,11 +1097,7 @@ def tost_poisson_2indep(count1, exposure1, count2, exposure2, low, upp,
     low, upp :
         equivalence margin for the ratio of Poisson rates
     method: string
-        Method for the test statistic and the p-value. Defaults to `'score'`.
-        Current Methods are based on Gu et. al 2008
-        Implemented are 'wald', 'score' and 'sqrt' based asymptotic normal
-        distribution, and the exact conditional test 'exact-cond', and its
-        mid-point version 'cond-midp', see Notes
+        TOST uses ``test_poisson_2indep`` and has the same methods.
 
     Returns
     -------
@@ -341,19 +1128,967 @@ def tost_poisson_2indep(count1, exposure1, count2, exposure2, low, upp,
     '''
 
     tt1 = test_poisson_2indep(count1, exposure1, count2, exposure2,
-                              ratio_null=low, method=method,
+                              value=low, method=method,
+                              compare=compare,
                               alternative='larger')
     tt2 = test_poisson_2indep(count1, exposure1, count2, exposure2,
-                              ratio_null=upp, method=method,
+                              value=upp, method=method,
+                              compare=compare,
                               alternative='smaller')
 
-    idx_max = 0 if tt1.pvalue < tt2.pvalue else 1
+    idx_max = 0 if tt1.pvalue > tt2.pvalue else 1
     res = HolderTuple(statistic=[tt1.statistic, tt2.statistic][idx_max],
                       pvalue=[tt1.pvalue, tt2.pvalue][idx_max],
                       method=method,
+                      compare=compare,
+                      equiv_limits=(low, upp),
                       results_larger=tt1,
                       results_smaller=tt2,
                       title="Equivalence test for 2 independent Poisson rates"
                       )
 
     return res
+
+
+def nonequivalence_poisson_2indep(count1, exposure1, count2, exposure2,
+                                  low, upp, method='score', compare="ratio"):
+    """Test for non-equivalence, minimum effect for poisson
+
+    This reverses null and alternative hypothesis compared to equivalence
+    testing. The null hypothesis is that the effect, ratio (or diff), is in
+    an interval that specifies a range of irrelevant or unimportant
+    differences between the two samples.
+
+    The Null and alternative hypothesis comparing the ratio of rates are
+
+    - H0: low < g1 / g2 < upp
+    - H1: g1 / g2 <= low or upp <= g1 / g2
+
+    where g1 and g2 are the Poisson rates.
+
+
+    Notes
+    -----
+    This is implemented as two one-sided tests at the minimum effect boundaries
+    (low, upp) with (nominal) size alpha / 2 each.
+    The size of the test is the sum of the two one-tailed tests, which
+    corresponds to an equal-tailed two-sided test.
+    If low and upp are equal, then the result is the same as the standard
+    two-sided test.
+
+    The p-value is computed as `2 * min(pvalue_low, pvalue_upp)` in analogy to
+    two-sided equal-tail tests.
+
+    In large samples the nominal size of the test will be below alpha.
+
+    References
+    ----------
+    Hodges, J. L., Jr., and E. L. Lehmann. 1954. Testing the Approximate
+    Validity of Statistical Hypotheses. Journal of the Royal Statistical
+    Society, Series B (Methodological) 16: 261–68.
+
+    Kim, Jae H., and Andrew P. Robinson. 2019. “Interval-Based Hypothesis
+    Testing and Its Applications to Economics and Finance.”
+    Econometrics 7 (2): 21. https://doi.org/10.3390/econometrics7020021.
+
+    """
+    tt1 = test_poisson_2indep(count1, exposure1, count2, exposure2,
+                              value=low, method=method, compare=compare,
+                              alternative='smaller')
+    tt2 = test_poisson_2indep(count1, exposure1, count2, exposure2,
+                              value=upp, method=method, compare=compare,
+                              alternative='larger')
+
+    # idx_min = 0 if tt1.pvalue < tt2.pvalue else 1
+    idx_min = np.asarray(tt1.pvalue < tt2.pvalue, int)
+    pvalue = 2 * np.minimum(tt1.pvalue, tt2.pvalue)
+    statistic = np.choose(idx_min, [tt1.statistic, tt2.statistic])
+    res = HolderTuple(statistic=statistic,
+                      pvalue=pvalue,
+                      method=method,
+                      results_larger=tt1,
+                      results_smaller=tt2,
+                      title="Equivalence test for 2 independent Poisson rates"
+                      )
+
+    return res
+
+
+def confint_poisson_2indep(count1, exposure1, count2, exposure2,
+                           method='score', compare='ratio', alpha=0.05,
+                           method_mover="score",
+                           ):
+    """Confidence interval for ratio of 2 indep poisson rates
+
+    Parameters
+    ----------
+    count1 : int
+        Number of events in first sample.
+    exposure1 : float
+        Total exposure (time * subjects) in first sample.
+    count2 : int
+        Number of events in second sample.
+    exposure2 : float
+        Total exposure (time * subjects) in second sample.
+    method : string
+        Method for the test statistic and the p-value. Defaults to `'score'`.
+        see Notes.
+
+        ratio:
+
+        - 'wald': NOT YET, method W1A, wald test, variance based on observed
+          rates
+        - 'waldcc' :
+        - 'score': method W2A, score test, variance based on estimate under
+          the Null hypothesis
+        - 'wald-log': W3A, uses log-ratio, variance based on observed rates
+        - 'score-log' W4A, uses log-ratio, variance based on estimate under
+          the Null hypothesis
+        - 'sqrt': W5A, based on variance stabilizing square root transformation
+        - 'sqrtcc' :
+        - 'exact-cond': NOT YET, exact conditional test based on binomial
+          distribution
+           This uses ``binom_test`` which is minlike in the two-sided case.
+        - 'cond-midp': NOT YET, midpoint-pvalue of exact conditional test
+        - 'mover' :
+
+        diff:
+
+        - 'wald',
+        - 'waldccv'
+        - 'score'
+        - 'mover'
+
+    compare : {'diff', 'ratio'}
+        Default is "ratio".
+        If compare is `diff`, then the hypothesis test is for
+        diff = rate1 - rate2.
+        If compare is `ratio`, then the hypothesis test is for the
+        rate ratio defined by ratio = rate1 / rate2.
+    alternative : string
+        The alternative hypothesis, H1, has to be one of the following
+
+        - 'two-sided': H1: ratio of rates is not equal to ratio_null (default)
+        - 'larger' :   H1: ratio of rates is larger than ratio_null
+        - 'smaller' :  H1: ratio of rates is smaller than ratio_null
+
+    alpha : float in (0, 1)
+        Significance level, nominal coverage of the confidence interval is
+        1 - alpha.
+
+    Returns
+    -------
+    tuple (low, upp) : confidence limits.
+
+    """
+
+    # shortcut names
+    y1, n1, y2, n2 = map(np.asarray, [count1, exposure1, count2, exposure2])
+    rate1, rate2 = y1 / n1, y2 / n2
+    alpha = alpha / 2  # two-sided only
+
+    if compare == "ratio":
+
+        if method == "score":
+            low, upp = _invert_test_confint_2indep(
+                count1, exposure1, count2, exposure2,
+                alpha=alpha * 2,   # check how alpha is defined
+                method="score",
+                compare="ratio",
+                method_start="waldcc"
+                )
+            ci = (low, upp)
+
+        elif method == "wald-log":
+            crit = stats.norm.isf(alpha)
+            c = 0
+            center = (count1 + c) / (count2 + c) * n2 / n1
+            std = np.sqrt(1 / (count1 + c) + 1 / (count2 + c))
+
+            ci = (center * np.exp(- crit * std), center * np.exp(crit * std))
+
+        elif method == "score-log":
+            low, upp = _invert_test_confint_2indep(
+                count1, exposure1, count2, exposure2,
+                alpha=alpha * 2,   # check how alpha is defined
+                method="score-log",
+                compare="ratio",
+                method_start="waldcc"
+                )
+            ci = (low, upp)
+
+        elif method == "waldcc":
+            crit = stats.norm.isf(alpha)
+            center = (count1 + 0.5) / (count2 + 0.5) * n2 / n1
+            std = np.sqrt(1 / (count1 + 0.5) + 1 / (count2 + 0.5))
+
+            ci = (center * np.exp(- crit * std), center * np.exp(crit * std))
+
+        elif method == "sqrtcc":
+            # coded based on Price, Bonett 2000 equ (2.4)
+            crit = stats.norm.isf(alpha)
+            center = np.sqrt((count1 + 0.5) * (count2 + 0.5))
+            std = 0.5 * np.sqrt(count1 + 0.5 + count2 + 0.5 - 0.25 * crit)
+            denom = (count2 + 0.5 - 0.25 * crit**2)
+
+            low_sqrt = (center - crit * std) / denom
+            upp_sqrt = (center + crit * std) / denom
+
+            ci = (low_sqrt**2, upp_sqrt**2)
+
+        elif method == "mover":
+            method_p = method_mover
+            ci1 = confint_poisson(y1, n1, method=method_p, alpha=2*alpha)
+            ci2 = confint_poisson(y2, n2, method=method_p, alpha=2*alpha)
+
+            ci = _mover_confint(rate1, rate2, ci1, ci2, contrast="ratio")
+
+        else:
+            raise ValueError(f'method "{method}" not recognized')
+
+        ci = (np.maximum(ci[0], 0), ci[1])
+
+    elif compare == "diff":
+
+        if method in ['wald']:
+            crit = stats.norm.isf(alpha)
+            center = rate1 - rate2
+            half = crit * np.sqrt(rate1 / n1 + rate2 / n2)
+            ci = center - half, center + half
+
+        elif method in ['waldccv']:
+            crit = stats.norm.isf(alpha)
+            center = rate1 - rate2
+            std = np.sqrt((count1 + 0.5) / n1**2 + (count2 + 0.5) / n2**2)
+            half = crit * std
+            ci = center - half, center + half
+
+        elif method == "score":
+            low, upp = _invert_test_confint_2indep(
+                count1, exposure1, count2, exposure2,
+                alpha=alpha * 2,   # check how alpha is defined
+                method="score",
+                compare="diff",
+                method_start="waldccv"
+                )
+            ci = (low, upp)
+
+        elif method == "mover":
+            method_p = method_mover
+            ci1 = confint_poisson(y1, n1, method=method_p, alpha=2*alpha)
+            ci2 = confint_poisson(y2, n2, method=method_p, alpha=2*alpha)
+
+            ci = _mover_confint(rate1, rate2, ci1, ci2, contrast="diff")
+        else:
+            raise ValueError(f'method "{method}" not recognized')
+    else:
+        raise NotImplementedError('"compare" needs to be ratio or diff')
+
+    return ci
+
+
+def power_poisson_ratio_2indep(
+        rate1, rate2, nobs1,
+        nobs_ratio=1,
+        exposure=1,
+        value=0,
+        alpha=0.05,
+        dispersion=1,
+        alternative="smaller",
+        method_var="alt",
+        return_results=True,
+        ):
+    """Power of test of ratio of 2 independent poisson rates.
+
+    This is based on Zhu and Zhu and Lakkis. It does not directly correspond
+    to `test_poisson_2indep`.
+
+    Parameters
+    ----------
+    rate1 : float
+        Poisson rate for the first sample, treatment group, under the
+        alternative hypothesis.
+    rate2 : float
+        Poisson rate for the second sample, reference group, under the
+        alternative hypothesis.
+    nobs1 : float or int
+        Number of observations in sample 1.
+    nobs_ratio : float
+        Sample size ratio, nobs2 = nobs_ratio * nobs1.
+    exposure : float
+        Exposure for each observation. Total exposure is nobs1 * exposure
+        and nobs2 * exposure.
+    alpha : float in interval (0,1)
+        Significance level, e.g. 0.05, is the probability of a type I
+        error, that is wrong rejections if the Null Hypothesis is true.
+    value : float
+        Rate ratio, rate1 / rate2, under the null hypothesis.
+    dispersion : float
+        Dispersion coefficient for quasi-Poisson. Dispersion different from
+        one can capture over or under dispersion relative to Poisson
+        distribution.
+    method_var : {"score", "alt"}
+        The variance of the test statistic for the null hypothesis given the
+        rates under the alternative can be either equal to the rates under the
+        alternative ``method_var="alt"``, or estimated under the constrained
+        of the null hypothesis, ``method_var="score"``.
+    alternative : string, 'two-sided' (default), 'larger', 'smaller'
+        Alternative hypothesis whether the power is calculated for a
+        two-sided (default) or one sided test. The one-sided test can be
+        either 'larger', 'smaller'.
+    return_results : bool
+        If true, then a results instance with extra information is returned,
+        otherwise only the computed power is returned.
+
+    Returns
+    -------
+    results : results instance or float
+        If return_results is False, then only the power is returned.
+        If return_results is True, then a results instance with the
+        information in attributes is returned.
+
+        power : float
+            Power of the test, e.g. 0.8, is one minus the probability of a
+            type II error. Power is the probability that the test correctly
+            rejects the Null Hypothesis if the Alternative Hypothesis is true.
+
+        Other attributes in results instance include :
+
+        std_null
+            standard error of difference under the null hypothesis (without
+            sqrt(nobs1))
+        std_alt
+            standard error of difference under the alternative hypothesis
+            (without sqrt(nobs1))
+
+    References
+    ----------
+    .. [1] Zhu, Haiyuan. 2017. “Sample Size Calculation for Comparing Two
+       Poisson or Negative Binomial Rates in Noninferiority or Equivalence
+       Trials.” Statistics in Biopharmaceutical Research, March.
+       https://doi.org/10.1080/19466315.2016.1225594
+    .. [2] Zhu, Haiyuan, and Hassan Lakkis. 2014. “Sample Size Calculation for
+       Comparing Two Negative Binomial Rates.” Statistics in Medicine 33 (3):
+       376–87. https://doi.org/10.1002/sim.5947.
+    .. [3] PASS documentation
+    """
+    # TODO: avoid possible circular import, check if needed
+    from statsmodels.stats.power import normal_power_het
+
+    rate1, rate2, nobs1 = map(np.asarray, [rate1, rate2, nobs1])
+
+    nobs2 = nobs_ratio * nobs1
+    v1 = dispersion / exposure * (1 / rate1 + 1 / (nobs_ratio * rate2))
+    if method_var == "alt":
+        v0 = v1
+    elif method_var == "score":
+        # nobs_ratio = 1 / nobs_ratio
+        v0 = dispersion / exposure * (1 + value / nobs_ratio)**2
+        v0 /= value / nobs_ratio * (rate1 + (nobs_ratio * rate2))
+    else:
+        raise NotImplementedError(f"method_var {method_var} not recognized")
+
+    std_null = np.sqrt(v0)
+    std_alt = np.sqrt(v1)
+    es = np.log(rate1 / rate2) - np.log(value)
+
+    pow_ = normal_power_het(es, nobs1, alpha, std_null=std_null,
+                            std_alternative=std_alt,
+                            alternative=alternative)
+
+    p_pooled = None  # TODO: replace or remove
+
+    if return_results:
+        res = HolderTuple(
+            power=pow_,
+            p_pooled=p_pooled,
+            std_null=std_null,
+            std_alt=std_alt,
+            nobs1=nobs1,
+            nobs2=nobs2,
+            nobs_ratio=nobs_ratio,
+            alpha=alpha,
+            tuple_=("power",),  # override default
+            )
+        return res
+
+    return pow_
+
+
+def power_equivalence_poisson_2indep(rate1, rate2, nobs1,
+                                     low, upp, nobs_ratio=1,
+                                     exposure=1, alpha=0.05, dispersion=1,
+                                     method_var="alt",
+                                     return_results=False):
+    """Power of equivalence test of ratio of 2 independent poisson rates.
+
+    Parameters
+    ----------
+    rate1 : float
+        Poisson rate for the first sample, treatment group, under the
+        alternative hypothesis.
+    rate2 : float
+        Poisson rate for the second sample, reference group, under the
+        alternative hypothesis.
+    nobs1 : float or int
+        Number of observations in sample 1.
+    low : float
+        Lower equivalence margin for the rate ratio, rate1 / rate2.
+    upp : float
+        Upper equivalence margin for the rate ratio, rate1 / rate2.
+    nobs_ratio : float
+        Sample size ratio, nobs2 = nobs_ratio * nobs1.
+    exposure : float
+        Exposure for each observation. Total exposure is nobs1 * exposure
+        and nobs2 * exposure.
+    alpha : float in interval (0,1)
+        Significance level, e.g. 0.05, is the probability of a type I
+        error, that is wrong rejections if the Null Hypothesis is true.
+    value : float
+        Difference between rates 1 and 2 under the null hypothesis.
+    method_var : {"score", "alt"}
+        The variance of the test statistic for the null hypothesis given the
+        rates uder the alternative, can be either equal to the rates under the
+        alternative ``method_var="alt"``, or estimated under the constrained
+        of the null hypothesis, ``method_var="score"``.
+    alternative : string, 'two-sided' (default), 'larger', 'smaller'
+        Alternative hypothesis whether the power is calculated for a
+        two-sided (default) or one sided test. The one-sided test can be
+        either 'larger', 'smaller'.
+    return_results : bool
+        If true, then a results instance with extra information is returned,
+        otherwise only the computed power is returned.
+
+    Returns
+    -------
+    results : results instance or float
+        If return_results is False, then only the power is returned.
+        If return_results is True, then a results instance with the
+        information in attributes is returned.
+
+        power : float
+            Power of the test, e.g. 0.8, is one minus the probability of a
+            type II error. Power is the probability that the test correctly
+            rejects the Null Hypothesis if the Alternative Hypothesis is true.
+
+        Other attributes in results instance include :
+
+        std_null
+            standard error of difference under the null hypothesis (without
+            sqrt(nobs1))
+        std_alt
+            standard error of difference under the alternative hypothesis
+            (without sqrt(nobs1))
+
+    References
+    ----------
+    .. [1] Zhu, Haiyuan. 2017. “Sample Size Calculation for Comparing Two
+       Poisson or Negative Binomial Rates in Noninferiority or Equivalence
+       Trials.” Statistics in Biopharmaceutical Research, March.
+       https://doi.org/10.1080/19466315.2016.1225594
+    .. [2] Zhu, Haiyuan, and Hassan Lakkis. 2014. “Sample Size Calculation for
+       Comparing Two Negative Binomial Rates.” Statistics in Medicine 33 (3):
+       376–87. https://doi.org/10.1002/sim.5947.
+    .. [3] PASS documentation
+    """
+    rate1, rate2, nobs1 = map(np.asarray, [rate1, rate2, nobs1])
+
+    nobs2 = nobs_ratio * nobs1
+    v1 = dispersion / exposure * (1 / rate1 + 1 / (nobs_ratio * rate2))
+
+    if method_var == "alt":
+        v0_low = v0_upp = v1
+    elif method_var == "score":
+        v0_low = dispersion / exposure * (1 + low * nobs_ratio)**2
+        v0_low /= low * nobs_ratio * (rate1 + (nobs_ratio * rate2))
+        v0_upp = dispersion / exposure * (1 + upp * nobs_ratio)**2
+        v0_upp /= upp * nobs_ratio * (rate1 + (nobs_ratio * rate2))
+    else:
+        raise NotImplementedError(f"method_var {method_var} not recognized")
+
+    es_low = np.log(rate1 / rate2) - np.log(low)
+    es_upp = np.log(rate1 / rate2) - np.log(upp)
+    std_null_low = np.sqrt(v0_low)
+    std_null_upp = np.sqrt(v0_upp)
+    std_alternative = np.sqrt(v1)
+
+    pow_ = _power_equivalence_het(es_low, es_upp, nobs2, alpha=alpha,
+                                  std_null_low=std_null_low,
+                                  std_null_upp=std_null_upp,
+                                  std_alternative=std_alternative)
+
+    if return_results:
+        res = HolderTuple(
+            power=pow_[0],
+            power_margins=pow[1:],
+            std_null_low=std_null_low,
+            std_null_upp=std_null_upp,
+            std_alt=std_alternative,
+            nobs1=nobs1,
+            nobs2=nobs2,
+            nobs_ratio=nobs_ratio,
+            alpha=alpha,
+            tuple_=("power",),  # override default
+            )
+        return res
+    else:
+        return pow_[0]
+
+
+def _power_equivalence_het_v0(es_low, es_upp, nobs, alpha=0.05,
+                              std_null_low=None,
+                              std_null_upp=None,
+                              std_alternative=None):
+    """power for equivalence test
+
+    """
+
+    s0_low = std_null_low
+    s0_upp = std_null_upp
+    s1 = std_alternative
+
+    crit = norm.isf(alpha)
+    pow_ = (
+        norm.cdf((np.sqrt(nobs) * es_low - crit * s0_low) / s1) +
+        norm.cdf((np.sqrt(nobs) * es_upp - crit * s0_upp) / s1) - 1
+        )
+    return pow_
+
+
+def _power_equivalence_het(es_low, es_upp, nobs, alpha=0.05,
+                           std_null_low=None,
+                           std_null_upp=None,
+                           std_alternative=None):
+    """power for equivalence test
+
+    """
+
+    s0_low = std_null_low
+    s0_upp = std_null_upp
+    s1 = std_alternative
+
+    crit = norm.isf(alpha)
+
+    # Note: rejection region is an interval [low, upp]
+    # Here we compute the complement of the two tail probabilities
+    p1 = norm.sf((np.sqrt(nobs) * es_low - crit * s0_low) / s1)
+    p2 = norm.cdf((np.sqrt(nobs) * es_upp + crit * s0_upp) / s1)
+    pow_ = 1 - (p1 + p2)
+    return pow_, p1, p2
+
+
+def _std_2poisson_power(
+        rate1, rate2, nobs_ratio=1, alpha=0.05,
+        exposure=1,
+        dispersion=1,
+        value=0,
+        method_var="score",
+        ):
+    rates_pooled = (rate1 + rate2 * nobs_ratio) / (1 + nobs_ratio)
+    # v1 = dispersion / exposure * (1 / rate2 + 1 / (nobs_ratio * rate1))
+    if method_var == "alt":
+        v0 = v1 = rate1 + rate2 / nobs_ratio
+    else:
+        # uaw n1 = 1 as normalization
+        _, r1_cmle, r2_cmle = _score_diff(
+            rate1, 1, rate2 * nobs_ratio, nobs_ratio, value=value,
+            return_cmle=True)
+        v1 = rate1 + rate2 / nobs_ratio
+        v0 = r1_cmle + r2_cmle / nobs_ratio
+    return rates_pooled, np.sqrt(v0), np.sqrt(v1)
+
+
+def power_poisson_diff_2indep(rate1, rate2, nobs1, nobs_ratio=1, alpha=0.05,
+                              value=0,
+                              method_var="score",
+                              alternative='two-sided',
+                              return_results=True):
+    """Power of ztest for the difference between two independent poisson rates.
+
+    Parameters
+    ----------
+    rate1 : float
+        Poisson rate for the first sample, treatment group, under the
+        alternative hypothesis.
+    rate2 : float
+        Poisson rate for the second sample, reference group, under the
+        alternative hypothesis.
+    nobs1 : float or int
+        Number of observations in sample 1.
+    nobs_ratio : float
+        Sample size ratio, nobs2 = nobs_ratio * nobs1.
+    alpha : float in interval (0,1)
+        Significance level, e.g. 0.05, is the probability of a type I
+        error, that is wrong rejections if the Null Hypothesis is true.
+    value : float
+        Difference between rates 1 and 2 under the null hypothesis.
+    method_var : {"score", "alt"}
+        The variance of the test statistic for the null hypothesis given the
+        rates uder the alternative, can be either equal to the rates under the
+        alternative ``method_var="alt"``, or estimated under the constrained
+        of the null hypothesis, ``method_var="score"``.
+    alternative : string, 'two-sided' (default), 'larger', 'smaller'
+        Alternative hypothesis whether the power is calculated for a
+        two-sided (default) or one sided test. The one-sided test can be
+        either 'larger', 'smaller'.
+    return_results : bool
+        If true, then a results instance with extra information is returned,
+        otherwise only the computed power is returned.
+
+    Returns
+    -------
+    results : results instance or float
+        If return_results is False, then only the power is returned.
+        If return_results is True, then a results instance with the
+        information in attributes is returned.
+
+        power : float
+            Power of the test, e.g. 0.8, is one minus the probability of a
+            type II error. Power is the probability that the test correctly
+            rejects the Null Hypothesis if the Alternative Hypothesis is true.
+
+        Other attributes in results instance include :
+
+        std_null
+            standard error of difference under the null hypothesis (without
+            sqrt(nobs1))
+        std_alt
+            standard error of difference under the alternative hypothesis
+            (without sqrt(nobs1))
+
+    References
+    ----------
+    .. [1] PASS manual chapter 436
+    .. [2] Stucke, Kathrin, and Meinhard Kieser. 2013. “Sample Size
+       Calculations for Noninferiority Trials with Poisson Distributed Count
+       Data.” Biometrical Journal 55 (2): 203–16.
+       https://doi.org/10.1002/bimj.201200142.
+
+    """
+    # TODO: avoid possible circular import, check if needed
+    from statsmodels.stats.power import normal_power_het
+
+    rate1, rate2, nobs1 = map(np.asarray, [rate1, rate2, nobs1])
+
+    diff = rate1 - rate2
+    _, std_null, std_alt = _std_2poisson_power(
+        rate1,
+        rate2,
+        nobs_ratio=nobs_ratio,
+        alpha=alpha,
+        value=value,
+        method_var=method_var,
+        )
+
+    pow_ = normal_power_het(diff - value, nobs1, alpha, std_null=std_null,
+                            std_alternative=std_alt,
+                            alternative=alternative)
+
+    if return_results:
+        res = HolderTuple(
+            power=pow_,
+            rates_alt=(rate2 + diff, rate2),
+            std_null=std_null,
+            std_alt=std_alt,
+            nobs1=nobs1,
+            nobs2=nobs_ratio * nobs1,
+            nobs_ratio=nobs_ratio,
+            alpha=alpha,
+            tuple_=("power",),  # override default
+            )
+        return res
+    else:
+        return pow_
+
+
+def _var_cmle_negbin(rate1, rate2, nobs_ratio, exposure=1, value=1,
+                     dispersion=0):
+    """
+    variance based on constrained cmle, for score test version
+
+    for ratio comparison of two negative binomial samples
+
+    value = rate1 / rate2 under the null
+    """
+    # definitions in Zhu
+    # nobs_ratio = n1 / n0
+    # value = ratio = r1 / r0
+    rate0 = rate2  # control
+    nobs_ratio = 1 / nobs_ratio
+
+    a = - dispersion * exposure * value * (1 + nobs_ratio)
+    b = (dispersion * exposure * (rate0 * value + nobs_ratio * rate1) -
+         (1 + nobs_ratio * value))
+    c = rate0 + nobs_ratio * rate1
+    if dispersion == 0:
+        r0 = -c / b
+    else:
+        r0 = (-b - np.sqrt(b**2 - 4 * a * c)) / (2 * a)
+    r1 = r0 * value
+    v = (1 / exposure / r0 * (1 + 1 / value / nobs_ratio) +
+         (1 + nobs_ratio) / nobs_ratio * dispersion)
+
+    r2 = r0
+    return v * nobs_ratio, r1, r2
+
+
+def power_negbin_ratio_2indep(
+        rate1, rate2, nobs1,
+        nobs_ratio=1,
+        exposure=1,
+        value=1,
+        alpha=0.05,
+        dispersion=0.01,
+        alternative="two-sided",
+        method_var="alt",
+        return_results=True):
+    """
+    Power of test of ratio of 2 independent negative binomial rates.
+
+    Parameters
+    ----------
+    rate1 : float
+        Poisson rate for the first sample, treatment group, under the
+        alternative hypothesis.
+    rate2 : float
+        Poisson rate for the second sample, reference group, under the
+        alternative hypothesis.
+    nobs1 : float or int
+        Number of observations in sample 1.
+    low : float
+        Lower equivalence margin for the rate ratio, rate1 / rate2.
+    upp : float
+        Upper equivalence margin for the rate ratio, rate1 / rate2.
+    nobs_ratio : float
+        Sample size ratio, nobs2 = nobs_ratio * nobs1.
+    exposure : float
+        Exposure for each observation. Total exposure is nobs1 * exposure
+        and nobs2 * exposure.
+    value : float
+        Rate ratio, rate1 / rate2, under the null hypothesis.
+    alpha : float in interval (0,1)
+        Significance level, e.g. 0.05, is the probability of a type I
+        error, that is wrong rejections if the Null Hypothesis is true.
+    dispersion : float >= 0.
+        Dispersion parameter for Negative Binomial distribution.
+        The Poisson limiting case corresponds to ``dispersion=0``.
+    method_var : {"score", "alt"}
+        The variance of the test statistic for the null hypothesis given the
+        rates under the alternative, can be either equal to the rates under the
+        alternative ``method_var="alt"``, or estimated under the constrained
+        of the null hypothesis, ``method_var="score"``, or based on a moment
+        constrained estimate, ``method_var="ftotal"``. see references.
+    alternative : string, 'two-sided' (default), 'larger', 'smaller'
+        Alternative hypothesis whether the power is calculated for a
+        two-sided (default) or one sided test. The one-sided test can be
+        either 'larger', 'smaller'.
+    return_results : bool
+        If true, then a results instance with extra information is returned,
+        otherwise only the computed power is returned.
+
+    Returns
+    -------
+    results : results instance or float
+        If return_results is False, then only the power is returned.
+        If return_results is True, then a results instance with the
+        information in attributes is returned.
+
+        power : float
+            Power of the test, e.g. 0.8, is one minus the probability of a
+            type II error. Power is the probability that the test correctly
+            rejects the Null Hypothesis if the Alternative Hypothesis is true.
+
+        Other attributes in results instance include :
+
+        std_null
+            standard error of difference under the null hypothesis (without
+            sqrt(nobs1))
+        std_alt
+            standard error of difference under the alternative hypothesis
+            (without sqrt(nobs1))
+
+    References
+    ----------
+    .. [1] Zhu, Haiyuan. 2017. “Sample Size Calculation for Comparing Two
+       Poisson or Negative Binomial Rates in Noninferiority or Equivalence
+       Trials.” Statistics in Biopharmaceutical Research, March.
+       https://doi.org/10.1080/19466315.2016.1225594
+    .. [2] Zhu, Haiyuan, and Hassan Lakkis. 2014. “Sample Size Calculation for
+       Comparing Two Negative Binomial Rates.” Statistics in Medicine 33 (3):
+       376–87. https://doi.org/10.1002/sim.5947.
+    .. [3] PASS documentation
+    """
+    # TODO: avoid possible circular import, check if needed
+    from statsmodels.stats.power import normal_power_het
+
+    rate1, rate2, nobs1 = map(np.asarray, [rate1, rate2, nobs1])
+
+    nobs2 = nobs_ratio * nobs1
+    v1 = ((1 / rate1 + 1 / (nobs_ratio * rate2)) / exposure +
+          (1 + nobs_ratio) / nobs_ratio * dispersion)
+    if method_var == "alt":
+        v0 = v1
+    elif method_var == "ftotal":
+        v0 = (1 + value * nobs_ratio)**2 / (
+             exposure * nobs_ratio * value * (rate1 + nobs_ratio * rate2))
+        v0 += (1 + nobs_ratio) / nobs_ratio * dispersion
+    elif method_var == "score":
+        v0 = _var_cmle_negbin(rate1, rate2, nobs_ratio,
+                              exposure=exposure, value=value,
+                              dispersion=dispersion)[0]
+    else:
+        raise NotImplementedError(f"method_var {method_var} not recognized")
+
+    std_null = np.sqrt(v0)
+    std_alt = np.sqrt(v1)
+    es = np.log(rate1 / rate2) - np.log(value)
+
+    pow_ = normal_power_het(es, nobs1, alpha, std_null=std_null,
+                            std_alternative=std_alt,
+                            alternative=alternative)
+
+    if return_results:
+        res = HolderTuple(
+            power=pow_,
+            std_null=std_null,
+            std_alt=std_alt,
+            nobs1=nobs1,
+            nobs2=nobs2,
+            nobs_ratio=nobs_ratio,
+            alpha=alpha,
+            tuple_=("power",),  # override default
+            )
+        return res
+
+    return pow_
+
+
+def power_equivalence_neginb_2indep(rate1, rate2, nobs1,
+                                    low, upp, nobs_ratio=1,
+                                    exposure=1, alpha=0.05, dispersion=0,
+                                    method_var="alt",
+                                    return_results=False):
+    """
+    Power of equivalence test of ratio of 2 indep. negative binomial rates.
+
+    Parameters
+    ----------
+    rate1 : float
+        Poisson rate for the first sample, treatment group, under the
+        alternative hypothesis.
+    rate2 : float
+        Poisson rate for the second sample, reference group, under the
+        alternative hypothesis.
+    nobs1 : float or int
+        Number of observations in sample 1.
+    low : float
+        Lower equivalence margin for the rate ratio, rate1 / rate2.
+    upp : float
+        Upper equivalence margin for the rate ratio, rate1 / rate2.
+    nobs_ratio : float
+        Sample size ratio, nobs2 = nobs_ratio * nobs1.
+    alpha : float in interval (0,1)
+        Significance level, e.g. 0.05, is the probability of a type I
+        error, that is wrong rejections if the Null Hypothesis is true.
+    dispersion : float >= 0.
+        Dispersion parameter for Negative Binomial distribution.
+        The Poisson limiting case corresponds to ``dispersion=0``.
+    method_var : {"score", "alt"}
+        The variance of the test statistic for the null hypothesis given the
+        rates under the alternative, can be either equal to the rates under the
+        alternative ``method_var="alt"``, or estimated under the constrained
+        of the null hypothesis, ``method_var="score"``, or based on a moment
+        constrained estimate, ``method_var="ftotal"``. see references.
+    alternative : string, 'two-sided' (default), 'larger', 'smaller'
+        Alternative hypothesis whether the power is calculated for a
+        two-sided (default) or one sided test. The one-sided test can be
+        either 'larger', 'smaller'.
+    return_results : bool
+        If true, then a results instance with extra information is returned,
+        otherwise only the computed power is returned.
+
+    Returns
+    -------
+    results : results instance or float
+        If return_results is False, then only the power is returned.
+        If return_results is True, then a results instance with the
+        information in attributes is returned.
+
+        power : float
+            Power of the test, e.g. 0.8, is one minus the probability of a
+            type II error. Power is the probability that the test correctly
+            rejects the Null Hypothesis if the Alternative Hypothesis is true.
+
+        Other attributes in results instance include :
+
+        std_null
+            standard error of difference under the null hypothesis (without
+            sqrt(nobs1))
+        std_alt
+            standard error of difference under the alternative hypothesis
+            (without sqrt(nobs1))
+
+
+    References
+    ----------
+    .. [1] Zhu, Haiyuan. 2017. “Sample Size Calculation for Comparing Two
+       Poisson or Negative Binomial Rates in Noninferiority or Equivalence
+       Trials.” Statistics in Biopharmaceutical Research, March.
+       https://doi.org/10.1080/19466315.2016.1225594
+    .. [2] Zhu, Haiyuan, and Hassan Lakkis. 2014. “Sample Size Calculation for
+       Comparing Two Negative Binomial Rates.” Statistics in Medicine 33 (3):
+       376–87. https://doi.org/10.1002/sim.5947.
+    .. [3] PASS documentation
+    """
+    rate1, rate2, nobs1 = map(np.asarray, [rate1, rate2, nobs1])
+
+    nobs2 = nobs_ratio * nobs1
+
+    v1 = ((1 / rate2 + 1 / (nobs_ratio * rate1)) / exposure +
+          (1 + nobs_ratio) / nobs_ratio * dispersion)
+    if method_var == "alt":
+        v0_low = v0_upp = v1
+    elif method_var == "ftotal":
+        v0_low = (1 + low * nobs_ratio)**2 / (
+             exposure * nobs_ratio * low * (rate1 + nobs_ratio * rate2))
+        v0_low += (1 + nobs_ratio) / nobs_ratio * dispersion
+        v0_upp = (1 + upp * nobs_ratio)**2 / (
+             exposure * nobs_ratio * upp * (rate1 + nobs_ratio * rate2))
+        v0_upp += (1 + nobs_ratio) / nobs_ratio * dispersion
+    elif method_var == "score":
+        v0_low = _var_cmle_negbin(rate1, rate2, nobs_ratio,
+                                  exposure=exposure, value=low,
+                                  dispersion=dispersion)[0]
+        v0_upp = _var_cmle_negbin(rate1, rate2, nobs_ratio,
+                                  exposure=exposure, value=upp,
+                                  dispersion=dispersion)[0]
+    else:
+        raise NotImplementedError(f"method_var {method_var} not recognized")
+
+    es_low = np.log(rate1 / rate2) - np.log(low)
+    es_upp = np.log(rate1 / rate2) - np.log(upp)
+    std_null_low = np.sqrt(v0_low)
+    std_null_upp = np.sqrt(v0_upp)
+    std_alternative = np.sqrt(v1)
+
+    pow_ = _power_equivalence_het(es_low, es_upp, nobs1, alpha=alpha,
+                                  std_null_low=std_null_low,
+                                  std_null_upp=std_null_upp,
+                                  std_alternative=std_alternative)
+
+    if return_results:
+        res = HolderTuple(
+            power=pow_[0],
+            power_margins=pow[1:],
+            std_null_low=std_null_low,
+            std_null_upp=std_null_upp,
+            std_alt=std_alternative,
+            nobs1=nobs1,
+            nobs2=nobs2,
+            nobs_ratio=nobs_ratio,
+            alpha=alpha,
+            tuple_=("power",),  # override default
+            )
+        return res
+    else:
+        return pow_[0]


Test patch:
diff --git a/statsmodels/stats/tests/test_rates_poisson.py b/statsmodels/stats/tests/test_rates_poisson.py
--- a/statsmodels/stats/tests/test_rates_poisson.py
+++ b/statsmodels/stats/tests/test_rates_poisson.py
@@ -2,12 +2,307 @@
 
 import pytest
 import warnings
+import numpy as np
 from numpy import arange
 from numpy.testing import assert_allclose, assert_equal
+from scipy import stats
 
 # we cannot import test_poisson_2indep directly, pytest treats that as test
 import statsmodels.stats.rates as smr
-from statsmodels.stats.rates import etest_poisson_2indep
+from statsmodels.stats.rates import (
+    # test_poisson, # cannot import functions that start with test
+    confint_poisson,
+    tolerance_int_poisson,
+    confint_quantile_poisson,
+    etest_poisson_2indep,
+    confint_poisson_2indep,
+    nonequivalence_poisson_2indep,
+    power_poisson_ratio_2indep,
+    power_equivalence_poisson_2indep,
+    power_poisson_diff_2indep,
+    power_equivalence_neginb_2indep,
+    power_negbin_ratio_2indep,
+    method_names_poisson_1samp,
+    method_names_poisson_2indep,
+    )
+
+methods = ["wald", "score", "exact-c", "waldccv", "sqrt-a", "sqrt-v", "midp-c",
+           "sqrt",
+           ]
+
+
+@pytest.mark.parametrize('method', methods)
+def test_rate_poisson_consistency(method):
+    # check consistency between test and confint for one poisson rate
+    count, nobs = 15, 400
+    ci = confint_poisson(count, nobs, method=method)
+    pv1 = smr.test_poisson(count, nobs, value=ci[0], method=method).pvalue
+    pv2 = smr.test_poisson(count, nobs, value=ci[1], method=method).pvalue
+
+    rtol = 1e-10
+    if method in ["midp-c"]:
+        # numerical root finding, lower precision
+        rtol = 1e-6
+    assert_allclose(pv1, 0.05, rtol=rtol)
+    assert_allclose(pv2, 0.05, rtol=rtol)
+
+    # check one-sided, note all confint are central
+    pv1 = smr.test_poisson(count, nobs, value=ci[0], method=method,
+                           alternative="larger").pvalue
+    pv2 = smr.test_poisson(count, nobs, value=ci[1], method=method,
+                           alternative="smaller").pvalue
+
+    assert_allclose(pv1, 0.025, rtol=rtol)
+    assert_allclose(pv2, 0.025, rtol=rtol)
+
+
+def test_rate_poisson_r():
+    count, nobs = 15, 400
+
+    # DescTools
+    # PoissonCI(x=15, n=400, method = c("exact","score", "wald", "byar"))
+    # exact 0.0375 0.0209884653319583 0.0618505471787146
+    # score 0.0375 0.0227264749053794 0.0618771721463559
+    # wald  0.0375 0.0185227303217751 0.0564772696782249
+    # byar  0.0375 0.0219084369245707 0.0602933510943048
+
+    # > rr = poisson.exact(15, 400, r=0.05, tsmethod="central")
+    # > rr$p.value
+    # > rr$conf.int
+    pv2 = 0.313026269279486
+    ci2 = (0.0209884653319583, 0.0618505471787146)
+    rt = smr.test_poisson(count, nobs, value=0.05, method="exact-c")
+    ci = confint_poisson(count, nobs, method="exact-c")
+    assert_allclose(rt.pvalue, pv2, rtol=1e-12)
+    assert_allclose(ci, ci2, rtol=1e-12)
+
+    # R package ratesci
+    # sr = scoreci(15, 400, contrast="p", distrib = "poi", skew=FALSE,
+    #      theta0=0.05)
+    # pval computed from 2 * min(pval_left, pval_right)
+    pv2 = 0.263552477282973
+    # ci2 = (0.022726, 0.061877)  # no additional digits available
+    ci2 = (0.0227264749053794, 0.0618771721463559)  # from DescTools
+    rt = smr.test_poisson(count, nobs, value=0.05, method="score")
+    ci = confint_poisson(count, nobs, method="score")
+    assert_allclose(rt.pvalue, pv2, rtol=1e-12)
+    assert_allclose(ci, ci2, rtol=1e-12)
+
+    # > jeffreysci(15, 400, distrib = "poi")
+    ci2 = (0.0219234232268444, 0.0602898619930649)
+    ci = confint_poisson(count, nobs, method="jeff")
+    assert_allclose(ci, ci2, rtol=1e-12)
+
+    # from DescTools
+    ci2 = (0.0185227303217751, 0.0564772696782249)
+    ci = confint_poisson(count, nobs, method="wald")
+    assert_allclose(ci, ci2, rtol=1e-12)
+
+    # from ratesci
+    # rateci(15, 400, distrib = "poi")
+    # I think their lower ci is wrong, it also doesn't match for exact_c
+    ci2 = (0.0243357599260795, 0.0604627555786095)
+    ci = confint_poisson(count, nobs, method="midp-c")
+    assert_allclose(ci[1], ci2[1], rtol=1e-5)
+
+
+cases_tolint = [
+    ("wald", 15, 1, 1, (3, 32), (3, np.inf), (0, 31)),
+    ("score", 15, 1, 1, (4, 35), (4, np.inf), (0, 33)),
+    ("wald", 150, 100, 100, (104, 200), (108, np.inf), (0, 196)),
+    ("score", 150, 100, 100, (106, 202), (109, np.inf), (0, 198)),
+    ("exact-c", 150, 100, 100, (105, 202), (109, np.inf), (0, 198)),
+    ]
+
+
+@pytest.mark.parametrize('case', cases_tolint)
+def test_tol_int(case):
+    # results values are from R package `tolerance, e.g.
+    # poistol.int(15, 1, m = 1, alpha = 0.05, P = 0.975, side = 1, method="LS")
+    # poistol.int(150, 100, m = 100, alpha = 0.05, P = 0.95, side = 2,
+    #     method="SC")
+    # exact-c is method="TAB"
+
+    prob = 0.95
+    prob_one = 0.975
+    meth, count, exposure, exposure_new, r2, rs, rl = case
+    ti = tolerance_int_poisson(
+        count, exposure, prob, exposure_new=exposure_new,
+        method=meth, alpha=0.05, alternative="two-sided")
+    assert_equal(ti, r2)
+    ti = tolerance_int_poisson(
+        count, exposure, prob_one, exposure_new=exposure_new,
+        method=meth, alpha=0.05, alternative="larger")
+    assert_equal(ti, rl)
+    ti = tolerance_int_poisson(
+        count, exposure, prob_one, exposure_new=exposure_new,
+        method=meth, alpha=0.05, alternative="smaller")
+    assert_equal(ti, rs)
+
+    # check without parameter uncertainty, confint at alpha=1
+    if meth not in ["exact-c"]:
+        # confint for "exact-c" does not collapse to point if alpha=1, check
+        ti = tolerance_int_poisson(
+            count, exposure, prob, exposure_new=exposure_new,
+            method=meth, alpha=0.99999, alternative="two-sided")
+        ci = stats.poisson.interval(prob, count / exposure * exposure_new)
+        assert_equal(ti, ci)
+
+    # check confint_quantile_poisson,
+    # should same as upper tol_int
+    ciq = confint_quantile_poisson(
+        count, exposure, prob_one, exposure_new=exposure_new,
+        method=meth, alpha=0.05, alternative="two-sided")
+
+    assert_equal(ciq[1], r2[1])
+
+    ciq = confint_quantile_poisson(
+        count, exposure, prob_one, exposure_new=exposure_new,
+        method=meth, alpha=0.05, alternative="larger")
+
+    assert_equal(ciq[1], rl[1])
+
+    # should same as lower tol_int
+    prob_low = 0.025
+    ciq = confint_quantile_poisson(
+        count, exposure, prob_low, exposure_new=exposure_new,
+        method=meth, alpha=0.05, alternative="two-sided")
+
+    assert_equal(ciq[0], r2[0])
+
+    ciq = confint_quantile_poisson(
+        count, exposure, prob_low, exposure_new=exposure_new,
+        method=meth, alpha=0.05, alternative="smaller")
+
+    assert_equal(ciq[0], rs[0])
+
+
+class TestMethodsCompar1samp():
+    # check that all methods for test and confint produce similar results
+
+    @pytest.mark.parametrize('meth', method_names_poisson_1samp["test"])
+    def test_test(self, meth):
+        count1, n1 = 60, 514.775
+        tst = smr.test_poisson(count1, n1, method=meth, value=0.1,
+                               alternative='two-sided')
+
+        assert_allclose(tst.pvalue, 0.25, rtol=0.1)
+
+    @pytest.mark.parametrize('meth', method_names_poisson_1samp["confint"])
+    def test_confint(self, meth):
+        count1, n1 = 60, 514.775
+        ci = confint_poisson(count1, n1, method=meth, alpha=0.05)
+        assert_allclose(ci, [0.089, 0.158], rtol=0.1)
+
+
+# ######## below are 2indep tests
+
+
+methods_diff = ["wald", "score", "waldccv",
+                ]
+
+
+@pytest.mark.parametrize('method', methods_diff)
+def test_rate_poisson_diff_consistency(method):
+    # check consistency between test and confint for diff of poisson rates
+    count1, n1, count2, n2 = 30, 400 / 10, 7, 300 / 10   # avoid low=0
+    ci = confint_poisson_2indep(count1, n1, count2, n2, method=method,
+                                compare="diff")
+    pv1 = smr.test_poisson_2indep(count1, n1, count2, n2, value=ci[0],
+                                  method=method, compare="diff").pvalue
+    pv2 = smr.test_poisson_2indep(count1, n1, count2, n2, value=ci[1],
+                                  method=method, compare="diff").pvalue
+
+    rtol = 1e-10
+    if method in ["score"]:
+        # numerical root finding, lower precision
+        rtol = 1e-6
+    assert_allclose(pv1, 0.05, rtol=rtol)
+    assert_allclose(pv2, 0.05, rtol=rtol)
+
+    # check one-sided, note all confint are central
+    pv1 = smr.test_poisson_2indep(count1, n1, count2, n2, value=ci[0],
+                                  method=method,
+                                  compare="diff",
+                                  alternative="larger").pvalue
+    pv2 = smr.test_poisson_2indep(count1, n1, count2, n2, value=ci[1],
+                                  method=method,
+                                  compare="diff",
+                                  alternative="smaller").pvalue
+
+    assert_allclose(pv1, 0.025, rtol=rtol)
+    assert_allclose(pv2, 0.025, rtol=rtol)
+
+
+methods_ratio = ["wald-log", "score-log",  # "waldccv",
+                 ]
+
+
+@pytest.mark.parametrize('method', methods_ratio)
+def test_rate_poisson_ratio_consistency(method):
+    compare = "ratio"
+    # check consistency between test and confint for ratio of poisson rates
+    count1, n1, count2, n2 = 30, 400 / 10, 7, 300 / 10   # avoid low=0
+    ci = confint_poisson_2indep(count1, n1, count2, n2, method=method,
+                                compare=compare)
+    pv1 = smr.test_poisson_2indep(count1, n1, count2, n2, value=ci[0],
+                                  method=method, compare=compare).pvalue
+    pv2 = smr.test_poisson_2indep(count1, n1, count2, n2, value=ci[1],
+                                  method=method, compare=compare).pvalue
+
+    rtol = 1e-10
+    if method in ["score", "score-log"]:
+        # numerical root finding, lower precision
+        rtol = 1e-6
+    assert_allclose(pv1, 0.05, rtol=rtol)
+    assert_allclose(pv2, 0.05, rtol=rtol)
+
+    # check one-sided, note all confint are central
+    pv1 = smr.test_poisson_2indep(count1, n1, count2, n2, value=ci[0],
+                                  method=method,
+                                  compare=compare,
+                                  alternative="larger").pvalue
+    pv2 = smr.test_poisson_2indep(count1, n1, count2, n2, value=ci[1],
+                                  method=method,
+                                  compare=compare,
+                                  alternative="smaller").pvalue
+
+    assert_allclose(pv1, 0.025, rtol=rtol)
+    assert_allclose(pv2, 0.025, rtol=rtol)
+
+
+methods_diff_ratio = ["wald", "score", "etest", "etest-wald",
+                      ]
+
+
+@pytest.mark.parametrize('method', methods_diff_ratio)
+def test_rate_poisson_diff_ratio_consistency(method):
+    # check consistency between test for rate and diff for equality null
+    count1, n1, count2, n2 = 30, 400 / 10, 7, 300 / 10   # avoid low=0
+    t1 = smr.test_poisson_2indep(count1, n1, count2, n2,
+                                 method=method, compare="ratio")
+    t2 = smr.test_poisson_2indep(count1, n1, count2, n2,
+                                 method=method, compare="diff")
+
+    assert_allclose(t1.tuple, t2.tuple, rtol=1e-13)
+
+    t1 = smr.test_poisson_2indep(count1, n1, count2, n2,
+                                 method=method, compare="ratio",
+                                 alternative="larger")
+    t2 = smr.test_poisson_2indep(count1, n1, count2, n2,
+                                 method=method, compare="diff",
+                                 alternative="larger")
+
+    assert_allclose(t1.tuple, t2.tuple, rtol=1e-13)
+
+    t1 = smr.test_poisson_2indep(count1, n1, count2, n2,
+                                 method=method, compare="ratio",
+                                 alternative="smaller")
+    t2 = smr.test_poisson_2indep(count1, n1, count2, n2,
+                                 method=method, compare="diff",
+                                 alternative="smaller")
+
+    assert_allclose(t1.tuple, t2.tuple, rtol=1e-13)
 
 
 def test_twosample_poisson():
@@ -26,6 +321,18 @@ def test_twosample_poisson():
     assert_allclose(pv2, pv2r*2, rtol=0, atol=5e-6)
     assert_allclose(s2, 3.417402, atol=0, rtol=5e-6)  # regression test
 
+    s2, pv2 = smr.test_poisson_2indep(count1, n1, count2, n2,
+                                      method='wald-log')
+    pv2r = 0.000420
+    assert_allclose(pv2, pv2r*2, rtol=0, atol=5e-6)
+    assert_allclose(s2, 3.3393, atol=0, rtol=5e-6)
+
+    s2, pv2 = smr.test_poisson_2indep(count1, n1, count2, n2,
+                                      method='score-log')
+    pv2r = 0.000200
+    assert_allclose(pv2, pv2r*2, rtol=0, atol=5e-6)
+    assert_allclose(s2, 3.5406, atol=0, rtol=5e-5)
+
     s2, pv2 = smr.test_poisson_2indep(count1, n1, count2, n2, method='sqrt')
     pv2r = 0.000285
     assert_allclose(pv2, pv2r*2, rtol=0, atol=5e-6)
@@ -36,19 +343,32 @@ def test_twosample_poisson():
     # I don't know why it's only 2.5 decimal agreement, rounding?
     count1, n1, count2, n2 = 41, 28010, 15, 19017
     s1, pv1 = smr.test_poisson_2indep(count1, n1, count2, n2, method='wald',
-                                      ratio_null=1.5)
+                                      value=1.5)
     pv1r = 0.2309
-    assert_allclose(pv1, pv1r*2, rtol=0, atol=5e-3)
+    assert_allclose(pv1, pv1r*2, rtol=0, atol=5e-4)
     assert_allclose(s1, 0.735447, atol=0, rtol=5e-6)  # regression test
 
     s2, pv2 = smr.test_poisson_2indep(count1, n1, count2, n2, method='score',
-                                      ratio_null=1.5)
+                                      value=1.5)
     pv2r = 0.2398
-    assert_allclose(pv2, pv2r*2, rtol=0, atol=5e-3)
+    assert_allclose(pv2, pv2r*2, rtol=0, atol=5e-4)
     assert_allclose(s2, 0.706631, atol=0, rtol=5e-6)  # regression test
 
+    s2, pv2 = smr.test_poisson_2indep(count1, n1, count2, n2,
+                                      method='wald-log', value=1.5)
+    pv2r = 0.2402
+    assert_allclose(pv2, pv2r*2, rtol=0, atol=5e-4)
+    assert_allclose(s2, 0.7056, atol=0, rtol=5e-4)
+
+    with pytest.warns(DeprecationWarning):
+        s2, pv2 = smr.test_poisson_2indep(count1, n1, count2, n2,
+                                          method='score-log', ratio_null=1.5)
+    pv2r = 0.2303
+    assert_allclose(pv2, pv2r*2, rtol=0, atol=5e-4)
+    assert_allclose(s2, 0.7380, atol=0, rtol=5e-4)
+
     s2, pv2 = smr.test_poisson_2indep(count1, n1, count2, n2, method='sqrt',
-                                      ratio_null=1.5)
+                                      value=1.5)
     pv2r = 0.2499
     assert_allclose(pv2, pv2r*2, rtol=0, atol=5e-3)
     assert_allclose(s2, 0.674401, atol=0, rtol=5e-6)  # regression test
@@ -76,25 +396,25 @@ def test_twosample_poisson():
 
     s2, pv2 = smr.test_poisson_2indep(count1, n1, count2, n2,
                                       method='exact-cond',
-                                      ratio_null=1, alternative='larger')
+                                      value=1, alternative='larger')
     pv2r = 0.000428  # typo in Gu et al, switched pvalues between C and M
     assert_allclose(pv2, pv2r, rtol=0, atol=5e-4)
 
     s2, pv2 = smr.test_poisson_2indep(count1, n1, count2, n2,
                                       method='cond-midp',
-                                      ratio_null=1, alternative='larger')
+                                      value=1, alternative='larger')
     pv2r = 0.000310
     assert_allclose(pv2, pv2r, rtol=0, atol=5e-4)
 
     _, pve1 = etest_poisson_2indep(count1, n1, count2, n2,
                                    method='score',
-                                   ratio_null=1, alternative='larger')
+                                   alternative='larger')
     pve1r = 0.000298
     assert_allclose(pve1, pve1r, rtol=0, atol=5e-4)
 
     _, pve1 = etest_poisson_2indep(count1, n1, count2, n2,
                                    method='wald',
-                                   ratio_null=1, alternative='larger')
+                                   alternative='larger')
     pve1r = 0.000298
     assert_allclose(pve1, pve1r, rtol=0, atol=5e-4)
 
@@ -102,17 +422,17 @@ def test_twosample_poisson():
     # I don't know why it's only 2.5 decimal agreement, rounding?
     count1, n1, count2, n2 = 41, 28010, 15, 19017
     s1, pv1 = smr.test_poisson_2indep(count1, n1, count2, n2, method='wald',
-                                      ratio_null=1.5, alternative='larger')
+                                      value=1.5, alternative='larger')
     pv1r = 0.2309
     assert_allclose(pv1, pv1r, rtol=0, atol=5e-4)
 
     s2, pv2 = smr.test_poisson_2indep(count1, n1, count2, n2, method='score',
-                                      ratio_null=1.5, alternative='larger')
+                                      value=1.5, alternative='larger')
     pv2r = 0.2398
     assert_allclose(pv2, pv2r, rtol=0, atol=5e-4)
 
     s2, pv2 = smr.test_poisson_2indep(count1, n1, count2, n2, method='sqrt',
-                                      ratio_null=1.5, alternative='larger')
+                                      value=1.5, alternative='larger')
     pv2r = 0.2499
     assert_allclose(pv2, pv2r, rtol=0, atol=5e-4)
 
@@ -120,29 +440,61 @@ def test_twosample_poisson():
 
     s2, pv2 = smr.test_poisson_2indep(count1, n1, count2, n2,
                                       method='exact-cond',
-                                      ratio_null=1.5, alternative='larger')
+                                      value=1.5, alternative='larger')
     pv2r = 0.2913
     assert_allclose(pv2, pv2r, rtol=0, atol=5e-4)
 
     s2, pv2 = smr.test_poisson_2indep(count1, n1, count2, n2,
                                       method='cond-midp',
-                                      ratio_null=1.5, alternative='larger')
+                                      value=1.5, alternative='larger')
     pv2r = 0.2450
     assert_allclose(pv2, pv2r, rtol=0, atol=5e-4)
 
     _, pve2 = etest_poisson_2indep(count1, n1, count2, n2,
                                    method='score',
-                                   ratio_null=1.5, alternative='larger')
+                                   value=1.5, alternative='larger')
     pve2r = 0.2453
     assert_allclose(pve2, pve2r, rtol=0, atol=5e-4)
 
     _, pve2 = etest_poisson_2indep(count1, n1, count2, n2,
                                    method='wald',
-                                   ratio_null=1.5, alternative='larger')
+                                   value=1.5, alternative='larger')
     pve2r = 0.2453
     assert_allclose(pve2, pve2r, rtol=0, atol=5e-4)
 
 
+cases_diff_ng = [
+    ("wald",  (2.2047, 0.0137), (1.5514, 0.06040)),
+    ("score", (2.0818, 0.0187), (1.5023, 0.06651)),
+    ("etest-wald",  (2.2047, 0.0184), (1.5514, 0.06626)),
+    # Ng reports 0.07088 at value=0.0002, looks strange
+    ("etest-score", (2.0818, 0.0179), (1.5023, 0.06626)),  # 0.07088)),
+    ]
+
+
+@pytest.mark.parametrize('case', cases_diff_ng)
+def test_twosample_poisson_diff(case):
+    # testing against two examples Ng et al 2007
+    # methods_diff = ["wald", "score", "etest-wald", "etest"]
+
+    meth, res1, res2 = case
+    count1, exposure1, count2, exposure2 = 41, 28010, 15, 19017
+
+    value = 0
+    t = smr.test_poisson_2indep(count1, exposure1, count2, exposure2,
+                                value=value,
+                                method=meth, compare='diff',
+                                alternative='larger', etest_kwds=None)
+    assert_allclose((t.statistic, t.pvalue), res1, atol=0.0006)
+
+    value = 0.0002
+    t = smr.test_poisson_2indep(count1, exposure1, count2, exposure2,
+                                value=value,
+                                method=meth, compare='diff',
+                                alternative='larger', etest_kwds=None)
+    assert_allclose((t.statistic, t.pvalue), res2, atol=0.0007)
+
+
 def test_twosample_poisson_r():
     # testing against R package `exactci
     from .results.results_rates import res_pexact_cond, res_pexact_cond_midp
@@ -168,31 +520,111 @@ def test_twosample_poisson_r():
     # > pe$p.value
     pv2 = 0.9949053964701466
     rest = smr.test_poisson_2indep(count1, n1, count2, n2, method='cond-midp',
-                                   ratio_null=1.2, alternative='smaller')
+                                   value=1.2, alternative='smaller')
     assert_allclose(rest.pvalue, pv2, rtol=1e-12)
     # > pe = poisson.exact(c(60, 30), c(51477.5, 54308.7), r=1.2,
     #           alternative="greater", tsmethod="minlike", midp=TRUE)
     # > pe$p.value
     pv2 = 0.005094603529853279
     rest = smr.test_poisson_2indep(count1, n1, count2, n2, method='cond-midp',
-                                   ratio_null=1.2, alternative='larger')
+                                   value=1.2, alternative='larger')
     assert_allclose(rest.pvalue, pv2, rtol=1e-12)
     # > pe = poisson.exact(c(60, 30), c(51477.5, 54308.7), r=1.2,
     #           alternative="greater", tsmethod="minlike", midp=FALSE)
     # > pe$p.value
     pv2 = 0.006651774552714537
     rest = smr.test_poisson_2indep(count1, n1, count2, n2, method='exact-cond',
-                                   ratio_null=1.2, alternative='larger')
+                                   value=1.2, alternative='larger')
     assert_allclose(rest.pvalue, pv2, rtol=1e-12)
     # > pe = poisson.exact(c(60, 30), c(51477.5, 54308.7), r=1.2,
     #                      alternative="less", tsmethod="minlike", midp=FALSE)
     # > pe$p.value
     pv2 = 0.9964625674930079
     rest = smr.test_poisson_2indep(count1, n1, count2, n2, method='exact-cond',
-                                   ratio_null=1.2, alternative='smaller')
+                                   value=1.2, alternative='smaller')
     assert_allclose(rest.pvalue, pv2, rtol=1e-12)
 
 
+def test_confint_poisson_2indep():
+    # test other confint methods
+
+    count1, exposure1, count2, exposure2 = 60, 51477.5, 30, 54308.7
+
+    ci = confint_poisson_2indep(count1, exposure1, count2, exposure2,
+                                method='mover', compare='ratio', alpha=0.1,
+                                method_mover="jeff",
+                                )
+    ci1 = (1.4667, 3.0608)  # LTW 2014
+    assert_allclose(ci, ci1, atol=0.05)
+    # moverci(60, 51477.5, 30, 54308.7, type = "jeff", distrib="poi",
+    # contrast="RR", cc=0, level=0.9)
+    #   Lower Estimate    Upper
+    # est 1.466768 2.104135 3.058634
+    # ratesci uses different center for mover-jeffreys
+    ci1 = (1.466768, 3.058634)
+    assert_allclose(ci, ci1, rtol=0.001)
+
+    ci = confint_poisson_2indep(count1, exposure1, count2, exposure2,
+                                method='mover', compare='ratio', alpha=0.1,
+                                method_mover="score",
+                                )
+    ci1 = (1.4611, 3.0424)  # LTW 2014
+    assert_allclose(ci, ci1, atol=0.05)
+
+    ci = confint_poisson_2indep(count1, exposure1, count2, exposure2,
+                                method='waldcc', compare='ratio', alpha=0.1,
+                                )
+    ci1 = (1.4523, 3.0154)  # LTW 2014
+    assert_allclose(ci, ci1, atol=0.0005)
+
+    ci = confint_poisson_2indep(count1, exposure1, count2, exposure2,
+                                method='score', compare='ratio', alpha=0.05,
+                                )
+    ci1 = (1.365962, 3.259306)
+    assert_allclose(ci, ci1, atol=5e-6)
+
+    # with diff
+    # use larger rates for diff
+    exposure1 /= 1000
+    exposure2 /= 1000
+    ci = confint_poisson_2indep(count1, exposure1, count2, exposure2,
+                                method='mover', compare='diff', alpha=0.05,
+                                method_mover="jeff",
+                                )
+    # moverci(60, 51477.5/1000, 30, 54308.7/1000, type = "jeff", distrib="poi",
+    # contrast="RD", cc=0, level=0.95)
+    ci1 = (0.2629322, 0.9786493)
+    assert_allclose(ci, ci1, atol=0.005)
+    ci = confint_poisson_2indep(count1, exposure1, count2, exposure2,
+                                method='score', compare='diff', alpha=0.05,
+                                )
+    # scoreci(60, 51477.5/1000, 30, 54308.7/1000, distrib="poi", contrast="RD",
+    # skew=FALSE, cc=0, level=0.95)
+    ci1 = (0.265796, 0.989192)
+    assert_allclose(ci, ci1, atol=5e-6)
+
+    # example in Li et al 2011, scaled
+    ci = confint_poisson_2indep(count2, exposure2, count1, exposure1,
+                                method='mover', compare='diff', alpha=0.1,
+                                method_mover="jeff",
+                                )
+    # moverci(30, 54308.7/1000, 60, 51477.5/1000, type = "jeff", distrib="poi",
+    # contrast="RD", cc=0, level=0.90)
+    ci1 = (-0.9183272231752, -0.3188611692202)
+    assert_allclose(ci, ci1, atol=0.005)
+    ci1 = (-0.9195, -0.3193)  # from Li et al 2011
+    assert_allclose(ci, ci1, atol=0.005)
+
+    ci = confint_poisson_2indep(count2, exposure2, count1, exposure1,
+                                method='mover', compare='diff', alpha=0.1,
+                                method_mover="jeff",
+                                )
+    # scoreci(30, 54308.7/1000, 60, 51477.5/1000, distrib="poi", contrast="RD",
+    # skew=FALSE, cc=0, level=0.90)
+    ci1 = (-0.9232, -0.3188)  # from Li et al 2011
+    assert_allclose(ci, ci1, atol=0.006)
+
+
 def test_tost_poisson():
 
     count1, n1, count2, n2 = 60, 51477.5, 30, 54308.7
@@ -245,10 +677,113 @@ def test_alternative(case):
     alt, meth = case
     count1, n1, count2, n2 = 6, 51., 1, 54.
     _, pv = smr.test_poisson_2indep(count1, n1, count2, n2, method=meth,
-                                    ratio_null=1.2, alternative=alt)
+                                    value=1.2, alternative=alt)
     assert_allclose(pv, cases_alt[case], rtol=1e-13)
 
 
+class TestMethodsCompare2indep():
+    # check that all methods for test and confint produce similar results
+
+    @pytest.mark.parametrize(
+        "compare, meth",
+        [("ratio", meth) for meth
+         in method_names_poisson_2indep["test"]["ratio"]] +
+        [("diff", meth) for meth
+         in method_names_poisson_2indep["test"]["diff"]]
+        )
+    def test_test(self, meth, compare):
+        count1, n1, count2, n2 = 60, 514.775, 40, 543.0870000
+        tst = smr.test_poisson_2indep(count1, n1, count2, n2, method=meth,
+                                      compare=compare,
+                                      value=None, alternative='two-sided')
+
+        assert_allclose(tst.pvalue, 0.0245, rtol=0.2)
+
+        # compare with degenerate interval for nonequivalence
+        if compare == "ratio":
+            f = 1.00
+            low, upp = 1 / f, f
+        else:
+            v = 0.00
+            low, upp = -v, v
+
+        tst2 = nonequivalence_poisson_2indep(count1, n1, count2, n2, low, upp,
+                                             method=meth,
+                                             compare=compare)
+        if "cond" in meth or "etest" in meth:
+            # comparison is not exact for noncentral methods
+            rtol = 0.1
+        else:
+            rtol = 1e-12
+        assert_allclose(tst2.pvalue, tst.pvalue, rtol=rtol)
+
+        # check corner case count2 = 0, see issue #8313
+        with pytest.warns(RuntimeWarning):
+            tst = smr.test_poisson_2indep(count1, n1, 0, n2, method=meth,
+                                          compare=compare,
+                                          value=None, alternative='two-sided')
+
+    @pytest.mark.parametrize(
+        "compare, meth",
+        [("ratio", meth) for meth
+         in method_names_poisson_2indep["confint"]["ratio"]] +
+        [("diff", meth) for meth
+         in method_names_poisson_2indep["confint"]["diff"]]
+        )
+    def test_confint(self, meth, compare):
+        count1, n1, count2, n2 = 60, 514.775, 40, 543.0870000
+        if compare == "ratio":
+            ci_val = [1.04, 2.34]
+        else:
+            ci_val = [0.0057, 0.081]
+
+        ci = confint_poisson_2indep(count1, n1, count2, n2, method=meth,
+                                    compare=compare, alpha=0.05)
+        assert_allclose(ci, ci_val, rtol=0.1)
+
+    @pytest.mark.parametrize(
+        "compare, meth",
+        [("ratio", meth) for meth
+         in method_names_poisson_2indep["test"]["ratio"]] +
+        [("diff", meth) for meth
+         in method_names_poisson_2indep["test"]["diff"]]
+        )
+    def test_test_vectorized(self, meth, compare):
+        if "etest" in meth:
+            pytest.skip("nonequivalence etest not vectorized")
+
+        count1, n1, count2, n2 = 60, 514.775, 40, 543.0870000
+        count1v = np.array([count1, count2])
+        n1v = np.array([n1, n2])
+        nfact = 1.
+        # scipy binom test requires int dtype
+        count2v = np.array([count2, count1 * nfact], dtype=int)
+        n2v = np.array([n2, n1 * nfact])
+        count1, n1, count2, n2 = count1v, n1v, count2v, n2v
+
+        # compare with degenerate interval for nonequivalence
+        if compare == "ratio":
+            f = 1.00
+            low, upp = 1 / f, f
+        else:
+            v = 0.00
+            low, upp = -v, v
+
+        tst2 = nonequivalence_poisson_2indep(count1, n1, count2, n2, low, upp,
+                                             method=meth,
+                                             compare=compare)
+        assert tst2.statistic.shape == (2,)
+        assert tst2.pvalue.shape == (2,)
+
+        if not ("cond" in meth or "etest" in meth):
+            # not all methods are vectorized for smr.test_poisson_2indep
+            tst = smr.test_poisson_2indep(count1, n1, count2, n2, method=meth,
+                                          compare=compare,
+                                          value=None, alternative='two-sided')
+
+            assert_allclose(tst2.pvalue, tst.pvalue, rtol=1e-12)
+
+
 def test_y_grid_regression():
     y_grid = arange(1000)
 
@@ -275,3 +810,313 @@ def test_invalid_y_grid():
     with pytest.raises(ValueError) as e:
         etest_poisson_2indep(1, 1, 1, 1, y_grid=1)
     assert "y_grid" in str(e.value)
+
+
+def test_poisson_power_2ratio():
+    # power compared to PASS documentation
+
+    rate1, rate2 = 2.2, 2.2
+    nobs1, nobs2 = 95, 95
+    # nobs_ratio = 1
+    alpha = 0.025
+    exposure = 2.5
+    low, upp = 0.8, 1.25
+    dispersion = 1
+
+    # check power of equivalence test
+    cases = [
+        (1.9, 704, 704, 0.90012),
+        (2.0, 246, 246, 0.90057),
+        (2.2, 95, 95, 0.90039),
+        (2.5, 396, 396, 0.90045),
+    ]
+
+    for case in cases:
+        rate1, nobs1, nobs2, p = case
+        pow_ = power_equivalence_poisson_2indep(
+            rate1, rate2, nobs1, low, upp,
+            nobs_ratio=nobs2 / nobs1,
+            exposure=exposure, alpha=alpha,
+            dispersion=dispersion)
+        assert_allclose(pow_, p, atol=5e-5)
+
+        # compare other method_var for similar results
+        pow_2 = power_equivalence_poisson_2indep(
+            rate1, rate2, nobs1, low, upp,
+            nobs_ratio=nobs2 / nobs1,
+            exposure=exposure,
+            alpha=alpha,
+            method_var="score",
+            dispersion=dispersion)
+
+        assert_allclose(pow_2, p, rtol=5e-3)
+
+    # check power of onesided test, smaller with a margin
+    # non-inferiority
+    # alternative smaller H1: rate1 / rate2 < R
+    cases = [
+        (1.8, 29, 29, 0.90056),
+        (1.9, 39, 39, 0.90649),
+        (2.2, 115, 115, 0.90014),
+        (2.4, 404, 404, 0.90064),
+    ]
+
+    low = 1.2
+    for case in cases:
+        rate1, nobs1, nobs2, p = case
+        pow_ = power_poisson_ratio_2indep(
+            rate1, rate2, nobs1, nobs_ratio=nobs2 / nobs1,
+            exposure=exposure, value=low, alpha=0.025, dispersion=1,
+            alternative="smaller")
+
+        assert_allclose(pow_, p, atol=5e-5)
+
+        pow_ = power_poisson_ratio_2indep(
+            rate1, rate2, nobs1, nobs_ratio=nobs2 / nobs1,
+            exposure=exposure, value=low, alpha=0.05,
+            dispersion=1, alternative="two-sided")
+        assert_allclose(pow_, p, atol=5e-5)
+
+    # check size, power at null
+    pow_ = power_poisson_ratio_2indep(
+            rate1, rate2, nobs1, nobs_ratio=nobs2 / nobs1,
+            exposure=exposure, value=rate1 / rate2,
+            alpha=0.05, dispersion=1, alternative="two-sided")
+    assert_allclose(pow_, 0.05, atol=5e-5)
+
+    # check power of onesided test, larger with a margin (superiority)
+    # alternative larger H1: rate1 / rate2 > R
+    # here I just reverse the case of smaller alternative
+
+    cases = [
+        (1.8, 29, 29, 0.90056),
+        (1.9, 39, 39, 0.90649),
+        (2.2, 115, 115, 0.90014),
+        (2.4, 404, 404, 0.90064),
+    ]
+
+    rate1 = 2.2
+    low = 1 / 1.2
+    for case in cases:
+        rate2, nobs1, nobs2, p = case
+        pow_ = power_poisson_ratio_2indep(
+            rate1, rate2, nobs1, nobs_ratio=nobs2 / nobs1,
+            exposure=exposure, value=low, alpha=0.025,
+            dispersion=1, alternative="larger")
+        assert_allclose(pow_, p, atol=5e-5)
+
+        # compare other method_var for similar results
+        pow_2 = power_poisson_ratio_2indep(
+            rate1, rate2, nobs1, nobs_ratio=nobs2 / nobs1,
+            exposure=exposure, value=low, alpha=0.025,
+            method_var="score",
+            dispersion=1, alternative="larger")
+        assert_allclose(pow_2, p, rtol=5e-3)
+
+        pow_ = power_poisson_ratio_2indep(
+            rate1, rate2, nobs1, nobs_ratio=nobs2 / nobs1,
+            exposure=exposure, value=low, alpha=0.05,
+            dispersion=1, alternative="two-sided")
+        assert_allclose(pow_, p, atol=5e-5)
+
+        # compare other method_var for similar results
+        pow_2 = power_poisson_ratio_2indep(
+            rate1, rate2, nobs1, nobs_ratio=nobs2 / nobs1,
+            exposure=exposure, value=low, alpha=0.05,
+            method_var="score",
+            dispersion=1, alternative="two-sided")
+        assert_allclose(pow_2, p, rtol=5e-3)
+
+
+def test_power_poisson_equal():
+
+    # Example from Chapter 436: Tests for the Difference Between Two Poisson
+    # Rates
+    # equality null H0: rate1 = rate2
+    #
+    # Power N1 N2 N rate1 rate2 diff(rate2-rate1) ratio(rate2/rate1) Alpha
+    # 0.82566 8 6 14 10.00 15.00 5.00 1.5000 0.050
+    #
+    # "1" is reference group
+    # we use group 2 reference
+
+    nobs1, nobs2 = 6, 8
+    nobs_ratio = nobs2 / nobs1
+    rate1, rate2 = 15, 10
+
+    pow_ = power_poisson_diff_2indep(
+        rate1, rate2, nobs1, nobs_ratio=nobs_ratio, alpha=0.05, value=0,
+        method_var="alt", alternative='larger', return_results=True)
+    assert_allclose(pow_.power, 0.82566, atol=5e-5)
+
+    # This supports now nonzero value
+    # example in table 1 of Stucke, Kieser (2013)
+    # regression numbers but close to exact power in table
+
+    pow_ = power_poisson_diff_2indep(
+        0.6, 0.6, 97, 3 / 2,
+        value=0.3,
+        alpha=0.025,
+        alternative="smaller",
+        method_var="score",
+        return_results=True)
+
+    assert_allclose(pow_.power, 0.802596, atol=5e-5)
+
+    pow_ = power_poisson_diff_2indep(
+        0.6, 0.6, 128, 2 / 3,
+        value=0.3,
+        alpha=0.025,
+        alternative="smaller",
+        method_var="score",
+        return_results=True)
+
+    assert_allclose(pow_.power, 0.80194, atol=5e-5)
+
+
+def test_power_negbin():
+
+    # example from PASS ch. 468, last example Zhu 2016
+    # Note, the table has wrong RU, does not correspond to text
+    rate1, rate2 = 2.5, 2.5
+    nobs1, nobs2 = 965, 965
+    alpha = 0.05
+    exposure = 0.9
+    low, upp = 0.875, 1 / 0.875
+    dispersion = 0.35
+
+    pow1 = 0.90022
+    pow_ = power_equivalence_neginb_2indep(
+        rate1, rate2, nobs1, low, upp,
+        nobs_ratio=nobs2 / nobs1,
+        exposure=exposure,
+        alpha=alpha,
+        dispersion=dispersion, method_var="alt")
+    assert_allclose(pow_, pow1, atol=5e-5)
+
+    nobs1, nobs2 = 966, 966
+
+    pow1 = 0.90015
+    pow_ = power_equivalence_neginb_2indep(
+        rate1, rate2, nobs1, low, upp,
+        nobs_ratio=nobs2 / nobs1,
+        exposure=exposure,
+        alpha=alpha,
+        dispersion=dispersion, method_var="ftotal")
+    assert_allclose(pow_, pow1, atol=5e-5)
+
+    pow1 = 0.90034
+    pow_ = power_equivalence_neginb_2indep(
+        rate1, rate2, nobs1, low, upp,
+        nobs_ratio=nobs2 / nobs1,
+        exposure=exposure,
+        alpha=alpha,
+        dispersion=dispersion, method_var="score")
+    assert_allclose(pow_, pow1, atol=5e-5)
+
+    # test with unequal sample size agains R
+    # R packages MKmisc and PASSED have only equality test, ratio=1, for negbin
+    # package PASSED has same results as MKmisc but different signature
+    # in R: treatment and control are reversed, theta = 1 / dispersion
+    # > power_NegativeBinomial(n1 = 100, n2=50, mu1 = 0.5, mu2 = 0.3, theta=2,
+    #   duration = 2, approach = 3))
+
+    rate2, nobs2, rate1, nobs1, exposure = 0.3, 50, 0.5, 100, 2
+    pow1 = 0.6207448
+    pow_ = power_negbin_ratio_2indep(
+        rate2, rate1, nobs2,
+        nobs_ratio=nobs1 / nobs2,
+        exposure=exposure,
+        value=1,
+        alpha=alpha, dispersion=0.5,
+        alternative="two-sided",
+        method_var="score",
+        return_results=False,
+        )
+    assert_allclose(pow_, pow1, atol=5e-2)
+
+    # flip nobs ratio
+    pow1 = 0.5825763
+    nobs1, nobs2 = nobs2, nobs1
+    pow_ = power_negbin_ratio_2indep(
+        rate2, rate1, nobs2,
+        nobs_ratio=nobs1 / nobs2,
+        exposure=exposure,
+        value=1,
+        alpha=alpha, dispersion=0.5,
+        alternative="two-sided",
+        method_var="score",
+        return_results=False)
+    assert_allclose(pow_, pow1, atol=5e-2)
+
+    # corner case poisson
+    # > power_NegativeBinomial(n1 = 50, n2=100, mu1 = 0.5, mu2 = 0.3,
+    #   theta=200000, duration = 2, approach = 3)
+    pow1 = 0.7248956
+    pow_ = power_negbin_ratio_2indep(
+        rate2, rate1, nobs2,
+        nobs_ratio=nobs1 / nobs2,
+        exposure=exposure,
+        value=1,
+        alpha=alpha, dispersion=0, alternative="two-sided",
+        method_var="score",
+        return_results=False)
+
+    assert_allclose(pow_, pow1, atol=5e-2)
+
+    pow_p = power_poisson_ratio_2indep(
+        rate2, rate1, nobs2,
+        nobs_ratio=nobs1 / nobs2,
+        exposure=exposure,
+        value=1,
+        alpha=alpha, dispersion=1, alternative="two-sided",
+        method_var="score",
+        return_results=True)
+
+    assert_allclose(pow_p, pow1, atol=5e-2)
+    assert_allclose(pow_p, pow_, rtol=1e-13)
+
+    # test one-sided
+    pow1 = 0.823889
+    pow_ = power_negbin_ratio_2indep(
+        rate2, rate1, nobs2,
+        nobs_ratio=nobs1 / nobs2,
+        exposure=exposure,
+        value=1,
+        alpha=alpha, dispersion=0, alternative="smaller",
+        method_var="score",
+        return_results=False)
+
+    pow_p = power_poisson_ratio_2indep(
+        rate2, rate1, nobs2,
+        nobs_ratio=nobs1 / nobs2,
+        exposure=exposure,
+        value=1,
+        alpha=alpha, dispersion=1, alternative="smaller",
+        method_var="score",
+        return_results=True)
+
+    assert_allclose(pow_p, pow1, atol=5e-2)
+    assert_allclose(pow_p, pow_, rtol=1e-13)
+
+    # reverse 2 samples
+    pow_ = power_negbin_ratio_2indep(
+        rate1, rate2, nobs1,
+        nobs_ratio=nobs2 / nobs1,
+        exposure=exposure,
+        value=1,
+        alpha=alpha, dispersion=0, alternative="larger",
+        method_var="score",
+        return_results=False)
+
+    pow_p = power_poisson_ratio_2indep(
+        rate1, rate2, nobs1,
+        nobs_ratio=nobs2 / nobs1,
+        exposure=exposure,
+        value=1,
+        alpha=alpha, dispersion=1, alternative="larger",
+        method_var="score",
+        return_results=True)
+
+    assert_allclose(pow_p, pow1, atol=5e-2)
+    assert_allclose(pow_p, pow_, rtol=1e-13)