# Path: econtools/util/gentools.py
# def force_list(x: Any) -> list:
#     """If type not `list`, pass to `force_interable`, then convert to list."""
#     if isinstance(x, list):
#         return x
#     else:
#         return list(force_iterable(x))
# 
# def force_df(s: Union[pd.DataFrame, pd.Series],
#              name: Optional[str]=None) -> pd.DataFrame:
#     """
#     Forces a Series to a DataFrame. DataFrames are returned unaffected. Other
#     objects raise `ValueError`.
#     """
#     # Check if DF or Series
#     if isinstance(s, pd.core.frame.DataFrame):
#         return s
#     elif not isinstance(s, pd.core.series.Series):
#         raise ValueError("`s` is a {}".format(type(s)))
#     else:
#         return s.to_frame(name)


from typing import Optional, List
from econtools.util.gentools import force_list, force_df
import pandas as pd
import numpy as np
import numpy.linalg as la
    varlist = []
    for var in args:
        if var is not None:
            varlist += force_list(var)
    sample = df[varlist].notnull().all(axis=1)
    return sample


def set_sample(df, sample, names):
    return tuple(_set_samp_core(df, sample, names))

def _set_samp_core(df, sample, names):
    for name in names:
        if name is None:
            yield None
        else:
            yield df.loc[sample, name].copy().reset_index(drop=True)


def demeaner(A, *args):
    return tuple(_demean_guts(A.squeeze(), args))

def _demean_guts(A, args):
    for df in args:
        # Ignore empty `df` (e.g. empty list of exogenous included regressors)
        if df is None or df.empty:
            yield df
        else:
            group_name = A.name
            mean = df.groupby(A).mean()

# Path: econtools/util/gentools.py
# def force_list(x: Any) -> list:
#     """If type not `list`, pass to `force_interable`, then convert to list."""
#     if isinstance(x, list):
#         return x
#     else:
#         return list(force_iterable(x))
# 
# def force_df(s: Union[pd.DataFrame, pd.Series],
#              name: Optional[str]=None) -> pd.DataFrame:
#     """
#     Forces a Series to a DataFrame. DataFrames are returned unaffected. Other
#     objects raise `ValueError`.
#     """
#     # Check if DF or Series
#     if isinstance(s, pd.core.frame.DataFrame):
#         return s
#     elif not isinstance(s, pd.core.series.Series):
#         raise ValueError("`s` is a {}".format(type(s)))
#     else:
#         return s.to_frame(name)


# Path: econtools/metrics/regutil.py
from typing import Optional, List
from econtools.util.gentools import force_list, force_df
import pandas as pd
import numpy as np
import numpy.linalg as la

    varlist = []
    for var in args:
        if var is not None:
            varlist += force_list(var)
    sample = df[varlist].notnull().all(axis=1)
    return sample


def set_sample(df, sample, names):
    return tuple(_set_samp_core(df, sample, names))

def _set_samp_core(df, sample, names):
    for name in names:
        if name is None:
            yield None
        else:
            yield df.loc[sample, name].copy().reset_index(drop=True)


def demeaner(A, *args):
    return tuple(_demean_guts(A.squeeze(), args))

def _demean_guts(A, args):
    for df in args:
        # Ignore empty `df` (e.g. empty list of exogenous included regressors)
        if df is None or df.empty:
            yield df
        else:
            group_name = A.name
            mean = df.groupby(A).mean()

            large_mean = force_df(A).join(mean, on=group_name).drop(group_name,