Repository: pandas-dev/pandas

Issue:
(Re)-deprecate order keyword in factorize
In https://github.com/pandas-dev/pandas/pull/6930 we deprecated `order`.

As of 0.18.1, the keyword was no-longer documented, and doesn't raise, but wasn't actually removed.

```python
In [3]: pandas.factorize([0, 1], order=True)
Out[3]: (array([0, 1]), array([0, 1]))

In [4]: pandas.__version__
Out[4]: '0.18.1'
```


Discussion:
I don't have a strong preference on deprecating vs. just removing. Probably best to deprecate for one release.
To be clear, it seems the order keyword does nothing (so it was forgotten to be removed from the signature).

Resolving patch:
diff --git a/pandas/core/algorithms.py b/pandas/core/algorithms.py
--- a/pandas/core/algorithms.py
+++ b/pandas/core/algorithms.py
@@ -32,6 +32,7 @@
 from pandas.core import common as com
 from pandas._libs import algos, lib, hashtable as htable
 from pandas._libs.tslib import iNaT
+from pandas.util._decorators import deprecate_kwarg
 
 
 # --------------- #
@@ -436,6 +437,7 @@ def isin(comps, values):
     return f(comps, values)
 
 
+@deprecate_kwarg(old_arg_name='order', new_arg_name=None)
 def factorize(values, sort=False, order=None, na_sentinel=-1, size_hint=None):
     """
     Encode input values as an enumerated type or categorical variable
diff --git a/pandas/util/_decorators.py b/pandas/util/_decorators.py
--- a/pandas/util/_decorators.py
+++ b/pandas/util/_decorators.py
@@ -65,8 +65,9 @@ def deprecate_kwarg(old_arg_name, new_arg_name, mapping=None, stacklevel=2):
     ----------
     old_arg_name : str
         Name of argument in function to deprecate
-    new_arg_name : str
-        Name of preferred argument in function
+    new_arg_name : str or None
+        Name of preferred argument in function. Use None to raise warning that
+        ``old_arg_name`` keyword is deprecated.
     mapping : dict or callable
         If mapping is present, use it to translate old arguments to
         new arguments. A callable must do its own value checking;
@@ -82,12 +83,15 @@ def deprecate_kwarg(old_arg_name, new_arg_name, mapping=None, stacklevel=2):
     ...
     >>> f(columns='should work ok')
     should work ok
+
     >>> f(cols='should raise warning')
     FutureWarning: cols is deprecated, use columns instead
       warnings.warn(msg, FutureWarning)
     should raise warning
+
     >>> f(cols='should error', columns="can\'t pass do both")
     TypeError: Can only specify 'cols' or 'columns', not both
+
     >>> @deprecate_kwarg('old', 'new', {'yes': True, 'no': False})
     ... def f(new=False):
     ...     print('yes!' if new else 'no!')
@@ -96,6 +100,25 @@ def deprecate_kwarg(old_arg_name, new_arg_name, mapping=None, stacklevel=2):
     FutureWarning: old='yes' is deprecated, use new=True instead
       warnings.warn(msg, FutureWarning)
     yes!
+
+
+    To raise a warning that a keyword will be removed entirely in the future
+
+    >>> @deprecate_kwarg(old_arg_name='cols', new_arg_name=None)
+    ... def f(cols='', another_param=''):
+    ...     print(cols)
+    ...
+    >>> f(cols='should raise warning')
+    FutureWarning: the 'cols' keyword is deprecated and will be removed in a
+    future version please takes steps to stop use of 'cols'
+    should raise warning
+    >>> f(another_param='should not raise warning')
+    should not raise warning
+
+    >>> f(cols='should raise warning', another_param='')
+    FutureWarning: the 'cols' keyword is deprecated and will be removed in a
+    future version please takes steps to stop use of 'cols'
+    should raise warning
     """
 
     if mapping is not None and not hasattr(mapping, 'get') and \
@@ -107,6 +130,17 @@ def _deprecate_kwarg(func):
         @wraps(func)
         def wrapper(*args, **kwargs):
             old_arg_value = kwargs.pop(old_arg_name, None)
+
+            if new_arg_name is None and old_arg_value is not None:
+                msg = (
+                    "the '{old_name}' keyword is deprecated and will be "
+                    "removed in a future version "
+                    "please takes steps to stop use of '{old_name}'"
+                ).format(old_name=old_arg_name)
+                warnings.warn(msg, FutureWarning, stacklevel=stacklevel)
+                kwargs[old_arg_name] = old_arg_value
+                return func(*args, **kwargs)
+
             if old_arg_value is not None:
                 if mapping is not None:
                     if hasattr(mapping, 'get'):


Test patch:
diff --git a/pandas/tests/test_algos.py b/pandas/tests/test_algos.py
--- a/pandas/tests/test_algos.py
+++ b/pandas/tests/test_algos.py
@@ -248,6 +248,15 @@ def test_uint64_factorize(self):
         tm.assert_numpy_array_equal(labels, exp_labels)
         tm.assert_numpy_array_equal(uniques, exp_uniques)
 
+    def test_deprecate_order(self):
+        # gh 19727 - check warning is raised for deprecated keyword, order.
+        # Test not valid once order keyword is removed.
+        data = np.array([2**63, 1, 2**63], dtype=np.uint64)
+        with tm.assert_produces_warning(expected_warning=FutureWarning):
+            algos.factorize(data, order=True)
+        with tm.assert_produces_warning(False):
+            algos.factorize(data)
+
 
 class TestUnique(object):
 
diff --git a/pandas/tests/util/test_util.py b/pandas/tests/util/test_util.py
--- a/pandas/tests/util/test_util.py
+++ b/pandas/tests/util/test_util.py
@@ -34,9 +34,14 @@ def _f2(new=False):
         def _f3(new=0):
             return new
 
+        @deprecate_kwarg('old', None)
+        def _f4(old=True, unchanged=True):
+            return old
+
         self.f1 = _f1
         self.f2 = _f2
         self.f3 = _f3
+        self.f4 = _f4
 
     def test_deprecate_kwarg(self):
         x = 78
@@ -72,6 +77,15 @@ def test_bad_deprecate_kwarg(self):
             def f4(new=None):
                 pass
 
+    def test_deprecate_keyword(self):
+        x = 9
+        with tm.assert_produces_warning(FutureWarning):
+            result = self.f4(old=x)
+        assert result is x
+        with tm.assert_produces_warning(None):
+            result = self.f4(unchanged=x)
+        assert result is True
+
 
 def test_rands():
     r = tm.rands(10)