# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/01_APIS/00_data.ipynb.

# %% auto 0
__all__ = ['Dataset']

# %% ../nbs/01_APIS/00_data.ipynb 3
from fastcore.utils import *
import numpy as np
import pandas as pd
import gc, copy

# %% ../nbs/01_APIS/00_data.ipynb 5
class Dataset:
    """
    Dataset Adaptor Class

    This class is meant to make dataset possible which would be consumed by models further
    """
    def __init__(self, df:pd.DataFrame,      # Dataframe that needs to be converted 
                reduce_datatype:bool = True, # Shall we try to reduce datatype to make is smaller
                encode_category:str = None,  # Do encoding of categories default to None as no encoding
                add_intercept:bool = False,  # Add a constant value intercept to data. This might be needed for Model based Trees.
                na_treatment:str = 'allow',  # How to work with nas. Default: 'allow'
                copy_data:bool = False,      # Keep a self copy of original data
                digits:int = None,           # To round float to certain digits or not, Default: None means no rounding
                n_category:Union[int, float, None] = None  # How many different level shoud be treated as category. If a value less than one the number of levels is defined aas % oft total rows
                ) -> None:
        self.df, self.reduce_datatype = df, reduce_datatype
        self.encode_category, self.add_intercept = encode_category, add_intercept
        self.na_treatment, self.copy,self.digits = na_treatment, copy_data, digits
        self.n_category = n_category
        self.split_ratio = None
        self.features_ = self.df.columns.tolist()
        self._shape = self.df.shape
        self._dtypes = self.df.dtypes
        self._category_lbl_dict = {}
        self._dataranges = {}
        
        self._process_data()      

    def _reduce_size(self):
        df = pd.DataFrame()
        for col,dtype in self._dtypes.items():
            if dtype != object:
                max_,min_,hasna_ = self.df[col].max(),self.df[col].min(),np.isfinite(self.df[col]).all()
                isint_ = dtype == int
                if not hasna_:
                    asint = self.df[col].fillna(0).astype(np.int64)
                    result = (self.df[col] - asint).sum()
                    if -0.01 < result < 0.01:
                        isint_ = True

                # Make Integer/unsigned Integer datatypes
                if isint_:
                    if min_ >= 0:
                        df[col] = pd.to_numeric(self.df[col].fillna(min_-1), downcast="unsigned",errors='coerce')
                    else:
                        df[col] = pd.to_numeric(self.df[col].fillna(min_-1), downcast="integer",errors='coerce')
                        
                    self._dataranges[col] = {'hasna':hasna_,'min':min_-1,'max':max_,'inferredtype':df[col].dtype}

                # Make float datatypes 32 bit
                else: #todo make this more advanced with rounding and more : evaluate the below an
                    if self.digits is None:
                        df[col] = pd.to_numeric(self.df[col], downcast="float",errors='coerce')
                    else:
                        df[col] = pd.to_numeric(np.round(self.df[col],self.digits), downcast="float",errors='coerce')
                        max_,min_,hasna_ = df[col].max(),df[col].min(),np.isfinite(df[col]).all()
                    self._dataranges[col] = {'hasna':hasna_,'min':min_,'max':max_,'inferredtype':df[col].dtype}
            else:
                #todo : add logic of makig a bing object to small category based on defintion
                df[col] =self.df[col]
                self._dataranges[col] = {'hasna':hasna_,'min':None,'max':None,'inferredtype':df[col].dtype}
        
        try: 
            self.data = np.array([tuple(x) for x in df.values], dtype=[(k,v['inferredtype']) for k,v in self._dataranges.items()])
        except:
            print('Their was an error in above')
            self.data = df.copy(deep=True)

    def _process_data(self):
        self._reduce_size()
        if not self.copy:
            self.df = None
            gc.collect()

    def dtypes(self,actual=False):
        if actual:
            return pd.DataFrame.from_dict(self._dtypes,orient='index').rename(columns={0:'data_type'})
        else:
            dt = {k:v['inferredtype'] for k,v in self._dataranges.items()}

    @property
    def shape(self): return self._shape

    def __str__(self) -> str:
        s = f"Dataset(df=Shape({self.shape}"
        for k,v in self.__dict__.items() :
            if (k not in ('df','features_','data')) and (not k.startswith('_')) : s += f", {k}={v}"
        s += ')'
        return s
    
    __repr__ = __str__

    
    

