Repository: astropy/astropy

Issue:
Inconsistent keyword names for binom_conf_interval and poisson_conf_interval
The functions ``binom_conf_interval`` and ``poisson_conf_interval`` in astropy.stats both have a keyword argument for the confidence level, but one is called ``conf`` and the other one ``conflevel``. Can we consider renaming the most recent one via a deprecation phase, or have both functions been around for too long?

Discussion:
astrofrog: Just for the record I don't really mind which way we change it - I can see arguments both ways. ``binom_conf_interval`` has been around for seven years though so it might have more users. ``poisson_conf_interval`` has been around for four years.

In fact, because the functions have both been around for a bit (which I hadn't realized) I think this PR should definitely go past the astropy.stats maintainers @stscicrawford and @larrybradley. We might want to consider this to be a case where we deprecate but don't remove for a long long time (if ever).
astrofrog: Also pinging @aarchiba as the original author of ``poisson_conf_interval``!
aarchiba: > Also pinging @aarchiba as the original author of `poisson_conf_interval`!

I think I wrote the binomial confidence interval too so it would be my fault they disagree. I'm starting to prefer more explicit names like `confidence_level`, especially as it's unlikely you're going to be typing this every third line.
crawfordsm: I probably would go with something more explicit like `confidence_level` as well as that would no longer be ambiguous.   I have no objection to depreciating the keywords in 4.0 and that it is a good time to do it.   

A quick search reveals two other functions that use a variation of confidence level that probable should make it more consistent throughout the package:
* jackknife_stats
* binned_binom_proportion

Thanks for the PR @pllim !
larrybradley: I also would prefer a more explicit name like `confidence_level`.  I would deprecate the old keywords, but given their long-time usage we should keep them around for next several releases.

+1 for making the keyword consistent across the package.
bsipocz: OK, then my proposal is to deprecate in 4.0 LTS, and revisit the removal in 5.0 LTS in two years time. Hopefully by that time we'll have better tools to track usage and notify folks about updating deprecated usages.
astrofrog: Sounds good to me!
pllim: I thought it was gonna be a simple one to complete during a short break... 😭 
pllim: I think I have implemented all your requests. I also threw in a bonus of warning catching for the tests.

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index e399e6a3aa..9e4c13e186 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -364,6 +364,18 @@ astropy.stats
 - Renamed the ``a`` parameter to ``data`` in ``median_absolute_deviation``.
   [#9011]
 
+- Renamed the ``conflevel`` keyword to ``confidence_level`` in
+  ``poisson_conf_interval``. Usage of ``conflevel`` now issues
+  ``AstropyDeprecationWarning``. [#9408]
+
+- Renamed the ``conf`` keyword to ``confidence_level`` in
+  ``binom_conf_interval`` and ``binned_binom_proportion``. Usage of ``conf``
+  now issues ``AstropyDeprecationWarning``. [#9408]
+
+- Renamed the ``conf_lvl`` keyword to ``confidence_level`` in
+  ``jackknife_stats``. Usage of ``conf_lvl`` now issues
+  ``AstropyDeprecationWarning``. [#9408]
+
 astropy.table
 ^^^^^^^^^^^^^
 
@@ -499,9 +511,6 @@ astropy.io.misc
 astropy.io.fits
 ^^^^^^^^^^^^^^^
 
-- Implemented skip (after warning) of header cards with reserved
-  keywords in ``table_to_hdu``. [#9390]
-
 astropy.io.registry
 ^^^^^^^^^^^^^^^^^^^
 
diff --git a/astropy/io/fits/convenience.py b/astropy/io/fits/convenience.py
index 8cb1dba6a7..bdf365fd51 100644
--- a/astropy/io/fits/convenience.py
+++ b/astropy/io/fits/convenience.py
@@ -460,8 +460,8 @@ def table_to_hdu(table, character_as_bytes=False):
         unsupported_cols = table.columns.not_isinstance((BaseColumn, Quantity, Time))
         if unsupported_cols:
             unsupported_names = [col.info.name for col in unsupported_cols]
-            raise ValueError(f'cannot write table with mixin column(s) '
-                             f'{unsupported_names}')
+            raise ValueError('cannot write table with mixin column(s) {}'
+                         .format(unsupported_names))
 
         time_cols = table.columns.isinstance(Time)
         if time_cols:
@@ -519,17 +519,17 @@ def table_to_hdu(table, character_as_bytes=False):
             except UnitScaleError:
                 scale = unit.scale
                 raise UnitScaleError(
-                    f"The column '{col.name}' could not be stored in FITS "
-                    f"format because it has a scale '({str(scale)})' that "
-                    f"is not recognized by the FITS standard. Either scale "
-                    f"the data or change the units.")
+                    "The column '{}' could not be stored in FITS format "
+                    "because it has a scale '({})' that "
+                    "is not recognized by the FITS standard. Either scale "
+                    "the data or change the units.".format(col.name, str(scale)))
             except ValueError:
                 # Warn that the unit is lost, but let the details depend on
                 # whether the column was serialized (because it was a
                 # quantity), since then the unit can be recovered by astropy.
                 warning = (
-                    f"The unit '{unit.to_string()}' could not be saved in "
-                    f"native FITS format ")
+                    "The unit '{}' could not be saved in native FITS format "
+                    .format(unit.to_string()))
                 if any('SerializedColumn' in item and 'name: '+col.name in item
                        for item in table.meta.get('comments', [])):
                     warning += (
@@ -564,9 +564,8 @@ def table_to_hdu(table, character_as_bytes=False):
     for key, value in table.meta.items():
         if is_column_keyword(key.upper()) or key.upper() in REMOVE_KEYWORDS:
             warnings.warn(
-                f"Meta-data keyword {key} will be ignored since it conflicts "
-                f"with a FITS reserved keyword", AstropyUserWarning)
-            continue
+                "Meta-data keyword {} will be ignored since it conflicts "
+                "with a FITS reserved keyword".format(key), AstropyUserWarning)
 
         # Convert to FITS format
         if key == 'comments':
@@ -578,15 +577,17 @@ def table_to_hdu(table, character_as_bytes=False):
                     table_hdu.header.append((key, item))
                 except ValueError:
                     warnings.warn(
-                        f"Attribute `{key}` of type {type(value)} cannot be "
-                        f"added to FITS Header - skipping", AstropyUserWarning)
+                        "Attribute `{}` of type {} cannot be added to "
+                        "FITS Header - skipping".format(key, type(value)),
+                        AstropyUserWarning)
         else:
             try:
                 table_hdu.header[key] = value
             except ValueError:
                 warnings.warn(
-                    f"Attribute `{key}` of type {type(value)} cannot be "
-                    f"added to FITS Header - skipping", AstropyUserWarning)
+                    "Attribute `{}` of type {} cannot be added to FITS "
+                    "Header - skipping".format(key, type(value)),
+                    AstropyUserWarning)
     return table_hdu
 
 
diff --git a/astropy/stats/funcs.py b/astropy/stats/funcs.py
index 679e6506b1..c2929a7d58 100644
--- a/astropy/stats/funcs.py
+++ b/astropy/stats/funcs.py
@@ -13,6 +13,7 @@ import math
 
 import numpy as np
 
+from astropy.utils.decorators import deprecated_renamed_argument
 from . import _stats
 
 __all__ = ['gaussian_fwhm_to_sigma', 'gaussian_sigma_to_fwhm',
@@ -87,7 +88,8 @@ def _expand_dims(data, axis):
 
 
 # TODO Note scipy dependency
-def binom_conf_interval(k, n, conf=0.68269, interval='wilson'):
+@deprecated_renamed_argument('conf', 'confidence_level', '4.0')
+def binom_conf_interval(k, n, confidence_level=0.68269, interval='wilson'):
     r"""Binomial proportion confidence interval given k successes,
     n trials.
 
@@ -98,7 +100,7 @@ def binom_conf_interval(k, n, conf=0.68269, interval='wilson'):
     n : int or numpy.ndarray
         Number of trials (``n`` > 0).  If both ``k`` and ``n`` are arrays,
         they must have the same shape.
-    conf : float in [0, 1], optional
+    confidence_level : float in [0, 1], optional
         Desired probability content of interval. Default is 0.68269,
         corresponding to 1 sigma in a 1-dimensional Gaussian distribution.
     interval : {'wilson', 'jeffreys', 'flat', 'wald'}, optional
@@ -242,15 +244,14 @@ def binom_conf_interval(k, n, conf=0.68269, interval='wilson'):
     For confidence intervals approaching 1, the Wald interval for
     0 < k < N can give intervals that extend outside [0, 1]:
 
-    >>> binom_conf_interval([0, 1, 2, 5], 5, interval='wald', conf=0.99)
+    >>> binom_conf_interval([0, 1, 2, 5], 5, interval='wald', confidence_level=0.99)
     array([[ 0.        , -0.26077835, -0.16433593,  1.        ],
            [ 0.        ,  0.66077835,  0.96433593,  1.        ]])
 
     """
-
-    if conf < 0. or conf > 1.:
-        raise ValueError('conf must be between 0. and 1.')
-    alpha = 1. - conf
+    if confidence_level < 0. or confidence_level > 1.:
+        raise ValueError('confidence_level must be between 0. and 1.')
+    alpha = 1. - confidence_level
 
     k = np.asarray(k).astype(int)
     n = np.asarray(n).astype(int)
@@ -262,7 +263,7 @@ def binom_conf_interval(k, n, conf=0.68269, interval='wilson'):
 
     if interval == 'wilson' or interval == 'wald':
         from scipy.special import erfinv
-        kappa = np.sqrt(2.) * min(erfinv(conf), 1.e10)  # Avoid overflows.
+        kappa = np.sqrt(2.) * min(erfinv(confidence_level), 1.e10)  # Avoid overflows.
         k = k.astype(float)
         n = n.astype(float)
         p = k / n
@@ -313,8 +314,9 @@ def binom_conf_interval(k, n, conf=0.68269, interval='wilson'):
 
 
 # TODO Note scipy dependency (needed in binom_conf_interval)
-def binned_binom_proportion(x, success, bins=10, range=None, conf=0.68269,
-                            interval='wilson'):
+@deprecated_renamed_argument('conf', 'confidence_level', '4.0')
+def binned_binom_proportion(x, success, bins=10, range=None,
+                            confidence_level=0.68269, interval='wilson'):
     """Binomial proportion and confidence interval in bins of a continuous
     variable ``x``.
 
@@ -340,7 +342,7 @@ def binned_binom_proportion(x, success, bins=10, range=None, conf=0.68269,
         The lower and upper range of the bins. If `None` (default),
         the range is set to ``(x.min(), x.max())``. Values outside the
         range are ignored.
-    conf : float in [0, 1], optional
+    confidence_level : float in [0, 1], optional
         Desired probability content in the confidence
         interval ``(p - perr[0], p + perr[1])`` in each bin. Default is
         0.68269.
@@ -373,7 +375,6 @@ def binned_binom_proportion(x, success, bins=10, range=None, conf=0.68269,
     binom_conf_interval : Function used to estimate confidence interval in
                           each bin.
 
-
     Examples
     --------
     Suppose we wish to estimate the efficiency of a survey in
@@ -463,7 +464,6 @@ def binned_binom_proportion(x, success, bins=10, range=None, conf=0.68269,
        plt.show()
 
     """
-
     x = np.ravel(x)
     success = np.ravel(success).astype(bool)
     if x.shape != success.shape:
@@ -484,26 +484,27 @@ def binned_binom_proportion(x, success, bins=10, range=None, conf=0.68269,
     k = k[valid]
 
     p = k / n
-    bounds = binom_conf_interval(k, n, conf=conf, interval=interval)
+    bounds = binom_conf_interval(k, n, confidence_level=confidence_level, interval=interval)
     perr = np.abs(bounds - p)
 
     return bin_ctr, bin_halfwidth, p, perr
 
 
-def _check_poisson_conf_inputs(sigma, background, conflevel, name):
+def _check_poisson_conf_inputs(sigma, background, confidence_level, name):
     if sigma != 1:
         raise ValueError("Only sigma=1 supported for interval {}"
                          .format(name))
     if background != 0:
         raise ValueError("background not supported for interval {}"
                          .format(name))
-    if conflevel is not None:
-        raise ValueError("conflevel not supported for interval {}"
+    if confidence_level is not None:
+        raise ValueError("confidence_level not supported for interval {}"
                          .format(name))
 
 
+@deprecated_renamed_argument('conflevel', 'confidence_level', '4.0')
 def poisson_conf_interval(n, interval='root-n', sigma=1, background=0,
-                          conflevel=None):
+                          confidence_level=None):
     r"""Poisson parameter confidence interval given observed counts
 
     Parameters
@@ -520,7 +521,7 @@ def poisson_conf_interval(n, interval='root-n', sigma=1, background=0,
         Number of counts expected from the background; only supported for
         the 'kraft-burrows-nousek' mode. This number is assumed to be determined
         from a large region so that the uncertainty on its value is negligible.
-    conflevel : float, optional
+    confidence_level : float, optional
         Confidence level between 0 and 1; only supported for the
         'kraft-burrows-nousek' mode.
 
@@ -680,8 +681,8 @@ def poisson_conf_interval(n, interval='root-n', sigma=1, background=0,
            [  5.62771868,  11.37228132],
            [  6.45861873,  12.54138127]])
 
-    >>> poisson_conf_interval(np.arange(10),
-    ...                       interval='frequentist-confidence').T
+    >>> poisson_conf_interval(
+    ...     np.arange(10), interval='frequentist-confidence').T
     array([[  0.        ,   1.84102165],
            [  0.17275378,   3.29952656],
            [  0.70818544,   4.63785962],
@@ -693,12 +694,13 @@ def poisson_conf_interval(n, interval='root-n', sigma=1, background=0,
            [  5.23161394,  11.94514152],
            [  6.05653896,  13.11020414]])
 
-    >>> poisson_conf_interval(7,
-    ...                       interval='frequentist-confidence').T
+    >>> poisson_conf_interval(
+    ...     7, interval='frequentist-confidence').T
     array([  4.41852954,  10.77028072])
 
-    >>> poisson_conf_interval(10, background=1.5, conflevel=0.95,
-    ...                       interval='kraft-burrows-nousek').T  # doctest: +FLOAT_CMP
+    >>> poisson_conf_interval(
+    ...     10, background=1.5, confidence_level=0.95,
+    ...     interval='kraft-burrows-nousek').T  # doctest: +FLOAT_CMP
     array([[ 3.47894005, 16.113329533]])
 
     """
@@ -707,11 +709,11 @@ def poisson_conf_interval(n, interval='root-n', sigma=1, background=0,
         n = np.asanyarray(n)
 
     if interval == 'root-n':
-        _check_poisson_conf_inputs(sigma, background, conflevel, interval)
+        _check_poisson_conf_inputs(sigma, background, confidence_level, interval)
         conf_interval = np.array([n - np.sqrt(n),
                                   n + np.sqrt(n)])
     elif interval == 'root-n-0':
-        _check_poisson_conf_inputs(sigma, background, conflevel, interval)
+        _check_poisson_conf_inputs(sigma, background, confidence_level, interval)
         conf_interval = np.array([n - np.sqrt(n),
                                   n + np.sqrt(n)])
         if np.isscalar(n):
@@ -720,15 +722,15 @@ def poisson_conf_interval(n, interval='root-n', sigma=1, background=0,
         else:
             conf_interval[1, n == 0] = 1
     elif interval == 'pearson':
-        _check_poisson_conf_inputs(sigma, background, conflevel, interval)
+        _check_poisson_conf_inputs(sigma, background, confidence_level, interval)
         conf_interval = np.array([n + 0.5 - np.sqrt(n + 0.25),
                                   n + 0.5 + np.sqrt(n + 0.25)])
     elif interval == 'sherpagehrels':
-        _check_poisson_conf_inputs(sigma, background, conflevel, interval)
+        _check_poisson_conf_inputs(sigma, background, confidence_level, interval)
         conf_interval = np.array([n - 1 - np.sqrt(n + 0.75),
                                   n + 1 + np.sqrt(n + 0.75)])
     elif interval == 'frequentist-confidence':
-        _check_poisson_conf_inputs(1., background, conflevel, interval)
+        _check_poisson_conf_inputs(1., background, confidence_level, interval)
         import scipy.stats
         alpha = scipy.stats.norm.sf(sigma)
         conf_interval = np.array([0.5 * scipy.stats.chi2(2 * n).ppf(alpha),
@@ -739,17 +741,17 @@ def poisson_conf_interval(n, interval='root-n', sigma=1, background=0,
         else:
             conf_interval[0, n == 0] = 0
     elif interval == 'kraft-burrows-nousek':
-        if conflevel is None:
-            raise ValueError('Set conflevel for method {}. (sigma is '
+        if confidence_level is None:
+            raise ValueError('Set confidence_level for method {}. (sigma is '
                              'ignored.)'.format(interval))
-        conflevel = np.asanyarray(conflevel)
-        if np.any(conflevel <= 0) or np.any(conflevel >= 1):
-            raise ValueError('Conflevel must be a number between 0 and 1.')
+        confidence_level = np.asanyarray(confidence_level)
+        if np.any(confidence_level <= 0) or np.any(confidence_level >= 1):
+            raise ValueError('confidence_level must be a number between 0 and 1.')
         background = np.asanyarray(background)
         if np.any(background < 0):
             raise ValueError('Background must be >= 0.')
         conf_interval = np.vectorize(_kraft_burrows_nousek,
-                                     cache=True)(n, background, conflevel)
+                                     cache=True)(n, background, confidence_level)
         conf_interval = np.vstack(conf_interval)
     else:
         raise ValueError("Invalid method for Poisson confidence intervals: "
diff --git a/astropy/stats/jackknife.py b/astropy/stats/jackknife.py
index 562770d664..9eb14eae1c 100644
--- a/astropy/stats/jackknife.py
+++ b/astropy/stats/jackknife.py
@@ -2,6 +2,7 @@
 
 import numpy as np
 
+from astropy.utils.decorators import deprecated_renamed_argument
 
 __all__ = ['jackknife_resampling', 'jackknife_stats']
 __doctest_requires__ = {'jackknife_stats': ['scipy']}
@@ -39,7 +40,7 @@ def jackknife_resampling(data):
         Stanford University, December, 1980.
 
     .. [3] Jackknife resampling <https://en.wikipedia.org/wiki/Jackknife_resampling>
-    """
+    """  # noqa
 
     n = data.shape[0]
     if n <= 0:
@@ -53,7 +54,8 @@ def jackknife_resampling(data):
     return resamples
 
 
-def jackknife_stats(data, statistic, conf_lvl=0.95):
+@deprecated_renamed_argument('conf_lvl', 'confidence_level', '4.0')
+def jackknife_stats(data, statistic, confidence_level=0.95):
     """ Performs jackknife estimation on the basis of jackknife resamples.
 
     This function requires `SciPy <https://www.scipy.org/>`_ to be installed.
@@ -66,7 +68,7 @@ def jackknife_stats(data, statistic, conf_lvl=0.95):
         Any function (or vector of functions) on the basis of the measured
         data, e.g, sample mean, sample variance, etc. The jackknife estimate of
         this statistic will be returned.
-    conf_lvl : float, optional
+    confidence_level : float, optional
         Confidence level for the confidence interval of the Jackknife estimate.
         Must be a real-valued number in (0,1). Default value is 0.95.
 
@@ -143,14 +145,18 @@ def jackknife_stats(data, statistic, conf_lvl=0.95):
 
     IMPORTANT: Note that confidence intervals are given as columns
     """
-
-    from scipy.special import erfinv
+    # jackknife confidence interval
+    if not (0 < confidence_level < 1):
+        raise ValueError("confidence level must be in (0, 1).")
 
     # make sure original data is proper
     n = data.shape[0]
     if n <= 0:
         raise ValueError("data must contain at least one measurement.")
 
+    # Only import scipy if inputs are valid
+    from scipy.special import erfinv
+
     resamples = jackknife_resampling(data)
 
     stat_data = statistic(data)
@@ -167,11 +173,7 @@ def jackknife_stats(data, statistic, conf_lvl=0.95):
     # bias-corrected "jackknifed estimate"
     estimate = stat_data - bias
 
-    # jackknife confidence interval
-    if not (0 < conf_lvl < 1):
-        raise ValueError("confidence level must be in (0, 1).")
-
-    z_score = np.sqrt(2.0)*erfinv(conf_lvl)
+    z_score = np.sqrt(2.0)*erfinv(confidence_level)
     conf_interval = estimate + z_score*np.array((-std_err, std_err))
 
     return estimate, bias, std_err, conf_interval


Test patch:
diff --git a/astropy/io/fits/tests/test_convenience.py b/astropy/io/fits/tests/test_convenience.py
index 44801dd762..28dd59b87d 100644
--- a/astropy/io/fits/tests/test_convenience.py
+++ b/astropy/io/fits/tests/test_convenience.py
@@ -12,10 +12,8 @@ from astropy import units as u
 from astropy.table import Table
 from astropy.io.fits import printdiff
 from astropy.tests.helper import catch_warnings
-from astropy.utils.exceptions import AstropyUserWarning
 
 from . import FitsTestCase
-from ..connect import REMOVE_KEYWORDS
 
 
 class TestConvenience(FitsTestCase):
@@ -91,77 +89,6 @@ class TestConvenience(FitsTestCase):
         with pytest.raises(ValueError):
             hdu.header.index('comments')
 
-    def test_table_to_hdu_filter_reserved(self):
-        """
-        Regression test for https://github.com/astropy/astropy/issues/9387
-        """
-        diag = 'be ignored since it conflicts with a FITS reserved keyword'
-        ins_cards = {'EXPTIME': 32.1, 'XTENSION': 'NEWTABLE',
-                     'NAXIS': 1, 'NAXIS1': 3, 'NAXIS2': 9,
-                     'PCOUNT': 42, 'OBSERVER': 'Adams'}
-        table = Table([[1, 2, 3], ['a', 'b', 'c'], [2.3, 4.5, 6.7]],
-                      names=['a', 'b', 'c'], dtype=['i4', 'U1', 'f8'])
-        table.meta.update(ins_cards)
-
-        with pytest.warns(AstropyUserWarning,
-                          match=rf'Meta-data keyword \w+ will {diag}') as w:
-            hdu = fits.table_to_hdu(table)
-
-        # This relies on the warnings being raised in the order of the
-        # meta dict (note that the first and last card are legitimate keys)
-        assert len(w) == len(ins_cards) - 2
-        for i, key in enumerate(list(ins_cards)[1:-1]):
-            assert f'Meta-data keyword {key}' in str(w[i].message)
-
-        assert hdu.header.get('XTENSION') == 'BINTABLE'
-        assert hdu.header.get('NAXIS') == 2
-        assert hdu.header.get('NAXIS1') == 13
-        assert hdu.header.get('NAXIS2') == 3
-        assert hdu.header.get('PCOUNT') == 0
-        np.testing.assert_almost_equal(hdu.header.get('EXPTIME'), 3.21e1)
-
-    @pytest.mark.parametrize('card', REMOVE_KEYWORDS)
-    def test_table_to_hdu_warn_reserved(self, card):
-        """
-        Test warning for each keyword in ..connect.REMOVE_KEYWORDS, 1 by 1
-        """
-        diag = 'be ignored since it conflicts with a FITS reserved keyword'
-        res_cards = {'XTENSION': 'BINTABLE', 'BITPIX': 8,
-                     'NAXIS': 2, 'NAXIS1': 12, 'NAXIS2': 3,
-                     'PCOUNT': 0, 'GCOUNT': 1, 'TFIELDS': 2, 'THEAP': None}
-        ins_cards = {'XTENSION': 'TABLE', 'BITPIX': 16,
-                     'NAXIS': 1, 'NAXIS1': 2, 'NAXIS2': 6,
-                     'PCOUNT': 2, 'GCOUNT': 2, 'TFIELDS': 4, 'THEAP': 36}
-
-        table = Table([[1.0, 2.0, 3.0], [2.3, 4.5, 6.7]],
-                      names=['wavelength', 'flux'], dtype=['f8', 'f4'])
-        table.meta['ORIGIN'] = 'Min.Silly Walks'
-        table.meta[card] = ins_cards[card]
-        assert table.meta.get(card) != res_cards[card]
-
-        with pytest.warns(AstropyUserWarning,
-                          match=f'Meta-data keyword {card} will {diag}'):
-            hdu = fits.table_to_hdu(table)
-
-        assert hdu.header.get(card) == res_cards[card]
-        assert hdu.header.get('ORIGIN') == 'Min.Silly Walks'
-
-    def test_table_to_hdu_filter_incompatible(self):
-        """
-        Test removal of unsupported data types from header
-        """
-        table = Table([[1, 2, 3], ['a', 'b', 'c'], [2.3, 4.5, 6.7]],
-                      names=['a', 'b', 'c'], dtype=['i4', 'U1', 'f8'])
-        table.meta.update({'OBSDATE': '2001-05-26', 'RAMP': np.arange(5),
-                           'TARGETS': {'PRIMARY': 1, 'SECONDAR': 3}})
-        with pytest.warns(AstropyUserWarning, match=r'Attribute \S+ of type '
-                          r'.+ cannot be added to FITS Header - skipping'):
-            hdu = fits.table_to_hdu(table)
-
-        assert hdu.header.get('OBSDATE') == '2001-05-26'
-        assert 'RAMP' not in hdu.header
-        assert 'TARGETS' not in hdu.header
-
     def test_table_writeto_header(self):
         """
         Regression test for https://github.com/astropy/astropy/issues/5988
diff --git a/astropy/stats/tests/test_bayesian_blocks.py b/astropy/stats/tests/test_bayesian_blocks.py
index cd69000aca..5b7b4bf754 100644
--- a/astropy/stats/tests/test_bayesian_blocks.py
+++ b/astropy/stats/tests/test_bayesian_blocks.py
@@ -6,6 +6,7 @@ import numpy as np
 from numpy.testing import assert_allclose
 
 from astropy.stats import bayesian_blocks, RegularEvents
+from astropy.utils.exceptions import AstropyUserWarning
 
 
 def test_single_change_point(rseed=0):
@@ -13,7 +14,9 @@ def test_single_change_point(rseed=0):
     x = np.concatenate([rng.rand(100),
                         1 + rng.rand(200)])
 
-    bins = bayesian_blocks(x)
+    with pytest.warns(AstropyUserWarning,
+                      match=r'p0 does not seem to accurate'):
+        bins = bayesian_blocks(x)
 
     assert (len(bins) == 3)
     assert_allclose(bins[1], 1, rtol=0.02)
@@ -27,8 +30,10 @@ def test_duplicate_events(rseed=0):
     x = np.ones_like(t)
     x[:20] += 1
 
-    bins1 = bayesian_blocks(t)
-    bins2 = bayesian_blocks(t[:80], x[:80])
+    with pytest.warns(AstropyUserWarning,
+                      match=r'p0 does not seem to accurate'):
+        bins1 = bayesian_blocks(t)
+        bins2 = bayesian_blocks(t[:80], x[:80])
 
     assert_allclose(bins1, bins2)
 
@@ -84,33 +89,47 @@ def test_errors():
 
     # x must be integer or None for events
     with pytest.raises(ValueError):
-        bayesian_blocks(t, fitness='events', x=t)
+        with pytest.warns(AstropyUserWarning,
+                          match=r'p0 does not seem to accurate'):
+            bayesian_blocks(t, fitness='events', x=t)
 
     # x must be binary for regular events
     with pytest.raises(ValueError):
-        bayesian_blocks(t, fitness='regular_events', x=10 * t, dt=1)
+        with pytest.warns(AstropyUserWarning,
+                          match=r'p0 does not seem to accurate'):
+            bayesian_blocks(t, fitness='regular_events', x=10 * t, dt=1)
 
     # x must be specified for measures
     with pytest.raises(ValueError):
-        bayesian_blocks(t, fitness='measures')
+        with pytest.warns(AstropyUserWarning,
+                          match=r'p0 does not seem to accurate'):
+            bayesian_blocks(t, fitness='measures')
 
     # sigma cannot be specified without x
     with pytest.raises(ValueError):
-        bayesian_blocks(t, fitness='events', sigma=0.5)
+        with pytest.warns(AstropyUserWarning,
+                          match=r'p0 does not seem to accurate'):
+            bayesian_blocks(t, fitness='events', sigma=0.5)
 
     # length of x must match length of t
     with pytest.raises(ValueError):
-        bayesian_blocks(t, fitness='measures', x=t[:-1])
+        with pytest.warns(AstropyUserWarning,
+                          match=r'p0 does not seem to accurate'):
+            bayesian_blocks(t, fitness='measures', x=t[:-1])
 
     # repeated values in t fail when x is specified
     t2 = t.copy()
     t2[1] = t2[0]
     with pytest.raises(ValueError):
-        bayesian_blocks(t2, fitness='measures', x=t)
+        with pytest.warns(AstropyUserWarning,
+                          match=r'p0 does not seem to accurate'):
+            bayesian_blocks(t2, fitness='measures', x=t)
 
     # sigma must be broadcastable with x
     with pytest.raises(ValueError):
-        bayesian_blocks(t, fitness='measures', x=t, sigma=t[:-1])
+        with pytest.warns(AstropyUserWarning,
+                          match=r'p0 does not seem to accurate'):
+            bayesian_blocks(t, fitness='measures', x=t, sigma=t[:-1])
 
 
 def test_fitness_function_results():
@@ -119,12 +138,16 @@ def test_fitness_function_results():
 
     # Event Data
     t = rng.randn(100)
-    edges = bayesian_blocks(t, fitness='events')
+    with pytest.warns(AstropyUserWarning,
+                      match=r'p0 does not seem to accurate'):
+        edges = bayesian_blocks(t, fitness='events')
     assert_allclose(edges, [-2.6197451, -0.71094865, 0.36866702, 1.85227818])
 
     # Event data with repeats
     t[80:] = t[:20]
-    edges = bayesian_blocks(t, fitness='events', p0=0.01)
+    with pytest.warns(AstropyUserWarning,
+                      match=r'p0 does not seem to accurate'):
+        edges = bayesian_blocks(t, fitness='events', p0=0.01)
     assert_allclose(edges, [-2.6197451, -0.47432431, -0.46202823, 1.85227818])
 
     # Regular event data
@@ -165,10 +188,15 @@ def test_fitness_function_results():
 
 
 def test_zero_change_points(rseed=0):
-    ''' Ensure that edges contains both endpoints when there are no change points '''
+    """
+    Ensure that edges contains both endpoints when there are no change points
+    """
     np.random.seed(rseed)
-    # Using the failed edge case from https://github.com/astropy/astropy/issues/8558
+    # Using the failed edge case from
+    # https://github.com/astropy/astropy/issues/8558
     values = np.array([1, 1, 1, 1, 1, 1, 1, 1, 2])
-    bins = bayesian_blocks(values)
+    with pytest.warns(AstropyUserWarning,
+                      match=r'p0 does not seem to accurate'):
+        bins = bayesian_blocks(values)
     assert values.min() == bins[0]
     assert values.max() == bins[-1]
diff --git a/astropy/stats/tests/test_funcs.py b/astropy/stats/tests/test_funcs.py
index 20c0712a4f..2a1916ca0f 100644
--- a/astropy/stats/tests/test_funcs.py
+++ b/astropy/stats/tests/test_funcs.py
@@ -6,14 +6,14 @@ import numpy as np
 from numpy.testing import assert_equal, assert_allclose
 
 try:
-    import scipy  # pylint: disable=W0611
+    import scipy  # pylint: disable=W0611 # noqa
 except ImportError:
     HAS_SCIPY = False
 else:
     HAS_SCIPY = True
 
 try:
-    import mpmath  # pylint: disable=W0611
+    import mpmath  # pylint: disable=W0611 # noqa
 except ImportError:
     HAS_MPMATH = False
 else:
@@ -22,6 +22,7 @@ else:
 from astropy.stats import funcs
 from astropy import units as u
 from astropy.tests.helper import catch_warnings
+from astropy.utils.exceptions import AstropyDeprecationWarning
 from astropy.utils.misc import NumpyRNGContext
 
 
@@ -138,13 +139,13 @@ def test_median_absolute_deviation_quantity():
 def test_binom_conf_interval():
 
     # Test Wilson and Jeffreys interval for corner cases:
-    # Corner cases: k = 0, k = n, conf = 0., conf = 1.
+    # Corner cases: k = 0, k = n, confidence_level = 0., confidence_level = 1.
     n = 5
     k = [0, 4, 5]
     for conf in [0., 0.5, 1.]:
-        res = funcs.binom_conf_interval(k, n, conf=conf, interval='wilson')
+        res = funcs.binom_conf_interval(k, n, confidence_level=conf, interval='wilson')
         assert ((res >= 0.) & (res <= 1.)).all()
-        res = funcs.binom_conf_interval(k, n, conf=conf, interval='jeffreys')
+        res = funcs.binom_conf_interval(k, n, confidence_level=conf, interval='jeffreys')
         assert ((res >= 0.) & (res <= 1.)).all()
 
     # Test Jeffreys interval accuracy against table in Brown et al. (2001).
@@ -152,27 +153,28 @@ def test_binom_conf_interval():
     k = [0, 1, 2, 3, 4]
     n = 7
     conf = 0.95
-    result = funcs.binom_conf_interval(k, n, conf=conf, interval='jeffreys')
+    result = funcs.binom_conf_interval(k, n, confidence_level=conf, interval='jeffreys')
     table = np.array([[0.000, 0.016, 0.065, 0.139, 0.234],
                       [0.292, 0.501, 0.648, 0.766, 0.861]])
     assert_allclose(result, table, atol=1.e-3, rtol=0.)
 
     # Test scalar version
-    result = np.array([funcs.binom_conf_interval(kval, n, conf=conf,
+    result = np.array([funcs.binom_conf_interval(kval, n, confidence_level=conf,
                                                  interval='jeffreys')
                        for kval in k]).transpose()
     assert_allclose(result, table, atol=1.e-3, rtol=0.)
 
     # Test flat
-    result = funcs.binom_conf_interval(k, n, conf=conf, interval='flat')
+    result = funcs.binom_conf_interval(k, n, confidence_level=conf, interval='flat')
     table = np.array([[0., 0.03185, 0.08523, 0.15701, 0.24486],
                       [0.36941, 0.52650, 0.65085, 0.75513, 0.84298]])
     assert_allclose(result, table, atol=1.e-3, rtol=0.)
 
     # Test scalar version
-    result = np.array([funcs.binom_conf_interval(kval, n, conf=conf,
-                                                 interval='flat')
-                       for kval in k]).transpose()
+    with pytest.warns(AstropyDeprecationWarning):
+        result = np.array([funcs.binom_conf_interval(kval, n, conf=conf,
+                                                     interval='flat')
+                           for kval in k]).transpose()
     assert_allclose(result, table, atol=1.e-3, rtol=0.)
 
     # Test Wald interval
@@ -180,7 +182,7 @@ def test_binom_conf_interval():
     assert_allclose(result, 0.)  # conf interval is [0, 0] when k = 0
     result = funcs.binom_conf_interval(5, 5, interval='wald')
     assert_allclose(result, 1.)  # conf interval is [1, 1] when k = n
-    result = funcs.binom_conf_interval(500, 1000, conf=0.68269,
+    result = funcs.binom_conf_interval(500, 1000, confidence_level=0.68269,
                                        interval='wald')
     assert_allclose(result[0], 0.5 - 0.5 / np.sqrt(1000.))
     assert_allclose(result[1], 0.5 + 0.5 / np.sqrt(1000.))
@@ -239,6 +241,12 @@ def test_binned_binom_proportion():
     assert (p == 0.).all()
 
 
+def test_binned_binom_proportion_exception():
+    with pytest.raises(ValueError):
+        with pytest.warns(AstropyDeprecationWarning):
+            funcs.binned_binom_proportion([0], [1, 2], conf=0.75)
+
+
 def test_signal_to_noise_oir_ccd():
 
     result = funcs.signal_to_noise_oir_ccd(1, 25, 0, 0, 0, 1)
@@ -355,7 +363,7 @@ def test_mad_std_warns():
         data = np.random.normal(5, 2, size=(10, 10))
         data[5, 5] = np.nan
 
-        with catch_warnings() as warns:
+        with catch_warnings():
             rslt = funcs.mad_std(data, ignore_nan=False)
             assert np.isnan(rslt)
 
@@ -391,8 +399,10 @@ def test_mad_std_with_axis_and_nan():
                              2.22390333, np.nan])
     result_axis1 = np.array([1.48260222, 1.48260222])
 
-    assert_allclose(funcs.mad_std(data, axis=0, ignore_nan=True), result_axis0)
-    assert_allclose(funcs.mad_std(data, axis=1, ignore_nan=True), result_axis1)
+    with pytest.warns(RuntimeWarning,
+                      match=r'All-NaN slice encountered'):
+        assert_allclose(funcs.mad_std(data, axis=0, ignore_nan=True), result_axis0)
+        assert_allclose(funcs.mad_std(data, axis=1, ignore_nan=True), result_axis1)
 
 
 def test_mad_std_with_axis_and_nan_array_type():
@@ -400,7 +410,9 @@ def test_mad_std_with_axis_and_nan_array_type():
     data = np.array([[1, 2, 3, 4, np.nan],
                      [4, 3, 2, 1, np.nan]])
 
-    result = funcs.mad_std(data, axis=0, ignore_nan=True)
+    with pytest.warns(RuntimeWarning,
+                      match=r'All-NaN slice encountered'):
+        result = funcs.mad_std(data, axis=0, ignore_nan=True)
     assert not np.ma.isMaskedArray(result)
 
     data = np.ma.masked_where(np.isnan(data), data)
@@ -580,7 +592,7 @@ def test_scipy_poisson_limit():
                     (0, 10.67), rtol=1e-3)
     conf = funcs.poisson_conf_interval([5., 6.], 'kraft-burrows-nousek',
                                        background=[2.5, 2.],
-                                       conflevel=[.99, .9])
+                                       confidence_level=[.99, .9])
     assert_allclose(conf[:, 0], (0, 10.67), rtol=1e-3)
     assert_allclose(conf[:, 1], (0.81, 8.99), rtol=5e-3)
 
@@ -605,8 +617,8 @@ def test_poisson_conf_value_errors():
 
     with pytest.raises(ValueError) as e:
         funcs.poisson_conf_interval([5, 6], 'sherpagehrels',
-                                    conflevel=[2.5, 2.])
-    assert 'conflevel not supported' in str(e.value)
+                                    confidence_level=[2.5, 2.])
+    assert 'confidence_level not supported' in str(e.value)
 
     with pytest.raises(ValueError) as e:
         funcs.poisson_conf_interval(1, 'foo')
@@ -618,26 +630,27 @@ def test_poisson_conf_kbn_value_errors():
     with pytest.raises(ValueError) as e:
         funcs.poisson_conf_interval(5., 'kraft-burrows-nousek',
                                     background=2.5,
-                                    conflevel=99)
+                                    confidence_level=99)
     assert 'number between 0 and 1' in str(e.value)
 
     with pytest.raises(ValueError) as e:
         funcs.poisson_conf_interval(5., 'kraft-burrows-nousek',
                                     background=2.5)
-    assert 'Set conflevel for method' in str(e.value)
+    assert 'Set confidence_level for method' in str(e.value)
 
     with pytest.raises(ValueError) as e:
         funcs.poisson_conf_interval(5., 'kraft-burrows-nousek',
                                     background=-2.5,
-                                    conflevel=.99)
+                                    confidence_level=.99)
     assert 'Background must be' in str(e.value)
 
 
 @pytest.mark.skipif('HAS_SCIPY or HAS_MPMATH')
 def test_poisson_limit_nodependencies():
     with pytest.raises(ImportError):
-        funcs.poisson_conf_interval(20., interval='kraft-burrows-nousek',
-                                    background=10., conflevel=.95)
+        with pytest.warns(AstropyDeprecationWarning):
+            funcs.poisson_conf_interval(20., interval='kraft-burrows-nousek',
+                                        background=10., conflevel=.95)
 
 
 @pytest.mark.skipif('not HAS_SCIPY')
diff --git a/astropy/stats/tests/test_histogram.py b/astropy/stats/tests/test_histogram.py
index 7d127182a5..9737501617 100644
--- a/astropy/stats/tests/test_histogram.py
+++ b/astropy/stats/tests/test_histogram.py
@@ -1,14 +1,17 @@
 # Licensed under a 3-clause BSD style license - see LICENSE.rst
 
+import warnings
+
 import pytest
 import numpy as np
 from numpy.testing import assert_allclose
 
-from astropy.stats import (histogram, calculate_bin_edges,
-                scott_bin_width, freedman_bin_width, knuth_bin_width)
+from astropy.stats import (histogram, calculate_bin_edges, scott_bin_width,
+                           freedman_bin_width, knuth_bin_width)
+from astropy.utils.exceptions import AstropyUserWarning
 
 try:
-    import scipy  # pylint: disable=W0611
+    import scipy  # pylint: disable=W0611 # noqa
 except ImportError:
     HAS_SCIPY = False
 else:
@@ -47,7 +50,8 @@ def test_freedman_bin_width(N=10000, rseed=0):
     # data with too small IQR
     test_x = [1, 2, 3] + [4] * 100 + [5, 6, 7]
     with pytest.raises(ValueError) as e:
-        freedman_bin_width(test_x, return_bins=True)
+        with pytest.warns(RuntimeWarning, match=r'divide by zero encountered'):
+            freedman_bin_width(test_x, return_bins=True)
         assert 'Please use another bin method' in str(e.value)
 
     # data with small IQR but not too small
@@ -93,7 +97,12 @@ def test_histogram(bin_type, N=1000, rseed=0):
     rng = np.random.RandomState(rseed)
     x = rng.randn(N)
 
-    counts, bins = histogram(x, bin_type)
+    # Warning is emitted for blocks
+    with warnings.catch_warnings():
+        warnings.filterwarnings(
+            'ignore', message=r'.*p0 does not seem to accurate.*',
+            category=AstropyUserWarning)
+        counts, bins = histogram(x, bin_type)
     assert (counts.sum() == len(x))
     assert (len(counts) == len(bins) - 1)
 
@@ -107,7 +116,12 @@ def test_histogram_range(bin_type, N=1000, rseed=0):
     x = rng.randn(N)
     range = (0.1, 0.8)
 
-    bins = calculate_bin_edges(x, bin_type, range=range)
+    # Warning is emitted for blocks
+    with warnings.catch_warnings():
+        warnings.filterwarnings(
+            'ignore', message=r'.*p0 does not seem to accurate.*',
+            category=AstropyUserWarning)
+        bins = calculate_bin_edges(x, bin_type, range=range)
     assert bins.max() == range[1]
     assert bins.min() == range[0]
 
@@ -157,7 +171,9 @@ def test_histogram_output():
                            -0.17288406, 0.42214237, 1.01716881, 1.61219525,
                            2.20722169, 2.80224813])
 
-    counts, bins = histogram(X, bins='blocks')
+    with pytest.warns(AstropyUserWarning,
+                      match=r'p0 does not seem to accurate'):
+        counts, bins = histogram(X, bins='blocks')
     assert_allclose(counts, [10, 61, 29])
     assert_allclose(bins, [-2.55298982, -1.24381059, 0.46422235, 2.26975462])
 
diff --git a/astropy/stats/tests/test_jackknife.py b/astropy/stats/tests/test_jackknife.py
index 3e397011a5..5ebef15b2a 100644
--- a/astropy/stats/tests/test_jackknife.py
+++ b/astropy/stats/tests/test_jackknife.py
@@ -2,17 +2,17 @@
 
 import pytest
 import numpy as np
-
 from numpy.testing import assert_equal, assert_allclose
 
 try:
-    import scipy  # pylint: disable=W0611
+    import scipy  # pylint: disable=W0611 # noqa
 except ImportError:
     HAS_SCIPY = False
 else:
     HAS_SCIPY = True
 
 from astropy.stats.jackknife import jackknife_resampling, jackknife_stats
+from astropy.utils.exceptions import AstropyDeprecationWarning
 
 
 def test_jackknife_resampling():
@@ -54,3 +54,12 @@ def test_jackknife_stats_conf_interval():
     # test confidence interval
     answer = np.array((70.14615, 157.42616))
     assert_allclose(answer, jackknife_stats(data, mle_var, 0.95)[3], atol=1e-4)
+
+
+def test_jackknife_stats_exceptions():
+    with pytest.raises(ValueError):
+        with pytest.warns(AstropyDeprecationWarning):
+            jackknife_stats(np.array([]), np.mean, conf_lvl=0.9)
+
+    with pytest.raises(ValueError):
+        jackknife_stats(np.arange(2), np.mean, confidence_level=42)
diff --git a/astropy/stats/tests/test_sigma_clipping.py b/astropy/stats/tests/test_sigma_clipping.py
index 4c0772dcf4..1f8d6beef0 100644
--- a/astropy/stats/tests/test_sigma_clipping.py
+++ b/astropy/stats/tests/test_sigma_clipping.py
@@ -1,9 +1,7 @@
 # Licensed under a 3-clause BSD style license - see LICENSE.rst
 
-
 import pytest
 import numpy as np
-
 from numpy.testing import assert_equal, assert_allclose
 
 try:
@@ -15,6 +13,7 @@ else:
 
 from astropy import units as u
 from astropy.stats.sigma_clipping import sigma_clip, SigmaClip, sigma_clipped_stats
+from astropy.utils.exceptions import AstropyUserWarning
 from astropy.utils.misc import NumpyRNGContext
 
 
@@ -170,7 +169,9 @@ def test_invalid_sigma_clip():
     data[3, 4] = np.nan
     data[1, 1] = np.inf
 
-    result = sigma_clip(data)
+    with pytest.warns(AstropyUserWarning,
+                      match=r'Input data contains invalid values'):
+        result = sigma_clip(data)
 
     # Pre #4051 if data contains any NaN or infs sigma_clip returns the
     # mask containing `False` only or TypeError if data also contains a
@@ -179,18 +180,24 @@ def test_invalid_sigma_clip():
     assert result.mask[3, 4]
     assert result.mask[1, 1]
 
-    result2 = sigma_clip(data, axis=0)
+    with pytest.warns(AstropyUserWarning,
+                      match=r'Input data contains invalid values'):
+        result2 = sigma_clip(data, axis=0)
     assert result2.mask[1, 1]
     assert result2.mask[3, 4]
 
-    result3 = sigma_clip(data, axis=0, copy=False)
+    with pytest.warns(AstropyUserWarning,
+                      match=r'Input data contains invalid values'):
+        result3 = sigma_clip(data, axis=0, copy=False)
     assert result3.mask[1, 1]
     assert result3.mask[3, 4]
 
     # stats along axis with all nans
     data[0, :] = np.nan     # row of all nans
-    result4, minarr, maxarr = sigma_clip(data, axis=1, masked=False,
-                                         return_bounds=True)
+    with pytest.warns(AstropyUserWarning,
+                      match=r'Input data contains invalid values'):
+        result4, minarr, maxarr = sigma_clip(data, axis=1, masked=False,
+                                             return_bounds=True)
     assert np.isnan(minarr[0])
     assert np.isnan(maxarr[0])