def similarity(p1, p2):
    '''
    :param p1: dictionary with the data for a house
    :param p2: dictionary with the data for a house
        
    :returns: a float with the distance between the houses in the input. 
    NOTE: the bigger the number the less the similarity among p1 and p2
    
    Example: 
        In [8]: similarity({"lat" : 123123.2, "lng" : 12321.2, "gross_size" : 123312},
                   {"lat" : 234323.2, "lng" : 2321.2, "gross_size" : 23423})
        Out[8]: 149810.7216490195
    ''' 
    return terceroB_ML_banks.pos2dist(p1["lat"], p1["lng"], p2["lat"], p2["lng"])  
def train(input_data, house_type="flat", house_subtype=""):
    '''
    :param input_data: Pandas data frame with the input data
    :param house_type: string with the type of the houses corresponding to 
    this model, houses in input_data with other type will be discarded
    :param house_subtype: currently not used, in the future will be used
    like house_type
    
    :returns a pair of dictionaries:
        * the first one is a dictionary for the output model
        * the second one is a dictionary for the error metrics
    '''    

    original_size = len(input_data)
    
    # Data load    
    #   Drop houses which are not flats
    #   NOTE: originally the encoding flat = 1, house = 2, office = 3 was used
    #   that encoding was probably originated by an enumeration performed by numpy
    input_data = input_data[input_data["type"] == house_type]
    type_size = len(input_data)
    if (len(house_subtype) > 0):
        input_data = input_data[input_data["subType"] == house_subtype]
    subtype_size = len(input_data)

    T = len(input_data)
    
    #   latitud y longitud tienen que ser números decimales (ojo con el formato idealista)
    lat   = np.array(input_data["lat"], np.float) 
    lgt   = np.array(input_data["lng"], np.float)  
    m2    = np.array(input_data["gross_size"], np.float)
    price = np.array(input_data["price"], np.float)
    rooms = np.array(input_data["rooms"], np.float) 
    planta= np.array(input_data["planta"],np.float)
    
    dist=np.zeros((T,T))
    
    for i in range(T):
       dist[i][i]=float('Inf')
       for j in range(i+1, T):
           dist[i][j]=dist[j][i]=terceroB_ML_banks.pos2dist(lat[i],lgt[i],lat[j],lgt[j])

    ##ahora ordenamos la matriz por vecindades
    n_vecinos=min(T-1, 6)
    index_vecinos=dist.argsort(axis=1)[:,0:n_vecinos]
    
    ############################# SAMPLE ###########################

    Tr=round(0.75*T) #Muestra de entrenamiento
    
    priceTr=price[0:Tr]
    priceTs=price[Tr+1:T]
    
    # [JB] Only take into account the training samples for "planta"
    Splanta=np.sum(planta[0:Tr])
    
    ############################ MEDIANAS ###########################
    
    #obtengo la mediana de los seis vecinos
    medianvec=np.median(price[index_vecinos], axis=1)

    #genero las variables en desviaciones con sus medianas
    desvm2=m2-np.median(m2[index_vecinos], axis=1)
    desvroom=rooms-np.median(rooms[index_vecinos], axis=1)
    if Splanta > 0:
        desvplanta=planta-np.median(planta[index_vecinos], axis=1)
    else:
        desvplanta=0
        
    ###########################################################################
    # Base models
    ###########################################################################
    
    #genero la primera matriz de regresores donde utilizamos la mediana de los 
    #seis vecinos las variables hedonicas y sus interacciones
    
    REG_1=np.column_stack((
        medianvec,m2,rooms,
        medianvec*m2,
        medianvec*rooms,
        m2*rooms))

    # estimo los parámetros
    param_1=np.transpose(sm.OLS(priceTr,REG_1[:Tr,:]).fit().params)
    priceEstAll_1=np.dot(REG_1,param_1)
    
    # compruebo la bondad de los modelos
    errorpc_1=np.median(abs(priceTs-priceEstAll_1[Tr+1:T])/priceTs)
    
    # initialize with Inf
    errorpc_1p=float('Inf')
    
    if Splanta > 0:
    
        REG_1p=np.column_stack((
            medianvec,m2,rooms,planta,
            medianvec*m2,
            medianvec*rooms,
            medianvec*planta,
            m2*rooms,
            m2*planta,
            rooms*planta))

        # estimo los parámetros
        param_1p=np.transpose(sm.OLS(priceTr,REG_1p[:Tr,:]).fit().params)
        priceEstAll_1p=np.dot(REG_1p,param_1p)
        
        # compruebo la bondad de los modelos
        errorpc_1p=np.median(abs(priceTs-priceEstAll_1p[Tr+1:T])/priceTs)
    
    ###########################################################################
    # Adjusted models
    ###########################################################################
    
    # para los siguientes modelos, tengo que trabajar con los datos
    desvm2plus=np.zeros((T))
    desvm2min=np.zeros((T))
    desvroomplus=np.zeros((T))
    desvroommin=np.zeros((T))
    if Splanta > 0:
        desvplantaplus=np.zeros((T))
        desvplantamin=np.zeros((T))
    
    # genero las variables que miden asimetrias positivas frente a negativas
    for i in range(T):
        if desvm2[i]>0:
            desvm2plus[i]=desvm2[i]
        if desvm2[i]<0:
            desvm2min[i]=desvm2[i]  
        if desvroom[i]>0:
            desvroomplus[i]=desvroom[i]
        if desvroom[i]<0:
            desvroommin[i]=desvroom[i]
        if (Splanta > 0 and desvplanta[i]>0):
            desvplantaplus[i]=desvplanta[i]
        if (Splanta > 0 and desvplanta[i]<0):
            desvplantamin[i]=desvplanta[i]

    ####utilizo como regresores la previsión del modelo 1 y las desviaciones    
    REG_2=np.column_stack((
        priceEstAll_1,
        desvm2plus,desvm2min,
        desvroomplus,desvroommin))

    param_2=np.transpose(sm.OLS(priceTr,REG_2[:Tr,:]).fit().params)
    priceEstTs_2=np.dot(REG_2[Tr+1:T,:],param_2)
    
    # compruebo la bondad de los modelos
    errorpc_2=np.median(abs(priceTs-priceEstTs_2)/priceTs)    
    
    # initialize with Inf
    errorpc_2p=float('Inf')
    
    if Splanta > 0:
    
        REG_2p=np.column_stack((
            priceEstAll_1p,
            desvm2plus,desvm2min,
            desvroomplus,desvroommin,
            desvplantaplus,desvplantamin))

        param_2p=np.transpose(sm.OLS(priceTr,REG_2p[:Tr,:]).fit().params)
        priceTsEstTs_2p=np.dot(REG_2p[Tr+1:T,:],param_2p)
        
        # compruebo la bondad de los modelos
        errorpc_2p=np.median(abs(priceTs-priceTsEstTs_2p)/priceTs)        
    
    ###########################################################################
    # Build the return value, which is a pair of dictionaries:
    ###########################################################################

    #ordeno los errores de menor a mayor
    index_err=np.array([errorpc_1,errorpc_1p,errorpc_2,errorpc_2p]).argsort()
 
    #  the first one is a dictionary for the output model
    if Splanta > 0:
        model_dict = {"param_1" : param_1.tolist(), "param_1p": param_1p.tolist(), 
                      "param_2" : param_2.tolist(), "param_2p": param_2p.tolist(),
                      "index_err": index_err.tolist()}
    else:
        model_dict = {"param_1" : param_1.tolist(), "param_1p": [], 
                      "param_2" : param_2.tolist(), "param_2p": [],
                      "index_err": index_err.tolist()}
                       
    #  the second one is a dictionary for the error metrics
    metrics_dict = {"errorpc_1" : errorpc_1, "errorpc_1p" : errorpc_1p, 
                    "errorpc_2" : errorpc_2, "errorpc_2p" : errorpc_2p,
                    "size" : original_size, 
                    "type" : type_size, "subt" : subtype_size,
                    "train" : Tr, "test" : (T-Tr)}
                    
    return (model_dict, metrics_dict)