################################### Paquetes ##############################################
import pandas as pd
from scipy import stats
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors
import seaborn as sns
from tabulate import tabulate
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn import linear_model
from sklearn.model_selection import cross_val_score, cross_val_predict
import joblib
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import GridSearchCV
import sys
import xgboost as xgb

#################################### Funciones ##############################################


# Función para agregar columnas con indices (ufm2, etc) y cambiar el tipo de
# dato de ciertas columnas
def InsertColumns(D): #(D es la tabla) 
    D=D.astype({'ingreso':'float64','antiguedad':'float64','supCSII':'float64','supTSII':'float64',
                'valor':'float64','avaluofiscal' :'float64'})
    
    D.insert(D.shape[1],'ufm2(supCSII)',D.loc[:,'valor']/D.loc[:,'supCSII'], True) 
    D.insert(D.shape[1],'ufm2(supTSII)',D.loc[:,'valor']/D.loc[:,'supTSII'], True)
    D.insert(D.shape[1],'supTSII/supCSII',D.loc[:,'supTSII']/D.loc[:,'supCSII'], True) 
    return D

# Concatenación de tablas de ventas y tasaciones
def tabla_auxiliar(df1,df2,method): #(df1 es la tabla de ventas, df2 es la tabla de tasaciones, 
                                     #method: el proposito para el cual se genera la tabla: ML o AVM) #variables: nombres
    while (method!="ML" and method!="AVM"):
        print("El método seleccionado no es válido")
        method=input("Ingrese correctamente el método a utilizar (ML o AVM): ")
    ## Aquí hubo una modificación
    if method=="ML":
        variablesML=['ingreso','antiguedad','longitud','latitud','supCSII','supTSII','valor','avaluofiscal',
             'cve_comuna', 'ufm2(supCSII)','ufm2(supTSII)','supTSII/supCSII']
        df1_aux=df1[variablesML]
        df2_aux=df2[variablesML]
    else:
        variablesAVM=['num_','cve_propiedad','rol','cve_comuna','cve_region','ah','ah_hom','zona_eod',
             'zona_jjvv','materialidad','ingreso','antiguedad','longitud',
             'latitud','supCSII','supTSII','valor','avaluofiscal']
        df1_aux=df1[variablesAVM]
        df2_aux=df2[variablesAVM]
    df1_df2_aux=pd.concat([df1_aux,df2_aux], ignore_index=True, sort=False)
    return df1_df2_aux

# Selección de comuna
def Selec_Comuna(D1,cve): #(D1 es la tabla, cve: es el codigo de la comuna)
    comunas=[19,21,22,52]
    while cve not in comunas:
        print('Ingrese alguna de las siguientes comunas: La Reina (19), Las Condes (21), Lo Barnechea (22) o Vitacura (52):')
        cve=int(input())
    if cve==52:
        tol=5000
    else:
        tol=1000
    D_comuna=D1.loc[(D1.loc[:,'cve_comuna']==cve) & (D1.loc[:,'valor']>=tol)]
    return D_comuna

# Eliminación de datos duplicados
def datosduplicados(tabla,T): #(tabla es la tabla, T es True entonces se muestra la cantidad de datos eliminados)
    n_inicial = tabla.shape[0];
    tabla2 = tabla.drop_duplicates(subset=['ingreso','longitud','latitud',
                                          'supCSII','supTSII','avaluofiscal'])
    
    if T==True:
        print(f'Hay {n_inicial-tabla2.shape[0]} datos duplicados')
        print(f'Al eliminarlos quedan {tabla2.shape[0]} datos')
    return tabla2

#Identificación de atípicos dada una columna
def outliers_col(df,columna,n,a,T,n_i,limit):  #(df:tabla, columna, n: cantidad de datos inicial,
                                               # a: zscore o IQR, T es True entonces se muestran los resultados
                                               # n_i: cantidad de datos inicial, limit: limite del zscore)
    tabla= pd.DataFrame.from_dict({
    'Variable': [],'Cantidad de Atípicos': [],
    'Type': []});
    col = ['Variable','Cantidad de Atípicos','Type'];
    k=0;
    if (a=='zscore'):
        n_outliers = len(df[np.abs(stats.zscore(df[columna])) > limit])
        k=k+n_outliers;
        tablaux = pd.DataFrame([[df[columna].name,n_outliers,df[columna].dtype]],
                                    columns=col);
        tabla=pd.concat([tabla, tablaux],ignore_index=True);
        
    if (a=='IQR'):
        Q1,Q3 = np.percentile(df[columna], [25,75])
        iqr = Q3 - Q1
        ul = Q3+1.5*iqr
        ll = Q1-1.5*iqr
        n_outliers = len(df[(df[columna] > ul) | (df[columna] < ll)])
        k=k+n_outliers;
        tablaux = pd.DataFrame([[df[columna].name,n_outliers,df[columna].dtype]],
                                    columns=col);
        tabla=pd.concat([tabla, tablaux],ignore_index=True);
    if T==True:
        print(tabulate(tabla, headers=col, tablefmt="fancy_grid"))  
        print('\nSe eliminarán:',k,'datos, y quedarán al menos:',n-k)
        print('en porcentaje con respecto a la cantidad inicial:',(n-k)*100/n_i,'%.\n')     
    return k,tabla
