We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[INF] Set multi-test env to compat dependency and system
<!-- Thank you for your PR!

BEFORE YOU CONTINUE! Please add the appropriate three-letter abbreviation to your title.

The abbreviations can be:
- [DOC]: Documentation fixes.
- [ENH]: Code contributions and new features.
- [TST]: Test-related contributions.
- [INF]: Infrastructure-related contributions.

Also, do not forget to tag the relevant issue here as well.

Finally, as commits come in, don't forget to regularly rebase!
-->

# PR Description

Please describe the changes proposed in the pull request:

Aim:
- Set multi-test environment
- Compat different dependencies and systems

ToDo/Doing:
- [x] Set latest env: test pyjanitor work with the latest dependencies to get the minimal python version
- [ ] Set minimal env: get the minimal version of dependencies

Part of #1133

# PR Checklist

<!-- This checklist exists for newcomers who are not yet familiar with our requirements. If you are experienced with
the project, please feel free to delete this section. -->

Please ensure that you have done the following:

1. [x] PR in from a fork off your branch. Do not PR from `<your_username>`:`dev`, but rather from `<your_username>`:`<feature-branch_name>`.
<!-- Doing this helps us keep the commit history much cleaner than it would otherwise be. -->
2. [x] If you're not on the contributors list, add yourself to `AUTHORS.md`.
<!-- We'd like to acknowledge your contributions! -->
3. [x] Add a line to `CHANGELOG.md` under the latest version header (i.e. the one that is "on deck") describing the contribution.
    - Do use some discretion here; if there are multiple PRs that are related, keep them in a single line.

# Automatic checks

There will be automatic checks run on the PR. These include:

- Building a preview of the docs on Netlify
- Automatically linting the code
- Making sure the code is documented
- Making sure that all tests are passed
- Making sure that code coverage doesn't go down.

# Relevant Reviewers

<!-- Finally, please tag relevant maintainers to review. -->

Please tag maintainers to review.

