################ # Secretaría de Movilidad y Agencia Digital de Innovación Pública # # 29 de mayo de 2019 ############### # importar las bibliotecas necesarias import random import pandas as pd import itertools import time import sys import matplotlib.pyplot as plt ###import seaborn as sns import math import numpy as np %matplotlib inline def encuentra_equilibrio_desde_archivo( df_entrada,max_unidades = 1750, graficar = True): ########## # función que lee un conunto de ofertas y encuentra la combinación de ellas que # maximiza la recaudación en primer lugar y el orden de preferencia de las empresas # -------------------------------------------------------- # Entradas: # graficar - variable lógica que indica si se debe mostrar la gráfica de distribución por empresa # max_unidades - número máximo de unidades permitidas por empresa (1750 para patines y 2400 para bicicletas) # df_entrada - dataframe que contiene una observación para cada oferta de cada empresa # este debe tener los siguientes variables: # empresa -- nombre de la empresa # unidades -- cantidad de unidades de la oferta # pago -- contraprestación por unidad # peso -- orden de prioridad de la oferta donde 1 es la más prioritaria # ------------------------------------------------------ # Salidas: # winner - lista que contiene la pareja de ofertas ganadoras en su primera posición (winner[0]), # el total de unidades de la combinación ganadora en su segunda posición (winner[1]), # la recaudación total de la combinación ganadora en su tercera posición (winner[2]) # el peso promedio de las ofertas de la combinación ganadora en su cuarta posición (winner[3]) # # df - dataframe que tiene una observación por empresa con su nombre y su conjunto de ofertas ########### start = time.time() ############# # generar un dataframe de ofertas que tendrá una observación por cada empresa # los campos serán: # nombre - nombre de la empresa # oferta - lista cuyos elementos son cada oferta, estas a su vez son listas por la siguiente estructura: # [unidades,pago,peso,nombre] ############# #comenzamos con un dataframe vacío df = pd.DataFrame() #iteramos sobre los nombres de empresas for i in list(set(df_entrada['empresa'])): #hacemos un subconjunto por empresa subset = df_entrada.loc[df_entrada['empresa'] == i,].copy(deep = True) subset.reset_index(inplace = True) #inicializar una lista vacía donde se guardarán las ofertas lista_ofertas = [] #iterar sobre las ofertas de cada empresa for j in subset.index: #guardar cada oferta como una lista dentro de la lista_ofertas oferta = [subset.loc[j,'unidades'] , subset.loc[j,'pago'] , subset.loc[j,'peso'] , subset.loc[j, 'empresa'] ] lista_ofertas.append(oferta) #guardar una entrada por cada empresa en df df = pd.concat([df,pd.DataFrame({'nombre' : [i] , 'oferta' : [lista_ofertas]})]) ######### # encontrar la combinación de ofertas óptima # ############ #calcular conjunto de todas las combinaciones de ofertas posibles S = list(itertools.product(*df['oferta'])) #empezamos con una lista tmp vacía tmp = [] #iteramos sobre cada posible combinación de ofertas for s in S: totBikes = 0 totUtility = 0 totWeight = 0 participants = 0 #iterar sobre las ofertas de cada combinación for bid in s: #se suman la cantidad de bicicletas y la utilidad a partir de cada oferta bikes = bid[0] utilityPerBike = bid[1] weight = bid[2] totBikes += bikes totUtility += bikes * utilityPerBike #se calcula el peso promedio de la combinación if bikes > 0: participants += 1 totWeight += weight totWeight = totWeight/participants if participants > 0 else 0 #se guarda toda la información de la combinación en tmp tmp.append((s, totBikes, totUtility, totWeight)) #para encontrar la combinación óptima se hacen una serie de filtros S = tmp # Filtrar por número total de unidades S = [s for s in S if s[1] <= 2400/0.5] # bicis #S = [s for s in S if s[1] <= 1750/0.5] # monopatines print("Número total de combinaciones con total de bicicletas dentro del rango:", len(S)) # Filtrar por utilidad máxima maxUtility = max(S, key = lambda s : s[2])[2] S = [s for s in S if s[2] == maxUtility] print("Número total de combinaciones que maximizan la utilidad:", len(S)) # Filtrar por peso mínimo minWeight = min(S, key = lambda s : s[3])[3] S = [s for s in S if s[3] == minWeight] print("Número total de combinaciones que minimizan el peso:", len(S)) for s in S: print(s) print() # después de estos filtros, si S tiene más de un elemento, se escoge uno aleatoriamente #winner = random.choice(S) #Imprimir en pantalla la información sobre la combinación ganadora #print("Combinación ganadora:") #print(winner[0]) #print("Número de unidades:") #print(winner[1]) #print("Contraprestación total:") #print(winner[2]) #guardar el tiempo de ejecución #end = time.time() #print (end - start) ################################### # para probar con ofertas importadas desde un archivo .csv # es importante que las ofertas consideren las restricciones sobre la contraprestación mínima y el número de unidades máximas # ya que el algoritmo no hace la validación sobre estas restricciones # la SEMOVI se encarga de verificar que las ofertas introducidas sean válidas al momento de ingresarlas al archivo con el # que se determina el resultado ### leer desde la ruta local df_entrada = pd.read_csv('./prueba_bicis.csv') # correr encuentra_equilibrio_desde_archivo encuentra_equilibrio_desde_archivo( df_entrada,max_unidades=2400, graficar = True)