#Eliminación de atípicos dada una columna
def outliers_col_eliminacion(df,columna,a,limit):  #(df:tabla, columna, a: zscore o IQR, limit: limite del zscore)
    if a=='zscore':
        l=df[np.abs(stats.zscore(df[columna])) > limit].index;
        for x in l.values:
            df.loc[x,columna] = np.nan;
                
    if a=='IQR':
        Q1,Q3 = np.percentile(df[columna], [25,75])
        iqr = Q3 - Q1
        ul = Q3+1.5*iqr
        ll = Q1-1.5*iqr
        l=df[(df[columna] > ul) | (df[columna] < ll)].index;
        for x in l.values:
            df.loc[x,columna] = np.nan;
    
    df=df.dropna(axis = 0);
    return df

# Gráficas
def grafico_histograma_sns(df,columna,option1,option2):   #(df:tabla, columna, option1: kde True or False
                                                          #,option2: discrete True or False)
    plt.figure(figsize = (9,4))
    #sns.set_style("whitegrid")
    sns.histplot(data=df[columna],color="#008080",
                 kde=option1,discrete=option2,bins=100);
    plt.xlabel(None)
    plt.title(columna);
    plt.ylabel('Cantidad')
    plt.grid(True, color='lightgrey',linestyle='--')
    plt.show() 
def grafico_hyb_sns(df,columna,option1,option2,binss,col):   #(df:tabla, columna, option1: kde True or False
                                                             #,option2: discrete True or False, binss: numero de bins,
                                                             # col: color)
    #plt.rcParams['figure.figsize'] = (6,4)
    f, (ax_box, ax_hist) = plt.subplots(2,sharex=True,gridspec_kw={"height_ratios": (.25, .95)})
    red_cir = dict(markerfacecolor='maroon',marker='o',markersize=6)
    #sns.set_style("whitegrid")
    plt.grid(True, color='lightgrey',linestyle='--')
    sns.boxplot(x=df[columna],ax=ax_box,color=col,
                flierprops=red_cir).set_title(columna)
    
    sns.stripplot(x=df[columna],ax=ax_box,color="lightsalmon",jitter=0.15, size=2.5)
    sns.histplot(data=df[columna],color=col,ax=ax_hist,
                 kde=option1,discrete=option2,bins=binss)   
    ax_box.set(xlabel='')
    ax_box.grid(True, color='lightgrey',linestyle='--')
    ax_hist.set(xlabel='')
    ax_hist.set(ylabel='Cantidad')
    ax_hist.grid(True, color='lightgrey',linestyle='--')
    plt.show()
def grafico_boxplot_jitted(df,columna,jit):              #(df:tabla, columna, jit: si o no)
    plt.rcParams['figure.figsize'] = (9,12)
    red_cir = dict(markerfacecolor='maroon',marker='o',markersize=6)
    #sns.set_style("whitegrid")
    plt.grid(True, color='lightgrey',linestyle='--')
    if(jit=='no'):
        sns.boxplot(y=df[columna],color="#008080",
                     flierprops=red_cir).set_title(columna);  
    else:
        ax=sns.boxplot(x=df[columna],data=df,color="#008080",
                flierprops=red_cir).set_title(columna); 
        ax=sns.stripplot(x=df[columna], data=df, color="lightsalmon", jitter=0.15, size=2.5)

    plt.xlabel(None)   
    plt.grid(True, color='lightgrey',linestyle='--')
    plt.show()
    
def atypicals_be_gone(df,pars,colors,T,metodo,limit):
    ## Gráficos antes de la eliminación
    # Histogramas 1
    print('Histogramas (con atípicos)')
    for j in range(0,len(pars)):
        grafico_hyb_sns(df,pars[j],True,False,100,colors[j])
        plt.show()
    ## Eliminación de atípicos  
    n_i=df.shape[0]
    for j in range(0,len(pars)):
        w=1
        if T==True:
            print(f'Eliminación de atípicos considerando: {pars[j]}')
        while (w!=0):
            [w,resum]=outliers_col(df,pars[j],df.shape[0],metodo,T,n_i,limit);
            df=outliers_col_eliminacion(df,pars[j],metodo,limit);
    ## Gráficos después de la eliminación
    # Histogramas 2
    print('Histogramas (sin atípicos)')
    for j in range(0,len(pars)):
        grafico_hyb_sns(df,pars[j],True,False,100,colors[j])
        plt.show()

    return df

# Matriz de correlación
def matriz_correlacion(df):
    matriz = df.corr(method='kendall')
    plt.rcParams['figure.figsize'] = (7,7);
    plt.matshow(matriz, cmap='BrBG', vmin=-1, vmax=1)
    plt.xticks(range(df.shape[1]), df.columns, rotation=90)
    plt.yticks(range(df.shape[1]), df.columns)

    for i in range(len(matriz.columns)):
          for j in range(len(matriz.columns)):
                 plt.text(i, j, round(matriz.iloc[i, j], 2),
                 ha="center", va="center")

    plt.colorbar()
    plt.grid(False)
    plt.show()
