We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ENH] Decorator utility for handling NAs and errors (for transform_column)
# Brief Description

If say `df["x"]` is a string column with NAs, then the routine `df["x"].apply(lambda x : x+"1")` doesn't work due to the NAs. It would be useful to have a decorator such that 
```python
df.transform_column("x", skipna(lambda x : x + "1"))
``` 
would work

# Example API

```python
from janitor.func_utils import skipna, skiperror

df.transform_column("x", skipna(lambda x : x + "1"))
df.transform_column("y", skiperror(func))

@skiperror
def foo(x):
    ...
    pass
```

# Example implementation

```python

def skipna(f):
    def _wrapped(x):
        if type(x) is float and np.isnan(x) or x is None:
            return np.nan
        else:
            return f(x)

    return _wrapped


def skiperror(f, return_val=None, return_x=False):
    def _wrapped(x):
        try:
            return f(x)
        except:
            if return_x:
                return x
            return return_val

    return _wrapped
```


--- 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/utils.py`
Content:
```
1 """ Miscellaneous internal PyJanitor helper functions. """
2 
3 import functools
4 import warnings
5 from typing import Callable, Dict, List, Union
6 
7 import pandas as pd
8 
9 from .errors import JanitorError
10 
11 
12 def check(varname: str, value, expected_types: list):
13     """
14     One-liner syntactic sugar for checking types.
15 
16     Should be used like this::
17 
18         check('x', x, [int, float])
19 
20     :param varname: The name of the variable.
21     :param value: The value of the varname.
22     :param expected_types: The types we expect the item to be.
23     :returns: TypeError if data is not the expected type.
24     """
25     is_expected_type = False
26     for t in expected_types:
27         if isinstance(value, t):
28             is_expected_type = True
29             break
30 
31     if not is_expected_type:
32         raise TypeError(
33             "{varname} should be one of {expected_types}".format(
34                 varname=varname, expected_types=expected_types
35             )
36         )
37 
38 
39 def _clean_accounting_column(x: str) -> float:
40     """
41     Perform the logic for the `cleaning_style == "accounting"` attribute.
42 
43     This is a private function, not intended to be used outside of
44     ``currency_column_to_numeric``.
45 
46     It is intended to be used in a pandas `apply` method.
47 
48     :returns: An object with a cleaned column.
49     """
50     y = x.strip()
51     y = y.replace(",", "")
52     y = y.replace(")", "")
53     y = y.replace("(", "-")
54     if y == "-":
55         return 0.00
56     return float(y)
57 
58 
59 def _currency_column_to_numeric(x, cast_non_numeric=None) -> str:
60     """
61     Perform logic for changing cell values.
62 
63     This is a private function intended to be used only in
64     ``currency_column_to_numeric``.
65 
66     It is intended to be used in a pandas `apply` method, after being passed
67     through `partial`.
68     """
69     acceptable_currency_characters = {
70         "-",
71         ".",
72         "1",
73         "2",
74         "3",
75         "4",
76         "5",
77         "6",
78         "7",
79         "8",
80         "9",
81         "0",
82     }
83     if len(x) == 0:
84         return "ORIGINAL_NA"
85 
86     if cast_non_numeric:
87         if x in cast_non_numeric.keys():
88             check(
89                 "{%r: %r}" % (x, str(cast_non_numeric[x])),
90                 cast_non_numeric[x],
91                 [int, float],
92             )
93             return cast_non_numeric[x]
94         else:
95             return "".join(i for i in x if i in acceptable_currency_characters)
96     else:
97         return "".join(i for i in x if i in acceptable_currency_characters)
98 
99 
100 def _replace_empty_string_with_none(x):
101     if isinstance(x, int):
102         return x
103 
104     elif isinstance(x, float):
105         return x
106 
107     elif len(x):
108         return x
109 
110 
111 def _replace_original_empty_string_with_none(x):
112     if x != "ORIGINAL_NA":
113         return x
114 
115 
116 def _strip_underscores(
117     df: pd.DataFrame, strip_underscores: Union[str, bool] = None
118 ) -> pd.DataFrame:
119     """
120     Strip underscores from DataFrames column names.
121 
122     Underscores can be stripped from the beginning, end or both.
123 
124     .. code-block:: python
125 
126         df = _strip_underscores(df, strip_underscores='left')
127 
128     :param df: The pandas DataFrame object.
129     :param strip_underscores: (optional) Removes the outer underscores from all
130         column names. Default None keeps outer underscores. Values can be
131         either 'left', 'right' or 'both' or the respective shorthand 'l', 'r'
132         and True.
133     :returns: A pandas DataFrame with underscores removed.
134     """
135 
136     df = df.rename(
137         columns=lambda x: _strip_underscores_func(x, strip_underscores)
138     )
139     return df
140 
141 
142 def _strip_underscores_func(
143     col: str, strip_underscores: Union[str, bool] = None
144 ) -> pd.DataFrame:
145     """Strip underscores from a string."""
146     underscore_options = [None, "left", "right", "both", "l", "r", True]
147     if strip_underscores not in underscore_options:
148         raise JanitorError(
149             f"strip_underscores must be one of: {underscore_options}"
150         )
151 
152     if strip_underscores in ["left", "l"]:
153         col = col.lstrip("_")
154     elif strip_underscores in ["right", "r"]:
155         col = col.rstrip("_")
156     elif strip_underscores == "both" or strip_underscores is True:
157         col = col.strip("_")
158     return col
159 
160 
161 def import_message(submodule: str, package: str, installation: str):
162     """
163     Generic message for indicating to the user when a function relies on an
164     optional module / package that is not currently installed. Includes
165     installation instructions. Used in `chemistry.py` and `biology.py`.
166 
167     :param submodule: pyjanitor submodule that needs an external dependency.
168     :param package: External package this submodule relies on.
169     :param installation: Command to execute in the environment to install
170         the package.
171     """
172 
173     print(
174         f"To use the janitor submodule {submodule}, you need to install "
175         f"{package}."
176     )
177     print()
178     print(f"To do so, use the following command:")
179     print()
180     print(f"    {installation}")
181 
182 
183 def idempotent(func: Callable, df: pd.DataFrame, *args, **kwargs):
184     """
185     Raises error if a function operating on a `DataFrame` is not idempotent,
186     that is, `func(func(df)) = func(df)` is not true for all `df`.
187 
188     :param func: A python method.
189     :param df: A pandas `DataFrame`.
190     :param args: Positional arguments supplied to the method.
191     :param kwargs: Keyword arguments supplied to the method.
192     :raises ValueError: If `func` is found to not be idempotent for the given
193         `DataFrame` `df`.
194     """
195     if not func(df, *args, **kwargs) == func(
196         func(df, *args, **kwargs), *args, **kwargs
197     ):
198         raise ValueError(
199             "Supplied function is not idempotent for the given " "DataFrame."
200         )
201 
202 
203 def deprecated_alias(**aliases) -> Callable:
204     """
205     Used as a decorator when deprecating old function argument names, while
206     keeping backwards compatibility.
207 
208     Implementation is inspired from `StackOverflow`_.
209 
210     .. _StackOverflow: https://stackoverflow.com/questions/49802412/how-to-implement-deprecation-in-python-with-argument-alias  # noqa: E501
211 
212     Functional usage example:
213 
214     .. code-block:: python
215 
216         @deprecated_alias(a='alpha', b='beta')
217         def simple_sum(alpha, beta):
218             return alpha + beta
219 
220     :param aliases: Dictionary of aliases for a function's arguments.
221     :return: Your original function wrapped with the kwarg redirection
222         function.
223     """
224 
225     def decorator(func):
226         @functools.wraps(func)
227         def wrapper(*args, **kwargs):
228             rename_kwargs(func.__name__, kwargs, aliases)
229             return func(*args, **kwargs)
230 
231         return wrapper
232 
233     return decorator
234 
235 
236 def rename_kwargs(func_name: str, kwargs: Dict, aliases: Dict):
237     """
238     Used to update deprecated argument names with new names. Throws a
239     TypeError if both arguments are provided, and warns if old alias is used.
240 
241     Implementation is inspired from `StackOverflow`_.
242 
243     .. _StackOverflow: https://stackoverflow.com/questions/49802412/how-to-implement-deprecation-in-python-with-argument-alias  # noqa: E501
244 
245     :param func_name: name of decorated function.
246     :param kwargs: Arguments supplied to the method.
247     :param aliases: Dictionary of aliases for a function's arguments.
248     :return: Nothing; the passed `kwargs` are modified directly.
249     """
250     for old_alias, new_alias in aliases.items():
251         if old_alias in kwargs:
252             if new_alias in kwargs:
253                 raise TypeError(
254                     f"{func_name} received both {old_alias} and {new_alias}"
255                 )
256             warnings.warn(
257                 f"{old_alias} is deprecated; use {new_alias}",
258                 DeprecationWarning,
259             )
260             kwargs[new_alias] = kwargs.pop(old_alias)
261 
262 
263 def check_column(
264     df: pd.DataFrame, old_column_names: List, present: bool = True
265 ):
266     """
267     One-liner syntactic sugar for checking the presence or absence of a column.
268 
269     Should be used like this::
270 
271         check(df, ['a', 'b'], present=True)
272 
273     :param df: The name of the variable.
274     :param old_column_names: A list of column names we want to check to see if
275         present (or absent) in df.
276     :param present: If True (default), checks to see if all of old_column_names
277         are in df.columns. If False, checks that none of old_column_names are
278         in df.columns.
279     :returns: ValueError if data is not the expected type.
280     """
281     for column_name in old_column_names:
282         if present:
283             if column_name not in df.columns:
284                 raise ValueError(
285                     f"{column_name} not present in dataframe columns!"
286                 )
287         else:  # Tests for exclusion
288             if column_name in df.columns:
289                 raise ValueError(
290                     f"{column_name} already present in dataframe columns!"
291                 )
292
```
--- 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/utils.py b/janitor/utils.py
--- a/janitor/utils.py
+++ b/janitor/utils.py
@@ -4,6 +4,7 @@
 import warnings
 from typing import Callable, Dict, List, Union
 
+import numpy as np
 import pandas as pd
 
 from .errors import JanitorError
@@ -289,3 +290,66 @@
                 raise ValueError(
                     f"{column_name} already present in dataframe columns!"
                 )
+
+
+def skipna(f: Callable) -> Callable:
+    """
+    Decorator for escaping np.nan and None in a function
+
+    Should be used like this::
+
+        df[column].apply(skipna(transform))
+
+    or::
+
+        @skipna
+        def transform(x):
+            pass
+
+    :param f: the function to be wrapped
+    :returns: _wrapped, the wrapped function
+    """
+
+    def _wrapped(x, *args, **kwargs):
+        if (type(x) is float and np.isnan(x)) or x is None:
+            return np.nan
+        else:
+            return f(x, *args, **kwargs)
+
+    return _wrapped
+
+
+def skiperror(
+    f: Callable, return_x: bool = False, return_val=np.nan
+) -> Callable:
+    """
+    Decorator for escaping errors in a function
+
+    Should be used like this::
+
+        df[column].apply(
+            skiperror(transform, return_val=3, return_x=False))
+
+    or::
+
+        @skiperror(return_val=3, return_x=False)
+        def transform(x):
+            pass
+
+    :param f: the function to be wrapped
+    :param return_x: whether or not the original value that caused error
+        should be returned
+    :param return_val: the value to be returned when an error hits.
+        Ignored if return_x is True
+    :returns: _wrapped, the wrapped function
+    """
+
+    def _wrapped(x, *args, **kwargs):
+        try:
+            return f(x, *args, **kwargs)
+        except Exception:
+            if return_x:
+                return x
+            return return_val
+
+    return _wrapped

{"golden_diff": "diff --git a/janitor/utils.py b/janitor/utils.py\n--- a/janitor/utils.py\n+++ b/janitor/utils.py\n@@ -4,6 +4,7 @@\n import warnings\n from typing import Callable, Dict, List, Union\n \n+import numpy as np\n import pandas as pd\n \n from .errors import JanitorError\n@@ -289,3 +290,66 @@\n                 raise ValueError(\n                     f\"{column_name} already present in dataframe columns!\"\n                 )\n+\n+\n+def skipna(f: Callable) -> Callable:\n+    \"\"\"\n+    Decorator for escaping np.nan and None in a function\n+\n+    Should be used like this::\n+\n+        df[column].apply(skipna(transform))\n+\n+    or::\n+\n+        @skipna\n+        def transform(x):\n+            pass\n+\n+    :param f: the function to be wrapped\n+    :returns: _wrapped, the wrapped function\n+    \"\"\"\n+\n+    def _wrapped(x, *args, **kwargs):\n+        if (type(x) is float and np.isnan(x)) or x is None:\n+            return np.nan\n+        else:\n+            return f(x, *args, **kwargs)\n+\n+    return _wrapped\n+\n+\n+def skiperror(\n+    f: Callable, return_x: bool = False, return_val=np.nan\n+) -> Callable:\n+    \"\"\"\n+    Decorator for escaping errors in a function\n+\n+    Should be used like this::\n+\n+        df[column].apply(\n+            skiperror(transform, return_val=3, return_x=False))\n+\n+    or::\n+\n+        @skiperror(return_val=3, return_x=False)\n+        def transform(x):\n+            pass\n+\n+    :param f: the function to be wrapped\n+    :param return_x: whether or not the original value that caused error\n+        should be returned\n+    :param return_val: the value to be returned when an error hits.\n+        Ignored if return_x is True\n+    :returns: _wrapped, the wrapped function\n+    \"\"\"\n+\n+    def _wrapped(x, *args, **kwargs):\n+        try:\n+            return f(x, *args, **kwargs)\n+        except Exception:\n+            if return_x:\n+                return x\n+            return return_val\n+\n+    return _wrapped\n", "issue": "[ENH] Decorator utility for handling NAs and errors (for transform_column)\n# Brief Description\r\n\r\nIf say `df[\"x\"]` is a string column with NAs, then the routine `df[\"x\"].apply(lambda x : x+\"1\")` doesn't work due to the NAs. It would be useful to have a decorator such that \r\n```python\r\ndf.transform_column(\"x\", skipna(lambda x : x + \"1\"))\r\n``` \r\nwould work\r\n\r\n# Example API\r\n\r\n```python\r\nfrom janitor.func_utils import skipna, skiperror\r\n\r\ndf.transform_column(\"x\", skipna(lambda x : x + \"1\"))\r\ndf.transform_column(\"y\", skiperror(func))\r\n\r\n@skiperror\r\ndef foo(x):\r\n    ...\r\n    pass\r\n```\r\n\r\n# Example implementation\r\n\r\n```python\r\n\r\ndef skipna(f):\r\n    def _wrapped(x):\r\n        if type(x) is float and np.isnan(x) or x is None:\r\n            return np.nan\r\n        else:\r\n            return f(x)\r\n\r\n    return _wrapped\r\n\r\n\r\ndef skiperror(f, return_val=None, return_x=False):\r\n    def _wrapped(x):\r\n        try:\r\n            return f(x)\r\n        except:\r\n            if return_x:\r\n                return x\r\n            return return_val\r\n\r\n    return _wrapped\r\n```\r\n\n", "before_files": [{"content": "\"\"\" Miscellaneous internal PyJanitor helper functions. \"\"\"\n\nimport functools\nimport warnings\nfrom typing import Callable, Dict, List, Union\n\nimport pandas as pd\n\nfrom .errors import JanitorError\n\n\ndef check(varname: str, value, expected_types: list):\n    \"\"\"\n    One-liner syntactic sugar for checking types.\n\n    Should be used like this::\n\n        check('x', x, [int, float])\n\n    :param varname: The name of the variable.\n    :param value: The value of the varname.\n    :param expected_types: The types we expect the item to be.\n    :returns: TypeError if data is not the expected type.\n    \"\"\"\n    is_expected_type = False\n    for t in expected_types:\n        if isinstance(value, t):\n            is_expected_type = True\n            break\n\n    if not is_expected_type:\n        raise TypeError(\n            \"{varname} should be one of {expected_types}\".format(\n                varname=varname, expected_types=expected_types\n            )\n        )\n\n\ndef _clean_accounting_column(x: str) -> float:\n    \"\"\"\n    Perform the logic for the `cleaning_style == \"accounting\"` attribute.\n\n    This is a private function, not intended to be used outside of\n    ``currency_column_to_numeric``.\n\n    It is intended to be used in a pandas `apply` method.\n\n    :returns: An object with a cleaned column.\n    \"\"\"\n    y = x.strip()\n    y = y.replace(\",\", \"\")\n    y = y.replace(\")\", \"\")\n    y = y.replace(\"(\", \"-\")\n    if y == \"-\":\n        return 0.00\n    return float(y)\n\n\ndef _currency_column_to_numeric(x, cast_non_numeric=None) -> str:\n    \"\"\"\n    Perform logic for changing cell values.\n\n    This is a private function intended to be used only in\n    ``currency_column_to_numeric``.\n\n    It is intended to be used in a pandas `apply` method, after being passed\n    through `partial`.\n    \"\"\"\n    acceptable_currency_characters = {\n        \"-\",\n        \".\",\n        \"1\",\n        \"2\",\n        \"3\",\n        \"4\",\n        \"5\",\n        \"6\",\n        \"7\",\n        \"8\",\n        \"9\",\n        \"0\",\n    }\n    if len(x) == 0:\n        return \"ORIGINAL_NA\"\n\n    if cast_non_numeric:\n        if x in cast_non_numeric.keys():\n            check(\n                \"{%r: %r}\" % (x, str(cast_non_numeric[x])),\n                cast_non_numeric[x],\n                [int, float],\n            )\n            return cast_non_numeric[x]\n        else:\n            return \"\".join(i for i in x if i in acceptable_currency_characters)\n    else:\n        return \"\".join(i for i in x if i in acceptable_currency_characters)\n\n\ndef _replace_empty_string_with_none(x):\n    if isinstance(x, int):\n        return x\n\n    elif isinstance(x, float):\n        return x\n\n    elif len(x):\n        return x\n\n\ndef _replace_original_empty_string_with_none(x):\n    if x != \"ORIGINAL_NA\":\n        return x\n\n\ndef _strip_underscores(\n    df: pd.DataFrame, strip_underscores: Union[str, bool] = None\n) -> pd.DataFrame:\n    \"\"\"\n    Strip underscores from DataFrames column names.\n\n    Underscores can be stripped from the beginning, end or both.\n\n    .. code-block:: python\n\n        df = _strip_underscores(df, strip_underscores='left')\n\n    :param df: The pandas DataFrame object.\n    :param strip_underscores: (optional) Removes the outer underscores from all\n        column names. Default None keeps outer underscores. Values can be\n        either 'left', 'right' or 'both' or the respective shorthand 'l', 'r'\n        and True.\n    :returns: A pandas DataFrame with underscores removed.\n    \"\"\"\n\n    df = df.rename(\n        columns=lambda x: _strip_underscores_func(x, strip_underscores)\n    )\n    return df\n\n\ndef _strip_underscores_func(\n    col: str, strip_underscores: Union[str, bool] = None\n) -> pd.DataFrame:\n    \"\"\"Strip underscores from a string.\"\"\"\n    underscore_options = [None, \"left\", \"right\", \"both\", \"l\", \"r\", True]\n    if strip_underscores not in underscore_options:\n        raise JanitorError(\n            f\"strip_underscores must be one of: {underscore_options}\"\n        )\n\n    if strip_underscores in [\"left\", \"l\"]:\n        col = col.lstrip(\"_\")\n    elif strip_underscores in [\"right\", \"r\"]:\n        col = col.rstrip(\"_\")\n    elif strip_underscores == \"both\" or strip_underscores is True:\n        col = col.strip(\"_\")\n    return col\n\n\ndef import_message(submodule: str, package: str, installation: str):\n    \"\"\"\n    Generic message for indicating to the user when a function relies on an\n    optional module / package that is not currently installed. Includes\n    installation instructions. Used in `chemistry.py` and `biology.py`.\n\n    :param submodule: pyjanitor submodule that needs an external dependency.\n    :param package: External package this submodule relies on.\n    :param installation: Command to execute in the environment to install\n        the package.\n    \"\"\"\n\n    print(\n        f\"To use the janitor submodule {submodule}, you need to install \"\n        f\"{package}.\"\n    )\n    print()\n    print(f\"To do so, use the following command:\")\n    print()\n    print(f\"    {installation}\")\n\n\ndef idempotent(func: Callable, df: pd.DataFrame, *args, **kwargs):\n    \"\"\"\n    Raises error if a function operating on a `DataFrame` is not idempotent,\n    that is, `func(func(df)) = func(df)` is not true for all `df`.\n\n    :param func: A python method.\n    :param df: A pandas `DataFrame`.\n    :param args: Positional arguments supplied to the method.\n    :param kwargs: Keyword arguments supplied to the method.\n    :raises ValueError: If `func` is found to not be idempotent for the given\n        `DataFrame` `df`.\n    \"\"\"\n    if not func(df, *args, **kwargs) == func(\n        func(df, *args, **kwargs), *args, **kwargs\n    ):\n        raise ValueError(\n            \"Supplied function is not idempotent for the given \" \"DataFrame.\"\n        )\n\n\ndef deprecated_alias(**aliases) -> Callable:\n    \"\"\"\n    Used as a decorator when deprecating old function argument names, while\n    keeping backwards compatibility.\n\n    Implementation is inspired from `StackOverflow`_.\n\n    .. _StackOverflow: https://stackoverflow.com/questions/49802412/how-to-implement-deprecation-in-python-with-argument-alias  # noqa: E501\n\n    Functional usage example:\n\n    .. code-block:: python\n\n        @deprecated_alias(a='alpha', b='beta')\n        def simple_sum(alpha, beta):\n            return alpha + beta\n\n    :param aliases: Dictionary of aliases for a function's arguments.\n    :return: Your original function wrapped with the kwarg redirection\n        function.\n    \"\"\"\n\n    def decorator(func):\n        @functools.wraps(func)\n        def wrapper(*args, **kwargs):\n            rename_kwargs(func.__name__, kwargs, aliases)\n            return func(*args, **kwargs)\n\n        return wrapper\n\n    return decorator\n\n\ndef rename_kwargs(func_name: str, kwargs: Dict, aliases: Dict):\n    \"\"\"\n    Used to update deprecated argument names with new names. Throws a\n    TypeError if both arguments are provided, and warns if old alias is used.\n\n    Implementation is inspired from `StackOverflow`_.\n\n    .. _StackOverflow: https://stackoverflow.com/questions/49802412/how-to-implement-deprecation-in-python-with-argument-alias  # noqa: E501\n\n    :param func_name: name of decorated function.\n    :param kwargs: Arguments supplied to the method.\n    :param aliases: Dictionary of aliases for a function's arguments.\n    :return: Nothing; the passed `kwargs` are modified directly.\n    \"\"\"\n    for old_alias, new_alias in aliases.items():\n        if old_alias in kwargs:\n            if new_alias in kwargs:\n                raise TypeError(\n                    f\"{func_name} received both {old_alias} and {new_alias}\"\n                )\n            warnings.warn(\n                f\"{old_alias} is deprecated; use {new_alias}\",\n                DeprecationWarning,\n            )\n            kwargs[new_alias] = kwargs.pop(old_alias)\n\n\ndef check_column(\n    df: pd.DataFrame, old_column_names: List, present: bool = True\n):\n    \"\"\"\n    One-liner syntactic sugar for checking the presence or absence of a column.\n\n    Should be used like this::\n\n        check(df, ['a', 'b'], present=True)\n\n    :param df: The name of the variable.\n    :param old_column_names: A list of column names we want to check to see if\n        present (or absent) in df.\n    :param present: If True (default), checks to see if all of old_column_names\n        are in df.columns. If False, checks that none of old_column_names are\n        in df.columns.\n    :returns: ValueError if data is not the expected type.\n    \"\"\"\n    for column_name in old_column_names:\n        if present:\n            if column_name not in df.columns:\n                raise ValueError(\n                    f\"{column_name} not present in dataframe columns!\"\n                )\n        else:  # Tests for exclusion\n            if column_name in df.columns:\n                raise ValueError(\n                    f\"{column_name} already present in dataframe columns!\"\n                )\n", "path": "janitor/utils.py"}], "after_files": [{"content": "\"\"\" Miscellaneous internal PyJanitor helper functions. \"\"\"\n\nimport functools\nimport warnings\nfrom typing import Callable, Dict, List, Union\n\nimport numpy as np\nimport pandas as pd\n\nfrom .errors import JanitorError\n\n\ndef check(varname: str, value, expected_types: list):\n    \"\"\"\n    One-liner syntactic sugar for checking types.\n\n    Should be used like this::\n\n        check('x', x, [int, float])\n\n    :param varname: The name of the variable.\n    :param value: The value of the varname.\n    :param expected_types: The types we expect the item to be.\n    :returns: TypeError if data is not the expected type.\n    \"\"\"\n    is_expected_type = False\n    for t in expected_types:\n        if isinstance(value, t):\n            is_expected_type = True\n            break\n\n    if not is_expected_type:\n        raise TypeError(\n            \"{varname} should be one of {expected_types}\".format(\n                varname=varname, expected_types=expected_types\n            )\n        )\n\n\ndef _clean_accounting_column(x: str) -> float:\n    \"\"\"\n    Perform the logic for the `cleaning_style == \"accounting\"` attribute.\n\n    This is a private function, not intended to be used outside of\n    ``currency_column_to_numeric``.\n\n    It is intended to be used in a pandas `apply` method.\n\n    :returns: An object with a cleaned column.\n    \"\"\"\n    y = x.strip()\n    y = y.replace(\",\", \"\")\n    y = y.replace(\")\", \"\")\n    y = y.replace(\"(\", \"-\")\n    if y == \"-\":\n        return 0.00\n    return float(y)\n\n\ndef _currency_column_to_numeric(x, cast_non_numeric=None) -> str:\n    \"\"\"\n    Perform logic for changing cell values.\n\n    This is a private function intended to be used only in\n    ``currency_column_to_numeric``.\n\n    It is intended to be used in a pandas `apply` method, after being passed\n    through `partial`.\n    \"\"\"\n    acceptable_currency_characters = {\n        \"-\",\n        \".\",\n        \"1\",\n        \"2\",\n        \"3\",\n        \"4\",\n        \"5\",\n        \"6\",\n        \"7\",\n        \"8\",\n        \"9\",\n        \"0\",\n    }\n    if len(x) == 0:\n        return \"ORIGINAL_NA\"\n\n    if cast_non_numeric:\n        if x in cast_non_numeric.keys():\n            check(\n                \"{%r: %r}\" % (x, str(cast_non_numeric[x])),\n                cast_non_numeric[x],\n                [int, float],\n            )\n            return cast_non_numeric[x]\n        else:\n            return \"\".join(i for i in x if i in acceptable_currency_characters)\n    else:\n        return \"\".join(i for i in x if i in acceptable_currency_characters)\n\n\ndef _replace_empty_string_with_none(x):\n    if isinstance(x, int):\n        return x\n\n    elif isinstance(x, float):\n        return x\n\n    elif len(x):\n        return x\n\n\ndef _replace_original_empty_string_with_none(x):\n    if x != \"ORIGINAL_NA\":\n        return x\n\n\ndef _strip_underscores(\n    df: pd.DataFrame, strip_underscores: Union[str, bool] = None\n) -> pd.DataFrame:\n    \"\"\"\n    Strip underscores from DataFrames column names.\n\n    Underscores can be stripped from the beginning, end or both.\n\n    .. code-block:: python\n\n        df = _strip_underscores(df, strip_underscores='left')\n\n    :param df: The pandas DataFrame object.\n    :param strip_underscores: (optional) Removes the outer underscores from all\n        column names. Default None keeps outer underscores. Values can be\n        either 'left', 'right' or 'both' or the respective shorthand 'l', 'r'\n        and True.\n    :returns: A pandas DataFrame with underscores removed.\n    \"\"\"\n\n    df = df.rename(\n        columns=lambda x: _strip_underscores_func(x, strip_underscores)\n    )\n    return df\n\n\ndef _strip_underscores_func(\n    col: str, strip_underscores: Union[str, bool] = None\n) -> pd.DataFrame:\n    \"\"\"Strip underscores from a string.\"\"\"\n    underscore_options = [None, \"left\", \"right\", \"both\", \"l\", \"r\", True]\n    if strip_underscores not in underscore_options:\n        raise JanitorError(\n            f\"strip_underscores must be one of: {underscore_options}\"\n        )\n\n    if strip_underscores in [\"left\", \"l\"]:\n        col = col.lstrip(\"_\")\n    elif strip_underscores in [\"right\", \"r\"]:\n        col = col.rstrip(\"_\")\n    elif strip_underscores == \"both\" or strip_underscores is True:\n        col = col.strip(\"_\")\n    return col\n\n\ndef import_message(submodule: str, package: str, installation: str):\n    \"\"\"\n    Generic message for indicating to the user when a function relies on an\n    optional module / package that is not currently installed. Includes\n    installation instructions. Used in `chemistry.py` and `biology.py`.\n\n    :param submodule: pyjanitor submodule that needs an external dependency.\n    :param package: External package this submodule relies on.\n    :param installation: Command to execute in the environment to install\n        the package.\n    \"\"\"\n\n    print(\n        f\"To use the janitor submodule {submodule}, you need to install \"\n        f\"{package}.\"\n    )\n    print()\n    print(f\"To do so, use the following command:\")\n    print()\n    print(f\"    {installation}\")\n\n\ndef idempotent(func: Callable, df: pd.DataFrame, *args, **kwargs):\n    \"\"\"\n    Raises error if a function operating on a `DataFrame` is not idempotent,\n    that is, `func(func(df)) = func(df)` is not true for all `df`.\n\n    :param func: A python method.\n    :param df: A pandas `DataFrame`.\n    :param args: Positional arguments supplied to the method.\n    :param kwargs: Keyword arguments supplied to the method.\n    :raises ValueError: If `func` is found to not be idempotent for the given\n        `DataFrame` `df`.\n    \"\"\"\n    if not func(df, *args, **kwargs) == func(\n        func(df, *args, **kwargs), *args, **kwargs\n    ):\n        raise ValueError(\n            \"Supplied function is not idempotent for the given \" \"DataFrame.\"\n        )\n\n\ndef deprecated_alias(**aliases) -> Callable:\n    \"\"\"\n    Used as a decorator when deprecating old function argument names, while\n    keeping backwards compatibility.\n\n    Implementation is inspired from `StackOverflow`_.\n\n    .. _StackOverflow: https://stackoverflow.com/questions/49802412/how-to-implement-deprecation-in-python-with-argument-alias  # noqa: E501\n\n    Functional usage example:\n\n    .. code-block:: python\n\n        @deprecated_alias(a='alpha', b='beta')\n        def simple_sum(alpha, beta):\n            return alpha + beta\n\n    :param aliases: Dictionary of aliases for a function's arguments.\n    :return: Your original function wrapped with the kwarg redirection\n        function.\n    \"\"\"\n\n    def decorator(func):\n        @functools.wraps(func)\n        def wrapper(*args, **kwargs):\n            rename_kwargs(func.__name__, kwargs, aliases)\n            return func(*args, **kwargs)\n\n        return wrapper\n\n    return decorator\n\n\ndef rename_kwargs(func_name: str, kwargs: Dict, aliases: Dict):\n    \"\"\"\n    Used to update deprecated argument names with new names. Throws a\n    TypeError if both arguments are provided, and warns if old alias is used.\n\n    Implementation is inspired from `StackOverflow`_.\n\n    .. _StackOverflow: https://stackoverflow.com/questions/49802412/how-to-implement-deprecation-in-python-with-argument-alias  # noqa: E501\n\n    :param func_name: name of decorated function.\n    :param kwargs: Arguments supplied to the method.\n    :param aliases: Dictionary of aliases for a function's arguments.\n    :return: Nothing; the passed `kwargs` are modified directly.\n    \"\"\"\n    for old_alias, new_alias in aliases.items():\n        if old_alias in kwargs:\n            if new_alias in kwargs:\n                raise TypeError(\n                    f\"{func_name} received both {old_alias} and {new_alias}\"\n                )\n            warnings.warn(\n                f\"{old_alias} is deprecated; use {new_alias}\",\n                DeprecationWarning,\n            )\n            kwargs[new_alias] = kwargs.pop(old_alias)\n\n\ndef check_column(\n    df: pd.DataFrame, old_column_names: List, present: bool = True\n):\n    \"\"\"\n    One-liner syntactic sugar for checking the presence or absence of a column.\n\n    Should be used like this::\n\n        check(df, ['a', 'b'], present=True)\n\n    :param df: The name of the variable.\n    :param old_column_names: A list of column names we want to check to see if\n        present (or absent) in df.\n    :param present: If True (default), checks to see if all of old_column_names\n        are in df.columns. If False, checks that none of old_column_names are\n        in df.columns.\n    :returns: ValueError if data is not the expected type.\n    \"\"\"\n    for column_name in old_column_names:\n        if present:\n            if column_name not in df.columns:\n                raise ValueError(\n                    f\"{column_name} not present in dataframe columns!\"\n                )\n        else:  # Tests for exclusion\n            if column_name in df.columns:\n                raise ValueError(\n                    f\"{column_name} already present in dataframe columns!\"\n                )\n\n\ndef skipna(f: Callable) -> Callable:\n    \"\"\"\n    Decorator for escaping np.nan and None in a function\n\n    Should be used like this::\n\n        df[column].apply(skipna(transform))\n\n    or::\n\n        @skipna\n        def transform(x):\n            pass\n\n    :param f: the function to be wrapped\n    :returns: _wrapped, the wrapped function\n    \"\"\"\n\n    def _wrapped(x, *args, **kwargs):\n        if (type(x) is float and np.isnan(x)) or x is None:\n            return np.nan\n        else:\n            return f(x, *args, **kwargs)\n\n    return _wrapped\n\n\ndef skiperror(\n    f: Callable, return_x: bool = False, return_val=np.nan\n) -> Callable:\n    \"\"\"\n    Decorator for escaping errors in a function\n\n    Should be used like this::\n\n        df[column].apply(\n            skiperror(transform, return_val=3, return_x=False))\n\n    or::\n\n        @skiperror(return_val=3, return_x=False)\n        def transform(x):\n            pass\n\n    :param f: the function to be wrapped\n    :param return_x: whether or not the original value that caused error\n        should be returned\n    :param return_val: the value to be returned when an error hits.\n        Ignored if return_x is True\n    :returns: _wrapped, the wrapped function\n    \"\"\"\n\n    def _wrapped(x, *args, **kwargs):\n        try:\n            return f(x, *args, **kwargs)\n        except Exception:\n            if return_x:\n                return x\n            return return_val\n\n    return _wrapped\n", "path": "janitor/utils.py"}]}