Repository: pyjanitor-devs/pyjanitor
Pull request #874 (2021-08-16 04:52:49)

[ENH] Pivot wider improve
<!-- Thank you for your PR!

BEFORE YOU CONTINUE! Please add the appropriate three-letter abbreviation to your title.

The abbreviations can be:
- [DOC]: Documentation fixes.


# PR Description

Please describe the changes proposed in the pull request:

- focus is just to transform from long to wide via pivot, with some additional conveniences
- get rid of `aggfunc`, `fill_value` (aggregation can be achieved with groupby)
- replace `names_prefix` with `levels_order` and `names_glue`, allowing for finer control of output
- aiming to reduce complexity

<!-- 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 improves `pivot_wider`.**

<!-- 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. [x] PR in from a fork off your branch. Do not PR from `<your_username>`:`dev`, but rather from `<your_username>`:`<feature-branch_name>`.
<!-- Doing this helps us keep the commit history much cleaner than it would otherwise be. -->
2. [x] If you're not on the contributors list, add yourself to `AUTHORS.rst`.
<!-- 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


codecov[bot]: # [Codecov](https://codecov.io/gh/pyjanitor-devs/pyjanitor/pull/874?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#874](https://codecov.io/gh/pyjanitor-devs/pyjanitor/pull/874?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (460a2e5) into [dev](https://codecov.io/gh/pyjanitor-devs/pyjanitor/commit/5e2ccb36c77728660d6c8f550d53bf20d0383391?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (5e2ccb3) will **decrease** coverage by `0.03%`.
> The diff coverage is `100.00%`.


```diff
@@            Coverage Diff             @@
##              dev     #874      +/-   ##
==========================================
- Coverage   94.89%   94.85%   -0.04%     
==========================================
  Files          19       19              
  Lines        2078     2062      -16     
==========================================
- Hits         1972     1956      -16     
  Misses        106      106              
```

ericmjl: @samukweku I'm going to hit the big green button here!

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7dc64b2..b017fef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,9 +2,8 @@
 
 ## [Unreleased]
 -   [INF] Simplify a bit linting, use pre-commit as the CI linting checker. @Zeroto521
-
-
 -   [ENH] Fix bug in `pivot_longer` for wrong output when `names_pattern` is a sequence with a single value. Issue #885 @samukweku
+-   [ENH] Deprecate `aggfunc` from `pivot_wider`; aggregation can be chained with pandas' `groupby`.
 
 ## [v0.21.1] - 2021-08-29
 
diff --git a/examples/notebooks/Pivoting Data from Wide to Long.ipynb b/examples/notebooks/Pivoting Data from Wide to Long.ipynb
index 608de1b..a748da3 100644
--- a/examples/notebooks/Pivoting Data from Wide to Long.ipynb
+++ b/examples/notebooks/Pivoting Data from Wide to Long.ipynb
@@ -8676,4 +8676,4 @@
  },
  "nbformat": 4,
  "nbformat_minor": 2