# Cálcular el tamaño de la muestra
def tam_muestra(ven_tas_comuna1,confianza):
    alpha=1-confianza # Confianza del 90%=0.9
    N=ven_tas_comuna1.shape[0]
    er=10/ven_tas_comuna1['valor'].mean()
    Z=stats.norm.ppf(1-alpha/2)
    COV=ven_tas_comuna1['valor'].std()/ven_tas_comuna1['valor'].mean()
    nmuestra=(N*(COV**2)*(Z**2))/((er**2)*(N-1)+(COV**2)*(Z**2))
    n_muestra=int(nmuestra)
    return n_muestra

def Muestra_uni(df1,df2,cve,ant):
    nn=tam_muestra(df1,0.9) 
    n1=int(nn/10) 
    N=df1.shape[0] 
    n2=int(N/10) 
    df3=df1.sort_values(by=ant, ascending= True)
    Muestras=[]
    for i in range(0,2):
        a1=df3.iloc[0:n2,:]
        b1=df3.iloc[n2:2*n2,:]
        c1=df3.iloc[2*n2:3*n2,:]
        d1=df3.iloc[3*n2:4*n2,:]
        e1=df3.iloc[4*n2:5*n2,:]
        f1=df3.iloc[5*n2:6*n2,:]
        g1=df3.iloc[6*n2:7*n2,:]
        h1=df3.iloc[7*n2:8*n2,:]
        i1=df3.iloc[8*n2:9*n2,:]
        j1=df3.iloc[9*n2:N+1,:]
        if n1==n2:
            A1=a1.sample(n=n1)
            B1=b1.sample(n=n1)
            C1=c1.sample(n=n1)
            D1=d1.sample(n=n1)
            E1=e1.sample(n=n1)
            F1=f1.sample(n=n1)
            G1=g1.sample(n=n1)
            H1=h1.sample(n=n1)
            I1=i1.sample(n=n1)
            J1=j1.sample(n=nn-9*n1)
        else:
            k,n3=0,n1 
            while 10*n1+k<=nn:
                if 10*n1+k==nn:
                    A1=a1.sample(n=n1)
                    B1=b1.sample(n=n1)
                    C1=c1.sample(n=n1)
                    D1=d1.sample(n=n1)
                    E1=e1.sample(n=n1)
                    F1=f1.sample(n=n1)
                    G1=g1.sample(n=n1)
                    H1=h1.sample(n=n1)
                    I1=i1.sample(n=n1)
                    J1=j1.sample(n=n1)
                    break   
                k=k+1 
                n3=n3+1
                A1=a1.sample(n=n3)
                if 10*n1+k==nn:
                    B1=b1.sample(n=n1)
                    C1=c1.sample(n=n1)
                    D1=d1.sample(n=n1)
                    E1=e1.sample(n=n1)
                    F1=f1.sample(n=n1)
                    G1=g1.sample(n=n1)
                    H1=h1.sample(n=n1)
                    I1=i1.sample(n=n1)
                    J1=j1.sample(n=n1)
                    break
                k=k+1
                B1=b1.sample(n=n3) 
                if 10*n1+k==nn:
                    C1=c1.sample(n=n1)
                    D1=d1.sample(n=n1)
                    E1=e1.sample(n=n1)
                    F1=f1.sample(n=n1)
                    G1=g1.sample(n=n1)
                    H1=h1.sample(n=n1)
                    I1=i1.sample(n=n1)
                    J1=j1.sample(n=n1)
                    break
                k=k+1
                C1=c1.sample(n=n3)
                if 10*n1+k==nn:    
                    D1=d1.sample(n=n1)
                    E1=e1.sample(n=n1)
                    F1=f1.sample(n=n1)
                    G1=g1.sample(n=n1)
                    H1=h1.sample(n=n1)
                    I1=i1.sample(n=n1)
                    J1=j1.sample(n=n1)
                    break  
                k=k+1
                D1=d1.sample(n=n3) 
                if 10*n1+k==nn:
                    E1=e1.sample(n=n1)
                    F1=f1.sample(n=n1)
                    G1=g1.sample(n=n1)
                    H1=h1.sample(n=n1)
                    I1=i1.sample(n=n1)
                    J1=j1.sample(n=n1)
                    break 
                k=k+1
                E1=e1.sample(n=n3)
                if 10*n1+k==nn:
                    F1=f1.sample(n=n1)
                    G1=g1.sample(n=n1)
                    H1=h1.sample(n=n1)
                    I1=i1.sample(n=n1)
                    J1=j1.sample(n=n1)
                    break 
                k=k+1
                F1=f1.sample(n=n3)
                if 10*n1+k==nn:
                    G1=g1.sample(n=n1)
                    H1=h1.sample(n=n1)
                    I1=i1.sample(n=n1)
                    J1=j1.sample(n=n1)
                    break
                k=k+1
                G1=g1.sample(n=n3)
                if 10*n1+k==nn:
                    H1=h1.sample(n=n1)
                    I1=i1.sample(n=n1)
                    J1=j1.sample(n=n1)
                    break 
                k=k+1
                H1=h1.sample(n=n3)
                if 10*n1+k==nn:
                    I1=i1.sample(n=n1)
                    J1=j1.sample(n=n1)
                    break 
                k=k+1
                I1=i1.sample(n=n3)
                if 10*n1+k==nn:
                    J1=j1.sample(n=n1)
                    break 
                k=k+1
                J1=j1.sample(n=n3)
        MuestraML=pd.concat([A1,B1,C1,D1,E1,F1,G1,H1,I1,J1],sort=False)
        MuestraML=datosduplicados(MuestraML,False)
        Muestras.append(MuestraML)
    MuestraML=Muestras[0]
    MuestraAVM=pd.merge(df2,Muestras[1], how="right", 
                        on=["ingreso","antiguedad","longitud","latitud","supCSII","supTSII","valor","avaluofiscal"])
    MuestraAVM=datosduplicados(MuestraAVM,False)
    return MuestraML,MuestraAVM
