# Path: econtools/util/gentools.py
# def force_iterable(x) -> Iterable:
#     """If not iterable, wrap in tuple"""
#     if hasattr(x, '__iter__') and type(x) is not str:
#         return x
#     else:
#         return (x,)


from typing import Optional, Union, Iterable, cast
from econtools.util.gentools import force_iterable
import pandas as pd
import numpy as np
        ``DataFrame`` with group ID merged on as a new column.
    """
    if not cols:
        cols = df.columns.tolist()

    if name in df.columns:
        raise ValueError("ID name '{}' is a column name.".format(name))

    unique_df = df[cols].drop_duplicates().reset_index(drop=True)
    unique_df.index.name = name
    unique_df = unique_df.reset_index()

    if merge:
        unique_df = stata_merge(df, unique_df, on=cols, how='left',
                                assertval=3)
        new_i, new_j = unique_df.shape
        old_i, old_j = df.shape
        assert new_i == old_i and new_j == old_j + 1
        unique_df.index = df.index

    return unique_df


def winsorize(df: pd.DataFrame, by: Union[str, Iterable[str]],
              p: Iterable[Union[tuple, float]]=(.01, .99)):
    """Drop variables in `by' outside quantiles `p`."""
    # TODO: Some kind of warning/error if too fine of quantiles are
    #       requested for the number of rows, e.g. .99 with 5 rows.
    df = df.copy()


# Path: econtools/util/gentools.py
# def force_iterable(x) -> Iterable:
#     """If not iterable, wrap in tuple"""
#     if hasattr(x, '__iter__') and type(x) is not str:
#         return x
#     else:
#         return (x,)


# Path: econtools/util/frametools.py
from typing import Optional, Union, Iterable, cast
from econtools.util.gentools import force_iterable
import pandas as pd
import numpy as np

        ``DataFrame`` with group ID merged on as a new column.
    """
    if not cols:
        cols = df.columns.tolist()

    if name in df.columns:
        raise ValueError("ID name '{}' is a column name.".format(name))

    unique_df = df[cols].drop_duplicates().reset_index(drop=True)
    unique_df.index.name = name
    unique_df = unique_df.reset_index()

    if merge:
        unique_df = stata_merge(df, unique_df, on=cols, how='left',
                                assertval=3)
        new_i, new_j = unique_df.shape
        old_i, old_j = df.shape
        assert new_i == old_i and new_j == old_j + 1
        unique_df.index = df.index

    return unique_df


def winsorize(df: pd.DataFrame, by: Union[str, Iterable[str]],
              p: Iterable[Union[tuple, float]]=(.01, .99)):
    """Drop variables in `by' outside quantiles `p`."""
    # TODO: Some kind of warning/error if too fine of quantiles are
    #       requested for the number of rows, e.g. .99 with 5 rows.
    df = df.copy()


    by = cast(Iterable[str], force_iterable(by))