# AUTOGENERATED! DO NOT EDIT! File to edit: 00_core.ipynb (unless otherwise specified).

__all__ = ['makeMixedDataFrame', 'getCrashes', 'is_numeric', 'drop_singletons', 'discretize']

# Cell

import pandas as pd
from pandas.api.types import is_numeric_dtype as isnum
#from matplotlib.pyplot import rcParams

# Cell

def makeMixedDataFrame():
    '''Return a constant mixed-type dataframe [float, float, str, datetime]'''
    return pd.DataFrame(
        {'A': {0: 0.0, 1: 1.0, 2: 2.0, 3: 3.0, 4: 4.0},
         'B': {0: 0.0, 1: 1.0, 2: 0.0, 3: 1.0, 4: 0.0},
         'C': {0: 'foo1', 1: 'foo2', 2: 'foo3', 3: 'foo4', 4: 'foo5'},
         'D': {0: pd.Timestamp('2009-01-01 00:00:00'),
          1: pd.Timestamp('2009-01-02 00:00:00'),
          2: pd.Timestamp('2009-01-05 00:00:00'),
          3: pd.Timestamp('2009-01-06 00:00:00'),
          4: pd.Timestamp('2009-01-07 00:00:00')}}
    )

def getCrashes(dataset='car_crashes'):
    try:
        import seaborn as sns
        return sns.load_dataset(dataset)
    except ModuleNotFoundError:
        return pd.read_csv(f'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/{dataset}.csv')


# Cell

def is_numeric(col:str):
    """Returns True iff already numeric, or can be coerced.
    Usage: df.apply(is_numeric)
    Usage: is_numeric(df['colname'])

    Returns Boolean series.

    From:
    https://stackoverflow.com/questions/54426845/how-to-check-if-a-pandas-dataframe-contains-only-numeric-column-wise

    """
    return isnum(col) or pd.to_numeric(col, errors='coerce').notnull().all()

# Cell

def drop_singletons(df, verbose=1) -> None:
    """Drop columns with < 2 unique values. Inplace."""
    dropcols = []
    for col in df:
        if len(df[col].unique()) < 2:
            df.drop(columns=col, inplace=True)
            dropcols.append(col)
    if verbose:
        print(f"  DROPPED {dropcols} because < 2 vals each.")

# Cell

def discretize(df, nbins=10, cut=pd.qcut,
               verbose=2, drop_useless=True):
    """Discretize columns in {df} to have at most {nbins} categories.
      * Categorical columns: take the Top n-1 plus "Other"
      * Continuous columns: cut into {nbins} using {cut}.

    Returns a new discretized dataframe with the same column names.
    Promotes discrete columns to categories.

    Parameters
    -----------
    df: Dataframe to discretize
    nbins: Max number of bins to use. May return fewer.
    cut: Cutting method. Default `pd.qcut`. Consider pd.cut, or write your own.
    verbose: 0: silent, 1: colnames, 2: (Default) top N for each column
    drop_useless: Removes columns that have < 2 unique values.

    Replaces numerical NA values with 'NA'.

    """
    out = pd.DataFrame(index = df.index)
    isnum = df.apply(is_numeric)
    for col in df:
        if verbose > 0:
            print(col, end=':\n\t')
        if isnum[col]:
            out[col] = cut(df[col], nbins, duplicates='drop')
        else:
            topN = df[col].value_counts(dropna=False).head(nbins).keys()
            # Promote to Category, add Other, Drop all but TopN
            out[col] = df[col].astype('category')
            try:
                out[col] = out[col].cat.add_categories(['Other'])
            except ValueError:
                pass  # Already had 'Other'
            out[col] = out[col].where(out[col].isin(topN), 'Other')
            out[col] = out[col].cat.remove_unused_categories()
        if verbose > 1:
            print('\n\t'.join(out[col].value_counts(dropna=False, sort=False)\
                              .to_string().split('\n')))
        elif verbose > 0:
            print()
    if drop_useless is True:
        drop_singletons(out)
    return out
# AUTOGENERATED! DO NOT EDIT! File to edit: 00_core.ipynb (unless otherwise specified).

__all__ = ['makeMixedDataFrame', 'getCrashes', 'is_numeric', 'drop_singletons', 'discretize']

# Cell

import pandas as pd
from pandas.api.types import is_numeric_dtype as isnum
#from matplotlib.pyplot import rcParams

# Cell