#def Muestra_Total(A):
#    if A=="ML":
#        LR = pd.read_excel('MuestraML19.xlsx');
#        LC = pd.read_excel('MuestraML21.xlsx');
#        LB = pd.read_excel('MuestraML22.xlsx');
#        V = pd.read_excel('MuestraML52.xlsx');
#        Muestra = pd.concat([LR,LC,LB,V], ignore_index=True, sort=False)
#        Muestra.to_excel('Muestra_Total_ML.xlsx')
#    elif A=="AVM":
#        LR = pd.read_excel('MuestraAVM19.xlsx');
#        LC = pd.read_excel('MuestraAVM21.xlsx');
#        LB = pd.read_excel('MuestraAVM22.xlsx');
#        V = pd.read_excel('MuestraAVM52.xlsx');
#        Muestra = pd.concat([LR,LC,LB,V], ignore_index=True, sort=False)
#        Muestra.to_excel('Muestra_Total_AVM.xlsx')
#    return Muestra

def indexacion(df,mini,maxi):
    years1=[]
    for i in range(mini,maxi,10):
        years1.append(i)
    if maxi not in years1:
        years1.append(maxi)
    conditionlist1=[]
    for k in range(0,(len(years1))-2): 
        c=(df['antiguedad'] >=years1[k]) & (df['antiguedad'] <years1[k+1])
        conditionlist1.append(c)
    c=(df['antiguedad'] >=years1[(len(years1))-2]) & (df['antiguedad'] <=years1[(len(years1))-1])
    conditionlist1.append(c)
    L=["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","W","X","Y","Z"]
    vectorS1=[]
    for i in range(0,len(years1)-2):
        vectorS1.append(L[i]+str(years1[i])+"/"+str(years1[i+1]-1))
    vectorS1.append(L[len(years1)-2]+str(years1[len(years1)-2])+"/"+str(years1[len(years1)-1]))
    choicelist1=[]
    for i in range(0,len(vectorS1)):
        choicelist1.append(vectorS1[i][0]+vectorS1[i][3:6]+vectorS1[i][8:10])
    df.insert(df.shape[1],'Index_antiguedad_10',np.select(conditionlist1, choicelist1, default=0))
    df2=df.astype({"ingreso":"float64",'antiguedad':'float64','supCSII':'float64','supTSII':'float64','valor':'float64',"avaluofiscal": "float64"})
    return df2

# Gráfica de boxplots
def grafico_boxplot_rcParams(df2):
    plt.rcParams['figure.figsize'] = (9,6);
    sns.boxplot(data=df2.sort_values(by=['Index_antiguedad_10'],
              ascending=True, inplace=False), 
              x="Index_antiguedad_10", y="valor",
              showfliers=False,palette="Set2");
    sns.stripplot(data=df2.sort_values(by=['Index_antiguedad_10'], 
              ascending=True, inplace=False), 
              x="Index_antiguedad_10", y="valor",
              linewidth=1.0,palette="Set2");
    plt.xlabel('Década')
    plt.ylabel('Valor UF')
    plt.title('Distribución valor UF por década')
    plt.grid(True, color='lightgrey',linestyle='--')
    plt.show()