-}
\ No newline at end of file
+}
diff --git a/janitor/functions.py b/janitor/functions.py
index 1e7cce4..c0d0be4 100644
--- a/janitor/functions.py
+++ b/janitor/functions.py
@@ -16,7 +16,6 @@ from typing import (
     NamedTuple,
     Optional,
     Pattern,
-    Sequence,
     Set,
     Tuple,
     Union,
@@ -6228,16 +6227,14 @@ def pivot_longer(
 @pf.register_dataframe_method
 def pivot_wider(
     df: pd.DataFrame,
-    index: Optional[Union[Sequence[str], str]] = None,
-    names_from: Optional[Union[Sequence[str], str]] = None,
-    values_from: Optional[Union[Sequence[str], str]] = None,
-    names_sort: Optional[bool] = True,
+    index: Optional[Union[List, str]] = None,
+    names_from: Optional[Union[List, str]] = None,
+    values_from: Optional[Union[List, str]] = None,
+    names_sort: Optional[bool] = False,
+    levels_order: Optional[list] = None,
     flatten_levels: Optional[bool] = True,
-    names_from_position: Optional[str] = "first",
-    names_prefix: Optional[str] = None,
-    names_sep: Optional[str] = "_",
-    aggfunc: Optional[Union[str, list, dict, Callable]] = None,
-    fill_value: Optional[Union[int, float, str]] = None,
+    names_sep="_",
+    names_glue: Callable = None,
 ) -> pd.DataFrame:
     """
     Reshapes data from 'long' to 'wide' form.
@@ -6248,6 +6245,10 @@ def pivot_wider(
 
     This method does not mutate the original DataFrame.
 
+    .. note:: Column selection in `index`, `names_from`
+        and `values_from` is possible using the
+        `janitor.select_columns` syntax.
+
     Reshaping to wide form :
 
     .. code-block:: python
@@ -6266,9 +6267,7 @@ def pivot_wider(
         10  Carla      wk3     39
         11  Carla      wk4     40
 
-        df = (
-            pd.DataFrame(...)
-            .pivot_wider(
+        df.pivot_wider(
                 index = "name",
                 names_from = "variable",
                 values_from = "value"
@@ -6288,60 +6287,137 @@ def pivot_wider(
         1     2  20.0  0.2
         2     3  30.0  0.3
 
-        df = (
-            pd.DataFrame(...)
-            .assign(num = 0)
-            .pivot_wider(
-                index = "num",
-                names_from = "name",
-                values_from = ["n", "pct"],
-                names_sep = "_"
-             )
-         )
+        (df.assign(num = 0)
+           .pivot_wider(
+              index = "num",
+              names_from = "name",
+              values_from = ["n", "pct"],
+              names_sep = "_"
+              )
+        )
 
             num n_1  n_2  n_3  pct_1  pct_2  pct_3
         0   0   10   20   30   0.1    0.2    0.3
 
-    Aggregations are also possible with the ``aggfunc`` parameter::
-
-        df = pd.DataFrame([{'id': 'a', 'name': 'Adam', 'value': 5},
-                           {'id': 'b', 'name': 'Eve', 'value': 6},
-                           {'id': 'c', 'name': 'Adam', 'value': 4},
-                           {'id': 'a', 'name': 'Eve', 'value': 3},
-                           {'id': 'd', 'name': 'Seth', 'value': 2},
-                           {'id': 'b', 'name': 'Adam', 'value': 4},
-                           {'id': 'a', 'name': 'Adam', 'value': 2}])
-
-        id  name    value
-        a   Adam    5
-        b   Eve     6
-        c   Adam    4
-        a   Eve     3
-        d   Seth    2
-        b   Adam    4
-        a   Adam    2
+
+    You may choose not to flatten the columns,
+    by setting `flatten_levels` to False::
+
+        df
+
+           dependent_variable  step   a   b
+        0                 5.5     1  20  30
+        1                 5.5     2  25  37
+        2                 6.1     1  22  19
+        3                 6.1     2  18  29
+
+
+        df.pivot_wider(
+            index = "dep*",
+            names_from  = 'step',
+            flatten_levels = False
+            )
+
+                             a       b
+        step                 1   2   1   2
+        dependent_variable
+        5.5                 20  25  30  37
+        6.1                 22  18  19  29
+
+
+    The order of the levels can be changed with the `levels_order`
+    parameter, which internally uses `pd.DataFrame.reorder_levels`::
+
+        df.pivot_wider(
+            index = "dep*",
+            names_from  = 'step',
+            flatten_levels = False,
+            levels_order = ['step', None]
+            )
+
+        step                 1   2   1   2
+                             a   a   b   b
+        dependent_variable
+        5.5                 20  25  30  37
+        6.1                 22  18  19  29
 
         df.pivot_wider(
-            index = "id",
-            names_from = "name",
-            aggfunc = np.sum,
-            values_from = "value",
+            index = ['a', 'b'],
+            names_from = 'name',
             flatten_levels = True,
-            fill_value = 0
             )
 
-            id  Adam  Eve  Seth
-        0   a     7    3     0
-        1   b     4    6     0
-        2   c     4    0     0
-        3   d     0    0     2
+           dependent_variable  a_1  a_2  b_1  b_2
+        0                 5.5   20   25   30   37
+        1                 6.1   22   18   19   29
+
+
+        df.pivot_wider(
+            index = ['a', 'b'],
+            names_from = 'name',
+            flatten_levels= True,
+            levels_order = ['step', None]
+            )
+
+           dependent_variable  1_a  2_a  1_b  2_b
+        0                 5.5   20   25   30   37
+        1                 6.1   22   18   19   29
+
+    `names_sep` and `names_glue` come in handy in situations where `names_from`
+    and/or `values_from` contain multiple variables; it is used primarily when
+    the columns are flattened. The default value for `names_sep` is `_`::
+
+        # default value of names_sep is '_'
+        df.pivot_wider(index = "dep*", names_from = "step")
 
+           dependent_variable  a_1  a_2  b_1  b_2
+        0                 5.5   20   25   30   37
+        1                 6.1   22   18   19   29
 
-    .. note:: You may choose not to collapse the levels by passing `False`
-        to the ``flatten_levels`` argument.
+        df.pivot_wider(
+            index = "dep*",
+            names_from = "step",
+            names_sep = "")
+
+           dependent_variable  a1  a2  b1  b2
+        0                 5.5  20  25  30  37
+        1                 6.1  22  18  19  29
 
-    .. note:: A ValueError is raised if the index is not unique and
-        `aggfunc` is None.
+    With `names_glue` you can `glue` the individual levels (if MultiIndex)
+    into one (similar to `names_sep`), or you can modify the final columns,
+    as long as it can be passed to `pd.Index.map`::
+
+        # replicate `names_sep`
+        df.pivot_wider(
+            index = "dep*",
+            names_from = "step",
+            names_sep = None,
+            names_glue = "_".join
+            )
+
+           dependent_variable  a_1  a_2  b_1  b_2
+        0                 5.5   20   25   30   37
+        1                 6.1   22   18   19   29
+
+        # going beyond names_sep
+        df.pivot_wider(
+            index = "dep*",
+            names_from = "step",
+            names_sep = None,
+            names_glue = lambda col: f"{col[0]}_step{col[1]}"
+            )
+
+           dependent_variable  a_step1  a_step2  b_step1  b_step2
+        0                 5.5       20       25       30       37
+        1                 6.1       22       18       19       29
+
+    .. note:: A ValueError is raised if the combination
+        of the `index` and `names_from` is not unique.
+
+    .. note:: By default, values from `values_from` are always
+        at the top level if the columns are not flattened.
+        If flattened, the values from `values_from` are usually
+        at the start of each label in the columns.
 
     Functional usage syntax:
 
@@ -6358,12 +6434,11 @@ def pivot_wider(
             names_from = [column3, column4, ...],
             value_from = [column5, column6, ...],
             names_sort = True/False,
-            names_prefix = string,
-            names_sep = string,
+            levels_order = None/list,
             flatten_levels = True/False,
-            names_from_position = "first"/"last",
-            aggfunc,
-            fill_value = fill_value
+            names_sep='_',
+            names_glue= Callable
+
         )
 
     Method chaining syntax:
@@ -6377,12 +6452,10 @@ def pivot_wider(
                 names_from = [column3, column4, ...],
                 value_from = [column5, column6, ...],
                 names_sort = True/False,
-                names_prefix = string,
-                names_sep = string,
+                levels_order = None/list,
                 flatten_levels = True/False,
-                names_from_position = "first"/"last",
-                aggfunc,
-                fill_value = fill_value
+                names_sep='_',
+                names_glue= Callable
                 )
         )
 
@@ -6391,7 +6464,7 @@ def pivot_wider(
         Should be either a single column name, or a list of column names.
         The `janitor.select_columns` syntax is supported here,
         allowing for flexible and dynamic column selection.
-        If `index` is not provided, the current dataframe's index is used.
+        If `index` is not provided, the dataframe's index is used.
     :param names_from: Name(s) of column(s) to use to make the new
         dataframe's columns. Should be either a single column name, or a
         list of column names.
@@ -6399,49 +6472,33 @@ def pivot_wider(
         allowing for flexible and dynamic column selection.
         A label or labels must be provided for ``names_from``.
     :param values_from: Name(s) of column(s) that will be used for populating
-        the new dataframe's values. Should be either a single column name,
-        or a list of column names.
+        the new dataframe's values.
         The `janitor.select_columns` syntax is supported here,
         allowing for flexible and dynamic column selection.
-        If ``values_from`` is not specified,
-        all remaining columns will be used. If `flatten_levels` is ``False``,
-        a MultiIndex dataframe is created.
+        If ``values_from`` is not specified,  all remaining columns
+        will be used. Note that values from `values_from` are usually at
+        the top level, the dataframe's columns is not flattened, or the
+        start of each label in the columns, if flattened.
     :param names_sort: Default is `True`. Sorts columns by order of
         appearance.
-        Set as `True` to get the columns sorted lexicographicially,
-        or if the columns are of category type.
+    :param levels_order: Applicable if there are multiple `names_from`
+        and/or `values_from`. Reorders the levels. Accepts a list of strings.
+        If there are multiple `values_from`, pass a None to represent that
+        level.
     :param flatten_levels: Default is `True`. If `False`, the dataframe stays
         as a MultiIndex.
-    :param names_from_position: By default, the values in ``names_from`` stay
-        at the front of the new column names. This can be changed to "last";
-        this places the values in ``names_from``
-        at the tail of the column names.
-    :param names_prefix: String to be added to the front of each output column.
-        Can be handy if the values in ``names_from`` are numeric data types.
-        Applicable only if ``flatten_levels`` is True.
     :param names_sep: If ``names_from`` or ``values_from`` contain multiple
         variables, this will be used to join their values into a single string
         to use as a column name. Default is ``_``.
         Applicable only if ``flatten_levels`` is ``True``.
-    :param aggfunc: An aggregate function. It can be a function, a string,
-        list of functions, or a dictionary, pairing column name with aggregate
-        function.
-    :param fill_value: Scalar value to replace missing values with
-        (after pivoting).
+    :param names_glue: A callable to control the output
+        of the flattened columns. Applicable only if
+        ``flatten_levels`` is ``True``. Function should be
+        acceptable to pandas' `map` function.
     :returns: A pandas DataFrame that has been unpivoted from long to wide
         form.
-    :raises TypeError: if `index` or `names_from` is not a string, or a list of
-        strings.
     :raises ValueError: if `names_from` is None.
-    :raises TypeError: if `names_sep` is not a string.
-    :raises TypeError: if `values_from` is not a string or a list of strings.
-    :raises TypeError: if `names_sort` is not a boolean.
     :raises TypeError: if `flatten_levels` is not a boolean.
-    :raises ValueError: if values in `index` or `names_from` or `values_from`
-        do not exist in the dataframe.
-    :raises ValueError: if the combination of `index` and `names_from` is not
-        unique and ``aggfunc`` is ``None``.
-
 
     .. # noqa: DAR402
     """
@@ -6454,24 +6511,20 @@ def pivot_wider(
         names_from,
         values_from,
         names_sort,
+        levels_order,
         flatten_levels,
-        names_from_position,
-        names_prefix,
         names_sep,
-        aggfunc,
-        fill_value,
+        names_glue,
     ) = _data_checks_pivot_wider(
         df,
         index,
         names_from,
         values_from,
         names_sort,
+        levels_order,
         flatten_levels,
-        names_from_position,
-        names_prefix,
         names_sep,
-        aggfunc,
-        fill_value,
+        names_glue,
     )
 
     df = _computations_pivot_wider(
@@ -6480,12 +6533,10 @@ def pivot_wider(
         names_from,
         values_from,
         names_sort,
+        levels_order,
         flatten_levels,
-        names_from_position,
-        names_prefix,
         names_sep,
-        aggfunc,
-        fill_value,
+        names_glue,
     )
 
     return df
diff --git a/janitor/utils.py b/janitor/utils.py
index 3328d98..de07859 100644
--- a/janitor/utils.py
+++ b/janitor/utils.py
@@ -1836,12 +1836,10 @@ def _data_checks_pivot_wider(
     names_from,
     values_from,
     names_sort,
+    levels_order,
     flatten_levels,
-    names_from_position,
-    names_prefix,
     names_sep,
-    aggfunc,
-    fill_value,
+    names_glue,
 ):
 
     """
@@ -1854,47 +1852,37 @@ def _data_checks_pivot_wider(
 
     if index is not None:
         if is_list_like(index):
-            index = list(index)
+            index = [*index]
         index = _select_columns(index, df)
 
     if names_from is None:
         raise ValueError(
-            "pivot_wider() missing 1 required argument: 'names_from'"
+            "pivot_wider() is missing 1 required argument: 'names_from'"
         )
 
     if is_list_like(names_from):
-        names_from = list(names_from)
+        names_from = [*names_from]
     names_from = _select_columns(names_from, df)
 
     if values_from is not None:
-        check("values_from", values_from, [list, str])
+        if is_list_like(values_from):
+            values_from = [*values_from]
         values_from = _select_columns(values_from, df)
+        if len(values_from) == 1:
+            values_from = values_from[0]
 
     check("names_sort", names_sort, [bool])
 
-    check("flatten_levels", flatten_levels, [bool])
+    if levels_order is not None:
+        check("levesl_order", levels_order, [list])
 
-    if names_from_position is not None:
-        check("names_from_position", names_from_position, [str])
-        if names_from_position not in ("first", "last"):
-            raise ValueError(
-                """
-                The position of `names_from`
-                must be either "first" or "last".
-                """
-            )
-
-    if names_prefix is not None:
-        check("names_prefix", names_prefix, [str])
+    check("flatten_levels", flatten_levels, [bool])
 
     if names_sep is not None:
         check("names_sep", names_sep, [str])
 
-    if aggfunc is not None:
-        check("aggfunc", aggfunc, [str, list, dict, callable])
-
-    if fill_value is not None:
-        check("fill_value", fill_value, [int, float, str])
+    if names_glue is not None:
+        check("names_glue", names_glue, [callable])
 
     return (
         df,
@@ -1902,12 +1890,10 @@ def _data_checks_pivot_wider(
         names_from,
         values_from,
         names_sort,
+        levels_order,
         flatten_levels,
-        names_from_position,
-        names_prefix,
         names_sep,
-        aggfunc,
-        fill_value,
+        names_glue,
     )
 
 
@@ -1917,103 +1903,60 @@ def _computations_pivot_wider(
     names_from: Optional[Union[List, str]] = None,
     values_from: Optional[Union[List, str]] = None,
     names_sort: Optional[bool] = False,
+    levels_order: Optional[list] = None,
     flatten_levels: Optional[bool] = True,
-    names_from_position: Optional[str] = "first",
-    names_prefix: Optional[str] = None,
-    names_sep: Optional[str] = "_",
-    aggfunc: Optional[Union[str, list, dict, Callable]] = None,
-    fill_value: Optional[Union[int, float, str]] = None,
+    names_sep="_",
+    names_glue: Callable = None,
 ) -> pd.DataFrame:
     """
     This is the main workhorse of the `pivot_wider` function.
 
-    By default, values from `names_from` are at the front of
-    each output column. If there are multiple `values_from`,
-    this can be changed via the `names_from_position`,
-    by setting it to `last`.
+    It is a wrapper around `pd.pivot`. For a MultiIndex, the
+    order of the levels can be changed with `levels_order`.
+    The output for multiple `names_from` and/or `values_from`
+    can be controlled with `names_glue` and/or `names_sep`.
 
-    A dataframe is returned.
+    A dataframe pivoted from long to wide form is returned.
     """
+    # check dtype of `names_from` is string
+    names_from_all_strings = df.filter(names_from).agg(is_string_dtype).all()
 
-    if not names_sort:
+    if names_sort is True:
         # Categorical dtypes created only for `names_from`
         # since that is what will become the new column names
         dtypes = {
             column_name: CategoricalDtype(
-                categories=column.dropna().unique(), ordered=True
+                df[column_name].factorize(sort=False)[-1], ordered=True
             )
-            if column.hasnans
-            else CategoricalDtype(categories=column.unique(), ordered=True)
-            for column_name, column in df.filter(names_from).items()
+            for column_name in names_from
         }
-
         df = df.astype(dtypes)
 
-    if aggfunc is None:
-        df = df.pivot(  # noqa: PD010
-            index=index, columns=names_from, values=values_from
-        )
+    df = df.pivot(  # noqa: PD010
+        index=index, columns=names_from, values=values_from
+    )
 
-    else:
-        if index:
-            df = df.set_index(index + names_from)
-        else:
-            df = df.set_index(names_from, append=True)
-
-        if values_from:
-            df = df.groupby(
-                level=[*range(df.index.nlevels)],
-                observed=True,
-                dropna=False,
-                sort=False,
-            )[values_from].agg(aggfunc)
-        else:
-            df = df.groupby(
-                level=[*range(df.index.nlevels)],
-                observed=True,
-                dropna=False,
-                sort=False,
-            ).agg(aggfunc)
-
-        df = df.unstack(level=names_from)  # noqa: PD010
-
-    if fill_value is not None:
-        df = df.fillna(fill_value)
-
-    # no point keeping `values_from`
-    # if it's just one name;
-    # could do same for aggfunc; but not worth the extra check
-    if df.columns.get_level_values(0).unique().size == 1:
-        df = df.droplevel(0, axis="columns")
-
-    other_levels = None
-    names_from_levels = None
-    df_columns = df.columns
-    df_columns_names = df_columns.names
-    if names_from_position == "first":
-        if df_columns_names[: len(names_from)] != names_from:
-            other_levels = [
-                num
-                for num, name in enumerate(df_columns_names)
-                if name not in names_from
-            ]
-            names_from_levels = [
-                num
-                for num, name in enumerate(df_columns_names)
-                if name in names_from
-            ]
-            df = df.reorder_levels(
-                names_from_levels + other_levels, axis="columns"
-            )
+    if levels_order and (isinstance(df.columns, pd.MultiIndex)):
+        df = df.reorder_levels(order=levels_order, axis="columns")
 
-    if not flatten_levels:
+    # an empty df is likely because
+    # there are no `values_from`
+    if any((df.empty, flatten_levels is False)):
         return df
 
-    if df_columns.nlevels > 1:
-        df.columns = [names_sep.join(column_tuples) for column_tuples in df]
+    # ensure all entries in names_from are strings
+    if not names_from_all_strings:
+        if isinstance(df.columns, pd.MultiIndex):
+            new_columns = [tuple(map(str, ent)) for ent in df]
+            df.columns = pd.MultiIndex.from_tuples(new_columns)
+        else:
+            df.columns = df.columns.astype(str)
+
+    if names_sep is not None and (isinstance(df.columns, pd.MultiIndex)):
+        df.columns = df.columns.map(names_sep.join)
 
-    if names_prefix:
-        df = df.add_prefix(names_prefix)
+    if names_glue:
+        df.columns = df.columns.map(names_glue)
 
     # if columns are of category type
     # this returns columns to object dtype


Test patch:
diff --git a/tests/functions/test_pivot_wider.py b/tests/functions/test_pivot_wider.py
index 4c79fda..adf4795 100644
--- a/tests/functions/test_pivot_wider.py
+++ b/tests/functions/test_pivot_wider.py
@@ -22,172 +22,6 @@ def df_checks_output():
     )
 
 
-combinations = [
-    (
-        pd.DataFrame(
-            {
-                "geoid": [1, 1, 1, 1, 13, 13, 13, 13],
-                "name": [
-                    "Alabama",
-                    "Alabama",
-                    "Alabama",
-                    "Alabama",
-                    "Georgia",
-                    "Georgia",
-                    "Georgia",
-                    "Georgia",
-                ],
-                "variable": [
-                    "pop_renter",
-                    "pop_renter",
-                    "median_rent",
-                    "median_rent",
-                    "pop_renter",
-                    "pop_renter",
-                    "median_rent",
-                    "median_rent",
-                ],
-                "measure": [
-                    "estimate",
-                    "error",
-                    "estimate",
-                    "error",
-                    "estimate",
-                    "error",
-                    "estimate",
-                    "error",
-                ],
-                "value": [1434765, 16736, 747, 3, 3592422, 33385, 927, 3],
-            }
-        ),
-        pd.DataFrame(
-            {
-                "geoid": [1, 13],
-                "name": ["Alabama", "Georgia"],
-                "pop_renter_estimate": [1434765, 3592422],
-                "pop_renter_error": [16736, 33385],
-                "median_rent_estimate": [747, 927],
-                "median_rent_error": [3, 3],
-            }
-        ),
-        ["geoid", "name"],
-        ["variable", "measure"],
-        "value",
-        None,
-        True,
-        "first",
-    ),
-    (
-        pd.DataFrame(
-            {
-                "family": ["Kelly", "Kelly", "Quin", "Quin"],
-                "name": ["Mark", "Scott", "Tegan", "Sara"],
-                "n": [1, 2, 1, 2],
-            }
-        ),
-        pd.DataFrame(
-            {
-                "family": ["Kelly", "Quin"],
-                1: ["Mark", "Tegan"],
-                2: ["Scott", "Sara"],
-            }
-        ),
-        "family",
-        "n",
-        "name",
-        None,
-        True,
-        "first",
-    ),
-    (
-        pd.DataFrame(
-            {
-                "family": ["Kelly", "Kelly", "Quin", "Quin"],
-                "name": ["Mark", "Scott", "Tegan", "Sara"],
-                "n": [1, 2, 1, 2],
-            }
-        ),
-        pd.DataFrame(
-            {
-                "family": ["Kelly", "Quin"],
-                "name1": ["Mark", "Tegan"],
-                "name2": ["Scott", "Sara"],
-            }
-        ),
-        "family",
-        "n",
-        "name",
-        "name",
-        True,
-        "first",
-    ),
-    (
-        pd.DataFrame(
-            {
-                "geoid": [1, 1, 13, 13],
-                "name": ["Alabama", "Alabama", "Georgia", "Georgia"],
-                "variable": [
-                    "pop_renter",
-                    "median_rent",
-                    "pop_renter",
-                    "median_rent",
-                ],
-                "estimate": [1434765, 747, 3592422, 927],
-                "error": [16736, 3, 33385, 3],
-            }
-        ),
-        pd.DataFrame(
-            {
-                "geoid": [1, 13],
-                "name": ["Alabama", "Georgia"],
-                "pop_renter_estimate": [1434765, 3592422],
-                "median_rent_estimate": [747, 927],
-                "pop_renter_error": [16736, 33385],
-                "median_rent_error": [3, 3],
-            }
-        ),
-        ["geoid", "name"],
-        "variable",
-        ["estimate", "error"],
-        None,
-        False,
-        "first",
-    ),
-    (
-        pd.DataFrame(
-            {
-                "geoid": [1, 1, 13, 13],
-                "name": ["Alabama", "Alabama", "Georgia", "Georgia"],
-                "variable": [
-                    "pop_renter",
-                    "median_rent",
-                    "pop_renter",
-                    "median_rent",
-                ],
-                "estimate": [1434765, 747, 3592422, 927],
-                "error": [16736, 3, 33385, 3],
-            }
-        ),
-        pd.DataFrame(
-            {
-                "geoid": {0: 1, 1: 13},
-                "name": {0: "Alabama", 1: "Georgia"},
-                "estimate_pop_renter": {0: 1434765, 1: 3592422},
-                "estimate_median_rent": {0: 747, 1: 927},
-                "error_pop_renter": {0: 16736, 1: 33385},
-                "error_median_rent": {0: 3, 1: 3},
-            }
-        ),
-        ["geoid", "name"],
-        "variable",
-        ["estimate", "error"],
-        None,
-        False,
-        "last",
-    ),
-]
-
-
 @pytest.mark.xfail(reason="list-like is converted to list.")
 def test_type_index(df_checks_output):
     """Raise TypeError if wrong type is provided for the `index`."""
@@ -262,6 +96,7 @@ def test_flatten_levels_wrong_type(df_checks_output):
         )
 
 
+@pytest.mark.xfail(reason="parameter is deprecated.")
 def test_names_from_position_wrong_type(df_checks_output):
     """
     Raise TypeError if the wrong type
@@ -275,6 +110,7 @@ def test_names_from_position_wrong_type(df_checks_output):
         )
 
 
+@pytest.mark.xfail(reason="parameter is deprecated.")
 def test_names_from_position_wrong_value(df_checks_output):
     """
     Raise ValueError if `names_from_position`
@@ -288,6 +124,7 @@ def test_names_from_position_wrong_value(df_checks_output):
         )
 
 
+@pytest.mark.xfail(reason="parameter is deprecated.")
 def test_name_prefix_wrong_type(df_checks_output):
     """Raise TypeError if the wrong type is provided for `names_prefix`."""
     with pytest.raises(TypeError):
@@ -304,6 +141,23 @@ def test_name_sep_wrong_type(df_checks_output):
         )
 
 
+def test_name_glue_wrong_type(df_checks_output):
+    """Raise TypeError if the wrong type is provided for `names_glue`."""
+    with pytest.raises(TypeError):
+        df_checks_output.pivot_wider(
+            index="name", names_from=["estimate", "variable"], names_glue=1
+        )
+
+
+def test_levels_order_wrong_type(df_checks_output):
+    """Raise TypeError if the wrong type is provided for `levels_order`."""
+    with pytest.raises(TypeError):
+        df_checks_output.pivot_wider(
+            index="name", names_from=["estimate", "variable"], levels_order=1
+        )
+
+
+@pytest.mark.xfail(reason="parameter is deprecated.")
 def test_fill_value_wrong_type(df_checks_output):
     """Raise TypeError if the wrong type is provided for `fill_value`."""
     with pytest.raises(TypeError):
@@ -312,6 +166,7 @@ def test_fill_value_wrong_type(df_checks_output):
         )
 
 
+@pytest.mark.xfail(reason="parameter is deprecated.")
 def test_aggfunc_wrong_type(df_checks_output):
     """Raise TypeError if the wrong type is provided for `aggfunc`."""
     with pytest.raises(TypeError):
@@ -362,7 +217,7 @@ def test_pivot_long_wide_long():
 
     result = result.pivot_longer(
         index=["a", "b"],
-        names_to=("name", ".value"),
+        names_to=(".value", "name"),
         names_sep="_",
     )
     assert_frame_equal(result, df_in)
@@ -393,38 +248,6 @@ def test_pivot_wide_long_wide():
     assert_frame_equal(result, df)
 
 
-@pytest.mark.parametrize(
-    """
-    df_in,df_out,index,names_from,
-    values_from, names_prefix,
-    names_sort,names_from_position
-    """,
-    combinations,
-)
-def test_pivot_wider_various(
-    df_in,
-    df_out,
-    index,
-    names_from,
-    values_from,
-    names_prefix,
-    names_sort,
-    names_from_position,
-):
-    """
-    Test `pivot_wider` function with various combinations.
-    """
-    result = df_in.pivot_wider(
-        index=index,
-        names_from=names_from,
-        values_from=values_from,
-        names_prefix=names_prefix,
-        names_sort=names_sort,
-        names_from_position=names_from_position,
-    )
-    assert_frame_equal(result, df_out)
-
-
 def test_flatten_levels_false():
     """Test output if `flatten_levels` is False."""
 
@@ -442,8 +265,6 @@ def test_flatten_levels_false():
         names_from="bar",
         values_from=["baz", "zoo"],
         flatten_levels=False,
-        names_sort=True,
-        names_from_position="last",
     )
 
     expected_output = df_collapse.pivot(  # noqa: PD010
@@ -453,39 +274,8 @@ def test_flatten_levels_false():
     assert_frame_equal(
         result,
         expected_output,
-        check_dtype=False,
-    )
-
-
-def test_fill_values():
-    """Test output if `fill_value` is provided."""
-
-    df_fill_value = pd.DataFrame(
-        {
-            "lev1": [1, 1, 1, 2, 2, 2],
-            "lev2": [1, 1, 2, 1, 1, 2],
-            "lev3": [1, 2, 1, 2, 1, 2],
-            "lev4": [1, 2, 3, 4, 5, 6],
-            "values": [0, 1, 2, 3, 4, 5],
-        }
-    )
-
-    result = df_fill_value.pivot_wider(
-        index=["lev1", "lev2"],
-        names_from=["lev3"],
-        values_from="values",
-        flatten_levels=False,
-        fill_value=0,
-    )
-
-    expected_output = pd.DataFrame(
-        {1: [0, 2.0, 4, 0], 2: [1, 0, 3.0, 5]},
-        index=pd.MultiIndex.from_tuples(
-            [(1, 1), (1, 2), (2, 1), (2, 2)], names=["lev1", "lev2"]
-        ),
-        columns=pd.Int64Index([1, 2], dtype="int64", name="lev3"),
+        # check_dtype=False,
     )
-    assert_frame_equal(result, expected_output)
 
 
 def test_no_index():
@@ -505,7 +295,7 @@ def test_no_index():
         }
     )
 
-    result = df_in.pivot_wider(names_from="gender", names_prefix="contVar_")
+    result = df_in.pivot_wider(names_from="gender")
 
     assert_frame_equal(result, expected_output)
 
@@ -522,14 +312,12 @@ def test_no_index_names_sort_True():
 
     expected_output = pd.DataFrame(
         {
-            "contVar_Female": [24523.0, 23421.0, np.nan],
             "contVar_Male": [22379.0, 23831.0, 29234.0],
+            "contVar_Female": [24523.0, 23421.0, np.nan],
         }
     )
 
-    result = df_in.pivot_wider(
-        names_from="gender", names_sort=True, names_prefix="contVar_"
-    )
+    result = df_in.pivot_wider(names_from="gender", names_sort=True)
 
     assert_frame_equal(result, expected_output)
 
@@ -558,239 +346,95 @@ def test_index_names_sort_True():
     assert_frame_equal(result, expected_output)
 
 
-@pytest.fixture
-def df_aggfunc():
-    return pd.DataFrame(
-        [
-            {"V4": "A", "variable": "V1", "value": 0},
-            {"V4": "A", "variable": "V1", "value": 0},
-            {"V4": "A", "variable": "V1", "value": 0},
-            {"V4": "B", "variable": "V1", "value": 4},
-            {"V4": "B", "variable": "V1", "value": 4},
-            {"V4": "B", "variable": "V1", "value": 1},
-            {"V4": "C", "variable": "V1", "value": 4},
-            {"V4": "C", "variable": "V1", "value": 1},
-            {"V4": "C", "variable": "V1", "value": 1},
-            {"V4": "A", "variable": "V2", "value": 3},
-            {"V4": "A", "variable": "V2", "value": 4},
-            {"V4": "A", "variable": "V2", "value": 7},
-            {"V4": "B", "variable": "V2", "value": 0},
-            {"V4": "B", "variable": "V2", "value": 8},
-            {"V4": "B", "variable": "V2", "value": 5},
-            {"V4": "C", "variable": "V2", "value": 5},
-            {"V4": "C", "variable": "V2", "value": 0},
-            {"V4": "C", "variable": "V2", "value": 9},
-        ]
-    )
-
-
-@pytest.fixture
-def df_aggfunc_multiple_names_from():
-    return pd.DataFrame(
+def test_names_glue():
+    """Test output with `names_glue`"""
+    df_in = pd.DataFrame(
         {
-            "A": [
-                "foo",
-                "foo",
-                "foo",
-                "foo",
-                "foo",
-                "bar",
-                "bar",
-                "bar",
-                "bar",
-            ],
-            "B": [
-                "one",
-                "one",
-                "one",
-                "two",
-                "two",
-                "one",
-                "one",
-                "two",
-                "two",
-            ],
-            "C": [
-                "small",
-                "large",
-                "large",
-                "small",
-                "small",
-                "large",
-                "small",
-                "small",
-                "large",
-            ],
-            "D": [1, 2, 2, 3, 3, 4, 5, 6, 7],
-            "E": [2, 4, 5, 5, 6, 6, 8, 9, 9],
+            "family": ["Kelly", "Kelly", "Quin", "Quin"],
+            "name": ["Mark", "Scott", "Tegan", "Sara"],
+            "n": [1, 2, 1, 2],
         }
     )
-
-
-def test_aggfunc(df_aggfunc):
-    """Test output when `aggfunc` is provided."""
-    expected = pd.DataFrame(
-        {"V4": ["A", "B", "C"], "V1": [3, 3, 3], "V2": [3, 3, 3]}
-    )
-    result = df_aggfunc.pivot_wider(
-        index="V4", names_from="variable", aggfunc="size", flatten_levels=True
-    )
-    assert_frame_equal(result, expected)
-
-
-def test_aggfunc_names_sort(df_aggfunc):
-    """
-    Test output when `aggfunc` is provided
-    and `names_sort` is True.
-    """
-    expected = pd.DataFrame(
-        {"V4": ["A", "B", "C"], "V1": [3, 3, 3], "V2": [3, 3, 3]}
-    )
-    result = df_aggfunc.pivot_wider(
-        index="V4",
-        names_from="variable",
-        aggfunc="size",
-        names_sort=True,
-        flatten_levels=True,
+    df_out = pd.DataFrame(
+        {
+            "family": ["Kelly", "Quin"],
+            "name1": ["Mark", "Tegan"],
+            "name2": ["Scott", "Sara"],
+        }
     )
-    assert_frame_equal(result, expected)
 
-
-def test_aggfunc_list(df_aggfunc):
-    """Test output when `aggfunc` is a list."""
-    expected = pd.DataFrame(
-        {"V4": ["A", "B", "C"], "V1_sum": [0, 9, 6], "V2_sum": [14, 13, 14]}
-    )
-    result = df_aggfunc.pivot_wider(
-        index="V4", names_from="variable", aggfunc=["sum"], flatten_levels=True
+    result = df_in.pivot_wider(
+        index="family",
+        names_from="n",
+        values_from="name",
+        names_glue=lambda col: f"name{col}",
     )
-    assert_frame_equal(result, expected)
+    assert_frame_equal(result, df_out)
 
 
-def test_aggfunc_multiple_names_from(df_aggfunc_multiple_names_from):
-    """Test output when ``names_from`` is more than one."""
-    expected = pd.DataFrame(
+def test_change_level_order():
+    """Test output with `levels_order`"""
+    df_in = pd.DataFrame(
         {
-            "A": ["bar", "bar", "foo", "foo"],
-            "C": ["large", "small", "large", "small"],
-            "one_D_mean": [4.0, 5.0, 2.0, 1.0],
-            "two_D_mean": [7.0, 6.0, np.nan, 3.0],
-            "one_E_mean": [6.0, 8.0, 4.5, 2.0],
-            "two_E_mean": [9.0, 9.0, np.nan, 5.5],
+            "geoid": [1, 1, 13, 13],
+            "name": ["Alabama", "Alabama", "Georgia", "Georgia"],
+            "variable": [
+                "pop_renter",
+                "median_rent",
+                "pop_renter",
+                "median_rent",
+            ],
+            "estimate": [1434765, 747, 3592422, 927],
+            "error": [16736, 3, 33385, 3],
         }
     )
-    result = df_aggfunc_multiple_names_from.pivot_wider(
-        index=["A", "C"],
-        names_from=["B"],
-        values_from=["D", "E"],
-        aggfunc=["mean"],
-        flatten_levels=True,
-    )
-    assert_frame_equal(result, expected)
-
-
-def test_df_multiple_aggfuncs():
-    """Test output when ``aggfunc`` is more than one."""
-
-    df_frame = pd.DataFrame(
-        [
-            {"A": "foo", "B": "one", "C": "small", "D": 1, "E": 2},
-            {"A": "foo", "B": "one", "C": "large", "D": 2, "E": 4},
-            {"A": "foo", "B": "one", "C": "large", "D": 2, "E": 5},
-            {"A": "foo", "B": "one", "C": "small", "D": 3, "E": 5},
-            {"A": "foo", "B": "one", "C": "small", "D": 3, "E": 6},
-            {"A": "bar", "B": "one", "C": "large", "D": 4, "E": 6},
-            {"A": "bar", "B": "one", "C": "small", "D": 5, "E": 8},
-            {"A": "bar", "B": "one", "C": "small", "D": 6, "E": 9},
-            {"A": "bar", "B": "one", "C": "large", "D": 7, "E": 9},
-        ]
-    )
-
-    expected = pd.DataFrame(
+    df_out = pd.DataFrame(
         {
-            "A": ["bar", "bar", "foo", "foo"],
-            "C": ["large", "small", "large", "small"],
-            "one_D_mean": [5.5, 5.5, 2.0, 2.3333333333333335],
-            "one_D_sum": [11, 11, 4, 7],
-            "one_E_mean": [7.5, 8.5, 4.5, 4.333333333333333],
-            "one_E_sum": [15, 17, 9, 13],
+            "geoid": [1, 13],
+            "name": ["Alabama", "Georgia"],
+            "pop_renter_estimate": [1434765, 3592422],
+            "median_rent_estimate": [747, 927],
+            "pop_renter_error": [16736, 33385],
+            "median_rent_error": [3, 3],
         }
     )
 
-    result = df_frame.pivot_wider(
-        index=["A", "C"],
-        names_from=["B"],
-        values_from=["D", "E"],
-        aggfunc=["mean", "sum"],
-        flatten_levels=True,
+    result = df_in.pivot_wider(
+        index=["geoid", "name"],
+        names_from="variable",
+        values_from=["estimate", "error"],
+        levels_order=["variable", None],
+        names_sort=True,
     )
+    assert_frame_equal(result, df_out)
 
-    assert_frame_equal(result, expected)
-
-
-def test_df_aggfunc_no_index():
-    """Test output when for `aggfunc` if no `index is provided."""
 
-    df = pd.DataFrame(
+def test_int_columns():
+    """Test output when names_from is not a string dtype."""
+    df_in = pd.DataFrame(
         [
-            {"A": "foo", "B": "one", "C": "small", "D": 1, "E": 2},
-            {"A": "foo", "B": "one", "C": "large", "D": 2, "E": 4},
-            {"A": "foo", "B": "one", "C": "large", "D": 2, "E": 5},
-            {"A": "foo", "B": "one", "C": "small", "D": 3, "E": 5},
-            {"A": "foo", "B": "one", "C": "small", "D": 3, "E": 6},
-            {"A": "bar", "B": "one", "C": "large", "D": 4, "E": 6},
-            {"A": "bar", "B": "one", "C": "small", "D": 5, "E": 8},
-            {"A": "bar", "B": "one", "C": "small", "D": 6, "E": 9},
-            {"A": "bar", "B": "one", "C": "large", "D": 7, "E": 9},
+            {"name": 1, "n": 10, "pct": 0.1},
+            {"name": 2, "n": 20, "pct": 0.2},
+            {"name": 3, "n": 30, "pct": 0.3},
         ]
     )
 
-    df = df.set_index(["A", "C"])
-
-    expected = pd.DataFrame(
+    df_out = pd.DataFrame(
         [
             {
-                "one_D_mean": 5.5,
-                "one_D_sum": 11,
-                "one_E_mean": 7.5,
-                "one_E_sum": 15,
-            },
-            {
-                "one_D_mean": 5.5,
-                "one_D_sum": 11,
-                "one_E_mean": 8.5,
-                "one_E_sum": 17,
-            },
-            {
-                "one_D_mean": 2.0,
-                "one_D_sum": 4,
-                "one_E_mean": 4.5,
-                "one_E_sum": 9,
-            },
-            {
-                "one_D_mean": 2.3333333333333335,
-                "one_D_sum": 7,
-                "one_E_mean": 4.333333333333333,
-                "one_E_sum": 13,
-            },
-        ],
-        index=pd.MultiIndex.from_tuples(
-            [
-                ("bar", "large"),
-                ("bar", "small"),
-                ("foo", "large"),
-                ("foo", "small"),
-            ],
-            names=["A", "C"],
-        ),
+                "num": 0,
+                "n_1": 10.0,
+                "n_2": 20.0,
+                "n_3": 30.0,
+                "pct_1": 0.1,
+                "pct_2": 0.2,
+                "pct_3": 0.3,
+            }
+        ]
     )
 
-    result = df.pivot_wider(
-        index=None,
-        names_from=["B"],
-        values_from=["D", "E"],
-        aggfunc=["mean", "sum"],
-        flatten_levels=True,
+    result = df_in.assign(num=0).pivot_wider(
+        index="num", names_from="name", values_from=["n", "pct"], names_sep="_"
     )
 
-    assert_frame_equal(result, expected)
+    assert_frame_equal(result, df_out)