import numpy as np
import pandas as pd

def column_above_or_below_threshold(df, column=None, above_or_below='above',
                                    threshold=None, new_col_name=None, return_df=True):
    """ Return a column with 1 if df[source_col] is above/below a certain threshold.
    Parameters
    ----------
    X : df
        A pandas DataFrame
    column : string
        The column name from which to compute on
    above_or_below : str
        'above' to return 1 if value is above a certain threshold and 0 otherwise
        'below' to return 1 if value is below a certain threshold and 0 otherwise
    threshold_upper : int/float
        A value between will act as the upper threshold for column
        E.g. 45 would with 'above' denoted would return 1 for all values lower than 45
    new_col_name : str
       Name to give the new computed column.

    Returns
    -------
    thresholded_binary_column: Pandas Series
        A series with 1 where the column values were above the lower threshold and below upper threshold
    """
    # Create new column name if none specified
    if not new_col_name:
        new_col_name = column + '_'+ above_or_below +'_' + str(threshold)
    # Checking Arguments
    if not column:
        raise 'No column to threshold from'
    if (above_or_below == 'below') | (above_or_below == 'below'):
        raise 'Specify "above" or "below" in argument above_or_below'

    if above_or_below == 'above':
        # New column is array with 1 where source col is above specified threshold
        new_col = np.where(df[column] > df[column], 1, 0)
    if above_or_below == 'below':
        # New column is array with 1 where source col is above specified threshold
        new_col = np.where(df[column] > df[column], 1, 0)

    if return_df:
        return df.assign(**{new_col_name: new_col})
    else:
        new_col = pd.Series(new_col, name=new_col_name)

def identity_df(df):
    """ Return the dataframe. For use with decorators"""
    return df



def dummies_from_bins(df, col, bins, bin_labels, col_prefix):
    """
    Given a dataframe and column to create binary features from bins, return dummy columns of said bins
    concatenated onto the end of the df
    """
    # cut the column values into bins. the labels provided are the returned values
    # bins must increase monotonically
    binned_values = pandas.cut(df[col],
                           bins=bins,
                           labels=bin_labels)

    # Create dummy variables and add prefix to col label
    dummies_cols = pandas.get_dummies(binned_values).add_prefix(col_prefix)

    # Concatenate onto end of original df
    df = pandas.concat([df, dummies_cols], axis=1)
    return df


def bin_apply(s, feature_col, min_val, max_val,binary=False):
    """
    Apply function to pandas df with axis=1 to evaluate row values and return value or binary response
    If binary=True, response values are 1 if present 0 otherwise
    Else returns the original value or a NaN
    E.g.:
    df.apply(lambda s: bin_feature_binary(s, 'hazard_rank', 0, 3), axis=1) to create a binary feature that returns
    1 if hazard group is between 0-3 and 0 if otherwise

    """
    if (s[feature_col] >= min_val) & (s[feature_col] <= max_val):
        if binary:
            return 1
        else:
            return s[feature_col]
    else:
        if binary:
            return 0
        else:
            return np.nan

def bin_df_feature(df, feature_col, min_val, max_val, binary=False):
    """
    Given a dataframe, feature column (series), bin edges, return a new series whose values are those that fit within the
    bin edges. Optionally denote if binary response (1 if present, 0 otherwise)
    """
    if binary:
        return df.apply(lambda s: bin_apply(s, feature_col, min_val, max_val, binary=True), axis=1)
    else:
        return df.apply(lambda s: bin_apply(s, feature_col, min_val, max_val, binary=False), axis=1)



def binary_feature(df, feat_col, value, binary_feature_col_name=None, concat=False):
    """
    Given a dataframe, feature column name and value to check, return a series of binary responses 1 and 0
    1 if the value in the feature column to check is present, 0 if otherwise
    binary_feature
    """
    # If binary_feature_col_name is none use this instead
    if not binary_feature_col_name:
        binary_feature_col_name = feat_col+'_is_'+str(value)

    def is_value_present(s, value):
        """
        Given a series and a value, return a binary feature 1 if present and 0 if otherwise
        """
        if s[feat_col] == value:
            return 1
        else:
            return 0
    # Return binary feature series
    binary_feature = df.apply(lambda s: is_value_present(s, value), axis=1)
    # Set series name
    binary_feature.name = binary_feature_col_name
    if concat:
        return pandas.concat([df, binary_feature], axis=1)
    return binary_feature