def GraEstModels(e,f,g,h): ##### MODIFICADA POR TENER REGRESION Y ENET #####
    xvec=list(e)
    xvec2=list(g)
    for k in range(0,len(xvec2)):
        xvec.append(xvec2[k])  
    xmin,xmax=min(xvec)-250,max(xvec)+250
    yvec=list(f)
    yvec2=list(h)
    for k in range(0,len(yvec2)):
        yvec.append(yvec2[k])
    ymin,ymax=min(yvec)-250,max(yvec)+250
    red=[xmin,xmax]
    
    fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(14,5))
    axs[0].scatter(e,f,color="#008080",edgecolor="c");
    axs[0].plot(red,red,color="red")
    axs[0].set_xlabel('Valor Observado',size=12)
    axs[0].set_ylabel('Valor Estimado',size=12)
    axs[0].grid(True)
    axs[0].set_xlim(xmin,xmax)
    axs[0].set_ylim(ymin,ymax)
    axs[0].set_title('Random Forest', size= 16)

    axs[1].scatter(g,h,color="#008080",edgecolor="c");
    axs[1].plot(red,red,color="red")
    axs[1].set_xlabel('Valor Observado',size=12)
    axs[1].set_ylabel('Valor Estimado',size=12)
    axs[1].grid(True)
    axs[1].set_xlim(xmin,xmax)
    axs[1].set_ylim(ymin,ymax)
    axs[1].set_title('Extreme Gradient Boosting', size= 16)

    plt.show()

def porcentajeerror(a,b): # a: y_test, b: y_pred
    r1=100*(b-a)/a;
    rr1=r1.tolist();
    tabla= pd.DataFrame.from_dict({'Intervalo':[],'%_Est_Acumulado': [],'Cantidad_Est_Relativa': []});
    col = ['Intervalo','%_Est_Acumulado','Cantidad_Est_Relativa'];
    cant=0
    k=[5,10,15,20,25,50]
    for lim in range(0,len(k)):
        if k[lim]==5:
            inter=f"|Error|<={k[lim]} %"
        else: 
            inter=f"{k[lim-1]}% <|Error|<={k[lim]}%"
        porcentaje1=100*sum(map(lambda x : (x>=-k[lim]) & (x<=k[lim]), rr1))/len(rr1)
        cantidad1=sum(map(lambda x : (x>=-k[lim]) & (x<=k[lim]), rr1))-cant
        cant=cant+cantidad1

        tablaux = pd.DataFrame([[inter,porcentaje1,cantidad1]], columns=col);
        tabla=pd.concat([tabla, tablaux],ignore_index=True);
    return tabla

def Entrenamiento(df2, t_s):
    df3=df2.drop(['valor'],axis=1)
    Xrlinter = np.array(df3)
    yrlinter = np.array(df2.valor)
    Xrlinter_train, Xrlinter_test, yrlinter_train, yrlinter_test = train_test_split(Xrlinter, yrlinter, test_size=t_s)
    return Xrlinter_train, Xrlinter_test, yrlinter_train, yrlinter_test, Xrlinter

def Escalas(cve, Xrlinter_train, Xrlinter_test):
    scl = StandardScaler().fit(Xrlinter_train)
    Xrlinter_train = scl.transform(Xrlinter_train)  
    Xrlinter_test = scl.transform(Xrlinter_test)  
    return Xrlinter_train, Xrlinter_test,scl


## Random Forest para un par de datos
def RForest(Xtrain,Xtest,ytrain,ytest,k):
    rforest = RandomForestRegressor(max_features=7,random_state=42)  
    params={'n_estimators':[300,500,800],
           'max_depth':[30,50,80]}  
    Search=GridSearchCV(estimator=rforest,
                       param_grid=params,
                       n_jobs=-1)
    rforest1=Search.fit(Xtrain,ytrain)
    rforest1.best_estimator_
    y_pred43 = cross_val_predict(rforest1, Xtest, np.ravel(ytest), cv=k)
    return rforest1,y_pred43

def RForestEleccion(df2,cve,k):
    # Entrenamiento
    Xtodos_train, Xtodos_test, ytodos_train, ytodos_test,Xtodos=Entrenamiento(df2,0.2)
    # Escala
    Xtodos_train, Xtodos_test,scl1 =Escalas(cve, Xtodos_train, Xtodos_test)
    # Random Forest
    rforest1,y_pred43=RForest(Xtodos_train,Xtodos_test,ytodos_train,ytodos_test,k)

    #scaler1_file = "escalaRF_"+str(cve)+".save"
    #joblib.dump(scl1, scaler1_file)
    #print("El método de Random Forest se ha ejecutado con éxito. Se procederá a guardar los resultados.")
    #joblib.dump(rforest1,"RForest_"+str(cve)+".joblib")
    # (Esto se hace aparte)
    return scl1,ytodos_test,y_pred43,rforest1

# XGB para un par de datos
def XGB(Xtrain,Xtest,ytrain,ytest,k):
    xg=xgb.XGBRegressor(objective="reg:squarederror",alpha=1)
    colsample= [0.3,0.5,0.7]
    lr=[0.05,0.2,0.5]
    max_d=[30,50,80]
    n_est=[300,500,800]
    params={"colsample_bytree":colsample,"learning_rate":lr,"max_depth":max_d,"n_estimators":n_est}
    search=GridSearchCV(estimator=xg,param_grid=params,n_jobs=-1)
    search_model=search.fit(Xtrain,ytrain)
    search_model.best_params_                    
    y_pred63 = cross_val_predict(search_model, Xtest, np.ravel(ytest), cv=k)
    return search_model,y_pred63