def makeMixedDataFrame():
    '''Return a constant mixed-type dataframe [float, float, str, datetime]'''
    return pd.DataFrame(
        {'A': {0: 0.0, 1: 1.0, 2: 2.0, 3: 3.0, 4: 4.0},
         'B': {0: 0.0, 1: 1.0, 2: 0.0, 3: 1.0, 4: 0.0},
         'C': {0: 'foo1', 1: 'foo2', 2: 'foo3', 3: 'foo4', 4: 'foo5'},
         'D': {0: pd.Timestamp('2009-01-01 00:00:00'),
          1: pd.Timestamp('2009-01-02 00:00:00'),
          2: pd.Timestamp('2009-01-05 00:00:00'),
          3: pd.Timestamp('2009-01-06 00:00:00'),
          4: pd.Timestamp('2009-01-07 00:00:00')}}
    )

def getCrashes(dataset='car_crashes'):
    try:
        import seaborn as sns
        return sns.load_dataset(dataset)
    except ModuleNotFoundError:
        return pd.read_csv(f'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/{dataset}.csv')


# Cell

def is_numeric(col:str):
    """Returns True iff already numeric, or can be coerced.
    Usage: df.apply(is_numeric)
    Usage: is_numeric(df['colname'])

    Returns Boolean series.

    From:
    https://stackoverflow.com/questions/54426845/how-to-check-if-a-pandas-dataframe-contains-only-numeric-column-wise

    """
    return isnum(col) or pd.to_numeric(col, errors='coerce').notnull().all()

# Cell

def drop_singletons(df, verbose=1) -> None:
    """Drop columns with < 2 unique values. Inplace."""
    dropcols = []
    for col in df:
        if len(df[col].unique()) < 2:
            df.drop(columns=col, inplace=True)
            dropcols.append(col)
    if verbose:
        print(f"  DROPPED {dropcols} because < 2 vals each.")

# Cell

def discretize(df, nbins=10, cut=pd.qcut,
               verbose=2, drop_useless=True):
    """Discretize columns in {df} to have at most {nbins} categories.
      * Categorical columns: take the Top n-1 plus "Other"
      * Continuous columns: cut into {nbins} using {cut}.

    Returns a new discretized dataframe with the same column names.
    Promotes discrete columns to categories.

    Parameters
    -----------
    df: Dataframe to discretize
    nbins: Max number of bins to use. May return fewer.
    cut: Cutting method. Default `pd.qcut`. Consider pd.cut, or write your own.
    verbose: 0: silent, 1: colnames, 2: (Default) top N for each column
    drop_useless: Removes columns that have < 2 unique values.

    Replaces numerical NA values with 'NA'.

    """
    out = pd.DataFrame(index = df.index)
    isnum = df.apply(is_numeric)
    for col in df:
        if verbose > 0:
            print(col, end=':\n\t')
        if isnum[col]:
            out[col] = cut(df[col], nbins, duplicates='drop')
        else:
            topN = df[col].value_counts(dropna=False).head(nbins).keys()
            # Promote to Category, add Other, Drop all but TopN
            out[col] = df[col].astype('category')
            try:
                out[col] = out[col].cat.add_categories(['Other'])
            except ValueError:
                pass  # Already had 'Other'
            out[col] = out[col].where(out[col].isin(topN), 'Other')
            out[col] = out[col].cat.remove_unused_categories()
        if verbose > 1:
            print('\n\t'.join(out[col].value_counts(dropna=False, sort=False)\
                              .to_string().split('\n')))
        elif verbose > 0:
            print()
    if drop_useless is True:
        drop_singletons(out)
    return out
# AUTOGENERATED! DO NOT EDIT! File to edit: 00_core.ipynb (unless otherwise specified). # Cell #from matplotlib.pyplot import rcParams # Cell Return a constant mixed-type dataframe [float, float, str, datetime] # Cell Returns True iff already numeric, or can be coerced.     Usage: df.apply(is_numeric)     Usage: is_numeric(df['colname'])      Returns Boolean series.      From:     https://stackoverflow.com/questions/54426845/how-to-check-if-a-pandas-dataframe-contains-only-numeric-column-wise # Cell Drop columns with < 2 unique values. Inplace. # Cell Discretize columns in {df} to have at most {nbins} categories.       * Categorical columns: take the Top n-1 plus "Other"       * Continuous columns: cut into {nbins} using {cut}.      Returns a new discretized dataframe with the same column names.     Promotes discrete columns to categories.      Parameters     -----------     df: Dataframe to discretize     nbins: Max number of bins to use. May return fewer.     cut: Cutting method. Default `pd.qcut`. Consider pd.cut, or write your own.     verbose: 0: silent, 1: colnames, 2: (Default) top N for each column     drop_useless: Removes columns that have < 2 unique values.      Replaces numerical NA values with 'NA'. # Promote to Category, add Other, Drop all but TopN # Already had 'Other'