def column_is_value(df, column=None, value=None, new_col_name=None, return_df=True):
    """ Return a column of 1s where df[column] == value and 0s if not"""
    if not new_col_name:
        new_col_name = column+'is_not_'+str(value)
    df[new_col_name] = np.where(df[column] == value, 1, 0)
    if return_df:
        return df
    else:
        return df[new_col_name]

def column_is_not_value(df, column=None, value=None, new_col_name=None, return_df=True):
    """ Return a column of 1s where df[column] == value and 0s if not"""
    if not new_col_name:
        new_col_name = column+'is_not_'+str(value)
    df[new_col_name] = np.where(df[column] != value, 1, 0)
    if return_df:
        return df
    else:
        return df[new_col_name]
import numpy as np
import pandas as pd

def column_above_or_below_threshold(df, column=None, above_or_below='above',
                                    threshold=None, new_col_name=None, return_df=True):
    """ Return a column with 1 if df[source_col] is above/below a certain threshold.
    Parameters
    ----------
    X : df
        A pandas DataFrame
    column : string
        The column name from which to compute on
    above_or_below : str
        'above' to return 1 if value is above a certain threshold and 0 otherwise
        'below' to return 1 if value is below a certain threshold and 0 otherwise
    threshold_upper : int/float
        A value between will act as the upper threshold for column
        E.g. 45 would with 'above' denoted would return 1 for all values lower than 45
    new_col_name : str
       Name to give the new computed column.

    Returns
    -------
    thresholded_binary_column: Pandas Series
        A series with 1 where the column values were above the lower threshold and below upper threshold
    """
    # Create new column name if none specified
    if not new_col_name:
        new_col_name = column + '_'+ above_or_below +'_' + str(threshold)
    # Checking Arguments
    if not column:
        raise 'No column to threshold from'
    if (above_or_below == 'below') | (above_or_below == 'below'):
        raise 'Specify "above" or "below" in argument above_or_below'

    if above_or_below == 'above':
        # New column is array with 1 where source col is above specified threshold
        new_col = np.where(df[column] > df[column], 1, 0)
    if above_or_below == 'below':
        # New column is array with 1 where source col is above specified threshold
        new_col = np.where(df[column] > df[column], 1, 0)

    if return_df:
        return df.assign(**{new_col_name: new_col})
    else:
        new_col = pd.Series(new_col, name=new_col_name)

def identity_df(df):
    """ Return the dataframe. For use with decorators"""
    return df



def dummies_from_bins(df, col, bins, bin_labels, col_prefix):
    """
    Given a dataframe and column to create binary features from bins, return dummy columns of said bins
    concatenated onto the end of the df
    """
    # cut the column values into bins. the labels provided are the returned values
    # bins must increase monotonically
    binned_values = pandas.cut(df[col],
                           bins=bins,
                           labels=bin_labels)

    # Create dummy variables and add prefix to col label
    dummies_cols = pandas.get_dummies(binned_values).add_prefix(col_prefix)

    # Concatenate onto end of original df
    df = pandas.concat([df, dummies_cols], axis=1)
    return df


def bin_apply(s, feature_col, min_val, max_val,binary=False):
    """
    Apply function to pandas df with axis=1 to evaluate row values and return value or binary response
    If binary=True, response values are 1 if present 0 otherwise
    Else returns the original value or a NaN
    E.g.:
    df.apply(lambda s: bin_feature_binary(s, 'hazard_rank', 0, 3), axis=1) to create a binary feature that returns
    1 if hazard group is between 0-3 and 0 if otherwise

    """
    if (s[feature_col] >= min_val) & (s[feature_col] <= max_val):
        if binary:
            return 1
        else:
            return s[feature_col]
    else:
        if binary:
            return 0
        else:
            return np.nan