def XGB_Eleccion(df2,cve,k):
    # Entrenamientos
    Xtodos_train, Xtodos_test, ytodos_train, ytodos_test,Xtodos=Entrenamiento(df2,0.2)
    # Escalas
    Xtodos_train, Xtodos_test,scl1 =Escalas(cve, Xtodos_train, Xtodos_test)
    # XGBs
    xgb1,y_pred43=XGB(Xtodos_train,Xtodos_test,ytodos_train,ytodos_test,k)

    #scaler1_file = "escalaXGB_"+str(cve)+".save"
    #joblib.dump(scl1, scaler1_file)
    #print("El método de Extreme Gradient Boosting se ha ejecutado con éxito. Se procederá a guardar los resultados.")
    #joblib.dump(xgb1,"XGB_"+str(cve)+".joblib")
    # (Se hace aparte)
    return scl1,ytodos_test,y_pred43,xgb1

# Error para los 4 modelos
def porcentajeerror2(e,f,g,h):  ##### MODIFICADA POR TENER RL Y ENET #####
    r3=100*(f-e)/e;
    rr3=r3.tolist();
    r4=100*(h-g)/g;
    rr4=r4.tolist();
    
    tabla= pd.DataFrame.from_dict({'Intervalo':[],
                                '%_Est_RandomForest': [],'Cantidad_Est_RandomForest': [],
                                 '%_Est_XGboosting': [],'Cantidad_Est_XGboosting':[]});
    
    col = ['Intervalo','%_Est_RandomForest','Cantidad_Est_RandomForest', '%_Est_XGboosting','Cantidad_Est_XGboosting'];
    cant=[0,0]
    k=[5,10,15,20,25,50]
    for lim in range(0,len(k)):
        if k[lim]==5:
            inter=f"|Error|<={k[lim]} %"
        else: 
            inter=f"{k[lim-1]}% <|Error|<={k[lim]}%"
        porcentaje3=100*sum(map(lambda x : (x>=-k[lim]) & (x<=k[lim]), rr3))/len(rr3)
        cantidad3=sum(map(lambda x : (x>=-k[lim]) & (x<=k[lim]), rr3))-cant[0]
        porcentaje4=100*sum(map(lambda x : (x>=-k[lim]) & (x<=k[lim]), rr4))/len(rr4)
        cantidad4=sum(map(lambda x : (x>=-k[lim]) & (x<=k[lim]), rr4))-cant[1]
        cant=[cant[0]+cantidad3,cant[1]+cantidad4]
      
        tablaux = pd.DataFrame([[inter,porcentaje3,cantidad3,porcentaje4,cantidad4]],
                              columns=col);
        tabla=pd.concat([tabla, tablaux],ignore_index=True);
    return tabla

# Estadisticas de los errores de los 4 modelos
def Tabla_Estadistica_Error(y31,y32,y41,y42): #### MOD POR TENER RL Y ENET
    E_RForest=[]
    E_XGB=[]
    Sobreestimados3=0
    Sobreestimados4=0
    Subestimados3=0
    Subestimados4=0
    Estimados3=0
    Estimados4=0
    for i in range(0,len(y31)):
        E_RForest.append(y31[i]-y32[i])
        if y31[i]-y32[i]<0:
            Sobreestimados3+=1
        elif y31[i]-y32[i]>0:
            Subestimados3+=1
        Estimados3+=1
    for i in range(0,len(y41)):
        E_XGB.append(y41[i]-y42[i])
        if y41[i]-y42[i]<0:
            Sobreestimados4+=1
        elif y41[i]-y42[i]>0:
            Subestimados4+=1
        Estimados4+=1
        
    Sobreestimados=[Sobreestimados3,Sobreestimados4]
    Subestimados=[Subestimados3,Subestimados4]
    Estimados=[Estimados3,Estimados4]
    tabla= pd.DataFrame.from_dict({'Método':[],
                                'Media': [],'Desviación estándar': [],
                                'Mediana': [], "Datos sobreestimados":[], "Datos subestimados":[],"Datos Estimados":[]});
    col = ['Método','Media','Desviación estándar','Mediana',"Datos sobreestimados","Datos subestimados","Datos Estimados"];
    inter=["Random Forest", "Xtreme Gradient Boosting"]
    media=[np.mean(E_RForest),np.mean(E_XGB)]
    ds=[np.std(E_RForest),np.std(E_XGB)] 
    med=[np.percentile(E_RForest,50),np.percentile(E_XGB,50)]
    for i in range(0,2):
        tablaux = pd.DataFrame([[inter[i],media[i],ds[i],med[i],Sobreestimados[i],Subestimados[i],Estimados[i]]],
                                  columns=col);
        tabla=pd.concat([tabla, tablaux],ignore_index=True);
    return tabla

##################### PARTE 3 #########################

def InsertColumnasAVM(reavm):
    reavm.insert(reavm.shape[1],'% de error',
             100*(reavm.loc[:,'Estimación AVM']-reavm.loc[:,'valor'])/reavm.loc[:,'valor'],
             True)
    reavm.insert(reavm.shape[1],'error AVM',reavm['Estimación AVM']-reavm['valor'],True)
    return reavm

