# AUTOGENERATED! DO NOT EDIT! File to edit: nbs/01_tabular.core.ipynb (unless otherwise specified).

__all__ = ['Interpret', 'sv_var', 'ld_var', 'list_diff', 'which_elms', 'is_in_list', 'listify', 'isNone', 'isNotNone']

# Cell
from fastai.tabular.all import *

# Cell
class Interpret():
    def __init__(self, learn, df):
        """
        MasterClass what knows how to deal with learner and dataframe
        Now for classification only
        """
        self.learn = learn
        self.df = df

    def _predict_row(self, row):
        """
        Wrapper for prediction on a single row
        """
        learn = self.learn
        return float(learn.get_preds(dl=learn.dls.test_dl(pd.DataFrame([row])))[0][0][0])

    def _predict_df(self, df=None, is_ret_actls=False):
        """
        returns predictions of df with certain learner
        """
        df = df if isNotNone(df) else self.df
        if (is_ret_actls == False):
            return np.array(self.learn.get_preds(dl=self.learn.dls.test_dl(df))[0].T[0])
        else:
            out = self.learn.get_preds(dl=self.learn.dls.test_dl(df))
            return np.array(out[0].T[0]), np.array(out[1].T[0])

    def _convert_dep_col(self, dep_col, use_log=False):
        '''
        Converts dataframe column, named "depended column", into tensor, that can later be used to compare with predictions.
        Log will be applied if use_log is set True
        '''
        actls = self.df[dep_col].T.to_numpy()[np.newaxis].T.astype('float32')
        actls = np.log(actls) if (use_log == True) else actls
        return torch.tensor(actls)

    def _list_to_key(self, field):
        """
        Turns unhashable list of strings to hashable key
        """
        return f"{field}" if isinstance(field, str) else ', '.join(f"{e}" for e in field)

    def _sv_var(self, var, name, path: Path = None):
        "Save variable as pickle object to path with name"
        f = open(path / f"{name}.pkl", "wb")
        dump(var, f)
        f.close()

    def _ld_var(self, name, path: Path = None):
        "Returns a pickle object from path with name"

        f = open(path / f"{name}.pkl", "rb")
        var = load(f)
        f.close()
        return var

    def _calc_loss(self, pred, targ):
        '''
        Calculates error from predictions and actuals with a learner loss function
        '''
        func = self.learn.loss_func
        return func(torch.tensor(pred, device=default_device()), torch.tensor(targ, device=default_device()))

    def _calc_error(self, df=None):
        '''
        Wrapping function to calculate error for new dataframe on existing learner (learn.model)
        See following functions' docstrings for details
        '''
        df = df if isNotNone(df) else self.df
        preds, actls = self._predict_df(df=df, is_ret_actls=True)
        error = self._calc_loss(pred=preds, targ=actls)
        return float(error)

    def _get_cat_columns(self, is_wo_na=False):
        if (is_wo_na == False):
            return self.learn.dls.cat_names
        else:
            return self.learn.dls.cat_names.filter(lambda x: x[-3:] != "_na")

    def _get_cont_columns(self):
        return self.learn.dls.cont_names

    def _get_all_columns(self):
        return self._get_cat_columns() + self._get_cont_columns()

    def _get_dep_var(self):
        return self.learn.dls.y_names[0]

# Cell
def sv_var(var, name, path, bzipped=False):
    "Save variable as pickle object to path with name"
    if (bzipped == False):
        f = open(path/f"{name}.pkl","wb")
    else:
        f = BZ2File(path/f"{name}.pkl.bz2", "wb")
    dump(var, f)
    f.close()

def ld_var(name, path, bzipped=False):
    "Returns a pickle object from path with name"
    if (bzipped == False):
        f = open(path/f"{name}.pkl","rb")
    else:
        f = BZ2File(path/f"{name}.pkl.bz2","rb")
    var = load(f)
    f.close()
    return var

# Cell
def _list_diff(list_1:list, list_2:list)->list:
    "Difference between first and second lists"
    diff = set(list_1) - set(list_2)
    return [item for item in list_1 if item in diff]

def list_diff(list1, list2, *args)->list:
    "Difference between first and any number of lists"
    diff = _list_diff(list1, list2)
    for arg in args:
        diff = _list_diff(diff, arg)
    return diff

# Cell
def which_elms(values:list, in_list:list)->list:
    '''
    Just returns elements from values that are in list in_list
    '''
    return [x for x in values if (x in in_list)]

# Cell
def is_in_list(values:list, in_list:list)->bool:
    '''
    Just returns is any of the elements from values is in list in_list
    '''
    if (len(which_elms(values, in_list)) > 0):
        return True
    else:
        return False

# Cell
def listify(p=None, match=None):
    "Make `p` listy and the same length as `match`."
    if p is None: p=[]
    elif isinstance(p, str): p = [p]
    else:
        try: a = len(p)
        except: p = [p]
    n = match if type(match)==int else len(p) if match is None else len(match)
    if len(p)==1: p = p * n
    assert len(p)==n, f'List len mismatch ({len(p)} vs {n})'
    return list(p)

# Cell
def isNone(cond):
    return cond is None

def isNotNone(cond):
    return cond is not None