- @ericmjl


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `janitor/functions/encode_categorical.py`
Content:
```
1 import warnings
2 from enum import Enum
3 from typing import Hashable, Iterable, Union
4 
5 import pandas_flavor as pf
6 import pandas as pd
7 from pandas.api.types import is_list_like
8 
9 from janitor.utils import check, check_column, deprecated_alias
10 
11 
12 @pf.register_dataframe_method
13 @deprecated_alias(columns="column_names")
14 def encode_categorical(
15     df: pd.DataFrame,
16     column_names: Union[str, Iterable[str], Hashable] = None,
17     **kwargs,
18 ) -> pd.DataFrame:
19     """Encode the specified columns with Pandas' [category dtype][cat].
20 
21     [cat]: http://pandas.pydata.org/pandas-docs/stable/user_guide/categorical.html
22 
23     It is syntactic sugar around `pd.Categorical`.
24 
25     This method does not mutate the original DataFrame.
26 
27     Simply pass a string, or a sequence of column names to `column_names`;
28     alternatively, you can pass kwargs, where the keys are the column names
29     and the values can either be None, `sort`, `appearance`
30     or a 1-D array-like object.
31 
32     - None: column is cast to an unordered categorical.
33     - `sort`: column is cast to an ordered categorical,
34               with the order defined by the sort-order of the categories.
35     - `appearance`: column is cast to an ordered categorical,
36                     with the order defined by the order of appearance
37                     in the original column.
38     - 1d-array-like object: column is cast to an ordered categorical,
39                             with the categories and order as specified
40                             in the input array.
41 
42     `column_names` and `kwargs` parameters cannot be used at the same time.
43 
44     Example: Using `column_names`
45 
46         >>> import pandas as pd
47         >>> import janitor
48         >>> df = pd.DataFrame({
49         ...     "foo": ["b", "b", "a", "c", "b"],
50         ...     "bar": range(4, 9),
51         ... })
52         >>> df
53           foo  bar
54         0   b    4
55         1   b    5
56         2   a    6
57         3   c    7
58         4   b    8
59         >>> df.dtypes
60         foo    object
61         bar     int64
62         dtype: object
63         >>> enc_df = df.encode_categorical(column_names="foo")
64         >>> enc_df.dtypes
65         foo    category
66         bar       int64
67         dtype: object
68         >>> enc_df["foo"].cat.categories
69         Index(['a', 'b', 'c'], dtype='object')
70         >>> enc_df["foo"].cat.ordered
71         False
72 
73     Example: Using `kwargs` to specify an ordered categorical.
74 
75         >>> import pandas as pd
76         >>> import janitor
77         >>> df = pd.DataFrame({
78         ...     "foo": ["b", "b", "a", "c", "b"],
79         ...     "bar": range(4, 9),
80         ... })
81         >>> df.dtypes
82         foo    object
83         bar     int64
84         dtype: object
85         >>> enc_df = df.encode_categorical(foo="appearance")
86         >>> enc_df.dtypes
87         foo    category
88         bar       int64
89         dtype: object
90         >>> enc_df["foo"].cat.categories
91         Index(['b', 'a', 'c'], dtype='object')
92         >>> enc_df["foo"].cat.ordered
93         True
94 
95     :param df: A pandas DataFrame object.
96     :param column_names: A column name or an iterable (list or tuple)
97         of column names.
98     :param **kwargs: A mapping from column name to either `None`,
99         `'sort'` or `'appearance'`, or a 1-D array. This is useful
100         in creating categorical columns that are ordered, or
101         if the user needs to explicitly specify the categories.
102     :returns: A pandas DataFrame.
103     :raises ValueError: If both `column_names` and `kwargs` are provided.
104     """  # noqa: E501
105 
106     if all((column_names, kwargs)):
107         raise ValueError(
108             "Only one of `column_names` or `kwargs` can be provided."
109         )
110     # column_names deal with only category dtype (unordered)
111     # kwargs takes care of scenarios where user wants an ordered category
112     # or user supplies specific categories to create the categorical
113     if column_names is not None:
114         check("column_names", column_names, [list, tuple, Hashable])
115         if isinstance(column_names, Hashable):
116             column_names = [column_names]
117         check_column(df, column_names)
118         dtypes = {col: "category" for col in column_names}
119         return df.astype(dtypes)
120 
121     return _computations_as_categorical(df, **kwargs)
122 
123 
124 def _computations_as_categorical(df: pd.DataFrame, **kwargs) -> pd.DataFrame:
125     """
126     This function handles cases where
127     categorical columns are created with an order,
128     or specific values supplied for the categories.
129     It uses a kwarg, where the key is the column name,
130     and the value is either a string, or a 1D array.
131     The default for value is None and will return a categorical dtype
132     with no order and categories inferred from the column.
133     A DataFrame, with categorical columns, is returned.
134     """
135 
136     categories_dict = _as_categorical_checks(df, **kwargs)
137 
138     categories_dtypes = {}
139 
140     for column_name, value in categories_dict.items():
141         if value is None:
142             cat_dtype = pd.CategoricalDtype()
143         elif isinstance(value, str):
144             if value == _CategoryOrder.SORT.value:
145                 _, cat_dtype = df[column_name].factorize(sort=True)
146             else:
147                 _, cat_dtype = df[column_name].factorize(sort=False)
148             if cat_dtype.empty:
149                 raise ValueError(
150                     "Kindly ensure there is at least "
151                     f"one non-null value in {column_name}."
152                 )
153             cat_dtype = pd.CategoricalDtype(categories=cat_dtype, ordered=True)
154 
155         else:  # 1-D array
156             cat_dtype = pd.CategoricalDtype(categories=value, ordered=True)
157 
158         categories_dtypes[column_name] = cat_dtype
159 
160     return df.astype(categories_dtypes)
161 
162 
163 def _as_categorical_checks(df: pd.DataFrame, **kwargs) -> dict:
164     """
165     This function raises errors if columns in `kwargs` are
166     absent from the dataframe's columns.
167     It also raises errors if the value in `kwargs`
168     is not a string (`'appearance'` or `'sort'`), or a 1D array.
169 
170     This function is executed before proceeding to the computation phase.
171 
172     If all checks pass, a dictionary of column names and value is returned.
173 
174     :param df: The pandas DataFrame object.
175     :param **kwargs: A pairing of column name and value.
176     :returns: A dictionary.
177     :raises TypeError: If `value` is not a 1-D array, or a string.
178     :raises ValueError: If `value` is a 1-D array, and contains nulls,
179         or is non-unique.
180     """
181 
182     check_column(df, kwargs)
183 
184     categories_dict = {}
185 
186     for column_name, value in kwargs.items():
187         # type check
188         if (value is not None) and not (
189             is_list_like(value) or isinstance(value, str)
190         ):
191             raise TypeError(f"{value} should be list-like or a string.")
192         if is_list_like(value):
193             if not hasattr(value, "shape"):
194                 value = pd.Index([*value])
195 
196             arr_ndim = value.ndim
197             if (arr_ndim != 1) or isinstance(value, pd.MultiIndex):
198                 raise ValueError(
199                     f"{value} is not a 1-D array. "
200                     "Kindly provide a 1-D array-like object."
201                 )
202 
203             if not isinstance(value, (pd.Series, pd.Index)):
204                 value = pd.Index(value)
205 
206             if value.hasnans:
207                 raise ValueError(
208                     "Kindly ensure there are no nulls in the array provided."
209                 )
210 
211             if not value.is_unique:
212                 raise ValueError(
213                     "Kindly provide unique, "
214                     "non-null values for the array provided."
215                 )
216 
217             if value.empty:
218                 raise ValueError(
219                     "Kindly ensure there is at least "
220                     "one non-null value in the array provided."
221                 )
222 
223             # uniques, without nulls
224             uniques = df[column_name].factorize(sort=False)[-1]
225             if uniques.empty:
226                 raise ValueError(
227                     "Kindly ensure there is at least "
228                     f"one non-null value in {column_name}."
229                 )
230 
231             missing = uniques.difference(value, sort=False)
232             if not missing.empty and (uniques.size > missing.size):
233                 warnings.warn(
234                     f"Values {tuple(missing)} are missing from "
235                     f"the provided categories {value} "
236                     f"for {column_name}; this may create nulls "
237                     "in the new categorical column.",
238                     UserWarning,
239                     stacklevel=2,
240                 )
241 
242             elif uniques.equals(missing):
243                 warnings.warn(
244                     f"None of the values in {column_name} are in "
245                     f"{value}; this might create nulls for all values "
246                     f"in the new categorical column.",
247                     UserWarning,
248                     stacklevel=2,
249                 )
250 
251         elif isinstance(value, str):
252             category_order_types = {ent.value for ent in _CategoryOrder}
253             if value.lower() not in category_order_types:
254                 raise ValueError(
255                     "Argument should be one of 'appearance' or 'sort'."
256                 )
257 
258         categories_dict[column_name] = value
259 
260     return categories_dict
261 
262 
263 class _CategoryOrder(Enum):
264     """
265     order types for encode_categorical.
266     """
267 
268     SORT = "sort"
269     APPEARANCE = "appearance"
270
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/janitor/functions/encode_categorical.py b/janitor/functions/encode_categorical.py
--- a/janitor/functions/encode_categorical.py
+++ b/janitor/functions/encode_categorical.py
@@ -2,8 +2,9 @@
 from enum import Enum
 from typing import Hashable, Iterable, Union
 
-import pandas_flavor as pf
+import numpy as np
 import pandas as pd
+import pandas_flavor as pf
 from pandas.api.types import is_list_like
 
 from janitor.utils import check, check_column, deprecated_alias
@@ -191,10 +192,9 @@
             raise TypeError(f"{value} should be list-like or a string.")
         if is_list_like(value):
             if not hasattr(value, "shape"):
-                value = pd.Index([*value])
+                value = np.asarray(value)
 
-            arr_ndim = value.ndim
-            if (arr_ndim != 1) or isinstance(value, pd.MultiIndex):
+            if (value.ndim != 1) or isinstance(value, pd.MultiIndex):
                 raise ValueError(
                     f"{value} is not a 1-D array. "
                     "Kindly provide a 1-D array-like object."

{"golden_diff": "diff --git a/janitor/functions/encode_categorical.py b/janitor/functions/encode_categorical.py\n--- a/janitor/functions/encode_categorical.py\n+++ b/janitor/functions/encode_categorical.py\n@@ -2,8 +2,9 @@\n from enum import Enum\n from typing import Hashable, Iterable, Union\n \n-import pandas_flavor as pf\n+import numpy as np\n import pandas as pd\n+import pandas_flavor as pf\n from pandas.api.types import is_list_like\n \n from janitor.utils import check, check_column, deprecated_alias\n@@ -191,10 +192,9 @@\n             raise TypeError(f\"{value} should be list-like or a string.\")\n         if is_list_like(value):\n             if not hasattr(value, \"shape\"):\n-                value = pd.Index([*value])\n+                value = np.asarray(value)\n \n-            arr_ndim = value.ndim\n-            if (arr_ndim != 1) or isinstance(value, pd.MultiIndex):\n+            if (value.ndim != 1) or isinstance(value, pd.MultiIndex):\n                 raise ValueError(\n                     f\"{value} is not a 1-D array. \"\n                     \"Kindly provide a 1-D array-like object.\"\n", "issue": "[INF] Set multi-test env to compat dependency and system\n<!-- Thank you for your PR!\r\n\r\nBEFORE YOU CONTINUE! Please add the appropriate three-letter abbreviation to your title.\r\n\r\nThe abbreviations can be:\r\n- [DOC]: Documentation fixes.\r\n- [ENH]: Code contributions and new features.\r\n- [TST]: Test-related contributions.\r\n- [INF]: Infrastructure-related contributions.\r\n\r\nAlso, do not forget to tag the relevant issue here as well.\r\n\r\nFinally, as commits come in, don't forget to regularly rebase!\r\n-->\r\n\r\n# PR Description\r\n\r\nPlease describe the changes proposed in the pull request:\r\n\r\nAim:\r\n- Set multi-test environment\r\n- Compat different dependencies and systems\r\n\r\nToDo/Doing:\r\n- [x] Set latest env: test pyjanitor work with the latest dependencies to get the minimal python version\r\n- [ ] Set minimal env: get the minimal version of dependencies\r\n\r\nPart of #1133\r\n\r\n# PR Checklist\r\n\r\n<!-- This checklist exists for newcomers who are not yet familiar with our requirements. If you are experienced with\r\nthe project, please feel free to delete this section. -->\r\n\r\nPlease ensure that you have done the following:\r\n\r\n1. [x] PR in from a fork off your branch. Do not PR from `<your_username>`:`dev`, but rather from `<your_username>`:`<feature-branch_name>`.\r\n<!-- Doing this helps us keep the commit history much cleaner than it would otherwise be. -->\r\n2. [x] If you're not on the contributors list, add yourself to `AUTHORS.md`.\r\n<!-- We'd like to acknowledge your contributions! -->\r\n3. [x] Add a line to `CHANGELOG.md` under the latest version header (i.e. the one that is \"on deck\") describing the contribution.\r\n    - Do use some discretion here; if there are multiple PRs that are related, keep them in a single line.\r\n\r\n# Automatic checks\r\n\r\nThere will be automatic checks run on the PR. These include:\r\n\r\n- Building a preview of the docs on Netlify\r\n- Automatically linting the code\r\n- Making sure the code is documented\r\n- Making sure that all tests are passed\r\n- Making sure that code coverage doesn't go down.\r\n\r\n# Relevant Reviewers\r\n\r\n<!-- Finally, please tag relevant maintainers to review. -->\r\n\r\nPlease tag maintainers to review.\r\n\r\n- @ericmjl\r\n\n", "before_files": [{"content": "import warnings\nfrom enum import Enum\nfrom typing import Hashable, Iterable, Union\n\nimport pandas_flavor as pf\nimport pandas as pd\nfrom pandas.api.types import is_list_like\n\nfrom janitor.utils import check, check_column, deprecated_alias\n\n\n@pf.register_dataframe_method\n@deprecated_alias(columns=\"column_names\")\ndef encode_categorical(\n    df: pd.DataFrame,\n    column_names: Union[str, Iterable[str], Hashable] = None,\n    **kwargs,\n) -> pd.DataFrame:\n    \"\"\"Encode the specified columns with Pandas' [category dtype][cat].\n\n    [cat]: http://pandas.pydata.org/pandas-docs/stable/user_guide/categorical.html\n\n    It is syntactic sugar around `pd.Categorical`.\n\n    This method does not mutate the original DataFrame.\n\n    Simply pass a string, or a sequence of column names to `column_names`;\n    alternatively, you can pass kwargs, where the keys are the column names\n    and the values can either be None, `sort`, `appearance`\n    or a 1-D array-like object.\n\n    - None: column is cast to an unordered categorical.\n    - `sort`: column is cast to an ordered categorical,\n              with the order defined by the sort-order of the categories.\n    - `appearance`: column is cast to an ordered categorical,\n                    with the order defined by the order of appearance\n                    in the original column.\n    - 1d-array-like object: column is cast to an ordered categorical,\n                            with the categories and order as specified\n                            in the input array.\n\n    `column_names` and `kwargs` parameters cannot be used at the same time.\n\n    Example: Using `column_names`\n\n        >>> import pandas as pd\n        >>> import janitor\n        >>> df = pd.DataFrame({\n        ...     \"foo\": [\"b\", \"b\", \"a\", \"c\", \"b\"],\n        ...     \"bar\": range(4, 9),\n        ... })\n        >>> df\n          foo  bar\n        0   b    4\n        1   b    5\n        2   a    6\n        3   c    7\n        4   b    8\n        >>> df.dtypes\n        foo    object\n        bar     int64\n        dtype: object\n        >>> enc_df = df.encode_categorical(column_names=\"foo\")\n        >>> enc_df.dtypes\n        foo    category\n        bar       int64\n        dtype: object\n        >>> enc_df[\"foo\"].cat.categories\n        Index(['a', 'b', 'c'], dtype='object')\n        >>> enc_df[\"foo\"].cat.ordered\n        False\n\n    Example: Using `kwargs` to specify an ordered categorical.\n\n        >>> import pandas as pd\n        >>> import janitor\n        >>> df = pd.DataFrame({\n        ...     \"foo\": [\"b\", \"b\", \"a\", \"c\", \"b\"],\n        ...     \"bar\": range(4, 9),\n        ... })\n        >>> df.dtypes\n        foo    object\n        bar     int64\n        dtype: object\n        >>> enc_df = df.encode_categorical(foo=\"appearance\")\n        >>> enc_df.dtypes\n        foo    category\n        bar       int64\n        dtype: object\n        >>> enc_df[\"foo\"].cat.categories\n        Index(['b', 'a', 'c'], dtype='object')\n        >>> enc_df[\"foo\"].cat.ordered\n        True\n\n    :param df: A pandas DataFrame object.\n    :param column_names: A column name or an iterable (list or tuple)\n        of column names.\n    :param **kwargs: A mapping from column name to either `None`,\n        `'sort'` or `'appearance'`, or a 1-D array. This is useful\n        in creating categorical columns that are ordered, or\n        if the user needs to explicitly specify the categories.\n    :returns: A pandas DataFrame.\n    :raises ValueError: If both `column_names` and `kwargs` are provided.\n    \"\"\"  # noqa: E501\n\n    if all((column_names, kwargs)):\n        raise ValueError(\n            \"Only one of `column_names` or `kwargs` can be provided.\"\n        )\n    # column_names deal with only category dtype (unordered)\n    # kwargs takes care of scenarios where user wants an ordered category\n    # or user supplies specific categories to create the categorical\n    if column_names is not None:\n        check(\"column_names\", column_names, [list, tuple, Hashable])\n        if isinstance(column_names, Hashable):\n            column_names = [column_names]\n        check_column(df, column_names)\n        dtypes = {col: \"category\" for col in column_names}\n        return df.astype(dtypes)\n\n    return _computations_as_categorical(df, **kwargs)\n\n\ndef _computations_as_categorical(df: pd.DataFrame, **kwargs) -> pd.DataFrame:\n    \"\"\"\n    This function handles cases where\n    categorical columns are created with an order,\n    or specific values supplied for the categories.\n    It uses a kwarg, where the key is the column name,\n    and the value is either a string, or a 1D array.\n    The default for value is None and will return a categorical dtype\n    with no order and categories inferred from the column.\n    A DataFrame, with categorical columns, is returned.\n    \"\"\"\n\n    categories_dict = _as_categorical_checks(df, **kwargs)\n\n    categories_dtypes = {}\n\n    for column_name, value in categories_dict.items():\n        if value is None:\n            cat_dtype = pd.CategoricalDtype()\n        elif isinstance(value, str):\n            if value == _CategoryOrder.SORT.value:\n                _, cat_dtype = df[column_name].factorize(sort=True)\n            else:\n                _, cat_dtype = df[column_name].factorize(sort=False)\n            if cat_dtype.empty:\n                raise ValueError(\n                    \"Kindly ensure there is at least \"\n                    f\"one non-null value in {column_name}.\"\n                )\n            cat_dtype = pd.CategoricalDtype(categories=cat_dtype, ordered=True)\n\n        else:  # 1-D array\n            cat_dtype = pd.CategoricalDtype(categories=value, ordered=True)\n\n        categories_dtypes[column_name] = cat_dtype\n\n    return df.astype(categories_dtypes)\n\n\ndef _as_categorical_checks(df: pd.DataFrame, **kwargs) -> dict:\n    \"\"\"\n    This function raises errors if columns in `kwargs` are\n    absent from the dataframe's columns.\n    It also raises errors if the value in `kwargs`\n    is not a string (`'appearance'` or `'sort'`), or a 1D array.\n\n    This function is executed before proceeding to the computation phase.\n\n    If all checks pass, a dictionary of column names and value is returned.\n\n    :param df: The pandas DataFrame object.\n    :param **kwargs: A pairing of column name and value.\n    :returns: A dictionary.\n    :raises TypeError: If `value` is not a 1-D array, or a string.\n    :raises ValueError: If `value` is a 1-D array, and contains nulls,\n        or is non-unique.\n    \"\"\"\n\n    check_column(df, kwargs)\n\n    categories_dict = {}\n\n    for column_name, value in kwargs.items():\n        # type check\n        if (value is not None) and not (\n            is_list_like(value) or isinstance(value, str)\n        ):\n            raise TypeError(f\"{value} should be list-like or a string.\")\n        if is_list_like(value):\n            if not hasattr(value, \"shape\"):\n                value = pd.Index([*value])\n\n            arr_ndim = value.ndim\n            if (arr_ndim != 1) or isinstance(value, pd.MultiIndex):\n                raise ValueError(\n                    f\"{value} is not a 1-D array. \"\n                    \"Kindly provide a 1-D array-like object.\"\n                )\n\n            if not isinstance(value, (pd.Series, pd.Index)):\n                value = pd.Index(value)\n\n            if value.hasnans:\n                raise ValueError(\n                    \"Kindly ensure there are no nulls in the array provided.\"\n                )\n\n            if not value.is_unique:\n                raise ValueError(\n                    \"Kindly provide unique, \"\n                    \"non-null values for the array provided.\"\n                )\n\n            if value.empty:\n                raise ValueError(\n                    \"Kindly ensure there is at least \"\n                    \"one non-null value in the array provided.\"\n                )\n\n            # uniques, without nulls\n            uniques = df[column_name].factorize(sort=False)[-1]\n            if uniques.empty:\n                raise ValueError(\n                    \"Kindly ensure there is at least \"\n                    f\"one non-null value in {column_name}.\"\n                )\n\n            missing = uniques.difference(value, sort=False)\n            if not missing.empty and (uniques.size > missing.size):\n                warnings.warn(\n                    f\"Values {tuple(missing)} are missing from \"\n                    f\"the provided categories {value} \"\n                    f\"for {column_name}; this may create nulls \"\n                    \"in the new categorical column.\",\n                    UserWarning,\n                    stacklevel=2,\n                )\n\n            elif uniques.equals(missing):\n                warnings.warn(\n                    f\"None of the values in {column_name} are in \"\n                    f\"{value}; this might create nulls for all values \"\n                    f\"in the new categorical column.\",\n                    UserWarning,\n                    stacklevel=2,\n                )\n\n        elif isinstance(value, str):\n            category_order_types = {ent.value for ent in _CategoryOrder}\n            if value.lower() not in category_order_types:\n                raise ValueError(\n                    \"Argument should be one of 'appearance' or 'sort'.\"\n                )\n\n        categories_dict[column_name] = value\n\n    return categories_dict\n\n\nclass _CategoryOrder(Enum):\n    \"\"\"\n    order types for encode_categorical.\n    \"\"\"\n\n    SORT = \"sort\"\n    APPEARANCE = \"appearance\"\n", "path": "janitor/functions/encode_categorical.py"}], "after_files": [{"content": "import warnings\nfrom enum import Enum\nfrom typing import Hashable, Iterable, Union\n\nimport numpy as np\nimport pandas as pd\nimport pandas_flavor as pf\nfrom pandas.api.types import is_list_like\n\nfrom janitor.utils import check, check_column, deprecated_alias\n\n\n@pf.register_dataframe_method\n@deprecated_alias(columns=\"column_names\")\ndef encode_categorical(\n    df: pd.DataFrame,\n    column_names: Union[str, Iterable[str], Hashable] = None,\n    **kwargs,\n) -> pd.DataFrame:\n    \"\"\"Encode the specified columns with Pandas' [category dtype][cat].\n\n    [cat]: http://pandas.pydata.org/pandas-docs/stable/user_guide/categorical.html\n\n    It is syntactic sugar around `pd.Categorical`.\n\n    This method does not mutate the original DataFrame.\n\n    Simply pass a string, or a sequence of column names to `column_names`;\n    alternatively, you can pass kwargs, where the keys are the column names\n    and the values can either be None, `sort`, `appearance`\n    or a 1-D array-like object.\n\n    - None: column is cast to an unordered categorical.\n    - `sort`: column is cast to an ordered categorical,\n              with the order defined by the sort-order of the categories.\n    - `appearance`: column is cast to an ordered categorical,\n                    with the order defined by the order of appearance\n                    in the original column.\n    - 1d-array-like object: column is cast to an ordered categorical,\n                            with the categories and order as specified\n                            in the input array.\n\n    `column_names` and `kwargs` parameters cannot be used at the same time.\n\n    Example: Using `column_names`\n\n        >>> import pandas as pd\n        >>> import janitor\n        >>> df = pd.DataFrame({\n        ...     \"foo\": [\"b\", \"b\", \"a\", \"c\", \"b\"],\n        ...     \"bar\": range(4, 9),\n        ... })\n        >>> df\n          foo  bar\n        0   b    4\n        1   b    5\n        2   a    6\n        3   c    7\n        4   b    8\n        >>> df.dtypes\n        foo    object\n        bar     int64\n        dtype: object\n        >>> enc_df = df.encode_categorical(column_names=\"foo\")\n        >>> enc_df.dtypes\n        foo    category\n        bar       int64\n        dtype: object\n        >>> enc_df[\"foo\"].cat.categories\n        Index(['a', 'b', 'c'], dtype='object')\n        >>> enc_df[\"foo\"].cat.ordered\n        False\n\n    Example: Using `kwargs` to specify an ordered categorical.\n\n        >>> import pandas as pd\n        >>> import janitor\n        >>> df = pd.DataFrame({\n        ...     \"foo\": [\"b\", \"b\", \"a\", \"c\", \"b\"],\n        ...     \"bar\": range(4, 9),\n        ... })\n        >>> df.dtypes\n        foo    object\n        bar     int64\n        dtype: object\n        >>> enc_df = df.encode_categorical(foo=\"appearance\")\n        >>> enc_df.dtypes\n        foo    category\n        bar       int64\n        dtype: object\n        >>> enc_df[\"foo\"].cat.categories\n        Index(['b', 'a', 'c'], dtype='object')\n        >>> enc_df[\"foo\"].cat.ordered\n        True\n\n    :param df: A pandas DataFrame object.\n    :param column_names: A column name or an iterable (list or tuple)\n        of column names.\n    :param **kwargs: A mapping from column name to either `None`,\n        `'sort'` or `'appearance'`, or a 1-D array. This is useful\n        in creating categorical columns that are ordered, or\n        if the user needs to explicitly specify the categories.\n    :returns: A pandas DataFrame.\n    :raises ValueError: If both `column_names` and `kwargs` are provided.\n    \"\"\"  # noqa: E501\n\n    if all((column_names, kwargs)):\n        raise ValueError(\n            \"Only one of `column_names` or `kwargs` can be provided.\"\n        )\n    # column_names deal with only category dtype (unordered)\n    # kwargs takes care of scenarios where user wants an ordered category\n    # or user supplies specific categories to create the categorical\n    if column_names is not None:\n        check(\"column_names\", column_names, [list, tuple, Hashable])\n        if isinstance(column_names, Hashable):\n            column_names = [column_names]\n        check_column(df, column_names)\n        dtypes = {col: \"category\" for col in column_names}\n        return df.astype(dtypes)\n\n    return _computations_as_categorical(df, **kwargs)\n\n\ndef _computations_as_categorical(df: pd.DataFrame, **kwargs) -> pd.DataFrame:\n    \"\"\"\n    This function handles cases where\n    categorical columns are created with an order,\n    or specific values supplied for the categories.\n    It uses a kwarg, where the key is the column name,\n    and the value is either a string, or a 1D array.\n    The default for value is None and will return a categorical dtype\n    with no order and categories inferred from the column.\n    A DataFrame, with categorical columns, is returned.\n    \"\"\"\n\n    categories_dict = _as_categorical_checks(df, **kwargs)\n\n    categories_dtypes = {}\n\n    for column_name, value in categories_dict.items():\n        if value is None:\n            cat_dtype = pd.CategoricalDtype()\n        elif isinstance(value, str):\n            if value == _CategoryOrder.SORT.value:\n                _, cat_dtype = df[column_name].factorize(sort=True)\n            else:\n                _, cat_dtype = df[column_name].factorize(sort=False)\n            if cat_dtype.empty:\n                raise ValueError(\n                    \"Kindly ensure there is at least \"\n                    f\"one non-null value in {column_name}.\"\n                )\n            cat_dtype = pd.CategoricalDtype(categories=cat_dtype, ordered=True)\n\n        else:  # 1-D array\n            cat_dtype = pd.CategoricalDtype(categories=value, ordered=True)\n\n        categories_dtypes[column_name] = cat_dtype\n\n    return df.astype(categories_dtypes)\n\n\ndef _as_categorical_checks(df: pd.DataFrame, **kwargs) -> dict:\n    \"\"\"\n    This function raises errors if columns in `kwargs` are\n    absent from the dataframe's columns.\n    It also raises errors if the value in `kwargs`\n    is not a string (`'appearance'` or `'sort'`), or a 1D array.\n\n    This function is executed before proceeding to the computation phase.\n\n    If all checks pass, a dictionary of column names and value is returned.\n\n    :param df: The pandas DataFrame object.\n    :param **kwargs: A pairing of column name and value.\n    :returns: A dictionary.\n    :raises TypeError: If `value` is not a 1-D array, or a string.\n    :raises ValueError: If `value` is a 1-D array, and contains nulls,\n        or is non-unique.\n    \"\"\"\n\n    check_column(df, kwargs)\n\n    categories_dict = {}\n\n    for column_name, value in kwargs.items():\n        # type check\n        if (value is not None) and not (\n            is_list_like(value) or isinstance(value, str)\n        ):\n            raise TypeError(f\"{value} should be list-like or a string.\")\n        if is_list_like(value):\n            if not hasattr(value, \"shape\"):\n                value = np.asarray(value)\n\n            if (value.ndim != 1) or isinstance(value, pd.MultiIndex):\n                raise ValueError(\n                    f\"{value} is not a 1-D array. \"\n                    \"Kindly provide a 1-D array-like object.\"\n                )\n\n            if not isinstance(value, (pd.Series, pd.Index)):\n                value = pd.Index(value)\n\n            if value.hasnans:\n                raise ValueError(\n                    \"Kindly ensure there are no nulls in the array provided.\"\n                )\n\n            if not value.is_unique:\n                raise ValueError(\n                    \"Kindly provide unique, \"\n                    \"non-null values for the array provided.\"\n                )\n\n            if value.empty:\n                raise ValueError(\n                    \"Kindly ensure there is at least \"\n                    \"one non-null value in the array provided.\"\n                )\n\n            # uniques, without nulls\n            uniques = df[column_name].factorize(sort=False)[-1]\n            if uniques.empty:\n                raise ValueError(\n                    \"Kindly ensure there is at least \"\n                    f\"one non-null value in {column_name}.\"\n                )\n\n            missing = uniques.difference(value, sort=False)\n            if not missing.empty and (uniques.size > missing.size):\n                warnings.warn(\n                    f\"Values {tuple(missing)} are missing from \"\n                    f\"the provided categories {value} \"\n                    f\"for {column_name}; this may create nulls \"\n                    \"in the new categorical column.\",\n                    UserWarning,\n                    stacklevel=2,\n                )\n\n            elif uniques.equals(missing):\n                warnings.warn(\n                    f\"None of the values in {column_name} are in \"\n                    f\"{value}; this might create nulls for all values \"\n                    f\"in the new categorical column.\",\n                    UserWarning,\n                    stacklevel=2,\n                )\n\n        elif isinstance(value, str):\n            category_order_types = {ent.value for ent in _CategoryOrder}\n            if value.lower() not in category_order_types:\n                raise ValueError(\n                    \"Argument should be one of 'appearance' or 'sort'.\"\n                )\n\n        categories_dict[column_name] = value\n\n    return categories_dict\n\n\nclass _CategoryOrder(Enum):\n    \"\"\"\n    order types for encode_categorical.\n    \"\"\"\n\n    SORT = \"sort\"\n    APPEARANCE = \"appearance\"\n", "path": "janitor/functions/encode_categorical.py"}]}