def Is_estimated_by_AVM(reavm):
    reavm1=reavm.loc[reavm.loc[:,'Estimación AVM']<=0]
    print(f"La cantidad de datos no estimados por AVM es de: {len(reavm[reavm['Estimación AVM']<=0])}")
    print(f"Cuyo porcentaje equivale a: {(len(reavm[reavm['Estimación AVM']<=0])*100)/reavm.shape[0]: .2f}%")
    reavm2=reavm.loc[reavm.loc[:,'Estimación AVM']>0]
    print(f"La cantidad de datos estimados por AVM es de: {len(reavm[reavm['Estimación AVM']>0])}")
    print(f"Cuyo porcentaje equivale a: {(len(reavm[reavm['Estimación AVM']>0])*100)/reavm.shape[0]: .2f}%")
    return reavm1,reavm2

def Rend_Est_AVM(reavm2):
    plt.rcParams['figure.figsize'] = (9,5);
    sns.scatterplot(data=reavm2,x='valor',y='Estimación AVM',color="#008080")
    plt.plot(reavm2['valor'],reavm2['valor'],color="red")
    plt.plot(reavm2['valor'],reavm2['valor']*(1.05),color="blue")
    plt.plot(reavm2['valor'],reavm2['valor']*(0.95),color="blue")
    plt.plot(reavm2['valor'],reavm2['valor']*(1.2),color="orange")
    plt.plot(reavm2['valor'],reavm2['valor']*(0.8),color="orange")
    plt.legend(["Datos","Recta ajustada a los datos","Error del +5%","Error del -5%","Error del +20%","Error del -20%"],bbox_to_anchor=(1, 1), loc='upper left')
    plt.title("Datos estimados por el AVM",size=16)
    plt.xlabel('Valor Observado');
    plt.ylabel('Valor Estimado AVM');
    plt.grid(True)
    plt.show()
    
# Selección de datos del AVM para escalar
def Xrl_inter(remL):
    indexaciones=['ingreso','Index_antiguedad_10','longitud','latitud','supCSII','supTSII','avaluofiscal']
    Xrlinter = np.array(remL[indexaciones])
    return Xrlinter

def SCL_model(scl,Xtodos,modelo):
    Xtodos=scl.transform(Xtodos)
    ValEst_mod=modelo.predict(Xtodos)
    return ValEst_mod

def InsertColumnsML(reml2,ValEst_RF,ValEst_XGB): ###### MOD X TENER RL Y ENET
    
    reml2.insert(reml2.shape[1],'ValEst_RF',ValEst_RF,True)
    reml2.insert(reml2.shape[1],'ValEst_XGB',ValEst_XGB,True)

    reml2.insert(reml2.shape[1],'% de error RF',100*(reml2['ValEst_RF']-reml2['valor'])/reml2['valor'],True)
    reml2.insert(reml2.shape[1],'error RF',reml2['ValEst_RF']-reml2['valor'],True)

    reml2.insert(reml2.shape[1],'% de error XGB',100*(reml2['ValEst_XGB']-reml2['valor'])/reml2['valor'],True)
    reml2.insert(reml2.shape[1],'error XGB',reml2['ValEst_XGB']-reml2['valor'],True)
    return reml2

def Rend_Est_ML(reml2): #### MOD X TENER RL Y ENET
    fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(14, 5))

    axs[0].scatter(reml2['valor'],reml2['ValEst_RF'],color="#008080",edgecolor="c")
    axs[0].plot(reml2['valor'],reml2['valor'],color="red")
    axs[0].plot(reml2['valor'],reml2['valor']*(1.05),color="blue")
    axs[0].plot(reml2['valor'],reml2['valor']*(0.95),color="blue")
    axs[0].plot(reml2['valor'],reml2['valor']*(1.2),color="orange")
    axs[0].plot(reml2['valor'],reml2['valor']*(0.8),color="orange")
    axs[0].set_xlabel('Valor Observado')
    axs[0].set_ylabel('Valor Estimado')
    axs[0].set_title('Random Forest')
    axs[0].grid(True)

    axs[1].scatter(reml2['valor'],reml2['ValEst_XGB'],color="#008080",edgecolor="c")
    axs[1].plot(reml2['valor'],reml2['valor'],color="red")
    axs[1].plot(reml2['valor'],reml2['valor']*(1.05),color="blue")
    axs[1].plot(reml2['valor'],reml2['valor']*(0.95),color="blue")
    axs[1].plot(reml2['valor'],reml2['valor']*(1.2),color="orange")
    axs[1].plot(reml2['valor'],reml2['valor']*(0.8),color="orange")
    axs[1].set_xlabel('Valor Observado')
    axs[1].set_ylabel('Valor Estimado')
    axs[1].set_title('Extreme Gradient Boosting')
    axs[1].grid(True)

    plt.show()

def atypicals_be_goneAVM(df,pars,T,metodo,limit): 
    ## Eliminación de atípicos  
    n_i=df.shape[0]
    for j in range(0,len(pars)):
        w=1
        if T==True:
            print(f'Eliminación de atípicos considerando: {pars[j]}')
        while (w!=0):
            [w,resum]=outliers_col(df,pars[j],df.shape[0],metodo,T,n_i,limit);
            df=outliers_col_eliminacion(df,pars[j],metodo,limit);
    return df

