Repository: pyjanitor-devs/pyjanitor
Pull request #1106 (2022-05-16 02:06:24)

[ENH] Extend column selection to non-strings
# PR Description

Please describe the changes proposed in the pull request:

- Allow column selection on non-strings, if the columns support it (dates, numbers, ...)
- Allow return of pd.Index where possible
- Allow Pandas to generate error message where possible 
- Move column checks for `index` and `column_names` in `pivot_longer` to `_select_columns`

<!-- Doing so provides maintainers with context on what the PR is, and can help us more effectively review your PR. -->

<!-- Please also identify below which issue that has been raised that you are going to close. -->

**This PR relates to #1105 .**

<!-- As you go down the PR template, please feel free to delete sections that are irrelevant. -->

# 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. [ ] 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


ericmjl: <!-- NETLIFY DEPLOY COMMENT GENERATED BY ACTIONS_NETLIFY -->
🚀 Deployed on https://deploy-preview-1106--pyjanitor.netlify.app
codecov[bot]: # [Codecov](https://codecov.io/gh/pyjanitor-devs/pyjanitor/pull/1106?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyjanitor-devs) Report
> Merging [#1106](https://codecov.io/gh/pyjanitor-devs/pyjanitor/pull/1106?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyjanitor-devs) (f597a08) into [dev](https://codecov.io/gh/pyjanitor-devs/pyjanitor/commit/4067119be19d7372d2915eadad67469731d6c3a1?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyjanitor-devs) (4067119) will **decrease** coverage by `54.69%`.
> The diff coverage is `20.00%`.


```diff
@@             Coverage Diff             @@
##              dev    #1106       +/-   ##
===========================================
- Coverage   97.77%   43.08%   -54.70%     
===========================================
  Files          77       77               
  Lines        3145     3152        +7     
===========================================
- Hits         3075     1358     -1717     
- Misses         70     1794     +1724     
```


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 50f99ea..10e21d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,8 @@
 
 -   [DOC] Updated developer guide docs.
 -   [ENH] Allow column selection/renaming within conditional_join. #1102 @samukweku.
--   [EHN] New decorator `deprecated_kwargs` for breaking API. #1103 @Zeroto521
+-   [ENH] New decorator `deprecated_kwargs` for breaking API. #1103 @Zeroto521
+-   [ENH] Extend select_columns to support non-string columns. #1105 @samukweku
 
 ## [v0.23.1] - 2022-05-03
 
diff --git a/examples/notebooks/select_columns.ipynb b/examples/notebooks/select_columns.ipynb
index 751450c..8022e6c 100644
--- a/examples/notebooks/select_columns.ipynb
+++ b/examples/notebooks/select_columns.ipynb
@@ -9,7 +9,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 2,
+   "execution_count": 1,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -24,7 +24,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 3,
+   "execution_count": 2,
    "metadata": {},
    "outputs": [
     {
@@ -97,7 +97,7 @@
        "1   1  XYZ     2    NaN     5    R   NaN     U     b 2018-01-01"
       ]
      },
-     "execution_count": 3,
+     "execution_count": 2,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -132,53 +132,16 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": 3,
    "metadata": {},
    "outputs": [
     {
      "data": {
-      "text/html": [
-       "<div>\n",
-       "<style scoped>\n",
-       "    .dataframe tbody tr th:only-of-type {\n",
-       "        vertical-align: middle;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe tbody tr th {\n",
-       "        vertical-align: top;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe thead th {\n",
-       "        text-align: right;\n",
-       "    }\n",
-       "</style>\n",
-       "<table border=\"1\" class=\"dataframe\">\n",
-       "  <thead>\n",
-       "    <tr style=\"text-align: right;\">\n",
-       "      <th></th>\n",
-       "      <th>id</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>0</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>1</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
       "text/plain": [
-       "   id\n",
-       "0   0\n",
-       "1   1"
+       "['id']"
       ]
      },
-     "execution_count": 4,
+     "execution_count": 3,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -196,62 +159,16 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
+   "execution_count": 4,
    "metadata": {},
    "outputs": [
     {
      "data": {
-      "text/html": [
-       "<div>\n",
-       "<style scoped>\n",
-       "    .dataframe tbody tr th:only-of-type {\n",
-       "        vertical-align: middle;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe tbody tr th {\n",
-       "        vertical-align: top;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe thead th {\n",
-       "        text-align: right;\n",
-       "    }\n",
-       "</style>\n",
-       "<table border=\"1\" class=\"dataframe\">\n",
-       "  <thead>\n",
-       "    <tr style=\"text-align: right;\">\n",
-       "      <th></th>\n",
-       "      <th>type</th>\n",
-       "      <th>type1</th>\n",
-       "      <th>type2</th>\n",
-       "      <th>type3</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>S</td>\n",
-       "      <td>E</td>\n",
-       "      <td>T</td>\n",
-       "      <td>2018-01-01</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>R</td>\n",
-       "      <td>NaN</td>\n",
-       "      <td>U</td>\n",
-       "      <td>2018-01-01</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
       "text/plain": [
-       "  type type1 type2      type3\n",
-       "0    S     E     T 2018-01-01\n",
-       "1    R   NaN     U 2018-01-01"
+       "['type', 'type1', 'type2', 'type3']"
       ]
      },
-     "execution_count": 5,
+     "execution_count": 4,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -269,62 +186,16 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
+   "execution_count": 5,
    "metadata": {},
    "outputs": [
     {
      "data": {
-      "text/html": [
-       "<div>\n",
-       "<style scoped>\n",
-       "    .dataframe tbody tr th:only-of-type {\n",
-       "        vertical-align: middle;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe tbody tr th {\n",
-       "        vertical-align: top;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe thead th {\n",
-       "        text-align: right;\n",
-       "    }\n",
-       "</style>\n",
-       "<table border=\"1\" class=\"dataframe\">\n",
-       "  <thead>\n",
-       "    <tr style=\"text-align: right;\">\n",
-       "      <th></th>\n",
-       "      <th>code1</th>\n",
-       "      <th>code2</th>\n",
-       "      <th>type</th>\n",
-       "      <th>type1</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>4.0</td>\n",
-       "      <td>8</td>\n",
-       "      <td>S</td>\n",
-       "      <td>E</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>NaN</td>\n",
-       "      <td>5</td>\n",
-       "      <td>R</td>\n",
-       "      <td>NaN</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
       "text/plain": [
-       "   code1 code2 type type1\n",
-       "0    4.0     8    S     E\n",
-       "1    NaN     5    R   NaN"
+       "['code1', 'code2', 'type', 'type1']"
       ]
      },
-     "execution_count": 6,
+     "execution_count": 5,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -340,6 +211,26 @@
     "- Select by `Callable` (the callable is applied to every column  and should return a single `True` or `False` per column):"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "['type3']"
+      ]
+     },
+     "execution_count": 6,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "df.select_columns(is_datetime64_dtype)"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": 7,
@@ -347,45 +238,8 @@
    "outputs": [
     {
      "data": {
-      "text/html": [
-       "<div>\n",
-       "<style scoped>\n",
-       "    .dataframe tbody tr th:only-of-type {\n",
-       "        vertical-align: middle;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe tbody tr th {\n",
-       "        vertical-align: top;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe thead th {\n",
-       "        text-align: right;\n",
-       "    }\n",
-       "</style>\n",
-       "<table border=\"1\" class=\"dataframe\">\n",
-       "  <thead>\n",
-       "    <tr style=\"text-align: right;\">\n",
-       "      <th></th>\n",
-       "      <th>type3</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>2018-01-01</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>2018-01-01</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
       "text/plain": [
-       "       type3\n",
-       "0 2018-01-01\n",
-       "1 2018-01-01"
+       "['code', 'code1', 'code2', 'type1', 'code3']"
       ]
      },
      "execution_count": 7,
@@ -394,7 +248,8 @@
     }
    ],
    "source": [
-    "df.select_columns(is_datetime64_dtype)"
+    "df.select_columns(lambda x: x.name.startswith(\"code\") or\n",
+    "                            x.name.endswith(\"1\"))"
    ]
   },
   {
@@ -404,57 +259,8 @@
    "outputs": [
     {
      "data": {
-      "text/html": [
-       "<div>\n",
-       "<style scoped>\n",
-       "    .dataframe tbody tr th:only-of-type {\n",
-       "        vertical-align: middle;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe tbody tr th {\n",
-       "        vertical-align: top;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe thead th {\n",
-       "        text-align: right;\n",
-       "    }\n",
-       "</style>\n",
-       "<table border=\"1\" class=\"dataframe\">\n",
-       "  <thead>\n",
-       "    <tr style=\"text-align: right;\">\n",
-       "      <th></th>\n",
-       "      <th>code</th>\n",
-       "      <th>code1</th>\n",
-       "      <th>code2</th>\n",
-       "      <th>type1</th>\n",
-       "      <th>code3</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>1</td>\n",
-       "      <td>4.0</td>\n",
-       "      <td>8</td>\n",
-       "      <td>E</td>\n",
-       "      <td>a</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>2</td>\n",
-       "      <td>NaN</td>\n",
-       "      <td>5</td>\n",
-       "      <td>NaN</td>\n",
-       "      <td>b</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
       "text/plain": [
-       "   code  code1 code2 type1 code3\n",
-       "0     1    4.0     8     E     a\n",
-       "1     2    NaN     5   NaN     b"
+       "['code1', 'type1']"
       ]
      },
      "execution_count": 8,
@@ -463,8 +269,14 @@
     }
    ],
    "source": [
-    "df.select_columns(lambda x: x.name.startswith(\"code\") or\n",
-    "                            x.name.endswith(\"1\"))"
+    "df.select_columns(lambda x: x.isna().any())"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "- Select by regular expression:"
    ]
   },
   {
@@ -474,48 +286,8 @@
    "outputs": [
     {
      "data": {
-      "text/html": [
-       "<div>\n",
-       "<style scoped>\n",
-       "    .dataframe tbody tr th:only-of-type {\n",
-       "        vertical-align: middle;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe tbody tr th {\n",
-       "        vertical-align: top;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe thead th {\n",
-       "        text-align: right;\n",
-       "    }\n",
-       "</style>\n",
-       "<table border=\"1\" class=\"dataframe\">\n",
-       "  <thead>\n",
-       "    <tr style=\"text-align: right;\">\n",
-       "      <th></th>\n",
-       "      <th>code1</th>\n",
-       "      <th>type1</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>4.0</td>\n",
-       "      <td>E</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>NaN</td>\n",
-       "      <td>NaN</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
       "text/plain": [
-       "   code1 type1\n",
-       "0    4.0     E\n",
-       "1    NaN   NaN"
+       "['code1', 'code2', 'type1', 'type2', 'code3', 'type3']"
       ]
      },
      "execution_count": 9,
@@ -524,14 +296,7 @@
     }
    ],
    "source": [
-    "df.select_columns(lambda x: x.isna().any())"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "- Select by regular expression:"
+    "df.select_columns(re.compile(\"\\\\d+\"))"
    ]
   },
   {
@@ -540,136 +305,20 @@
    "metadata": {},
    "outputs": [
     {
-     "data": {
-      "text/html": [
-       "<div>\n",
-       "<style scoped>\n",
-       "    .dataframe tbody tr th:only-of-type {\n",
-       "        vertical-align: middle;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe tbody tr th {\n",
-       "        vertical-align: top;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe thead th {\n",
-       "        text-align: right;\n",
-       "    }\n",
-       "</style>\n",
-       "<table border=\"1\" class=\"dataframe\">\n",
-       "  <thead>\n",
-       "    <tr style=\"text-align: right;\">\n",
-       "      <th></th>\n",
-       "      <th>code1</th>\n",
-       "      <th>code2</th>\n",
-       "      <th>type1</th>\n",
-       "      <th>type2</th>\n",
-       "      <th>code3</th>\n",
-       "      <th>type3</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>4.0</td>\n",
-       "      <td>8</td>\n",
-       "      <td>E</td>\n",
-       "      <td>T</td>\n",
-       "      <td>a</td>\n",
-       "      <td>2018-01-01</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>NaN</td>\n",
-       "      <td>5</td>\n",
-       "      <td>NaN</td>\n",
-       "      <td>U</td>\n",
-       "      <td>b</td>\n",
-       "      <td>2018-01-01</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
-      "text/plain": [
-       "   code1 code2 type1 type2 code3      type3\n",
-       "0    4.0     8     E     T     a 2018-01-01\n",
-       "1    NaN     5   NaN     U     b 2018-01-01"
-      ]
-     },
-     "execution_count": 10,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "df.select_columns(re.compile(\"\\\\d+\"))"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 11,
-   "metadata": {},
-   "outputs": [
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/tmp/ipykernel_5901/1520232378.py:4: DeprecationWarning: This function is deprecated. Kindly use `re.compile` instead.\n",
+      "  df.select_columns(patterns(\"\\\\d+\"))\n"
+     ]
+    },
     {
      "data": {
-      "text/html": [
-       "<div>\n",
-       "<style scoped>\n",
-       "    .dataframe tbody tr th:only-of-type {\n",
-       "        vertical-align: middle;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe tbody tr th {\n",
-       "        vertical-align: top;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe thead th {\n",
-       "        text-align: right;\n",
-       "    }\n",
-       "</style>\n",
-       "<table border=\"1\" class=\"dataframe\">\n",
-       "  <thead>\n",
-       "    <tr style=\"text-align: right;\">\n",
-       "      <th></th>\n",
-       "      <th>code1</th>\n",
-       "      <th>code2</th>\n",
-       "      <th>type1</th>\n",
-       "      <th>type2</th>\n",
-       "      <th>code3</th>\n",
-       "      <th>type3</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>4.0</td>\n",
-       "      <td>8</td>\n",
-       "      <td>E</td>\n",
-       "      <td>T</td>\n",
-       "      <td>a</td>\n",
-       "      <td>2018-01-01</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>NaN</td>\n",
-       "      <td>5</td>\n",
-       "      <td>NaN</td>\n",
-       "      <td>U</td>\n",
-       "      <td>b</td>\n",
-       "      <td>2018-01-01</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
       "text/plain": [
-       "   code1 code2 type1 type2 code3      type3\n",
-       "0    4.0     8     E     T     a 2018-01-01\n",
-       "1    NaN     5   NaN     U     b 2018-01-01"
+       "['code1', 'code2', 'type1', 'type2', 'code3', 'type3']"
       ]
      },
-     "execution_count": 11,
+     "execution_count": 10,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -690,65 +339,16 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 12,
+   "execution_count": 11,
    "metadata": {},
    "outputs": [
     {
      "data": {
-      "text/html": [
-       "<div>\n",
-       "<style scoped>\n",
-       "    .dataframe tbody tr th:only-of-type {\n",
-       "        vertical-align: middle;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe tbody tr th {\n",
-       "        vertical-align: top;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe thead th {\n",
-       "        text-align: right;\n",
-       "    }\n",
-       "</style>\n",
-       "<table border=\"1\" class=\"dataframe\">\n",
-       "  <thead>\n",
-       "    <tr style=\"text-align: right;\">\n",
-       "      <th></th>\n",
-       "      <th>id</th>\n",
-       "      <th>code</th>\n",
-       "      <th>code1</th>\n",
-       "      <th>code2</th>\n",
-       "      <th>code3</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>0</td>\n",
-       "      <td>1</td>\n",
-       "      <td>4.0</td>\n",
-       "      <td>8</td>\n",
-       "      <td>a</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>1</td>\n",
-       "      <td>2</td>\n",
-       "      <td>NaN</td>\n",
-       "      <td>5</td>\n",
-       "      <td>b</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
       "text/plain": [
-       "   id  code  code1 code2 code3\n",
-       "0   0     1    4.0     8     a\n",
-       "1   1     2    NaN     5     b"
+       "array(['id', 'code', 'code1', 'code2', 'code3'], dtype=object)"
       ]
      },
-     "execution_count": 12,
+     "execution_count": 11,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -766,65 +366,16 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 13,
+   "execution_count": 12,
    "metadata": {},
    "outputs": [
     {
      "data": {
-      "text/html": [
-       "<div>\n",
-       "<style scoped>\n",
-       "    .dataframe tbody tr th:only-of-type {\n",
-       "        vertical-align: middle;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe tbody tr th {\n",
-       "        vertical-align: top;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe thead th {\n",
-       "        text-align: right;\n",
-       "    }\n",
-       "</style>\n",
-       "<table border=\"1\" class=\"dataframe\">\n",
-       "  <thead>\n",
-       "    <tr style=\"text-align: right;\">\n",
-       "      <th></th>\n",
-       "      <th>id</th>\n",
-       "      <th>code</th>\n",
-       "      <th>code1</th>\n",
-       "      <th>code2</th>\n",
-       "      <th>code3</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>0</td>\n",
-       "      <td>1</td>\n",
-       "      <td>4.0</td>\n",
-       "      <td>8</td>\n",
-       "      <td>a</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>1</td>\n",
-       "      <td>2</td>\n",
-       "      <td>NaN</td>\n",
-       "      <td>5</td>\n",
-       "      <td>b</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
       "text/plain": [
-       "   id  code  code1 code2 code3\n",
-       "0   0     1    4.0     8     a\n",
-       "1   1     2    NaN     5     b"
+       "Index(['id', 'code', 'code1', 'code2', 'code3'], dtype='object')"
       ]
      },
-     "execution_count": 13,
+     "execution_count": 12,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -843,65 +394,16 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 14,
+   "execution_count": 13,
    "metadata": {},
    "outputs": [
     {
      "data": {
-      "text/html": [
-       "<div>\n",
-       "<style scoped>\n",
-       "    .dataframe tbody tr th:only-of-type {\n",
-       "        vertical-align: middle;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe tbody tr th {\n",
-       "        vertical-align: top;\n",
-       "    }\n",
-       "\n",
-       "    .dataframe thead th {\n",
-       "        text-align: right;\n",
-       "    }\n",
-       "</style>\n",
-       "<table border=\"1\" class=\"dataframe\">\n",
-       "  <thead>\n",
-       "    <tr style=\"text-align: right;\">\n",
-       "      <th></th>\n",
-       "      <th>Name</th>\n",
-       "      <th>type</th>\n",
-       "      <th>type1</th>\n",
-       "      <th>type2</th>\n",
-       "      <th>type3</th>\n",
-       "    </tr>\n",
-       "  </thead>\n",
-       "  <tbody>\n",
-       "    <tr>\n",
-       "      <th>0</th>\n",
-       "      <td>ABC</td>\n",
-       "      <td>S</td>\n",
-       "      <td>E</td>\n",
-       "      <td>T</td>\n",
-       "      <td>2018-01-01</td>\n",
-       "    </tr>\n",
-       "    <tr>\n",
-       "      <th>1</th>\n",
-       "      <td>XYZ</td>\n",
-       "      <td>R</td>\n",
-       "      <td>NaN</td>\n",
-       "      <td>U</td>\n",
-       "      <td>2018-01-01</td>\n",
-       "    </tr>\n",
-       "  </tbody>\n",
-       "</table>\n",
-       "</div>"
-      ],
       "text/plain": [
-       "  Name type type1 type2      type3\n",
-       "0  ABC    S     E     T 2018-01-01\n",
-       "1  XYZ    R   NaN     U 2018-01-01"
+       "array(['id', 'code', 'code1', 'code2', 'code3'], dtype=object)"
       ]
      },
-     "execution_count": 14,
+     "execution_count": 13,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -931,7 +433,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.8.10"
+   "version": "3.9.10"
   },
   "orig_nbformat": 4
  },
diff --git a/janitor/functions/pivot.py b/janitor/functions/pivot.py
index fb7aaf5..a377d50 100644
--- a/janitor/functions/pivot.py
+++ b/janitor/functions/pivot.py
@@ -376,25 +376,25 @@ def _data_checks_pivot_longer(
             )
 
     if column_names is not None:
-        if is_list_like(column_names) and (
-            not isinstance(column_names, tuple)
-        ):
-            column_names = [*column_names]
+        if is_list_like(column_names):
+            column_names = list(column_names)
         column_names = _select_column_names(column_names, df)
+        column_names = list(column_names)
 
     if index is not None:
-        if is_list_like(index) and (not isinstance(index, tuple)):
-            index = [*index]
+        if is_list_like(index):
+            index = list(index)
         index = _select_column_names(index, df)
+        index = list(index)
 
-    if not index:
-        if not column_names:
-            column_names = [*df.columns]
+    if index is None:
+        if column_names is None:
+            column_names = df.columns.tolist()
         else:
-            index = [*df.columns.difference(column_names, sort=False)]
+            index = df.columns.difference(column_names, sort=False).tolist()
     else:
-        if not column_names:
-            column_names = [*df.columns.difference(index, sort=False)]
+        if column_names is None:
+            column_names = df.columns.difference(index, sort=False).tolist()
 
     len_names_to = 0
     if names_to is not None:
@@ -599,7 +599,7 @@ def _computations_pivot_longer(
     # since we already have made a copy of the original df
 
     if not column_names:
-        return df.rename_axis(columns=None)
+        return df
 
     if index:
         index = {
@@ -761,7 +761,7 @@ def _pivot_longer_names_pattern_sequence(
 
 def _pivot_longer_names_pattern_str(
     df: pd.DataFrame,
-    index: Union[list, None],
+    index: Union[dict, None],
     len_index: int,
     names_to: list,
     names_pattern: Union[str, Pattern],
@@ -818,7 +818,7 @@ def _pivot_longer_names_pattern_str(
 
 def _pivot_longer_names_sep(
     df: pd.DataFrame,
-    index: list,
+    index: Union[dict, None],
     len_index: int,
     names_to: list,
     names_sep: Union[str, Pattern],
diff --git a/janitor/functions/select_columns.py b/janitor/functions/select_columns.py
index 9cb7cae..9ddc1e2 100644
--- a/janitor/functions/select_columns.py
+++ b/janitor/functions/select_columns.py
@@ -56,16 +56,12 @@ def select_columns(
 
     # applicable for any
     # list-like object (ndarray, Series, pd.Index, ...)
-    # excluding tuples, which are returned as is
     search_column_names = []
     for arg in args:
         if is_list_like(arg) and (not isinstance(arg, tuple)):
-            search_column_names.extend([*arg])
+            search_column_names.extend(arg)
         else:
             search_column_names.append(arg)
-    if len(search_column_names) == 1:
-        search_column_names = search_column_names[0]
-
     full_column_list = _select_column_names(search_column_names, df)
 
     if invert:
diff --git a/janitor/functions/utils.py b/janitor/functions/utils.py
index 3e033b8..7f0b832 100644
--- a/janitor/functions/utils.py
+++ b/janitor/functions/utils.py
@@ -2,7 +2,6 @@
 from itertools import chain
 import fnmatch
 import warnings
-
 from collections.abc import Callable as dispatch_callable
 import re
 from typing import Hashable, Iterable, List, Optional, Pattern, Union
@@ -211,112 +210,62 @@ def _select_column_names(columns_to_select, df):
     base function for column selection.
     Returns a list of column names.
     """
-    raise TypeError("This type is not supported in column selection.")
-
-
-# hack to get it to recognize typing.Pattern
-# functools.singledispatch does not natively
-# recognize types from the typing module
-# `type(re.compile(r"\d+"))` returns re.Pattern
-# which is a type and functools.singledispatch
-# accepts it without drama;
-# however, the same type from typing.Pattern
-# is not accepted.
-@_select_column_names.register(type(re.compile(r"\d+")))  # noqa: F811
-def _column_sel_dispatch(columns_to_select, df):  # noqa: F811
-    """
-    Base function for column selection.
-    Applies only to regular expressions.
-    `re.compile` is required for the regular expression.
-    A list of column names is returned.
-    """
-    df_columns = df.columns
-    filtered_columns = [
-        column_name
-        for column_name in df_columns
-        if re.search(columns_to_select, column_name)
-    ]
-
-    if not filtered_columns:
-        raise KeyError("No column name matched the regular expression.")
-    df_columns = None
-
-    return filtered_columns
-
-
-@_select_column_names.register(tuple)  # noqa: F811
-def _column_sel_dispatch(columns_to_select, df):  # noqa: F811
-    """
-    Base function for column selection.
-    This caters to columns that are of tuple type.
-    The tuple is returned as is, if it exists in the columns.
-    """
-    if columns_to_select not in df.columns:
-        raise KeyError(f"No match was returned for {columns_to_select}")
-    return columns_to_select
+    if columns_to_select in df.columns:
+        return [columns_to_select]
+    raise KeyError(f"No match was returned for '{columns_to_select}'.")
 
 
-@_select_column_names.register(list)  # noqa: F811
+@_select_column_names.register(str)  # noqa: F811
 def _column_sel_dispatch(columns_to_select, df):  # noqa: F811
     """
     Base function for column selection.
-    Applies only to list type.
-    It can take any of slice, str, callable, re.Pattern types,
-    or a combination of these types.
-    A list of column names is returned.
+    Applies only to strings.
+    It is also applicable to shell-like glob strings,
+    specifically, the `*`.
+    A list/pandas Index of matching column names is returned.
     """
-
-    # takes care of boolean entries
-    if all(map(pd.api.types.is_bool, columns_to_select)):
-        if len(columns_to_select) != len(df.columns):
+    df_columns = df.columns
+    if pd.api.types.is_string_dtype(df_columns):
+        if (
+            "*" in columns_to_select
+        ):  # shell-style glob string (e.g., `*_thing_*`)
+            return fnmatch.filter(df_columns, columns_to_select)
+        if columns_to_select in df_columns:
+            return [columns_to_select]
+        raise KeyError(f"No match was returned for '{columns_to_select}'.")
+    if pd.api.types.is_datetime64_any_dtype(df_columns):
+        if not df_columns.is_monotonic_increasing:
             raise ValueError(
-                "The length of the list of booleans "
-                "does not match the number of columns "
-                "in the dataframe."
+                "The column is a DatetimeIndex and should be "
+                "monotonic increasing."
             )
-
-        return [*df.columns[columns_to_select]]
-
-    filtered_columns = []
-    columns_to_select = (
-        _select_column_names(entry, df) for entry in columns_to_select
+        timestamp = df_columns.get_loc(columns_to_select)
+        if isinstance(timestamp, slice):
+            return df_columns[timestamp]
+        return [df_columns[timestamp]]
+    raise KeyError(
+        f"String('{columns_to_select}') can be applied "
+        "only to string/datetime columns."
     )
 
-    # this is required,
-    # to maintain `tuple` status
-    # when combining all the entries into a single list
-    columns_to_select = (
-        [entry] if isinstance(entry, tuple) else entry
-        for entry in columns_to_select
-    )
-
-    columns_to_select = chain.from_iterable(columns_to_select)
-
-    # get rid of possible duplicates
-    for column_name in columns_to_select:
-        if column_name not in filtered_columns:
-            filtered_columns.append(column_name)
-
-    return filtered_columns
-
 
-@_select_column_names.register(str)  # noqa: F811
+@_select_column_names.register(re.Pattern)  # noqa: F811
 def _column_sel_dispatch(columns_to_select, df):  # noqa: F811
     """
     Base function for column selection.
-    Applies only to strings.
-    It is also applicable to shell-like glob strings,
-    specifically, the `*`.
-    A list of column names is returned.
+    Applies only to regular expressions.
+    `re.compile` is required for the regular expression.
+    A pandas Index of matching column names is returned.
     """
-
-    if "*" in columns_to_select:  # shell-style glob string (e.g., `*_thing_*`)
-        filtered_columns = fnmatch.filter(df.columns, columns_to_select)
-        if filtered_columns:
-            return filtered_columns
-    elif columns_to_select in df.columns:
-        return [columns_to_select]
-    raise KeyError(f"No match was returned for '{columns_to_select}'")
+    if pd.api.types.is_string_dtype(df.columns):
+        bools = df.columns.str.contains(
+            columns_to_select, na=False, regex=True
+        )
+        return df.columns[bools]
+    raise KeyError(
+        f"Regular expressions('{columns_to_select}') "
+        "can be applied only to string columns."
+    )
 
 
 @_select_column_names.register(slice)  # noqa: F811
@@ -324,20 +273,23 @@ def _column_sel_dispatch(columns_to_select, df):  # noqa: F811
     """
     Base function for column selection.
     Applies only to slices.
-    The start slice value must be a string or None;
+
+    The start slice value must be a string/tuple/None,
+    or exist in the dataframe's columns;
     same goes for the stop slice value.
     The step slice value should be an integer or None.
     A slice, if passed correctly in a Multindex column,
     returns a list of tuples across all levels of the
     column.
-    A list of column names is returned.
-    """
 
+    A pandas Index of matching column names is returned.
+    """
     df_columns = df.columns
     filtered_columns = None
     start_check = None
     stop_check = None
     step_check = None
+    method = None
 
     if not df_columns.is_unique:
         raise ValueError(
@@ -345,58 +297,60 @@ def _column_sel_dispatch(columns_to_select, df):  # noqa: F811
             "Kindly ensure the labels are unique "
             "to ensure the correct output."
         )
+    is_date_column = pd.api.types.is_datetime64_any_dtype(df_columns)
+    if is_date_column:
+        if not df_columns.is_monotonic_increasing:
+            raise ValueError(
+                "The column is a DatetimeIndex and should be "
+                "monotonic increasing."
+            )
+        method = "nearest"
 
     start, stop, step = (
         columns_to_select.start,
         columns_to_select.stop,
         columns_to_select.step,
     )
-    start_check = any((start is None, isinstance(start, str)))
-    stop_check = any((stop is None, isinstance(stop, str)))
+
     step_check = any((step is None, isinstance(step, int)))
-    if not start_check:
-        raise ValueError(
-            "The start value for the slice "
-            "must either be a string or `None`."
-        )
-    if not stop_check:
-        raise ValueError(
-            "The stop value for the slice "
-            "must either be a string or `None`."
-        )
     if not step_check:
         raise ValueError(
             "The step value for the slice "
             "must either be an integer or `None`."
         )
-    start_check = any((start is None, start in df_columns))
-    stop_check = any((stop is None, stop in df_columns))
-    if not start_check:
-        raise ValueError(
-            "The start value for the slice must either be `None` "
-            "or exist in the dataframe's columns."
-        )
-    if not stop_check:
-        raise ValueError(
-            "The stop value for the slice must either be `None` "
-            "or exist in the dataframe's columns."
-        )
+
+    if not is_date_column:
+        start_check = any((start is None, start in df_columns))
+        if not start_check:
+            raise ValueError(
+                "The start value for the slice must either be `None` "
+                "or exist in the dataframe's columns."
+            )
+        stop_check = any((stop is None, stop in df_columns))
+        if not stop_check:
+            raise ValueError(
+                "The stop value for the slice must either be `None` "
+                "or exist in the dataframe's columns."
+            )
 
     if start is None:
         start = 0
     else:
-        start = df_columns.get_loc(start)
+        start = df_columns.get_loc(start, method=method)
+        if isinstance(start, slice):
+            start = start.start
     if stop is None:
         stop = len(df_columns) + 1
     else:
-        stop = df_columns.get_loc(stop)
+        stop = df_columns.get_loc(stop, method=method)
+        if isinstance(stop, slice):
+            stop = stop.stop - 1
 
     if start > stop:
         filtered_columns = df_columns[slice(stop, start + 1, step)][::-1]
     else:
         filtered_columns = df_columns[slice(start, stop + 1, step)]
-    df_columns = None
-    return [*filtered_columns]
+    return filtered_columns
 
 
 @_select_column_names.register(dispatch_callable)  # noqa: F811
@@ -406,7 +360,8 @@ def _column_sel_dispatch(columns_to_select, df):  # noqa: F811
     Applies only to callables.
     The callable is applied to every column in the dataframe.
     Either True or False is expected per column.
-    A list of column names is returned.
+
+    A pandas Index of matching column names is returned.
     """
     # the function will be applied per series.
     # this allows filtration based on the contents of the series
@@ -416,9 +371,45 @@ def _column_sel_dispatch(columns_to_select, df):  # noqa: F811
     # the returned values should be a sequence of booleans,
     # with at least one True.
 
-    filtered_columns = df.agg(columns_to_select)
+    filtered_columns = df.apply(columns_to_select)
+
+    if not pd.api.types.is_bool_dtype(filtered_columns):
+        raise TypeError(
+            "The output of the applied callable should be a boolean array."
+        )
+
+    return df.columns[filtered_columns]
+
+
+@_select_column_names.register(list)  # noqa: F811
+def _column_sel_dispatch(columns_to_select, df):  # noqa: F811
+    """
+    Base function for column selection.
+    Applies only to list type.
+    It can take any of slice, str, callable, re.Pattern types, ...,
+    or a combination of these types.
+    A list of column names is returned.
+    """
+
+    if all(map(pd.api.types.is_bool, columns_to_select)):
+        if len(columns_to_select) != len(df.columns):
+            raise ValueError(
+                "The length of the list of booleans "
+                f"({len(columns_to_select)}) does not match "
+                f"the number of columns({df.columns.size}) "
+                "in the dataframe."
+            )
 
-    if not filtered_columns.any():
-        raise ValueError("No match was returned for the provided callable.")
+        return df.columns[columns_to_select]
 
-    return [*df.columns[filtered_columns]]
+    filtered_columns = (
+        _select_column_names(entry, df) for entry in columns_to_select
+    )
+
+    filtered_columns = list(chain.from_iterable(filtered_columns))
+
+    # get rid of possible duplicates
+    if len(filtered_columns) != len(set(filtered_columns)):
+        filtered_columns = pd.unique(filtered_columns)
+
+    return filtered_columns


Test patch:
diff --git a/tests/functions/test_pivot_longer.py b/tests/functions/test_pivot_longer.py
index 551acf7..d2c8ffa 100644
--- a/tests/functions/test_pivot_longer.py
+++ b/tests/functions/test_pivot_longer.py
@@ -36,12 +36,14 @@ def test_column_level_wrong_type(df_multi):
         df_multi.pivot_longer(index="name", column_level={0})
 
 
+@pytest.mark.xfail(reason="checking is done within _select_columns")
 def test_type_index(df_checks):
     """Raise TypeError if wrong type is provided for the index."""
     with pytest.raises(TypeError):
         df_checks.pivot_longer(index=2007)
 
 
+@pytest.mark.xfail(reason="checking is done within _select_columns")
 def test_type_column_names(df_checks):
     """Raise TypeError if wrong type is provided for column_names."""
     with pytest.raises(TypeError):
@@ -599,7 +601,10 @@ def test_no_column_names(df_checks):
     Test output if all the columns
     are assigned to the index parameter.
     """
-    assert_frame_equal(df_checks.pivot_longer(df_checks.columns), df_checks)
+    assert_frame_equal(
+        df_checks.pivot_longer(df_checks.columns).rename_axis(columns=None),
+        df_checks,
+    )
 
 
 @pytest.fixture
diff --git a/tests/utils/test__select_column.py b/tests/utils/test__select_column.py
index c9b3949..70e8304 100644
--- a/tests/utils/test__select_column.py
+++ b/tests/utils/test__select_column.py
@@ -5,11 +5,28 @@ import numpy as np
 import pandas as pd
 import pytest
 
+from pandas.testing import assert_index_equal
 from janitor.functions.utils import _select_column_names, patterns
 
 
+@pytest.fixture
+def df_dates():
+    """pytest fixture"""
+    start = datetime.datetime(2011, 1, 1)
+    end = datetime.datetime(2012, 1, 1)
+    rng = pd.date_range(start, end, freq="BM")
+    return pd.DataFrame([np.random.randn(len(rng))], columns=rng)
+
+
+@pytest.fixture
+def df_numbers():
+    """pytest fixture"""
+    return pd.DataFrame([np.random.randn(20)], columns=range(20))
+
+
 @pytest.fixture
 def df():
+    """pytest fixture."""
     return pd.DataFrame(
         {
             "id": [1, 2, 3],
@@ -27,6 +44,7 @@ def df():
 
 @pytest.fixture
 def df1():
+    """pytest fixture."""
     return pd.DataFrame(
         {
             "id": [0, 1],
@@ -47,6 +65,7 @@ def df1():
 
 @pytest.fixture
 def df_tuple():
+    "pytest fixture."
     frame = pd.DataFrame(
         {
             "A": {0: "a", 1: "b", 2: "c"},
@@ -58,16 +77,218 @@ def df_tuple():
     return frame
 
 
-def test_type(df):
-    """Raise TypeError if `columns_to_select` is the wrong type."""
-    with pytest.raises(TypeError):
+def test_col_not_found(df):
+    """Raise KeyError if `columns_to_select` is not in df.columns."""
+    with pytest.raises(KeyError, match="No match was returned.+"):
         _select_column_names(2.5, df)
-    with pytest.raises(TypeError):
+
+
+def test_col_not_found1(df):
+    """Raise KeyError if `columns_to_select` is not in df.columns."""
+    with pytest.raises(KeyError, match="No match was returned.+"):
         _select_column_names(1, df)
-    with pytest.raises(TypeError):
+
+
+def test_col_not_found2(df):
+    """Raise KeyError if `columns_to_select` is not in df.columns."""
+    with pytest.raises(KeyError, match="No match was returned.+"):
         _select_column_names([3, "id"], df)
 
 
+def test_col_not_found3(df_dates):
+    """Raise KeyError if `columns_to_select` is not in df.columns."""
+    with pytest.raises(KeyError):
+        _select_column_names("id", df_dates)
+
+
+def test_col_not_found4(df_numbers):
+    """Raise KeyError if `columns_to_select` is not in df.columns."""
+    with pytest.raises(KeyError, match=r"String\(.+\) can be applied.+"):
+        _select_column_names("id", df_numbers)
+
+
+def test_tuple(df_tuple):
+    """Test _select_column_names function on tuple."""
+    assert _select_column_names(("A", "D"), df_tuple) == [("A", "D")]
+
+
+def test_strings(df1):
+    """Test _select_column_names function on strings."""
+    assert _select_column_names("id", df1) == ["id"]
+    assert _select_column_names("*type*", df1) == [
+        "type",
+        "type1",
+        "type2",
+        "type3",
+    ]
+
+
+def test_strings_do_not_exist(df):
+    """
+    Raise KeyError if `columns_to_select` is a string
+    and does not exist in the dataframe's columns.
+    """
+    with pytest.raises(KeyError, match="No match was returned for.+"):
+        _select_column_names("word", df)
+
+
+def test_strings_dates(df_dates):
+    """
+    Test output for datetime column.
+    """
+    assert (
+        _select_column_names("2011-01-31", df_dates)[0]
+        == df_dates.loc[:, "2011-01-31"].name
+    )
+
+
+def test_strings_dates_range(df_dates):
+    """Test output for datetime column."""
+    assert_index_equal(
+        _select_column_names("2011-01", df_dates),
+        df_dates.loc[:, slice("2011-01")].columns,
+    )
+
+
+def test_unsorted_dates(df_dates):
+    """Raise Error if the dates are unsorted."""
+    df_dates = df_dates.iloc[:, [10, 4, 7, 2, 1, 3, 5, 6, 8, 9, 11, 0]]
+    with pytest.raises(
+        ValueError,
+        match="The column is a DatetimeIndex and should be "
+        "monotonic increasing.",
+    ):
+        _select_column_names("2011-01-31", df_dates)
+
+
+def test_regex(df1):
+    """Test _select_column_names function on regular expressions."""
+    assert_index_equal(
+        _select_column_names(re.compile(r"\d$"), df1),
+        df1.filter(regex=r"\d$").columns,
+    )
+
+
+def test_patterns_warning(df1):
+    """
+    Check that warning is raised if `janitor.patterns` is used.
+    """
+    with pytest.warns(DeprecationWarning):
+        assert_index_equal(
+            _select_column_names(patterns(r"\d$"), df1),
+            df1.filter(regex=r"\d$").columns,
+        )
+
+
+def test_regex_presence(df_dates):
+    """
+    Raise KeyError if `columns_to_select` is a regex
+    and the columns is not a string column.
+    """
+    with pytest.raises(
+        KeyError, match=r"Regular expressions\(.+\) can be applied.+"
+    ):
+        _select_column_names(re.compile(r"^\d+"), df_dates)
+
+
+def test_slice_unique():
+    """
+    Raise ValueError if the columns are not unique.
+    """
+    not_unique = pd.DataFrame([], columns=["code", "code", "code1", "code2"])
+    with pytest.raises(ValueError):
+        _select_column_names(slice("code", "code2"), not_unique)
+
+
+def test_slice_presence(df):
+    """
+    Raise ValueError if `columns_to_select` is a slice instance
+    and either the start value or the end value is not present
+    in the dataframe.
+    """
+    with pytest.raises(ValueError):
+        _select_column_names(slice("Id", "M_start_date_1"), df)
+    with pytest.raises(ValueError):
+        _select_column_names(slice("id", "M_end_date"), df)
+
+
+def test_slice_dtypes(df):
+    """
+    Raise ValueError if `columns_to_select` is a slice instance
+    and either the start value or the stop value is not a string,
+    or the step value is not an integer.
+    """
+    with pytest.raises(ValueError):
+        _select_column_names(slice(1, "M_end_date_2"), df)
+    with pytest.raises(ValueError):
+        _select_column_names(slice("id", 2), df)
+    with pytest.raises(ValueError):
+        _select_column_names(slice("id", "M_end_date_2", "3"), df)
+
+
+def test_unsorted_dates_slice(df_dates):
+    """Raise Error if the dates are unsorted."""
+    df_dates = df_dates.iloc[:, ::-1]
+    with pytest.raises(
+        ValueError,
+        match="The column is a DatetimeIndex and should be "
+        "monotonic increasing.",
+    ):
+        _select_column_names(slice("2011-01-31", "2011-03-31"), df_dates)
+
+
+def test_slice(df1):
+    """Test _select_column_names function on slices."""
+    assert_index_equal(
+        _select_column_names(slice("code", "code2"), df1),
+        df1.loc[:, slice("code", "code2")].columns,
+    )
+
+    assert_index_equal(
+        _select_column_names(slice("code2", None), df1),
+        df1.loc[:, slice("code2", None)].columns,
+    )
+
+    assert_index_equal(
+        _select_column_names(slice(None, "code2"), df1),
+        df1.loc[:, slice(None, "code2")].columns,
+    )
+
+    assert_index_equal(
+        _select_column_names(slice(None, None), df1), df1.columns
+    )
+    assert_index_equal(
+        _select_column_names(slice(None, None, 2), df1),
+        df1.loc[:, slice(None, None, 2)].columns,
+    )
+    assert_index_equal(
+        _select_column_names(slice("code2", "code"), df1),
+        pd.Index(
+            [
+                "code2",
+                "code1",
+                "code",
+            ]
+        ),
+    )
+
+
+def test_slice_dates(df_dates):
+    """Test output of slice on date column."""
+    assert_index_equal(
+        _select_column_names(slice("2011-01-31", "2011-03-31"), df_dates),
+        df_dates.loc[:, "2011-01-31":"2011-03-31"].columns,
+    )
+
+
+def test_slice_dates_inexact(df_dates):
+    """Test output of slice on date column."""
+    assert_index_equal(
+        _select_column_names(slice("2011-01", "2011-03"), df_dates),
+        df_dates.loc[:, "2011-01":"2011-03"].columns,
+    )
+
+
 @pytest.mark.xfail(reason="level parameter removed.")
 def test_level_type(df_tuple):
     """Raise TypeError if `level` is the wrong type."""
@@ -105,17 +326,6 @@ def test_tuple_regex(df_tuple):
         _select_column_names(re.compile("A"), df_tuple)
 
 
-def test_strings_do_not_exist(df):
-    """
-    Raise KeyError if `columns_to_select` is a string
-    and does not exist in the dataframe's columns.
-    """
-    with pytest.raises(KeyError):
-        _select_column_names("word", df)
-    with pytest.raises(KeyError):
-        _select_column_names("*starter", df)
-
-
 def test_boolean_list_dtypes(df):
     """
     Raise ValueError if `columns_to_select` is a list of booleans
@@ -131,63 +341,22 @@ def test_boolean_list_dtypes(df):
         )
 
 
-def test_slice_dtypes(df):
-    """
-    Raise ValueError if `columns_to_select` is a slice instance
-    and either the start value or the stop value is not a string,
-    or the step value is not an integer.
-    """
-    with pytest.raises(ValueError):
-        _select_column_names(slice(1, "M_end_date_2"), df)
-    with pytest.raises(ValueError):
-        _select_column_names(slice("id", 2), df)
-    with pytest.raises(ValueError):
-        _select_column_names(slice("id", "M_end_date_2", "3"), df)
-
-
-def test_slice_presence(df):
-    """
-    Raise ValueError if `columns_to_select` is a slice instance
-    and either the start value or the end value is not present
-    in the dataframe.
-    """
-    with pytest.raises(ValueError):
-        _select_column_names(slice("Id", "M_start_date_1"), df)
-    with pytest.raises(ValueError):
-        _select_column_names(slice("id", "M_end_date"), df)
-
-
-def test_slice_unique():
-    """
-    Raise ValueError if the columns are not unique.
-    """
-    not_unique = pd.DataFrame([], columns=["code", "code", "code1", "code2"])
-    with pytest.raises(ValueError):
-        _select_column_names(slice("code", "code2"), not_unique)
-
-
-def test_callable(df):
+def test_callable(df_numbers):
     """
     Check that error is raised if `columns_to_select` is a
     callable, and at lease one Series has a wrong data type
     that makes the callable unapplicable.
     """
-    with pytest.raises(TypeError):
-        _select_column_names(object, df)
-
-
-def test_patterns_warning(df1):
-    """
-    Check that warning is raised if `janitor.patterns` is used.
-    """
-    with pytest.warns(DeprecationWarning):
-        assert _select_column_names(patterns(r"\d$"), df1) == list(
-            df1.filter(regex=r"\d$").columns
-        )
+    with pytest.raises(
+        TypeError,
+        match="The output of the applied callable "
+        "should be a boolean array.",
+    ):
+        _select_column_names(lambda df: df + 3, df_numbers)
 
 
 @pytest.mark.xfail(reason="Indexing in Pandas is possible with a Series.")
-def test_callable_returns_Series(df):
+def test_callable_returns_series(df):
     """
     Check that error is raised if `columns_to_select` is a
     callable, and returns a Series.
@@ -198,23 +367,12 @@ def test_callable_returns_Series(df):
 
 def test_callable_no_match(df):
     """
-    Raise ValueError if `columns_to_select` is a callable,
+    Test if `columns_to_select` is a callable,
     and no value is returned.
     """
-    with pytest.raises(ValueError):
-        _select_column_names(pd.api.types.is_float_dtype, df)
-
-    with pytest.raises(ValueError):
-        _select_column_names(lambda x: "Date" in x.name, df)
-
+    assert _select_column_names(pd.api.types.is_float_dtype, df).empty
 
-def test_regex_presence(df):
-    """
-    Raise KeyError if `columns_to_select` is a regex
-    and none of the column names match.
-    """
-    with pytest.raises(KeyError):
-        _select_column_names(re.compile(r"^\d+"), df)
+    assert _select_column_names(lambda x: "Date" in x.name, df).empty
 
 
 def test_tuple_presence(df_tuple):
@@ -222,83 +380,43 @@ def test_tuple_presence(df_tuple):
     Raise KeyError if `columns_to_select` is a tuple
     and no match is returned.
     """
-    with pytest.raises(KeyError):
+    with pytest.raises(KeyError, match="No match was returned.+"):
         _select_column_names(("A", "C"), df_tuple)
 
 
-def test_strings(df1):
-    """Test _select_column_names function on strings."""
-    assert _select_column_names("id", df1) == ["id"]
-    assert _select_column_names("*type*", df1) == [
-        "type",
-        "type1",
-        "type2",
-        "type3",
-    ]
-
-
-def test_slice(df1):
-    """Test _select_column_names function on slices."""
-    assert (
-        _select_column_names(slice("code", "code2"), df1)
-        == df1.loc[:, slice("code", "code2")].columns.tolist()
-    )
-
-    assert (
-        _select_column_names(slice("code2", None), df1)
-        == df1.loc[:, slice("code2", None)].columns.tolist()
-    )
-
-    assert (
-        _select_column_names(slice(None, "code2"), df1)
-        == df1.loc[:, slice(None, "code2")].columns.tolist()
-    )
-
-    assert _select_column_names(slice(None, None), df1) == df1.columns.tolist()
-    assert (
-        _select_column_names(slice(None, None, 2), df1)
-        == df1.loc[:, slice(None, None, 2)].columns.tolist()
-    )
-    assert _select_column_names(slice("code2", "code"), df1) == [
-        "code2",
-        "code1",
-        "code",
-    ]
-
-
 def test_callable_data_type(df1):
     """
     Test _select_column_names function on callables,
     specifically for data type checks.
     """
-    assert (
-        _select_column_names(pd.api.types.is_integer_dtype, df1)
-        == df1.select_dtypes(int).columns.tolist()
+    assert_index_equal(
+        _select_column_names(pd.api.types.is_integer_dtype, df1),
+        df1.select_dtypes(int).columns,
     )
 
-    assert (
-        _select_column_names(pd.api.types.is_float_dtype, df1)
-        == df1.select_dtypes(float).columns.tolist()
+    assert_index_equal(
+        _select_column_names(pd.api.types.is_float_dtype, df1),
+        df1.select_dtypes(float).columns,
     )
 
-    assert (
-        _select_column_names(pd.api.types.is_numeric_dtype, df1)
-        == df1.select_dtypes("number").columns.tolist()
+    assert_index_equal(
+        _select_column_names(pd.api.types.is_numeric_dtype, df1),
+        df1.select_dtypes("number").columns,
     )
 
-    assert (
-        _select_column_names(pd.api.types.is_categorical_dtype, df1)
-        == df1.select_dtypes("category").columns.tolist()
+    assert_index_equal(
+        _select_column_names(pd.api.types.is_categorical_dtype, df1),
+        df1.select_dtypes("category").columns,
     )
 
-    assert (
-        _select_column_names(pd.api.types.is_datetime64_dtype, df1)
-        == df1.select_dtypes(np.datetime64).columns.tolist()
+    assert_index_equal(
+        _select_column_names(pd.api.types.is_datetime64_dtype, df1),
+        df1.select_dtypes(np.datetime64).columns,
     )
 
-    assert (
-        _select_column_names(pd.api.types.is_object_dtype, df1)
-        == df1.select_dtypes("object").columns.tolist()
+    assert_index_equal(
+        _select_column_names(pd.api.types.is_object_dtype, df1),
+        df1.select_dtypes("object").columns,
     )
 
 
@@ -307,25 +425,34 @@ def test_callable_string_methods(df1):
     Test _select_column_names function on callables,
     specifically for column name checks.
     """
-    assert _select_column_names(
-        lambda x: x.name.startswith("type"), df1
-    ) == list(df1.filter(like="type").columns)
+    assert_index_equal(
+        _select_column_names(lambda x: x.name.startswith("type"), df1),
+        df1.filter(like="type").columns,
+    )
 
-    assert _select_column_names(
-        lambda x: x.name.endswith(("1", "2", "3")), df1
-    ) == list(df1.filter(regex=r"\d$").columns)
+    assert_index_equal(
+        _select_column_names(lambda x: x.name.endswith(("1", "2", "3")), df1),
+        df1.filter(regex=r"\d$").columns,
+    )
 
-    assert _select_column_names(lambda x: "d" in x.name, df1) == list(
-        df1.filter(regex="d").columns
+    assert_index_equal(
+        _select_column_names(lambda x: "d" in x.name, df1),
+        df1.filter(regex="d").columns,
     )
 
-    assert _select_column_names(
-        lambda x: x.name.startswith("code") and x.name.endswith("1"), df1
-    ) == list(df1.filter(regex=r"code.*1$").columns)
+    assert_index_equal(
+        _select_column_names(
+            lambda x: x.name.startswith("code") and x.name.endswith("1"), df1
+        ),
+        df1.filter(regex=r"code.*1$").columns,
+    )
 
-    assert _select_column_names(
-        lambda x: x.name.startswith("code") or x.name.endswith("1"), df1
-    ) == list(df1.filter(regex=r"^code.*|.*1$").columns)
+    assert_index_equal(
+        _select_column_names(
+            lambda x: x.name.startswith("code") or x.name.endswith("1"), df1
+        ),
+        df1.filter(regex=r"^code.*|.*1$").columns,
+    )
 
 
 def test_callable_computations(df1):
@@ -333,23 +460,12 @@ def test_callable_computations(df1):
     Test _select_column_names function on callables,
     specifically for computations.
     """
-    assert _select_column_names(lambda x: x.isna().any(), df1) == list(
-        df1.columns[df1.isna().any().array]
-    )
-
-
-def test_regex(df1):
-    """Test _select_column_names function on regular expressions."""
-    assert _select_column_names(re.compile(r"\d$"), df1) == list(
-        df1.filter(regex=r"\d$").columns
+    assert_index_equal(
+        _select_column_names(lambda x: x.isna().any(), df1),
+        df1.columns[df1.isna().any().array],
     )
 
 
-def test_tuple(df_tuple):
-    """Test _select_column_names function on tuple."""
-    assert _select_column_names(("A", "D"), df_tuple) == ("A", "D")
-
-
 def test_list_various(df1):
     """Test _select_column_names function on list type."""
 
@@ -357,14 +473,15 @@ def test_list_various(df1):
     assert _select_column_names(["id", "code*"], df1) == list(
         df1.filter(regex="^id|^code").columns
     )
-    assert (
-        _select_column_names(["id", "code*", slice("code", "code2")], df1)
-        == df1.filter(regex="^(id|code)").columns.tolist()
-    )
+    assert [
+        *_select_column_names(["id", "code*", slice("code", "code2")], df1)
+    ] == df1.filter(regex="^(id|code)").columns.tolist()
     assert _select_column_names(["id", "Name"], df1) == ["id", "Name"]
 
 
 def test_list_boolean(df):
     """Test _select_column_names function on list of booleans."""
     booleans = [True, True, True, False, False, False, True, True, True]
-    assert _select_column_names(booleans, df) == list(df.columns[booleans])
+    assert_index_equal(
+        _select_column_names(booleans, df), df.columns[booleans]
+    )