//  [python]
def data_transform(X_train, Y_train, X_test="", Y_test="", modeltype='Classification',
            multi_label=False, enc_method='label', scaler=""):
    
    #### All these are needed for transforming cat variables and building a pipeline ###
    imp_constant = SimpleImputer(strategy='constant', fill_value='missing')
    ohe = OneHotEncoder()
    imp_ohe = make_pipeline(imp_constant, ohe)
    vect = CountVectorizer()
    imp = SimpleImputer()
    le = My_LabelEncoder()

    def drop_second_col(Xt):
        ### This deletes the 2nd column. Hence col number=1 and axis=1 ###
        return np.delete(Xt, 1, 1)

    ####  This is where we define the Pipeline for cat encoders and Label Encoder ############
    lep = My_LabelEncoder_Pipe()
    drop_second_col_func = FunctionTransformer(drop_second_col)
    ### lep_one uses My_LabelEncoder to first label encode and then drop the second unused column ##
    lep_one = make_pipeline(lep, drop_second_col_func)

    ### if you drop remainder variables, then leftovervars is not needed.
    ### If you passthrough remainder variables, then leftovers must be included 
    remainder = 'drop'

    ### If you choose MaxAbsScaler, then NaNs which were Label Encoded as -1 are preserved as - (negatives). This is fantastic.
    ### If you choose StandardScaler or MinMaxScaler, the integer values become stretched as if they are far 
    ###    apart when in reality they are close. So avoid it for now.
    scaler = MaxAbsScaler()
    #scaler = StandardScaler()
    ##### First make sure that the originals are not modified ##########
    X_train_encoded = copy.deepcopy(X_train)
    X_test_encoded = copy.deepcopy(X_test)
    ##### Use My_Label_Encoder to transform label targets if needed #####
    if multi_label:
        if modeltype != 'Regression':
            targets = Y_train.columns
            Y_train_encoded = copy.deepcopy(Y_train)
            for each_target in targets:
                mlb = My_LabelEncoder()
                if not isinstance(Y_train, str):
                    Y_train_encoded[each_target] = mlb.fit_transform(Y_train[each_target])
                else:
                    Y_train_encoded = copy.deepcopy(Y_train)
                if not isinstance(Y_test, str):
                    Y_test_encoded= mlb.transform(Y_test)
                else:
                    Y_test_encoded = copy.deepcopy(Y_test)
        else:
            Y_train_encoded = copy.deepcopy(Y_train)
            Y_test_encoded = copy.deepcopy(Y_test)
    else:
        if modeltype != 'Regression':
            mlb = My_LabelEncoder()
            if not isinstance(Y_train, str):
                Y_train_encoded= mlb.fit_transform(Y_train)
            else:
                Y_train_encoded = copy.deepcopy(Y_train)
            if not isinstance(Y_test, str):
                Y_test_encoded= mlb.transform(Y_test)
            else:
                Y_test_encoded = copy.deepcopy(Y_test)
        else:
            Y_train_encoded = copy.deepcopy(Y_train)
            Y_test_encoded = copy.deepcopy(Y_test)
    
    #### This is where we find out how to transform X_train and X_test  ####
    catvars = X_train.select_dtypes('object').columns.tolist() + X_train.select_dtypes('category').columns.tolist()
    numvars = X_train.select_dtypes('number').columns.tolist()
    ########    This is where we define the pipeline for cat variables ###########
    ### How do we make sure that we create one new LE_Pipe for each catvar? This is one way.
    init_str = 'make_column_transformer('
    middle_str = "".join(['(lep_one, catvars['+str(i)+']),' for i in range(len(catvars))])
    end_str = '(imp, numvars),    remainder=remainder)'
    full_str = init_str+middle_str+end_str
    ct = eval(full_str)
    pipe = make_pipeline(ct, scaler )
    ###  You will get a multidimensional numpy array ############
    dfo = pipe.fit_transform(X_train)
    if not isinstance(X_test, str):
        dfn = pipe.fit_transform(X_test)
    
    ### The first columns should be whatever is in the Transformer_Pipeline list of columns
    ### Hence they will be catvars. The second list will be numvars. Then only other columns that are passed through.
    ### So after the above 2 lists, you will get remainder cols unchanged: we call them leftovers.
    leftovervars = left_subtract(X_train.columns.tolist(), catvars+numvars)

    ## So if you do it correctly, you will get the list of names in proper order this way:
    ## first is catvars, then numvars and then leftovervars
    if remainder == 'drop':
        cols_names = catvars+numvars                    
    else:
        cols_names = catvars+numvars+leftovervars

    dfo = pd.DataFrame(dfo, columns = cols_names)
    if not isinstance(X_test, str):
        dfn = pd.DataFrame(dfn, columns = cols_names)
    
    copy_names = copy.deepcopy(cols_names)

    for each_col in copy_names:
        X_train_encoded[each_col] = dfo[each_col].values
        if not isinstance(X_test, str):
            X_test_encoded[each_col] = dfn[each_col].values

    return X_train_encoded, Y_train_encoded, X_test_encoded, Y_test_encoded, pipe