def Error_AVM_vs_ML(reml2): ### MOD X TENER RL Y ENET
    real=reml2['valor']
    estimado3=reml2['ValEst_RF']
    estimado4=reml2['ValEst_XGB']
    r3=100*(estimado3-real)/real;
    rr3=r3.tolist();
    r4=100*(estimado4-real)/real;
    rr4=r4.tolist();

    tabla= pd.DataFrame.from_dict({'Intervalo':[],
                                   '%_RF_Acumulado': [],'Cantidad_RF': [],
                                   '%_XGB_Acumulado': [],'Cantidad_XGB': []});

    col = ['Intervalo','%_RF_Acumulado','Cantidad_RF', "%_XGB_Acumulado",'Cantidad_XGB'];
    cant=[0,0,0]
    k=[5,10,15,20,25,50]
    for lim in range(0,len(k)):
        if k[lim]==5:
            inter=f"|Error|<={k[lim]} %"
        else: 
            inter=f"{k[lim-1]}% <|Error|<={k[lim]}%"
        porcentaje3=100*sum(map(lambda x : (x>=-k[lim]) & (x<=k[lim]), rr3))/len(rr3)
        cantidad3=sum(map(lambda x : (x>=-k[lim]) & (x<=k[lim]), rr3))-cant[0]
        porcentaje4=100*sum(map(lambda x : (x>=-k[lim]) & (x<=k[lim]), rr4))/len(rr4)
        cantidad4=sum(map(lambda x : (x>=-k[lim]) & (x<=k[lim]), rr4))-cant[1]
        cant=[cant[0]+cantidad3,cant[1]+cantidad4]


        tablaux = pd.DataFrame([[inter,porcentaje3,cantidad3,porcentaje4,cantidad4]],
                                columns=col);
        tabla=pd.concat([tabla, tablaux],ignore_index=True);
    return tabla

def Tabla_Estadistica_Error2(df): ## MOD X TENER RL Y ENET ##
    E_RForest=df.loc[:,"error RF"]
    E_XGB=df.loc[:,"error XGB"]
    Sobreestimados4=len(E_RForest[E_RForest>0])
    Sobreestimados5=len(E_XGB[E_XGB>0])
    
    Subestimados4=len(E_RForest[E_RForest<0])
    Subestimados5=len(E_XGB[E_XGB<0])
    
    Estimados4=Sobreestimados4+Subestimados4+len(E_RForest[E_RForest==0])
    Estimados5=Sobreestimados5+Subestimados5+len(E_XGB[E_XGB==0])
    
    Sobreestimados=[Sobreestimados4,Sobreestimados5]
    Subestimados=[Subestimados4,Subestimados5]
    Estimados=[Estimados4,Estimados5]
    
    tabla= pd.DataFrame.from_dict({'Método':[],
                                'Media': [],'Desviación estándar': [],
                                'Mediana': [],"Datos sobreestimados":[], "Datos subestimados":[],"Datos estimados":[]});
    col = ['Método','Media','Desviación estándar','Mediana',"Datos sobreestimados",
        "Datos subestimados","Datos estimados"];
    inter=["Random Forest", "Xtreme Gradient Boosting"]
    media=[np.mean(E_RForest),np.mean(E_XGB)]
    ds=[np.std(E_RForest),np.std(E_XGB)] 
    med=[np.percentile(E_RForest,50),np.percentile(E_XGB,50)]

    for i in range(0,2):
        tablaux = pd.DataFrame([[inter[i],media[i],ds[i],med[i],Sobreestimados[i],Subestimados[i],
                                Estimados[i]]],
                                  columns=col);
        tabla=pd.concat([tabla, tablaux],ignore_index=True);
    return tabla

def ModeloFinal(reml2,scls,models,cve): ### MOD X TENER RL Y ENET
    RF_er=np.array(list(map(abs, reml2['% de error RF'].tolist())))
    XGB_er=np.array(list(map(abs, reml2['% de error XGB'].tolist())))
    L=[RF_er,XGB_er] #Lista 

    a3=len(RF_er[RF_er>17])
    a4=len(XGB_er[XGB_er>17])
    
    p=min(a3,a4)
    
    if p==a3: 
        print("El método escogido de manera definitiva será el de Random Forest. Se procede a guardarlo")
        scaler_file = "escala_Final_"+str(cve)+".save"
        joblib.dump(scls[0], scaler_file)
        joblib.dump(models[0],"Modelo_Final_"+str(cve)+".joblib")
        return models[0],scls[0]
    
    elif p==a4: 
        print("El método escogido de manera definitiva será el Extreme Gradient Boosting. Se procede a guardarlo")
        scaler_file = "escala_Final_"+str(cve)+".save"
        joblib.dump(scls[1], scaler_file)
        joblib.dump(models[1],"Modelo_Final_"+str(cve)+".joblib")
        return models[1],scls[1]