We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Case Sensitive True/False kwarg on clean_names
Using pyjanitor again today I realised I didn't want to change capitals to lowercase. I think a case sensitive kwarg (default on) would be good.

Happy to do PR for this if you think it is a good idea. One Q on this, what would should the kwarg be called?

Some ideas:
`lower`, `remove_upper`, `drop_case`, `case_sensitive`,...

--- 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.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 import datetime as dt
3 from functools import reduce
4 
5 import pandas as pd
6 
7 from .errors import JanitorError
8 
9 import re
10 
11 
12 def _strip_underscores(df, strip_underscores=None):
13     """
14     Strip underscores from the beginning, end or both of the
15     of the DataFrames column names.
16 
17     .. code-block:: python
18 
19         df = _strip_underscores(df, strip_underscores='left')
20 
21     :param df: The pandas DataFrame object.
22     :param strip_underscores: (optional) Removes the outer underscores from all
23         column names. Default None keeps outer underscores. Values can be
24         either 'left', 'right' or 'both' or the respective shorthand 'l', 'r'
25         and True.
26     :returns: A pandas DataFrame.
27     """
28     underscore_options = [None, 'left', 'right', 'both', 'l', 'r', True]
29     if strip_underscores not in underscore_options:
30         raise JanitorError(
31             """strip_underscores must be one of: %s""" % underscore_options
32             )
33 
34     if strip_underscores in ['left', 'l']:
35         df = df.rename(columns=lambda x: x.lstrip('_'))
36     elif strip_underscores in ['right', 'r']:
37         df = df.rename(columns=lambda x: x.rstrip('_'))
38     elif strip_underscores == 'both' or strip_underscores is True:
39         df = df.rename(columns=lambda x: x.strip('_'))
40     return df
41 
42 
43 def clean_names(df, strip_underscores=None):
44     """
45     Clean column names.
46 
47     Takes all column names, converts them to lowercase, then replaces all
48     spaces with underscores.
49 
50     Functional usage example:
51 
52     .. code-block:: python
53 
54         df = clean_names(df)
55 
56     Method chaining example:
57 
58     .. code-block:: python
59 
60         df = pd.DataFrame(...)
61         df = jn.DataFrame(df).clean_names()
62 
63     :param df: The pandas DataFrame object.
64     :param strip_underscores: (optional) Removes the outer underscores from all
65         column names. Default None keeps outer underscores. Values can be
66         either 'left', 'right' or 'both' or the respective shorthand 'l', 'r'
67         and True.
68     :returns: A pandas DataFrame.
69     """
70     df = df.rename(
71         columns=lambda x: x.lower()
72                            .replace(' ', '_')
73                            .replace('/', '_')
74                            .replace(':', '_')
75                            .replace("'", '')
76                            .replace(u'’', '')
77                            .replace(',', '_')
78                            .replace('?', '_')
79                            .replace('-', '_')
80                            .replace('(', '_')
81                            .replace(')', '_')
82                            .replace('.', '_')
83     )
84 
85     df = df.rename(columns=lambda x: re.sub('_+', '_', x))
86     df = _strip_underscores(df, strip_underscores)
87     return df
88 
89 
90 def remove_empty(df):
91     """
92     Drop all rows and columns that are completely null.
93 
94     Implementation is shamelessly copied from `StackOverflow`_.
95 
96     .. _StackOverflow: https://stackoverflow.com/questions/38884538/python-pandas-find-all-rows-where-all-values-are-nan  # noqa: E501
97 
98     Functional usage example:
99 
100     .. code-block:: python
101 
102         df = remove_empty(df)
103 
104     Method chaining example:
105 
106     .. code-block:: python
107 
108         df = pd.DataFrame(...)
109         df = jn.DataFrame(df).remove_empty()
110 
111     :param df: The pandas DataFrame object.
112     :returns: A pandas DataFrame.
113     """
114 
115     nanrows = df.index[df.isnull().all(axis=1)]
116     df.drop(index=nanrows, inplace=True)
117 
118     nancols = df.columns[df.isnull().all(axis=0)]
119     df.drop(columns=nancols, inplace=True)
120 
121     return df
122 
123 
124 def get_dupes(df, columns=None):
125     """
126     Returns all duplicate rows.
127 
128     Functional usage example:
129 
130     .. code-block:: python
131 
132         get_dupes(df)
133 
134     Method chaining example:
135 
136     .. code-block:: python
137 
138         df = pd.DataFrame(...)
139         jn.DataFrame(df).get_dupes()
140 
141     :param df: The pandas DataFrame object.
142     :param str/iterable columns: (optional) A column name or an iterable (list
143         or tuple) of column names. Following pandas API, this only considers
144         certain columns for identifying duplicates. Defaults to using all
145         columns.
146     :returns: The duplicate rows, as a pandas DataFrame.
147     """
148     dupes = df.duplicated(subset=columns, keep=False)
149     return df[dupes == True]  # noqa: E712
150 
151 
152 def encode_categorical(df, columns):
153     """
154     Encode the specified columns as categorical.
155 
156     Functional usage example:
157 
158     .. code-block:: python
159 
160         encode_categorical(df, columns="my_categorical_column")  # one way
161 
162     Method chaining example:
163 
164     .. code-block:: python
165 
166         df = pd.DataFrame(...)
167         categorical_cols = ['col1', 'col2', 'col4']
168         jn.DataFrame(df).encode_categorical(columns=categorical_cols)
169 
170     :param df: The pandas DataFrame object.
171     :param str/iterable columns: A column name or an iterable (list or tuple)
172         of column names.
173     :returns: A pandas DataFrame
174     """
175     if isinstance(columns, list) or isinstance(columns, tuple):
176         for col in columns:
177             assert col in df.columns, \
178                 JanitorError("{col} missing from dataframe columns!".format(col=col))  # noqa: E501
179             df[col] = pd.Categorical(df[col])
180     elif isinstance(columns, str):
181         df[columns] = pd.Categorical(df[columns])
182     else:
183         raise JanitorError('kwarg `columns` must be a string or iterable!')
184     return df
185 
186 
187 def get_features_targets(df, target_columns, feature_columns=None):
188     """
189     Get the features and targets as separate DataFrames/Series.
190 
191     The behaviour is as such:
192 
193     - `target_columns` is mandatory.
194     - If `feature_columns` is present, then we will respect the column names
195       inside there.
196     - If `feature_columns` is not passed in, then we will assume that the
197       rest of the columns are feature columns, and return them.
198 
199     Functional usage example:
200 
201     .. code-block:: python
202 
203         X, y = get_features_targets(df, target_columns="measurement")
204 
205     Method chaining example:
206 
207     .. code-block:: python
208 
209         df = pd.DataFrame(...)
210         target_cols = ['output1', 'output2']
211         X, y = jn.DataFrame(df).get_features_targets(target_columns=target_cols)  # noqa: E501
212 
213     :param df: The pandas DataFrame object.
214     :param str/iterable target_columns: Either a column name or an iterable
215         (list or tuple) of column names that are the target(s) to be predicted.
216     :param str/iterable feature_columns: (optional) The column name or iterable
217         of column names that are the features (a.k.a. predictors) used to
218         predict the targets.
219     :returns: (X, Y) the feature matrix (X) and the target matrix (Y). Both are
220         pandas DataFrames.
221     """
222     Y = df[target_columns]
223 
224     if feature_columns:
225         X = df[feature_columns]
226     else:
227         if isinstance(target_columns, str):
228             xcols = [c for c in df.columns if target_columns != c]
229         elif (isinstance(target_columns, list)
230               or isinstance(target_columns, tuple)):
231             xcols = [c for c in df.columns if c not in target_columns]
232         X = df[xcols]
233     return X, Y
234 
235 
236 def rename_column(df, old, new):
237     """
238     Rename a column in place.
239 
240     Functional usage example:
241 
242     .. code-block:: python
243 
244         df = rename_column("old_column_name", "new_column_name")
245 
246     Method chaining example:
247 
248     .. code-block:: python
249 
250         df = pd.DataFrame(...)
251         df = jn.DataFrame(df).rename_column("old_column_name", "new_column_name")  # noqa: E501
252 
253     This is just syntactic sugar/a convenience function for renaming one column
254     at a time. If you are convinced that there are multiple columns in need of
255     changing, then use the :py:meth:`pandas.DataFrame.rename` method.
256 
257     :param str old: The old column name.
258     :param str new: The new column name.
259     :returns: A pandas DataFrame.
260     """
261     return df.rename(columns={old: new})
262 
263 
264 def coalesce(df, columns, new_column_name):
265     """
266     Coalesces two or more columns of data in order of column names provided.
267 
268     Functional usage example:
269 
270     .. code-block:: python
271 
272         df = coalesce(df, columns=['col1', 'col2'])
273 
274     Method chaining example:
275 
276     .. code-block:: python
277 
278         df = pd.DataFrame(...)
279         df = jn.DataFrame(df).coalesce(['col1', 'col2'])
280 
281 
282     The result of this function is that we take the first non-null value across
283     rows.
284 
285     This is more syntactic diabetes! For R users, this should look familiar to
286     `dplyr`'s `coalesce` function; for Python users, the interface
287     should be more intuitive than the :py:meth:`pandas.Series.combine_first`
288     method (which we're just using internally anyways).
289 
290     :param df: A pandas DataFrame.
291     :param columns: A list of column names.
292     :param str new_column_name: The new column name after combining.
293     :returns: A pandas DataFrame.
294     """
295     series = [df[c] for c in columns]
296 
297     def _coalesce(series1, series2):
298         return series1.combine_first(series2)
299     df = df.drop(columns=columns)
300     df[new_column_name] = reduce(_coalesce, series)  # noqa: F821
301     return df
302 
303 
304 def convert_excel_date(df, column):
305     """
306     Convert Excel's serial date format into Python datetime format.
307 
308     Implementation is also from `Stack Overflow`.
309 
310     .. _Stack Overflow: https://stackoverflow.com/questions/38454403/convert-excel-style-date-with-pandas  # noqa: E501
311 
312     Functional usage example:
313 
314     .. code-block:: python
315 
316         df = convert_excel_date(df, column='date')
317 
318     Method chaining example:
319 
320     .. code-block:: python
321 
322         df = pd.DataFrame(...)
323         df = jn.DataFrame(df).convert_excel_date('date')
324 
325     :param df: A pandas DataFrame.
326     :param str column: A column name.
327     :returns: A pandas DataFrame with corrected dates.
328     """
329     df[column] = (pd.TimedeltaIndex(df[column], unit='d')
330                   + dt.datetime(1899, 12, 30))
331     return df
332 
333 
334 def fill_empty(df, columns, value):
335     """
336     Fill `NaN` values in specified columns with a given value.
337 
338     Super sugary syntax that wraps :py:meth:`pandas.DataFrame.fillna`.
339 
340     Functional usage example:
341 
342     .. code-block:: python
343 
344         df = fill_empty(df, columns=['col1', 'col2'], value=0)
345 
346     Method chaining example:
347 
348     .. code-block:: python
349 
350         df = pd.DataFrame(...)
351         df = jn.DataFrame(df).fill_empty(df, columns='col1', value=0)
352 
353     :param df: A pandas DataFrame.
354     :param columns: Either a `str` or `list` or `tuple`. If a string is passed
355         in, then only that column will be filled; if a list or tuple of strings
356         are passed in, then they will all be filled with the same value.
357     :param value: The value that replaces the `NaN` values.
358     """
359     if isinstance(columns, list) or isinstance(columns, tuple):
360         for col in columns:
361             assert col in df.columns, \
362                 JanitorError("{col} missing from dataframe columns!".format(col=col))  # noqa: E501
363             df[col] = df[col].fillna(value)
364     elif isinstance(columns, str):
365         df[columns] = df[columns].fillna(value)
366     else:
367         raise JanitorError('kwarg `columns` must be a string or iterable!')
368 
369     return df
370
```
--- 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.py b/janitor/functions.py
--- a/janitor/functions.py
+++ b/janitor/functions.py
@@ -40,7 +40,7 @@
     return df
 
 
-def clean_names(df, strip_underscores=None):
+def clean_names(df, strip_underscores=None, preserve_case=False):
     """
     Clean column names.
 
@@ -65,11 +65,18 @@
         column names. Default None keeps outer underscores. Values can be
         either 'left', 'right' or 'both' or the respective shorthand 'l', 'r'
         and True.
+    :param preserve_case: (optional) Allows you to choose whether to make all
+    column names lowercase, or to preserve current cases. Default False makes
+    all characters lowercase.
     :returns: A pandas DataFrame.
     """
+    if preserve_case is False:
+        df = df.rename(
+            columns=lambda x: x.lower()
+        )
+
     df = df.rename(
-        columns=lambda x: x.lower()
-                           .replace(' ', '_')
+        columns=lambda x: x.replace(' ', '_')
                            .replace('/', '_')
                            .replace(':', '_')
                            .replace("'", '')
@@ -111,7 +118,6 @@
     :param df: The pandas DataFrame object.
     :returns: A pandas DataFrame.
     """
-
     nanrows = df.index[df.isnull().all(axis=1)]
     df.drop(index=nanrows, inplace=True)

{"golden_diff": "diff --git a/janitor/functions.py b/janitor/functions.py\n--- a/janitor/functions.py\n+++ b/janitor/functions.py\n@@ -40,7 +40,7 @@\n     return df\n \n \n-def clean_names(df, strip_underscores=None):\n+def clean_names(df, strip_underscores=None, preserve_case=False):\n     \"\"\"\n     Clean column names.\n \n@@ -65,11 +65,18 @@\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+    :param preserve_case: (optional) Allows you to choose whether to make all\n+    column names lowercase, or to preserve current cases. Default False makes\n+    all characters lowercase.\n     :returns: A pandas DataFrame.\n     \"\"\"\n+    if preserve_case is False:\n+        df = df.rename(\n+            columns=lambda x: x.lower()\n+        )\n+\n     df = df.rename(\n-        columns=lambda x: x.lower()\n-                           .replace(' ', '_')\n+        columns=lambda x: x.replace(' ', '_')\n                            .replace('/', '_')\n                            .replace(':', '_')\n                            .replace(\"'\", '')\n@@ -111,7 +118,6 @@\n     :param df: The pandas DataFrame object.\n     :returns: A pandas DataFrame.\n     \"\"\"\n-\n     nanrows = df.index[df.isnull().all(axis=1)]\n     df.drop(index=nanrows, inplace=True)\n", "issue": "Case Sensitive True/False kwarg on clean_names\nUsing pyjanitor again today I realised I didn't want to change capitals to lowercase. I think a case sensitive kwarg (default on) would be good.\r\n\r\nHappy to do PR for this if you think it is a good idea. One Q on this, what would should the kwarg be called?\r\n\r\nSome ideas:\r\n`lower`, `remove_upper`, `drop_case`, `case_sensitive`,...\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\nimport datetime as dt\nfrom functools import reduce\n\nimport pandas as pd\n\nfrom .errors import JanitorError\n\nimport re\n\n\ndef _strip_underscores(df, strip_underscores=None):\n    \"\"\"\n    Strip underscores from the beginning, end or both of the\n    of the DataFrames column names.\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.\n    \"\"\"\n    underscore_options = [None, 'left', 'right', 'both', 'l', 'r', True]\n    if strip_underscores not in underscore_options:\n        raise JanitorError(\n            \"\"\"strip_underscores must be one of: %s\"\"\" % underscore_options\n            )\n\n    if strip_underscores in ['left', 'l']:\n        df = df.rename(columns=lambda x: x.lstrip('_'))\n    elif strip_underscores in ['right', 'r']:\n        df = df.rename(columns=lambda x: x.rstrip('_'))\n    elif strip_underscores == 'both' or strip_underscores is True:\n        df = df.rename(columns=lambda x: x.strip('_'))\n    return df\n\n\ndef clean_names(df, strip_underscores=None):\n    \"\"\"\n    Clean column names.\n\n    Takes all column names, converts them to lowercase, then replaces all\n    spaces with underscores.\n\n    Functional usage example:\n\n    .. code-block:: python\n\n        df = clean_names(df)\n\n    Method chaining example:\n\n    .. code-block:: python\n\n        df = pd.DataFrame(...)\n        df = jn.DataFrame(df).clean_names()\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.\n    \"\"\"\n    df = df.rename(\n        columns=lambda x: x.lower()\n                           .replace(' ', '_')\n                           .replace('/', '_')\n                           .replace(':', '_')\n                           .replace(\"'\", '')\n                           .replace(u'\u2019', '')\n                           .replace(',', '_')\n                           .replace('?', '_')\n                           .replace('-', '_')\n                           .replace('(', '_')\n                           .replace(')', '_')\n                           .replace('.', '_')\n    )\n\n    df = df.rename(columns=lambda x: re.sub('_+', '_', x))\n    df = _strip_underscores(df, strip_underscores)\n    return df\n\n\ndef remove_empty(df):\n    \"\"\"\n    Drop all rows and columns that are completely null.\n\n    Implementation is shamelessly copied from `StackOverflow`_.\n\n    .. _StackOverflow: https://stackoverflow.com/questions/38884538/python-pandas-find-all-rows-where-all-values-are-nan  # noqa: E501\n\n    Functional usage example:\n\n    .. code-block:: python\n\n        df = remove_empty(df)\n\n    Method chaining example:\n\n    .. code-block:: python\n\n        df = pd.DataFrame(...)\n        df = jn.DataFrame(df).remove_empty()\n\n    :param df: The pandas DataFrame object.\n    :returns: A pandas DataFrame.\n    \"\"\"\n\n    nanrows = df.index[df.isnull().all(axis=1)]\n    df.drop(index=nanrows, inplace=True)\n\n    nancols = df.columns[df.isnull().all(axis=0)]\n    df.drop(columns=nancols, inplace=True)\n\n    return df\n\n\ndef get_dupes(df, columns=None):\n    \"\"\"\n    Returns all duplicate rows.\n\n    Functional usage example:\n\n    .. code-block:: python\n\n        get_dupes(df)\n\n    Method chaining example:\n\n    .. code-block:: python\n\n        df = pd.DataFrame(...)\n        jn.DataFrame(df).get_dupes()\n\n    :param df: The pandas DataFrame object.\n    :param str/iterable columns: (optional) A column name or an iterable (list\n        or tuple) of column names. Following pandas API, this only considers\n        certain columns for identifying duplicates. Defaults to using all\n        columns.\n    :returns: The duplicate rows, as a pandas DataFrame.\n    \"\"\"\n    dupes = df.duplicated(subset=columns, keep=False)\n    return df[dupes == True]  # noqa: E712\n\n\ndef encode_categorical(df, columns):\n    \"\"\"\n    Encode the specified columns as categorical.\n\n    Functional usage example:\n\n    .. code-block:: python\n\n        encode_categorical(df, columns=\"my_categorical_column\")  # one way\n\n    Method chaining example:\n\n    .. code-block:: python\n\n        df = pd.DataFrame(...)\n        categorical_cols = ['col1', 'col2', 'col4']\n        jn.DataFrame(df).encode_categorical(columns=categorical_cols)\n\n    :param df: The pandas DataFrame object.\n    :param str/iterable columns: A column name or an iterable (list or tuple)\n        of column names.\n    :returns: A pandas DataFrame\n    \"\"\"\n    if isinstance(columns, list) or isinstance(columns, tuple):\n        for col in columns:\n            assert col in df.columns, \\\n                JanitorError(\"{col} missing from dataframe columns!\".format(col=col))  # noqa: E501\n            df[col] = pd.Categorical(df[col])\n    elif isinstance(columns, str):\n        df[columns] = pd.Categorical(df[columns])\n    else:\n        raise JanitorError('kwarg `columns` must be a string or iterable!')\n    return df\n\n\ndef get_features_targets(df, target_columns, feature_columns=None):\n    \"\"\"\n    Get the features and targets as separate DataFrames/Series.\n\n    The behaviour is as such:\n\n    - `target_columns` is mandatory.\n    - If `feature_columns` is present, then we will respect the column names\n      inside there.\n    - If `feature_columns` is not passed in, then we will assume that the\n      rest of the columns are feature columns, and return them.\n\n    Functional usage example:\n\n    .. code-block:: python\n\n        X, y = get_features_targets(df, target_columns=\"measurement\")\n\n    Method chaining example:\n\n    .. code-block:: python\n\n        df = pd.DataFrame(...)\n        target_cols = ['output1', 'output2']\n        X, y = jn.DataFrame(df).get_features_targets(target_columns=target_cols)  # noqa: E501\n\n    :param df: The pandas DataFrame object.\n    :param str/iterable target_columns: Either a column name or an iterable\n        (list or tuple) of column names that are the target(s) to be predicted.\n    :param str/iterable feature_columns: (optional) The column name or iterable\n        of column names that are the features (a.k.a. predictors) used to\n        predict the targets.\n    :returns: (X, Y) the feature matrix (X) and the target matrix (Y). Both are\n        pandas DataFrames.\n    \"\"\"\n    Y = df[target_columns]\n\n    if feature_columns:\n        X = df[feature_columns]\n    else:\n        if isinstance(target_columns, str):\n            xcols = [c for c in df.columns if target_columns != c]\n        elif (isinstance(target_columns, list)\n              or isinstance(target_columns, tuple)):\n            xcols = [c for c in df.columns if c not in target_columns]\n        X = df[xcols]\n    return X, Y\n\n\ndef rename_column(df, old, new):\n    \"\"\"\n    Rename a column in place.\n\n    Functional usage example:\n\n    .. code-block:: python\n\n        df = rename_column(\"old_column_name\", \"new_column_name\")\n\n    Method chaining example:\n\n    .. code-block:: python\n\n        df = pd.DataFrame(...)\n        df = jn.DataFrame(df).rename_column(\"old_column_name\", \"new_column_name\")  # noqa: E501\n\n    This is just syntactic sugar/a convenience function for renaming one column\n    at a time. If you are convinced that there are multiple columns in need of\n    changing, then use the :py:meth:`pandas.DataFrame.rename` method.\n\n    :param str old: The old column name.\n    :param str new: The new column name.\n    :returns: A pandas DataFrame.\n    \"\"\"\n    return df.rename(columns={old: new})\n\n\ndef coalesce(df, columns, new_column_name):\n    \"\"\"\n    Coalesces two or more columns of data in order of column names provided.\n\n    Functional usage example:\n\n    .. code-block:: python\n\n        df = coalesce(df, columns=['col1', 'col2'])\n\n    Method chaining example:\n\n    .. code-block:: python\n\n        df = pd.DataFrame(...)\n        df = jn.DataFrame(df).coalesce(['col1', 'col2'])\n\n\n    The result of this function is that we take the first non-null value across\n    rows.\n\n    This is more syntactic diabetes! For R users, this should look familiar to\n    `dplyr`'s `coalesce` function; for Python users, the interface\n    should be more intuitive than the :py:meth:`pandas.Series.combine_first`\n    method (which we're just using internally anyways).\n\n    :param df: A pandas DataFrame.\n    :param columns: A list of column names.\n    :param str new_column_name: The new column name after combining.\n    :returns: A pandas DataFrame.\n    \"\"\"\n    series = [df[c] for c in columns]\n\n    def _coalesce(series1, series2):\n        return series1.combine_first(series2)\n    df = df.drop(columns=columns)\n    df[new_column_name] = reduce(_coalesce, series)  # noqa: F821\n    return df\n\n\ndef convert_excel_date(df, column):\n    \"\"\"\n    Convert Excel's serial date format into Python datetime format.\n\n    Implementation is also from `Stack Overflow`.\n\n    .. _Stack Overflow: https://stackoverflow.com/questions/38454403/convert-excel-style-date-with-pandas  # noqa: E501\n\n    Functional usage example:\n\n    .. code-block:: python\n\n        df = convert_excel_date(df, column='date')\n\n    Method chaining example:\n\n    .. code-block:: python\n\n        df = pd.DataFrame(...)\n        df = jn.DataFrame(df).convert_excel_date('date')\n\n    :param df: A pandas DataFrame.\n    :param str column: A column name.\n    :returns: A pandas DataFrame with corrected dates.\n    \"\"\"\n    df[column] = (pd.TimedeltaIndex(df[column], unit='d')\n                  + dt.datetime(1899, 12, 30))\n    return df\n\n\ndef fill_empty(df, columns, value):\n    \"\"\"\n    Fill `NaN` values in specified columns with a given value.\n\n    Super sugary syntax that wraps :py:meth:`pandas.DataFrame.fillna`.\n\n    Functional usage example:\n\n    .. code-block:: python\n\n        df = fill_empty(df, columns=['col1', 'col2'], value=0)\n\n    Method chaining example:\n\n    .. code-block:: python\n\n        df = pd.DataFrame(...)\n        df = jn.DataFrame(df).fill_empty(df, columns='col1', value=0)\n\n    :param df: A pandas DataFrame.\n    :param columns: Either a `str` or `list` or `tuple`. If a string is passed\n        in, then only that column will be filled; if a list or tuple of strings\n        are passed in, then they will all be filled with the same value.\n    :param value: The value that replaces the `NaN` values.\n    \"\"\"\n    if isinstance(columns, list) or isinstance(columns, tuple):\n        for col in columns:\n            assert col in df.columns, \\\n                JanitorError(\"{col} missing from dataframe columns!\".format(col=col))  # noqa: E501\n            df[col] = df[col].fillna(value)\n    elif isinstance(columns, str):\n        df[columns] = df[columns].fillna(value)\n    else:\n        raise JanitorError('kwarg `columns` must be a string or iterable!')\n\n    return df\n", "path": "janitor/functions.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\nimport datetime as dt\nfrom functools import reduce\n\nimport pandas as pd\n\nfrom .errors import JanitorError\n\nimport re\n\n\ndef _strip_underscores(df, strip_underscores=None):\n    \"\"\"\n    Strip underscores from the beginning, end or both of the\n    of the DataFrames column names.\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.\n    \"\"\"\n    underscore_options = [None, 'left', 'right', 'both', 'l', 'r', True]\n    if strip_underscores not in underscore_options:\n        raise JanitorError(\n            \"\"\"strip_underscores must be one of: %s\"\"\" % underscore_options\n            )\n\n    if strip_underscores in ['left', 'l']:\n        df = df.rename(columns=lambda x: x.lstrip('_'))\n    elif strip_underscores in ['right', 'r']:\n        df = df.rename(columns=lambda x: x.rstrip('_'))\n    elif strip_underscores == 'both' or strip_underscores is True:\n        df = df.rename(columns=lambda x: x.strip('_'))\n    return df\n\n\ndef clean_names(df, strip_underscores=None, preserve_case=False):\n    \"\"\"\n    Clean column names.\n\n    Takes all column names, converts them to lowercase, then replaces all\n    spaces with underscores.\n\n    Functional usage example:\n\n    .. code-block:: python\n\n        df = clean_names(df)\n\n    Method chaining example:\n\n    .. code-block:: python\n\n        df = pd.DataFrame(...)\n        df = jn.DataFrame(df).clean_names()\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    :param preserve_case: (optional) Allows you to choose whether to make all\n    column names lowercase, or to preserve current cases. Default False makes\n    all characters lowercase.\n    :returns: A pandas DataFrame.\n    \"\"\"\n    if preserve_case is False:\n        df = df.rename(\n            columns=lambda x: x.lower()\n        )\n\n    df = df.rename(\n        columns=lambda x: x.replace(' ', '_')\n                           .replace('/', '_')\n                           .replace(':', '_')\n                           .replace(\"'\", '')\n                           .replace(u'\u2019', '')\n                           .replace(',', '_')\n                           .replace('?', '_')\n                           .replace('-', '_')\n                           .replace('(', '_')\n                           .replace(')', '_')\n                           .replace('.', '_')\n    )\n\n    df = df.rename(columns=lambda x: re.sub('_+', '_', x))\n    df = _strip_underscores(df, strip_underscores)\n    return df\n\n\ndef remove_empty(df):\n    \"\"\"\n    Drop all rows and columns that are completely null.\n\n    Implementation is shamelessly copied from `StackOverflow`_.\n\n    .. _StackOverflow: https://stackoverflow.com/questions/38884538/python-pandas-find-all-rows-where-all-values-are-nan  # noqa: E501\n\n    Functional usage example:\n\n    .. code-block:: python\n\n        df = remove_empty(df)\n\n    Method chaining example:\n\n    .. code-block:: python\n\n        df = pd.DataFrame(...)\n        df = jn.DataFrame(df).remove_empty()\n\n    :param df: The pandas DataFrame object.\n    :returns: A pandas DataFrame.\n    \"\"\"\n    nanrows = df.index[df.isnull().all(axis=1)]\n    df.drop(index=nanrows, inplace=True)\n\n    nancols = df.columns[df.isnull().all(axis=0)]\n    df.drop(columns=nancols, inplace=True)\n\n    return df\n\n\ndef get_dupes(df, columns=None):\n    \"\"\"\n    Returns all duplicate rows.\n\n    Functional usage example:\n\n    .. code-block:: python\n\n        get_dupes(df)\n\n    Method chaining example:\n\n    .. code-block:: python\n\n        df = pd.DataFrame(...)\n        jn.DataFrame(df).get_dupes()\n\n    :param df: The pandas DataFrame object.\n    :param str/iterable columns: (optional) A column name or an iterable (list\n        or tuple) of column names. Following pandas API, this only considers\n        certain columns for identifying duplicates. Defaults to using all\n        columns.\n    :returns: The duplicate rows, as a pandas DataFrame.\n    \"\"\"\n    dupes = df.duplicated(subset=columns, keep=False)\n    return df[dupes == True]  # noqa: E712\n\n\ndef encode_categorical(df, columns):\n    \"\"\"\n    Encode the specified columns as categorical.\n\n    Functional usage example:\n\n    .. code-block:: python\n\n        encode_categorical(df, columns=\"my_categorical_column\")  # one way\n\n    Method chaining example:\n\n    .. code-block:: python\n\n        df = pd.DataFrame(...)\n        categorical_cols = ['col1', 'col2', 'col4']\n        jn.DataFrame(df).encode_categorical(columns=categorical_cols)\n\n    :param df: The pandas DataFrame object.\n    :param str/iterable columns: A column name or an iterable (list or tuple)\n        of column names.\n    :returns: A pandas DataFrame\n    \"\"\"\n    if isinstance(columns, list) or isinstance(columns, tuple):\n        for col in columns:\n            assert col in df.columns, \\\n                JanitorError(\"{col} missing from dataframe columns!\".format(col=col))  # noqa: E501\n            df[col] = pd.Categorical(df[col])\n    elif isinstance(columns, str):\n        df[columns] = pd.Categorical(df[columns])\n    else:\n        raise JanitorError('kwarg `columns` must be a string or iterable!')\n    return df\n\n\ndef get_features_targets(df, target_columns, feature_columns=None):\n    \"\"\"\n    Get the features and targets as separate DataFrames/Series.\n\n    The behaviour is as such:\n\n    - `target_columns` is mandatory.\n    - If `feature_columns` is present, then we will respect the column names\n      inside there.\n    - If `feature_columns` is not passed in, then we will assume that the\n      rest of the columns are feature columns, and return them.\n\n    Functional usage example:\n\n    .. code-block:: python\n\n        X, y = get_features_targets(df, target_columns=\"measurement\")\n\n    Method chaining example:\n\n    .. code-block:: python\n\n        df = pd.DataFrame(...)\n        target_cols = ['output1', 'output2']\n        X, y = jn.DataFrame(df).get_features_targets(target_columns=target_cols)  # noqa: E501\n\n    :param df: The pandas DataFrame object.\n    :param str/iterable target_columns: Either a column name or an iterable\n        (list or tuple) of column names that are the target(s) to be predicted.\n    :param str/iterable feature_columns: (optional) The column name or iterable\n        of column names that are the features (a.k.a. predictors) used to\n        predict the targets.\n    :returns: (X, Y) the feature matrix (X) and the target matrix (Y). Both are\n        pandas DataFrames.\n    \"\"\"\n    Y = df[target_columns]\n\n    if feature_columns:\n        X = df[feature_columns]\n    else:\n        if isinstance(target_columns, str):\n            xcols = [c for c in df.columns if target_columns != c]\n        elif (isinstance(target_columns, list)\n              or isinstance(target_columns, tuple)):\n            xcols = [c for c in df.columns if c not in target_columns]\n        X = df[xcols]\n    return X, Y\n\n\ndef rename_column(df, old, new):\n    \"\"\"\n    Rename a column in place.\n\n    Functional usage example:\n\n    .. code-block:: python\n\n        df = rename_column(\"old_column_name\", \"new_column_name\")\n\n    Method chaining example:\n\n    .. code-block:: python\n\n        df = pd.DataFrame(...)\n        df = jn.DataFrame(df).rename_column(\"old_column_name\", \"new_column_name\")  # noqa: E501\n\n    This is just syntactic sugar/a convenience function for renaming one column\n    at a time. If you are convinced that there are multiple columns in need of\n    changing, then use the :py:meth:`pandas.DataFrame.rename` method.\n\n    :param str old: The old column name.\n    :param str new: The new column name.\n    :returns: A pandas DataFrame.\n    \"\"\"\n    return df.rename(columns={old: new})\n\n\ndef coalesce(df, columns, new_column_name):\n    \"\"\"\n    Coalesces two or more columns of data in order of column names provided.\n\n    Functional usage example:\n\n    .. code-block:: python\n\n        df = coalesce(df, columns=['col1', 'col2'])\n\n    Method chaining example:\n\n    .. code-block:: python\n\n        df = pd.DataFrame(...)\n        df = jn.DataFrame(df).coalesce(['col1', 'col2'])\n\n\n    The result of this function is that we take the first non-null value across\n    rows.\n\n    This is more syntactic diabetes! For R users, this should look familiar to\n    `dplyr`'s `coalesce` function; for Python users, the interface\n    should be more intuitive than the :py:meth:`pandas.Series.combine_first`\n    method (which we're just using internally anyways).\n\n    :param df: A pandas DataFrame.\n    :param columns: A list of column names.\n    :param str new_column_name: The new column name after combining.\n    :returns: A pandas DataFrame.\n    \"\"\"\n    series = [df[c] for c in columns]\n\n    def _coalesce(series1, series2):\n        return series1.combine_first(series2)\n    df = df.drop(columns=columns)\n    df[new_column_name] = reduce(_coalesce, series)  # noqa: F821\n    return df\n\n\ndef convert_excel_date(df, column):\n    \"\"\"\n    Convert Excel's serial date format into Python datetime format.\n\n    Implementation is also from `Stack Overflow`.\n\n    .. _Stack Overflow: https://stackoverflow.com/questions/38454403/convert-excel-style-date-with-pandas  # noqa: E501\n\n    Functional usage example:\n\n    .. code-block:: python\n\n        df = convert_excel_date(df, column='date')\n\n    Method chaining example:\n\n    .. code-block:: python\n\n        df = pd.DataFrame(...)\n        df = jn.DataFrame(df).convert_excel_date('date')\n\n    :param df: A pandas DataFrame.\n    :param str column: A column name.\n    :returns: A pandas DataFrame with corrected dates.\n    \"\"\"\n    df[column] = (pd.TimedeltaIndex(df[column], unit='d')\n                  + dt.datetime(1899, 12, 30))\n    return df\n\n\ndef fill_empty(df, columns, value):\n    \"\"\"\n    Fill `NaN` values in specified columns with a given value.\n\n    Super sugary syntax that wraps :py:meth:`pandas.DataFrame.fillna`.\n\n    Functional usage example:\n\n    .. code-block:: python\n\n        df = fill_empty(df, columns=['col1', 'col2'], value=0)\n\n    Method chaining example:\n\n    .. code-block:: python\n\n        df = pd.DataFrame(...)\n        df = jn.DataFrame(df).fill_empty(df, columns='col1', value=0)\n\n    :param df: A pandas DataFrame.\n    :param columns: Either a `str` or `list` or `tuple`. If a string is passed\n        in, then only that column will be filled; if a list or tuple of strings\n        are passed in, then they will all be filled with the same value.\n    :param value: The value that replaces the `NaN` values.\n    \"\"\"\n    if isinstance(columns, list) or isinstance(columns, tuple):\n        for col in columns:\n            assert col in df.columns, \\\n                JanitorError(\"{col} missing from dataframe columns!\".format(col=col))  # noqa: E501\n            df[col] = df[col].fillna(value)\n    elif isinstance(columns, str):\n        df[columns] = df[columns].fillna(value)\n    else:\n        raise JanitorError('kwarg `columns` must be a string or iterable!')\n\n    return df\n", "path": "janitor/functions.py"}]}