text: # Module: Anomaly Detection
  # Author: Moez Ali <moez.ali@queensu.ca>
  # License: MIT
  # Release: PyCaret 2.1
  # Last modified : 16/08/2020
  
  def setup(data, 
            categorical_features = None,
            categorical_imputation = 'constant',
            ordinal_features = None, 
            high_cardinality_features = None,
            numeric_features = None,
            numeric_imputation = 'mean',
            date_features = None,
            ignore_features = None,
            normalize = False,
            normalize_method = 'zscore',
            transformation = False,
            transformation_method = 'yeo-johnson',
            handle_unknown_categorical = True,           
            unknown_categorical_method = 'least_frequent',
            pca = False,
            pca_method = 'linear',
            pca_components = None,
            ignore_low_variance = False, 
            combine_rare_levels = False, 
            rare_level_threshold = 0.10, 
            bin_numeric_features = None, 
            remove_multicollinearity = False,
            multicollinearity_threshold = 0.9,
            group_features = None,
            group_names = None,
            supervised = False,
            supervised_target = None,
            n_jobs = -1, #added in pycaret==2.0.0
            html = True, #added in pycaret==2.0.0
            session_id = None,
            log_experiment = False, #added in pycaret==2.0.0
            experiment_name = None, #added in pycaret==2.0.0
            log_plots = False, #added in pycaret==2.0.0
            log_profile = False, #added in pycaret==2.0.0
            log_data = False, #added in pycaret==2.0.0
            silent=False, #added in pycaret==2.0.0
            verbose=True,
            profile = False):
      
      """
      This function initializes the environment in pycaret. setup() must called before
      executing any other function in pycaret. It takes one mandatory parameter:
      data.
  
      Example
      -------
      >>> from pycaret.datasets import get_data
      >>> anomaly = get_data('anomaly')
      >>> experiment_name = setup(data = anomaly, normalize = True)
      
      'anomaly' is a pandas.DataFrame.
  
      Parameters
      ----------
      data : pandas.DataFrame
          Shape (n_samples, n_features) where n_samples is the number of samples and n_features is the number of features.
      
      categorical_features: string, default = None
          If the inferred data types are not correct, categorical_features can be used to
          overwrite the inferred type. If when running setup the type of 'column1' is
          inferred as numeric instead of categorical, then this parameter can be used 
          to overwrite the type by passing categorical_features = ['column1'].
      
      categorical_imputation: string, default = 'constant'
          If missing values are found in categorical features, they will be imputed with
          a constant 'not_available' value. The other available option is 'mode' which 
          imputes the missing value using most frequent value in the training dataset. 
      
      ordinal_features: dictionary, default = None
          When the data contains ordinal features, they must be encoded differently using 
          the ordinal_features param. If the data has a categorical variable with values
          of 'low', 'medium', 'high' and it is known that low < medium < high, then it can 
          be passed as ordinal_features = { 'column_name' : ['low', 'medium', 'high'] }. 
          The list sequence must be in increasing order from lowest to highest.
      
      high_cardinality_features: string, default = None
          When the data containts features with high cardinality, they can be compressed
          into fewer levels by passing them as a list of column names with high cardinality.
          Features are compressed using frequency distribution. As such original features
          are replaced with the frequency distribution and converted into numeric variable. 
      
      numeric_features: string, default = None
          If the inferred data types are not correct, numeric_features can be used to
          overwrite the inferred type. If when running setup the type of 'column1' is 
          inferred as a categorical instead of numeric, then this parameter can be used 
          to overwrite by passing numeric_features = ['column1'].    
  
      numeric_imputation: string, default = 'mean'
          If missing values are found in numeric features, they will be imputed with the 
          mean value of the feature. The other available option is 'median' which imputes 
          the value using the median value in the training dataset. 
      
      date_features: string, default = None
          If the data has a DateTime column that is not automatically detected when running
          setup, this parameter can be used by passing date_features = 'date_column_name'. 
          It can work with multiple date columns. Date columns are not used in modeling. 
          Instead, feature extraction is performed and date columns are dropped from the 
          dataset. If the date column includes a time stamp, features related to time will 
          also be extracted.
      
      ignore_features: string, default = None
          If any feature should be ignored for modeling, it can be passed to the param
          ignore_features. The ID and DateTime columns when inferred, are automatically 
          set to ignore for modeling. 
      
      normalize: bool, default = False
          When set to True, the feature space is transformed using the normalized_method
          param. Generally, linear algorithms perform better with normalized data however, 
          the results may vary and it is advised to run multiple experiments to evaluate
          the benefit of normalization.
      
      normalize_method: string, default = 'zscore'
          Defines the method to be used for normalization. By default, normalize method
          is set to 'zscore'. The standard zscore is calculated as z = (x - u) / s. The
          other available options are:
          
          'minmax'    : scales and translates each feature individually such that it is in 
                      the range of 0 - 1.
          
          'maxabs'    : scales and translates each feature individually such that the maximal 
                      absolute value of each feature will be 1.0. It does not shift/center 
                      the data, and thus does not destroy any sparsity.
          
          'robust'    : scales and translates each feature according to the Interquartile range.
                      When the dataset contains outliers, robust scaler often gives better
                      results.
      
      transformation: bool, default = False
          When set to True, a power transformation is applied to make the data more normal /
          Gaussian-like. This is useful for modeling issues related to heteroscedasticity or 
          other situations where normality is desired. The optimal parameter for stabilizing 
          variance and minimizing skewness is estimated through maximum likelihood.
      
      transformation_method: string, default = 'yeo-johnson'
          Defines the method for transformation. By default, the transformation method is set
          to 'yeo-johnson'. The other available option is 'quantile' transformation. Both 
          the transformation transforms the feature set to follow a Gaussian-like or normal
          distribution. Note that the quantile transformer is non-linear and may distort linear 
          correlations between variables measured at the same scale.
      
      handle_unknown_categorical: bool, default = True
          When set to True, unknown categorical levels in new / unseen data are replaced by
          the most or least frequent level as learned in the training data. The method is 
          defined under the unknown_categorical_method param.
      
      unknown_categorical_method: string, default = 'least_frequent'
          Method used to replace unknown categorical levels in unseen data. Method can be
          set to 'least_frequent' or 'most_frequent'.
      
      pca: bool, default = False
          When set to True, dimensionality reduction is applied to project the data into 
          a lower dimensional space using the method defined in pca_method param. In 
          supervised learning pca is generally performed when dealing with high feature
          space and memory is a constraint. Note that not all datasets can be decomposed
          efficiently using a linear PCA technique and that applying PCA may result in loss 
          of information. As such, it is advised to run multiple experiments with different 
          pca_methods to evaluate the impact. 
  
      pca_method: string, default = 'linear'
          The 'linear' method performs Linear dimensionality reduction using Singular Value 
          Decomposition. The other available options are:
          
          kernel      : dimensionality reduction through the use of RVF kernel.  
          
          incremental : replacement for 'linear' pca when the dataset to be decomposed is 
                      too large to fit in memory
      
      pca_components: int/float, default = 0.99
          Number of components to keep. if pca_components is a float, it is treated as a 
          target percentage for information retention. When pca_components is an integer
          it is treated as the number of features to be kept. pca_components must be strictly
          less than the original number of features in the dataset.
          
          ignore_low_variance: bool, default = False
          When set to True, all categorical features with statistically insignificant variances 
          are removed from the dataset. The variance is calculated using the ratio of unique 
          values to the number of samples, and the ratio of the most common value to the 
          frequency of the second most common value.
      
      combine_rare_levels: bool, default = False
          When set to True, all levels in categorical features below the threshold defined 
          in rare_level_threshold param are combined together as a single level. There must be 
          atleast two levels under the threshold for this to take effect. rare_level_threshold
          represents the percentile distribution of level frequency. Generally, this technique 
          is applied to limit a sparse matrix caused by high numbers of levels in categorical 
          features. 
          
          rare_level_threshold: float, default = 0.1
          Percentile distribution below which rare categories are combined. Only comes into
          effect when combine_rare_levels is set to True.
      
      bin_numeric_features: list, default = None
          When a list of numeric features is passed they are transformed into categorical
          features using KMeans, where values in each bin have the same nearest center of a 
          1D k-means cluster. The number of clusters are determined based on the 'sturges' 
          method. It is only optimal for gaussian data and underestimates the number of bins 
          for large non-gaussian datasets.
      
      remove_multicollinearity: bool, default = False
          When set to True, the variables with inter-correlations higher than the threshold
          defined under the multicollinearity_threshold param are dropped. When two features
          are highly correlated with each other, the feature with higher average correlation 
          in the feature space is dropped. 
      
      multicollinearity_threshold: float, default = 0.9
          Threshold used for dropping the correlated features. Only comes into effect when 
          remove_multicollinearity is set to True.
      
      group_features: list or list of list, default = None
          When a dataset contains features that have related characteristics, the group_features
          param can be used for statistical feature extraction. For example, if a dataset has 
          numeric features that are related with each other (i.e 'Col1', 'Col2', 'Col3'), a list 
          containing the column names can be passed under group_features to extract statistical 
          information such as the mean, median, mode and standard deviation.
      
      group_names: list, default = None
          When group_features is passed, a name of the group can be passed into the group_names 
          param as a list containing strings. The length of a group_names list must equal to the 
          length  of group_features. When the length doesn't match or the name is not passed, new 
          features are sequentially named such as group_1, group_2 etc.
      
      supervised: bool, default = False
          When set to True, supervised_target column is ignored for transformation. This
          param is only for internal use. 
      
      supervised_target: string, default = None
          Name of supervised_target column that will be ignored for transformation. Only
          applicable when tune_model() function is used. This param is only for internal use.
  
      n_jobs: int, default = -1
          The number of jobs to run in parallel (for functions that supports parallel 
          processing) -1 means using all processors. To run all functions on single processor 
          set n_jobs to None.
  
      html: bool, default = True
          If set to False, prevents runtime display of monitor. This must be set to False
          when using environment that doesnt support HTML.
  
      session_id: int, default = None
          If None, a random seed is generated and returned in the Information grid. The 
          unique number is then distributed as a seed in all functions used during the 
          experiment. This can be used for later reproducibility of the entire experiment.
      
      log_experiment: bool, default = True
          When set to True, all metrics and parameters are logged on MLFlow server.
  
      experiment_name: str, default = None
          Name of experiment for logging. When set to None, 'clf' is by default used as 
          alias for the experiment name.    
  
      log_plots: bool, default = False
          When set to True, specific plots are logged in MLflow as a png file. By default,
          it is set to False. 
  
      log_profile: bool, default = False
          When set to True, data profile is also logged on MLflow as a html file. By default,
          it is set to False. 
  
      silent: bool, default = False
          When set to True, confirmation of data types is not required. All preprocessing will 
          be performed assuming automatically inferred data types. Not recommended for direct use 
          except for established pipelines.
  
      verbose: Boolean, default = True
          Information grid is not printed when verbose is set to False.
  
      profile: bool, default = False
          If set to true, a data profile for Exploratory Data Analysis will be displayed 
          in an interactive HTML report. 
      
      Returns
      -------
      info_grid
          Information grid is printed.
  
      environment
          This function returns various outputs that are stored in variable
          as tuple. They are used by other functions in pycaret.
        
            
      """
      
      #exception checking   
      import sys
      
      from pycaret.utils import __version__
      ver = __version__()
  
      import logging
  
      # create logger
      global logger
      
      logger = logging.getLogger('logs')
      logger.setLevel(logging.DEBUG)
      
      # create console handler and set level to debug
      if logger.hasHandlers():
          logger.handlers.clear()
      
      ch = logging.FileHandler('logs.log')
      ch.setLevel(logging.DEBUG)
  
      # create formatter
      formatter = logging.Formatter('%(asctime)s:%(levelname)s:%(message)s')
  
      # add formatter to ch
      ch.setFormatter(formatter)
  
      # add ch to logger
      logger.addHandler(ch)
  
      logger.info("PyCaret Anomaly Detection Module")
      logger.info('version ' + str(ver))
      logger.info("Initializing setup()")
  
      #generate USI for mlflow tracking
      import secrets
      global USI
      USI = secrets.token_hex(nbytes=2)
      logger.info('USI: ' + str(USI))
  
      logger.info("""setup(data={}, categorical_features={}, categorical_imputation={}, ordinal_features={}, high_cardinality_features={}, 
                      numeric_features={}, numeric_imputation={}, date_features={}, ignore_features={}, normalize={},
                      normalize_method={}, transformation={}, transformation_method={}, handle_unknown_categorical={}, unknown_categorical_method={}, pca={}, pca_method={},
                      pca_components={}, ignore_low_variance={}, combine_rare_levels={}, rare_level_threshold={}, bin_numeric_features={},
                      remove_multicollinearity={}, multicollinearity_threshold={}, group_features={},
                      group_names={}, supervised={}, supervised_target={}, n_jobs={}, html={}, session_id={}, log_experiment={},
                      experiment_name={}, log_plots={}, log_profile={}, log_data={}, silent={}, verbose={}, profile={})""".format(\
              str(data.shape), str(categorical_features), str(categorical_imputation), str(ordinal_features),\
              str(high_cardinality_features), str(numeric_features), str(numeric_imputation), str(date_features), str(ignore_features),\
              str(normalize), str(normalize_method), str(transformation), str(transformation_method), str(handle_unknown_categorical), str(unknown_categorical_method), str(pca),\
              str(pca_method), str(pca_components), str(ignore_low_variance), str(combine_rare_levels), str(rare_level_threshold), str(bin_numeric_features),\
              str(remove_multicollinearity), str(multicollinearity_threshold), str(group_features),str(group_names),str(supervised), str(supervised_target), str(n_jobs), str(html),\
              str(session_id),str(log_experiment), str(experiment_name), str(log_plots),str(log_profile), str(log_data), str(silent), str(verbose), str(profile)))
  
      #logging environment and libraries
      logger.info("Checking environment")
      
      from platform import python_version, platform, python_build, machine
  
      try:
          logger.info("python_version: " + str(python_version()))
      except:
          logger.warning("cannot find platform.python_version")
  
      try:
          logger.info("python_build: " + str(python_build()))
      except:
          logger.warning("cannot find platform.python_build")
  
      try:
          logger.info("machine: " + str(machine()))
      except:
          logger.warning("cannot find platform.machine")
  
      try:
          logger.info("platform: " + str(platform()))
      except:
          logger.warning("cannot find platform.platform")
  
      try:
          import psutil
          logger.info("Memory: " + str(psutil.virtual_memory()))
          logger.info("Physical Core: " + str(psutil.cpu_count(logical=False)))
          logger.info("Logical Core: " + str(psutil.cpu_count(logical=True)))
      except:
          logger.warning("cannot find psutil installation. memory not traceable. Install psutil using pip to enable memory logging. ")
          
      logger.info("Checking libraries")
  
      try:
          from pandas import __version__
          logger.info("pd==" + str(__version__))
      except:
          logger.warning("pandas not found")
  
      try:
          from numpy import __version__
          logger.info("numpy==" + str(__version__))
      except:
          logger.warning("numpy not found")
  
      try:
          from pyod import __version__
          logger.info("pyod==" + str(__version__))
      except:
          logger.warning("pyod not found")
  
      try:
          from mlflow.version import VERSION
          import warnings
          warnings.filterwarnings('ignore') 
          logger.info("mlflow==" + str(VERSION))
      except:
          logger.warning("mlflow not found")
  
      logger.info("Checking Exceptions")
  
      #run_time
      import datetime, time
      runtime_start = time.time()
  
      """
      error handling starts here
      """
      
      #checking data type
      if hasattr(data,'shape') is False:
          sys.exit('(Type Error): data passed must be of type pandas.DataFrame')  
  
      #checking session_id
      if session_id is not None:
          if type(session_id) is not int:
              sys.exit('(Type Error): session_id parameter must be an integer.')  
              
      #checking normalize parameter
      if type(normalize) is not bool:
          sys.exit('(Type Error): normalize parameter only accepts True or False.')
          
      #checking transformation parameter
      if type(transformation) is not bool:
          sys.exit('(Type Error): transformation parameter only accepts True or False.')
          
      #checking categorical imputation
      allowed_categorical_imputation = ['constant', 'mode']
      if categorical_imputation not in allowed_categorical_imputation:
          sys.exit("(Value Error): categorical_imputation param only accepts 'constant' or 'mode' ")
      
      #ordinal_features
      if ordinal_features is not None:
          if type(ordinal_features) is not dict:
              sys.exit("(Type Error): ordinal_features must be of type dictionary with column name as key and ordered values as list. ")
      
      #ordinal features check
      if ordinal_features is not None:
          data_cols = data.columns
          #data_cols = data_cols.drop(target)
          ord_keys = ordinal_features.keys()
  
          for i in ord_keys:
              if i not in data_cols:
                  sys.exit("(Value Error) Column name passed as a key in ordinal_features param doesnt exist. ")
                  
          for k in ord_keys:
              if data[k].nunique() != len(ordinal_features.get(k)):
                  sys.exit("(Value Error) Levels passed in ordinal_features param doesnt match with levels in data. ")
                  
          for i in ord_keys:
              value_in_keys = ordinal_features.get(i)
              value_in_data = list(data[i].unique().astype(str))
              for j in value_in_keys:
                  if j not in value_in_data:
                      text =  "Column name '" + str(i) + "' doesnt contain any level named '" + str(j) + "'."
                      sys.exit(text)
      
      #high_cardinality_features
      if high_cardinality_features is not None:
          if type(high_cardinality_features) is not list:
              sys.exit("(Type Error): high_cardinality_features param only accepts name of columns as a list. ")
          
      if high_cardinality_features is not None:
          data_cols = data.columns
          #data_cols = data_cols.drop(target)
          for i in high_cardinality_features:
              if i not in data_cols:
                  sys.exit("(Value Error): Column type forced is either target column or doesn't exist in the dataset.")
                  
      #checking numeric imputation
      allowed_numeric_imputation = ['mean', 'median']
      if numeric_imputation not in allowed_numeric_imputation:
          sys.exit("(Value Error): numeric_imputation param only accepts 'mean' or 'median' ")
          
      #checking normalize method
      allowed_normalize_method = ['zscore', 'minmax', 'maxabs', 'robust']
      if normalize_method not in allowed_normalize_method:
          sys.exit("(Value Error): normalize_method param only accepts 'zscore', 'minxmax', 'maxabs' or 'robust'. ")    
      
      #checking transformation method
      allowed_transformation_method = ['yeo-johnson', 'quantile']
      if transformation_method not in allowed_transformation_method:
          sys.exit("(Value Error): transformation_method param only accepts 'yeo-johnson' or 'quantile' ")        
      
      #handle unknown categorical
      if type(handle_unknown_categorical) is not bool:
          sys.exit('(Type Error): handle_unknown_categorical parameter only accepts True or False.')
          
      #unknown categorical method
      unknown_categorical_method_available = ['least_frequent', 'most_frequent']
      
      #forced type check
      all_cols = list(data.columns)
      
      #categorical
      if categorical_features is not None:
          for i in categorical_features:
              if i not in all_cols:
                  sys.exit("(Value Error): Column type forced is either target column or doesn't exist in the dataset.") 
      #numeric
      if numeric_features is not None:
          for i in numeric_features:
              if i not in all_cols:
                  sys.exit("(Value Error): Column type forced is either target column or doesn't exist in the dataset.")    
      
      #date features
      if date_features is not None:
          for i in date_features:
              if i not in all_cols:
                  sys.exit("(Value Error): Column type forced is either target column or doesn't exist in the dataset.")      
      
      #drop features
      if ignore_features is not None:
          for i in ignore_features:
              if i not in all_cols:
                  sys.exit("(Value Error): Feature ignored is either target column or doesn't exist in the dataset.")     
      
      #check pca
      if type(pca) is not bool:
          sys.exit('(Type Error): PCA parameter only accepts True or False.')
          
      #pca method check
      allowed_pca_methods = ['linear', 'kernel', 'incremental']
      if pca_method not in allowed_pca_methods:
          sys.exit("(Value Error): pca method param only accepts 'linear', 'kernel', or 'incremental'. ")    
      
      #pca components check
      if pca is True:
          if pca_method is not 'linear':
              if pca_components is not None:
                  if(type(pca_components)) is not int:
                      sys.exit("(Type Error): pca_components parameter must be integer when pca_method is not 'linear'. ")
  
      #pca components check 2
      if pca is True:
          if pca_method is not 'linear':
              if pca_components is not None:
                  if pca_components > len(data.columns):
                      sys.exit("(Type Error): pca_components parameter cannot be greater than original features space.")                
   
      #pca components check 3
      if pca is True:
          if pca_method is 'linear':
              if pca_components is not None:
                  if type(pca_components) is not float:
                      if pca_components > len(data.columns): 
                          sys.exit("(Type Error): pca_components parameter cannot be greater than original features space or float between 0 - 1.")      
          
      #check ignore_low_variance
      if type(ignore_low_variance) is not bool:
          sys.exit('(Type Error): ignore_low_variance parameter only accepts True or False.')
          
      #check ignore_low_variance
      if type(combine_rare_levels) is not bool:
          sys.exit('(Type Error): combine_rare_levels parameter only accepts True or False.')
          
      #check rare_level_threshold
      if type(rare_level_threshold) is not float:
          sys.exit('(Type Error): rare_level_threshold must be a float between 0 and 1. ')
      
      #bin numeric features
      if bin_numeric_features is not None:
          all_cols = list(data.columns)
          
          for i in bin_numeric_features:
              if i not in all_cols:
                  sys.exit("(Value Error): Column type forced is either target column or doesn't exist in the dataset.")
      
      #remove_multicollinearity
      if type(remove_multicollinearity) is not bool:
          sys.exit('(Type Error): remove_multicollinearity parameter only accepts True or False.')
          
      #multicollinearity_threshold
      if type(multicollinearity_threshold) is not float:
          sys.exit('(Type Error): multicollinearity_threshold must be a float between 0 and 1. ')  
      
      #group features
      if group_features is not None:
          if type(group_features) is not list:
              sys.exit('(Type Error): group_features must be of type list. ')     
      
      if group_names is not None:
          if type(group_names) is not list:
              sys.exit('(Type Error): group_names must be of type list. ')        
  
      #silent
      if type(silent) is not bool:
          sys.exit("(Type Error): silent parameter only accepts True or False. ")
          
      #html
      if type(html) is not bool:
          sys.exit('(Type Error): html parameter only accepts True or False.')
  
      #log_experiment
      if type(log_experiment) is not bool:
          sys.exit('(Type Error): log_experiment parameter only accepts True or False.')
  
      #log_plots
      if type(log_plots) is not bool:
          sys.exit('(Type Error): log_plots parameter only accepts True or False.')
  
      #log_data
      if type(log_data) is not bool:
          sys.exit('(Type Error): log_data parameter only accepts True or False.')
  
      #log_profile
      if type(log_profile) is not bool:
          sys.exit('(Type Error): log_profile parameter only accepts True or False.')
          
      
      """
      error handling ends here
      """
      
      logger.info("Preloading libraries")
      #pre-load libraries
      import pandas as pd
      import ipywidgets as ipw
      from IPython.display import display, HTML, clear_output, update_display
      import datetime, time
      import secrets
      
      #pandas option
      pd.set_option('display.max_columns', 500)
      pd.set_option('display.max_rows', 500)
      
      #global html_param
      global html_param
  
      #create html_param
      html_param = html
  
      logger.info("Preparing display monitor")
  
      #progress bar
      max_steps = 4
          
      progress = ipw.IntProgress(value=0, min=0, max=max_steps, step=1 , description='Processing: ')
      
          
      timestampStr = datetime.datetime.now().strftime("%H:%M:%S")
      monitor = pd.DataFrame( [ ['Initiated' , '. . . . . . . . . . . . . . . . . .', timestampStr ], 
                               ['Status' , '. . . . . . . . . . . . . . . . . .' , 'Loading Dependencies' ] ],
                               #['Step' , '. . . . . . . . . . . . . . . . . .',  'Step 0 of ' + str(total_steps)] ],
                                columns=['', ' ', '   ']).set_index('')
      
      if verbose:
          if html_param:
              display(progress)
              display(monitor, display_id = 'monitor')
      
      logger.info("Importing libraries")
      #general dependencies
      import numpy as np
      import pandas as pd
      import random
      
      #setting sklearn config to print all parameters including default
      import sklearn
      sklearn.set_config(print_changed_only=False)
  
      #define highlight function for function grid to display
      def highlight_max(s):
          is_max = s == True
          return ['background-color: lightgreen' if v else '' for v in is_max]
      
      #ignore warnings
      import warnings
      warnings.filterwarnings('ignore') 
      
      logger.info("Declaring global variables")
      #defining global variables
      global data_, X, seed, prep_pipe, prep_param, experiment__,\
          n_jobs_param, exp_name_log, logging_param, log_plots_param
      
      logger.info("Copying data for preprocessing")
      #copy original data for pandas profiler
      data_before_preprocess = data.copy()
      
      #copying data
      data_ = data.copy()
      
      #data without target
      if supervised:
          data_without_target = data.copy()
          data_without_target.drop(supervised_target, axis=1, inplace=True)
      
      if supervised:
          data_for_preprocess = data_without_target.copy()
      else:
          data_for_preprocess = data_.copy()
          
      #generate seed to be used globally
      if session_id is None:
          seed = random.randint(150,9000)
      else:
          seed = session_id    
  
      """
      preprocessing starts here
      """
      
      pd.set_option('display.max_columns', 500)
      pd.set_option('display.max_rows', 500)
  
      monitor.iloc[1,1:] = 'Preparing Data for Modeling'
      if verbose:
          if html_param:
              update_display(monitor, display_id = 'monitor')
              
      #define parameters for preprocessor
      
      logger.info("Declaring preprocessing parameters")
  
      #categorical features
      if categorical_features is None:
          cat_features_pass = []
      else:
          cat_features_pass = categorical_features
      
      #numeric features
      if numeric_features is None:
          numeric_features_pass = []
      else:
          numeric_features_pass = numeric_features
       
      #drop features
      if ignore_features is None:
          ignore_features_pass = []
      else:
          ignore_features_pass = ignore_features
       
      #date features
      if date_features is None:
          date_features_pass = []
      else:
          date_features_pass = date_features
          
      #categorical imputation strategy
      if categorical_imputation == 'constant':
          categorical_imputation_pass = 'not_available'
      elif categorical_imputation == 'mode':
          categorical_imputation_pass = 'most frequent'
      
      #transformation method strategy
      if transformation_method == 'yeo-johnson':
          trans_method_pass = 'yj'
      elif transformation_method == 'quantile':
          trans_method_pass = 'quantile'
      
      #pass method
      if pca_method == 'linear':
          pca_method_pass = 'pca_liner'
              
      elif pca_method == 'kernel':
          pca_method_pass = 'pca_kernal'
              
      elif pca_method == 'incremental':
          pca_method_pass = 'incremental'
              
      elif pca_method == 'pls':
          pca_method_pass = 'pls'
          
      #pca components
      if pca is True:
          if pca_components is None:
              if pca_method == 'linear':
                  pca_components_pass = 0.99
              else:
                  pca_components_pass = int((len(data.columns))*0.5)
                  
          else:
              pca_components_pass = pca_components
              
      else:
          pca_components_pass = 0.99
          
      if bin_numeric_features is None:
          apply_binning_pass = False
          features_to_bin_pass = []
      
      else:
          apply_binning_pass = True
          features_to_bin_pass = bin_numeric_features
      
      #group features
      #=============#
      
      #apply grouping
      if group_features is not None:
          apply_grouping_pass = True
      else:
          apply_grouping_pass = False
      
      #group features listing
      if apply_grouping_pass is True:
          
          if type(group_features[0]) is str:
              group_features_pass = []
              group_features_pass.append(group_features)
          else:
              group_features_pass = group_features
              
      else:
          
          group_features_pass = [[]]
      
      #group names
      if apply_grouping_pass is True:
  
          if (group_names is None) or (len(group_names) != len(group_features_pass)):
              group_names_pass = list(np.arange(len(group_features_pass)))
              group_names_pass = ['group_' + str(i) for i in group_names_pass]
  
          else:
              group_names_pass = group_names
              
      else:
          group_names_pass = []
          
  
      #unknown categorical
      if unknown_categorical_method == 'least_frequent':
          unknown_categorical_method_pass = 'least frequent'
      elif unknown_categorical_method == 'most_frequent':
          unknown_categorical_method_pass = 'most frequent'
      
      #ordinal_features
      if ordinal_features is not None:
          apply_ordinal_encoding_pass = True
      else:
          apply_ordinal_encoding_pass = False
          
      if apply_ordinal_encoding_pass is True:
          ordinal_columns_and_categories_pass = ordinal_features
      else:
          ordinal_columns_and_categories_pass = {}
         
      #high cardinality
      if apply_ordinal_encoding_pass is True:
          ordinal_columns_and_categories_pass = ordinal_features
      else:
          ordinal_columns_and_categories_pass = {}
          
      if high_cardinality_features is not None:
          apply_cardinality_reduction_pass = True
      else:
          apply_cardinality_reduction_pass = False
          
      cardinal_method_pass = 'count'
          
      if apply_cardinality_reduction_pass:
          cardinal_features_pass = high_cardinality_features
      else:
          cardinal_features_pass = []
      
      #display dtypes
      if supervised is False:
          display_types_pass = True
      else:
          display_types_pass = False
      
      if silent:
          display_types_pass = False
  
      logger.info("Importing preprocessing module")
  
      #import library
      from pycaret import preprocess
      
      logger.info("Creating preprocessing pipeline")
  
      X = preprocess.Preprocess_Path_Two(train_data = data_for_preprocess, 
                                         categorical_features = cat_features_pass,
                                         apply_ordinal_encoding = apply_ordinal_encoding_pass, #new
                                         ordinal_columns_and_categories = ordinal_columns_and_categories_pass,
                                         apply_cardinality_reduction = apply_cardinality_reduction_pass, #latest
                                         cardinal_method = cardinal_method_pass, #latest
                                         cardinal_features = cardinal_features_pass, #latest
                                         numerical_features = numeric_features_pass,
                                         time_features = date_features_pass,
                                         features_todrop = ignore_features_pass,
                                         display_types = display_types_pass,
                                         numeric_imputation_strategy = numeric_imputation,
                                         categorical_imputation_strategy = categorical_imputation_pass,
                                         scale_data = normalize,
                                         scaling_method = normalize_method,
                                         Power_transform_data = transformation,
                                         Power_transform_method = trans_method_pass,
                                         apply_untrained_levels_treatment= handle_unknown_categorical, #new
                                         untrained_levels_treatment_method = unknown_categorical_method_pass, #new
                                         apply_pca = pca,
                                         pca_method = pca_method_pass, #new
                                         pca_variance_retained_or_number_of_components = pca_components_pass, #new
                                         apply_zero_nearZero_variance = ignore_low_variance, #new
                                         club_rare_levels = combine_rare_levels, #new
                                         rara_level_threshold_percentage = rare_level_threshold, #new
                                         apply_binning = apply_binning_pass, #new
                                         features_to_binn = features_to_bin_pass, #new
                                         remove_multicollinearity = remove_multicollinearity, #new
                                         maximum_correlation_between_features = multicollinearity_threshold, #new
                                         apply_grouping = apply_grouping_pass, #new
                                         features_to_group_ListofList = group_features_pass, #new
                                         group_name = group_names_pass, #new
                                         random_state = seed)
          
      progress.value += 1
      logger.info("Preprocessing pipeline created successfully")
  
      try:
          res_type = ['quit','Quit','exit','EXIT','q','Q','e','E','QUIT','Exit']
          res = preprocess.dtypes.response
          if res in res_type:
              sys.exit("(Process Exit): setup has been interupted with user command 'quit'. setup must rerun." )
      except:
          pass
      
  
      #save prep pipe
      prep_pipe = preprocess.pipe
      prep_param = preprocess
      
      logger.info("Creating grid variables")
  
      #generate values for grid show
      missing_values = data_before_preprocess.isna().sum().sum()
      if missing_values > 0:
          missing_flag = True
      else:
          missing_flag = False
      
      if normalize is True:
          normalize_grid = normalize_method
      else:
          normalize_grid = 'None'
          
      if transformation is True:
          transformation_grid = transformation_method
      else:
          transformation_grid = 'None'
      
      if pca is True:
          pca_method_grid = pca_method
      else:
          pca_method_grid = 'None'
     
      if pca is True:
          pca_components_grid = pca_components_pass
      else:
          pca_components_grid = 'None'
          
      if combine_rare_levels:
          rare_level_threshold_grid = rare_level_threshold
      else:
          rare_level_threshold_grid = 'None'
      
      if bin_numeric_features is None:
          numeric_bin_grid = False
      else:
          numeric_bin_grid = True
  
      if ordinal_features is not None:
          ordinal_features_grid = True
      else:
          ordinal_features_grid = False
      
      if remove_multicollinearity is False:
          multicollinearity_threshold_grid = None
      else:
          multicollinearity_threshold_grid = multicollinearity_threshold
       
      if group_features is not None:
          group_features_grid = True
      else:
          group_features_grid = False
       
      if high_cardinality_features is not None:
          high_cardinality_features_grid = True
      else:
          high_cardinality_features_grid = False
  
      learned_types = preprocess.dtypes.learent_dtypes
      #learned_types.drop(target, inplace=True)
  
      float_type = 0 
      cat_type = 0
  
      for i in preprocess.dtypes.learent_dtypes:
          if 'float' in str(i):
              float_type += 1
          elif 'object' in str(i):
              cat_type += 1
          elif 'int' in str(i):
              float_type += 1
         
      
      """
      preprocessing ends here
      """
      
      #reset pandas option
      pd.reset_option("display.max_rows") 
      pd.reset_option("display.max_columns")
      
      logger.info("Creating global containers")
  
      #create an empty list for pickling later.
      if supervised is False:
          experiment__ = []
      else:
          try:
              experiment__.append('dummy')
              experiment__.remove('dummy')
          except:
              experiment__ = []
      
      #create n_jobs_param
      n_jobs_param = n_jobs
  
      #create logging parameter
      logging_param = log_experiment
  
      #create exp_name_log param incase logging is False
      exp_name_log = 'no_logging'
  
      #create an empty log_plots_param
      if log_plots:
          log_plots_param = True
      else:
          log_plots_param = False
  
      progress.value += 1
      
      #monitor update
      monitor.iloc[1,1:] = 'Compiling Results'
      if verbose:
          if html_param:
              update_display(monitor, display_id = 'monitor')
          
      '''
      Final display Starts
      '''
      
      shape = data.shape
      shape_transformed = X.shape
      
      if profile:
          if verbose:
              print('Setup Succesfully Completed! Loading Profile Now... Please Wait!')
      else:
          if verbose:
              print('Setup Succesfully Completed!')
  
      functions = pd.DataFrame ( [ ['session_id ', seed ],
                                   ['Original Data ', shape ],
                                   ['Missing Values ', missing_flag],
                                   ['Numeric Features ', str(float_type-1) ],
                                   ['Categorical Features ', str(cat_type) ],
                                   ['Ordinal Features ', ordinal_features_grid],
                                   ['High Cardinality Features ', high_cardinality_features_grid],
                                   ['Transformed Data ', shape_transformed ],
                                   ['Numeric Imputer ', numeric_imputation],
                                   ['Categorical Imputer ', categorical_imputation],
                                   ['Normalize ', normalize ],
                                   ['Normalize Method ', normalize_grid ],
                                   ['Transformation ', transformation ],
                                   ['Transformation Method ', transformation_grid ],
                                   ['PCA ', pca],
                                   ['PCA Method ', pca_method_grid],
                                   ['PCA components ', pca_components_grid],
                                   ['Ignore Low Variance ', ignore_low_variance],
                                   ['Combine Rare Levels ', combine_rare_levels],
                                   ['Rare Level Threshold ', rare_level_threshold_grid],
                                   ['Numeric Binning ', numeric_bin_grid],
                                   ['Remove Multicollinearity ', remove_multicollinearity],
                                   ['Multicollinearity Threshold ', multicollinearity_threshold_grid],
                                   ['Group Features ', group_features_grid],
                                 ], columns = ['Description', 'Value'] )
  
      functions_ = functions.style.apply(highlight_max)
      
      progress.value += 1
      
      if verbose:
          if html_param:
              clear_output()
              print('Setup Succesfully Completed!')
              display(functions_)
          else:
              print(functions_.data)
           
      if profile:
          try:
              import pandas_profiling
              pf = pandas_profiling.ProfileReport(data_before_preprocess)
              clear_output()
              display(pf)
          except:
              print('Data Profiler Failed. No output to show, please continue with Modeling.')
  
      '''
      Final display Ends
      '''   
      
      #log into experiment
      if verbose:
          experiment__.append(('Anomaly Setup Config', functions))
          experiment__.append(('Orignal Dataset', data_))
          experiment__.append(('Transformed Dataset', X))
          experiment__.append(('Transformation Pipeline', prep_pipe))
      
      
      #end runtime
      runtime_end = time.time()
      runtime = np.array(runtime_end - runtime_start).round(2)
  
      if logging_param:
          
          logger.info("Logging experiment in MLFlow")
  
          import mlflow
          from pathlib import Path
          import os
  
          if experiment_name is None:
              exp_name_ = 'ano-default-name'
          else:
              exp_name_ = experiment_name
  
          URI = secrets.token_hex(nbytes=4)    
          exp_name_log = exp_name_
          
          try:
              mlflow.create_experiment(exp_name_log)
          except:
              pass
  
          #mlflow logging
          mlflow.set_experiment(exp_name_log)
  
          run_name_ = 'Session Initialized ' + str(USI)
          with mlflow.start_run(run_name=run_name_) as run:
  
              # Get active run to log as tag
              RunID = mlflow.active_run().info.run_id
              
              k = functions.copy()
              k.set_index('Description',drop=True,inplace=True)
              kdict = k.to_dict()
              params = kdict.get('Value')
              mlflow.log_params(params)
  
              #set tag of compare_models
              mlflow.set_tag("Source", "setup")
              
              import secrets
              URI = secrets.token_hex(nbytes=4)
              mlflow.set_tag("URI", URI)
              mlflow.set_tag("USI", USI) 
              mlflow.set_tag("Run Time", runtime)
              mlflow.set_tag("Run ID", RunID)
  
              # Log the transformation pipeline
              logger.info("SubProcess save_model() called ==================================")
              save_model(prep_pipe, 'Transformation Pipeline', verbose=False)
              logger.info("SubProcess save_model() end ==================================")
              mlflow.log_artifact('Transformation Pipeline' + '.pkl')
              os.remove('Transformation Pipeline.pkl')
  
              # Log pandas profile
              if log_profile:
                  import pandas_profiling
                  pf = pandas_profiling.ProfileReport(data_before_preprocess)
                  pf.to_file("Data Profile.html")
                  mlflow.log_artifact("Data Profile.html")
                  os.remove("Data Profile.html")
                  clear_output()
                  display(functions_)
  
              # Log training and testing set
              if log_data:
                  data_before_preprocess.to_csv('data.csv')
                  mlflow.log_artifact('data.csv')
                  os.remove('data.csv')
  
      logger.info(str(prep_pipe))
      logger.info("setup() succesfully completed......................................")
  
      return X, data_, seed, prep_pipe, prep_param, experiment__,\
          n_jobs_param, html_param, exp_name_log, logging_param, log_plots_param, USI
  
  def create_model(model = None, 
                   fraction = 0.05,
                   verbose = True,
                   system=True, #added in pycaret==2.0.0
                   **kwargs): #added in pycaret==2.0.0
      
      """
      This function creates a model on the dataset passed as a data param during 
      the setup stage. setup() function must be called before using create_model().
  
      This function returns a trained model object. 
      
      Example
      -------
      >>> from pycaret.datasets import get_data
      >>> anomaly = get_data('anomaly')
      >>> experiment_name = setup(data = anomaly, normalize = True)
      >>> knn = create_model('knn')
  
      This will return trained k-Nearest Neighbors model.
  
      Parameters
      ----------
      model : string / object, default = None
          Enter ID of the models available in model library or pass an untrained model 
          object consistent with fit / predict API to train and evaluate model. List of 
          models available in model library (ID - Model):
  
          * 'abod' - Angle-base Outlier Detection       
          * 'cluster' - Clustering-Based Local Outlier            
          * 'cof' - Connectivity-Based Outlier Factor                 
          * 'histogram' - Histogram-based Outlier Detection          
          * 'knn' - k-Nearest Neighbors Detector                       
          * 'lof' - Local Outlier Factor                            
          * 'svm' - One-class SVM detector                          
          * 'pca' - Principal Component Analysis                    
          * 'mcd' - Minimum Covariance Determinant                    
          * 'sod' - Subspace Outlier Detection                       
          * 'sos' - Stochastic Outlier Selection                       
  
      fraction: float, default = 0.05
          The percentage / proportion of outliers in the dataset.
  
      verbose: Boolean, default = True
          Status update is not printed when verbose is set to False.
  
      system: Boolean, default = True
          Must remain True all times. Only to be changed by internal functions.
  
      **kwargs: 
          Additional keyword arguments to pass to the estimator.
  
      Returns
      -------
      model
          Trained model object.
  
      """
      
      import logging
      
      try:
          hasattr(logger, 'name')
      except:
          logger = logging.getLogger('logs')
          logger.setLevel(logging.DEBUG)
          
          # create console handler and set level to debug
          if logger.hasHandlers():
              logger.handlers.clear()
          
          ch = logging.FileHandler('logs.log')
          ch.setLevel(logging.DEBUG)
  
          # create formatter
          formatter = logging.Formatter('%(asctime)s:%(levelname)s:%(message)s')
  
          # add formatter to ch
          ch.setFormatter(formatter)
  
          # add ch to logger
          logger.addHandler(ch)
  
      try:
          logger.info("Initializing create_model()")
          logger.info("""create_model(model={}, fraction={}, verbose={}, system={})""".\
          format(str(model), str(fraction), str(verbose), str(system)))
  
          logger.info("Checking exceptions")
      
      except:
          logger = logging.getLogger('logs')
          logger.setLevel(logging.DEBUG)
          
          # create console handler and set level to debug
          if logger.hasHandlers():
              logger.handlers.clear()
          
          ch = logging.FileHandler('logs.log')
          ch.setLevel(logging.DEBUG)
  
          # create formatter
          formatter = logging.Formatter('%(asctime)s:%(levelname)s:%(message)s')
  
          # add formatter to ch
          ch.setFormatter(formatter)
  
          # add ch to logger
          logger.addHandler(ch)
  
          logger.info("Initializing create_model()")
          logger.info("Checking exceptions")
  
      #exception checking   
      import sys        
  
      #run_time
      import datetime, time
      runtime_start = time.time()
  
      #ignore warings
      import warnings
      warnings.filterwarnings('ignore') 
      
      """
      error handling starts here
      """
      
      #checking for model parameter
      if model is None:
          sys.exit('(Value Error): Model parameter Missing. Please see docstring for list of available models.')
          
      #checking for allowed models
      allowed_models = ['abod', 'iforest', 'cluster', 'cof', 'histogram', 'knn', 'lof', 'svm', 'pca', 'mcd', 'sod', 'sos']
      
      if model not in allowed_models:
          sys.exit('(Value Error): Model Not Available. Please see docstring for list of available models.')
          
      #checking fraction type:
      if fraction <= 0 or fraction >= 1:
          sys.exit('(Type Error): Fraction parameter can only take value as float between 0 to 1.')
          
      #checking verbose parameter
      if type(verbose) is not bool:
          sys.exit('(Type Error): Verbose parameter can only take argument as True or False.') 
          
      """
      error handling ends here
      """
      
      logger.info("Preloading libraries")
  
      #pre-load libraries
      import pandas as pd
      import numpy as np
      import ipywidgets as ipw
      from IPython.display import display, HTML, clear_output, update_display
      import datetime, time
      
      """
      monitor starts
      """
      
      logger.info("Preparing display monitor")
  
      #progress bar and monitor control    
      timestampStr = datetime.datetime.now().strftime("%H:%M:%S")
      progress = ipw.IntProgress(value=0, min=0, max=4, step=1 , description='Processing: ')
      monitor = pd.DataFrame( [ ['Initiated' , '. . . . . . . . . . . . . . . . . .', timestampStr ], 
                                ['Status' , '. . . . . . . . . . . . . . . . . .' , 'Initializing'] ],
                                columns=['', ' ', '  ']).set_index('')
      if verbose:
          if html_param:
              display(progress)
              display(monitor, display_id = 'monitor')
          
      progress.value += 1
      
      """
      monitor ends
      """
      
      #monitor update
      monitor.iloc[1,1:] = 'Importing the Model'
      if verbose:
          if html_param:
              update_display(monitor, display_id = 'monitor')
      
      progress.value += 1
      
      #create model
      logger.info("Importing untrained model")
  
      if model == 'abod':
          from pyod.models.abod import ABOD
          model = ABOD(contamination=fraction, **kwargs)
          full_name = 'Angle-base Outlier Detection'
          
      elif model == 'cluster':
          from pyod.models.cblof import CBLOF
          try:
              model = CBLOF(contamination=fraction, n_clusters=8, random_state=seed, **kwargs)
              model.fit(X)
          except:
              try:
                  model = CBLOF(contamination=fraction, n_clusters=12, random_state=seed, **kwargs)
                  model.fit(X)
              except:
                  sys.exit("(Type Error) Could not form valid cluster separation")
                  
          full_name = 'Clustering-Based Local Outlier'
          
      elif model == 'cof':
          from pyod.models.cof import COF
          model = COF(contamination=fraction, **kwargs)        
          full_name = 'Connectivity-Based Outlier Factor'
          
      elif model == 'iforest':
          from pyod.models.iforest import IForest
          model = IForest(contamination=fraction, behaviour = 'new', random_state=seed, **kwargs)    
          full_name = 'Isolation Forest'
          
      elif model == 'histogram':
          from pyod.models.hbos import HBOS
          model = HBOS(contamination=fraction, **kwargs) 
          full_name = 'Histogram-based Outlier Detection'
          
      elif model == 'knn':
          from pyod.models.knn import KNN
          model = KNN(contamination=fraction, **kwargs)  
          full_name = 'k-Nearest Neighbors Detector'
          
      elif model == 'lof':
          from pyod.models.lof import LOF
          model = LOF(contamination=fraction, **kwargs)
          full_name = 'Local Outlier Factor'
          
      elif model == 'svm':
          from pyod.models.ocsvm import OCSVM
          model = OCSVM(contamination=fraction, **kwargs)
          full_name = 'One-class SVM detector'
          
      elif model == 'pca':
          from pyod.models.pca import PCA
          model = PCA(contamination=fraction, random_state=seed, **kwargs)  
          full_name = 'Principal Component Analysis'
          
      elif model == 'mcd':
          from pyod.models.mcd import MCD
          model = MCD(contamination=fraction, random_state=seed, **kwargs)
          full_name = 'Minimum Covariance Determinant'
          
      elif model == 'sod':
          from pyod.models.sod import SOD
          model = SOD(contamination=fraction, **kwargs)         
          full_name = 'Subspace Outlier Detection'
          
      elif model == 'sos':
          from pyod.models.sos import SOS
          model = SOS(contamination=fraction, **kwargs)   
          full_name = 'Stochastic Outlier Selection'
      
      else:    
          def get_model_name(e):
              return str(e).split("(")[0]
  
          model == model
          full_name = get_model_name(model)
          
      logger.info(str(full_name) + ' Imported succesfully')
  
      #monitor update
      monitor.iloc[1,1:] = 'Fitting the Model'
      progress.value += 1
      if verbose:
          if html_param:
              update_display(monitor, display_id = 'monitor')
          
      #fitting the model
      model_fit_start = time.time()
      logger.info("Fitting Model")
      model.fit(X)
      model_fit_end = time.time()
  
      model_fit_time = np.array(model_fit_end - model_fit_start).round(2)
      
      #end runtime
      runtime_end = time.time()
      runtime = np.array(runtime_end - runtime_start).round(2)
  
      #mlflow logging
      if logging_param and system:
  
          logger.info("Creating MLFlow logs")
  
          #Creating Logs message monitor
          monitor.iloc[1,1:] = 'Creating Logs'
          if verbose:
              if html_param:
                  update_display(monitor, display_id = 'monitor')
  
          #import mlflow
          import mlflow
          from pathlib import Path
          import os
  
          mlflow.set_experiment(exp_name_log)
  
          with mlflow.start_run(run_name=full_name) as run:
  
              # Get active run to log as tag
              RunID = mlflow.active_run().info.run_id
  
              # Log model parameters
              params = model.get_params()
  
              for i in list(params):
                  v = params.get(i)
                  if len(str(v)) > 250:
                      params.pop(i)
  
              mlflow.log_params(params)
              
              #set tag of compare_models
              mlflow.set_tag("Source", "create_model")
              
              import secrets
              URI = secrets.token_hex(nbytes=4)
              mlflow.set_tag("URI", URI)   
              mlflow.set_tag("USI", USI)
              mlflow.set_tag("Run Time", runtime)
              mlflow.set_tag("Run ID", RunID)
  
              # Log training time in seconds
              mlflow.log_metric("TT", model_fit_time)
  
              # Log AUC and Confusion Matrix plot
              if log_plots_param:
  
                  logger.info("SubProcess plot_model() called ==================================")
  
                  try:
                      plot_model(model, plot = 'tsne', save=True, system=False)
                      mlflow.log_artifact('TSNE.html')
                      os.remove("TSNE.html")
                  except:
                      pass
                      
                  logger.info("SubProcess plot_model() end ==================================")
  
              # Log model and transformation pipeline
              from copy import deepcopy
  
              # get default conda env
              from mlflow.sklearn import get_default_conda_env
              default_conda_env = get_default_conda_env()
              default_conda_env['name'] = str(exp_name_log) + '-env'
              default_conda_env.get('dependencies').pop(-3)
              dependencies = default_conda_env.get('dependencies')[-1]
              from pycaret.utils import __version__
              dep = 'pycaret==' + str(__version__())
              dependencies['pip'] = [dep]
              
              # define model signature
              from mlflow.models.signature import infer_signature
              signature = infer_signature(data_)
              input_example = data_.iloc[0].to_dict()
  
              # log model as sklearn flavor
              prep_pipe_temp = deepcopy(prep_pipe)
              prep_pipe_temp.steps.append(['trained model', model])
              mlflow.sklearn.log_model(prep_pipe_temp, "model", conda_env = default_conda_env, signature = signature, input_example = input_example)
              del(prep_pipe_temp)
  
      progress.value += 1
      
      if verbose:
          clear_output()
  
      logger.info(str(model))
      logger.info("create_models() succesfully completed......................................")
  
      return model
  
  def assign_model(model,
                   transformation=False,
                   score=True,
                   verbose=True):
      
      """
      This function flags each of the data point in the dataset passed during setup
      stage as either outlier or inlier (1 = outlier, 0 = inlier) using trained model
      object passed as model param. create_model() function must be called before using 
      assign_model().
      
      This function returns dataframe with Outlier flag (1 = outlier, 0 = inlier) and 
      decision score, when score is set to True.
  
      Example
      -------
      >>> from pycaret.datasets import get_data
      >>> anomaly = get_data('anomaly')
      >>> experiment_name = setup(data = anomaly, normalize = True)
      >>> knn = create_model('knn')
      >>> knn_df = assign_model(knn)
  
      This will return a dataframe with inferred outliers using trained model.
  
      Parameters
      ----------
      model : trained model object, default = None
      
      transformation: bool, default = False
          When set to True, assigned outliers are returned on transformed dataset instead 
          of original dataset passed during setup().
      
      score: Boolean, default = True
          The outlier scores of the training data. The higher, the more abnormal. 
          Outliers tend to have higher scores. This value is available once the model 
          is fitted. If set to False, it will only return the flag (1 = outlier, 0 = inlier).
  
      verbose: Boolean, default = True
          Status update is not printed when verbose is set to False.
  
      Returns
      -------
      pandas.DataFrame
          Returns a dataframe with inferred outliers using a trained model.
    
      """
      
      #exception checking   
      import sys
      
      import logging
  
      try:
          hasattr(logger, 'name')
      except:
          logger = logging.getLogger('logs')
          logger.setLevel(logging.DEBUG)
          
          # create console handler and set level to debug
          if logger.hasHandlers():
              logger.handlers.clear()
          
          ch = logging.FileHandler('logs.log')
          ch.setLevel(logging.DEBUG)
  
          # create formatter
          formatter = logging.Formatter('%(asctime)s:%(levelname)s:%(message)s')
  
          # add formatter to ch
          ch.setFormatter(formatter)
  
          # add ch to logger
          logger.addHandler(ch)
          
      logger.info("Initializing assign_model()")
      logger.info("""assign_model(model={}, transformation={}, score={}, verbose={})""".\
          format(str(model), str(transformation), str(score), str(verbose)))
  
      #ignore warnings
      import warnings
      warnings.filterwarnings('ignore') 
      
      
      """
      error handling starts here
      """
      
      #determine model type and store in string
      mod_type = str(type(model))
      
      #checking for allowed models
      allowed_type = ['pyod']
      if 'pyod' not in mod_type:
          sys.exit('(Value Error): Model Not Recognized. Please see docstring for list of available models.')
      
      #checking transformation parameter
      if type(transformation) is not bool:
          sys.exit('(Type Error): Transformation parameter can only take argument as True or False.')   
          
      #checking verbose parameter
      if type(score) is not bool:
          sys.exit('(Type Error): Score parameter can only take argument as True or False.')    
          
      #checking verbose parameter
      if type(verbose) is not bool:
          sys.exit('(Type Error): Verbose parameter can only take argument as True or False.')     
      
      
      """
      error handling ends here
      """
      
      logger.info("Preloading libraries")
      #pre-load libraries
      import numpy as np
      import pandas as pd
      import ipywidgets as ipw
      from IPython.display import display, HTML, clear_output, update_display
      import datetime, time
      
      logger.info("Copying data")
      #copy data_
      if transformation:
          data__ = X.copy()
      else:
          data__ = data_.copy()
      
      logger.info("Preparing display monitor")
      #progress bar and monitor control 
      timestampStr = datetime.datetime.now().strftime("%H:%M:%S")
      progress = ipw.IntProgress(value=0, min=0, max=3, step=1 , description='Processing: ')
      monitor = pd.DataFrame( [ ['Initiated' , '. . . . . . . . . . . . . . . . . .', timestampStr ], 
                                ['Status' , '. . . . . . . . . . . . . . . . . .' , 'Initializing'] ],
                                columns=['', ' ', '  ']).set_index('')
      if verbose:
          if html_param:
              display(progress)
              display(monitor, display_id = 'monitor')
          
      progress.value += 1
      
      monitor.iloc[1,1:] = 'Inferring Outliers from Model'
      
      if verbose:
          if html_param:
              update_display(monitor, display_id = 'monitor')
      
      progress.value += 1
      
      #calculation labels and attaching to dataframe
      pred_labels = model.labels_
      data__['Label'] = pred_labels
      
      progress.value += 1
      
      #calculating score and attaching to dataframe
      if score:
          pred_score = model.decision_scores_
          data__['Score'] = pred_score
      
      progress.value += 1
  
      logger.info("Determining Trained Model")
  
      mod_type = str(model).split("(")[0]
      
      if 'ABOD' in mod_type:
          name_ = 'Angle-base Outlier Detection' 
          
      elif 'IForest' in mod_type:
          name_ = 'Isolation Forest'
          
      elif 'CBLOF' in mod_type:
          name_ = 'Clustering-Based Local Outlier'        
          
      elif 'COF' in mod_type:
          name_ = 'Connectivity-Based Outlier Factor'
          
      elif 'HBOS' in mod_type:
          name_ = 'Histogram-based Outlier Detection'
          
      elif 'KNN' in mod_type:
          name_ = 'k-Nearest Neighbors Detector'
          
      elif 'LOF' in mod_type:
          name_ = 'Local Outlier Factor'
          
      elif 'OCSVM' in mod_type:
          name_ = 'One-class SVM detector'
          
      elif 'PCA' in mod_type:
          name_ = 'Principal Component Analysis'
      
      elif 'MCD' in mod_type:
          name_ = 'Minimum Covariance Determinant'
          
      elif 'SOD' in mod_type:
          name_ = 'Subspace Outlier Detection'
          
      elif 'SOS' in mod_type:
          name_ = 'Stochastic Outlier Selection'
          
      else:
          name_ = 'Unknown Anomaly Detector'
          
      name_ = 'Assigned ' + str(name_)
  
      logger.info("Trained Model : " + str(name_))
      
      if verbose:
          clear_output()
  
      logger.info(str(data__.shape))
      logger.info("assign_model() succesfully completed......................................")
  
      return data__
  
  def plot_model(model,
                 plot = 'tsne',
                 feature = None,
                 scale = 1, #added in pycaret 2.1.0
                 save = False, #added in pycaret 2.0.0
                 system = True): #added in pycaret 2.0.0
                 
      
      """
      This function takes a trained model object and returns a plot on the dataset 
      passed during setup stage. This function internally calls assign_model before 
      generating a plot.  
  
      Example
      -------
      >>> from pycaret.datasets import get_data
      >>> anomaly = get_data('anomaly')
      >>> experiment_name = setup(data = anomaly, normalize = True)
      >>> knn = create_model('knn')
      >>> plot_model(knn)
  
      Parameters
      ----------
      model: object
          A trained model object can be passed. Model must be created using create_model().
  
      plot: string, default = 'tsne'
          Enter abbreviation of type of plot. The current list of plots supported are (Plot - Name):
  
          * 'tsne' - t-SNE (3d) Dimension Plot
          * 'umap' - UMAP Dimensionality Plot
  
      feature: string, default = None
          Feature column is used as a hoverover tooltip. By default, first of column of the
          dataset is chosen as hoverover tooltip, when no feature is passed.
      
      scale: float, default = 1
          The resolution scale of the figure.
  
      save: Boolean, default = False
          Plot is saved as png file in local directory when save parameter set to True.
  
      system: Boolean, default = True
          Must remain True all times. Only to be changed by internal functions.
  
      Returns
      -------
      Visual_Plot
          Prints the visual plot. 
  
      """  
      
      #exception checking   
      import sys
      
      import logging
  
      try:
          hasattr(logger, 'name')
      except:
          logger = logging.getLogger('logs')
          logger.setLevel(logging.DEBUG)
          
          # create console handler and set level to debug
          if logger.hasHandlers():
              logger.handlers.clear()
          
          ch = logging.FileHandler('logs.log')
          ch.setLevel(logging.DEBUG)
  
          # create formatter
          formatter = logging.Formatter('%(asctime)s:%(levelname)s:%(message)s')
  
          # add formatter to ch
          ch.setFormatter(formatter)
  
          # add ch to logger
          logger.addHandler(ch)
  
      logger.info("Initializing plot_model()")
      logger.info("""plot_model(model={}, plot={}, feature={}, save={}, system={})""".\
          format(str(model), str(plot), str(feature), str(save), str(system)))
  
      #ignore warnings
      import warnings
      warnings.filterwarnings('ignore') 
          
      """
      exception handling starts here
      """
      
      logger.info("Checking exceptions")
  
      #plot checking
      allowed_plots = ['tsne', 'umap']  
      if plot not in allowed_plots:
          sys.exit('(Value Error): Plot Not Available. Please see docstring for list of available plots.')
       
  
      
      """
      error handling ends here
      """
      
      logger.info("Importing libraries")
      #import dependencies
      import pandas as pd
      import numpy
      
      #import cufflinks
      import cufflinks as cf
      cf.go_offline()
      cf.set_config_file(offline=False, world_readable=True)
  
      logger.info("plot type: " + str(plot))
  
      if plot == 'tsne':
          
          logger.info("SubProcess assign_model() called ==================================")
          b = assign_model(model, verbose=False, transformation=True, score=False)
          logger.info("SubProcess assign_model() end ==================================")
          Label = pd.DataFrame(b['Label'])
          b.dropna(axis=0, inplace=True) #droping rows with NA's
          b.drop(['Label'], axis=1, inplace=True)
          
          logger.info("Getting dummies to cast categorical variables")
          b = pd.get_dummies(b) #casting categorical variables
  
          from sklearn.manifold import TSNE
          logger.info("Fitting TSNE()")
          X_embedded = TSNE(n_components=3).fit_transform(b)
  
          X = pd.DataFrame(X_embedded)
          X['Label'] = Label
          
          if feature is not None: 
              X['Feature'] = data_[feature]
          else:
              X['Feature'] = data_[data_.columns[0]]
  
          import plotly.express as px
          df = X
  
          logger.info("Rendering Visual")
  
          fig = px.scatter_3d(df, x=0, y=1, z=2, hover_data=['Feature'], color='Label', title='3d TSNE Plot for Outliers', 
                                  opacity=0.7, width=900*scale, height=800*scale)
              
              
          if system:
              fig.show()
  
          logger.info("Visual Rendered Successfully")
  
          if save:
              fig.write_html("TSNE.html")
              logger.info("Saving 'TSNE.html' in current active directory") 
  
      elif plot == 'umap':
          
          logger.info("SubProcess assign_model() called ==================================")
          b = assign_model(model, verbose=False, transformation=True, score=False)
          logger.info("SubProcess assign_model() end ==================================")
  
          Label = pd.DataFrame(b['Label'])
          b.dropna(axis=0, inplace=True) #droping rows with NA's
          b.drop(['Label'], axis=1, inplace=True)
          
          logger.info("Getting dummies to cast categorical variables")
          b = pd.get_dummies(b) #casting categorical variables
          
          import umap
          reducer = umap.UMAP()
          logger.info("Fitting UMAP()")
          embedding = reducer.fit_transform(b)
          X = pd.DataFrame(embedding)
  
          import plotly.express as px
          df = X
          df['Label'] = Label
          
          if feature is not None: 
              df['Feature'] = data_[feature]
          else:
              df['Feature'] = data_[data_.columns[0]]
          
          logger.info("Rendering Visual")
  
          fig = px.scatter(df, x=0, y=1,
                        color='Label', title='uMAP Plot for Outliers', hover_data=['Feature'], opacity=0.7, 
                           width=900*scale, height=800*scale)
          if system:
              fig.show() 
  
          logger.info("Visual Rendered Successfully")
          
          if save:
              fig.write_html("UMAP.html")
              logger.info("Saving 'UMAP.html' in current active directory") 
      
      logger.info("plot_model() succesfully completed......................................")
  
  def tune_model(model=None,
                 supervised_target=None,
                 method='drop',
                 estimator=None,
                 optimize=None,
                 custom_grid = None, #added in pycaret 2.0.0
                 fold=10,
                 verbose=True): #added in pycaret 2.0.0
      
      
      """
      This function tunes the fraction parameter using a predefined grid with
      the objective of optimizing a supervised learning metric as defined in 
      the optimize param. You can choose the supervised estimator from a large 
      library available in pycaret. By default, supervised estimator is Linear. 
      
      This function returns the tuned model object.
      
      Example
      -------
      >>> from pycaret.datasets import get_data
      >>> boston = get_data('boston')
      >>> experiment_name = setup(data = boston, normalize = True)
      >>> tuned_knn = tune_model(model = 'knn', supervised_target = 'medv') 
      
      This will return tuned k-Nearest Neighbors model.
  
      Parameters
      ----------
      model : string, default = None
          Enter ID of the models available in model library (ID - Model):
  
          * 'abod' - Angle-base Outlier Detection       
          * 'cluster' - Clustering-Based Local Outlier            
          * 'cof' - Connectivity-Based Outlier Factor                 
          * 'histogram' - Histogram-based Outlier Detection          
          * 'knn' - k-Nearest Neighbors Detector                       
          * 'lof' - Local Outlier Factor                            
          * 'svm' - One-class SVM detector                          
          * 'pca' - Principal Component Analysis                    
          * 'mcd' - Minimum Covariance Determinant                    
          * 'sod' - Subspace Outlier Detection                       
          * 'sos' - Stochastic Outlier Selection    
      
      supervised_target: string
          Name of the target column for supervised learning.
      
      method: string, default = 'drop'
          When method set to drop, it will drop the outlier rows from training dataset 
          of supervised estimator, when method set to 'surrogate', it will use the
          decision function and label as a feature without dropping the outliers from
          training dataset.
      
      estimator: string, default = None
          For Classification (ID - Name):
  
          * 'lr' - Logistic Regression             
          * 'knn' - K Nearest Neighbour             
          * 'nb' - Naive Bayes                                 
          * 'dt' - Decision Tree Classifier                           
          * 'svm' - SVM - Linear Kernel             	            
          * 'rbfsvm' - SVM - Radial Kernel                            
          * 'gpc' - Gaussian Process Classifier                       
          * 'mlp' - Multi Level Perceptron                            
          * 'ridge' - Ridge Classifier                
          * 'rf' - Random Forest Classifier                           
          * 'qda' - Quadratic Discriminant Analysis                   
          * 'ada' - Ada Boost Classifier                             
          * 'gbc' - Gradient Boosting Classifier                              
          * 'lda' - Linear Discriminant Analysis                      
          * 'et' - Extra Trees Classifier                             
          * 'xgboost' - Extreme Gradient Boosting                     
          * 'lightgbm' - Light Gradient Boosting                       
          * 'catboost' - CatBoost Classifier             
  
          For Regression (ID - Name):
  
          * 'lr' - Linear Regression                                  
          * 'lasso' - Lasso Regression              
          * 'ridge' - Ridge Regression              
          * 'en' - Elastic Net                   
          * 'lar' - Least Angle Regression                
          * 'llar' - Lasso Least Angle Regression                     
          * 'omp' - Orthogonal Matching Pursuit                        
          * 'br' - Bayesian Ridge                                   
          * 'ard' - Automatic Relevance Determ.                     
          * 'par' - Passive Aggressive Regressor                      
          * 'ransac' - Random Sample Consensus              
          * 'tr' - TheilSen Regressor                               
          * 'huber' - Huber Regressor                                              
          * 'kr' - Kernel Ridge                                                       
          * 'svm' - Support Vector Machine                                   
          * 'knn' - K Neighbors Regressor                                    
          * 'dt' - Decision Tree                                                     
          * 'rf' - Random Forest                                                     
          * 'et' - Extra Trees Regressor                                     
          * 'ada' - AdaBoost Regressor                                               
          * 'gbr' - Gradient Boosting                                            
          * 'mlp' - Multi Level Perceptron                                  
          * 'xgboost' - Extreme Gradient Boosting                                   
          * 'lightgbm' - Light Gradient Boosting                           
          * 'catboost' - CatBoost Regressor                 
          
          If set to None, Linear model is used by default for both classification
          and regression tasks.
      
      optimize: string, default = None
          For Classification tasks:
              Accuracy, AUC, Recall, Precision, F1, Kappa
          
          For Regression tasks:
              MAE, MSE, RMSE, R2, RMSLE, MAPE
          
          If set to None, default is 'Accuracy' for classification and 'R2' for 
          regression tasks.
      
      custom_grid: list, default = None
          By default, a pre-defined list of fraction values is iterated over to 
          optimize the supervised objective. To overwrite default iteration,
          pass a list of fraction value to iterate over in custom_grid param.
      
      fold: integer, default = 10
          Number of folds to be used in Kfold CV. Must be at least 2. 
  
      verbose: Boolean, default = True
          Status update is not printed when verbose is set to False.
  
      Returns
      -------
      Visual_Plot
          Visual plot with fraction param on x-axis with metric to
          optimize on y-axis. Also, prints the best model metric.
      
      model
          trained model object with best fraction param. 
            
      """
      
      
      
      """
      exception handling starts here
      """
      
      global data_, X
      
      import logging
  
      try:
          hasattr(logger, 'name')
      except:
          logger = logging.getLogger('logs')
          logger.setLevel(logging.DEBUG)
          
          # create console handler and set level to debug
          if logger.hasHandlers():
              logger.handlers.clear()
          
          ch = logging.FileHandler('logs.log')
          ch.setLevel(logging.DEBUG)
  
          # create formatter
          formatter = logging.Formatter('%(asctime)s:%(levelname)s:%(message)s')
  
          # add formatter to ch
          ch.setFormatter(formatter)
  
          # add ch to logger
          logger.addHandler(ch)
  
      logger.info("Initializing tune_model()")
      logger.info("""tune_model(model={}, supervised_target={}, method={}, estimator={}, optimize={}, custom_grid={}, fold={}, verbose={})""".\
          format(str(model), str(supervised_target), str(method), str(estimator), str(optimize), str(custom_grid), str(fold), str(verbose)))
  
      logger.info("Checking exceptions")
  
      #ignore warnings
      import warnings
      warnings.filterwarnings('ignore') 
      
      import sys
      
      #run_time
      import datetime, time
      runtime_start = time.time()
  
      #checking for model parameter
      if model is None:
          sys.exit('(Value Error): Model parameter Missing. Please see docstring for list of available models.')
          
      #checking for allowed models
      allowed_models = ['abod', 'iforest', 'cluster', 'cof', 'histogram', 'knn', 'lof', 'svm', 'pca', 'mcd', 'sod', 'sos']
      
      if model not in allowed_models:
          sys.exit('(Value Error): Model Not Available for Tuning. Please see docstring for list of available models.')
      
      #check method
      allowed_methods = ['drop', 'surrogate']
      if method not in allowed_methods:
          sys.exit('(Value Error): Method not recognized. See docstring for list of available methods.')
          
      #check if supervised target is None:
      if supervised_target is None:
          sys.exit('(Value Error): supervised_target cannot be None. A column name must be given for estimator.')
      
      #check supervised target
      if supervised_target is not None:
          all_col = list(data_.columns)
          if supervised_target not in all_col:
              sys.exit('(Value Error): supervised_target not recognized. It can only be one of the following: ' + str(all_col))
      
      #checking estimator:
      if estimator is not None:
          
          available_estimators = ['lr', 'knn', 'nb', 'dt', 'svm', 'rbfsvm', 'gpc', 'mlp', 'ridge', 'rf', 'qda', 'ada', 
                              'gbc', 'lda', 'et', 'lasso', 'ridge', 'en', 'lar', 'llar', 'omp', 'br', 'ard', 'par', 
                              'ransac', 'tr', 'huber', 'kr', 'svm', 'knn', 'dt', 'rf', 'et', 'ada', 'gbr', 
                              'mlp', 'xgboost', 'lightgbm', 'catboost']
                  
          if estimator not in available_estimators:
              sys.exit('(Value Error): Estimator Not Available. Please see docstring for list of available estimators.')
      
      
      #checking optimize parameter
      if optimize is not None:
          
          available_optimizers = ['MAE', 'MSE', 'RMSE', 'R2', 'RMSLE', 'MAPE', 'Accuracy', 'AUC', 'Recall', 'Precision', 'F1', 'Kappa']
          
          if optimize not in available_optimizers:
              sys.exit('(Value Error): optimize parameter Not Available. Please see docstring for list of available parameters.')
      
      #checking fold parameter
      if type(fold) is not int:
          sys.exit('(Type Error): Fold parameter only accepts integer value.')
      
      
      """
      exception handling ends here
      """
      
      logger.info("Preloading libraries")
  
      #pre-load libraries
      import pandas as pd
      import ipywidgets as ipw
      from ipywidgets import Output
      from IPython.display import display, HTML, clear_output, update_display
      import datetime, time
      
      logger.info("Preparing display monitor")
  
      #progress bar
      if custom_grid is None:
          max_steps = 25
      else:
          max_steps = 15 + len(custom_grid)
          
      progress = ipw.IntProgress(value=0, min=0, max=max_steps, step=1 , description='Processing: ')
      
      if verbose:
          if html_param:
              display(progress)
      
      timestampStr = datetime.datetime.now().strftime("%H:%M:%S")
      
      monitor = pd.DataFrame( [ ['Initiated' , '. . . . . . . . . . . . . . . . . .', timestampStr ], 
                               ['Status' , '. . . . . . . . . . . . . . . . . .' , 'Loading Dependencies'],
                               ['Step' , '. . . . . . . . . . . . . . . . . .',  'Initializing' ] ],
                                columns=['', ' ', '   ']).set_index('')
      
      monitor_out = Output()
      
      if verbose:
          if html_param:
              display(monitor_out)
              with monitor_out:
                  display(monitor, display_id = 'monitor')
  
      logger.info("Importing libraries")
  
      #General Dependencies
      from sklearn.linear_model import LogisticRegression
      from sklearn.model_selection import cross_val_predict
      from sklearn import metrics
      import numpy as np
      import plotly.express as px
      from copy import deepcopy
      from sklearn.preprocessing import StandardScaler
      scaler = StandardScaler()
      
      logger.info("Copying environment variables")
  
      a = data_.copy()
      b = X.copy()
      c = deepcopy(prep_pipe)
      e = exp_name_log
      z = logging_param
      
      def retain_original(a,b,c,e,z):
          
          global data_, X, prep_pipe, exp_name_log, logging_param
          
          data_ = a.copy()
          X = b.copy()
          prep_pipe = deepcopy(c)
          exp_name_log = e
          logging_param = z
  
          return data_, X, prep_pipe, exp_name_log, logging_param
              
      #setting up cufflinks
      import cufflinks as cf
      cf.go_offline()
      cf.set_config_file(offline=False, world_readable=True)
      
      progress.value += 1 
      
      #define the problem
      if data_[supervised_target].value_counts().count() == 2: 
          problem = 'classification'
          logger.info("Objective : Classification")
      else:
          problem = 'regression'    
          logger.info("Objective : Regression")
      
      #define model name
      
      logger.info("Defining Model Name")    
  
      if model == 'abod':
          model_name = 'Angle-base Outlier Detection'  
      elif model == 'iforest':
          model_name = 'Isolation Forest'        
      elif model == 'cluster':
          model_name = 'Clustering-Based Local Outlier'   
      elif model == 'cof':
          model_name = 'Connectivity-Based Outlier Factor'   
      elif model == 'histogram':
          model_name = 'Histogram-based Outlier Detection'   
      elif model == 'knn':
          model_name = 'k-Nearest Neighbors Detector'   
      elif model == 'lof':
          model_name = 'Local Outlier Factor'   
      elif model == 'svm':
          model_name = 'One-class SVM detector'   
      elif model == 'pca':
          model_name = 'Principal Component Analysis'   
      elif model == 'mcd':
          model_name = 'Minimum Covariance Determinant'   
      elif model == 'sod':
          model_name = 'Subspace Outlier Detection'   
      elif model == 'sos':
          model_name = 'Stochastic Outlier Selection'   
      
      logger.info("Defining Supervised Estimator")
  
      #defining estimator:
      if problem == 'classification' and estimator is None:
          estimator = 'lr'
      elif problem == 'regression' and estimator is None:
          estimator = 'lr'        
      else:
          estimator = estimator
      
      logger.info("Defining Optimizer")
  
      #defining optimizer:
      if optimize is None and problem == 'classification':
          optimize = 'Accuracy'
      elif optimize is None and problem == 'regression':
          optimize = 'R2'
      else:
          optimize=optimize
      
      logger.info("Optimize: " + str(optimize))
  
      progress.value += 1 
              
      #defining tuning grid
      logger.info("Defining Tuning Grid")
  
      if custom_grid is not None:
          
          logger.info("Custom Grid used")
          param_grid = custom_grid
          param_grid_with_zero = [0]
  
          for i in param_grid:
              param_grid_with_zero.append(i)
  
      else:
          
          logger.info("Pre-defined Grid used")
          param_grid_with_zero = [0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.10] 
          param_grid = [0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.10] 
      
      master = []; master_df = []
      
      monitor.iloc[1,1:] = 'Creating Outlier Detection Model'
      if verbose:
          if html_param:
              update_display(monitor, display_id = 'monitor')
      
      """
      preprocess starts here
      """
      
      logger.info("Defining setup variables for preprocessing")
  
      #removing target variable from data by defining new setup
      _data_ = data_.copy()
      target_ = pd.DataFrame(_data_[supervised_target])
      from sklearn.preprocessing import LabelEncoder
      le = LabelEncoder()
      target_ = le.fit_transform(target_)
      
      cat_pass = prep_param.dtypes.categorical_features
      num_pass = prep_param.dtypes.numerical_features
      time_pass = prep_param.dtypes.time_features
      ignore_pass = prep_param.dtypes.features_todrop
      
      #PCA
      if 'Empty' in str(prep_param.pca): 
          pca_pass = False
          pca_method_pass = 'linear'
      
      else:
          pca_pass = True
          
          if prep_param.pca.method == 'pca_liner':
              pca_method_pass = 'linear'
          elif prep_param.pca.method == 'pca_kernal':
              pca_method_pass = 'kernel'
          elif prep_param.pca.method == 'incremental':
              pca_method_pass = 'incremental'
          
      if pca_pass is True:
          pca_comp_pass = prep_param.pca.variance_retained
      else:
          pca_comp_pass = 0.99
      
      #IMPUTATION
      if 'not_available' in prep_param.imputer.categorical_strategy:
          cat_impute_pass = 'constant'
      elif 'most frequent' in prep_param.imputer.categorical_strategy:
          cat_impute_pass = 'mode'
      
      num_impute_pass = prep_param.imputer.numeric_strategy
      
      #NORMALIZE
      if 'Empty' in str(prep_param.scaling):
          normalize_pass = False
      else:
          normalize_pass = True
          
      if normalize_pass is True:
          normalize_method_pass = prep_param.scaling.function_to_apply
      else:
          normalize_method_pass = 'zscore'
      
      #FEATURE TRANSFORMATION
      if 'Empty' in str(prep_param.P_transform):
          transformation_pass = False
      else:
          transformation_pass = True
          
      if transformation_pass is True:
          
          if 'yj' in prep_param.P_transform.function_to_apply:
              transformation_method_pass = 'yeo-johnson'
          elif 'quantile' in prep_param.P_transform.function_to_apply:
              transformation_method_pass = 'quantile'
              
      else:
          transformation_method_pass = 'yeo-johnson'
      
      #BIN NUMERIC FEATURES
      if 'Empty' in str(prep_param.binn):
          features_to_bin_pass = []
          apply_binning_pass = False
          
      else:
          features_to_bin_pass = prep_param.binn.features_to_discretize
          apply_binning_pass = True
      
      #COMBINE RARE LEVELS
      if 'Empty' in str(prep_param.club_R_L):
          combine_rare_levels_pass = False
          combine_rare_threshold_pass = 0.1
      else:
          combine_rare_levels_pass = True
          combine_rare_threshold_pass = prep_param.club_R_L.threshold
          
      #ZERO NERO ZERO VARIANCE
      if 'Empty' in str(prep_param.znz):
          ignore_low_variance_pass = False
      else:
          ignore_low_variance_pass = True
      
      #MULTI-COLLINEARITY
      if 'Empty' in str(prep_param.fix_multi):
          remove_multicollinearity_pass = False
      else:
          remove_multicollinearity_pass = True
          
      if remove_multicollinearity_pass is True:
          multicollinearity_threshold_pass = prep_param.fix_multi.threshold
      else:
          multicollinearity_threshold_pass = 0.9
      
      #UNKNOWN CATEGORICAL LEVEL
      if 'Empty' in str(prep_param.new_levels):
          handle_unknown_categorical_pass = False
      else:
          handle_unknown_categorical_pass = True
          
      if handle_unknown_categorical_pass is True:
          unknown_level_preprocess = prep_param.new_levels.replacement_strategy
          if unknown_level_preprocess == 'least frequent':
              unknown_categorical_method_pass = 'least_frequent'
          elif unknown_level_preprocess == 'most frequent':
              unknown_categorical_method_pass = 'most_frequent'
          else:
              unknown_categorical_method_pass = 'least_frequent'
      else:
          unknown_categorical_method_pass = 'least_frequent'
      
      #GROUP FEATURES
      if 'Empty' in str(prep_param.group):
          apply_grouping_pass = False
      else:
          apply_grouping_pass = True
          
      if apply_grouping_pass is True:
          group_features_pass = prep_param.group.list_of_similar_features
      else:
          group_features_pass = None
          
      if apply_grouping_pass is True:
          group_names_pass = prep_param.group.group_name
      else:
          group_names_pass = None
      
      #ORDINAL FEATURES    
      if 'Empty' in str(prep_param.ordinal):
          ordinal_features_pass = None
      else:
          ordinal_features_pass = prep_param.ordinal.info_as_dict
      
      #HIGH CARDINALITY    
      if 'Empty' in str(prep_param.cardinality):
          high_cardinality_features_pass = None
      else:
          high_cardinality_features_pass = prep_param.cardinality.feature
          
      global setup_without_target
      
      logger.info("SubProcess setup() called")
  
      setup_without_target = setup(data = data_,
                                   categorical_features = cat_pass,
                                   categorical_imputation = cat_impute_pass,
                                   ordinal_features = ordinal_features_pass, #new
                                   high_cardinality_features = high_cardinality_features_pass, #latest
                                   numeric_features = num_pass,
                                   numeric_imputation = num_impute_pass,
                                   date_features = time_pass,
                                   ignore_features = ignore_pass,
                                   normalize = normalize_pass,
                                   normalize_method = normalize_method_pass,
                                   transformation = transformation_pass,
                                   transformation_method = transformation_method_pass,
                                   handle_unknown_categorical = handle_unknown_categorical_pass, 
                                   unknown_categorical_method = unknown_categorical_method_pass, 
                                   pca = pca_pass,
                                   pca_components = pca_comp_pass, 
                                   pca_method = pca_method_pass, 
                                   ignore_low_variance = ignore_low_variance_pass, 
                                   combine_rare_levels = combine_rare_levels_pass, 
                                   rare_level_threshold = combine_rare_threshold_pass,
                                   bin_numeric_features = features_to_bin_pass, 
                                   remove_multicollinearity = remove_multicollinearity_pass, 
                                   multicollinearity_threshold = multicollinearity_threshold_pass, 
                                   group_features = group_features_pass,
                                   group_names = group_names_pass,
                                   supervised = True,
                                   supervised_target = supervised_target,
                                   session_id = seed,
                                   log_experiment = False, #added in pycaret==2.0.0
                                   profile=False,
                                   verbose=False)
      
      data_without_target = setup_without_target[0]
      
      logger.info("SubProcess setup() end")
  
      """
      preprocess ends here
      """
      
      #adding dummy model in master
      master.append('No Model Required')
      master_df.append('No Model Required')
      
      model_fit_time_list = []
  
      for i in param_grid:
          logger.info("Fitting Model with Fraction = " +str(i))
          progress.value += 1                      
          monitor.iloc[2,1:] = 'Fitting Model With ' + str(i) + ' Fraction'
          if verbose:
              if html_param:
                  update_display(monitor, display_id = 'monitor')
                               
          #create and assign the model to dataset d
          model_fit_start = time.time()
          logger.info("SubProcess create_model() called==================================")
          m = create_model(model=model, fraction=i, verbose=False, system=False)
          logger.info("SubProcess create_model() end==================================")
          model_fit_end = time.time()
          model_fit_time = np.array(model_fit_end - model_fit_start).round(2)
          model_fit_time_list.append(model_fit_time)
  
          logger.info("Generating labels")
          logger.info("SubProcess assign_model() called==================================")
          d = assign_model(m, transformation=True, score=True, verbose=False)
          logger.info("SubProcess assign_model() ends==================================")
          d[str(supervised_target)] = target_
  
          master.append(m)
          master_df.append(d)
  
          
      #attaching target variable back
      data_[str(supervised_target)] = target_
  
      logger.info("Defining Supervised Estimator")
      
      if problem == 'classification':
          
          logger.info("Problem : Classification")
  
          """
          
          defining estimator
          
          """
          
          monitor.iloc[1,1:] = 'Evaluating Anomaly Model'
          if verbose:
              if html_param:
                  update_display(monitor, display_id = 'monitor')
                               
          if estimator == 'lr':
  
              from sklearn.linear_model import LogisticRegression
              model = LogisticRegression(random_state=seed)
              full_name = 'Logistic Regression'
  
          elif estimator == 'knn':
  
              from sklearn.neighbors import KNeighborsClassifier
              model = KNeighborsClassifier()
              full_name = 'K Nearest Neighbours'
  
          elif estimator == 'nb':
  
              from sklearn.naive_bayes import GaussianNB
              model = GaussianNB()
              full_name = 'Naive Bayes'
  
          elif estimator == 'dt':
  
              from sklearn.tree import DecisionTreeClassifier
              model = DecisionTreeClassifier(random_state=seed)
              full_name = 'Decision Tree'
  
          elif estimator == 'svm':
  
              from sklearn.linear_model import SGDClassifier
              model = SGDClassifier(max_iter=1000, tol=0.001, random_state=seed)
              full_name = 'Support Vector Machine'
  
          elif estimator == 'rbfsvm':
  
              from sklearn.svm import SVC
              model = SVC(gamma='auto', C=1, probability=True, kernel='rbf', random_state=seed)
              full_name = 'RBF SVM'
  
          elif estimator == 'gpc':
  
              from sklearn.gaussian_process import GaussianProcessClassifier
              model = GaussianProcessClassifier(random_state=seed)
              full_name = 'Gaussian Process Classifier'
  
          elif estimator == 'mlp':
  
              from sklearn.neural_network import MLPClassifier
              model = MLPClassifier(max_iter=500, random_state=seed)
              full_name = 'Multi Level Perceptron'    
  
          elif estimator == 'ridge':
  
              from sklearn.linear_model import RidgeClassifier
              model = RidgeClassifier(random_state=seed)
              full_name = 'Ridge Classifier'        
  
          elif estimator == 'rf':
  
              from sklearn.ensemble import RandomForestClassifier
              model = RandomForestClassifier(n_estimators=10, random_state=seed)
              full_name = 'Random Forest Classifier'    
  
          elif estimator == 'qda':
  
              from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis
              model = QuadraticDiscriminantAnalysis()
              full_name = 'Quadratic Discriminant Analysis' 
  
          elif estimator == 'ada':
  
              from sklearn.ensemble import AdaBoostClassifier
              model = AdaBoostClassifier(random_state=seed)
              full_name = 'AdaBoost Classifier'        
  
          elif estimator == 'gbc':
  
              from sklearn.ensemble import GradientBoostingClassifier    
              model = GradientBoostingClassifier(random_state=seed)
              full_name = 'Gradient Boosting Classifier'    
  
          elif estimator == 'lda':
  
              from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
              model = LinearDiscriminantAnalysis()
              full_name = 'Linear Discriminant Analysis'
  
          elif estimator == 'et':
  
              from sklearn.ensemble import ExtraTreesClassifier 
              model = ExtraTreesClassifier(random_state=seed)
              full_name = 'Extra Trees Classifier'
              
          elif estimator == 'xgboost':
              
              from xgboost import XGBClassifier
              model = XGBClassifier(random_state=seed, n_jobs=-1, verbosity=0)
              full_name = 'Extreme Gradient Boosting'
              
          elif estimator == 'lightgbm':
              
              import lightgbm as lgb
              model = lgb.LGBMClassifier(random_state=seed)
              full_name = 'Light Gradient Boosting Machine'
              
          elif estimator == 'catboost':
              from catboost import CatBoostClassifier
              model = CatBoostClassifier(random_state=seed, silent=True) # Silent is True to suppress CatBoost iteration results 
              full_name = 'CatBoost Classifier'
          
          logger.info(str(full_name) + " Imported Successfully")
  
          progress.value += 1 
          
          """
          start model building here
  
          """
                               
          logger.info("Creating Classifier without Anomaly")         
          acc = [];  auc = []; recall = []; prec = []; kappa = []; f1 = []
          
          #build model without anomaly
          monitor.iloc[2,1:] = 'Evaluating Classifier Without Anomaly Detector'
          if verbose:
              if html_param:
                  update_display(monitor, display_id = 'monitor')   
  
          d = master_df[1].copy()
          d.drop(['Label', 'Score'], axis=1, inplace=True)
  
          #drop NA's caution
          d.dropna(axis=0, inplace=True)
          
          #get_dummies to caste categorical variables for supervised learning 
          d = pd.get_dummies(d)
  
          #split the dataset
          X = d.drop(supervised_target, axis=1)
          y = d[supervised_target]
  
          #fit the model
          logger.info("Fitting Model") 
          model.fit(X,y)
          
          #generate the prediction and evaluate metric
          logger.info("Evaluating Cross Val Predictions") 
          pred = cross_val_predict(model,X,y,cv=fold, method = 'predict')
  
          acc_ = metrics.accuracy_score(y,pred)
          acc.append(acc_)
  
          recall_ = metrics.recall_score(y,pred)
          recall.append(recall_)
  
          precision_ = metrics.precision_score(y,pred)
          prec.append(precision_)
  
          kappa_ = metrics.cohen_kappa_score(y,pred)
          kappa.append(kappa_)
  
          f1_ = metrics.f1_score(y,pred)
          f1.append(f1_)
          
          if hasattr(model,'predict_proba'):
              pred_ = cross_val_predict(model,X,y,cv=fold, method = 'predict_proba')
              pred_prob = pred_[:,1]
              auc_ = metrics.roc_auc_score(y,pred_prob)
              auc.append(auc_)
  
          else:
              auc.append(0)
  
          for i in range(1,len(master_df)):
              progress.value += 1 
              param_grid_val = param_grid[i-1]
              
              logger.info("Creating Classifier with Fraction = " + str(param_grid_val))
  
              monitor.iloc[2,1:] = 'Evaluating Classifier With ' + str(param_grid_val) + ' Fraction'
              if verbose:
                  if html_param:
                      update_display(monitor, display_id = 'monitor')                
                               
              #prepare the dataset for supervised problem
              d = master_df[i]
              
              #cleaning the dataframe for supervised learning
              d.dropna(axis=0, inplace=True)
              Score_ = pd.DataFrame(d['Score'])
              Score = scaler.fit_transform(Score_)
              d['Score'] = Score
  
              if method == 'drop':
                  d = d[d['Label'] == 0]
                  d.drop(['Label'], axis=1, inplace=True)
                  
              #get_dummies to caste categorical variables for supervised learning 
              d = pd.get_dummies(d)
  
              #split the dataset
              X = d.drop(supervised_target, axis=1)
              y = d[supervised_target]
  
              #fit the model
              logger.info("Fitting Model") 
              model.fit(X,y)
  
              #generate the prediction and evaluate metric
              logger.info("Generating Cross Val Predictions") 
              pred = cross_val_predict(model,X,y,cv=fold, method = 'predict')
  
              acc_ = metrics.accuracy_score(y,pred)
              acc.append(acc_)
  
              recall_ = metrics.recall_score(y,pred)
              recall.append(recall_)
  
              precision_ = metrics.precision_score(y,pred)
              prec.append(precision_)
  
              kappa_ = metrics.cohen_kappa_score(y,pred)
              kappa.append(kappa_)
  
              f1_ = metrics.f1_score(y,pred)
              f1.append(f1_)
  
              if hasattr(model,'predict_proba'):
                  pred_ = cross_val_predict(model,X,y,cv=fold, method = 'predict_proba')
                  pred_prob = pred_[:,1]
                  auc_ = metrics.roc_auc_score(y,pred_prob)
                  auc.append(auc_)
  
              else:
                  auc.append(0)
  
                               
          monitor.iloc[1,1:] = 'Compiling Results'
          monitor.iloc[1,1:] = 'Finalizing'
          
          if verbose:
              if html_param:
                  update_display(monitor, display_id = 'monitor')
  
          logger.info("Creating metrics dataframe")                        
          df = pd.DataFrame({'Fraction %': param_grid_with_zero, 'Accuracy' : acc, 'AUC' : auc, 'Recall' : recall, 
                     'Precision' : prec, 'F1' : f1, 'Kappa' : kappa})
          
          sorted_df = df.sort_values(by=optimize, ascending=False)
          ival = sorted_df.index[0]
  
          best_model = master[ival]
          best_model_df = master_df[ival]
          best_model_tt = model_fit_time_list[ival]
  
          progress.value += 1 
          logger.info("Rendering Visual")
          sd = pd.melt(df, id_vars=['Fraction %'], value_vars=['Accuracy', 'AUC', 'Recall', 'Precision', 'F1', 'Kappa'], 
                       var_name='Metric', value_name='Score')
  
          fig = px.line(sd, x='Fraction %', y='Score', color='Metric', line_shape='linear', range_y = [0,1])
          fig.update_layout(plot_bgcolor='rgb(245,245,245)')
          title= str(full_name) + ' Metrics and Fraction %'
          fig.update_layout(title={'text': title, 'y':0.95,'x':0.45,'xanchor': 'center','yanchor': 'top'})
          
          fig.show()
          logger.info("Visual Rendered Successfully")
          
          #monitor = ''
          
          if verbose:
              if html_param:
                  update_display(monitor, display_id = 'monitor')
          
          if verbose:
              if html_param:
                  monitor_out.clear_output()
                  progress.close()
          
          best_k = np.array(sorted_df.head(1)['Fraction %'])[0]
          best_m = round(np.array(sorted_df.head(1)[optimize])[0],4)
          p = 'Best Model: ' + model_name + ' |' + ' Fraction %: ' + str(best_k) + ' | ' + str(optimize) + ' : ' + str(best_m)
          print(p)
  
      elif problem == 'regression':
          
          logger.info("Problem : Regression")
  
          """
          
          defining estimator
          
          """
          
          monitor.iloc[1,1:] = 'Evaluating Anomaly Model'
          if verbose:
              if html_param:
                  update_display(monitor, display_id = 'monitor')
                                      
          if estimator == 'lr':
          
              from sklearn.linear_model import LinearRegression
              model = LinearRegression()
              full_name = 'Linear Regression'
          
          elif estimator == 'lasso':
  
              from sklearn.linear_model import Lasso
              model = Lasso(random_state=seed)
              full_name = 'Lasso Regression'
  
          elif estimator == 'ridge':
  
              from sklearn.linear_model import Ridge
              model = Ridge(random_state=seed)
              full_name = 'Ridge Regression'
  
          elif estimator == 'en':
  
              from sklearn.linear_model import ElasticNet
              model = ElasticNet(random_state=seed)
              full_name = 'Elastic Net'
  
          elif estimator == 'lar':
  
              from sklearn.linear_model import Lars
              model = Lars()
              full_name = 'Least Angle Regression'
  
          elif estimator == 'llar':
  
              from sklearn.linear_model import LassoLars
              model = LassoLars()
              full_name = 'Lasso Least Angle Regression'
  
          elif estimator == 'omp':
  
              from sklearn.linear_model import OrthogonalMatchingPursuit
              model = OrthogonalMatchingPursuit()
              full_name = 'Orthogonal Matching Pursuit'
  
          elif estimator == 'br':
              from sklearn.linear_model import BayesianRidge
              model = BayesianRidge()
              full_name = 'Bayesian Ridge Regression' 
  
          elif estimator == 'ard':
  
              from sklearn.linear_model import ARDRegression
              model = ARDRegression()
              full_name = 'Automatic Relevance Determination'        
  
          elif estimator == 'par':
  
              from sklearn.linear_model import PassiveAggressiveRegressor
              model = PassiveAggressiveRegressor(random_state=seed)
              full_name = 'Passive Aggressive Regressor'    
  
          elif estimator == 'ransac':
  
              from sklearn.linear_model import RANSACRegressor
              model = RANSACRegressor(random_state=seed)
              full_name = 'Random Sample Consensus'   
  
          elif estimator == 'tr':
  
              from sklearn.linear_model import TheilSenRegressor
              model = TheilSenRegressor(random_state=seed)
              full_name = 'TheilSen Regressor'     
  
          elif estimator == 'huber':
  
              from sklearn.linear_model import HuberRegressor
              model = HuberRegressor()
              full_name = 'Huber Regressor'   
  
          elif estimator == 'kr':
  
              from sklearn.kernel_ridge import KernelRidge
              model = KernelRidge()
              full_name = 'Kernel Ridge'
  
          elif estimator == 'svm':
  
              from sklearn.svm import SVR
              model = SVR()
              full_name = 'Support Vector Regression'  
  
          elif estimator == 'knn':
  
              from sklearn.neighbors import KNeighborsRegressor
              model = KNeighborsRegressor()
              full_name = 'Nearest Neighbors Regression' 
  
          elif estimator == 'dt':
  
              from sklearn.tree import DecisionTreeRegressor
              model = DecisionTreeRegressor(random_state=seed)
              full_name = 'Decision Tree Regressor'
  
          elif estimator == 'rf':
  
              from sklearn.ensemble import RandomForestRegressor
              model = RandomForestRegressor(random_state=seed)
              full_name = 'Random Forest Regressor'
  
          elif estimator == 'et':
  
              from sklearn.ensemble import ExtraTreesRegressor
              model = ExtraTreesRegressor(random_state=seed)
              full_name = 'Extra Trees Regressor'    
  
          elif estimator == 'ada':
  
              from sklearn.ensemble import AdaBoostRegressor
              model = AdaBoostRegressor(random_state=seed)
              full_name = 'AdaBoost Regressor'   
  
          elif estimator == 'gbr':
  
              from sklearn.ensemble import GradientBoostingRegressor
              model = GradientBoostingRegressor(random_state=seed)
              full_name = 'Gradient Boosting Regressor'       
  
          elif estimator == 'mlp':
  
              from sklearn.neural_network import MLPRegressor
              model = MLPRegressor(random_state=seed)
              full_name = 'MLP Regressor'
              
          elif estimator == 'xgboost':
              
              from xgboost import XGBRegressor
              model = XGBRegressor(random_state=seed, n_jobs=-1, verbosity=0)
              full_name = 'Extreme Gradient Boosting Regressor'
              
          elif estimator == 'lightgbm':
              
              import lightgbm as lgb
              model = lgb.LGBMRegressor(random_state=seed)
              full_name = 'Light Gradient Boosting Machine'
              
          elif estimator == 'catboost':
              
              from catboost import CatBoostRegressor
              model = CatBoostRegressor(random_state=seed, silent = True)
              full_name = 'CatBoost Regressor'
              
          logger.info(str(full_name) + " Imported Successfully")
  
          progress.value += 1 
          
          """
          start model building here
  
          """
          
          logger.info("Creating Regressor without anomaly")
  
          score = []
          metric = []
          
          #build model without anomaly
          monitor.iloc[2,1:] = 'Evaluating Regressor Without Anomaly Detector'
          if verbose:
              if html_param:
                  update_display(monitor, display_id = 'monitor')   
  
          d = master_df[1].copy()
          d.drop(['Label', 'Score'], axis=1, inplace=True)
  
          #drop NA's caution
          d.dropna(axis=0, inplace=True)
          
          #get_dummies to caste categorical variables for supervised learning 
          d = pd.get_dummies(d)
          
          #split the dataset
          X = d.drop(supervised_target, axis=1)
          y = d[supervised_target]
              
          #fit the model
          logger.info("Fitting Model") 
          model.fit(X,y)
  
          #generate the prediction and evaluate metric
          logger.info("Generating Cross Val Predictions")
          pred = cross_val_predict(model,X,y,cv=fold, method = 'predict')
  
          if optimize == 'R2':
              r2_ = metrics.r2_score(y,pred)
              score.append(r2_)
  
          elif optimize == 'MAE':          
              mae_ = metrics.mean_absolute_error(y,pred)
              score.append(mae_)
  
          elif optimize == 'MSE':
              mse_ = metrics.mean_squared_error(y,pred)
              score.append(mse_)
  
          elif optimize == 'RMSE':
              mse_ = metrics.mean_squared_error(y,pred)        
              rmse_ = np.sqrt(mse_)
              score.append(rmse_)
  
          elif optimize == 'RMSLE':
              rmsle = np.sqrt(np.mean(np.power(np.log(np.array(abs(pred))+1) - np.log(np.array(abs(y))+1), 2)))
              score.append(rmsle)
              
          elif optimize == 'MAPE':
              
              def calculate_mape(actual, prediction):
                  mask = actual != 0
                  return (np.fabs(actual - prediction)/actual)[mask].mean()
              
              mape = calculate_mape(y,pred)
              score.append(mape)      
              
          metric.append(str(optimize))
          
          for i in range(1,len(master_df)):
              progress.value += 1 
              param_grid_val = param_grid[i-1]
              
              logger.info("Creating Regressor with Fraction = " + str(param_grid_val)) 
  
              monitor.iloc[2,1:] = 'Evaluating Regressor With ' + str(param_grid_val) + ' Fraction'
              if verbose:
                  if html_param:
                      update_display(monitor, display_id = 'monitor')    
                               
              #prepare the dataset for supervised problem
              d = master_df[i]
                      
              #cleaning the dataframe for supervised learning
              d.dropna(axis=0, inplace=True)
              Score_ = pd.DataFrame(d['Score'])
              Score = scaler.fit_transform(Score_)
              d['Score'] = Score
              
              if method == 'drop':
                  d = d[d['Label'] == 0]
                  d.drop(['Label'], axis=1, inplace=True)
              
              #get_dummies to caste categorical variable for supervised learning
              d = pd.get_dummies(d)
                          
              #split the dataset
              X = d.drop(supervised_target, axis=1)
              y = d[supervised_target]
  
              #fit the model
              logger.info("Fitting Model") 
              model.fit(X,y)
  
              #generate the prediction and evaluate metric
              logger.info("Generating Cross Val Predictions") 
              pred = cross_val_predict(model,X,y,cv=fold, method = 'predict')
  
              if optimize == 'R2':
                  r2_ = metrics.r2_score(y,pred)
                  score.append(r2_)
                  
              elif optimize == 'MAE':          
                  mae_ = metrics.mean_absolute_error(y,pred)
                  score.append(mae_)
  
              elif optimize == 'MSE':
                  mse_ = metrics.mean_squared_error(y,pred)
                  score.append(mse_)
                  
              elif optimize == 'RMSE':
                  mse_ = metrics.mean_squared_error(y,pred)        
                  rmse_ = np.sqrt(mse_)
                  score.append(rmse_)
              
              elif optimize == 'RMSLE':
                  rmsle = np.sqrt(np.mean(np.power(np.log(np.array(abs(pred))+1) - np.log(np.array(abs(y))+1), 2)))
                  score.append(rmsle)
  
              elif optimize == 'MAPE':
  
                  def calculate_mape(actual, prediction):
                      mask = actual != 0
                      return (np.fabs(actual - prediction)/actual)[mask].mean()
                  
                  mape = calculate_mape(y,pred)
                  score.append(mape)
                  
              metric.append(str(optimize))
          
          monitor.iloc[1,1:] = 'Compiling Results'
          monitor.iloc[1,1:] = 'Finalizing'
          if verbose:
              if html_param:
                  update_display(monitor, display_id = 'monitor')                    
  
          logger.info("Creating metrics dataframe")  
          df = pd.DataFrame({'Fraction': param_grid_with_zero, 'Score' : score, 'Metric': metric})
          df.columns = ['Fraction %', optimize, 'Metric']
          
          #sorting to return best model
          if optimize == 'R2':
              sorted_df = df.sort_values(by=optimize, ascending=False)
          else: 
              sorted_df = df.sort_values(by=optimize, ascending=True)
              
          ival = sorted_df.index[0]
  
          best_model = master[ival]
          best_model_df = master_df[ival]
          best_model_tt = model_fit_time_list[ival]
  
          logger.info("Rendering Visual")
  
          fig = px.line(df, x='Fraction %', y=optimize, line_shape='linear', 
                        title= str(full_name) + ' Metrics and Fraction %', color='Metric')
  
          fig.update_layout(plot_bgcolor='rgb(245,245,245)')
          progress.value += 1 
  
          fig.show()
          
          logger.info("Visual Rendered Successfully")
  
          #monitor = ''
          
          if verbose:
              if html_param:
                  update_display(monitor, display_id = 'monitor')
          monitor_out.clear_output()
          progress.close()
          
          best_k = np.array(sorted_df.head(1)['Fraction %'])[0]
          best_m = round(np.array(sorted_df.head(1)[optimize])[0],4)
          p = 'Best Model: ' + model_name + ' |' + ' Fraction %: ' + str(best_k) + ' | ' + str(optimize) + ' : ' + str(best_m)
          print(p)
          
      logger.info("Resetting environment to original variables")
      org = retain_original(a,b,c,e,z)
  
      #end runtime
      runtime_end = time.time()
      runtime = np.array(runtime_end - runtime_start).round(2)
  
      #mlflow logging
      if logging_param:
          
          logger.info("Creating MLFlow logs")
  
          #import mlflow
          import mlflow
          from pathlib import Path
          import os
  
          mlflow.set_experiment(exp_name_log)
  
          #Creating Logs message monitor
          monitor.iloc[1,1:] = 'Creating Logs'
          if verbose:
              if html_param:
                  update_display(monitor, display_id = 'monitor')
  
          mlflow.set_experiment(exp_name_log)
  
          with mlflow.start_run(run_name=model_name) as run:
  
              # Get active run to log as tag
              RunID = mlflow.active_run().info.run_id
  
              # Log model parameters
  
              try:   
                  params = best_model.get_params()
  
                  for i in list(params):
                      v = params.get(i)
                      if len(str(v)) > 250:
                          params.pop(i)
  
                  mlflow.log_params(params)
              except:
                  pass
              
              #set tag of compare_models
              mlflow.set_tag("Source", "tune_model")
              
              import secrets
              URI = secrets.token_hex(nbytes=4)
              mlflow.set_tag("URI", URI)   
              mlflow.set_tag("USI", USI)
              mlflow.set_tag("Run Time", runtime)
              mlflow.set_tag("Run ID", RunID)
  
              # Log training time in seconds
              mlflow.log_metric("TT", best_model_tt) #change this
  
              # Log plot to html
              fig.write_html("Iterations.html")
              mlflow.log_artifact('Iterations.html')
              os.remove('Iterations.html')
  
              # Log model and transformation pipeline
              from copy import deepcopy
              
              # get default conda env
              from mlflow.sklearn import get_default_conda_env
              default_conda_env = get_default_conda_env()
              default_conda_env['name'] = str(exp_name_log) + '-env'
              default_conda_env.get('dependencies').pop(-3)
              dependencies = default_conda_env.get('dependencies')[-1]
              from pycaret.utils import __version__
              dep = 'pycaret==' + str(__version__())
              dependencies['pip'] = [dep]
              
              # define model signature
              from mlflow.models.signature import infer_signature
              signature = infer_signature(data_)
              input_example = data_.iloc[0].to_dict()
  
              # log model as sklearn flavor
              prep_pipe_temp = deepcopy(prep_pipe)
              prep_pipe_temp.steps.append(['trained model', model])
              mlflow.sklearn.log_model(prep_pipe_temp, "model", conda_env = default_conda_env, signature = signature, input_example = input_example)
              del(prep_pipe_temp)
      
      logger.info(str(best_model))
      logger.info("tune_model() succesfully completed......................................")
  
      return best_model
  
  def save_model(model, model_name, model_only=False, verbose=True):
      
      """
      This function saves the transformation pipeline and trained model object 
      into the current active directory as a pickle file for later use. 
      
      Example
      -------
      >>> from pycaret.datasets import get_data
      >>> anomaly = get_data('anomaly')
      >>> experiment_name = setup(data = anomaly, normalize = True)
      >>> knn = create_model('knn')  
      >>> save_model(knn, 'knn_model_23122019')
      
      This will save the transformation pipeline and model as a binary pickle
      file in the current directory. 
  
      Parameters
      ----------
      model : object, default = none
          A trained model object should be passed.
      
      model_name : string, default = none
          Name of pickle file to be passed as a string.
  
      model_only : bool, default = False
          When set to True, only trained model object is saved and all the 
          transformations are ignored.
  
      verbose : bool, default = True
          When set to False, success message is not printed.
  
      Returns
      -------
      Success_Message
         
           
      """
      
      import logging
      from copy import deepcopy
  
      try:
          hasattr(logger, 'name')
      except:
          logger = logging.getLogger('logs')
          logger.setLevel(logging.DEBUG)
          
          # create console handler and set level to debug
          if logger.hasHandlers():
              logger.handlers.clear()
          
          ch = logging.FileHandler('logs.log')
          ch.setLevel(logging.DEBUG)
  
          # create formatter
          formatter = logging.Formatter('%(asctime)s:%(levelname)s:%(message)s')
  
          # add formatter to ch
          ch.setFormatter(formatter)
  
          # add ch to logger
          logger.addHandler(ch)
  
      logger.info("Initializing save_model()")
      logger.info("""save_model(model={}, model_name={}, model_only={}, verbose={})""".\
          format(str(model), str(model_name), str(model_only), str(verbose)))
  
      #ignore warnings
      import warnings
      warnings.filterwarnings('ignore') 
      
      logger.info("Adding model into prep_pipe")
  
      if model_only:
          model_ = deepcopy(model)
          logger.warning("Only Model saved. Transformations in prep_pipe are ignored.")
      else:
          model_ = deepcopy(prep_pipe)
          model_.steps.append(['trained model',model]) 
      
      import joblib
      model_name = model_name + '.pkl'
      joblib.dump(model_, model_name)
      if verbose:
          print('Transformation Pipeline and Model Succesfully Saved')
  
      logger.info(str(model_name) + ' saved in current working directory')
      logger.info(str(model_))
      logger.info("save_model() succesfully completed......................................")
  
  def load_model(model_name, 
                 platform = None, 
                 authentication = None,
                 verbose=True):
      
      """
      This function loads a previously saved transformation pipeline and model 
      from the current active directory into the current python environment. 
      Load object must be a pickle file.
      
      Example
      -------
      >>> saved_lr = load_model('knn_model_23122019')
      
      This will load the previously saved model in saved_lr variable. The file 
      must be in the current directory.
  
      Parameters
      ----------
      model_name : string, default = none
          Name of pickle file to be passed as a string.
        
      platform: string, default = None
          Name of platform, if loading model from cloud. 
          Currently available options are: 'aws', 'gcp', 'azure'.
      
      authentication : dict
          dictionary of applicable authentication tokens.
  
          When platform = 'aws':
          {'bucket' : 'name of bucket'}
  
          When platform = 'gcp':
          {'project': 'name of project', 'bucket' : 'name of bucket'}
  
          When platform = 'azure':
          {'container': 'name of container'}
      
      verbose: Boolean, default = True
          Success message is not printed when verbose is set to False.
  
      Returns
      -------
      Model Object
  
      """
      
      #ignore warnings
      import warnings
      warnings.filterwarnings('ignore') 
      
      #exception checking
      import sys
      
      if platform is not None:
          if authentication is None:
              sys.exit("(Value Error): Authentication is missing.")
  
      if platform is None:
  
          import joblib
          model_name = model_name + '.pkl'
          if verbose:
              print('Transformation Pipeline and Model Successfully Loaded')
          return joblib.load(model_name)
      
      # cloud providers
      elif platform == 'aws':
  
          import boto3
          bucketname = authentication.get('bucket')
          filename = str(model_name) + '.pkl'
          s3 = boto3.resource('s3')
          s3.Bucket(bucketname).download_file(filename, filename)
          filename = str(model_name)
          model = load_model(filename, verbose=False)
          model = load_model(filename, verbose=False)
  
          if verbose:
              print('Transformation Pipeline and Model Successfully Loaded')
  
          return model
  
      elif platform == 'gcp':
  
          bucket_name = authentication.get('bucket')
          project_name = authentication.get('project')
          filename = str(model_name) + '.pkl'
  
          model_downloaded = _download_blob_gcp(project_name,
                                                bucket_name, filename, filename)
  
          model = load_model(model_name, verbose=False)
  
          if verbose:
              print('Transformation Pipeline and Model Successfully Loaded')
          return model
  
      elif platform == 'azure':
  
          container_name = authentication.get('container')
          filename = str(model_name) + '.pkl'
  
          model_downloaded = _download_blob_azure(container_name, filename, filename)
  
          model = load_model(model_name, verbose=False)
  
          if verbose:
              print('Transformation Pipeline and Model Successfully Loaded')
          return model
      else:
          print('Platform { } is not supported by pycaret or illegal option'.format(platform))
  
  def predict_model(model, 
                    data):
      
      """
      This function is used to predict new data using a trained model. It requires a
      trained model object created using one of the function in pycaret that returns 
      a trained model object. New data must be passed to data param as pandas Dataframe. 
      
      Example
      -------
      >>> from pycaret.datasets import get_data
      >>> anomaly = get_data('anomaly')
      >>> experiment_name = setup(data = anomaly)
      >>> knn = create_model('knn')
      >>> knn_predictions = predict_model(model = knn, data = anomaly)
          
      Parameters
      ----------
      model : object / string,  default = None
          When model is passed as string, load_model() is called internally to load the
          pickle file from active directory or cloud platform when platform param is passed.
      
      data : pandas.DataFrame
          Shape (n_samples, n_features) where n_samples is the number of samples and n_features is the number of features.
          All features used during training must be present in the new dataset.
       
      Returns
      -------
      info_grid
          Information grid is printed when data is None.             
      
      """
      
      #ignore warnings
      import warnings
      warnings.filterwarnings('ignore') 
      
      #testing
      #no active tests
      
      #general dependencies
      from IPython.display import clear_output, update_display
      import numpy as np
      import pandas as pd
      import re
      from sklearn import metrics
      from copy import deepcopy
      import sys
      
      #copy data and model
      data__ = data.copy()
  
      #exception checking for predict param
      if hasattr(model, 'predict'):
          pass
      else:
          sys.exit("(Type Error): Model doesn't support predict parameter.")
      
      #predictions start here
      if 'Pipeline' in str(type(model)):
          pred = model.predict(data)
          pred_score = model.decision_function(data)
      else:
          _data_ = prep_pipe.transform(data__)
          pred = model.predict(_data_)
          pred_score = model.decision_function(_data_)        
          
      data__['Label'] = pred
      data__['Score'] = pred_score
      
      return data__
  
  def deploy_model(model, 
                   model_name, 
                   platform,
                   authentication):
      
      """
      This function deploys the transformation pipeline and trained model object for
      production use. The platform of deployment can be defined under the platform
      param along with the applicable authentication tokens which are passed as a
      dictionary to the authentication param.
          
      Platform: AWS
      -------------
      Before deploying a model to an AWS S3 ('aws'), environment variables must be 
      configured using the command line interface. To configure AWS env. variables, 
      type aws configure in your python command line. The following information is
      required which can be generated using the Identity and Access Management (IAM) 
      portal of your AWS console account:
  
      - AWS Access Key ID
      - AWS Secret Key Access
      - Default Region Name (can be seen under Global settings on your AWS console)
      - Default output format (must be left blank)
  
      >>> from pycaret.datasets import get_data
      >>> anomaly = get_data('anomaly')
      >>> experiment_name = setup(data = anomaly)
      >>> knn = create_model('knn')
      >>> deploy_model(model = knn, model_name = 'deploy_knn', platform = 'aws', authentication = {'bucket' : 'bucket-name'})
  
      Platform: GCP
      --------------
      Before deploying a model to Google Cloud Platform (GCP), project must be created either
      using command line or GCP console. Once project is created, you must create a service 
      account and download the service account key as a JSON file, which is then used to 
      set environment variable. 
  
      Learn more : https://cloud.google.com/docs/authentication/production
  
      >>> from pycaret.datasets import get_data
      >>> anomaly = get_data('anomaly')
      >>> experiment_name = setup(data = anomaly)
      >>> knn = create_model('knn')
      >>> os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = 'c:/path-to-json-file.json' 
      >>> deploy_model(model = knn, model_name = 'deploy_knn', platform = 'gcp', authentication = {'project' : 'project-name', 'bucket' : 'bucket-name'})
  
      Platform: Azure
      ---------------
      Before deploying a model to Microsoft Azure, environment variables for connection 
      string must be set. Connection string can be obtained from 'Access Keys' of your 
      storage account in Azure.
  
      >>> from pycaret.datasets import get_data
      >>> anomaly = get_data('anomaly')
      >>> experiment_name = setup(data = anomaly)
      >>> knn = create_model('knn')
      >>> os.environ['AZURE_STORAGE_CONNECTION_STRING'] = 'connection-string-here' 
      >>> deploy_model(model = knn, model_name = 'deploy_knn', platform = 'azure', authentication = {'container' : 'container-name'})
  
      Parameters
      ----------
      model : object
          A trained model object should be passed as an estimator. 
      
      model_name : string
          Name of model to be passed as a string.
      
      platform: string
          Name of platform for deployment. 
          Currently accepts: 'aws', 'gcp', 'azure'
  
      authentication : dict
          Dictionary of applicable authentication tokens.
  
          When platform = 'aws':
          {'bucket' : 'name of bucket'}
  
          When platform = 'gcp':
          {'project': 'name of project', 'bucket' : 'name of bucket'}
  
          When platform = 'azure':
          {'container': 'name of container'}
  
      Returns
      -------
      Success_Message
      
      Warnings
      --------
      - This function uses file storage services to deploy the model on cloud platform. 
        As such, this is efficient for batch-use. Where the production objective is to 
        obtain prediction at an instance level, this may not be the efficient choice as 
        it transmits the binary pickle file between your local python environment and
        the platform. 
      
      """
      
      import sys
      import logging
  
      try:
          hasattr(logger, 'name')
      except:
          logger = logging.getLogger('logs')
          logger.setLevel(logging.DEBUG)
          
          # create console handler and set level to debug
          if logger.hasHandlers():
              logger.handlers.clear()
          
          ch = logging.FileHandler('logs.log')
          ch.setLevel(logging.DEBUG)
  
          # create formatter
          formatter = logging.Formatter('%(asctime)s:%(levelname)s:%(message)s')
  
          # add formatter to ch
          ch.setFormatter(formatter)
  
          # add ch to logger
          logger.addHandler(ch)
  
      logger.info("Initializing deploy_model()")
      logger.info("""deploy_model(model={}, model_name={}, authentication={}, platform={})""".\
          format(str(model), str(model_name), str(authentication), str(platform)))
  
      #ignore warnings
      import warnings
      warnings.filterwarnings('ignore') 
      
      #general dependencies
      import ipywidgets as ipw
      import pandas as pd
      from IPython.display import clear_output, update_display
      import os
  
      if platform == 'aws':
          
          logger.info("Platform : AWS S3")
  
          #checking if awscli available
          try:
              import awscli
          except:
              logger.error("awscli library not found. pip install awscli to use deploy_model function.")
              sys.exit("awscli library not found. pip install awscli to use deploy_model function.")  
          
          import boto3
          
          logger.info("Saving model in active working directory")
          logger.info("SubProcess save_model() called ==================================")
          save_model(model, model_name = model_name, verbose=False)
          logger.info("SubProcess save_model() end ==================================")
          
          #initiaze s3
          logger.info("Initializing S3 client")
          s3 = boto3.client('s3')
          filename = str(model_name)+'.pkl'
          key = str(model_name)+'.pkl'
          bucket_name = authentication.get('bucket')
          s3.upload_file(filename,bucket_name,key)
          clear_output()
          
          os.remove(filename)
          
          print("Model Succesfully Deployed on AWS S3")
          logger.info("Model Succesfully Deployed on AWS S3")
          logger.info(str(model))
  
      elif platform == 'gcp':
  
          logger.info("Platform : GCP")
  
          try:
              import google.cloud
          except:
              logger.error("google-cloud-storage library not found. pip install google-cloud-storage to use deploy_model function with GCP.")
              sys.exit("google-cloud-storage library not found. pip install google-cloud-storage to use deploy_model function with GCP.")
  
          logger.info("Saving model in active working directory")
          logger.info("SubProcess save_model() called ==================================")
          save_model(model, model_name=model_name, verbose=False)
          logger.info("SubProcess save_model() end ==================================")
  
          # initialize deployment
          filename = str(model_name) + '.pkl'
          key = str(model_name) + '.pkl'
          bucket_name = authentication.get('bucket')
          project_name = authentication.get('project')
          try:
              _create_bucket_gcp(project_name, bucket_name)
              _upload_blob_gcp(project_name, bucket_name, filename, key)
          except:
              _upload_blob_gcp(project_name, bucket_name, filename, key)
          
          os.remove(filename)
          
          print("Model Succesfully Deployed on GCP")
          logger.info("Model Succesfully Deployed on GCP")
          logger.info(str(model))
  
      elif platform == 'azure':
  
          try:
              import azure.storage.blob
          except:
              logger.error("azure-storage-blob library not found. pip install azure-storage-blob to use deploy_model function with Azure.")
              sys.exit("azure-storage-blob library not found. pip install azure-storage-blob to use deploy_model function with Azure.")
  
          logger.info("Platform : Azure Blob Storage")
  
          logger.info("Saving model in active working directory")
          logger.info("SubProcess save_model() called ==================================")
          save_model(model, model_name=model_name, verbose=False)
          logger.info("SubProcess save_model() end ==================================")
  
          # initialize deployment
          filename = str(model_name) + '.pkl'
          key = str(model_name) + '.pkl'
          container_name = authentication.get('container')
          try:
              container_client = _create_container_azure(container_name)
              _upload_blob_azure(container_name, filename, key)
              del(container_client)
          except:
              _upload_blob_azure(container_name, filename, key)
  
          os.remove(filename)
  
          print("Model Succesfully Deployed on Azure Storage Blob")
          logger.info("Model Succesfully Deployed on Azure Storage Blob")
          logger.info(str(model))
  
      else:
          logger.error('Platform {} is not supported by pycaret or illegal option'.format(platform))
          sys.exit('Platform {} is not supported by pycaret or illegal option'.format(platform))
          
      logger.info("deploy_model() succesfully completed......................................")
  
  def models():
  
      """
      Returns table of models available in model library.
  
      Example
      -------
      >>> all_models = models()
  
      This will return pandas dataframe with all available 
      models and their metadata.     
      
      Returns
      -------
      pandas.DataFrame
  
      """
  
      import pandas as pd
  
      model_id = ['abod', 'iforest', 'cluster', 'cof', 'histogram', 'knn', 'lof', 'svm', 'pca', 'mcd', 'sod', 'sos']
  
      model_name = ['Angle-base Outlier Detection',
                    'Isolation Forest',
                    'Clustering-Based Local Outlier',
                    'Connectivity-Based Outlier Factor',
                    'Histogram-based Outlier Detection',
                    'k-Nearest Neighbors Detector',
                    'Local Outlier Factor',
                    'One-class SVM detector',
                    'Principal Component Analysis',
                    'Minimum Covariance Determinant',
                    'Subspace Outlier Detection',
                    'Stochastic Outlier Selection']
  
      model_ref = ['pyod.models.abod.ABOD',
                    'pyod.models.iforest',
                    'pyod.models.cblof',
                    'pyod.models.cof',
                    'pyod.models.hbos',
                    'pyod.models.knn',
                    'pyod.models.lof',
                    'pyod.models.ocsvm',
                    'pyod.models.pca',
                    'pyod.models.mcd',
                    'pyod.models.sod',
                    'pyod.models.sos']
  
      df = pd.DataFrame({'ID' : model_id, 
                          'Name' : model_name,
                          'Reference' : model_ref})
  
      df.set_index('ID', inplace=True)
  
      return df
  
  def get_logs(experiment_name = None, save = False):
  
      """
      Returns a table with experiment logs consisting
      run details, parameter, metrics and tags. 
  
      Example
      -------
      >>> logs = get_logs()
  
      This will return pandas dataframe.
  
      Parameters
      ----------
      experiment_name : string, default = None
          When set to None current active run is used.
  
      save : bool, default = False
          When set to True, csv file is saved in current directory.
        
      Returns
      -------
      pandas.DataFrame
  
      """
  
      import sys
  
      if experiment_name is None:
          exp_name_log_ = exp_name_log
      else:
          exp_name_log_ = experiment_name
  
      import mlflow
      from mlflow.tracking import MlflowClient
      
      client = MlflowClient()
  
      if client.get_experiment_by_name(exp_name_log_) is None:
          sys.exit('No active run found. Check logging parameter in setup or to get logs for inactive run pass experiment_name.')
      
      exp_id = client.get_experiment_by_name(exp_name_log_).experiment_id    
      runs = mlflow.search_runs(exp_id)
  
      if save:
          file_name = str(exp_name_log_) + '_logs.csv'
          runs.to_csv(file_name, index=False)
  
      return runs
  
  def get_config(variable):
  
      """
      This function is used to access global environment variables.
      Following variables can be accessed:
  
      - X: Transformed dataset
      - data_: Original dataset  
      - seed: random state set through session_id
      - prep_pipe: Transformation pipeline configured through setup
      - prep_param: prep_param configured through setup
      - n_jobs_param: n_jobs parameter used in model training
      - html_param: html_param configured through setup
      - exp_name_log: Name of experiment set through setup
      - logging_param: log_experiment param set through setup
      - log_plots_param: log_plots param set through setup
      - USI: Unique session ID parameter set through setup
  
      Example
      -------
      >>> X = get_config('X') 
  
      This will return transformed dataset.
  
      Returns
      -------
      variable
        
      """
  
      import logging
  
      try:
          hasattr(logger, 'name')
      except:
          logger = logging.getLogger('logs')
          logger.setLevel(logging.DEBUG)
          
          # create console handler and set level to debug
          if logger.hasHandlers():
              logger.handlers.clear()
          
          ch = logging.FileHandler('logs.log')
          ch.setLevel(logging.DEBUG)
  
          # create formatter
          formatter = logging.Formatter('%(asctime)s:%(levelname)s:%(message)s')
  
          # add formatter to ch
          ch.setFormatter(formatter)
  
          # add ch to logger
          logger.addHandler(ch)
  
      logger.info("Initializing get_config()")
      logger.info("""get_config(variable={})""".\
          format(str(variable)))
  
      if variable == 'X':
          global_var = X
      
      if variable == 'data_':
          global_var = data_
  
      if variable == 'seed':
          global_var = seed
  
      if variable == 'prep_pipe':
          global_var = prep_pipe
  
      if variable == 'prep_param':
          global_var = prep_param
          
      if variable == 'n_jobs_param':
          global_var = n_jobs_param
  
      if variable == 'html_param':
          global_var = html_param
  
      if variable == 'exp_name_log':
          global_var = exp_name_log
  
      if variable == 'logging_param':
          global_var = logging_param
  
      if variable == 'log_plots_param':
          global_var = log_plots_param
  
      if variable == 'USI':
          global_var = USI
  
      logger.info("Global variable: " + str(variable) + ' returned')
      logger.info("get_config() succesfully completed......................................")
  
      return global_var
  
  def set_config(variable,value):
  
      """
      This function is used to reset global environment variables.
      Following variables can be accessed:
  
      - X: Transformed dataset
      - data_: Original dataset  
      - seed: random state set through session_id
      - prep_pipe: Transformation pipeline configured through setup
      - prep_param: prep_param configured through setup
      - n_jobs_param: n_jobs parameter used in model training
      - html_param: html_param configured through setup
      - exp_name_log: Name of experiment set through setup
      - logging_param: log_experiment param set through setup
      - log_plots_param: log_plots param set through setup
      - USI: Unique session ID parameter set through setup
  
      Example
      -------
      >>> set_config('seed', 123) 
  
      This will set the global seed to '123'.
  
      """
  
      import logging
  
      try:
          hasattr(logger, 'name')
      except:
          logger = logging.getLogger('logs')
          logger.setLevel(logging.DEBUG)
          
          # create console handler and set level to debug
          if logger.hasHandlers():
              logger.handlers.clear()
          
          ch = logging.FileHandler('logs.log')
          ch.setLevel(logging.DEBUG)
  
          # create formatter
          formatter = logging.Formatter('%(asctime)s:%(levelname)s:%(message)s')
  
          # add formatter to ch
          ch.setFormatter(formatter)
  
          # add ch to logger
          logger.addHandler(ch)
          
      logger.info("Initializing set_config()")
      logger.info("""set_config(variable={}, value={})""".\
          format(str(variable), str(value)))
  
      if variable == 'X':
          global X
          X = value
  
      if variable == 'data_':
          global data_
          data_ = value
  
      if variable == 'seed':
          global seed
          seed = value
  
      if variable == 'prep_pipe':
          global prep_pipe
          prep_pipe = value
  
      if variable == 'prep_param':
          global prep_param
          prep_param = value
  
      if variable == 'n_jobs_param':
          global n_jobs_param
          n_jobs_param = value
  
      if variable == 'html_param':
          global html_param
          html_param = value
  
      if variable == 'exp_name_log':
          global exp_name_log
          exp_name_log = value
  
      if variable == 'logging_param':
          global logging_param
          logging_param = value
  
      if variable == 'log_plots_param':
          global log_plots_param
          log_plots_param = value
  
      if variable == 'USI':
          global USI
          USI = value
  
      logger.info("Global variable:  " + str(variable) + ' updated')
      logger.info("set_config() succesfully completed......................................")
  
  def get_system_logs():
  
      """
      Read and print 'logs.log' file from current active directory
      """
  
      file = open('logs.log', 'r')
      lines = file.read().splitlines()
      file.close()
  
      for line in lines:
          if not line:
              continue
  
          columns = [col.strip() for col in line.split(':') if col]
          print(columns)
  
  def get_outliers(data, 
                   model = None, 
                   fraction=0.05, 
                   ignore_features = None, 
                   normalize = True, 
                   transformation = False,
                   pca = False,
                   pca_components = 0.99,
                   ignore_low_variance=False,
                   combine_rare_levels=False,
                   rare_level_threshold=0.1,
                   remove_multicollinearity=False,
                   multicollinearity_threshold=0.9,
                   n_jobs = None):
      
      """
      Magic function to get outliers in Power Query / Power BI.    
      
      """
      
      if model is None:
          model = 'knn'
          
      if ignore_features is None:
          ignore_features_pass = []
      else:
          ignore_features_pass = ignore_features
      
      global X, data_, seed, n_jobs_param, logging_param, logger
      
      n_jobs_param = n_jobs
  
      logging_param = False
  
      import logging
  
      logger = logging.getLogger('logs')
      logger.setLevel(logging.DEBUG)
      
      # create console handler and set level to debug
      if logger.hasHandlers():
          logger.handlers.clear()
      
      ch = logging.FileHandler('logs.log')
      ch.setLevel(logging.DEBUG)
  
      # create formatter
      formatter = logging.Formatter('%(asctime)s:%(levelname)s:%(message)s')
  
      # add formatter to ch
      ch.setFormatter(formatter)
  
      # add ch to logger
      logger.addHandler(ch)
      
      data_ = data.copy()
      
      seed = 99
      
      from pycaret import preprocess
      
      X = preprocess.Preprocess_Path_Two(train_data = data, 
                                         features_todrop = ignore_features_pass,
                                         display_types = False,
                                         scale_data = normalize,
                                         scaling_method = 'zscore',
                                         Power_transform_data = transformation,
                                         Power_transform_method = 'yj',
                                         apply_pca = pca,
                                         pca_variance_retained_or_number_of_components=pca_components,
                                         apply_zero_nearZero_variance = ignore_low_variance,
                                         club_rare_levels=combine_rare_levels,
                                         rara_level_threshold_percentage=rare_level_threshold,
                                         remove_multicollinearity=remove_multicollinearity,
                                         maximum_correlation_between_features=multicollinearity_threshold,
                                         random_state = seed)
      
      
      
      c = create_model(model=model, fraction=fraction, verbose=False, system=False)
      
      dataset = assign_model(c, verbose=False)
      
      return dataset
  
  def _create_bucket_gcp(project_name, bucket_name):
      """
      Creates a bucket on Google Cloud Platform if it does not exists already
  
      Example
      -------
      >>> _create_bucket_gcp(project_name='GCP-Essentials', bucket_name='test-pycaret-gcp')
  
      Parameters
      ----------
      project_name : string
          A Project name on GCP Platform (Must have been created from console).
  
      bucket_name : string
          Name of the storage bucket to be created if does not exists already.
  
      Returns
      -------
      None
      """
  
      # bucket_name = "your-new-bucket-name"
      from google.cloud import storage
      storage_client = storage.Client(project_name)
  
      buckets = storage_client.list_buckets()
  
      if bucket_name not in buckets:
          bucket = storage_client.create_bucket(bucket_name)
          logger.info("Bucket {} created".format(bucket.name))
      else:
          raise FileExistsError('{} already exists'.format(bucket_name))
  
  def _upload_blob_gcp(project_name, bucket_name, source_file_name, destination_blob_name):
  
      """
      Upload blob to GCP storage bucket
  
      Example
      -------
      >>> _upload_blob_gcp(project_name='GCP-Essentials', bucket_name='test-pycaret-gcp', \
                          source_file_name='model-101.pkl', destination_blob_name='model-101.pkl')
  
      Parameters
      ----------
      project_name : string
          A Project name on GCP Platform (Must have been created from console).
  
      bucket_name : string
          Name of the storage bucket to be created if does not exists already.
  
      source_file_name : string
          A blob/file name to copy to GCP
  
      destination_blob_name : string
          Name of the destination file to be stored on GCP
  
      Returns
      -------
      None
      """
  
      # bucket_name = "your-bucket-name"
      # source_file_name = "local/path/to/file"
      # destination_blob_name = "storage-object-name"
      from google.cloud import storage
      storage_client = storage.Client(project_name)
      bucket = storage_client.bucket(bucket_name)
      blob = bucket.blob(destination_blob_name)
  
      blob.upload_from_filename(source_file_name)
  
      logger.info(
          "File {} uploaded to {}.".format(
              source_file_name, destination_blob_name
          )
      )
  
  def _download_blob_gcp(project_name, bucket_name, source_blob_name, destination_file_name):
      """
      Download a blob from GCP storage bucket
  
      Example
      -------
      >>> _download_blob_gcp(project_name='GCP-Essentials', bucket_name='test-pycaret-gcp', \
                            source_blob_name='model-101.pkl', destination_file_name='model-101.pkl')
  
      Parameters
      ----------
      project_name : string
          A Project name on GCP Platform (Must have been created from console).
  
      bucket_name : string
          Name of the storage bucket to be created if does not exists already.
  
      source_blob_name : string
          A blob/file name to download from GCP bucket
  
      destination_file_name : string
          Name of the destination file to be stored locally
  
      Returns
      -------
      Model Object
      """
  
      # bucket_name = "your-bucket-name"
      # source_blob_name = "storage-object-name"
      # destination_file_name = "local/path/to/file"
      from google.cloud import storage
      storage_client = storage.Client(project_name)
  
      bucket = storage_client.bucket(bucket_name)
      blob = bucket.blob(source_blob_name)
  
      if destination_file_name is not None:
          blob.download_to_filename(destination_file_name)
  
          logger.info(
              "Blob {} downloaded to {}.".format(
                  source_blob_name, destination_file_name
              )
          )
  
      return blob
  
  def _create_container_azure(container_name):
      """
      Creates a storage container on Azure Platform. gets the connection string from the environment variables.
  
      Example
      -------
      >>>  container_client = _create_container_azure(container_name='test-pycaret-azure')
  
      Parameters
      ----------
      container_name : string
          Name of the storage container to be created if does not exists already.
  
      Returns
      -------
      cotainer_client
      """
  
      # Create the container
      import os, uuid
      from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient
      connect_str = os.getenv('AZURE_STORAGE_CONNECTION_STRING')
      blob_service_client = BlobServiceClient.from_connection_string(connect_str)
      container_client = blob_service_client.create_container(container_name)
      return container_client
  
  def _upload_blob_azure(container_name, source_file_name, destination_blob_name):
      """
      Upload blob to Azure storage  container
  
      Example
      -------
      >>>  _upload_blob_azure(container_name='test-pycaret-azure', source_file_name='model-101.pkl', \
                             destination_blob_name='model-101.pkl')
  
      Parameters
      ----------
      container_name : string
          Name of the storage bucket to be created if does not exists already.
  
      source_file_name : string
          A blob/file name to copy to Azure
  
      destination_blob_name : string
          Name of the destination file to be stored on Azure
  
      Returns
      -------
      None
      """
  
      import os, uuid
      from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient
      connect_str = os.getenv('AZURE_STORAGE_CONNECTION_STRING')
  
      blob_service_client = BlobServiceClient.from_connection_string(connect_str)
      # Create a blob client using the local file name as the name for the blob
      blob_client = blob_service_client.get_blob_client(container=container_name, blob=destination_blob_name)
  
      # Upload the created file
      with open(source_file_name, "rb") as data:
        blob_client.upload_blob(data, overwrite=True)
  
  def _download_blob_azure(container_name, source_blob_name, destination_file_name):
      """
      Download blob from Azure storage  container
  
      Example
      -------
      >>>  _download_blob_azure(container_name='test-pycaret-azure', source_blob_name='model-101.pkl', \
                               destination_file_name='model-101.pkl')
  
      Parameters
      ----------
      container_name : string
          Name of the storage bucket to be created if does not exists already.
  
      source_blob_name : string
          A blob/file name to download from Azure storage container
  
      destination_file_name : string
          Name of the destination file to be stored locally
  
      Returns
      -------
      None
      """
  
      import os, uuid
      from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient
  
      connect_str = os.getenv('AZURE_STORAGE_CONNECTION_STRING')
      blob_service_client = BlobServiceClient.from_connection_string(connect_str)
      # Create a blob client using the local file name as the name for the blob
      blob_client = blob_service_client.get_blob_client(container=container_name, blob=source_blob_name)
  
      if destination_file_name is not None:
          with open(destination_file_name, "wb") as download_file:
            download_file.write(blob_client.download_blob().readall())