def bin_df_feature(df, feature_col, min_val, max_val, binary=False):
    """
    Given a dataframe, feature column (series), bin edges, return a new series whose values are those that fit within the
    bin edges. Optionally denote if binary response (1 if present, 0 otherwise)
    """
    if binary:
        return df.apply(lambda s: bin_apply(s, feature_col, min_val, max_val, binary=True), axis=1)
    else:
        return df.apply(lambda s: bin_apply(s, feature_col, min_val, max_val, binary=False), axis=1)



def binary_feature(df, feat_col, value, binary_feature_col_name=None, concat=False):
    """
    Given a dataframe, feature column name and value to check, return a series of binary responses 1 and 0
    1 if the value in the feature column to check is present, 0 if otherwise
    binary_feature
    """
    # If binary_feature_col_name is none use this instead
    if not binary_feature_col_name:
        binary_feature_col_name = feat_col+'_is_'+str(value)

    def is_value_present(s, value):
        """
        Given a series and a value, return a binary feature 1 if present and 0 if otherwise
        """
        if s[feat_col] == value:
            return 1
        else:
            return 0
    # Return binary feature series
    binary_feature = df.apply(lambda s: is_value_present(s, value), axis=1)
    # Set series name
    binary_feature.name = binary_feature_col_name
    if concat:
        return pandas.concat([df, binary_feature], axis=1)
    return binary_feature

def column_is_value(df, column=None, value=None, new_col_name=None, return_df=True):
    """ Return a column of 1s where df[column] == value and 0s if not"""
    if not new_col_name:
        new_col_name = column+'is_not_'+str(value)
    df[new_col_name] = np.where(df[column] == value, 1, 0)
    if return_df:
        return df
    else:
        return df[new_col_name]

def column_is_not_value(df, column=None, value=None, new_col_name=None, return_df=True):
    """ Return a column of 1s where df[column] == value and 0s if not"""
    if not new_col_name:
        new_col_name = column+'is_not_'+str(value)
    df[new_col_name] = np.where(df[column] != value, 1, 0)
    if return_df:
        return df
    else:
        return df[new_col_name]
Return a column with 1 if df[source_col] is above/below a certain threshold.     Parameters     ----------     X : df         A pandas DataFrame     column : string         The column name from which to compute on     above_or_below : str         'above' to return 1 if value is above a certain threshold and 0 otherwise         'below' to return 1 if value is below a certain threshold and 0 otherwise     threshold_upper : int/float         A value between will act as the upper threshold for column         E.g. 45 would with 'above' denoted would return 1 for all values lower than 45     new_col_name : str        Name to give the new computed column.      Returns     -------     thresholded_binary_column: Pandas Series         A series with 1 where the column values were above the lower threshold and below upper threshold # Create new column name if none specified # Checking Arguments # New column is array with 1 where source col is above specified threshold # New column is array with 1 where source col is above specified threshold Return the dataframe. For use with decorators Given a dataframe and column to create binary features from bins, return dummy columns of said bins     concatenated onto the end of the df # cut the column values into bins. the labels provided are the returned values # bins must increase monotonically # Create dummy variables and add prefix to col label # Concatenate onto end of original df Apply function to pandas df with axis=1 to evaluate row values and return value or binary response     If binary=True, response values are 1 if present 0 otherwise     Else returns the original value or a NaN     E.g.:     df.apply(lambda s: bin_feature_binary(s, 'hazard_rank', 0, 3), axis=1) to create a binary feature that returns     1 if hazard group is between 0-3 and 0 if otherwise Given a dataframe, feature column (series), bin edges, return a new series whose values are those that fit within the     bin edges. Optionally denote if binary response (1 if present, 0 otherwise) Given a dataframe, feature column name and value to check, return a series of binary responses 1 and 0     1 if the value in the feature column to check is present, 0 if otherwise     binary_feature # If binary_feature_col_name is none use this instead Given a series and a value, return a binary feature 1 if present and 0 if otherwise # Return binary feature series # Set series name Return a column of 1s where df[column] == value and 0s if not Return a column of 1s where df[column] == value and 0s if not