<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Python archivos &#187; Ingenieria Industrial Online</title>
	<atom:link href="https://ingenieriaindustrialonline.com/tag/python/feed/" rel="self" type="application/rss+xml" />
	<link>https://ingenieriaindustrialonline.com/tag/python/</link>
	<description>ingenieriaindustriaonline.com</description>
	<lastBuildDate>Fri, 20 Jan 2023 18:31:04 +0000</lastBuildDate>
	<language>es</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.3</generator>

<image>
	<url>https://ingenieriaindustrialonline.com/wp-content/uploads/2019/06/cropped-faVicon-32x32.png</url>
	<title>Python archivos &#187; Ingenieria Industrial Online</title>
	<link>https://ingenieriaindustrialonline.com/tag/python/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Programación de empleados mediante programación entera</title>
		<link>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-de-empleados-mediante-programacion-entera/</link>
					<comments>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-de-empleados-mediante-programacion-entera/#respond</comments>
		
		<dc:creator><![CDATA[Bryan Salazar López]]></dc:creator>
		<pubDate>Sat, 27 Nov 2021 18:27:47 +0000</pubDate>
				<category><![CDATA[Investigación de operaciones]]></category>
		<category><![CDATA[Logística]]></category>
		<category><![CDATA[Asignación]]></category>
		<category><![CDATA[Google OR-Tools]]></category>
		<category><![CDATA[Optimización]]></category>
		<category><![CDATA[Programación de empleados]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Scheduling]]></category>
		<category><![CDATA[Turnos]]></category>
		<guid isPermaLink="false">https://ingenieriaindustrialonline.com/?p=29010</guid>

					<description><![CDATA[<p>Las organizaciones que gestionan operaciones cada vez más robustas, requieren en cierto modo de la asignación de personas y recursos a tareas específicas. Desde hace algún tiempo se ha popularizado un planteamiento en torno al objetivo de la logística, generalmente aceptado como: «El objetivo de la logística consiste en llevar el producto correcto, en la &#8230;</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-de-empleados-mediante-programacion-entera/">Programación de empleados mediante programación entera</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></description>
										<content:encoded><![CDATA[
		<div id="introduccion" data-title="Introducción" class="index-title"></div>
	
<p>Las organizaciones que gestionan operaciones cada vez más robustas, requieren en cierto modo de la asignación de personas y recursos a tareas específicas. Desde hace algún tiempo se ha popularizado un planteamiento en torno al objetivo de la logística, generalmente aceptado como: «<em>El objetivo de la logística consiste en llevar el producto correcto, en la cantidad correcta, en el lugar correcto, en el momento correcto</em>» (acepta variaciones).</p>
<p>Ahora bien, tendencias de optimización abordan nuevos objetivos y plantean nuevos desafíos alrededor de la intralogística: <em>optimización extrema</em>. Es decir, optimización en la asignación de recursos internos, como por ejemplo: <em>el recurso correcto, asignado a la tarea correcta, en el momento correcto</em>.</p>
<p>Cuando aterrizamos el anterior planteamiento en términos prácticos, entendemos que se hace necesario resolver problemas de programación complejos de forma regular, que permitan la gestión de las operaciones de la organización. Un caso puntual consiste en la <strong>programación del recurso humano</strong>, sujeta a un conjunto complejo de restricciones y requisitos: <em>número de empleados, días laborales, turnos, tiempos inactivos, permisos, políticas internas, etc.</em></p>
<p>El planteamiento de nuevos desafíos de optimización, guarda una estrecha relación con la posibilidad que nos brinda la tecnología de abordar estos retos. Si bien, la complejidad de las organizaciones es cada vez mayor, los algoritmos cada vez son más refinados, y los solucionadores cada vez más robustos e integrados.</p>
<p>El objetivo de este artículo consiste en utilizar las técnicas robustas del solucionador basado en restricciones <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/que-es-y-para-que-sirve-google-or-tools/"><strong>OR-Tools de Google</strong></a>, para resolver un problema de programación de empleados sujeto a un conjunto complejo de restricciones.</p>

		<div class="box info  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>Cuando cursaban el tercer año, el Director del Lakeside School, les planteó a Bill Gates y Kent Evans, un problema muy complejo que aun nadie lograba resolver.</p>
<p>Lakeside se había fusionado con una escuela femenil local, por lo tanto, el alumnado había crecido considerablemente, y nadie lograba descifrar cómo ajustar los horarios de clase en las nuevas condiciones. Las restricciones de la programación no eran pocas, por ejemplo:</p>
<ul>
<li>Ningún maestro podía impartir cuatro clases seguidas</li>
<li>Debían considerarse pausas para alimentación</li>
<li>La cantidad de alumnos por sesión de clase no podía pasar de 16</li>
<li>Algunas clases como, por ejemplo, las clases de música no podían asignarse en salones conjuntos.</li>
</ul>
<p>La complejidad del problema hizo que Gates buscara la ayuda de Paul Allen, con quien lograría resolver el modelo. Este fue quizá, el algoritmo que les abrió un camino comercial, puesto que otras organizaciones que tenían problemas de programación, comenzarían a acudir a esta pareja de estudiantes.</p>
<p>Bill Gates y Paul Allen fundaron en 1975 la compañía Microsoft, el resto es historia. 
			</div>
		</div>
	
<hr />
<h2>Caso de aplicación</h2>

		<div id="caso-de-aplicacion" data-title="Caso de aplicación" class="index-title"></div>
	
<blockquote class=" quote-simple "><p>La compañía Stark Lab ha adquirido un nuevo torno para su área de mecanizado. El volumen de trabajo que tiene el área, demanda que esta nueva máquina sea operada en los 3 turnos del día (mañana, tarde y noche).</p>
<p>La compañía ha contratado a 3 trabajadores para operar la nueva máquina, y planea emplear a un aprendiz (estudiante) como cuarto operario.</p>
<p>El supervisor debe diseñar una programación para los cuatro operarios para un periodo de 7 días (una semana), sujeto a las siguientes condiciones:</p>
<ul>
<li>Cada día se divide en tres turnos de 8 horas.</li>
<li>Todos los días, cada turno se asigna a un solo operario, y ningún operario trabajará más de un turno por día.</li>
<li>El último día de la programación se realizan actividades de mantenimiento y limpieza profunda en planta. Por tal razón, solo se trabaja el turno de la mañana.</li>
<li>El operario aprendiz (estudiante), cuenta con la colaboración de la compañía para realizar sus estudios. Por tal razón, no puede trabajar el tercer día de la semana en el turno de la noche.</li>
<li>Como mínimo deben trabajarse 19 turnos en total en la programación. Esta cantidad de turnos ya considera los turnos destinados a mantenimiento y limpieza del último día de la programación.</li>
<li>En el caso en el que no se pueda realizar una distribución de turnos igualitaria, la distribución de los turnos debe realizarse de manera uniforme. Esto quiere decir que no puede existir una diferencia mayor a un turno entre las asignaciones para cada operario.</li>
</ul>
</blockquote>
<p>Se desea desarrollar una programación de empleados (asignación de empleados, turnos y días), que cumpla con las restricciones del planteamiento.</p>
<h3>¿Qué necesitaremos?</h3>

		<div id="que-necesitaremos" data-title="¿Qué necesitaremos?" class="index-title"></div>
	
<p>En el desarrollo de este ejercicio emplearemos:</p>

		<div class="plus tie-list-shortcode">
<div class="plus tie-list-shortcode">
<ul>
<li><em><strong>Colaboratory</strong>:<span> </span></em>Este es un entorno de programación y ejecución virtual de Python desarrollado por Google. Nos permitirá no tener la necesidad de realizar ninguna instalación en nuestros equipos. Todo lo que desarrollemos lo ejecutaremos en un cuaderno virtual.</li>
<li><strong><em>Python</em></strong>: Este será el lenguaje de programación que vamos a utilizar, y advertimos: No es necesario tener conocimientos previos, y el objetivo del artículo no es convertirnos en programadores expertos. Utilizaremos fragmentos de códigos, librerías disponibles, y explicaremos lo necesario para configurar nuestro desarrollo de acuerdo a los objetivos específicos de nuestros modelos.</li>
<li><strong><em>ORTools</em></strong>: OR-Tools es un paquete de software portable de código abierto desarrollado por Google, para resolución de problemas de optimización.</li>
</ul>

		</div>
	
</div>
<h3>Paso 1: Crear el entorno de trabajo en Colaboratory</h3>

		<div id="paso-1-crear-el-entorno-de-trabajo-en-colaboratory" data-title="Paso 1: Crear el entorno de trabajo en Colaboratory" class="index-title"></div>
	
<div id="paso-1-crear-el-entorno-de-trabajo-en-colaboratory" data-title="Paso 1: Crear el entorno de trabajo en Colaboratory" class="index-title visible full-visible"></div>
<p><span>Lo primero que vamos a hacer consiste en crear un entorno de trabajo en <em>Google</em> </span><em>Colaboratory</em><span>, así que vayamos allá: </span><a href="https://colab.research.google.com/#create=true" target="_blank" rel="noopener"><em><strong>Abrir cuaderno nuevo</strong></em></a><span>.</span></p>
<p>Verán que tienen un lienzo para programar el modelo, así que en este cuaderno podemos ir generando las líneas de código que explicaremos en los pasos siguientes.</p>
<h3>Paso 2: Instalar Google Or Tools</h3>

		<div id="paso-2-instalar-google-or-tools" data-title="Paso 2: Instalar Google Or Tools" class="index-title"></div>
	
<p>Es necesario instalar la librería de Google Or Tools en nuestro entorno de <em>Colaboratory</em> para poder utilizar nuestro modelo de programación entera.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>!pip install ortools</code></pre>
</div>
<p>Al ejecutar esta instrucción instalaremos el software del solucionador de Google.</p>
<h3>Paso 3: Importar las librerías necesarias</h3>

		<div id="paso-3-importar-las-librerias-necesarias" data-title="Paso 3: Importar las librerías necesarias" class="index-title"></div>
	
<p>Este modelo empleará programación entera, y por lo tanto instalaremos las librerías dispuestas por Google OR Tools para ello. En este caso utilizaremos un solucionador de programación entera llamado CP-SAT.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Importar la librería para utilizar CP-SAT
from ortools.sat.python import cp_model</code></pre>
</div>
<p><span>De esta manera, tenemos todo lo necesario para empezar a desarrollar nuestro código.</span></p>
<h3>Paso 4: Crear los datos de entrada del modelo</h3>

		<div id="paso-4-crear-los-datos-de-entrada-del-modelo" data-title="Paso 4: Crear los datos de entrada del modelo" class="index-title"></div>
	
<p>Los datos de entrada de un problema de programación de empleados suelen ser simples, la complejidad recae en el modelamiento de las restricciones. En este caso, los datos de entrada básicos son: <em>número de empleados, número de turnos por día, número de días del plan de programación</em>.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Datos de entrada del modelo
operarios = 4
turnos = 3
dias = 7
total_operarios = range(operarios)
total_turnos = range(turnos)
total_dias = range(dias)

turnos_no_laborales = 2
turnos_laborales = (turnos * dias) - turnos_no_laborales</code></pre>
</div>
<p>Así mismo, definimos el número de turnos no laborales, recordemos que el planteamiento del problema nos indicó que el último día se trabajaría tan solo un turno (por labores de mantenimiento y limpieza), por lo tanto, dos turnos pueden considerarse como no laborales.</p>
<p>¿Por qué utilizamos una variable llamada <em>operarios </em>y otra llamada <em>total_operarios</em>? Además, ¿Para qué se emplea la función <em>range</em>? Bueno, la variable <em>operarios </em>contendrá el valor entero de la cantidad de operarios del modelo; mientras tanto, la variable <em>total_operarios</em> dado que emplea la función <em>range</em>, contendrá la secuencia de números que nos ayudará a definir a cada operario, lo hace de esta manera: range(4) = 0, 1, 2, 3. Como podemos ver, inicia la secuencia en 0, y finaliza en el entero anterior al parámetro dado, en este caso el parámetro dado fue 4, por lo tanto, el entero anterior será 3.</p>
<h3>Paso 5: Crear el modelo de programación</h3>

		<div id="paso-5-crear-el-modelo-de-programacion" data-title="Paso 5: Crear el modelo de programación" class="index-title"></div>
	
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Crear el modelo
model = cp_model.CpModel()</code></pre>
</div>
<h3>Paso 6: Crear las variables de asignación</h3>

		<div id="paso-6-crear-las-variables-de-asignacion" data-title="Paso 6: Crear las variables de asignación" class="index-title"></div>
	
<p>El problema de programación de empleados es un caso de asignación, y por lo tanto, empleamos variables de decisión de asignación. Veamos:</p>
<p>Necesitamos definir <em>qué operario</em> será asignado a <em>qué turno</em> en <em>qué día </em>en específico. Por lo tanto podemos utilizar variables de asignación binarias, que tomen un valor de 1, si esa asignación específica tiene lugar, y tomen valor de 0, en el caso en el que no.</p>
<p>Algebraicamente sería algo así:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/algebra.png" alt="algebra" width="503" height="93" class="size-full wp-image-29015 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/algebra.png 503w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/algebra-300x55.png 300w" sizes="(max-width: 503px) 100vw, 503px" /></p>
<p>En el caso en el que tuviésemos que definir manualmente cada una de las variables de asignación, requeriríamos un total de 84 variables (7 días * 3 turnos * 4 operarios).</p>
<p>Veamos cómo podemos utilizar los ciclos en <em>Python </em>para simplificar la definición de las variables &#8211; Eso sí, en lugar de <em>X </em>llamaremos a la variable <em>asignacion</em>, y en lugar de <em>a, b </em>y<em> c, </em>utilizaremos la primera letra de cada parámetro.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># Creamos las variables de asignación
# asignacion[(o, d, t)]: operario 'o' trabaja en el turno 't' el día 'd'.
asignacion = {}
for o in total_operarios:
    for d in total_dias:
        for t in total_turnos:
            asignacion[(o, d, t)] = model.NewBoolVar('turno_n%id%is%i' % (o, d, t))</code></pre>
</div>
<p>De esta manera, haciendo uso de ciclos, creamos todas las variables de asignación del modelo; así mismo, definimos su naturaleza (<em>NewBoolVar = Variable booleana</em>). De manera que en el caso de que una asignación se efectúe su resultado será <em>«verdadero / True»</em>.</p>
<h3>Paso 7: Crear las restricciones del modelo</h3>

		<div id="paso-7-crear-las-restricciones-del-modelo" data-title="Paso 7: Crear las restricciones del modelo" class="index-title"></div>
	
<p>La complejidad de un problema de programación de recursos se define por la naturaleza de sus restricciones. Si bien las restricciones acotan el conjunto solución, y por ende los tiempos de procesamiento, la dificultad subyace del modelamiento. Veamos cómo abordar cada una de las restricciones que nos plantea el caso de aplicación.</p>
<p><strong>Cada turno se asigna a un solo operario</strong></p>
<p>La sumatoria de todas las asignaciones efectuadas en un día <em>d, </em>en el turno <em>t</em>, deben ser menores o iguales a 1. Eso restringe la posibilidad de que un mismo turno, en un mismo día, sea asignado a dos operarios diferentes.</p>
<p>Por ejemplo:</p>
<p style="text-align: center;">X<sub>000</sub> + X<sub>100</sub> + X<sub>200</sub> + X<sub>300</sub> &lt;= 1</p>
<p>Esta restricción nos diría que la sumatoria de todas las asignaciones del día <em>0</em>, para el turno <em>0</em>, de los operarios <em>0, 1, 2 y 3</em>; debe ser menor o igual a <em>1</em>. Es decir que el turno <em>0</em>, del día <em>0</em> no puede ser asignado a más de un operario.</p>
<p>Veamos cómo <em>Python </em>nos puede simplificar esta formulación de restricciones:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># Cada turno es asignado a un operario en el periodo de programación, o no es asignado
for d in total_dias:
    for t in total_turnos:
        model.Add(sum(asignacion[(o, d, t)] for o in total_operarios) &lt;= 1)</code></pre>
</div>
<p>Esta restricción bien podría formularse como que la sumatoria deba ser igual a 1. Sin embargo, recordemos que existen un par de turnos no laborales, y por lo tanto estos no deben ser asignados.</p>
<p>En el caso en el que tuviésemos que definir manualmente cada una de estas restricciones, requeriríamos un total de 21 restricciones (7 días * 3 turnos).</p>
<p><strong>Cada operario trabaja como máximo un turno por día</strong></p>
<p>La sumatoria de todas las asignaciones efectuadas en un día <em>d, </em>para el operario <em>o</em>, deben ser menores o iguales a 1. Eso restringe la posibilidad de que un operario, en un mismo día, sea asignado a dos turnos diferentes.</p>
<p>Por ejemplo:</p>
<p style="text-align: center;">X<sub>000</sub> + X<sub>001</sub> + X<sub>002</sub> &lt;= 1</p>
<p>Esta restricción nos diría que la sumatoria de todas las asignaciones del día <em>0</em>, para el operario <em>0</em>, de los turnos <em>0, 1, y 2</em>; debe ser menor o igual a <em>1</em>. Es decir que el operario <em>0</em>, del día <em>0</em> no puede ser asignado a más de un turno.</p>
<p>Veamos cómo <em>Python </em>nos puede simplificar esta formulación de restricciones:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># Cada operario trabaja como máximo un turno por día
for o in total_operarios:
    for d in total_dias:
        model.Add(sum(asignacion[(o, d, t)] for t in total_turnos) &lt;= 1)</code></pre>
</div>
<p>En el caso en el que tuviésemos que definir manualmente cada una de estas restricciones, requeriríamos un total de 28 restricciones (7 días * 4 operarios).</p>
<p><strong>Restricción del operario aprendiz</strong></p>
<p>De acuerdo al caso de aplicación, <em>el operario aprendiz no pude trabajar el tercer día de la semana, en el turno de la noche</em>. Esto nos permite abordar restricciones de requerimientos puntuales sobre el modelo.</p>
<p>Recordemos que el índice, tanto de operarios, turnos y días, comienza en 0. Por lo tanto, para efectos de la formulación de la restricción, el tercer día de la semana será el día 2 (los siete días de la semana son 0, 1, 2, 3, 4, 5 y 6); y el turno de la noche, será el turno 2 (turnos 0, 1 y 2 / mañana, tarde y noche).</p>
<p>Por otro lado, los operarios también son nombrados de acuerdo a sus índices: 0, 1, 2 y 3. Podemos arbitrariamente elegir el índice del operario aprendiz; en nuestro caso diremos que se trata del operario 0.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Restricción del operario (o) 1 para trabajar el día (d) 2, en el turno (t) 2
model.Add(asignacion[(0, 2, 2)] != 1)</code></pre>
</div>
<p>El operador != se utiliza para denotar <em>diferente de</em>, por lo tanto, la restricción indica que la asignación del operario 0, el día 2, en el turno 2, deberá ser diferente de 1; es decir que, al tratarse de una variable booleana, deberá ser <em>Falso = 0.</em></p>
<p><strong>Restricciones de turnos no programados (mantenimiento y limpieza)</strong></p>
<p>De acuerdo al caso de aplicación, el último día de la semana se programan actividades de mantenimiento y limpieza, y que por lo tanto, solo se trabaja en el turno de la mañana. Por esta razón, debemos excluir a los turnos de la tarde y la noche de la programación (turnos <em>1</em> y <em>2</em>) del último día (día 6).</p>
<p>Siguiendo con nuestras denominaciones algebraicas de ejemplo, tendríamos algo así:</p>
<p style="text-align: center;">X<sub>061</sub> + X<sub>161</sub> + X<sub>261</sub> + X<sub>361</sub> == 0</p>
<p style="text-align: center;">X<sub>062</sub> + X<sub>162</sub> + X<sub>262</sub> + X<sub>362</sub> == 0</p>
<p>Veamos cómo <em>Python </em>nos puede simplificar esta formulación de restricciones:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># Restricción de turnos no programados
model.Add(sum(asignacion[(o, 6, 1)] for o in total_operarios) == 0)
model.Add(sum(asignacion[(o, 6, 2)] for o in total_operarios) == 0)</code></pre>
</div>
<p><strong>Restricción de turnos mínimos programados</strong></p>
<p>De acuerdo al caso de aplicación, debe programarse una cantidad de 19 turnos laborales. Es decir, la cantidad de turnos diarios (3) * la cantidad de días del programa (7); menos la cantidad de turnos no programados por mantenimiento y limpieza (2).</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Como mínimo deben trabajarse "turnos_laborales"
min_turnos_totales = []
for o in total_operarios:
    for d in total_dias:
        for t in total_turnos:
            min_turnos_totales.append(asignacion[(o, d, t)])
model.Add(sum(min_turnos_totales) &gt;= turnos_laborales)</code></pre>
</div>
<p>Esta restricción involucra a todas las variables de asignación del modelo, y por lo tanto el uso de ciclos es importante para simplificar el desarrollo de la formulación. Básicamente indica que la sumatoria de todas las variables de decisión (asignación) del modelo (que son booleanas, y para este efecto podemos decir que binarias), debe ser mayor o igual al mínimo de turnos laborales exigidos para programación.</p>
<p>La restricción es 1 sola, pero involucra las 84 variables de asignación.</p>
<p><strong>Restricción de uniformidad en la distribución de turnos</strong></p>
<p>El objetivo de estas restricciones es la de distribuir de la manera más uniforme posible los turnos asignados. En términos prácticos, el caso de aplicación indica que, no puede existir una diferencia mayor a un turno entre las asignaciones de los operarios. Es decir, ningún operario podrá tener asignados dos turnos más que otro operario, por ejemplo.</p>
<p>Para lograr esto es necesario realizar una serie de cálculos intermedios sencillos, con el objetivo de identificar: <em>número de turnos asignables, cantidad mínima de turnos por operario, cantidad máxima de turnos por operario, entre otros</em>.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>min_turnos_por_operario = ((turnos * dias) - turnos_no_laborales) // operarios
if ((turnos * dias) - turnos_no_laborales) % operarios == 0:
    max_turnos_por_operario = min_turnos_por_operario
else:
    max_turnos_por_operario = min_turnos_por_operario + 1
for o in total_operarios:
    num_turnos_trabajados = []
    for d in total_dias:
        for t in total_turnos:
            num_turnos_trabajados.append(asignacion[(o, d, t)])
    model.Add(min_turnos_por_operario &lt;= sum(num_turnos_trabajados))
    model.Add(sum(num_turnos_trabajados) &lt;= max_turnos_por_operario)</code></pre>
</div>
<p>Lo primero que hicimos fue calcular la parte entera de la fracción entre el <em>número de turnos asignables</em> y los operarios; lo que nos indica la <em>cantidad mínima de turnos por operario</em>. Veamos con un ejemplo:</p>
<p style="text-align: center;">(turnos * días) &#8211; turnos no laborales // operarios</p>
<p style="text-align: center;">(3 turnos por día * 7 días) &#8211; 2 turnos // 4 operarios</p>
<p style="text-align: center;">(3 * 7) &#8211; 2 // 4</p>
<p style="text-align: center;">19 // 4 = 4 turnos por operario</p>
<p>Esto indica que existe la posibilidad de asignar al menos 4 turnos por cada operario. Lo siguiente que se debe considerar es la cantidad máxima de turnos por operario.</p>
<p>En el caso en el cual la cantidad que resulte de dividir el número de turnos asignables entre el número de operarios sea entera sin decimales, esto indicaría que la distribución de los turnos puede realizarse en partes iguales, y por lo tanto <em>la cantidad máxima de turnos por operario </em>será equivalente a <em>la cantidad mínima de turnos por operario</em>.</p>
<p>En los casos en los que esto no ocurra, <em>la cantidad máxima de turnos por operario</em> será equivalente a <em>la cantidad mínima de turnos por operario </em>más 1. Recordemos que la restricción nos indica que la diferencia entre asignaciones de turnos a operarios no puede ser mayor de un turno.</p>
<p>Por todo lo demás, las restricciones de este paso consisten en establecer que la cantidad de turnos asignados por operario deberá ser mayor que <em>la cantidad mínima de turnos por operario </em>y deberá ser menor que <em>a cantidad máxima de turnos por operario</em><em>.</em></p>
<h3>Paso 8: Crear el solucionador y enumerar las soluciones</h3>

		<div id="paso-8-crear-el-solucionador-y-enumerar-las-soluciones" data-title="Paso 8: Crear el solucionador y enumerar las soluciones" class="index-title"></div>
	
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># Creal (invocar) el solucionador y resolver
solver = cp_model.CpSolver()
solver.parameters.linearization_level = 0
# Enumerar las posibles soluciones
solver.parameters.enumerate_all_solutions = True</code></pre>
</div>
<h3>Paso 9: Configurar las salidas del modelo</h3>

		<div id="paso-9-configurar-las-salidas-del-modelo" data-title="Paso 9: Configurar las salidas del modelo" class="index-title"></div>
	
<p>Este paso consiste en configurar el formato de salida de las soluciones posibles. De forma arbitraria hemos determinado que se impriman las primeras 2 soluciones (<em>solution_limit</em>), de tal manera que el formato de salida tendrá la siguiente estructura:</p>
<ul>
<li>Solución <em>i</em>
<ul>
<li>Día <em>d</em>
<ul>
<li>Operario <em>o, </em>trabaja / no trabaja, en el turno <em>t.</em></li>
</ul>
</li>
</ul>
</li>
</ul>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>class OperariosParcialesSolucion(cp_model.CpSolverSolutionCallback):
    """Print intermediate solutions."""

    def __init__(self, asignacion, operarios, dias, turnos, limit):
        cp_model.CpSolverSolutionCallback.__init__(self)
        self._asignacion = asignacion
        self._operarios = operarios
        self._dias = dias
        self._turnos = turnos
        self._solution_count = 0
        self._solution_limit = limit

    def on_solution_callback(self):
        self._solution_count += 1
        print('Solución %i' % self._solution_count)
        for d in range(self._dias):
            print('Día %i' % d)
            for o in range(self._operarios):
                is_working = False
                for t in range(self._turnos):
                    if self.Value(self._asignacion[(o, d, t)]):
                        is_working = True
                        print('  Operario %i trabaja en el turno %i' % (o, t))
                if not is_working:
                    print('  Operario {} no trabaja'.format(o))
        if self._solution_count &gt;= self._solution_limit:
            print('Detenga la búsqueda después de  %i soluciones' % self._solution_limit)
            self.StopSearch()

    def solution_count(self):
        return self._solution_count

# Muestra las primeras 2 soluciones.
solution_limit = 2
solution_printer = OperariosParcialesSolucion(asignacion, operarios, dias, turnos,
                                                    solution_limit)</code></pre>
</div>
<h3>Paso 10: Invocar al solucionador y configurar algunas estadísticas</h3>

		<div id="paso-10-invocar-al-solucionador-y-configurar-algunas-estadisticas" data-title="Paso 10: Invocar al solucionador y configurar algunas estadísticas" class="index-title"></div>
	
<p>Por último, nos queda invocar al solucionador, y configurar algunos datos estadísticos para enriquecer las salidas del modelo.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>solver.Solve(model, solution_printer)

# Estadísticas.
print('\nEstadísticas')
print('  - conflictos      : %i' % solver.NumConflicts())
print('  - ramas de búsqueda       : %i' % solver.NumBranches())
print('  - tiempo de solución      : %f s' % solver.WallTime())
print('  - soluciones encontradas: %i' % solution_printer.solution_count())</code></pre>
</div>
<hr />
<p>Al ejecutar el programa completo, tendremos la siguiente salida:</p>
<ul>
<li><strong><em>Solución 1</em></strong>
<ul>
<li><em>Día 0</em>
<ul>
<li><em>Operario 0 no trabaja</em></li>
<li><em>Operario 1 trabaja en el turno 1</em></li>
<li><em>Operario 2 trabaja en el turno 2</em></li>
<li><em>Operario 3 trabaja en el turno 0</em></li>
</ul>
</li>
<li><em>Día 1</em>
<ul>
<li><em>Operario 0 no trabaja</em></li>
<li><em>Operario 1 trabaja en el turno 0</em></li>
<li><em>Operario 2 trabaja en el turno 2</em></li>
<li><em>Operario 3 trabaja en el turno 1</em></li>
</ul>
</li>
<li><em>Día 2</em>
<ul>
<li><em>Operario 0 trabaja en el turno 0</em></li>
<li><em>Operario 1 no trabaja</em></li>
<li><em>Operario 2 trabaja en el turno 2</em></li>
<li><em>Operario 3 trabaja en el turno 1</em></li>
</ul>
</li>
<li><em>Día 3</em>
<ul>
<li><em>Operario 0 trabaja en el turno 1</em></li>
<li><em>Operario 1 trabaja en el turno 0</em></li>
<li><em>Operario 2 trabaja en el turno 2</em></li>
<li><em>Operario 3 no trabaja</em></li>
</ul>
</li>
<li><em>Día 4</em>
<ul>
<li><em>Operario 0 trabaja en el turno 1</em></li>
<li><em>Operario 1 trabaja en el turno 0</em></li>
<li><em>Operario 2 no trabaja</em></li>
<li><em>Operario 3 trabaja en el turno 2</em></li>
</ul>
</li>
<li><em>Día 5</em>
<ul>
<li><em>Operario 0 trabaja en el turno 2</em></li>
<li><em>Operario 1 trabaja en el turno 0</em></li>
<li><em>Operario 2 no trabaja</em></li>
<li><em>Operario 3 trabaja en el turno 1</em></li>
</ul>
</li>
<li><em>Día 6</em>
<ul>
<li><em>Operario 0 trabaja en el turno 0</em></li>
<li><em>Operario 1 no trabaja</em></li>
<li><em>Operario 2 no trabaja</em></li>
<li><em>Operario 3 no trabaja</em></li>
</ul>
</li>
</ul>
</li>
<li><strong><em>Solución 2</em></strong>
<ul>
<li><em>Día 0</em>
<ul>
<li><em>Operario 0 no trabaja</em></li>
<li><em>Operario 1 trabaja en el turno 1</em></li>
<li><em>Operario 2 trabaja en el turno 2</em></li>
<li><em>Operario 3 trabaja en el turno 0</em></li>
</ul>
</li>
<li><em>Día 1</em>
<ul>
<li><em>Operario 0 no trabaja</em></li>
<li><em>Operario 1 trabaja en el turno 0</em></li>
<li><em>Operario 2 trabaja en el turno 2</em></li>
<li><em>Operario 3 trabaja en el turno 1</em></li>
</ul>
</li>
<li><em>Día 2</em>
<ul>
<li><em>Operario 0 trabaja en el turno 0</em></li>
<li><em>Operario 1 no trabaja</em></li>
<li><em>Operario 2 trabaja en el turno 2</em></li>
<li><em>Operario 3 trabaja en el turno 1</em></li>
</ul>
</li>
<li><em>Día 3</em>
<ul>
<li><em>Operario 0 trabaja en el turno 1</em></li>
<li><em>Operario 1 trabaja en el turno 0</em></li>
<li><em>Operario 2 trabaja en el turno 2</em></li>
<li><em>Operario 3 no trabaja</em></li>
</ul>
</li>
<li><em>Día 4</em>
<ul>
<li><em>Operario 0 trabaja en el turno 1</em></li>
<li><em>Operario 1 trabaja en el turno 0</em></li>
<li><em>Operario 2 no trabaja</em></li>
<li><em>Operario 3 trabaja en el turno 2</em></li>
</ul>
</li>
<li><em>Día 5</em>
<ul>
<li><em>Operario 0 trabaja en el turno 1</em></li>
<li><em>Operario 1 trabaja en el turno 0</em></li>
<li><em>Operario 2 no trabaja</em></li>
<li><em>Operario 3 trabaja en el turno 2</em></li>
</ul>
</li>
<li><em>Día 6</em>
<ul>
<li><em>Operario 0 trabaja en el turno 0</em></li>
<li><em>Operario 1 no trabaja</em></li>
<li><em>Operario 2 no trabaja</em></li>
<li><em>Operario 3 no trabaja</em></li>
</ul>
</li>
</ul>
</li>
</ul>
<p><em>Detenga la búsqueda después de 2 soluciones</em></p>
<p><em>Estadísticas</em><br />
<em>&#8211; conflictos : 973</em><br />
<em>&#8211; ramas de búsqueda : 1730</em><br />
<em>&#8211; tiempo de búsqueda : 0.044052 s</em><br />
<em>&#8211; soluciones encontradas: 2</em></p>
<hr />
<p>Hemos formulado un modelo con <strong>84 variables de decisión, 55 restricciones y algunos cálculos intermedios</strong>. Podemos observar cómo el modelo ha logrado encontrar soluciones que satisfacen las restricciones planteadas (formuladas). Del mismo modo, podemos observar el tiempo en el cual ha logrado el solucionador hallar la cantidad de soluciones solicitadas (fracciones de segundo).</p>
<p>Es interesante percibir las bondades de este tipo desarrollos, y considerar las posibilidades de aplicación en otros campos diferentes a la programación de empleados; teniendo en cuenta la flexibilidad de la programación basada en restricciones, la potencia de los solucionadores y la capacidad de procesamiento de los equipos en la actualidad.</p>
<p>El código completo de este desarrollo lo puedes encontrar en nuestro cuaderno: <a href="https://colab.research.google.com/drive/18dIH38IZyioGn7eIRaT8bhMatCyEqasJ?usp=sharing" target="_blank" rel="noopener"><em><strong>Programación de empleados</strong></em></a>.</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-de-empleados-mediante-programacion-entera/">Programación de empleados mediante programación entera</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-de-empleados-mediante-programacion-entera/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>¿Cómo calcular una matriz de distancias para modelar un VRP?</title>
		<link>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/como-calcular-una-matriz-de-distancias-para-modelar-un-vrp/</link>
					<comments>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/como-calcular-una-matriz-de-distancias-para-modelar-un-vrp/#comments</comments>
		
		<dc:creator><![CDATA[Bryan Salazar López]]></dc:creator>
		<pubDate>Tue, 16 Nov 2021 21:44:14 +0000</pubDate>
				<category><![CDATA[Investigación de operaciones]]></category>
		<category><![CDATA[Logística]]></category>
		<category><![CDATA[Google OR-Tools]]></category>
		<category><![CDATA[Haversine]]></category>
		<category><![CDATA[Matriz de distancias]]></category>
		<category><![CDATA[Optimización]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Routing]]></category>
		<category><![CDATA[Ruteo]]></category>
		<category><![CDATA[VRP]]></category>
		<guid isPermaLink="false">https://ingenieriaindustrialonline.com/?p=28858</guid>

					<description><![CDATA[<p>Tal como lo hemos abordado ampliamente, una de las aplicaciones más importantes del modelamiento de Cadenas de Suministro, es el diseño de red de abastecimiento, y dentro de esta categoría, el diseño de rutas de transporte (enrutamiento de vehículos). Los problemas de enrutamiento de vehículos (routing), se encuentran clasificados como problemas de optimización combinatoria, y &#8230;</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/como-calcular-una-matriz-de-distancias-para-modelar-un-vrp/">¿Cómo calcular una matriz de distancias para modelar un VRP?</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></description>
										<content:encoded><![CDATA[
		<div id="introduccion" data-title="Introducción" class="index-title"></div>
	
<p>Tal como lo hemos abordado ampliamente, u<span>na de las aplicaciones más importantes del modelamiento de Cadenas de Suministro, es el diseño de red de abastecimiento, y dentro de esta categoría, el diseño de rutas de transporte (enrutamiento de vehículos).</span></p>
<p>Los problemas de enrutamiento de vehículos (<em>routing</em>), se encuentran clasificados como problemas de optimización combinatoria, y esto producto de que la cantidad de rutas posibles en un modelo básico, se encuentra determinado por la <span>ecuación (</span><em>n</em><span> – 1)!, donde </span><em>n</em><span>, es igual al número de ubicaciones que componen el problema de enrutamiento.</span></p>
<p>Ahora bien, no solo la resolución de los problemas de enrutamiento es considerada como compleja, también lo es la fase preliminar en la que debemos levantar la información de entrada del modelo (<em>inputs</em>), específicamente, la <strong><em>matriz de distancias</em></strong>.</p>
<p>La forma más básica de un modelo <em>VRP</em>, requiere para su resolución la siguiente información mínima de entrada:</p>

		<div class="plus tie-list-shortcode">
<ul>
<li>Número de vehículos</li>
<li>Un depósito</li>
<li>Matriz de distancias</li>
</ul>

		</div>
	
<p>La matriz de distancias es un tabulado que registra la longitud entre cada uno de los nodos del modelo, incluido el nodo depósito. Su tamaño será <em>n x n </em>(siendo <em>n</em> el número de nodos del modelo). Asumiendo, por ejemplo, que tenemos un problema que se compone de 1 depósito y 19 ubicaciones, el modelo requerirá de una <em>matriz de distancias</em> con 400 datos.</p>

		<div class="box note  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>
<p>Recuerdo que cuando abordamos por primera vez este tema en la <em>Universidad</em>, en mi etapa de estudiante, se nos mencionó enfáticamente la complejidad que subyace en este tipo de modelos: El procesamiento, el levantamiento de datos, la consideración de ubicaciones reales, etc.</p>
<p>Pues bien, han pasado algunos años desde entonces, y todo cambió. El modelamiento de este tipo de problemas, la integración con sistemas de información geográfica, la posibilidad de automatizar procesos de captura de información. <em>¡Todo!</em></p>

			</div>
		</div>
	
<p>En artículos anteriores hemos abordado modelos robustos para la <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-vrp-con-google-or-tools/"><strong>resolución de problemas VRP</strong></a> y algunas de sus extensiones (<a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-capacitados-cvrp-con-google-or-tools/"><strong>CVRP</strong></a>, por ejemplo). El objetivo de este artículo será la de utilizar funciones espaciales de <em>Python</em>, para obtener una matriz de distancias de acuerdo a ubicaciones reales, con el propósito de resolver un modelo VRP básico.</p>
<hr />
<h2>Caso de aplicación</h2>

		<div id="caso-de-aplicacion" data-title="Caso de aplicación" class="index-title"></div>
	
<blockquote class=" quote-simple "><p>La Secretaría de Educación de Santiago de Cali, tiene dentro de sus funciones, distribuir el material pedagógico a las diferentes instituciones de formación de la ciudad. La distribución del material se realiza todos los lunes, y para ello, la Secretaría cuenta con dos vehículos con capacidad suficiente para transportar todo el material. El depósito del material (lugar desde donde salen y deben regresar los vehículos), se encuentra en la Alcaldía de Cali; y las instituciones que deben visitarse son 60. Tal como podemos apreciar en la siguiente tabla:</p></blockquote>
<table width="576">
<tbody>
<tr>
<td width="80" style="text-align: center;"><strong>Nodo</strong></td>
<td width="224" style="text-align: center;"><strong>Lugar</strong></td>
<td width="127" style="text-align: center;"><strong>Latitud</strong></td>
<td width="145" style="text-align: center;"><strong>Longitud</strong></td>
</tr>
<tr>
<td style="text-align: center;">0</td>
<td style="text-align: center;">Secretaría de Educación &#8211; Alcaldía</td>
<td style="text-align: center;">3,454431875</td>
<td style="text-align: center;">-76,53421433</td>
</tr>
<tr>
<td style="text-align: center;">1</td>
<td style="text-align: center;"> Comfandi San Nicolás</td>
<td style="text-align: center;">3,453591118</td>
<td style="text-align: center;">-76,52254886</td>
</tr>
<tr>
<td style="text-align: center;">2</td>
<td style="text-align: center;"> Mayor de Santiago de Cali</td>
<td style="text-align: center;">3,451577758</td>
<td style="text-align: center;">-76,51023216</td>
</tr>
<tr>
<td style="text-align: center;">3</td>
<td style="text-align: center;"> Municipal Comfandi</td>
<td style="text-align: center;">3,448107915</td>
<td style="text-align: center;">-76,51074714</td>
</tr>
<tr>
<td style="text-align: center;">4</td>
<td style="text-align: center;"> Internado San Carlos</td>
<td style="text-align: center;">3,446994135</td>
<td style="text-align: center;">-76,51525325</td>
</tr>
<tr>
<td style="text-align: center;">5</td>
<td style="text-align: center;"> León de Greiff</td>
<td style="text-align: center;">3,447979402</td>
<td style="text-align: center;">-76,49993247</td>
</tr>
<tr>
<td style="text-align: center;">6</td>
<td style="text-align: center;"> Nuestra Señora de la Anunciación</td>
<td style="text-align: center;">3,445152112</td>
<td style="text-align: center;">-76,49641342</td>
</tr>
<tr>
<td style="text-align: center;">7</td>
<td style="text-align: center;"> Fernando de Aragón</td>
<td style="text-align: center;">3,437355603</td>
<td style="text-align: center;">-76,51383704</td>
</tr>
<tr>
<td style="text-align: center;">8</td>
<td style="text-align: center;"> Casa Evangélica</td>
<td style="text-align: center;">3,437955337</td>
<td style="text-align: center;">-76,52299947</td>
</tr>
<tr>
<td style="text-align: center;">9</td>
<td style="text-align: center;"> San Alberto Magno</td>
<td style="text-align: center;">3,433028941</td>
<td style="text-align: center;">-76,52707643</td>
</tr>
<tr>
<td style="text-align: center;">10</td>
<td style="text-align: center;"> Santa María Goretty</td>
<td style="text-align: center;">3,433414486</td>
<td style="text-align: center;">-76,50720662</td>
</tr>
<tr>
<td style="text-align: center;">11</td>
<td style="text-align: center;"> San Alberto Magno</td>
<td style="text-align: center;">3,433157456</td>
<td style="text-align: center;">-76,5267331</td>
</tr>
<tr>
<td style="text-align: center;">12</td>
<td style="text-align: center;"> San Ignacio de Loyola</td>
<td style="text-align: center;">3,431786629</td>
<td style="text-align: center;">-76,51733464</td>
</tr>
<tr>
<td style="text-align: center;">13</td>
<td style="text-align: center;"> Nuestro Futuro</td>
<td style="text-align: center;">3,430629992</td>
<td style="text-align: center;">-76,50360174</td>
</tr>
<tr>
<td style="text-align: center;">14</td>
<td style="text-align: center;"> Sabio Caldas</td>
<td style="text-align: center;">3,429087807</td>
<td style="text-align: center;">-76,51660508</td>
</tr>
<tr>
<td style="text-align: center;">15</td>
<td style="text-align: center;"> CREAD</td>
<td style="text-align: center;">3,425060978</td>
<td style="text-align: center;">-76,51488847</td>
</tr>
<tr>
<td style="text-align: center;">16</td>
<td style="text-align: center;"> Licomtec</td>
<td style="text-align: center;">3,416664559</td>
<td style="text-align: center;">-76,51673383</td>
</tr>
<tr>
<td style="text-align: center;">17</td>
<td style="text-align: center;">  Nuestra Señora De La Providencia</td>
<td style="text-align: center;">3,419534772</td>
<td style="text-align: center;">-76,49591989</td>
</tr>
<tr>
<td style="text-align: center;">18</td>
<td style="text-align: center;"> Real Suizo</td>
<td style="text-align: center;">3,415208029</td>
<td style="text-align: center;">-76,49323768</td>
</tr>
<tr>
<td style="text-align: center;">19</td>
<td style="text-align: center;"> Nuevo Edén</td>
<td style="text-align: center;">3,415722099</td>
<td style="text-align: center;">-76,53383559</td>
</tr>
<tr>
<td style="text-align: center;">20</td>
<td style="text-align: center;"> Católico</td>
<td style="text-align: center;">3,413066071</td>
<td style="text-align: center;">-76,53984374</td>
</tr>
<tr>
<td style="text-align: center;">21</td>
<td style="text-align: center;"> Santa María Stella</td>
<td style="text-align: center;">3,427031556</td>
<td style="text-align: center;">-76,55134505</td>
</tr>
<tr>
<td style="text-align: center;">22</td>
<td style="text-align: center;"> Santa Isabel</td>
<td style="text-align: center;">3,40805355</td>
<td style="text-align: center;">-76,50817223</td>
</tr>
<tr>
<td style="text-align: center;">23</td>
<td style="text-align: center;"> Compartir</td>
<td style="text-align: center;">3,431957663</td>
<td style="text-align: center;">-76,47495575</td>
</tr>
<tr>
<td style="text-align: center;">24</td>
<td style="text-align: center;"> Lancaster</td>
<td style="text-align: center;">3,400770816</td>
<td style="text-align: center;">-76,55177421</td>
</tr>
<tr>
<td style="text-align: center;">25</td>
<td style="text-align: center;"> Parroquial Divino Salvador</td>
<td style="text-align: center;">3,397086588</td>
<td style="text-align: center;">-76,54259033</td>
</tr>
<tr>
<td style="text-align: center;">26</td>
<td style="text-align: center;"> Reyes Católicos</td>
<td style="text-align: center;">3,393316667</td>
<td style="text-align: center;">-76,53735466</td>
</tr>
<tr>
<td style="text-align: center;">27</td>
<td style="text-align: center;"> Liceo Anglo del Valle</td>
<td style="text-align: center;">3,387318719</td>
<td style="text-align: center;">-76,51975937</td>
</tr>
<tr>
<td style="text-align: center;">28</td>
<td style="text-align: center;"> Laurence</td>
<td style="text-align: center;">3,383420238</td>
<td style="text-align: center;">-76,52078934</td>
</tr>
<tr>
<td style="text-align: center;">29</td>
<td style="text-align: center;"> Los Almendros</td>
<td style="text-align: center;">3,381278208</td>
<td style="text-align: center;">-76,52023144</td>
</tr>
<tr>
<td style="text-align: center;">30</td>
<td style="text-align: center;"> Bautista</td>
<td style="text-align: center;">3,37720834</td>
<td style="text-align: center;">-76,52327843</td>
</tr>
<tr>
<td style="text-align: center;">31</td>
<td style="text-align: center;"> Lacordaire</td>
<td style="text-align: center;">3,378150837</td>
<td style="text-align: center;">-76,54460736</td>
</tr>
<tr>
<td style="text-align: center;">32</td>
<td style="text-align: center;"> General José María Córdoba</td>
<td style="text-align: center;">3,393573314</td>
<td style="text-align: center;">-76,54932805</td>
</tr>
<tr>
<td style="text-align: center;">33</td>
<td style="text-align: center;"> El Hogar</td>
<td style="text-align: center;">3,390745864</td>
<td style="text-align: center;">-76,5503151</td>
</tr>
<tr>
<td style="text-align: center;">34</td>
<td style="text-align: center;"> Americano</td>
<td style="text-align: center;">3,379093255</td>
<td style="text-align: center;">-76,54688187</td>
</tr>
<tr>
<td style="text-align: center;">35</td>
<td style="text-align: center;"> Santa Filomena</td>
<td style="text-align: center;">3,401969935</td>
<td style="text-align: center;">-76,51345082</td>
</tr>
<tr>
<td style="text-align: center;">36</td>
<td style="text-align: center;"> Tomás Vasconi</td>
<td style="text-align: center;">3,403040928</td>
<td style="text-align: center;">-76,5173132</td>
</tr>
<tr>
<td style="text-align: center;">37</td>
<td style="text-align: center;"> República del Salvador</td>
<td style="text-align: center;">3,404454636</td>
<td style="text-align: center;">-76,52143308</td>
</tr>
<tr>
<td style="text-align: center;">38</td>
<td style="text-align: center;"> Los Andes</td>
<td style="text-align: center;">3,429601077</td>
<td style="text-align: center;">-76,53761216</td>
</tr>
<tr>
<td style="text-align: center;">39</td>
<td style="text-align: center;">Villacolombia</td>
<td style="text-align: center;">3,445493943</td>
<td style="text-align: center;">-76,50169202</td>
</tr>
<tr>
<td style="text-align: center;">40</td>
<td style="text-align: center;">Las Américas</td>
<td style="text-align: center;">3,449220822</td>
<td style="text-align: center;">-76,50594064</td>
</tr>
<tr>
<td style="text-align: center;">41</td>
<td style="text-align: center;">Santa Fe</td>
<td style="text-align: center;">3,442238267</td>
<td style="text-align: center;">-76,50988885</td>
</tr>
<tr>
<td style="text-align: center;">42</td>
<td style="text-align: center;">Evaristo García</td>
<td style="text-align: center;">3,440781776</td>
<td style="text-align: center;">-76,51752778</td>
</tr>
<tr>
<td style="text-align: center;">43</td>
<td style="text-align: center;">Alfredo Vásquez Cobo</td>
<td style="text-align: center;">3,435598366</td>
<td style="text-align: center;">-76,5164549</td>
</tr>
<tr>
<td style="text-align: center;">44</td>
<td style="text-align: center;">Ciudad de Cali</td>
<td style="text-align: center;">3,431143181</td>
<td style="text-align: center;">-76,51272126</td>
</tr>
<tr>
<td style="text-align: center;">45</td>
<td style="text-align: center;">INEM</td>
<td style="text-align: center;">3,482761991</td>
<td style="text-align: center;">-76,49976083</td>
</tr>
<tr>
<td style="text-align: center;">46</td>
<td style="text-align: center;">Olaya Herrera</td>
<td style="text-align: center;">3,478178519</td>
<td style="text-align: center;">-76,51280709</td>
</tr>
<tr>
<td style="text-align: center;">47</td>
<td style="text-align: center;">Guillermo Valencia</td>
<td style="text-align: center;">3,47449459</td>
<td style="text-align: center;">-76,5136654</td>
</tr>
<tr>
<td style="text-align: center;">48</td>
<td style="text-align: center;">José Ignacio Rengifo</td>
<td style="text-align: center;">3,471624543</td>
<td style="text-align: center;">-76,5136654</td>
</tr>
<tr>
<td style="text-align: center;">49</td>
<td style="text-align: center;">Santo Tomás</td>
<td style="text-align: center;">3,45830227</td>
<td style="text-align: center;">-76,5164549</td>
</tr>
<tr>
<td style="text-align: center;">50</td>
<td style="text-align: center;">La Merced</td>
<td style="text-align: center;">3,46271449</td>
<td style="text-align: center;">-76,5024645</td>
</tr>
<tr>
<td style="text-align: center;">51</td>
<td style="text-align: center;">Pedro Antonio Molina</td>
<td style="text-align: center;">3,482804827</td>
<td style="text-align: center;">-76,48761579</td>
</tr>
<tr>
<td style="text-align: center;">52</td>
<td style="text-align: center;">Santa Librada</td>
<td style="text-align: center;">3,46228612</td>
<td style="text-align: center;">-76,52302095</td>
</tr>
<tr>
<td style="text-align: center;">53</td>
<td style="text-align: center;">República de Israel</td>
<td style="text-align: center;">3,463656904</td>
<td style="text-align: center;">-76,51053258</td>
</tr>
<tr>
<td style="text-align: center;">54</td>
<td style="text-align: center;">San Vicente Paul</td>
<td style="text-align: center;">3,466227117</td>
<td style="text-align: center;">-76,50950261</td>
</tr>
<tr>
<td style="text-align: center;">55</td>
<td style="text-align: center;">Manuel María Mallarino</td>
<td style="text-align: center;">3,456760129</td>
<td style="text-align: center;">-76,48851701</td>
</tr>
<tr>
<td style="text-align: center;">56</td>
<td style="text-align: center;">Sebastián de Belalcazar</td>
<td style="text-align: center;">3,460229941</td>
<td style="text-align: center;">-76,48521253</td>
</tr>
<tr>
<td style="text-align: center;">57</td>
<td style="text-align: center;">Liceo Departamental</td>
<td style="text-align: center;">3,423860462</td>
<td style="text-align: center;">-76,5385563</td>
</tr>
<tr>
<td style="text-align: center;">58</td>
<td style="text-align: center;">Libardo Madrid</td>
<td style="text-align: center;">3,422061154</td>
<td style="text-align: center;">-76,54383489</td>
</tr>
<tr>
<td style="text-align: center;">59</td>
<td style="text-align: center;">Metropolitano Santa Anita</td>
<td style="text-align: center;">3,401691038</td>
<td style="text-align: center;">-76,54218265</td>
</tr>
<tr>
<td style="text-align: center;">60</td>
<td style="text-align: center;">San José</td>
<td style="text-align: center;">3,396935816</td>
<td style="text-align: center;">-76,55031511</td>
</tr>
</tbody>
</table>
<p>Se desea desarrollar un plan de rutas para cada uno de los vehículos que logre minimizar la distancia total recorrida, al tiempo que asegure la visita de todos los colegios.</p>
<h3>¿Qué necesitaremos?</h3>

		<div id="que-necesitaremos" data-title="¿Qué necesitaremos?" class="index-title"></div>
	
<p>En el desarrollo de este ejercicio emplearemos:</p>

		<div class="plus tie-list-shortcode">
<div class="plus tie-list-shortcode">
<ul>
<li><em><strong>Colaboratory</strong>:<span> </span></em>Este es un entorno de programación y ejecución virtual de Python desarrollado por Google. Nos permitirá no tener la necesidad de realizar ninguna instalación en nuestros equipos. Todo lo que desarrollemos lo ejecutaremos en un cuaderno virtual.</li>
<li><strong><em>Python</em></strong>: Este será el lenguaje de programación que vamos a utilizar, y advertimos: No es necesario tener conocimientos previos, y el objetivo del artículo no es convertirnos en programadores expertos. Utilizaremos fragmentos de códigos, librerías disponibles, y explicaremos lo necesario para configurar nuestro desarrollo de acuerdo a los objetivos específicos de nuestros modelos.</li>
<li><strong><em>SkLearn</em></strong>: Las librerías son a <em>Python</em>, lo que las <em>apps<span> </span></em>son a un teléfono celular. Esta es quizá una de las características más a tractivas de este lenguaje: Casi que existe una librería para cada necesidad. En este caso, <em>SKLearn</em>, es una librería que integra un conjunto de métodos de aprendizaje automático y minería de datos. En este caso utilizaremos sus funciones para cálculo de distancias entre pares.</li>
<li><strong><em>Pandas</em></strong>:<span> </span><span>Es un paquete de Python que proporciona estructuras de datos rápidas, y flexibles, diseñadas para que el trabajo con datos estructurados (tabulares, multidimensionales, potencialmente heterogéneos) y de series de tiempo sea fácil e intuitivo.</span></li>
<li><em><strong>Numpy</strong>: </em>Es una librería que nos permitirá efectuar operaciones matriciales en Python.</li>
<li><strong><em>Math: </em></strong>Es una librería que contiene un conjunto de funciones matemáticas básicas.</li>
</ul>

		</div>
	
</div>
<h3>Paso 1: Crear el entorno de trabajo en Colaboratory</h3>

		<div id="paso-1-crear-el-entorno-de-trabajo-en-colaboratory" data-title="Paso 1: Crear el entorno de trabajo en Colaboratory" class="index-title"></div>
	
<div id="paso-1-crear-el-entorno-de-trabajo-en-colaboratory" data-title="Paso 1: Crear el entorno de trabajo en Colaboratory" class="index-title visible full-visible"></div>
<p><span>Lo primero que vamos a hacer consiste en crear un entorno de trabajo en <em>Google</em> </span><em>Colaboratory</em><span>, así que vayamos allá: </span><a href="https://colab.research.google.com/#create=true" target="_blank" rel="noopener"><em><strong>Abrir cuaderno nuevo</strong></em></a><span>.</span></p>
<p>Verán que tienen un lienzo para programar el modelo, así que en este cuaderno podemos ir generando las líneas de código que explicaremos en los pasos siguientes.</p>
<h3>Paso 2: Importar las librerías necesarias</h3>

		<div id="paso-2-importar-las-librerias-necesarias" data-title="Paso 2: Importar las librerías necesarias" class="index-title"></div>
	
<div id="paso-2-importar-las-librerias-necesarias" data-title="Paso 2: Importar las librerías necesarias" class="index-title visible full-visible"></div>
<p>Respecto a las librerías, en la introducción del artículo hicimos una descripción de la funcionalidad de cada una, veamos como importarlas en nuestro entorno:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>from sklearn.neighbors import DistanceMetric
from math import radians
import pandas as pd
import numpy as np</code></pre>
</div>
<p><span>De esta manera, tenemos todo lo necesario para empezar a desarrollar nuestro código.</span></p>
<h3>Paso 3: Importar los datos desde Excel</h3>

		<div id="paso-3-importar-los-datos-desde-excel" data-title="Paso 3: Importar los datos desde Excel" class="index-title"></div>
	
<div id="paso-3-importar-los-datos-desde-excel" data-title="Paso 3: Importar los datos desde Excel" class="index-title visible full-visible"></div>
<p>De acuerdo a las necesidades del modelo, podemos desarrollar un código que permita la entrada manual de la información, la captura de los datos desde entornos digitales (Internet, por ejemplo), o podemos, desde luego, alimentar nuestro modelo con información contenida en documentos externos, como es el caso de un archivo de Microsoft Excel.</p>
<p>Esta puede considerarse como una de las ventajas de utilizar <em>Python, </em>su capacidad de integrarse con cualquier fuente de datos. En nuestro caso, toda la información se encuentra contenida en un documento de Excel, el cual presenta el siguiente formato:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/datos_colegio.png" alt="datos_colegio" width="577" height="241" class="size-full wp-image-28867 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/datos_colegio.png 577w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/datos_colegio-300x125.png 300w" sizes="(max-width: 577px) 100vw, 577px" /></p>
<p>Utilizaremos ubicaciones reales, y para eso emplearemos las coordenadas de <em>latitud </em>y <em>longitud</em>.</p>

		<div class="box download  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span><span>Puedes descargar el documento de Excel que utilizamos en este ejemplo: </span><a href="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/colegios.xlsx" target="_blank" rel="noopener"><strong>Base de datos</strong></a>
			</div>
		</div>
	
<p><span>En </span><em>Colaboratory</em><span>, el siguiente fragmento permitirá cargar un archivo al entorno de ejecución:</span></p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>from google.colab import files

uploaded = files.upload()

for fn in uploaded.keys():
  print('User uploaded file "{name}" with length {length} bytes'.format(
      name=fn, length=len(uploaded[fn])))</code></pre>
</div>
<p>Al ejecutar este fragmento de código, se abrirá una ventana emergente del explorador que permitirá cargar nuestra base de datos, en nuestro caso el archivo tienen el nombre de <em>colegios.xlsx.</em></p>
<p>La siguiente línea de código permitirá almacenar los datos contenidos en el documento en un <em>Dataframe</em><span> </span>de nuestro entorno, dentro de la variable <em>data</em>.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Leer el documento de Excel y almacenar los datos en la variable data
data = pd.read_excel('colegios.xlsx')</code></pre>
</div>
<p><span>Podemos en cualquier momento confirmar si la carga de los datos se ha realizado correctamente, para eso imprimiremos las primeras cinco filas del  </span><em>DataFrame:</em></p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>data.head()</code></pre>
</div>
<p><span>Al ejecutar esta instrucción tenemos la siguiente salida (Una vista de las 5 primeras filas del marco de datos):</span></p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/data_head.png" alt="data_head" width="534" height="257" class="size-full wp-image-28869 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/data_head.png 534w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/data_head-300x144.png 300w" sizes="(max-width: 534px) 100vw, 534px" /></p>
<p>Podemos observar que los datos han sido perfectamente cargados, y que ahora se encuentran almacenados en la variable (<em>DataFrame</em>): <em><strong>data</strong></em>.</p>
<h3>Paso 4: Convertir las coordenadas de latitud y longitud en radianes</h3>

		<div id="paso-4-convertir-las-coordenadas-de-latitud-y-longitud-en-radianes" data-title="Paso 4: Convertir las coordenadas de latitud y longitud en radianes" class="index-title"></div>
	
<p>La mayor parte de las funciones de cálculo de distancias de <em>Sklearn</em> (<em>Scipy</em>) toman las entradas como radianes. Por esta razón, debemos convertir las coordenadas en radianes.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>data['Latitud'] = np.radians(data['Latitud'])
data['Longitud'] = np.radians(data['Longitud'])

#Creamos una matriz bidimensional con la latitud y la longitud en radianes
data[['Latitud','Longitud']].to_numpy()</code></pre>
</div>
<p>Al ejecutar estas líneas, las coordenadas quedarán convertidas en radianes.</p>
<h3>Paso 5: Declarar el tipo de métrica de distancias que se utilizará</h3>

		<div id="paso-5-declarar-el-tipo-de-metrica-de-distancias-que-se-utilizara" data-title="Paso 5: Declarar el tipo de métrica de distancias que se utilizará" class="index-title"></div>
	
<p>En este punto quiero detenerme para mencionar que existen decenas de funciones métricas de distancia rápida. Algunas de las más utilizadas son destinadas a espacios vectoriales de valor real, como: <em>distancias euclidianas, distancias de Manhattan, </em>etc. Prácticas cuando se emplean con coordenadas cartesianas.</p>
<p>En nuestro caso, ya que utilizamos ubicaciones reales y contamos con coordenadas de latitud y de longitud, podemos emplear una función de distancia de vectores bidimensionales que considere la curvatura de la tierra; tal es el caso de las <em>Distancias Haversine (Semiverseno)</em>.</p>

		<div class="box note  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>La distancia de Haversine (o gran círculo) es la distancia angular entre dos puntos en la superficie de una esfera. Se supone que la primera coordenada de cada punto es la latitud, la segunda es la longitud, expresada en radianes.</p>
<p>Como la Tierra es casi esférica, la fórmula Haversine proporciona una buena aproximación de la distancia entre dos puntos de la superficie terrestre, con un error de menos del 1% en promedio.
			</div>
		</div>
	
<p>Si quieren conocer la fórmula empleada para el cálculo de cada distancia <em>haversine</em>:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/haversine.png" alt="haversine" width="596" height="54" class="size-full wp-image-28870 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/haversine.png 596w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/haversine-300x27.png 300w" sizes="(max-width: 596px) 100vw, 596px" /></p>
<p>Para efectos de nuestro desarrollo, utilizaremos la librería <em>SKLearn</em> para calcular nuestras distancias. Veamos:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>dist = DistanceMetric.get_metric('haversine')

dist.pairwise(data[['Latitud','Longitud']].to_numpy())*6373</code></pre>
</div>
<p>El fragmento anterior crea un objeto con métricas <em>haversine</em>, y luego utiliza la función <em>pairwise()</em>, para calcular la distancia entre cada uno de los elementos de la matriz (nodos). Cada distancia calculada multiplica el escalar 6373 (radio esférico de la tierra), para calcular las distancias en kilómetros (Para millas multiplicar por 3798).</p>
<h3>Paso 6: Crear un marco de datos (tabulado) de matriz de distancias</h3>

		<div id="paso-6-crear-un-marco-de-datos-tabulado-de-matriz-de-distancias" data-title="Paso 6: Crear un marco de datos (tabulado) de matriz de distancias" class="index-title"></div>
	
<p>Una vez que ejecutemos el paso anterior, tendremos nuestras distancias entre nodos calculadas; lo que haremos en este paso será organizar dichos valores en forma de marco de datos o tabulado, obteniendo nuestra matriz de distancias.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>distance_matrix = pd.DataFrame(dist.pairwise(data[['Latitud','Longitud']].to_numpy())*6373)

distance_matrix.head()</code></pre>
</div>
<p><span>Al ejecutar esta instrucción tenemos la siguiente salida (Una vista de las 5 primeras filas del marco de datos):</span></p>
<figure id="attachment_28871" aria-describedby="caption-attachment-28871" style="width: 601px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/matriz_distancias.png" alt="matriz_distancias" width="601" height="274" class="size-full wp-image-28871" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/matriz_distancias.png 601w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/matriz_distancias-300x137.png 300w" sizes="(max-width: 601px) 100vw, 601px" /><figcaption id="caption-attachment-28871" class="wp-caption-text">Vista recortada</figcaption></figure>
<p>&nbsp;</p>
<p>Podemos imprimir la matriz completa:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>print(distance_matrix)</code></pre>
</div>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/matriz_distancias_2.png" alt="matriz_distancias_2" width="624" height="304" class="size-full wp-image-28872 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/matriz_distancias_2.png 624w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/matriz_distancias_2-300x146.png 300w" sizes="(max-width: 624px) 100vw, 624px" /></p>

		<div class="box note  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>Puedes utilizar Google Maps para validar la diferencia entre las distancias obtenidas mediante Haversine y las obtenidas mediante Google.
			</div>
		</div>
	
<hr />
<p>Hasta este punto hemos logrado nuestro objetivo principal, que era <strong>obtener una matriz de distancias</strong> de forma automática tomando como base las coordenadas de longitud y latitud de un conjunto de nodos. Específicamente hemos obtenido 3721 valores de distancia en cuestión de segundos (empleando la métrica <em>haversine</em>).</p>
<p>Lógicamente, estos valores de referencia en la práctica presentan algunas desventajas, como, por ejemplo, la dificultad subyacente de atravesar las ciudades por lugares diferentes que las vías dispuestas para ello. Sin embargo, estos valores pueden ser muy útiles como datos de entrada de un modelo VRP para obtener la secuencia del plan de rutas.</p>

		<div class="box info  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>Un método más preciso para obtener una matriz de distancias y tiempos consiste en utilizar la API Distance Matrix de Google Maps. Sin embargo, este es un servicio de pago que abordaremos en artículos posteriores
			</div>
		</div>
	
<hr />
<p>Lo siguiente que haremos, para finalizar nuestro caso de aplicación, será utilizar la matriz de distancias obtenida, en un <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-de-enrutamiento-de-vehiculos-vrp-con-google-or-tools/"><strong>modelo VRP básico</strong></a> (Si desea profundizar en el modelo, visite el artículo).</p>
<h3>Paso 7: Instalar Google Or Tools</h3>

		<div id="paso-7-instalar-google-or-tools" data-title="Paso 7: Instalar Google Or Tools" class="index-title"></div>
	
<p>Es necesario instalar la librería de Google Or Tools en nuestro entorno de <em>Colaboratory</em> para poder utilizar nuestro modelo VRP.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>!pip install ortools</code></pre>
</div>
<p>Al ejecutar esta instrucción instalaremos nuestro solucionador del modelo de enrutamiento.</p>
<h3>Paso 8: Incorporar el modelo VRP (Previamente formulado)</h3>

		<div id="paso-8-incorporar-el-modelo-vrp-previamente-formulado" data-title="Paso 8: Incorporar el modelo VRP (Previamente formulado)" class="index-title"></div>
	
<p>Como ya lo mencionamos, contamos con un modelo debidamente formulado para resolver problemas VRP básicos. Lo único que modificaremos serán los datos de entrada: <em>Matriz de distancias (distance_matrix), Número de vehículos (2), Depósito (Nodo 0).</em></p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>"""Problema de enrutamiento de vehículos simple (VRP)

Autor: MSc. Ing. Bryan Salazar López 2021

"""

from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp


def create_data_model():
    """Datos de entrada del modelo"""
    #Llamaremos la matriz de distancia previamente obtenida
    #Emplearemos dos vehículos como lo indica el problema
    #Definiremos el nodo 0 como el depósito (Secretaría)
    data = {}
    data['matriz_distancias'] = distance_matrix
    data['num_vehiculos'] = 2
    data['deposito'] = 0
    return data


def print_solution(data, manager, routing, solution):
    """Imprime la solución sobre la consola"""
    max_route_distance = 0
    for vehicle_id in range(data['num_vehiculos']):
        index = routing.Start(vehicle_id)
        plan_output = 'Ruta para el vehículo {}:\n'.format(vehicle_id)
        route_distance = 0
        while not routing.IsEnd(index):
            plan_output += ' {} -&gt; '.format(manager.IndexToNode(index))
            previous_index = index
            index = solution.Value(routing.NextVar(index))
            route_distance += routing.GetArcCostForVehicle(
                previous_index, index, vehicle_id)
        plan_output += '{}\n'.format(manager.IndexToNode(index))
        plan_output += 'Distancia de la ruta: {}km\n'.format(route_distance)
        print(plan_output)
        max_route_distance += route_distance
        max_route_distance = max(route_distance, max_route_distance)
    print('Distancia total de todas las rutas: {}km'.format(max_route_distance))



def main():
    """Punto de entrada del programa"""
    # Invocar la data de entrada.
    data = create_data_model()

    # Crea el administrador del índice de rutas.
    manager = pywrapcp.RoutingIndexManager(len(data['matriz_distancias']),
                                           data['num_vehiculos'], data['deposito'])

    # Crea el modelo de enrutamiento.
    routing = pywrapcp.RoutingModel(manager)


    # Crea y registra una devolución de llamada de distancia.
    def distance_callback(from_index, to_index):
        """Retorna la distancia entre dos nodos."""
        # Convierte desde la variable de ruta Index hasta la matriz de distancia NodeIndex.
        from_node = manager.IndexToNode(from_index)
        to_node = manager.IndexToNode(to_index)
        return data['matriz_distancias'][from_node][to_node]

    transit_callback_index = routing.RegisterTransitCallback(distance_callback)

    # Define el costo de cada arco.
    routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)

    # Adhiere la dimensión de distancia.
    dimension_name = 'Distancia'
    routing.AddDimension(
        transit_callback_index,
        0,  # Sin holgura
        3000,  # Distancia máxima de viaje del vehículo
        True,  # Iniciar el acumulador en cero
        dimension_name)
    distance_dimension = routing.GetDimensionOrDie(dimension_name)
    distance_dimension.SetGlobalSpanCostCoefficient(100)

    # Configurar los parámetros de búsqueda.
    search_parameters = pywrapcp.DefaultRoutingSearchParameters()
    search_parameters.first_solution_strategy = (
        routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)

    # Solucionador del problema.
    solution = routing.SolveWithParameters(search_parameters)

    # Imprimir la solución en la consola.
    if solution:
        print_solution(data, manager, routing, solution)
    else:
        print('No se encuentra solución !')


if __name__ == '__main__':
    main()</code></pre>
</div>
<p>Al ejecutar el modelo tendremos:</p>
<figure id="attachment_28873" aria-describedby="caption-attachment-28873" style="width: 1736px" class="wp-caption aligncenter"><a href="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/vrp_rutas.png" target="_blank" rel="noopener"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/vrp_rutas.png" alt="vrp_rutas" width="1736" height="176" class="wp-image-28873 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/vrp_rutas.png 1736w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/vrp_rutas-300x30.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/vrp_rutas-1024x104.png 1024w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/vrp_rutas-768x78.png 768w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/11/vrp_rutas-1536x156.png 1536w" sizes="(max-width: 1736px) 100vw, 1736px" /></a><figcaption id="caption-attachment-28873" class="wp-caption-text">Clic para ver en una pestaña nueva</figcaption></figure>
<p>&nbsp;</p>
<p>Un plan de rutas para los dos vehículos que parten desde los depósitos (ahí mismo finalizan sus recorridos), y visitan la totalidad de los nodos. La distancia total optimizada es equivalente a 33 km.</p>
<p>Por último, también es posible exportar la <strong>matriz de distancias</strong> que hemos obtenido, para eso utilizaremos el siguiente fragmento:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>distance_matrix.to_csv('distance_matrix.csv')
files.download('distance_matrix.csv')</code></pre>
</div>

		<div class="box info  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>
<p><span>Los contribuciones de la comunidad han revelado que el modelo VRP de Or Tools maneja valores enteros para las distancias en la matriz. Esto significa que, por ejemplo, una distancia de 0.58 km se trataría como un entero, lo que puede causar pérdida de precisión en los resultados. Por esta razón, recomendamos trabajar con la unidad más pequeña de longitud posible, como metros, y convertir las distancias antes de ejecutar el modelo de ruteo. Por ejemplo, 0.58 km se convertiría en 580 metros, evitando pérdida de precisión. Además, es importante tener en cuenta que esto afectaría la distancia máxima del viaje del vehículo, la cual debemos modificar, en consecuencia.</span></p>

			</div>
		</div>
	
<hr />
<p>El código completo de este desarrollo lo puedes encontrar en nuestro cuaderno: <a href="https://colab.research.google.com/drive/1YZIpYWOrTGBeGwRl9iOhWAwNH5WAVpWw?usp=sharing" target="_blank" rel="noopener"><em><strong>Matriz de distancias para modelar un VRP</strong></em></a>.</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/como-calcular-una-matriz-de-distancias-para-modelar-un-vrp/">¿Cómo calcular una matriz de distancias para modelar un VRP?</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/como-calcular-una-matriz-de-distancias-para-modelar-un-vrp/feed/</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
			</item>
		<item>
		<title>Método de la Ruta Crítica mediante Python (CPM)</title>
		<link>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/metodo-de-la-ruta-critica-mediante-python-cpm/</link>
					<comments>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/metodo-de-la-ruta-critica-mediante-python-cpm/#respond</comments>
		
		<dc:creator><![CDATA[Bryan Salazar López]]></dc:creator>
		<pubDate>Fri, 22 Oct 2021 19:02:49 +0000</pubDate>
				<category><![CDATA[Actualidad]]></category>
		<category><![CDATA[Diseño y distribución en planta]]></category>
		<category><![CDATA[Gestión de proyectos]]></category>
		<category><![CDATA[Investigación de operaciones]]></category>
		<category><![CDATA[CPM]]></category>
		<category><![CDATA[Pert]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Ruta crítica]]></category>
		<category><![CDATA[Teoría de redes]]></category>
		<guid isPermaLink="false">https://ingenieriaindustrialonline.com/?p=28576</guid>

					<description><![CDATA[<p>Para entender de qué se trata el método de Ruta Crítica, y cómo funciona como un algoritmo de redes, podemos dirigirnos a: Método de la Ruta Crítica (CPM). De acuerdo al objetivo de este artículo, basta con mencionar que la Ruta Crítica es una herramienta que soporta el análisis, la planificación, y la programación de &#8230;</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/metodo-de-la-ruta-critica-mediante-python-cpm/">Método de la Ruta Crítica mediante Python (CPM)</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></description>
										<content:encoded><![CDATA[
		<div id="introduccion" data-title="Introducción" class="index-title"></div>
	
<p>Para entender de qué se trata el método de Ruta Crítica, y cómo funciona como un algoritmo de redes, podemos dirigirnos a: <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/metodo-de-la-ruta-critica-cpm/"><em><strong>Método de la Ruta Crítica (CPM)</strong></em></a>. De acuerdo al objetivo de este artículo, basta con mencionar que la Ruta Crítica es una herramienta que soporta el análisis, la planificación, y la programación de proyectos. Que básicamente, nos ayuda a determinar cuáles de las actividades que componen un proyecto, son críticos con relación en su efecto sobre el tiempo total del proyecto.</p>
<p><span>El objetivo de este artículo es el de, mediante herramientas tecnológicas, abordar un caso básico de CPM, para desarrollar el método utilizando un lenguaje de programación, en este caso</span> <em>Python</em>, que nos permita, automatizar los cálculos y así obtener las actividades críticas del proyecto, la duración del mismo y un diagrama de Gantt.</p>
<p>En el desarrollo de este ejercicio emplearemos:</p>

		<div class="plus tie-list-shortcode">
<div class="plus tie-list-shortcode">
<ul>
<li><em><strong>Colaboratory</strong>:<span> </span></em>Este es un entorno de programación y ejecución virtual de Python desarrollado por Google. Nos permitirá no tener la necesidad de realizar ninguna instalación en nuestros equipos. Todo lo que desarrollemos lo ejecutaremos en un cuaderno virtual.</li>
<li><strong><em>Python</em></strong>: Este será el lenguaje de programación que vamos a utilizar, y advertimos: No es necesario tener conocimientos previos, y el objetivo del artículo no es convertirnos en programadores expertos. Utilizaremos fragmentos de códigos, librerías disponibles, y explicaremos lo necesario para configurar nuestro desarrollo de acuerdo a los objetivos específicos de nuestros modelos.</li>
<li><strong><em>CriticalPath</em></strong>: Las librerías son a <em>Python</em>, lo que las <em>apps<span> </span></em>son a un teléfono celular. Esta es quizá una de las características más a tractivas de este lenguaje: Casi que existe una librería para cada necesidad. En este caso, <em>CriticalPath</em>, es una librería que calcula la ruta crítica a través de una red de tareas.</li>
<li><em><strong>Matplotlib</strong>:<span> </span></em>Es una biblioteca completa para crear visualizaciones estáticas, animadas e interactivas en Python. Nos permitirá visualizar nuestros nodos y nuestras localizaciones solución.</li>
<li><strong><em>Pandas</em></strong>:<span> </span><span>Es un paquete de Python que proporciona estructuras de datos rápidas, y flexibles, diseñadas para que el trabajo con datos estructurados (tabulares, multidimensionales, potencialmente heterogéneos) y de series de tiempo sea fácil e intuitivo.</span></li>
<li><em><strong>Numpy</strong>: </em>Es una librería que nos permitirá efectuar operaciones matriciales en Python.</li>
<li><em><strong>Datetime</strong></em>: Es un módulo que proporciona herramientas para manipular fechas y horas.</li>
</ul>

		</div>
	
</div>
<hr />
<p>&nbsp;</p>
<p><span>Para evaluar los resultados obtenidos a través del tratamiento de un problema técnicamente formulado y abordado, utilizaremos un caso descrito en el libro Investigación de Operaciones (9na edición), de Hamdy A. Taha (University of Arkansas, Fayetteville), (Ejemplo 6.5-1):</span></p>
<h2>Caso de aplicación</h2>

		<div id="caso-de-aplicacion" data-title="Caso de aplicación" class="index-title"></div>
	
<blockquote class=" quote-simple "><p>Un editor firmó un contrato con un autor para publicar un libro de texto. El autor somete a consideración una copia impresa de un archivo de computadora del manuscrito. Las actividades (simplificadas) asociadas con la producción del libro de texto se resumen en la siguiente tabla.</p></blockquote>
<table width="741">
<tbody>
<tr>
<td colspan="2" width="571" style="text-align: center;">Actividades</td>
<td width="90" style="text-align: center;">Predecesoras</td>
<td width="80" style="text-align: center;">Duración (semanas)</td>
</tr>
<tr>
<td style="text-align: center;">A</td>
<td style="text-align: center;">Corrección del manuscrito, por parte del editor</td>
<td style="text-align: center;">&#8211;</td>
<td style="text-align: center;">3</td>
</tr>
<tr>
<td style="text-align: center;">B</td>
<td style="text-align: center;">Preparación de páginas muestra</td>
<td style="text-align: center;">&#8211;</td>
<td style="text-align: center;">2</td>
</tr>
<tr>
<td style="text-align: center;">C</td>
<td style="text-align: center;">Diseño de la portada del libro</td>
<td style="text-align: center;">&#8211;</td>
<td style="text-align: center;">4</td>
</tr>
<tr>
<td style="text-align: center;">D</td>
<td style="text-align: center;">Preparación de las ilustraciones</td>
<td style="text-align: center;">&#8211;</td>
<td style="text-align: center;">3</td>
</tr>
<tr>
<td style="text-align: center;">E</td>
<td style="text-align: center;">Aprobación del manuscrito editado y de páginas muestra, por parte del autor</td>
<td style="text-align: center;">A, B</td>
<td style="text-align: center;">2</td>
</tr>
<tr>
<td style="text-align: center;">F</td>
<td style="text-align: center;">Formación del libro</td>
<td style="text-align: center;">E</td>
<td style="text-align: center;">4</td>
</tr>
<tr>
<td style="text-align: center;">G</td>
<td style="text-align: center;">Revisión de las páginas formadas, por parte del autor</td>
<td style="text-align: center;">F</td>
<td style="text-align: center;">2</td>
</tr>
<tr>
<td style="text-align: center;">H</td>
<td style="text-align: center;">Revisión de las ilustraciones por el autor</td>
<td style="text-align: center;">D</td>
<td style="text-align: center;">1</td>
</tr>
<tr>
<td style="text-align: center;">I</td>
<td style="text-align: center;">Producción de las placas de impresión</td>
<td style="text-align: center;">G, H</td>
<td style="text-align: center;">2</td>
</tr>
<tr>
<td style="text-align: center;">J</td>
<td style="text-align: center;">Producción y encuadernación del libro</td>
<td style="text-align: center;">C, I</td>
<td style="text-align: center;">4</td>
</tr>
</tbody>
</table>
<p>La tarea será determinar la Ruta Crítica (Actividades críticas) y la duración estimada del proyecto.</p>
<hr />
<h3>Paso 1: Crear el entorno de trabajo en Colaboratory</h3>

		<div id="paso-1-crear-el-entorno-de-trabajo-en-colaboratory" data-title="Paso 1: Crear el entorno de trabajo en Colaboratory" class="index-title"></div>
	
<p><span>Lo primero que vamos a hacer consiste en crear un entorno de trabajo en <em>Google</em> </span><em>Colaboratory</em><span>, así que vayamos allá: </span><a href="https://colab.research.google.com/#create=true" target="_blank" rel="noopener"><em><strong>Abrir cuaderno nuevo</strong></em></a><span>.</span></p>
<p>Verán que tienen un lienzo para programar el modelo, así que en este cuaderno podemos ir generando las líneas de código que explicaremos en los pasos siguientes.</p>
<h3>Paso 2: Importar las librerías necesarias</h3>

		<div id="paso-2-importar-las-librerias-necesarias" data-title="Paso 2: Importar las librerías necesarias" class="index-title"></div>
	
<p>Respecto a las librerías, en la introducción del artículo hicimos una descripción de la funcionalidad de cada una, veamos como importarlas en nuestro entorno:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Importar las librerías necesarias
!pip install criticalpath
from criticalpath import Node
import datetime
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.patches import Patch
import numpy as np</code></pre>
</div>
<p>De esta manera, tenemos todo lo necesario para empezar a desarrollar nuestro código.</p>
<h3>Paso 3: Ingresar los datos del modelo</h3>

		<div id="paso-3-ingresar-los-datos-del-modelo" data-title="Paso 3: Ingresar los datos del modelo" class="index-title"></div>
	
<p>Básicamente los datos del modelo corresponden a las tareas, su duración y las relaciones de dependencia que rigen la secuencia del proyecto.</p>
<p>El siguiente fragmento permite ingresar estos datos al modelo:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Ingresar los datos del modelo (Tareas y dependencias)

#Crear el proyecto "p"
p = Node('proyecto')

tareas = [("A", {"duracion": 3}), 
          ("B", {"duracion": 2}), 
          ("C", {"duracion": 4}), 
          ("D", {"duracion": 3}), 
          ("E", {"duracion": 2}), 
          ("F", {"duracion": 4}), 
          ("G", {"duracion": 2}), 
          ("H", {"duracion": 1}), 
          ("I", {"duracion": 2}), 
          ("J", {"duracion": 4})]

dependencias = [("A", "E"), 
                ("B", "E"), 
                ("E", "F"),
                ("F", "G"), 
                ("G", "I"), 
                ("I", "J"),
                ("C", "J"), 
                ("H", "I"), 
                ("D", "H")]

# Cargar al proyecto las tareas y sus duraciones
for i in tareas:
    p.add(Node(i[0], duration=i[1]["duracion"]))

# Cargar al proyecto sus dependencias (secuencias)
for j in dependencias:
    p.link(j[0],j[1])

# Actualizar el proyecto:
p.update_all()</code></pre>
</div>
<p>El anterior fragmento nos permite cargar todos los datos necesarios para conocer la Ruta Crítica del modelo. Veamos cómo obtenerla:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Obtener la Ruta Crítica del modelo
p.get_critical_path()</code></pre>
</div>
<p>Al ejecutar esta instrucción tenemos la siguiente salida:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/Ruta-critica.png" alt="Ruta critica" width="492" height="104" class="size-full wp-image-28577 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/Ruta-critica.png 492w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/Ruta-critica-300x63.png 300w" sizes="(max-width: 492px) 100vw, 492px" /></p>
<p>De la misma manera, podemos obtener la duración estimada del proyecto:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Obtener la duración del proyecto
p.duration</code></pre>
</div>
<p>Al ejecutar esta instrucción tenemos la siguiente salida:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/Ruta-critica_duracion.png" alt="Ruta critica_duracion" width="492" height="84" class="size-full wp-image-28578 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/Ruta-critica_duracion.png 492w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/Ruta-critica_duracion-300x51.png 300w" sizes="(max-width: 492px) 100vw, 492px" /></p>
<p>Así entonces, de esta manera muy sencilla tenemos las actividades que componen la Ruta Crítica y la duración del proyecto de acuerdo a CPM (17 semanas). Podríamos finalizar el modelo hasta acá, sin embargo, queremos obtener el diagrama de Gantt del proyecto, y para eso es necesario obtener algunas variables adicionales.</p>
<h3>Paso 4: Obtener las variables de inicio y finalización</h3>

		<div id="paso-4-obtener-las-variables-de-inicio-y-finalizacion" data-title="Paso 4: Obtener las variables de inicio y finalización" class="index-title"></div>
	
<p>Ya que el problema planteado no establece fechas de inicio y finalización, podemos, mediante <em>Python</em>, utilizar una fecha de inicio artificial, por ejemplo: Hoy. <em>¿Con qué objetivo hacemos esto?</em> Los diagramas de Gantt requieren de una línea de tiempo y es preciso establecer estas variables. Estableceremos fechas de inicio, de finalización y un status para cada actividad del proyecto.</p>
<p>En este caso puntual, ya que la duración de cada actividad se nos da en semanas, multiplicaremos el valor de la duración por 7:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Obtener las variables de inicio y finalización
ruta_critica = [str(n) for n in p.get_critical_path()]

proj_fecha_inicio = datetime.date.today()

proj_calendario = pd.DataFrame([dict(Tarea = key, 
                                   Inicio = datetime.date.today(), 
                                   Fin = datetime.date.today() + datetime.timedelta(val['duracion']*7), 
                                   Status = 'Actividad Normal')
                              for key, val in dict(tareas).items()])

for key, val in dict(tareas).items():
    dep = [d for d in dependencias if d[1] == key]
    prev_tareas = [t[0] for t in dep]
    if prev_tareas:
        prev_fin = proj_calendario[proj_calendario.Tarea.isin(prev_tareas)]['Fin'].max()
        proj_calendario.loc[proj_calendario.Tarea == key, 'Inicio'] = prev_fin
        proj_calendario.loc[proj_calendario.Tarea == key, 'Fin'] = prev_fin + datetime.timedelta(val['duracion']*7)
        
proj_calendario.loc[proj_calendario.Tarea.isin(ruta_critica), 'Status'] = 'Ruta Crítica'
        
display(proj_calendario)</code></pre>
</div>
<p>Al ejecutar este fragmento de código, tendremos:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/Ruta-critica_fechas.png" alt="Ruta critica_fechas" width="381" height="342" class="size-full wp-image-28580 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/Ruta-critica_fechas.png 381w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/Ruta-critica_fechas-300x269.png 300w" sizes="(max-width: 381px) 100vw, 381px" /></p>
<p>Podemos apreciar cómo tenemos las fechas estimadas de inicio de cada actividad y su correspondiente fecha de finalización (teniendo en cuenta que la duración de las actividades está dada en semanas). También tenemos un <em>status</em> relacionado con la naturaleza de cada actividad: <em>Crítica o Normal</em>.</p>
<p>Y tenemos fechas de inicio y finalización, lo siguiente será calcular cuántos días pasan entre el inicio del proyecto y el inicio y finalización de cada actividad:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># Número de días desde que el proyecto inicia hasta que la tarea inicia
proj_calendario['dias_inicio'] = (proj_calendario.Inicio-proj_fecha_inicio).dt.days
# Número de días desde que el proyecto inicia hasta que la tarea finaliza
proj_calendario['dias_fin'] = (proj_calendario.Fin-proj_fecha_inicio).dt.days
# Días entre el inicio y el fin de cada tarea
proj_calendario['dias_inicio_fin'] = proj_calendario.dias_fin - proj_calendario.dias_inicio

display(proj_calendario)</code></pre>
</div>
<p>Al ejecutar este fragmento de código, tendremos:<span style="background-color: inherit; color: inherit; font-family: inherit; font-size: 15px; font-weight: lighter; white-space: pre;"></span></p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/Ruta-critica_inicio_fin.png" alt="Ruta Crítica mediante Python" width="690" height="342" class="aligncenter wp-image-28581 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/Ruta-critica_inicio_fin.png 690w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/Ruta-critica_inicio_fin-300x149.png 300w" sizes="(max-width: 690px) 100vw, 690px" /></p>
<h3>Paso 5: Obtener el diagrama de Gantt (Graficar)</h3>

		<div id="paso-5-obtener-el-diagrama-de-gantt-graficar" data-title="Paso 5: Obtener el diagrama de Gantt (Graficar)" class="index-title"></div>
	
<p>El siguiente paso consiste en graficar de acuerdo al diagrama de Gantt, ls actividades del proyecto. El eje <em>x</em> estará dado en días.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Graficar las actividades en un diagrama de Gantt
fig, ax = plt.subplots(1, figsize=(16,6))
ax.barh(proj_calendario.Tarea, proj_calendario.dias_inicio_fin, left=proj_calendario.dias_inicio)
plt.show()</code></pre>
</div>
<p>Al ejecutar el fragmento tendremos:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/diagrama_gantt.png" alt="diagrama_gantt" width="921" height="357" class="alignnone size-full wp-image-28583" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/diagrama_gantt.png 921w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/diagrama_gantt-300x116.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/diagrama_gantt-768x298.png 768w" sizes="(max-width: 921px) 100vw, 921px" /></p>
<h3>Paso 6: Mejorar el diagrama de Gantt</h3>

		<div id="paso-6-mejorar-el-diagrama-de-gantt" data-title="Paso 6: Mejorar el diagrama de Gantt" class="index-title"></div>
	
<p>Una vez obtenido el diagrama podemos realizar modificaciones sobre el mismo, por ejemplo: dar un color específico a las actividades de la ruta crítica, anexar leyendas, entre otros. Vamos a resaltar con rojo las actividades críticas, y a anexar alguna leyenda de actividades:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># Dar color rojo a las columnas de actividades críticas
def color(row):
    c_dict = {'Ruta Crítica':'#E64646', 'Actividad Normal':'#4F81BE'}
    return c_dict[row['Status']]
proj_calendario['color'] = proj_calendario.apply(color, axis=1)
fig, ax = plt.subplots(1, figsize=(16,6))
ax.barh(proj_calendario.Tarea, proj_calendario.dias_inicio_fin, left=proj_calendario.dias_inicio, color=proj_calendario.color)

#Anexar leyendas
c_dict = {'Ruta crítica':'#E64646', 'Actividad normal':'#4F81BE'}
leyenda = [Patch(facecolor=c_dict[i], label=i)  for i in c_dict]
plt.legend(handles=leyenda)

plt.show()</code></pre>
</div>
<p>Al ejecutar el fragmento tendremos:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/diagrama_gantt_cpm.png" alt="Ruta Crítica mediante Python" width="921" height="357" class="alignnone wp-image-28584 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/diagrama_gantt_cpm.png 921w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/diagrama_gantt_cpm-300x116.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/diagrama_gantt_cpm-768x298.png 768w" sizes="(max-width: 921px) 100vw, 921px" /></p>
<p>Ahora tenemos un modelo capaz de obtener las actividades críticas de un proyecto, determinar su duración de acuerdo al algoritmo CPM y graficar las actividades mediante un diagrama de Gantt.</p>
<p>También es posible incorporar una variable de «estado de terminación» de cada actividad, para así observar el avance del proyecto.</p>
<p>El código completo de este desarrollo lo puedes encontrar en nuestro cuaderno: <a href="https://colab.research.google.com/drive/1AaFQDIQ_n1nR1BsrxQqZg4pEu_dyP_6K?usp=sharing" target="_blank" rel="noopener"><em><strong>Método de la Ruta Crítica (CPM) mediante Python</strong></em></a>.</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/metodo-de-la-ruta-critica-mediante-python-cpm/">Método de la Ruta Crítica mediante Python (CPM)</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/metodo-de-la-ruta-critica-mediante-python-cpm/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Localización de varios almacenes mediante agrupación geoespacial</title>
		<link>https://ingenieriaindustrialonline.com/localizacion-de-instalaciones/localizacion-de-varios-almacenes-mediante-agrupacion-geoespacial/</link>
					<comments>https://ingenieriaindustrialonline.com/localizacion-de-instalaciones/localizacion-de-varios-almacenes-mediante-agrupacion-geoespacial/#respond</comments>
		
		<dc:creator><![CDATA[Bryan Salazar López]]></dc:creator>
		<pubDate>Thu, 14 Oct 2021 19:54:29 +0000</pubDate>
				<category><![CDATA[Actualidad]]></category>
		<category><![CDATA[Análisis de datos]]></category>
		<category><![CDATA[Diseño y distribución en planta]]></category>
		<category><![CDATA[Gestión de almacenes]]></category>
		<category><![CDATA[Inteligencia artificial]]></category>
		<category><![CDATA[Localización de instalaciones]]></category>
		<category><![CDATA[Logística]]></category>
		<category><![CDATA[Agrupación geoespacial]]></category>
		<category><![CDATA[Centro de masa]]></category>
		<category><![CDATA[Clustering]]></category>
		<category><![CDATA[Localización]]></category>
		<category><![CDATA[Machine learning]]></category>
		<category><![CDATA[Mapa de calor]]></category>
		<category><![CDATA[Mapas de calor]]></category>
		<category><![CDATA[Método del Centro de gravedad]]></category>
		<category><![CDATA[Métodos de localización]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://ingenieriaindustrialonline.com/?p=28444</guid>

					<description><![CDATA[<p>En un artículo anterior, desarrollamos un modelo capaz de determinar la localización de una instalación (almacén), de acuerdo a un conjunto de ubicaciones existentes (clientes); estas ubicaciones tenían una ponderación determinada (peso, por ejemplo demanda), y basamos nuestro desarrollo en el algoritmo de Centro de Gravedad. El valor agregado del modelo consistía en la integración &#8230;</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/localizacion-de-instalaciones/localizacion-de-varios-almacenes-mediante-agrupacion-geoespacial/">Localización de varios almacenes mediante agrupación geoespacial</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></description>
										<content:encoded><![CDATA[
		<div id="introduccion" data-title="Introducción" class="index-title"></div>
	
<p>En un artículo anterior, desarrollamos un modelo capaz de determinar la localización de una instalación (almacén), de acuerdo a un conjunto de ubicaciones existentes (clientes); estas ubicaciones tenían una ponderación determinada (peso, por ejemplo demanda), y basamos nuestro desarrollo en el algoritmo de Centro de Gravedad.</p>
<p>El valor agregado del modelo consistía en la integración de una capa de mapa de calor (para graficar la densidad), un proceso de geocodificación y el uso de un entorno geográfico real. El alcance de este modelo se encuentra determinado por la localización de una sola instalación (depósito, almacén, etc.), y en los casos en los que se requiera determinar múltiples localizaciones, el modelo no aplica.</p>

		<div class="box info  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>Para ver el artículo: <a href="https://ingenieriaindustrialonline.com/localizacion-de-instalaciones/mapas-de-calor-y-algoritmo-de-centro-de-gravedad-utilizando-python/" target="_blank" rel="noopener"><strong>Mapas de calor y Algoritmo de Centro de Gravedad utilizando Python</strong></a>
			</div>
		</div>
	
<p>La pregunta siguiente que nos hacemos es <em>¿Cómo determinar la localización de múltiples instalaciones?</em> En realidad, hay muchas respuestas para este interrogante, y gran parte de ellas conducen a la <strong>agrupación geoespacial</strong> (Clustering).</p>
<h2>¿Qué es la agrupación geoespacial (Clustering)?</h2>

		<div id="que-es-la-agrupacion-geoespacial-clustering" data-title="¿Qué es la agrupación geoespacial (Clustering)?" class="index-title"></div>
	
<p>La agrupación geoespacial es un método que se utiliza para asociar un conjunto de objetos espaciales en grupos denominados «<em>clusters</em>«. Los objetos que conforman cada grupo presentan un grado de similitud asociado a un atributo o varios atributos en particular.</p>
<p>El objetivo de la agrupación geoespacial, consiste en determinar una relación entre atributos espaciales (coordenadas, ubicación) y no espaciales (demanda, por ejemplo).</p>
<p>En la literatura encontraremos varios tipos de agrupación geoespacial, cada uno con un enfoque particular, y un campo de aplicación específico; entre los cuales podemos encontrar:</p>

		<div class="lightbulb tie-list-shortcode">
<ul>
<li>Agrupación de particiones</li>
<li>Agrupación jerárquica</li>
<li>Agrupación <em>Fuzzy</em></li>
<li>Agrupación basada en densidad</li>
</ul>

		</div>
	
<p>En nuestro caso, que pretendemos determinar la localización de varias instalaciones, considerando la ponderación y ubicación de los puntos existentes, requerimos de un modelo capaz de relacionar atributos espaciales (coordenadas) y no espaciales (peso de cada nodo). Que nos permita, primero agrupar los puntos dados (ubicaciones), y eventualmente, aplicar un algoritmo de <a href="https://ingenieriaindustrialonline.com/diseno-y-distribucion-en-planta/metodo-del-centro-de-gravedad/" target="_blank" rel="noopener"><strong>Centro de Gravedad</strong></a>, para determinar localizaciones potenciales.</p>
<p>Para tales efectos, vamos a utilizar la <strong>agrupación de particiones</strong>, que se caracteriza, entre otras, por:</p>

		<div class="checklist tie-list-shortcode">
<ul>
<li>Agrupar los puntos espaciales en subconjuntos</li>
<li>Cada punto agrupado pertenece solo a un subconjunto (clúster)</li>
<li>Cada subconjunto tiene al menos un punto</li>
</ul>

		</div>
	
<p>Vale la pena destacar que en cuanto a la agrupación de participaciones, en esta categoría encontraremos varios métodos de partición, y nosotros utilizaremos el método <em><strong>K-Means</strong></em>, un algoritmo de aprendizaje automático (Machine Learning) no supervisado. Para ello utilizaremos <em>Python</em>.</p>
<figure id="attachment_28445" aria-describedby="caption-attachment-28445" style="width: 299px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/clustering.png" alt="clustering" width="299" height="290" class=" wp-image-28445" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/clustering.png 471w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/clustering-300x291.png 300w" sizes="(max-width: 299px) 100vw, 299px" /><figcaption id="caption-attachment-28445" class="wp-caption-text">Agrupación por particiones</figcaption></figure>
<p>Para sintetizar, el objetivo de este artículo será el de emplear un algoritmo de aprendizaje automático capaz de agrupar nuestros nodos en <em>clusters</em>, de acuerdo a atributos espaciales (coordenadas) y no espaciales (ponderación); para luego, utilizar un algoritmo de Centro de Gravedad en cada <em>clúster</em> para determinar la localización de múltiples instalaciones (almacenes, depósitos, etc.).</p>
<p>En el desarrollo de este ejercicio emplearemos:</p>

		<div class="plus tie-list-shortcode">
<ul>
<li><em><strong>Colaboratory</strong>: </em>Este es un entorno de programación y ejecución virtual de Python desarrollado por Google. Nos permitirá no tener la necesidad de realizar ninguna instalación en nuestros equipos. Todo lo que desarrollemos lo ejecutaremos en un cuaderno virtual.</li>
<li><strong><em>Python</em></strong>: Este será el lenguaje de programación que vamos a utilizar, y advertimos: No es necesario tener conocimientos previos, y el objetivo del artículo no es convertirnos en programadores expertos. Utilizaremos fragmentos de códigos, librerías disponibles, y explicaremos lo necesario para configurar nuestro desarrollo de acuerdo a los objetivos específicos de nuestros modelos.</li>
<li><strong><em>SkLearn</em></strong>: Las librerías son a <em>Python</em>, lo que las <em>apps </em>son a un teléfono celular. Esta es quizá una de las características más a tractivas de este lenguaje: Casi que existe una librería para cada necesidad. En este caso, <em>SKLearn</em>, es una librería que integra un conjunto de métodos de aprendizaje automático y minería de datos.</li>
<li><em><strong>K-Means</strong>: </em>Este es un módulo de <em>SKLearn</em> que contiene el algoritmo de agrupación <em>KMeans,</em> el cual separa muestras en <em>n</em> grupos de varianza igual, minimizando un criterio conocido como inercia o suma de cuadrados dentro del grupo.</li>
<li><em><strong>Matplotlib</strong>: </em>Es una biblioteca completa para crear visualizaciones estáticas, animadas e interactivas en Python. Nos permitirá visualizar nuestros nodos y nuestras localizaciones solución.</li>
<li><strong><em>Pandas</em></strong>: <span>Es un paquete de Python que proporciona estructuras de datos rápidas, y flexibles, diseñadas para que el trabajo con datos estructurados (tabulares, multidimensionales, potencialmente heterogéneos) y de series de tiempo sea fácil e intuitivo.</span></li>
<li><em><strong>Numpy</strong>: </em>Es una librería que nos permitirá efectuar operaciones matriciales en Python.</li>
</ul>

		</div>
	
<hr />
<p>Para desarrollar estas herramientas, vamos a plantear un caso típico de localización de múltiples instalaciones a partir de la consideración de otros nodos (nodos de demanda, por ejemplo).</p>
<h2>Caso de aplicación</h2>

		<div id="caso-de-aplicacion" data-title="Caso de aplicación" class="index-title"></div>
	
<blockquote class=" quote-simple "><p>El Departamento de Desarrollo Sostenible de la ciudad de Cali se encuentra implementando una estrategia piloto de recolección de aceite de cocina usado. Ha articulado este proyecto con una Universidad, la cual desarrolló 4 contenedores inteligentes (BIN’s) para la disposición del bioresiduo.</p></blockquote>
<blockquote class=" quote-simple "><p>En investigaciones asociadas, la Universidad ha determinado que el reciclaje del aceite es un problema de densidad; esto quiere decir que es vital la ubicación de los contenedores (cobertura), para así mismo optimizar el proceso de disposición y recolección. El proyecto piloto piensa articular a las instituciones de educación como puntos potenciales de recolección. Por medio de las instituciones piensan socializar el programa con la comunidad. El primer reto del proyecto consiste en determinar la ubicación de los contenedores inteligentes (4 unidades). La información relacionada con las instituciones de educación que hacen parte del programa (ubicación geográfica / población estudiantil), se detalla a continuación:</p></blockquote>
<table width="611">
<tbody>
<tr>
<td width="80">Nodo</td>
<td width="224">Lugar (Colegios)</td>
<td width="127">Latitud</td>
<td width="100">Longitud</td>
<td width="80">Peso</td>
</tr>
<tr>
<td>0</td>
<td> Comfandi San Nicolás</td>
<td>3,453591118</td>
<td>-76,52254886</td>
<td>1494</td>
</tr>
<tr>
<td>1</td>
<td> Mayor de Santiago de Cali</td>
<td>3,451577758</td>
<td>-76,51023216</td>
<td>908</td>
</tr>
<tr>
<td>2</td>
<td> Municipal Comfandi</td>
<td>3,448107915</td>
<td>-76,51074714</td>
<td>697</td>
</tr>
<tr>
<td>3</td>
<td> Internado San Carlos</td>
<td>3,446994135</td>
<td>-76,51525325</td>
<td>1714</td>
</tr>
<tr>
<td>4</td>
<td> León de Greiff</td>
<td>3,447979402</td>
<td>-76,49993247</td>
<td>1731</td>
</tr>
<tr>
<td>5</td>
<td> Nuestra Señora de la Anunciación</td>
<td>3,445152112</td>
<td>-76,49641342</td>
<td>2297</td>
</tr>
<tr>
<td>6</td>
<td> Fernando de Aragón</td>
<td>3,437355603</td>
<td>-76,51383704</td>
<td>1265</td>
</tr>
<tr>
<td>7</td>
<td> Casa Evangélica</td>
<td>3,437955337</td>
<td>-76,52299947</td>
<td>1658</td>
</tr>
<tr>
<td>8</td>
<td> San Alberto Magno</td>
<td>3,433028941</td>
<td>-76,52707643</td>
<td>604</td>
</tr>
<tr>
<td>9</td>
<td> Santa María Goretty</td>
<td>3,433414486</td>
<td>-76,50720662</td>
<td>416</td>
</tr>
<tr>
<td>10</td>
<td> San Alberto Magno</td>
<td>3,433157456</td>
<td>-76,5267331</td>
<td>1584</td>
</tr>
<tr>
<td>11</td>
<td> San Ignacio de Loyola</td>
<td>3,431786629</td>
<td>-76,51733464</td>
<td>2350</td>
</tr>
<tr>
<td>12</td>
<td> Nuestro Futuro</td>
<td>3,430629992</td>
<td>-76,50360174</td>
<td>964</td>
</tr>
<tr>
<td>13</td>
<td> Sabio Caldas</td>
<td>3,429087807</td>
<td>-76,51660508</td>
<td>329</td>
</tr>
<tr>
<td>14</td>
<td> CREAD</td>
<td>3,425060978</td>
<td>-76,51488847</td>
<td>774</td>
</tr>
<tr>
<td>15</td>
<td> Licomtec</td>
<td>3,416664559</td>
<td>-76,51673383</td>
<td>1818</td>
</tr>
<tr>
<td>16</td>
<td>  Nuestra Señora De La Providencia</td>
<td>3,419534772</td>
<td>-76,49591989</td>
<td>1530</td>
</tr>
<tr>
<td>17</td>
<td> Real Suizo</td>
<td>3,415208029</td>
<td>-76,49323768</td>
<td>2106</td>
</tr>
<tr>
<td>18</td>
<td> Nuevo Edén</td>
<td>3,415722099</td>
<td>-76,53383559</td>
<td>330</td>
</tr>
<tr>
<td>19</td>
<td> Católico</td>
<td>3,413066071</td>
<td>-76,53984374</td>
<td>976</td>
</tr>
<tr>
<td>20</td>
<td> Santa María Stella</td>
<td>3,427031556</td>
<td>-76,55134505</td>
<td>1975</td>
</tr>
<tr>
<td>21</td>
<td> Santa Isabel</td>
<td>3,40805355</td>
<td>-76,50817223</td>
<td>936</td>
</tr>
<tr>
<td>22</td>
<td> Compartir</td>
<td>3,431957663</td>
<td>-76,47495575</td>
<td>1563</td>
</tr>
<tr>
<td>23</td>
<td> Lancaster</td>
<td>3,400770816</td>
<td>-76,55177421</td>
<td>1219</td>
</tr>
<tr>
<td>24</td>
<td> Parroquial Divino Salvador</td>
<td>3,397086588</td>
<td>-76,54259033</td>
<td>1954</td>
</tr>
<tr>
<td>25</td>
<td> Reyes Católicos</td>
<td>3,393316667</td>
<td>-76,53735466</td>
<td>399</td>
</tr>
<tr>
<td>26</td>
<td> Liceo Anglo del Valle</td>
<td>3,387318719</td>
<td>-76,51975937</td>
<td>1741</td>
</tr>
<tr>
<td>27</td>
<td> Laurence</td>
<td>3,383420238</td>
<td>-76,52078934</td>
<td>1111</td>
</tr>
<tr>
<td>28</td>
<td> Los Almendros</td>
<td>3,381278208</td>
<td>-76,52023144</td>
<td>1826</td>
</tr>
<tr>
<td>29</td>
<td> Bautista</td>
<td>3,37720834</td>
<td>-76,52327843</td>
<td>1772</td>
</tr>
<tr>
<td>30</td>
<td> Lacordaire</td>
<td>3,378150837</td>
<td>-76,54460736</td>
<td>1965</td>
</tr>
<tr>
<td>31</td>
<td> General José María Córdoba</td>
<td>3,393573314</td>
<td>-76,54932805</td>
<td>841</td>
</tr>
<tr>
<td>32</td>
<td> El Hogar</td>
<td>3,390745864</td>
<td>-76,5503151</td>
<td>770</td>
</tr>
<tr>
<td>33</td>
<td> Americano</td>
<td>3,379093255</td>
<td>-76,54688187</td>
<td>650</td>
</tr>
<tr>
<td>34</td>
<td> Santa Filomena</td>
<td>3,401969935</td>
<td>-76,51345082</td>
<td>1401</td>
</tr>
<tr>
<td>35</td>
<td> Tomás Vasconi</td>
<td>3,403040928</td>
<td>-76,5173132</td>
<td>1474</td>
</tr>
<tr>
<td>36</td>
<td> República del Salvador</td>
<td>3,404454636</td>
<td>-76,52143308</td>
<td>1926</td>
</tr>
<tr>
<td>37</td>
<td> Los Andes</td>
<td>3,429601077</td>
<td>-76,53761216</td>
<td>1566</td>
</tr>
<tr>
<td>38</td>
<td>Villacolombia</td>
<td>3,445493943</td>
<td>-76,50169202</td>
<td>2354</td>
</tr>
<tr>
<td>39</td>
<td>Las Américas</td>
<td>3,449220822</td>
<td>-76,50594064</td>
<td>2043</td>
</tr>
<tr>
<td>40</td>
<td>SantaFe</td>
<td>3,442238267</td>
<td>-76,50988885</td>
<td>2333</td>
</tr>
<tr>
<td>41</td>
<td>Evaristo García</td>
<td>3,440781776</td>
<td>-76,51752778</td>
<td>696</td>
</tr>
<tr>
<td>42</td>
<td>Alfredo Vasquez Cobo</td>
<td>3,435598366</td>
<td>-76,5164549</td>
<td>1073</td>
</tr>
<tr>
<td>43</td>
<td>Ciudad de Cali</td>
<td>3,431143181</td>
<td>-76,51272126</td>
<td>1275</td>
</tr>
<tr>
<td>44</td>
<td>INEM</td>
<td>3,482761991</td>
<td>-76,49976083</td>
<td>1485</td>
</tr>
<tr>
<td>45</td>
<td>Olaya Herrera</td>
<td>3,478178519</td>
<td>-76,51280709</td>
<td>1470</td>
</tr>
<tr>
<td>46</td>
<td>Guillermo Valencia</td>
<td>3,47449459</td>
<td>-76,5136654</td>
<td>1248</td>
</tr>
<tr>
<td>47</td>
<td>José Ignacio Rengifo</td>
<td>3,471624543</td>
<td>-76,5136654</td>
<td>2160</td>
</tr>
<tr>
<td>48</td>
<td>Santo Tomás</td>
<td>3,45830227</td>
<td>-76,5164549</td>
<td>1776</td>
</tr>
<tr>
<td>49</td>
<td>La Merced</td>
<td>3,46271449</td>
<td>-76,5024645</td>
<td>706</td>
</tr>
<tr>
<td>50</td>
<td>Pedro Antonio Molina</td>
<td>3,482804827</td>
<td>-76,48761579</td>
<td>2369</td>
</tr>
<tr>
<td>51</td>
<td>Santa Librada</td>
<td>3,46228612</td>
<td>-76,52302095</td>
<td>2498</td>
</tr>
<tr>
<td>52</td>
<td>República de Israel</td>
<td>3,463656904</td>
<td>-76,51053258</td>
<td>1510</td>
</tr>
<tr>
<td>53</td>
<td>San Vicente Paul</td>
<td>3,466227117</td>
<td>-76,50950261</td>
<td>2330</td>
</tr>
<tr>
<td>54</td>
<td>Manuel María Mallarino</td>
<td>3,456760129</td>
<td>-76,48851701</td>
<td>1464</td>
</tr>
<tr>
<td>55</td>
<td>Sebastían de Belalcazar</td>
<td>3,460229941</td>
<td>-76,48521253</td>
<td>628</td>
</tr>
<tr>
<td>56</td>
<td>Liceo Departamental</td>
<td>3,423860462</td>
<td>-76,5385563</td>
<td>364</td>
</tr>
<tr>
<td>57</td>
<td>Libardo Madrid</td>
<td>3,422061154</td>
<td>-76,54383489</td>
<td>2439</td>
</tr>
<tr>
<td>58</td>
<td>Metropolitano Santa Anita</td>
<td>3,401691038</td>
<td>-76,54218265</td>
<td>1815</td>
</tr>
<tr>
<td>59</td>
<td>San José</td>
<td>3,396935816</td>
<td>-76,55031511</td>
<td>2230</td>
</tr>
</tbody>
</table>
<hr />
<p>Para tales efectos, desarrollaremos un modelo que apoye el análisis preliminar y la localización de los múltiples contenedores. También, que tenga la capacidad de predecir el grupo (clúster) al que pertenecería un nodo nuevo.</p>
<hr />
<h3>Paso 1: Crear el entorno de trabajo en Colaboratory</h3>

		<div id="paso-1-crear-el-entorno-de-trabajo-en-colaboratory" data-title="Paso 1: Crear el entorno de trabajo en Colaboratory" class="index-title"></div>
	
<p><span>Lo primero que vamos a hacer consiste en crear un entorno de trabajo en <em>Google</em> </span><em>Colaboratory</em><span>, así que vayamos allá: </span><a href="https://colab.research.google.com/#create=true" target="_blank" rel="noopener"><em><strong>Abrir cuaderno nuevo</strong></em></a><span>.</span></p>
<p>Verán que tienen un lienzo para programar el modelo, así que en este cuaderno podemos ir generando las líneas de código que explicaremos en los pasos siguientes.</p>
<h3>Paso 2: Importar las librerías necesarias</h3>

		<div id="paso-2-importar-las-librerias-necesarias" data-title="Paso 2: Importar las librerías necesarias" class="index-title"></div>
	
<p>Respecto a las librerías, en la introducción del artículo hicimos una descripción de la funcionalidad de cada una, veamos como importarlas en nuestro entorno:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Importar las librerías necesarias
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans</code></pre>
</div>
<p>De esta manera, tenemos todo lo necesario para empezar a desarrollar nuestro código.</p>
<h3>Paso 3: Importar los datos desde Excel</h3>

		<div id="paso-3-importar-los-datos-desde-excel" data-title="Paso 3: Importar los datos desde Excel" class="index-title"></div>
	
<p>De acuerdo a las necesidades del modelo, podemos desarrollar un código que permita la entrada manual de la información, la captura de los datos desde entornos digitales (Internet, por ejemplo), o podemos, desde luego, alimentar nuestro modelo con información contenida en documentos externos, como es el caso de un archivo de Microsoft Excel.</p>
<p>Esta puede considerarse como una de las ventajas de utilizar <em>Python, </em>su capacidad de integrarse con cualquier fuente de datos. En nuestro caso, toda la información se encuentra contenida en un documento de Excel, el cual presenta el siguiente formato:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/datos_excel.png" alt="datos_excel" width="612" height="241" class="size-full wp-image-28446 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/datos_excel.png 612w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/datos_excel-300x118.png 300w" sizes="(max-width: 612px) 100vw, 612px" /></p>
<p>Ya veremos cómo, parte de estos datos son prescindibles y otros indispensables.</p>

		<div class="box download  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>Puedes descargar el documento de Excel que utilizamos en este ejemplo: <a href="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/cluster.xlsx" target="_blank" rel="noopener"><strong>Base de datos</strong></a>
			</div>
		</div>
	
<p>En <em>Colaboratory</em>, el siguiente fragmento permitirá cargar un archivo al entorno de ejecución:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>from google.colab import files

uploaded = files.upload()

for fn in uploaded.keys():
  print('User uploaded file "{name}" with length {length} bytes'.format(
      name=fn, length=len(uploaded[fn])))</code></pre>
</div>
<p>Al ejecutar este fragmento de código, se abrirá una ventana emergente del explorador que permitirá cargar nuestra base de datos, en nuestro caso el archivo tienen el nombre de <em>cluster.xlsx.</em></p>
<p>La siguiente línea de código permitirá almacenar los datos contenidos en el documento en un <em>Dataframe</em> de nuestro entorno, dentro de la variable <em>data</em>.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Leer el documento de Excel y almacenar los datos en la variable data
data = pd.read_excel('cluster.xlsx')</code></pre>
</div>
<p>Podemos en cualquier momento confirmar si la carga de los datos se ha realizado correctamente, para eso imprimiremos las primeras cinco filas del  <em>DataFrame:</em></p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>data.head()</code></pre>
</div>
<p>Al ejecutar esta instrucción tenemos la siguiente salida:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/head_cluster.png" alt="head_cluster" width="539" height="237" class="size-full wp-image-28448 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/head_cluster.png 539w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/head_cluster-300x132.png 300w" sizes="(max-width: 539px) 100vw, 539px" /></p>
<h3>Paso 4: Graficar los puntos dados iniciales (Nodos)</h3>

		<div id="paso-4-graficar-los-puntos-dados-iniciales-nodos" data-title="Paso 4: Graficar los puntos dados iniciales (Nodos)" class="index-title"></div>
	
<p>Nuestros puntos iniciales, o las ubicaciones de partida son las instituciones de educación que nos otorga el planteamiento del problema.</p>
<p>Para graficar estos puntos utilizamos el sistema de coordenadas disponible: Latitud y Longitud. Así entonces, debemos extraer estos datos de la hoja de cálculo (<em>DataFrame</em>) que hemos importado al modelo; convertir estas coordenadas en una matriz bidimensional (Latitud y Longitud) y graficar los puntos:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Graficar los nodos dados (ubicaciones)
Lat= data['Latitud']
Lon = data['Longitud']
Peso = data['Peso']
X = []
for i in range(len(data['Latitud'])):
    X.append(Lat[i])
    X.append(Lon[i])

X = np.array(X)

X = X.reshape(-1, 2,)


plt.scatter(Lat, Lon)
plt.show()</code></pre>
</div>
<p>Al ejecutar este fragmento de código, tendremos:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/ubicaciones-iniciales.png" alt="ubicaciones iniciales" width="393" height="248" class="size-full wp-image-28449 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/ubicaciones-iniciales.png 393w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/ubicaciones-iniciales-300x189.png 300w" sizes="(max-width: 393px) 100vw, 393px" /></p>
<p>Podemos apreciar cómo se encuentran dispersos los nodos iniciales, formando parte un mismo conjunto que es la población. Las coordenadas son latitud y longitud. Los nodos son, una vez más recordamos, las instituciones educativas, de acuerdo al caso de estudio.</p>
<h3>Paso 5: Agrupar los nodos geoespacialmente mediante Machine Learning</h3>

		<div id="paso-5-agrupar-los-nodos-geoespacialmente-mediante-machine-learning" data-title="Paso 5: Agrupar los nodos geoespacialmente mediante Machine Learning" class="index-title"></div>
	
<p>Cuando mencionamos Machine Learning, a menudo la primera consideración que tenemos es de complejidad. Pues bien, muchos de los algoritmos que hemos utilizado durante décadas son en realidad de aprendizaje automático, como por ejemplo la <a href="https://ingenieriaindustrialonline.com/pronostico-de-la-demanda/regresion-lineal-en-python/"><em><strong>regresión lineal</strong></em></a>. El algoritmo de <em>K-Means </em>que emplearemos de forma automatizada mediante <em>Python</em>, utiliza centroides que minimizan la inercia, o el criterio de suma de cuadrados de cada clúster:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/formula_cluster.png" alt="formula_cluster" width="203" height="65" class="size-full wp-image-28451 aligncenter" /></p>
<p>Me pareció conveniente explicar un poco la teoría, pero vayamos a la práctica. Toda vez que tenemos los nodos del modelo, lo siguiente que debemos indicar es la cantidad de agrupaciones que queremos (clúster). Ya que el problema plantea la disposición de 4 contenedores, vamos a dividir la población de nodos en 4 conjuntos.</p>
<p>Luego, correremos el algoritmo <em>K-Means, </em>veamos cómo:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Ejecutar el algoritmo KMeans
clusters = 4
KMean = KMeans(n_clusters=clusters)
KMean_g = KMean.fit_predict(X)
KMean.fit(X)</code></pre>
</div>
<p>Lo siguiente que haremos será establecer los centroides de cada clúster, es decir, la ubicación de nuestros contendores. En primer lugar, el modelo nos dará las coordenadas. Utilizaremos la siguiente línea de código:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Determinar los centroides de cada clúster
KMean.cluster_centers_</code></pre>
</div>
<p>Al ejecutarla tendremos:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centroides.png" alt="centroides" width="436" height="129" class="size-full wp-image-28452 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centroides.png 436w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centroides-300x89.png 300w" sizes="(max-width: 436px) 100vw, 436px" /></p>
<p>Estas son las coordenadas que indican el centro de cada uno de nuestros grupos de nodos. Y teóricamente ahí deberíamos disponer nuestros contenedores.</p>
<h3>Paso 6: Graficar los clusters y los centroides (Localizaciones múltiples)</h3>

		<div id="paso-6-graficar-los-clusters-y-los-centroides-localizaciones-multiples" data-title="Paso 6: Graficar los clusters y los centroides (Localizaciones múltiples)" class="index-title"></div>
	
<p>El siguiente paso consiste en graficar todas las coordenadas que ya tenemos: tantos los nodos iniciales, como los los centroides, o las localizaciones solución. Veamos cómo:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Graficar todas las coordenadas (Puntos y centroides)
plt.scatter(X[:,0], X[:,1], c=KMean_g) #Puntos iniciales

#Centroides
plt.scatter(KMean.cluster_centers_[0][0], KMean.cluster_centers_[0][1], s=50, c='r')
plt.scatter(KMean.cluster_centers_[1][0], KMean.cluster_centers_[1][1], s=50, c='r')
plt.scatter(KMean.cluster_centers_[2][0], KMean.cluster_centers_[2][1], s=50, c='r')
plt.scatter(KMean.cluster_centers_[3][0], KMean.cluster_centers_[3][1], s=50, c='r')</code></pre>
</div>
<p>Al ejecutar el fragmento tendremos:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centroides_II.png" alt="centroides_II" width="393" height="248" class="size-full wp-image-28453 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centroides_II.png 393w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centroides_II-300x189.png 300w" sizes="(max-width: 393px) 100vw, 393px" /></p>
<p>Vemos cómo se han efectuado las agrupaciones de los nodos (colores), y los marcadores rojos indican los centroides. Este debería ser el final de nuestro desarrollo, sin embargo, no sé si lo han notado, siempre hemos hablado de centroides, nunca de centros de gravedad o centros de masa. Bien, no sé si también han identificado que en ningún momento hemos considerado el peso de cada nodo, en este caso la <em>población estudiantil </em>de cada institución.</p>
<p><em>¿Esto qué implica?</em> Implica que hemos desarrollado un modelo que tiene exclusivamente consideraciones espaciales. De hecho, visualmente puede observarse cómo, básicamente los centroides se ubican en lo que podría considerarse el medio de cada clúster, sin ninguna consideración adicional aparente, por lo menos a la vista.</p>
<p>Pues bien, vamos a solucionarlo, para ello debemos retocar algunos de los pasos anteriores:</p>
<h3>Paso 5 (Recargado): Agrupar los nodos geoespacialmente mediante Machine Learning (Considerando atributos espaciales y no espaciales)</h3>

		<div id="paso-5-recargado-agrupar-los-nodos-geoespacialmente-mediante-machine-learning-considerando-atributos-espaciales-y-no-espaciales" data-title="Paso 5 (Recargado): Agrupar los nodos geoespacialmente mediante Machine Learning (Considerando atributos espaciales y no espaciales)" class="index-title"></div>
	
<p>Dentro de nuestro marco de datos (<em>DataFrame</em>) tenemos la información relacionada al peso de cada nodo (<em>Población estudiantil</em>). Pues bien, vamos a considerarla al ejecutar el algoritmo <em>K-Means</em>.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Ejecutar el algoritmo KMeans (Considerando el peso de los nodos)
clusters = 4
KMean = KMeans(n_clusters=clusters)
KMean_g = KMean.fit_predict(X)
KMean.fit(X, sample_weight=Peso)</code></pre>
</div>
<p>Lo siguiente que haremos será establecer los centroides de cada clúster, que ahora sí serán Centros de Gravedad; es decir, la ubicación de nuestros contendores. En primer lugar, el modelo nos dará las coordenadas. Utilizaremos la siguiente línea de código:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Determinar los centroides de cada clúster
KMean.cluster_centers_</code></pre>
</div>
<p>Al ejecutarla tendremos:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centroides_recargado.png" alt="centroides_recargado" width="436" height="129" class="size-full wp-image-28454 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centroides_recargado.png 436w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centroides_recargado-300x89.png 300w" sizes="(max-width: 436px) 100vw, 436px" /></p>
<p>Los centroides han cambiado, ahora son centros de gravedad afectados por el peso de cada nodo. Es posible que incluso haya cambiado la agrupación de los nodos (composición de los clusters).</p>
<h3>Paso 6 (Recargado): Graficar los clusters y los Centros de Gravedad (Localizaciones múltiples)</h3>

		<div id="paso-6-recargado-graficar-los-clusters-y-los-centros-de-gravedad-localizaciones-multiples" data-title="Paso 6 (Recargado): Graficar los clusters y los Centros de Gravedad (Localizaciones múltiples)" class="index-title"></div>
	
<p>El siguiente paso consiste en graficar todas las coordenadas que ya tenemos: tantos los nodos iniciales, como los los Centros de Gravedad, o las localizaciones solución. Veamos cómo:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Graficar todas las coordenadas (Puntos y centroides)
plt.scatter(X[:,0], X[:,1], c=KMean_g) #Puntos iniciales

#Centroides
plt.scatter(KMean.cluster_centers_[0][0], KMean.cluster_centers_[0][1], s=50, c='r')
plt.scatter(KMean.cluster_centers_[1][0], KMean.cluster_centers_[1][1], s=50, c='r')
plt.scatter(KMean.cluster_centers_[2][0], KMean.cluster_centers_[2][1], s=50, c='r')
plt.scatter(KMean.cluster_centers_[3][0], KMean.cluster_centers_[3][1], s=50, c='r')</code></pre>
</div>
<p>Al ejecutar el fragmento tendremos:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/Centros-de-gravedad.png" alt="Centros de gravedad" width="393" height="248" class="size-full wp-image-28455 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/Centros-de-gravedad.png 393w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/Centros-de-gravedad-300x189.png 300w" sizes="(max-width: 393px) 100vw, 393px" /></p>
<p>El resultado respecto a los centroides es diferente. La consideración de un atributo no espacial, en este caso el peso de cada nodo (<em>población estudiantil de cada institución educativa</em>), ha incidido en la ubicación propuesta de las localizaciones solución. Y este debería ser el final de nuestro modelo.</p>
<p>Hemos logrado agrupar nuestros puntos iniciales en clusters, y luego hemos determinado los Centros de Gravedad de cada uno de los clusters.</p>
<p>Por último, veamos una característica de la librería <em>K-Means </em>Análisis predictivo de nodos en clusters, es decir, de acuerdo a unas coordenadas dadas, podemos estimar el grupo al que pertenecerá un nuevo nodo.</p>
<h3>Paso 7: Predicción de clusters</h3>

		<div id="paso-7-prediccion-de-clusters" data-title="Paso 7: Predicción de clusters" class="index-title"></div>
	
<p>En primer lugar ejecutaremos una línea que nos permite identificar a cada nodo dentro de un grupo. Ya que tenemos 4 grupos, estos se identificarán de la siguiente manera: 0, 1, 2, 3.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>KMean.labels_</code></pre>
</div>
<p>Al ejecutar esta línea tendremos:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/grupos_nodos.png" alt="grupos_nodos" width="622" height="111" class="size-full wp-image-28456 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/grupos_nodos.png 622w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/grupos_nodos-300x54.png 300w" sizes="(max-width: 622px) 100vw, 622px" /></p>
<p>Vemos como cada una de las 60 instituciones educativas (nodos), tienen un identificador de grupo dentro del modelo.</p>
<p>Ahor, dadas las coordenadas de un nuevo nodo, podemos predecir el grupo al cual pertenecerá:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>sample_test=np.array([-3.433,-76.22])
second_test=sample_test.reshape(1, -1)
KMean.predict(second_test)</code></pre>
</div>
<p>Al ejecutar este fragmento tendremos:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/nodos_prediccion.png" alt="nodos_prediccion" width="483" height="118" class="size-full wp-image-28457 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/nodos_prediccion.png 483w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/nodos_prediccion-300x73.png 300w" sizes="(max-width: 483px) 100vw, 483px" /></p>
<p>Es decir, el algoritmo predice que de acuerdo a las coordenada dadas, el nuevo nodo formaría parte del clúster 0.</p>
<hr />
<p>Pudimos observar cómo varía el resultado dependiendo de la consideración de atributos netamente geoespaciales, y de atributos no espaciales, como la ponderación de cada nodo.</p>

		<div class="post-content-slideshow-outer">
			<div class="post-content-slideshow">

			<div class="loader-overlay"><div class="spinner-circle"></div></div>

				<div class="tie-slick-slider">

			<div class="slide post-content-slide">
				 Atributos geoespaciales |</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centroides_II.png" alt="centroides_II" width="393" height="248" class="size-full wp-image-28453 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centroides_II.png 393w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centroides_II-300x189.png 300w" sizes="(max-width: 393px) 100vw, 393px" /></p>

			</div><!-- post-content-slide -->
		

			<div class="slide post-content-slide">
				 Atributos geoespaciales y de ponderación |</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/Centros-de-gravedad.png" alt="Centros de gravedad" width="393" height="248" class="size-full wp-image-28455 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/Centros-de-gravedad.png 393w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/Centros-de-gravedad-300x189.png 300w" sizes="(max-width: 393px) 100vw, 393px" /></p>

			</div><!-- post-content-slide -->
		


					<div class="slider-nav-wrapper">
						<ul class="tie-slider-nav"></ul>
					</div>
				</div><!-- tie-slick-slider -->
			</div><!-- post-content-slideshow -->
		</div><!-- post-content-slideshow-outer -->
	
<h2>Integración con mapas de calor y entornos geográficos reales</h2>
<p>Uno de los puntos negativos del modelo que acabamos de desarrollar es quizá que no nos permite visualizar gráficamente la densidad de los puntos. Si observamos las gráficas, todos los puntos parecen tener el mismo tamaño, y si bien esta no es una consideración para el funcionamiento del algoritmo; en el análisis preliminar quisiéramos tener esta herramienta. Otra consideración adicional sería la posibilidad de graficar todos nuestros puntos, y los centros de gravedad de cada (clúster) en un entorno geográfico real.</p>
<p>Pues bien, una de las ventajas fundamentales de <em>Python </em>consiste en que podemos integrar distintos desarrollos en nuestros modelos, tal es el caso del desarrollo que efectúanos en un artículo anterior (<a href="https://ingenieriaindustrialonline.com/localizacion-de-instalaciones/mapas-de-calor-y-algoritmo-de-centro-de-gravedad-utilizando-python/"><strong>Mapas de calor y Entornos geográficos reales</strong></a>); de tal manera que podamos complementar nuestro modelo.</p>
<p>No vamos a profundizar en la librerías, ni en la definición de las variables, para eso recomendamos leer el artículo. Veamos entonces, como complementamos este modelo:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>import folium
import statistics
from folium.plugins import HeatMap
mediaLong = statistics.mean(Lon)
mediaLat = statistics.mean(Lat)

# Crear un objeto de mapa base Map()
mapa = folium.Map(location=[mediaLat, mediaLong], zoom_start = 13)

# Crear una capa de mapa de calor
mapa_calor = HeatMap( list(zip(Lat, Lon, data["Peso"])),
                   min_opacity=0.2,
                   max_val=data["Peso"].max(),
                   radius=50, 
                   blur=50, 
                   max_zoom=1)

#Creamos el marcador de Centro de Gravedad
tooltip = 'Centro de gravedad'
folium.Marker([KMean.cluster_centers_[0][0], KMean.cluster_centers_[0][1]], popup="Centro", tooltip = tooltip).add_to(mapa)
folium.Marker([KMean.cluster_centers_[1][0], KMean.cluster_centers_[1][1]], popup="Centro", tooltip = tooltip).add_to(mapa)
folium.Marker([KMean.cluster_centers_[2][0], KMean.cluster_centers_[2][1]], popup="Centro", tooltip = tooltip).add_to(mapa)
folium.Marker([KMean.cluster_centers_[3][0], KMean.cluster_centers_[3][1]], popup="Centro", tooltip = tooltip).add_to(mapa)

# Adherimos la capa de mapa de calor al mapa principal
mapa_calor.add_to(mapa)
mapa</code></pre>
</div>
<p>Al ejecutar este fragmento tendremos el siguiente resultado:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/Mapa-de-calorII.png" alt="Mapa de calorII" width="700" height="394" class="size-full wp-image-28492 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/Mapa-de-calorII.png 700w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/Mapa-de-calorII-300x169.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/Mapa-de-calorII-390x220.png 390w" sizes="(max-width: 700px) 100vw, 700px" /></p>
<p>Ahora tenemos un modelo capaz de <strong>agrupar nuestros nodos en clusters</strong> de acuerdo a atributos geoespaciales; capaz de <strong>determinar los centros de gravedad</strong> de cada cluster de acuerdo a atributos no espaciales (en nuestro caso la población de los nodos); capaz de complementarse con una capa de visualización de <strong>mapa de calor</strong> que nos permite apreciar la densidad y todo esto <strong>puede visualizarse en un entorno geográfico real</strong>.</p>
<h2>Consideraciones finales</h2>

		<div id="consideraciones-finales" data-title="Consideraciones finales" class="index-title"></div>
	
<p>Ya lo expresamos anteriormente, la base del algoritmo <em>K-Means </em>es la consideración y minimización de la inercia, y en espacios de muy altas dimensiones, las distancias suelen inflarse, ya que esta no es una medida normalizada.</p>
<p>Sin embargo, para los efectos que hemos empleado, el algoritmo suele arrojar resultados satisfactorios.</p>
<p>El código completo de este desarrollo lo puedes encontrar en nuestro cuaderno: <a href="https://colab.research.google.com/drive/1qboyfxT1kdjn9XiqXudZBf74dUtxwCkn?usp=sharing" target="_blank" rel="noopener"><em><strong>Localización de varias instalaciones mediante agrupación geoespacial y Centro de Gravedad (Python)</strong></em></a>.</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/localizacion-de-instalaciones/localizacion-de-varios-almacenes-mediante-agrupacion-geoespacial/">Localización de varios almacenes mediante agrupación geoespacial</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://ingenieriaindustrialonline.com/localizacion-de-instalaciones/localizacion-de-varios-almacenes-mediante-agrupacion-geoespacial/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Mapas de calor y Algoritmo de Centro de Gravedad utilizando Python</title>
		<link>https://ingenieriaindustrialonline.com/localizacion-de-instalaciones/mapas-de-calor-y-algoritmo-de-centro-de-gravedad-utilizando-python/</link>
					<comments>https://ingenieriaindustrialonline.com/localizacion-de-instalaciones/mapas-de-calor-y-algoritmo-de-centro-de-gravedad-utilizando-python/#comments</comments>
		
		<dc:creator><![CDATA[Bryan Salazar López]]></dc:creator>
		<pubDate>Wed, 13 Oct 2021 04:14:19 +0000</pubDate>
				<category><![CDATA[Actualidad]]></category>
		<category><![CDATA[Análisis de datos]]></category>
		<category><![CDATA[Diseño y distribución en planta]]></category>
		<category><![CDATA[Gestión de almacenes]]></category>
		<category><![CDATA[Localización de instalaciones]]></category>
		<category><![CDATA[Logística]]></category>
		<category><![CDATA[Centro de masa]]></category>
		<category><![CDATA[Localización]]></category>
		<category><![CDATA[Localización de plantas]]></category>
		<category><![CDATA[Mapa de calor]]></category>
		<category><![CDATA[Mapas de calor]]></category>
		<category><![CDATA[Método del Centro de gravedad]]></category>
		<category><![CDATA[Métodos de localización]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://ingenieriaindustrialonline.com/?p=28400</guid>

					<description><![CDATA[<p>Ya en artículos anteriores hemos abordado el uso de tecnología como herramienta que permita desarrollar problemas de análisis preliminar y localización de instalaciones, con el objetivo de incrementar el nivel de aplicabilidad de estas herramientas, utilizando algunos entornos de programación, algunos sistemas de información geográfica, librerías de geocodificación; etc., que nos facilitan la adopción de &#8230;</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/localizacion-de-instalaciones/mapas-de-calor-y-algoritmo-de-centro-de-gravedad-utilizando-python/">Mapas de calor y Algoritmo de Centro de Gravedad utilizando Python</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></description>
										<content:encoded><![CDATA[
		<div id="introduccion" data-title="Introducción" class="index-title"></div>
	
<p>Ya en artículos anteriores hemos abordado el uso de tecnología como herramienta que permita desarrollar problemas de análisis preliminar y localización de instalaciones, con el objetivo de incrementar el nivel de aplicabilidad de estas herramientas, utilizando algunos entornos de programación, algunos sistemas de información geográfica, librerías de geocodificación; etc., que nos facilitan la adopción de soluciones basadas en entornos reales.</p>
<p>Algunos de los artículos que hemos desarrollado al respecto son:</p>

		<div class="checklist tie-list-shortcode">
<ul>
<li><a href="https://ingenieriaindustrialonline.com/diseno-y-distribucion-en-planta/metodo-de-localizacion-de-instalaciones-utilizando-mapas-de-calor/" target="_blank" rel="noopener"><strong>Mapas de calor utilizando Google Maps (Javascript)</strong></a></li>
<li><a href="https://ingenieriaindustrialonline.com/localizacion-de-instalaciones/metodo-del-centro-de-gravedad-utilizando-r/" target="_blank" rel="noopener"><strong>Método de Centro de Gravedad utilizando R</strong></a></li>
</ul>

		</div>
	
<p>En estos artículos mencionamos que los mapas de calor aplicados a la localización de instalaciones, <strong>son capas de visualización</strong> que no nos proporcionan como resultado una localización específica; <strong>sí nos proporcionan una visión de densidad basada en un factor de ponderación</strong> establecido (Peso). Sin embargo, al utilizar mapas de calor en entornos como por ejemplo <em>Python, </em>podemos integrar a esta herramienta, un método heurístico que nos proporcione una localización específica, como por ejemplo el <a href="https://ingenieriaindustrialonline.com/localizacion-de-instalaciones/metodo-del-centro-de-gravedad-utilizando-r/" target="_blank" rel="noopener"><strong>Centro de Gravedad</strong></a>.</p>
<p>El objetivo de este artículo será el de utilizar mapas de calor y el algoritmo de Centro de gravedad de manera simultánea a través de <em>Python</em>; como herramienta de análisis preliminar y de localización de una instalación.</p>
<p>En el desarrollo de este ejercicio emplearemos:</p>

		<div class="plus tie-list-shortcode">
<ul>
<li><em><strong>Colaboratory</strong>: </em>Este es un entorno de programación y ejecución virtual de Python desarrollado por Google. Nos permitirá no tener la necesidad de realizar ninguna instalación en nuestros equipos. Todo lo que desarrollemos lo ejecutaremos en un cuaderno virtual.</li>
<li><strong><em>Python</em></strong>: Este será el lenguaje de programación que vamos a utilizar, y advertimos: No es necesario tener conocimientos previos, y el objetivo del artículo no es convertirnos en programadores expertos. Utilizaremos fragmentos de códigos, librerías disponibles, y explicaremos lo necesario para configurar nuestro desarrollo de acuerdo a los objetivos específicos de nuestros modelos.</li>
<li><strong><em>Geopy</em></strong>: Las librerías son a <em>Python</em>, lo que las <em>apps </em>son a un teléfono celular. Esta es quizá una de las características más a tractivas de este lenguaje: Casi que existe una librería para cada necesidad. En este caso, <em>Geopy</em>, es una librería que nos permitirá geocodificar un conjunto de ubicaciones. Es decir, a partir de un listado de ciudades o direcciones, poder obtener sus coordenadas de latitud y longitud que nos permitan georeferenciar dichas ubicaciones.</li>
<li><em><strong>Statistics</strong>: </em>Esta es una librería con un conjunto de herramientas estadísticas que nos permitirán hallar algunas medidas centrales como soporte de los modelos.</li>
<li><em><strong>Folium</strong>: </em>Esta librería nos permitirá graficar sobre un sistema de geolocalización, emplear la capa de mapas de calor y georeferenciar el centro de gravedad del modelo.</li>
<li><strong><em>Pandas</em></strong>: <span>Es un paquete de Python que proporciona estructuras de datos rápidas, y flexibles, diseñadas para que el trabajo con datos estructurados (tabulares, multidimensionales, potencialmente heterogéneos) y de series de tiempo sea fácil e intuitivo.</span></li>
<li><em><strong>Numpy</strong>: </em>Es una librería que nos permitirá efectuar operaciones matriciales en Python.</li>
</ul>

		</div>
	
<hr />
<p>Para desarrollar estas herramientas, vamos a plantear un caso típico de localización de una instalación (por ejemplo un depósito) a partir de la consideración de otras instalaciones (nodos de demanda, por ejemplo).</p>
<h2>Caso de aplicación</h2>

		<div id="caso-de-aplicacion" data-title="Caso de aplicación" class="index-title"></div>
	
<blockquote class=" quote-simple "><p>La empresa Bio-Food está desarrollando un nuevo modelo de negocio en la ciudad de Cali, Colombia. Este consiste en abastecer de comida saludable a la comunidad por medio de Vending Machines (Máquinas expendedoras). En las etapas de formulación de negocio, algo quedó muy claro: El éxito de su modelo consiste en maximizar la disponibilidad de todos los SKUs en cantidades suficientes. Algunas de las referencias son productos perecederos, de manera que consideran que algunos de sus equipos requerirán visitas diarias de reposición, y otros quizá sean surtidos en más de dos ocasiones por día.</p></blockquote>
<blockquote class=" quote-simple "><p>La fase inicial de su proyecto consideró la instalación de 13 equipos expendedores en 13 instituciones de educación superior de la ciudad. La logística inicial de su proyecto fue caótica (en fase piloto), algunos equipos funcionaron como sub-bodegas; algunos equipos fueron abastecidos directamente por los proveedores; y consideran la instalación de una bodega principal desde la cual serán abastecidas todas las máquinas que tienen disponibles.</p></blockquote>
<blockquote class=" quote-simple "><p>La fase inicial del proyecto arrojó información alrededor del volumen de ventas de cada equipo. Por esta razón, el departamento de logística considera utilizar esta información para efectuar un análisis preliminar y establecer la localización de la bodega principal. A continuación, se relacionan los movimientos de los equipos (media de unidades vendidas por día):</p></blockquote>
<table width="664">
<tbody>
<tr>
<td width="80" style="text-align: center;"><strong>Máquina</strong></td>
<td width="459" style="text-align: center;"><strong>Institución / Lugar</strong></td>
<td width="125" style="text-align: center;"><strong>Unidades vendidas</strong></td>
</tr>
<tr>
<td style="text-align: center;">1</td>
<td style="text-align: center;">PONTIFICIA UNIVERSIDAD JAVERIANA</td>
<td style="text-align: center;">1800</td>
</tr>
<tr>
<td style="text-align: center;">2</td>
<td style="text-align: center;">UNIVERSIDAD DEL VALLE</td>
<td style="text-align: center;">1650</td>
</tr>
<tr>
<td style="text-align: center;">3</td>
<td style="text-align: center;">UNIVERSIDAD SAN BUENAVENTURA</td>
<td style="text-align: center;">1110</td>
</tr>
<tr>
<td style="text-align: center;">4</td>
<td style="text-align: center;">UNIVERSIDAD ICESI</td>
<td style="text-align: center;">1540</td>
</tr>
<tr>
<td style="text-align: center;">5</td>
<td style="text-align: center;">UNIVERSIDAD SANTIAGO DE CALI</td>
<td style="text-align: center;">820</td>
</tr>
<tr>
<td style="text-align: center;">6</td>
<td style="text-align: center;">UNIVERSIDAD AUTÓNOMA DE OCCIDENTE</td>
<td style="text-align: center;">1020</td>
</tr>
<tr>
<td style="text-align: center;">7</td>
<td style="text-align: center;">UNIVERSIDAD COOPERATIVA DE COLOMBIA</td>
<td style="text-align: center;">635</td>
</tr>
<tr>
<td style="text-align: center;">8</td>
<td style="text-align: center;">FUNDACIÓN UNIVERSITARIA SAN MARTÍN</td>
<td style="text-align: center;">420</td>
</tr>
<tr>
<td style="text-align: center;">9</td>
<td style="text-align: center;">UNIVERSIDAD LIBRE</td>
<td style="text-align: center;">569</td>
</tr>
<tr>
<td style="text-align: center;">10</td>
<td style="text-align: center;">UNIVERSIDAD ANTONIO NARIÑO</td>
<td style="text-align: center;">350</td>
</tr>
<tr>
<td style="text-align: center;">11</td>
<td style="text-align: center;">UNICATOLICA &#8211; FUNDACION UNIVERSITARIA CATÓLICA LUMEN GENTIUM</td>
<td style="text-align: center;">620</td>
</tr>
<tr>
<td style="text-align: center;">12</td>
<td style="text-align: center;">CECEP</td>
<td style="text-align: center;">800</td>
</tr>
<tr>
<td style="text-align: center;">13</td>
<td style="text-align: center;">UNIVERSIDAD OBRERA</td>
<td style="text-align: center;">250</td>
</tr>
</tbody>
</table>
<hr />
<p>Utilizar el algoritmo de Centro de Gravedad puede servir como referencia para establecer una ubicación tentativa. Sin embargo, las posibilidades de que las coordenadas solución correspondan con un espacio disponible para la ubicación de la instalación son, de verdad, muy pocas.</p>
<p>Por esta razón, es interesante complementar este modelo mediante una capa de visualización de mapas de calor, que consideren la densidad del volumen de ventas de los equipos. De esta manera, puede complementarse el análisis preliminar para la localización de la bodega principal.</p>
<hr />
<h3>Paso 1: Crear el entorno de trabajo en Colaboratory</h3>

		<div id="paso-1-crear-el-entorno-de-trabajo-en-colaboratory" data-title="Paso 1: Crear el entorno de trabajo en Colaboratory" class="index-title"></div>
	
<p><span>Lo primero que vamos a hacer consiste en crear un entorno de trabajo en <em>Google</em> </span><em>Colaboratory</em><span>, así que vayamos allá: </span><a href="https://colab.research.google.com/#create=true" target="_blank" rel="noopener"><em><strong>Abrir cuaderno nuevo</strong></em></a><span>.</span></p>
<p>Verán que tienen un lienzo para programar el modelo, así que en este cuaderno podemos ir generando las líneas de código que explicaremos en los pasos siguientes.</p>
<h3>Paso 2: Importar las librerías necesarias</h3>

		<div id="paso-2-importar-las-librerias-necesarias" data-title="Paso 2: Importar las librerías necesarias" class="index-title"></div>
	
<p>Respecto a las librerías, en la introducción del artículo hicimos una descripción de la funcionalidad de cada una, veamos como importarlas en nuestro entorno:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># importar las librerías necesarias
import pandas as pd
import numpy as np
import folium
import statistics
from folium.plugins import HeatMap
import geopy
from geopy.extra.rate_limiter import RateLimiter</code></pre>
</div>
<p>De esta manera, tenemos todo lo necesario para empezar a desarrollar nuestro código.</p>
<h3>Paso 3: Importar los datos desde Excel</h3>

		<div id="paso-3-importar-los-datos-desde-excel" data-title="Paso 3: Importar los datos desde Excel" class="index-title"></div>
	
<p>De acuerdo a las necesidades del modelo, podemos desarrollar un código que permita la entrada manual de la información, la captura de los datos desde entornos digitales (Internet, por ejemplo), o podemos, desde luego, alimentar nuestro modelo con información contenida en documentos externos, como es el caso de un archivo de Microsoft Excel.</p>
<p>Esta puede considerarse como una de las ventajas de utilizar <em>Python, </em>su capacidad de integrarse con cualquier fuente de datos. En nuestro caso, toda la información se encuentra contenida en un documento de Excel, el cual presenta el siguiente formato:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/datos.png" alt="datos" width="730" height="241" class="wp-image-28403 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/datos.png 853w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/datos-300x99.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/datos-768x253.png 768w" sizes="(max-width: 730px) 100vw, 730px" /></p>

		<div class="box download  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>Puedes descargar el documento de Excel que utilizamos en este ejemplo: <a href="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/vending_machines-1.xlsx" target="_blank" rel="noopener"><strong>Base de datos</strong></a>
			</div>
		</div>
	
<p>En <em>Colaboratory</em>, el siguiente fragmento permitirá cargar un archivo al entorno de ejecución:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>from google.colab import files

uploaded = files.upload()

for fn in uploaded.keys():
  print('User uploaded file "{name}" with length {length} bytes'.format(
      name=fn, length=len(uploaded[fn])))</code></pre>
</div>
<p>Al ejecutar este fragmento de código, se abrirá una ventana emergente del explorador que permitirá cargar nuestra base de datos, en nuestro caso el archivo tienen el nombre de <em>vending_machines.xlsx.</em></p>
<p>La siguiente línea de código permitirá almacenar los datos contenidos en el documento en un <em>Dataframe</em> de nuestro entorno, dentro de la variable <em>data</em>.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Leer el documento de Excel y almacenar los datos en la variable data
data = pd.read_excel('vending_machines.xlsx')</code></pre>
</div>
<h3>Paso 4: Geocodificar las ubicaciones</h3>

		<div id="paso-4-geocodificar-las-ubicaciones" data-title="Paso 4: Geocodificar las ubicaciones" class="index-title"></div>
	
<p>Nosotros podemos partir desde la disponibilidad de las coordenadas geográficas de los puntos disponibles (Equipos expendedores). En cuyo caso, geocodificar las ubicaciones no sería necesario. Sin embargo, en nuestro ejemplo, disponemos del nombre de cada institución donde se encuentran ubicados los equipos, así como la ciudad y el país (todos los equipos se encuentran en la misma ciudad); y es necesario geocodificar estos puntos para obtener las coordenadas de latitud y longitud de cada ubicación.</p>
<p>Para lograrlo, utilizaremos el servicio de la librería <em>Geopy (Llamado Nominatim)</em>, el cual puede geocodificar nuestras ubicaciones. El servicio funciona de una manera sencilla, recibe una cadena con la información de nuestra ubicación, por ejemplo: ciudad, país, nombre del lugar; y devuelve las coordenadas de latitud y longitud asociadas a cada cadena.</p>
<p>Las siguientes líneas en primer lugar construyen cada cadena por punto (dirección), es decir, concatenan los datos (país, ciudad y nombre del lugar), y luego son geocodificados por <em>Geopy</em>. Veamos:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># fusionamos los datos país, ciudad y lugar en una misma cadena de dirección
geopy.geocoders.options.default_user_agent = "my-application"
data["direccion"] = data["pais"] + ", " + data["ciudad"] + ", " + data["Institución / Lugar"]
#Envíamos los datos a geocodificación
servicio = geopy.Nominatim()
data["coordenadas"] = data["direccion"].apply(RateLimiter(servicio.geocode,min_delay_seconds=1))</code></pre>
</div>
<p>Pueden apreciar que el código establece que tanto las cadenas con las direcciones concatenadas, como las respuestas en manera de coordenadas, queden dentro del mismo <em>DataFrame </em>donde se encuentran los datos de entrada del modelo. Podemos en cualquier momento confirmar si la geocodificación se ha realizado correctamente, para eso imprimiremos las primeras cinco filas del  <em>DataFrame:</em></p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>data.head()</code></pre>
</div>
<p>Al ejecutar esta instrucción tenemos la siguiente salida:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/head.png" alt="head" width="1300" height="185" class="alignnone size-full wp-image-28405" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/head.png 1300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/head-300x43.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/head-1024x146.png 1024w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/head-768x109.png 768w" sizes="(max-width: 1300px) 100vw, 1300px" /></p>
<p>Podemos observar cómo bajo la columna &#8216;<em>direccion&#8217; </em>se han concatenado las columnas <em>&#8216;pais&#8217;, &#8216;ciudad&#8217; e &#8216;Institución / Lugar&#8217;</em>, tal como lo establecimos previamente. Así mismo, podemos observar la respuesta de <em>Geopy </em> bajo la columna <em>&#8216;coordenadas&#8217;</em>.</p>
<h3>Paso 5: Calcular el Centro de Gravedad</h3>

		<div id="paso-5-calcular-el-centro-de-gravedad" data-title="Paso 5: Calcular el Centro de Gravedad" class="index-title"></div>
	
<p>El algoritmo de <a href="https://ingenieriaindustrialonline.com/diseno-y-distribucion-en-planta/metodo-del-centro-de-gravedad/" target="_blank" rel="noopener"><strong>Centro de Gravedad</strong></a> es relativamente sencillo, mucho más cuando se cuenta con los datos debidamente organizados. Las operaciones son sencillas, y en <em>Python</em>, se pueden desarrollar fácilmente mediante productos de matrices.</p>
<p>Ahora bien, la base del algoritmo son las coordenadas. En la respuesta obtenida desde <em>Geopy</em>, que se encuentra contenida en la columna <em>&#8216;coordenadas&#8217;</em> del <em>DataFrame &#8216;data&#8217;, </em>hay información adicional a las coordenadas necesarias. De hecho el servicio nos devuelve un nombre del lugar, una subregión, puede mostrarnos alguna nomenclatura y claro está, las coordenadas de latitud y longitud. Por tal razón, lo primero que debemos hacer es extraer las coordenadas de latitud y longitud, para eso utilizaremos las siguientes líneas:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># Extraer las coordenadas de latitud y longitud en dos variables separadas (listas)
longs = [coord.longitude for coord in data["coordenadas"]]
lats = [coord.latitude for coord in data["coordenadas"]]</code></pre>
</div>
<p>Una vez que tengamos las coordenadas de todos los puntos en un par de variables separadas, procedemos a crear nuestro algoritmo de centro de gravedad:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>centro_gravedad = {}
centro_gravedad['longs'] = np.dot(longs, data['Unidades vendidas']) / np.sum(data['Unidades vendidas'])
centro_gravedad['lats'] = np.dot(lats, data['Unidades vendidas']) / np.sum(data['Unidades vendidas'])</code></pre>
</div>
<p>De esta manera, dentro del arreglo <em>&#8216;centro_gravedad&#8217; </em>deberán quedar las coordenadas solución. Recordemos que este algoritmo utiliza puntos ponderados, en este caso el peso que determina la ponderación está dado por el volumen de ventas de cada equipo (<em>&#8216;Unidades vendidas&#8217;</em>).</p>
<p>Podemos ver el resultado, imprimiendo la variable <em>&#8216;centro_gravedad&#8217;:</em></p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>print(centro_gravedad)</code></pre>
</div>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centro_gravedad.png" alt="centro_gravedad" width="618" height="85" class="size-full wp-image-28406 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centro_gravedad.png 618w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centro_gravedad-300x41.png 300w" sizes="(max-width: 618px) 100vw, 618px" /></p>
<p>Estas son las coordenadas solución del método de Centro de Gravedad. Sin embargo, recordemos que debemos complementar el desarrollo del modelo mediante un mapa de calor.</p>
<h3>Paso 6: Graficar la capa del mapa de calor y el marcador de Centro de Gravedad</h3>

		<div id="paso-6-graficar-la-capa-del-mapa-de-calor-y-el-marcador-de-centro-de-gravedad" data-title="Paso 6: Graficar la capa del mapa de calor y el marcador de Centro de Gravedad" class="index-title"></div>
	
<p>En este caso utilizaremos la librería <em>Folium </em>para ambos fines: la capa del mapa de calor, y el marcador de Centro de Gravedad.</p>
<p>Lo primero que haremos será calcular la media de las latitudes y las longitudes para centrar el mapa que obtendremos.</p>
<p>Lo segundo que haremos será crear nuestro mapa, el cual configuraremos con las medidas centrales y un zoom inicial que podemos modificar de acuerdo a nuestras necesidades.</p>
<p>Lo tercero que haremos será crear nuestra capa de mapa de calor, utilizaremos como peso las <em>&#8216;Unidades vendidas&#8217;</em> para ponderar cada punto del mapa (Vending Machines). Configuramos algunos parámetros a nuestro criterio como la <em>opacidad, la intensidad, el radio de cada punto</em>.</p>
<p>Por último crearemos el marcador de Centro de Gravedad, para ello utilizaremos las coordenadas solución que hallamos en el paso anterior.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># Calcular la media de las latitudes y las longitudes para centrar el mapa
mediaLong = statistics.mean(longs)
mediaLat = statistics.mean(lats)

# Crear un objeto de mapa base Map()
mapa = folium.Map(location=[mediaLat, mediaLong], zoom_start = 12)

# Crear una capa de mapa de calor
mapa_calor = HeatMap( list(zip(lats, longs, data["Unidades vendidas"])),
                   min_opacity=0.2,
                   max_val=data["Unidades vendidas"].max(),
                   radius=50, 
                   blur=50, 
                   max_zoom=1)

#Creamos el marcador de Centro de Gravedad
tooltip = 'Centro de gravedad'
folium.Marker([centro_gravedad['lats'], centro_gravedad['longs']], popup="Centro", tooltip = tooltip).add_to(mapa)

# Adherimos la capa de mapa de calor al mapa principal
mapa_calor.add_to(mapa)
mapa</code></pre>
</div>
<p>Al ejecutar estas líneas tendremos el siguiente resultado:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centro_gravedad_mapa_calor.png" alt="centro_gravedad_mapa_calor" width="1404" height="760" class="alignnone size-full wp-image-28407" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centro_gravedad_mapa_calor.png 1404w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centro_gravedad_mapa_calor-300x162.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centro_gravedad_mapa_calor-1024x554.png 1024w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centro_gravedad_mapa_calor-768x416.png 768w" sizes="(max-width: 1404px) 100vw, 1404px" /></p>
<p>Podemos utilizar el zoom para explorar diversas visualizaciones de densidad:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centro_gravedad_mapa_calor_II.png" alt="centro_gravedad_mapa_calor_II" width="1351" height="747" class="alignnone size-full wp-image-28408" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centro_gravedad_mapa_calor_II.png 1351w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centro_gravedad_mapa_calor_II-300x166.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centro_gravedad_mapa_calor_II-1024x566.png 1024w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centro_gravedad_mapa_calor_II-768x425.png 768w" sizes="(max-width: 1351px) 100vw, 1351px" /></p>
<p>El código completo de este desarrollo lo puedes encontrar en nuestro cuaderno: <a href="https://colab.research.google.com/drive/1CWCVFeh6NibQGTg62zF0lg1A9Qow0rld?usp=sharing" target="_blank" rel="noopener"><em><strong>Mapas de Calor y Centro de Gravedad</strong></em></a>.</p>
<hr />
<p>En este caso, y a diferencia del artículo <em><strong><a href="https://ingenieriaindustrialonline.com/diseno-y-distribucion-en-planta/metodo-de-localizacion-de-instalaciones-utilizando-mapas-de-calor/" target="_blank" rel="noopener">mapas de calor utilizando Google Maps</a>, </strong></em>empleamos una capa de visualización de densidad de cada punto, y simultáneamente identificamos unas coordenadas solución de una localización de acuerdo al modelo de Centro de Gravedad (Centro de masa). De esta forma, construimos una herramienta un poco más robusta que permite soportar los procesos de análisis preliminar de localización de instalaciones, ambientada en un entorno geográfico real.</p>
<p>Así mismo, en el desarrollo de la herramienta empleamos algunas instrucciones que nos permiten automatizar procesos de georeferenciación, y de uso de datos a partir de fuentes diversas.</p>
<hr />
<p><span>El alcance de este modelo se encuentra determinado por la localización de una sola instalación (depósito, almacén, etc.), y en los casos en los que se requiera determinar múltiples localizaciones, el modelo no aplica.</span></p>

		<div class="box info  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>Para ver un modelo para varias localizaciones: <a href="https://ingenieriaindustrialonline.com/localizacion-de-instalaciones/localizacion-de-varios-almacenes-mediante-agrupacion-geoespacial/"><em><strong>Localización de varios almacenes mediante agrupación espacial</strong></em></a>
			</div>
		</div>
	
<p>La entrada <a href="https://ingenieriaindustrialonline.com/localizacion-de-instalaciones/mapas-de-calor-y-algoritmo-de-centro-de-gravedad-utilizando-python/">Mapas de calor y Algoritmo de Centro de Gravedad utilizando Python</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://ingenieriaindustrialonline.com/localizacion-de-instalaciones/mapas-de-calor-y-algoritmo-de-centro-de-gravedad-utilizando-python/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
		<item>
		<title>Método del Centro de gravedad utilizando R</title>
		<link>https://ingenieriaindustrialonline.com/localizacion-de-instalaciones/metodo-del-centro-de-gravedad-utilizando-r/</link>
					<comments>https://ingenieriaindustrialonline.com/localizacion-de-instalaciones/metodo-del-centro-de-gravedad-utilizando-r/#comments</comments>
		
		<dc:creator><![CDATA[Bryan Salazar López]]></dc:creator>
		<pubDate>Tue, 05 Oct 2021 00:17:12 +0000</pubDate>
				<category><![CDATA[Actualidad]]></category>
		<category><![CDATA[Análisis de datos]]></category>
		<category><![CDATA[Diseño y distribución en planta]]></category>
		<category><![CDATA[Localización de instalaciones]]></category>
		<category><![CDATA[Logística]]></category>
		<category><![CDATA[Centro de masa]]></category>
		<category><![CDATA[Localización de Instalaciones]]></category>
		<category><![CDATA[Método del Centro de gravedad]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[R]]></category>
		<guid isPermaLink="false">https://ingenieriaindustrialonline.com/?p=28249</guid>

					<description><![CDATA[<p>Cuando escribí el artículo método del centro de gravedad hice esta anotación: «Recuerdo que cuando utilicé este algoritmo en la Universidad, utilizamos la copia de un mapa de la región, sobre ella trazamos un plano cartesiano, definimos las ubicaciones del caso, registramos las coordenadas y como resultado obtuvimos las coordenadas de la localización ideal. La dificultad subyace &#8230;</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/localizacion-de-instalaciones/metodo-del-centro-de-gravedad-utilizando-r/">Método del Centro de gravedad utilizando R</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Cuando escribí el artículo <strong><a href="https://ingenieriaindustrialonline.com/diseno-y-distribucion-en-planta/metodo-del-centro-de-gravedad/">método del centro de gravedad</a> </strong>hice esta anotación: «<em>Recuerdo que cuando utilicé este algoritmo en la Universidad, utilizamos la copia de un mapa de la región, sobre ella trazamos un plano cartesiano, definimos las ubicaciones del caso, registramos las coordenadas y como resultado obtuvimos las coordenadas de la localización ideal. La dificultad subyace en la falta de practicidad».</em></p>
<p><span>El objetivo de este artículo es precisamente ese, apoyarnos en las nuevas herramientas que nos proporciona la tecnología, para abordar un problema de localización de una instalación aplicando el algoritmo de centro de gravedad de una manera práctica. Para ello utilizaremos R, de hecho, utilizaremos las librerías de R en un cuaderno virtual de <em>Python</em>.</span></p>

		<div class="box info  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>R es un entorno y lenguaje de programación con un enfoque al análisis estadístico. 
			</div>
		</div>
	
<p>El resultado de nuestro código nos arrojará la localización, de acuerdo al sistema de coordenadas que empleemos, además de que graficará la misma, y los puntos ponderados del modelo (peso de acuerdo a su demanda, producción, capacidad, etc.).</p>
<div id="cc-m-5713476913" class="j-module n j-text ">
<h3><em>Ejemplo de aplicación del Centro de gravedad</em></h3>
<div class="cc-m-hgrid-column last">
<div id="cc-matrix-1350164513">
<div id="cc-m-5713479913" class="j-module n j-text ">
<p><img decoding="async" class="size-full wp-image-2067 alignleft" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2019/09/Sin-título-58.png" alt="" width="171" height="144" /></p>
<p>La empresa GASOL S.A desea ubicar una instalación intermedia que requiere de disponibilidad de gasolina, desea ubicar esta instalación entre la ciudad de Barrancabermeja y sus principales distribuidores. En el siguiente cuadro se relaciona la información acerca de las coordenadas y el aporte de galones de gasolina de cada distribuidor.</p>
</div>
</div>
</div>
<p><img decoding="async" class="aligncenter size-full wp-image-2068" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2019/09/Sin-título-59.png" alt="" width="419" height="148" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2019/09/Sin-título-59.png 419w, https://ingenieriaindustrialonline.com/wp-content/uploads/2019/09/Sin-título-59-300x106.png 300w" sizes="(max-width: 419px) 100vw, 419px" /></p>
<p><em>Ubicación de las puntos en el plano cartesiano:</em></p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2019/09/Plano-Cartesiano.png" alt="Método de gravedad" width="615" height="279" class="aligncenter wp-image-27319" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2019/09/Plano-Cartesiano.png 2560w, https://ingenieriaindustrialonline.com/wp-content/uploads/2019/09/Plano-Cartesiano-300x136.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2019/09/Plano-Cartesiano-1024x464.png 1024w, https://ingenieriaindustrialonline.com/wp-content/uploads/2019/09/Plano-Cartesiano-768x348.png 768w, https://ingenieriaindustrialonline.com/wp-content/uploads/2019/09/Plano-Cartesiano-1536x696.png 1536w, https://ingenieriaindustrialonline.com/wp-content/uploads/2019/09/Plano-Cartesiano-2048x928.png 2048w" sizes="(max-width: 615px) 100vw, 615px" /></p>
<p>Los cálculos empleados en el algoritmo manual pueden ser consultados en: <a href="https://ingenieriaindustrialonline.com/diseno-y-distribucion-en-planta/metodo-del-centro-de-gravedad/"><strong>Modelo de Centro de Gravedad</strong></a>.</p>
</div>
<h2>Requerimientos para ejecutar el modelo</h2>
<p>En este caso sugerimos utilizar un entorno virtual de programación. Recomendamos el uso de <em><strong>Colaboratory de Google</strong></em>, un entorno que cuenta con todas las herramientas necesarias para nuestros desarrollos en <em>Python</em>. No tendremos que instalar nada en nuestro equipo, y aprovecharemos la potencia de las máquinas de Google, y su capacidad para ejecutar R.</p>
<h2>Programación del modelo de Centro de Gravedad en R</h2>
<p>Vamos a asumir que utilizarán el entorno virtual de <em>Colaboratory</em>, así que vayamos allá: <a href="https://colab.research.google.com/#create=true"><em><strong>Abrir cuaderno nuevo</strong></em></a>.</p>
<p>Ya que este entorno está desarrollado para escribir y ejecutar códigos en <em>Python</em>, lo primero que debemos hacer es ejecutar el siguiente comando en nuestro cuaderno, para así poder utilizar R en nuestro entorno:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>import rpy2
rpy2.__version__
import rpy2.robjects.conversion
%load_ext rpy2.ipython</code></pre>
</div>
<p>Después de eso, cada vez que desee usar R, agregue %%R al comienzo de cada celda. Veamos.</p>
<p>Lo primero que crearemos será a función del cálculo del Centro de Gravedad, la matemática detrás de esto no es nada especial, se trata de un <em>producto cruzado de matrices</em> para cada coordenada, para lo que utilizaremos <em>crossprod</em> de R.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>%%R 
centro_gravedad &lt;- function(x,y,w){
  c(crossprod(x,w)/sum(w),crossprod(y,w)/sum(w))
}</code></pre>
</div>
<p><em>x </em>= Coordenada en <em>x</em> (Que puede ser una coordenada cartesiana, latitud, etc.)</p>
<p><em>y </em>= Coordenada en <em>y</em> (Que puede ser una coordenada cartesiana, longitud, etc.)</p>
<p><em>w = </em>Representa el peso de cada punto dado (Que puede ser la demanda, la capacidad, la producción)</p>
<p>En nuestro caso, <strong><em>x</em></strong> y <strong><em>y</em></strong> serán coordenadas cartesianas, y <em><strong>w</strong></em> será la capacidad de suministro de gasolina de cada distribuidor. La función anterior calculará el centro de gravedad de acuerdo a los datos de entrada del modelo.</p>
<p>Ahora crearemos el marco de datos (<em>dataframe </em>al cual llamaremos <em>estaciones_df</em>) en el que se consignarán nuestros datos de entrada:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>%%R
estaciones_df &lt;- as.data.frame(matrix(nrow=5,ncol=3))
encabezados(estaciones_df) &lt;- c("x","y","galones")

estaciones_df$x &lt;- c(58.7, 26.2, 32.9, 42.5, 36.4)
estaciones_df$y &lt;- c(4.4, 5.2, 3.8, 4.1, 6.2)
estaciones_df$galones &lt;- c(3800, 2700, 310, 420, 250)</code></pre>
</div>
<p>El código anterior crea un marco de datos (<em>matrix</em>) de 5 filas y 3 columnas. Las filas representan el número de estaciones de gasolina que nos da el ejemplo, y las columnas servirán para consignar 3 valores: <em>coordenada en x, coordenada en y y el suministro de gasolina en galones de cada estación</em>.</p>
<p>Consignaremos los datos de entrada del ejemplo cuidando el orden estricto en el que registramos la información. Al finalizar tendremos toda la información consignada en nuestro marco de datos; para comprobarlo ejecutaremos el siguiente código:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>%%R
(estaciones_df)</code></pre>
</div>
<p>Al ejecutarlo, tendremos la siguiente salida:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/gravedad_1.png" alt="gravedad_1" width="343" height="125" class="size-full wp-image-28250 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/gravedad_1.png 343w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/gravedad_1-300x109.png 300w" sizes="(max-width: 343px) 100vw, 343px" /></p>
<p>Podemos corroborar que la información se ha consignado adecuadamente.</p>
<p>El siguiente paso consiste en calcular el centro de gravedad del modelo, para eso asignaremos los datos de entrada a la función inicial de centro de gravedad:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>%%R
centro_gravedad(estaciones_df$x,estaciones_df$y,estaciones_df$galones)</code></pre>
</div>
<p>Recordemos que de acuerdo a la función inicial, esta necesita 3 argumentos: <em><strong>x</strong></em>, <em><strong>y</strong></em>, <em><strong>w</strong></em> (coordenada en <em>x</em>, coordenada en <em>y</em>, peso). Esta información se encuentra en el marco de datos de entrada (<em>estaciones_df</em>).</p>
<p>Al ejecutarlo, tendremos la siguiente salida:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/gravedad_2.png" alt="gravedad_2" width="464" height="43" class="size-full wp-image-28251 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/gravedad_2.png 464w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/gravedad_2-300x28.png 300w" sizes="(max-width: 464px) 100vw, 464px" /></p>
<p><span>Esto quiere decir que en el sistema de coordenadas utilizado para establecer las ubicaciones propuestas en el ejercicio, la instalación óptima se ubicaría en las coordenadas </span><em><strong>X = 44,2</strong></em><span> – </span><em><strong>Y = 4,7. </strong></em>podemos confirmar que son las mismas coordenadas obtenidas mediante el <a href="https://ingenieriaindustrialonline.com/diseno-y-distribucion-en-planta/metodo-del-centro-de-gravedad/"><strong>modelo matemático original de Centro de Gravedad</strong></a>.</p>
<p>Tanto el resultado obtenido, como las ubicaciones de entrada (estaciones) pueden graficarse para una mayor comprensión del modelo. Utilizaremos un gráfico utilizando la librería <em>ggplot2</em> que nos muestre las instalaciones con un marcador de acuerdo a su ponderación. Veamos:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>%%R
library(ggplot2)
grafico_df &lt;- rbind(estaciones_df,c(centro_gravedad(estaciones_df$x,estaciones_df$y,estaciones_df$galones),2500))
grafico_df$tipo &lt;- c(rep(x="distribuidores",times=5),"instalacion")
ggplot(data=grafico_df) + geom_point(mapping=aes(x=x,y=y,size=galones,color=tipo))</code></pre>
</div>
<p>Al ejecutar tendremos el siguiente resultado:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centro-de-gravedad.png" alt="centro de gravedad" width="480" height="480" class="size-full wp-image-28252 aligncenter" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centro-de-gravedad.png 480w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centro-de-gravedad-300x300.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/10/centro-de-gravedad-150x150.png 150w" sizes="(max-width: 480px) 100vw, 480px" /></p>
<p>El marcador azul representa la ubicación sugerida por el modelo, de acuerdo a la ubicación del suministro de acuerdo a las estaciones. Podemos ver como el tamaño de las estaciones varía de acuerdo a su capacidad de suministro. Esta forma de desarrollar el modelo de Centro de Gravedad puede complementarse con diversas fuentes de datos, <a href="https://ingenieriaindustrialonline.com/diseno-y-distribucion-en-planta/metodo-de-localizacion-de-instalaciones-utilizando-mapas-de-calor/"><strong>mapas de calor</strong></a>, tipos de coordenadas, etc.</p>
<hr />
<p>Puedes ver el cuaderno de este módulo en nuestro <em>Colaboratory: <a href="https://colab.research.google.com/drive/1wSSla3U6NuJJHX6t-eetFiUSTvDpENUF?usp=sharing"><strong>Problema de localización de instalaciones mediante Centro de Gravedad</strong></a>.</em></p>
<h2>Consideraciones finales</h2>
<p><span>El alcance de este modelo se encuentra determinado por la localización de una sola instalación (depósito, almacén, etc.), y en los casos en los que se requiera determinar múltiples localizaciones, el modelo no aplica.</span></p>

		<div class="box info  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>Para ver un modelo para varias localizaciones: <a href="https://ingenieriaindustrialonline.com/localizacion-de-instalaciones/localizacion-de-varios-almacenes-mediante-agrupacion-geoespacial/"><em><strong>Localización de varios almacenes mediante agrupación espacial</strong></em></a>
			</div>
		</div>
	
<hr />

		<div class="box info  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>Lo invitamos a leer: <a href="https://ingenieriaindustrialonline.com/diseno-y-distribucion-en-planta/metodo-de-localizacion-de-instalaciones-utilizando-mapas-de-calor/"><em><strong>Método de localización de instalaciones utilizando mapas de calor</strong></em></a> y <em><a href="https://ingenieriaindustrialonline.com/localizacion-de-instalaciones/mapas-de-calor-y-algoritmo-de-centro-de-gravedad-utilizando-python/" target="_blank" rel="noopener"><strong>Mapas de calor y Algoritmo de Centro de Gravedad utilizando Python</strong></a></em>
			</div>
		</div>
	
<p>La entrada <a href="https://ingenieriaindustrialonline.com/localizacion-de-instalaciones/metodo-del-centro-de-gravedad-utilizando-r/">Método del Centro de gravedad utilizando R</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://ingenieriaindustrialonline.com/localizacion-de-instalaciones/metodo-del-centro-de-gravedad-utilizando-r/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>Solución de un modelo de transporte mediante un algoritmo de asignación</title>
		<link>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/solucion-de-un-modelo-de-transporte-mediante-un-algoritmo-de-asignacion/</link>
					<comments>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/solucion-de-un-modelo-de-transporte-mediante-un-algoritmo-de-asignacion/#respond</comments>
		
		<dc:creator><![CDATA[Bryan Salazar López]]></dc:creator>
		<pubDate>Tue, 10 Aug 2021 18:26:15 +0000</pubDate>
				<category><![CDATA[Investigación de operaciones]]></category>
		<category><![CDATA[Google OR-Tools]]></category>
		<category><![CDATA[Modelo de transporte]]></category>
		<category><![CDATA[Problema de asignación]]></category>
		<category><![CDATA[Problema de transporte]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://ingenieriaindustrialonline.com/?p=26835</guid>

					<description><![CDATA[<p>En su versión más básica, un modelo de transporte tiene por objetivo llevar unidades de un punto específico llamado fuente u origen  hacia otro punto específico llamado destino. Para cumplir con este objetivo deberá satisfacer los requerimientos establecidos por los destinos (demanda), al tiempo que satisface la disponibilidad de las fuentes (oferta). Estos planes de transporte deberán cumplir algún &#8230;</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/solucion-de-un-modelo-de-transporte-mediante-un-algoritmo-de-asignacion/">Solución de un modelo de transporte mediante un algoritmo de asignación</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></description>
										<content:encoded><![CDATA[
		<div id="introduccion" data-title="Introducción" class="index-title"></div>
	En su versión más básica, un <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problema-del-transporte-o-distribucion/"><strong>modelo de transporte</strong></a> tiene por objetivo llevar unidades de un punto específico llamado <em><strong>fuente</strong></em> u <em><strong>origen</strong></em>  hacia otro punto específico llamado <em><strong>destino</strong></em>. Para cumplir con este objetivo deberá satisfacer los requerimientos establecidos por los destinos (demanda), al tiempo que satisface la disponibilidad de las fuentes (oferta). Estos planes de transporte deberán cumplir algún criterio de optimización: <em>minimizar distancias, minimizar tiempos, maximizar ganancias, </em>por citar algunos ejemplos.</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Figure-2021-08-10-093443.png" alt="" width="345" height="353" class="aligncenter size-full wp-image-26838" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Figure-2021-08-10-093443.png 345w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Figure-2021-08-10-093443-293x300.png 293w" sizes="(max-width: 345px) 100vw, 345px" /></p>
<p>Es conocido, que los <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/problemas-de-asignacion/"><strong>problemas de asignación puros</strong></a> son una variación del problema original del transporte, en cuyos casos las variables de decisión solo pueden tomar valores binarios. <em>¿Esto qué significa? </em>Pues bien, como algoritmo de red, un problema básico de transporte es un modelo de asignación genérico; modelo en el cual se pretende establecer la asignación entre fuentes y destinos; es decir, cuántas unidades se transportarán desde el origen <em>i </em>hacia el destino <em>j</em>. Se entiende además, que en el caso del transporte, estas variables de asignación no son necesariamente binarias.</p>
<p>El objetivo de este artículo consiste en utilizar las librerías del software Google OR-Tools (<em>Python</em>), para abordar un problema de transporte básico, de acuerdo a un modelo de asignación.</p>
<hr />
<h2>El problema</h2>

		<div id="el-problema" data-title="El problema" class="index-title"></div>
	
<p><span>Con el propósito de evaluar los resultados obtenidos a través del tratamiento de un problema técnicamente formulado y abordado, utilizaremos un caso descrito en el libro Investigación de Operaciones (9na edición), de Hamdy A. Taha (University of Arkansas, Fayetteville), (Ejemplo 5.1-1):</span></p>
<blockquote class=" quote-simple "><p>MG Auto cuenta con tres plantas en Los Ángeles, Detroit y Nueva Orleáns, y dos importantes centros de distribución en Denver y Miami. Las capacidades trimestrales de las tres plantas son 1000, 1500 y 1200 automóviles, y las demandas de los dos centros de distribución durante el mismo periodo son de 2300 y 1400 automóviles. La distancia en millas entre las plantas y los centros de distribución aparece en la siguiente tabla: <cite>Hamdy A. Taha</cite></p></blockquote>
<p><strong>Relación de distancias entre plantas y cedis</strong></p>
<table width="247">
<tbody>
<tr>
<td width="87" style="text-align: center;"></td>
<td width="80" style="text-align: center;"><strong>Denver</strong></td>
<td width="80" style="text-align: center;"><strong>Miami</strong></td>
</tr>
<tr>
<td style="text-align: center;"><strong>Los Ángeles</strong></td>
<td style="text-align: center;">1000</td>
<td style="text-align: center;">2690</td>
</tr>
<tr>
<td style="text-align: center;"><strong>Detroit</strong></td>
<td style="text-align: center;">1250</td>
<td style="text-align: center;">1350</td>
</tr>
<tr>
<td style="text-align: center;"><strong>Nueva Orleáns</strong></td>
<td style="text-align: center;">1275</td>
<td style="text-align: center;">850</td>
</tr>
</tbody>
</table>
<p><em>Distancia dada en millas</em></p>
<p>El caso también plantea que la compañía de transporte cobra 8 centavos por milla por automóvil. Así entonces, el objetivo del modelo no será minimizar la distancia total, sino minimizar el costo total de transporte. Con base en la distancia dada en millas y el costo de transporte unitario, construimos nuestra tabla de costos (redondeada al dólar más cercano):</p>
<p><strong>Relación de costos de distribución entre plantas y cedis</strong></p>
<table width="247">
<tbody>
<tr>
<td width="87" style="text-align: center;"></td>
<td width="80" style="text-align: center;"><strong>Denver</strong></td>
<td width="80" style="text-align: center;"><strong>Miami</strong></td>
</tr>
<tr>
<td style="text-align: center;"><strong>Los Ángeles</strong></td>
<td style="text-align: center;">80</td>
<td style="text-align: center;">215</td>
</tr>
<tr>
<td style="text-align: center;"><strong>Detroit</strong></td>
<td style="text-align: center;">100</td>
<td style="text-align: center;">108</td>
</tr>
<tr>
<td style="text-align: center;"><strong>Nueva Orleáns</strong></td>
<td style="text-align: center;">102</td>
<td style="text-align: center;">68</td>
</tr>
</tbody>
</table>
<p><em>Costo dado en dólares automóvil</em></p>
<p>La representación gráfica de la red sería la siguiente:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Figure-2021-08-10-093443-1.png" alt="Problema del transporte" width="487" height="401" class="aligncenter size-full wp-image-26839" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Figure-2021-08-10-093443-1.png 487w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Figure-2021-08-10-093443-1-300x247.png 300w" sizes="(max-width: 487px) 100vw, 487px" /></p>
<h2>Resolviendo un problema de transporte mediante Or Tools (Asignación)</h2>

		<div id="resolviendo-un-problema-de-transporte-mediante-or-tools-asignacion" data-title="Resolviendo un problema de transporte mediante Or Tools (Asignación)" class="index-title"></div>
	
<p>De acuerdo a lo mencionado en el artículo de<span> </span><a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/que-es-y-para-que-sirve-google-or-tools/"><strong>introducción a Google OR-Tools</strong></a>, esta herramienta soporta múltiples lenguajes de programación, así entonces, haremos uso del lenguaje de programación Python.</p>
<p>Utilizaremos un solucionador para programación lineal mixta: <em>SCIP.</em></p>
<h3>Importar librerías</h3>
<p>El primer paso consiste en importar la librería de Google Or Tools, esto nos permitirá utilizar todas sus funciones.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>f<span style="font-size: 14px; font-weight: lighter;">rom ortools.linear_solver import pywraplp</span></code></pre>
</div>
<h3>Declarar el solucionador</h3>
<p>Tal como lo mencionamos, vamos a utilizar un solucionador de <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-lineal-mixta-con-google-or-tools/"><strong>programación lineal mixta</strong></a>. Esto nos permitirá utilizar tanto variables enteras como variables continuas. Debemos declarar el solucionador <em>SCIP</em>.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>solver = pywraplp.Solver.CreateSolver('SCIP')</code></pre>
</div>
<h3>Crear la data del modelo</h3>
<p>La data base del modelo corresponde a la matriz de costos. Debemos de considerar que el orden en el que se ingresa la información corresponda a la relación precisa entre las variables; veamos:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>costos = [
    [ 80, 215],
    [100, 108],
    [102,  68],
]

num_plantas = len(costos)
num_cedis = len(costos[0])</code></pre>
</div>
<p>Así mismo, utilizaremos el método <em><strong>len </strong></em>para calcular el número de <em>plantas</em> y el número de <em>cedis</em>. En el caso de las <em>plantas</em>, contará el número de filas de la matriz; en el caso de los <em>cedis</em>, contará en número de columnas (para saber que contará las columnas de la matriz se utiliza <em>costos[0]</em>).</p>
<h3>Crear las variables del modelo</h3>
<p>Este es un problema básico, y como tal las únicas variables necesarias serán las asignaciones. Es decir, las variables <span>x[i, j], donde <em>i </em>representa el origen y <em>j </em>representa el destino. Este proceso es relativamente sencillo y puede generarse cada variable de manera manual, sin embargo, automatizaremos la creación de las variables por medio de ciclos, con el propósito de preparar nuestro código para problemas más robustos:</span></p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>x = {}
for i in range(num_plantas):
    for j in range(num_cedis):
        x[i, j] = solver.IntVar(0, solver.infinity(), '')</code></pre>
</div>
<p>De esta manera se crean las variables de asignación. Es decir, tendremos tantos <em>i </em>como número de plantas (orígenes), asociados a tantos <em>j </em>como número de cedis (destinos). La naturaleza de las variables es entera (solver.IntVar), ya que hace referencia a la distribución de automóviles. Desde la creación de las variables podemos definir su rango, en este caso diremos que será desde 0 hasta infinito (solver.infinity).</p>
<p>Así entonces, se entiende que, por ejemplo, la variable x[0,0] hace referencia al número de automóviles que se enviarán desde el origen <em>0 </em>(<em>Los Ángeles</em>) hacia el destino 0 (<em>Denver</em>). El orden de la asignación se basa en la matriz de costos.</p>
<p>El código anterior crea todas las variables de asignación necesarias.</p>
<h3>Crear las restricciones</h3>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Restricciones de disponibilidad (oferta en plantas)
solver.Add(solver.Sum([x[0, j] for j in range(num_cedis)]) &lt;= 1000)
solver.Add(solver.Sum([x[1, j] for j in range(num_cedis)]) &lt;= 1500)
solver.Add(solver.Sum([x[2, j] for j in range(num_cedis)]) &lt;= 1200)

#Restricciones de demanda (cedis)
solver.Add(solver.Sum([x[i, 0] for i in range(num_plantas)]) &gt;= 2300)
solver.Add(solver.Sum([x[i, 1] for i in range(num_plantas)]) &gt;= 1400) </code></pre>
</div>
<p>El método <em><strong>Add </strong></em>de <em>solver</em>, nos servirá para agregar las restricciones al modelo. Las restricciones de oferta y demanda necesarias se adicionan tal cual el código anterior. Explicaremos la primera de ellas:</p>
<p style="text-align: center;">solver.Add(solver.Sum([x[0, j] for j in range(num_cedis)]) &lt;= 80)</p>
<p>La anterior restricción indica que, la sumatoria de todas las variables x[0, j], para los <em>j </em>del conjunto <em>cedis </em>deberá ser menor o igual a 1000. Esto significa que la cantidad enviada desde el nodo (planta) <em>0 </em>hacia los destinos <em>j (Denver y Miami) </em>no puede ser mayor que su disponibilidad de automóviles.</p>
<h3>Crear la función objetivo</h3>
<p>Tal como lo planteamos desde la formulación del problema, el criterio de optimización des este modelo corresponde a minimizar el costo total de distribución. El siguiente fragmento calcula el producto entre cada variable x[i, j] y su respectivo costo c[i, j] (obtenida desde la matriz de costos). La sumatoria de productos corresponde a la ecuación objetivo.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>for i in range(num_plantas):
    for j in range(num_cedis):
        objective_terms.append(costos[i][j] * x[i, j])
solver.Minimize(solver.Sum(objective_terms))</code></pre>
</div>
<p>Luego se declara el criterio de optimización: <em>Minimize </em>(para minimizar) y <em>Maximize </em>(para maximizar). En este caso minimizará la sumatoria de los productos.</p>
<h3>Invocar al solucionador</h3>
<p>La siguiente línea indica que el modelo debe solucionarse.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>status = solver.Solve()</code></pre>
</div>
<h3>Configurar las salidas del modelo</h3>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>if status == pywraplp.Solver.OPTIMAL or status == pywraplp.Solver.FEASIBLE:
    print('Costo total = ', solver.Objective().Value(), '\n')

    for i in range(num_plantas):
        for j in range(num_cedis):
            print('|Planta {} -&gt; Cedi {} - Cantidad: {}|'.format(
            i,
            j,
            x[i, j].solution_value()))</code></pre>
</div>
<p>En el caso en el que el modelo encuentre una solución óptima, hemos configurado las salidas de tal manera que nos muestre la relación solución entre fuentes &gt; destinos y cantidades. Al ejecutar el modelo obtendremos la siguiente solución:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/transporte-solucion-asignacion.png" alt="" width="425" height="153" class="aligncenter size-full wp-image-26842" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/transporte-solucion-asignacion.png 425w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/transporte-solucion-asignacion-300x108.png 300w" sizes="(max-width: 425px) 100vw, 425px" /></p>
<p>Podemos también detallar mucho mejor las salidas del modelo, de acuerdo al nombre de las plantas y los cedis. Para ello, en la data de entrada creamos las listas correspondientes, veamos:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>costos = [
    [ 80, 215],
    [100, 108],
    [102,  68],
]

num_plantas = len(costos)
num_cedis = len(costos[0])

plantas = ['Los Ángeles', 'Detroit', 'New Orleans']
cedis = ['Denver', 'Miami']</code></pre>
</div>
<p>La información de las listas debe guardar correspondencia con el orden de la matriz de costos.</p>
<p>Ahora, configuramos nuevamente las salidas del modelo:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>if status == pywraplp.Solver.OPTIMAL or status == pywraplp.Solver.FEASIBLE:
    print('Costo total = ', solver.Objective().Value(), '\n')

    for i in range(num_plantas):
        for j in range(num_cedis):
            print('|{:^20} -&gt; {:^20} | Cantidad: {:^20}|'.format(
            plantas[i],
            cedis[j],
            x[i, j].solution_value()))</code></pre>
</div>
<p>En este caso hemos remplazado el índice solo de plantas y cedis, por el índice dentro del listado de plantas y cedis. Así entonces, tendremos la siguiente salida del modelo:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/transporte-solucion-asignacion_2.png" alt="" width="663" height="158" class="aligncenter size-full wp-image-26843" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/transporte-solucion-asignacion_2.png 663w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/transporte-solucion-asignacion_2-300x71.png 300w" sizes="(max-width: 663px) 100vw, 663px" /></p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/modelo-de-transporte-solucion-de-red.png" alt="" width="482" height="401" class="aligncenter size-full wp-image-26844" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/modelo-de-transporte-solucion-de-red.png 482w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/modelo-de-transporte-solucion-de-red-300x250.png 300w" sizes="(max-width: 482px) 100vw, 482px" /></p>
<p>Pueden cotejar los resultados obtenidos los cuales corresponden a los mismos resultados presentados por <em>Taha, quien utiliza </em>TORA como solucionador.</p>
<hr />
<h2>¿Cómo ejecutar el modelo?
		<div id="como-ejecutar-el-modelo" data-title="¿Cómo ejecutar el modelo?" class="index-title"></div>
	</h2>
<p><em><strong>Alternativa 1, ejecución en nuestro equipo:</strong></em></p>
<p>Lo primero que debemos considerar, en el caso de que queramos ejecutar este código en nuestro equipo, es que es preciso contar con la instalación de <em>Python </em>en nuestro equipo de cómputo y por supuesto, el software <em>OR-Tools.</em> Una guía detallada de la instalación de estos requerimientos la podrás encontrar en el siguiente enlace:</p>
<p style="text-align: center;"><a href="https://developers.google.com/optimization/install/python/windows" target="_blank" class="shortc-button medium blue">Instalación de OR-Tools para Python</a>
<p>Ahora, lo recomendable es trabajar con algún editor de código práctico (IDE), por ejemplo: <a href="https://www.sublimetext.com/3"><strong><em>Sublime Text</em></strong></a>, o <em><strong>Spyder</strong></em> (Una herramienta más completa y por ende más robusta y pesada).</p>
<p><em><strong>Alternativa 2, ejecución en un entorno virtual (Recomendado):</strong></em></p>
<p>Podemos utilizar del mismo modo, un entorno virtual. En este caso recomendamos el uso de <em><strong>Colaboratory de Google</strong></em>, un entorno que cuenta con todas las herramientas necesarias para nuestros desarrollos. No tendremos que instalar nada en nuestro equipo, y aprovecharemos la potencia de las máquinas de Google.</p>
<hr />
<p>El código completo de nuestro desarrollo lo presentamos a continuación.</p>

		<div class="box info  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>También puedes ver el cuaderno de este módulo en nuestro <em>Colaboratory: <a href="https://colab.research.google.com/drive/1a4ctY8P8XuP_XDLy4tGuwFe8vpQLYRtK?usp=sharing"><strong>Transporte mediante asignación</strong></a>.</em>
			</div>
		</div>
	
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># Importar la librería de Google OR-Tools
from ortools.linear_solver import pywraplp

# Declarar el solucionador que abordará el modelo
solver = pywraplp.Solver.CreateSolver('SCIP')

# Data del modelo
costos = [
    [ 80, 215],
    [100, 108],
    [102,  68],
]

plantas = ['Los Ángeles', 'Detroit', 'New Orleans']
cedis = ['Denver', 'Miami']

num_plantas = len(costos)
num_cedis = len(costos[0])

# Variables del modelo
x = {}
for i in range(num_plantas):
    for j in range(num_cedis):
        x[i, j] = solver.IntVar(0, solver.infinity(), '')
        
#Restricciones de disponibilidad (oferta en plantas) 
solver.Add(solver.Sum([x[0, j] for j in range(num_cedis)]) &lt;= 1000) 
solver.Add(solver.Sum([x[1, j] for j in range(num_cedis)]) &lt;= 1500) 
solver.Add(solver.Sum([x[2, j] for j in range(num_cedis)]) &lt;= 1200) 

#Restricciones de demanda (cedis) 
solver.Add(solver.Sum([x[i, 0] for i in range(num_plantas)]) &gt;= 2300) 
solver.Add(solver.Sum([x[i, 1] for i in range(num_plantas)]) &gt;= 1400)      



# Función objetivo con criterio de optimización: minimizar
objective_terms = []
for i in range(num_plantas):
    for j in range(num_cedis):
        objective_terms.append(costos[i][j] * x[i, j])

solver.Minimize(solver.Sum(objective_terms))

# Invoca el solucionador
status = solver.Solve()

# Configura los parámetros de impresión, las salidas del modelo
if status == pywraplp.Solver.OPTIMAL or status == pywraplp.Solver.FEASIBLE:
    print('Costo total = ', solver.Objective().Value(), '\n')

    for i in range(num_plantas):
        for j in range(num_cedis):
            print('|{:^20} -&gt; {:^20} | Cantidad: {:^20}|'.format(
            plantas[i],
            cedis[j],
            x[i, j].solution_value()))</code></pre>
</div>
<hr />
<p>También es posible integrar nuestro desarrollo con data proveniente de fuentes externas, como por ejemplo Excel.</p>
<p>En próximos artículos, resolveremos el problema de transporte básico mediante un algoritmo de flujo mínimo.</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/solucion-de-un-modelo-de-transporte-mediante-un-algoritmo-de-asignacion/">Solución de un modelo de transporte mediante un algoritmo de asignación</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/solucion-de-un-modelo-de-transporte-mediante-un-algoritmo-de-asignacion/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>¿Cómo hacer un diagrama de Pareto utilizando Excel o Python?</title>
		<link>https://ingenieriaindustrialonline.com/gestion-y-control-de-calidad/como-hacer-un-diagrama-de-pareto-utilizando-excel-o-python/</link>
					<comments>https://ingenieriaindustrialonline.com/gestion-y-control-de-calidad/como-hacer-un-diagrama-de-pareto-utilizando-excel-o-python/#respond</comments>
		
		<dc:creator><![CDATA[Bryan Salazar López]]></dc:creator>
		<pubDate>Tue, 10 Aug 2021 03:05:50 +0000</pubDate>
				<category><![CDATA[Gestión y control de calidad]]></category>
		<category><![CDATA[Diagrama de Pareto]]></category>
		<category><![CDATA[Herramientas de Calidad]]></category>
		<category><![CDATA[Microsoft Excel]]></category>
		<category><![CDATA[Pareto]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://ingenieriaindustrialonline.com/?p=26801</guid>

					<description><![CDATA[<p>El diagrama de Pareto es una variación del histograma tradicional, puesto que en el Pareto se ordenan los datos por su frecuencia de mayor a menor. El principio de Pareto, también conocido como la regla 80 &#8211; 20, enunció en su momento que «el 20% de la población, poseía el 80% de la riqueza». Su &#8230;</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/gestion-y-control-de-calidad/como-hacer-un-diagrama-de-pareto-utilizando-excel-o-python/">¿Cómo hacer un diagrama de Pareto utilizando Excel o Python?</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>El diagrama de Pareto es una variación del histograma tradicional, puesto que en el Pareto se ordenan los datos por su frecuencia de mayor a menor. El principio de Pareto, también conocido como la regla 80 &#8211; 20, enunció en su momento que «<em>el 20% de la población, poseía el 80% de la riqueza</em>».</p>
<p>Su uso se aplica con relativo éxito en muchos ámbitos, soportando metodologías de priorización, por ejemplo. El diagrama de Pareto hace parte de lo que se denominan técnicas gráficas de calidad, como lo son las <a href="https://ingenieriaindustrialonline.com/gestion-de-calidad/las-siete-herramientas-de-la-calidad/">siete herramientas básicas</a><strong>, </strong>utilizadas para la solución de problemas referentes a la calidad, mencionadas por primera vez por <em><strong>Kaoru Ishikawa</strong></em>.</p>
<p>El objetivo de este artículo consiste en el desarrollo de un par de metodologías de elaboración de diagramas de Pareto, haciendo uso de dos herramientas: la tradicional hoja de cálculo de Excel, y el popularizado lenguaje de programación Python.</p>
<h2>Diagrama de Pareto en Excel (2016)</h2>

		<div id="diagrama-de-pareto-en-excel-2016" data-title="Diagrama de Pareto en Excel (2016)" class="index-title"></div>
	
<p>Supongamos que un proceso que produce refrigeradores desea establecer controles sobre los defectos que aparecen en las unidades que salen como producto terminado en la línea de producción. Para ello se hace necesario determinar cuáles son los defectos más frecuentes. En primer lugar se clasificaron todos los defectos posibles:</p>

		<div class="thumbdown tie-list-shortcode">
<ul>
<li>Motor no detiene</li>
<li>No enfría</li>
<li>Burlete def.</li>
<li>Pintura def.</li>
<li>Rayas</li>
<li>No funciona</li>
<li>Puerta no cierra</li>
<li>Gavetas def.</li>
<li>Motor no arranca</li>
<li>Mala nivelación</li>
<li>Puerta def.</li>
<li>Otros</li>
</ul>

		</div>
	
<p>Después de inspeccionar 88 refrigeradores defectuosos, se obtuvo la siguiente tabla de frecuencias:</p>
<table width="215">
<tbody>
<tr>
<td width="135" style="text-align: center;"><strong>Tipo de defecto</strong></td>
<td width="80" style="text-align: center;"><strong>Frecuencia</strong></td>
</tr>
<tr>
<td>Burlete defectuoso</td>
<td style="text-align: center;">9</td>
</tr>
<tr>
<td>Pintura defectuosa</td>
<td style="text-align: center;">5</td>
</tr>
<tr>
<td>Gavetas defectuosas</td>
<td style="text-align: center;">1</td>
</tr>
<tr>
<td>Mala nivelación</td>
<td style="text-align: center;">1</td>
</tr>
<tr>
<td>Motor no arranca</td>
<td style="text-align: center;">1</td>
</tr>
<tr>
<td>Motor no se detiene</td>
<td style="text-align: center;">36</td>
</tr>
<tr>
<td>No enfría</td>
<td style="text-align: center;">27</td>
</tr>
<tr>
<td>No funciona</td>
<td style="text-align: center;">2</td>
</tr>
<tr>
<td>Otros</td>
<td style="text-align: center;">0</td>
</tr>
<tr>
<td>Puerta defectuosa</td>
<td style="text-align: center;">0</td>
</tr>
<tr>
<td>Puerta no cierra</td>
<td style="text-align: center;">2</td>
</tr>
<tr>
<td>Rayas</td>
<td style="text-align: center;">4</td>
</tr>
<tr>
<td style="text-align: center;"><strong>Total</strong></td>
<td style="text-align: center;"><strong>88</strong></td>
</tr>
</tbody>
</table>
<p>Ordenamos los datos de mayor  a menor y anexamos una columna de frecuencia relativa (porcentual) y otra de frecuencia relativa acumulada (porcentual). Para obtener las frecuencias relativas utilizamos la siguientes fórmulas:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Sin-titulo.png" alt="Frecuencia relativa" width="478" height="78" class="aligncenter size-full wp-image-26803" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Sin-titulo.png 478w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Sin-titulo-300x49.png 300w" sizes="(max-width: 478px) 100vw, 478px" /></p>
<p>Veamos por ejemplo la frecuencia relativa del defecto «<em><strong>motor no se detiene</strong></em>«:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Sin-titulo_1.png" alt="" width="500" height="86" class="aligncenter size-full wp-image-26804" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Sin-titulo_1.png 500w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Sin-titulo_1-300x52.png 300w" sizes="(max-width: 500px) 100vw, 500px" /></p>
<p>Podemos calcular la frecuencia relativa porcentual de la siguiente manera:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Sin-titulo_2.png" alt="" width="500" height="86" class="aligncenter size-full wp-image-26805" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Sin-titulo_2.png 500w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Sin-titulo_2-300x52.png 300w" sizes="(max-width: 500px) 100vw, 500px" /></p>
<p><em>Veamos cómo hacerlo en Excel:</em></p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Sin-titulo_3.png" alt="Tabla de frecuencias Excel" width="719" height="299" class="aligncenter size-full wp-image-26806" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Sin-titulo_3.png 719w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Sin-titulo_3-300x125.png 300w" sizes="(max-width: 719px) 100vw, 719px" /></p>
<p>Recuerde que previamente debe ordenar las frecuencias de mayor a menor. Utilizando esta formulación, obtenemos el siguiente resultado:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Sin-titulo_4.png" alt="" width="588" height="287" class="aligncenter size-full wp-image-26807" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Sin-titulo_4.png 588w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Sin-titulo_4-300x146.png 300w" sizes="(max-width: 588px) 100vw, 588px" /></p>
<p>Aquí podemos observar como la <em><strong>causa 1: motor no se detiene</strong></em> se ha presentado con una frecuencia de 36 defectos (de un total de 88 defectos totales), lo que representa un 40,91% del total de defectos. También podemos, con base en nuestra data de estudio, que las causas 1, 2 y 3, representan más del 80% de los defectos totales (81,82% exactamente). Si consideramos que estas son 3 causas de 12 causas totales, esto se aproxima a la regla empírica de Pareto (3/12 = 0,25). En este caso, el 81,82% de los defectos se debe al 25% de las causas.</p>
<p>Veamos ahora cómo graficar el Diagrama de Pareto tradicional:</p>
<p>Seleccionamos los datos de las columnas &#8216;Tipo de defecto&#8217;, &#8216;Frecuencia&#8217; y &#8216;Frecuencia relativa acumulada&#8217;. A continuación damos clic en el Menú “<em><strong>Insertar</strong></em>”; seleccionamos «<em><strong>Gráficos recomendados</strong></em>”; luego en “<em><strong>Pareto</strong></em>”.</p>

		<div class="post-content-slideshow-outer">
			<div class="post-content-slideshow">

			<div class="loader-overlay"><div class="spinner-circle"></div></div>

				<div class="tie-slick-slider">

			<div class="slide post-content-slide">
				 <img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel1.webp" alt="" width="1183" height="518" class="aligncenter size-full wp-image-26810" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel1.webp 1183w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel1-300x131.webp 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel1-1024x448.webp 1024w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel1-768x336.webp 768w" sizes="(max-width: 1183px) 100vw, 1183px" /> 
			</div><!-- post-content-slide -->
		

			<div class="slide post-content-slide">
				 <img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel2.webp" alt="" width="1183" height="518" class="aligncenter size-full wp-image-26811" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel2.webp 1183w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel2-300x131.webp 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel2-1024x448.webp 1024w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel2-768x336.webp 768w" sizes="(max-width: 1183px) 100vw, 1183px" /> 
			</div><!-- post-content-slide -->
		

			<div class="slide post-content-slide">
				
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel5.png" alt="Excel Pareto" width="1183" height="518" class="aligncenter size-full wp-image-26815" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel5.png 1183w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel5-300x131.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel5-1024x448.png 1024w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel5-768x336.png 768w" sizes="(max-width: 1183px) 100vw, 1183px" /></p>

			</div><!-- post-content-slide -->
		

			<div class="slide post-content-slide">
				
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel6.png" alt="Excel Pareto" width="1183" height="518" class="aligncenter size-full wp-image-26816" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel6.png 1183w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel6-300x131.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel6-1024x448.png 1024w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel6-768x336.png 768w" sizes="(max-width: 1183px) 100vw, 1183px" /></p>

			</div><!-- post-content-slide -->
		


					<div class="slider-nav-wrapper">
						<ul class="tie-slider-nav"></ul>
					</div>
				</div><!-- tie-slick-slider -->
			</div><!-- post-content-slideshow -->
		</div><!-- post-content-slideshow-outer -->
	
<p>A diferencia de otras versiones, desde <em>Microsoft Excel 2016</em> podemos acceder a este tipo de representación gráfica con suma facilidad:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel7.png" alt="Diagrama de Pareto Excel" width="611" height="370" class="aligncenter size-full wp-image-26817" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel7.png 611w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Excel7-300x182.png 300w" sizes="(max-width: 611px) 100vw, 611px" /></p>

		<div class="box download  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>Descargar la plantilla de Excel: <a href="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Diagrama-de-Pareto-Excel.xlsx"><strong>Diagrama de Pareto</strong></a>
			</div>
		</div>
	
<hr />
<h2>Diagrama de Pareto en Python</h2>

		<div id="diagrama-de-pareto-en-python" data-title="Diagrama de Pareto en Python" class="index-title"></div>
	
<p>Quienes prefieran utilizar algún lenguaje de programación, ya sea porque el diagrama que requieren acompañará un desarrollo o un modelo más robusto, integrará datos desde fuentes externas, o simplemente así lo prefieren; pueden utilizar Python para graficar su Pareto son suma facilidad. Explicaremos el código paso a paso, sin embargo, al finalizar, dejaremos el código completo para su disposición.</p>
<h4><em>Importar las librerías</em></h4>
<p>En este caso, requerimos del uso de dos librerías</p>

		<div class="checklist tie-list-shortcode">
<ul>
<li><strong>Pandas</strong>: Un paquete de Python que proporciona estructuras de datos rápidas, flexibles y expresivas diseñadas para que el trabajo con datos «relacionales» o «etiquetados» sea fácil e intuitivo.</li>
<li><strong>Matplotlib</strong>: Es una biblioteca completa para crear visualizaciones estáticas, animadas e interactivas en Python.</li>
</ul>

		</div>
	
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>import pandas as pd
import matplotlib.pyplot as plt</code></pre>
</div>
<h4><em>Crear la data de entrada y ordenarla</em></h4>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>df = pd.DataFrame({'frecuencia': [9, 5, 1, 1, 1, 36, 27, 2, 0, 0, 2, 4]})
df.index = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12']
df = df.sort_values(by='frecuencia',ascending=False)</code></pre>
</div>
<p>En la variable <em><strong>df </strong></em>quedará contenida la data de entrada en un <em>Dataframe</em>. Inicialmente agregaremos las frecuencias de acuerdo a nuestro ejemplo.</p>
<p>Posteriormente, por medio de <em><strong>df.index</strong></em> nombraremos las causas de acuerdo al orden de las frecuencias ya asignadas. Preferimos en este caso numerarlas en lugar de colocar el nombre de cada defecto. Por ejemplo:</p>
<p style="text-align: center;"><em>Burlete defectuoso = Causa 1 (Frecuencia 9)</em></p>
<p style="text-align: center;"><em>Pintura defectuosa = Causa 2 (Frecuencia 5)</em></p>
<p>La siguiente línea de código utiliza el método <em><strong>.sort_values</strong></em> para ordenar las frecuencias contenidas en <em><strong>df </strong></em>de forma descendente. El resultado parcial de <em><strong>df </strong></em>será:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/python_pareto.png" alt="python_pareto" width="228" height="243" class="aligncenter size-full wp-image-26820" /></p>
<h4><em>Calcular la frecuencia relativa porcentual acumulada</em></h4>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>frecuencia_relativa = df["frecuencia"].cumsum()
total_defectos = df["frecuencia"].sum()
df["relativa_acum_porcentual"] = frecuencia_relativa/total_defectos*100
</code></pre>
</div>
<p>La frecuencia relativa se calcula con la función <strong><em>.cumsum</em>. </strong>Por eso seleccionamos dentro del <em>DataFrame <strong>df</strong> </em>la columna <em>frecuencia. </em>El número total de defectos se calcula con la función <em><strong>.sum </strong></em>sobre la columna frecuencia.</p>
<p>Para calcular la frecuencia relativa porcentual, dividimos la columna <em>frecuencia relativa </em>entre el <em>total de defectos</em> y se multiplica por <em>100</em>. Tal como se puede observar en la última línea del código. El resultado parcial de <em><strong>df </strong></em>será:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/python_pareto-1.png" alt="" width="444" height="239" class="aligncenter size-full wp-image-26821" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/python_pareto-1.png 444w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/python_pareto-1-300x161.png 300w" sizes="(max-width: 444px) 100vw, 444px" /></p>
<p>&nbsp;</p>
<h4><em>Graficar el diagrama de Pareto</em></h4>
<p>El último paso corresponde a graficar:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>fig, ax = plt.subplots()
ax.bar(df.index, df["frecuencia"], color="C0")
ax2 = ax.twinx()
ax2.plot(df.index, df["cumpercentage"], color="C1", marker="o", ms=5)


plt.show() </code></pre>
</div>
<p>El resultado será:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Pareto_Python.png" alt="Pareto_Python" width="394" height="248" class="aligncenter size-full wp-image-26822" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Pareto_Python.png 394w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Pareto_Python-300x189.png 300w" sizes="(max-width: 394px) 100vw, 394px" /></p>
<hr />
<h2>¿Cómo ejecutar el modelo?
		<div id="como-ejecutar-el-modelo" data-title="¿Cómo ejecutar el modelo?" class="index-title"></div>
	</h2>
<p><em><strong>Alternativa 1, ejecución en nuestro equipo:</strong></em></p>
<p>Lo primero que debemos considerar, en el caso de que queramos ejecutar este código en nuestro equipo, es que es preciso contar con la instalación de <em>Python</em>.</p>
<p>Ahora, lo recomendable es trabajar con algún editor de código práctico (IDE), por ejemplo: <a href="https://www.sublimetext.com/3"><strong><em>Sublime Text</em></strong></a>, o <em><strong>Spyder</strong></em> (Una herramienta más completa y por ende más robusta y pesada).</p>
<p><em><strong>Alternativa 2, ejecución en un entorno virtual (Recomendado):</strong></em></p>
<p>Podemos utilizar del mismo modo, un entorno virtual. En este caso recomendamos el uso de <em><strong>Colaboratory de Google</strong></em>, un entorno que cuenta con todas las herramientas necesarias para nuestros desarrollos. No tendremos que instalar nada en nuestro equipo, y aprovecharemos la potencia de las máquinas de Google.</p>
<p>Puedes ver y ejecutar el cuaderno de este módulo en nuestro <em>Colaboratory: <a href="https://colab.research.google.com/drive/1ayGiC4HrISKZpPJ6a9qmDANeF3-xtrvQ?usp=sharing"><strong>Diagrama de Pareto en Python</strong></a>.</em></p>
<p>Código completo:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>import pandas as pd
import matplotlib.pyplot as plt

df = pd.DataFrame({'frecuencia': [9, 5, 1, 1, 1, 36, 27, 2, 0, 0, 2, 4]})
df.index = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12']
df = df.sort_values(by='frecuencia',ascending=False)
frecuencia_relativa = df["frecuencia"].cumsum()
total_defectos = df["frecuencia"].sum()
df["cumpercentage"] = frecuencia_relativa/total_defectos*100


fig, ax = plt.subplots()
ax.bar(df.index, df["frecuencia"], color="C0")
ax2 = ax.twinx()
ax2.plot(df.index, df["cumpercentage"], color="C1", marker="o", ms=5)


plt.show()</code></pre>
</div>
<hr />
<p>&nbsp;</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/gestion-y-control-de-calidad/como-hacer-un-diagrama-de-pareto-utilizando-excel-o-python/">¿Cómo hacer un diagrama de Pareto utilizando Excel o Python?</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://ingenieriaindustrialonline.com/gestion-y-control-de-calidad/como-hacer-un-diagrama-de-pareto-utilizando-excel-o-python/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Regresión lineal en Python</title>
		<link>https://ingenieriaindustrialonline.com/pronostico-de-la-demanda/regresion-lineal-en-python/</link>
					<comments>https://ingenieriaindustrialonline.com/pronostico-de-la-demanda/regresion-lineal-en-python/#respond</comments>
		
		<dc:creator><![CDATA[Bryan Salazar López]]></dc:creator>
		<pubDate>Tue, 03 Aug 2021 04:48:19 +0000</pubDate>
				<category><![CDATA[Inteligencia artificial]]></category>
		<category><![CDATA[Pronóstico de la demanda]]></category>
		<category><![CDATA[Algoritmo supervisado]]></category>
		<category><![CDATA[Ciencia de datos]]></category>
		<category><![CDATA[Machine learning]]></category>
		<category><![CDATA[Mínimos cuadrados]]></category>
		<category><![CDATA[Planeación de la demanda]]></category>
		<category><![CDATA[Pronósticos]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Regresión lineal]]></category>
		<guid isPermaLink="false">https://ingenieriaindustrialonline.com/?p=26386</guid>

					<description><![CDATA[<p>Quienes nos adentramos desde hace varios años en el mundo de la estimación de la demanda, hemos considerado a la regresión lineal, o al método de los mínimos cuadrados, como un modelo de pronóstico, no mucho más que eso. Sabemos que nos permite hallar el valor esperado de una variable aleatoria a cuando b toma un valor específico. Entendemos &#8230;</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/pronostico-de-la-demanda/regresion-lineal-en-python/">Regresión lineal en Python</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></description>
										<content:encoded><![CDATA[
		<div id="introduccion" data-title="Introducción" class="index-title"></div>
	
<p>Quienes nos adentramos desde hace varios años en el mundo de la estimación de la demanda, hemos considerado a la <a href="https://ingenieriaindustrialonline.com/pronostico-de-la-demanda/regresion-lineal/"><strong>regresión lineal</strong></a>, o al método de los mínimos cuadrados, como un modelo de pronóstico, no mucho más que eso.</p>
<p>Sabemos que nos permite hallar el valor esperado de una variable aleatoria <em><strong>a</strong></em> cuando <em><strong>b</strong></em><strong> </strong>toma un valor específico. Entendemos que este método implica un supuesto de linealidad cuando la demanda presenta un comportamiento creciente o decreciente.</p>
<p>Es común que hagamos uso de una hoja de cálculo sobre la cual aplicamos las fórmulas de regresión, tal como lo detallamos en este artículo de introducción: <a href="https://ingenieriaindustrialonline.com/pronostico-de-la-demanda/regresion-lineal/"><em><strong>regresión lineal</strong></em>.</a></p>
<p>Sin embargo, con los avances computacionales, con el incremento de la capacidad de procesamiento de información, con el crecimiento de las herramientas utilizadas en la ciencia de datos; con la consideración de términos como <em>inteligencia artificial, machine learning</em>, y muchos conceptos relacionados; podemos definir a la regresión lineal en este contexto: <em>La regresión lineal es una técnica paramétrica de Machine Learning. Es un algoritmo supervisado.</em></p>

		<div class="box info  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>Con «paramétrica» queremos decir que, incluso antes de mirar a los datos, ya sabemos cuántos parámetros (o coeficientes) son necesarios. En el caso que estemos usando una sola variable, <em><strong>x</strong></em>, sabemos que una línea necesita 2 parámetros. Con «supervisado» queremos decir que, el algoritmo aprende por si mismo 
			</div>
		</div>
	
<hr />
<p>En esta oportunidad, y teniendo en cuenta lo que venimos mencionando; utilizaremos <em>Python</em>, y una librería para aprendizaje automático: <a href="https://scikit-learn.org/"><em>scikit-learn</em></a>, para abordar modelos de regresión lineal. Las ventajas de <em>Python </em>como lenguaje que nos permita integrar diversas aplicaciones, fuentes de información, y posibilidades de modelamiento de datos a gran escala, son muchas. Por esta razón dejaremos esta herramienta a disposición.</p>
<h2 id="cc-m-header-5710915713" class="">Ejemplo de aplicación de Regresión lineal mediante <em>Python</em></h2>

		<div id="ejemplo-de-aplicacion-de-regresion-lineal-mediante-python" data-title="Ejemplo de aplicación de Regresión lineal mediante Python" class="index-title"></div>
	
<div id="cc-m-5860634213" class="j-module n j-hgrid ">
<div class="cc-m-hgrid-column last">
<div id="cc-matrix-1350138413">
<div id="cc-m-5710916713" class="j-module n j-text ">
<p>La juguetería <em>Gaby</em> desea estimar mediante regresión lineal simple las ventas para el mes de Julio de su nuevo carrito infantil <em>«Mate»</em>. La información del comportamiento de las ventas de todos sus almacenes de cadena se presenta en el siguiente tabulado:</p>
<p>&nbsp;</p>
</div>
</div>
</div>
</div>
<div id="cc-m-5710917013" class="j-module n j-table ">
<table style="margin-left: auto; margin-right: auto;" border="0" cellspacing="0" cellpadding="3" align="">
<tbody>
<tr dir="" lang="" style="background-color: #000000;" xml:lang="" align="" valign="" bgcolor="">
<td style="text-align: center;"><span style="color: #ffffff;">Mes</span></td>
<td colspan="1" rowspan="1" style="text-align: center;"><span style="color: #ffffff;">Periodo</span></td>
<td style="text-align: center;"><span style="color: #ffffff;">Ventas</span></td>
</tr>
<tr>
<td style="text-align: center;"> Enero</td>
<td colspan="1" rowspan="1" style="text-align: center;">1</td>
<td style="text-align: center;">7000</td>
</tr>
<tr>
<td colspan="1" rowspan="1" style="text-align: center;">Febrero</td>
<td colspan="1" rowspan="1" style="text-align: center;">2</td>
<td style="text-align: center;" colspan="1" rowspan="1">9000</td>
</tr>
<tr>
<td colspan="1" rowspan="1" style="text-align: center;">Marzo</td>
<td colspan="1" rowspan="1" style="text-align: center;">3</td>
<td style="text-align: center;" colspan="1" rowspan="1">5000</td>
</tr>
<tr>
<td colspan="1" rowspan="1" style="text-align: center;">Abril</td>
<td colspan="1" rowspan="1" style="text-align: center;">4</td>
<td style="text-align: center;" colspan="1" rowspan="1">11000</td>
</tr>
<tr>
<td colspan="1" rowspan="1" style="text-align: center;">Mayo</td>
<td colspan="1" rowspan="1" style="text-align: center;">5</td>
<td style="text-align: center;" colspan="1" rowspan="1">10000</td>
</tr>
<tr>
<td colspan="1" rowspan="1" style="text-align: center;">Junio</td>
<td colspan="1" rowspan="1" style="text-align: center;">6</td>
<td style="text-align: center;" colspan="1" rowspan="1">13000</td>
</tr>
</tbody>
</table>
</div>
<div id="cc-m-5710917313" class="j-module n j-text ">
<hr />
</div>
<h3>Importar librerías</h3>

		<div id="importar-librerias" data-title="Importar librerías" class="index-title"></div>
	
<p>Para llevar a cabo este ejercicio necesitaremos una serie de librerías, vamos a describir cada una de ellas:</p>

		<div class="checklist tie-list-shortcode">
<ul>
<li>Numpy: Es el paquete fundamental para la computación en matrices con Python.</li>
<li>Matplotlib: Es un paquete para crear gráficos en Python.</li>
<li>Sklearn: Un conjunto de módulos de Python para el aprendizaje automático y la minería de datos.</li>
</ul>

		</div>
	
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>import numpy as np
import matplotlib.pyplot as plt
from sklearn import linear_model
from sklearn.metrics import mean_squared_error, r2_score
</code></pre>
</div>
<p>Desde la librería <em>sklearn </em>importaremos las clases <em>liner_model </em>y <em>mean_squared_error</em> para obtener la regresión lineal por mínimos cuadrados y para evaluar la calidad de a regresión.</p>
<h3>Crear la data de entrada</h3>

		<div id="crear-la-data-de-entrada" data-title="Crear la data de entrada" class="index-title"></div>
	
<p>De acuerdo a nuestro ejemplo, los periodos los almacenaremos en una lista contenida en la variable <em><strong>x</strong></em>. Así mismo, los datos de la demanda los almacenaremos en una lista contenida en la variable <em><strong>y</strong></em>.</p>
<p>Crearemos una lista (<em><strong>z</strong></em>) con los períodos que queremos pronosticar, en este caso, las ventas para el periodo 7.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>x = [1,2,3,4,5,6]
y = [7000,  9000, 5000, 11000, 10000, 13000]
z = [7]

#Convertimos las listas de entrada en matrices
x = np.array(x)
y = np.array(y)
z = np.array(z)

#Graficamos los datos de entrada
plt.scatter(x,y,label='data', color='blue')
plt.title('Distribución entre meses y demanda');
</code></pre>
</div>
<p>Las listas con los datos de entrada los convertiremos en matrices para procesar posteriormente la información. El modelo de aprendizaje automático trabaja a partir de matrices.</p>
<p>Por último, graficaremos la data de entrada. El resultado parcial de nuestro programa será:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Figure-2021-08-02-220254.png" alt="regresión_lineal" width="388" height="264" class="aligncenter size-full wp-image-26388" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Figure-2021-08-02-220254.png 388w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Figure-2021-08-02-220254-300x204.png 300w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/Figure-2021-08-02-220254-220x150.png 220w" sizes="(max-width: 388px) 100vw, 388px" /></p>
<h3>Entrenar el modelo de regresión lineal</h3>

		<div id="entrenar-el-modelo-de-regresion-lineal" data-title="Entrenar el modelo de regresión lineal" class="index-title"></div>
	
<p>Crearemos una instancia de la clase <em>LinearRegression</em> con el nombre de <em>regresion_lineal</em>. A continuación, utilizaremos el método <em><strong>fit </strong></em>el cual ajusta el modelo lineal de acuerdo a los datos de entrada (<em><strong>x</strong></em>, <em><strong>y</strong></em>).</p>

		<div class="box info  ">
			<div class="box-inner-block">
				<span class="fa tie-shortcode-boxicon"></span>Utilizamos la rutina <em><strong>reshape</strong></em> para dar una nueva forma a la matriz <em><strong>x</strong></em>. No se modifica, solo cambia de forma, en este caso los datos contenidos en la matriz se almacenarán en una sola columna
			</div>
		</div>
	
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>regresion_lineal = linear_model.LinearRegression()
regresion_lineal.fit(x.reshape(-1,1), y) 
# Imprimimos los parámetros que ha estimado la regresión lineal
print('\nParámetros del modelo de regresión')
print('b (Pendiente) = ' + str(regresion_lineal.coef_) + ', a (Punto de corte) = ' + str(regresion_lineal.intercept_))
</code></pre>
</div>
<p>Lo siguiente que haremos &#8211; y ya que el modelo de regresión se ha ajustado a los datos de entrada &#8211; será obtener los parámetros de regresión:</p>

		<div class="checklist tie-list-shortcode">
<ul>
<li><em>coef_ = </em>Valor de la pendiente.</li>
<li><em>intercept_ = </em>intersección de la línea con el eje.</li>
</ul>

		</div>
	
<p>Hemos añadido algo de formato a los parámetros que mostrará el desarrollo. Veamos:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/regresion_lineal_parametros.png" alt="regresion_lineal_parametros" width="613" height="330" class="aligncenter size-full wp-image-26389" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/regresion_lineal_parametros.png 613w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/regresion_lineal_parametros-300x162.png 300w" sizes="(max-width: 613px) 100vw, 613px" /></p>
<p>En este punto ya hemos obtenido a través del modelo entrenado, los parámetros de regresión del conjunto de datos.</p>
<h3>Prediciendo datos a partir del modelo entrenado</h3>

		<div id="prediciendo-datos-a-partir-del-modelo-entrenado" data-title="Prediciendo datos a partir del modelo entrenado" class="index-title"></div>
	
<p>Toda vez que el modelo ha obtenido los parámetros de regresión, podemos predecir datos utilizando el método <em><strong>predict</strong></em>.</p>
<p>En este caso, queremos pronosticar el valor de la demanda (<em><strong>y</strong></em>) para el periodo 7 (<em><strong>z</strong></em>).</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>pronostico = regresion_lineal.predict(z.reshape(-1,1))

print('\nPronósticos')
for i in range(len(z)):
print('Pronóstico para el periodo {0} = {1} '.format(z[i], pronostico[i]))
</code></pre>
</div>
<p>La matriz de las predicciones se almacenará en la variable <em><strong>pronostico</strong>. </em>El método <em><strong>predict </strong></em>hará la tarea. Recordemos que los periodos a partir de los cuales queremos conocer la demanda se encuentran en la matriz <em><strong>z</strong></em>. Por ende, queremos predecir con base en los valores de <em>z. </em>Así:</p>
<p style="text-align: center;">pronostico = regresion_lineal.predict(z.reshape(-1,1))</p>
<p>Podríamos simplemente imprimir la variable <em><strong>pronostico</strong></em>, en cuyo caso obtendremos nuestras predicciones en forma de matriz. Sin embargo, hemos decidido utilizar un bucle que nos imprima línea por línea (útil en los casos en los cuales queramos ronostica más de un período). Veamos el resultado parcial de nuestro código:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/regresion_lineal_pronostico.png" alt="regresion_lineal_pronostico" width="622" height="383" class="aligncenter size-full wp-image-26390" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/regresion_lineal_pronostico.png 622w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/regresion_lineal_pronostico-300x185.png 300w" sizes="(max-width: 622px) 100vw, 622px" /></p>
<p>Podemos observar que la estimación de la regresión lineal del modelo que acabamos de entrenar para <em><strong>x = 7</strong></em> es <em><strong>y = 13066,66</strong></em>. Puede contrastar este resultado con el obtenido mediante hojas de cálculo: <a href="https://ingenieriaindustrialonline.com/pronostico-de-la-demanda/regresion-lineal/"><em><strong>regresión lineal</strong></em></a>.</p>
<h3>Evaluando la calidad de la regresión</h3>

		<div id="evaluando-la-calidad-de-la-regresion" data-title="Evaluando la calidad de la regresión" class="index-title"></div>
	
<p>Para evaluar la calidad del modelo de regresión desarrollado utilizaremos el error cuadrático medio y el coeficiente de determinación R². Ambos datos podemos obtenerlos de una manera muy sencilla mediante la librería empleada. Veamos:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>pronostico_entrenamiento = regresion_lineal.predict(x.reshape(-1,1))
mse = mean_squared_error(y_true = y, y_pred = pronostico_entrenamiento)
rmse = np.sqrt(mse)
print('\nEvaluación de calidad de la regresión')
print('Error Cuadrático Medio (MSE) = ' + str(mse))
print('Raíz del Error Cuadrático Medio (RMSE) = ' + str(rmse))

r2 = regresion_lineal.score(x.reshape(-1,1), y)
print('Coeficiente de Determinación R2 = ' + str(r2))
</code></pre>
</div>
<p>Para tratar de simplificar lo anterior, podemos decir que en la práctica lo necesario es calcular el pronóstico para los valores en <em><strong>x </strong></em>conocidos como valores de entrada; dicho en otras palabras, el pronóstico para los valores en <em><strong>x </strong></em> utilizados para entrenar el modelo. De esa manera el error cuadrático medio (método <em><strong>mean_squared_error</strong></em>) comparará el valor de los datos en <em><strong>y </strong></em>«reales» versus los valores en <em><strong>y </strong></em>«pronosticados» (Para los <em><strong>x</strong></em> conocidos &#8211; entrenamiento -).</p>
<p>Para hallar la raíz del error cuadrático básicamente usamos la función de Python <em><strong>sqrt</strong></em> que calcula la raíz cuadrada del argumento dado.</p>
<p>Para obtener el coeficiente R² utilizaremos el método <em><strong>score</strong></em>.</p>
<p>Imprimiremos los valores y obtendremos:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/regresion_lineal_evaluacion_calidad.png" alt="regresion_lineal_evaluacion_calidad" width="609" height="469" class="aligncenter size-full wp-image-26391" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/regresion_lineal_evaluacion_calidad.png 609w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/regresion_lineal_evaluacion_calidad-300x231.png 300w" sizes="(max-width: 609px) 100vw, 609px" /></p>
<p>El coeficiente R² igual a 0,532 indica la existencia de una correlación pero nada fuerte.</p>
<h3>Graficar la línea de la tendencia del modelo</h3>

		<div id="graficar-la-linea-de-la-tendencia-del-modelo" data-title="Graficar la línea de la tendencia del modelo" class="index-title"></div>
	
<p>Vamos a graficar la línea de la tendencia, para eso necesitamos los valores de entrada para <em><strong>x</strong></em> y la demanda pronosticada (dependiente). Utilizaremos <em><strong>plot</strong></em> de manera que la gráfica resultante sea una línea y dentro de los argumentos le daremos color rojo.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>plt.plot(x,pronostico_entrenamiento,label='data', color='red')
plt.xlabel('Meses')
plt.ylabel('Demanda')
</code></pre>
</div>
<p>Además nombraremos los ejes del gráfico. Veamos:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/regresion_lineal_tendencia_grafica.png" alt="regresion_lineal_tendencia_grafica" width="610" height="499" class="aligncenter size-full wp-image-26392" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/regresion_lineal_tendencia_grafica.png 610w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/regresion_lineal_tendencia_grafica-300x245.png 300w" sizes="(max-width: 610px) 100vw, 610px" /></p>
<p>Hasta aquí hemos logrado entrenar un modelo de regresión lineal; logramos obtener la predicción con base en el modelo entrenado. Logramos graficar los datos de entrada y la línea de tendencia. Del mismo modo logramos evaluar la calidad de la regresión mediante un par de indicadores.</p>
<p>Veamos ahora una pequeña variación:<em> queremos obtener predicciones para diversos periodos.</em> Lo único que necesitaremos es modificar la lista de entrada <em><strong>z</strong></em>. En esta lista contenemos los periodos que queremos pronosticar. Veamos lo que pasa cuando z = [7, 8, 9, 10].</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/regresion_lineal_tendencia_graficaII.png" alt="regresion_lineal_tendencia_graficaII" width="602" height="553" class="aligncenter size-full wp-image-26393" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/regresion_lineal_tendencia_graficaII.png 602w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/08/regresion_lineal_tendencia_graficaII-300x276.png 300w" sizes="(max-width: 602px) 100vw, 602px" /></p>
<p>Aquí apreciamos la utilidad del bucle que implementamos para imprimir los pronósticos línea por línea.</p>
<hr />
<p>A continuación, dejamos a disposición el código completo del modelo de regresión:</p>
<p>rojo.</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>import numpy as np
import matplotlib.pyplot as plt 
from sklearn import linear_model
from sklearn.metrics import mean_squared_error, r2_score

#Inputs
x = np.array([1,2,3,4,5,6]) #periodo de entrenamiento
y = np.array([7000,  9000, 5000, 11000, 10000, 13000])
z = np.array([7, 8, 9, 10, 11, 12]) #Periodos que deseo pronosticar

plt.scatter(x,y,label='data', color='blue')
plt.title('Distribución entre meses y demanda');

regresion_lineal = linear_model.LinearRegression()
regresion_lineal.fit(x.reshape(-1,1), y) 
print('\nParámetros del modelo de regresión')
print('b (Pendiente) = ' + str(regresion_lineal.coef_) + ', a (Punto de corte) = ' + str(regresion_lineal.intercept_))

# vamos a predecir el periodo 7 (z = [7]
pronostico = regresion_lineal.predict(z.reshape(-1,1))

print('\nPronósticos')
for i in range(len(z)):
    print('Pronóstico para el periodo {0} = {1} '.format(z[i], pronostico[i]))

pronostico_entrenamiento = regresion_lineal.predict(x.reshape(-1,1))
mse = mean_squared_error(y_true = y, y_pred = pronostico_entrenamiento)
rmse = np.sqrt(mse)
print('\nEvaluación de calidad de la regresión')
print('Error Cuadrático Medio (MSE) = ' + str(mse))
print('Raíz del Error Cuadrático Medio (RMSE) = ' + str(rmse))

r2 = regresion_lineal.score(x.reshape(-1,1), y)
print('Coeficiente de Determinación R2 = ' + str(r2))

plt.plot(x,pronostico_entrenamiento,label='data', color='red')
plt.xlabel('Meses')
plt.ylabel('Demanda')
</code></pre>
</div>
<div>
<div id="cc-m-5710911713" class="j-module n j-imageSubtitle ">
<div id="cc-m-5710919613" class="j-module n j-imageSubtitle ">
<hr />
<h2>¿Cómo ejecutar el modelo?
		<div id="como-ejecutar-el-modelo" data-title="¿Cómo ejecutar el modelo?" class="index-title"></div>
	</h2>
<p><em><strong>Alternativa 1, ejecución en nuestro equipo:</strong></em></p>
<p>Lo primero que debemos considerar, en el caso de que queramos ejecutar este código en nuestro equipo, es que es preciso contar con la instalación de <em>Python</em>.</p>
<p>Ahora, lo recomendable es trabajar con algún editor de código práctico (IDE), por ejemplo: <a href="https://www.sublimetext.com/3"><strong><em>Sublime Text</em></strong></a>, o <em><strong>Spyder</strong></em> (Una herramienta más completa y por ende más robusta y pesada).</p>
<p><em><strong>Alternativa 2, ejecución en un entorno virtual (Recomendado):</strong></em></p>
<p>Podemos utilizar del mismo modo, un entorno virtual. En este caso recomendamos el uso de <em><strong>Colaboratory de Google</strong></em>, un entorno que cuenta con todas las herramientas necesarias para nuestros desarrollos. No tendremos que instalar nada en nuestro equipo, y aprovecharemos la potencia de las máquinas de Google.</p>
<p>Puedes ver y ejecutar el cuaderno de este módulo en nuestro <em>Colaboratory: <a href="https://colab.research.google.com/drive/1gH-nOk7xJT9bVjaVRl0jj73pStImGUBF?usp=sharing"><strong>Regresión lineal en Python</strong></a>.</em></p>
</div>
</div>
</div>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/pronostico-de-la-demanda/regresion-lineal-en-python/">Regresión lineal en Python</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://ingenieriaindustrialonline.com/pronostico-de-la-demanda/regresion-lineal-en-python/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Análisis de sensibilidad gráfica mediante el uso de Python (Caso 1)</title>
		<link>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/analisis-de-sensibilidad-grafica-mediante-el-uso-de-python-caso-1/</link>
					<comments>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/analisis-de-sensibilidad-grafica-mediante-el-uso-de-python-caso-1/#respond</comments>
		
		<dc:creator><![CDATA[Bryan Salazar López]]></dc:creator>
		<pubDate>Sun, 18 Jul 2021 18:57:10 +0000</pubDate>
				<category><![CDATA[Investigación de operaciones]]></category>
		<category><![CDATA[Análisis de sensibilidad]]></category>
		<category><![CDATA[Intervalos de factibilidad]]></category>
		<category><![CDATA[Investigación de Operaciones]]></category>
		<category><![CDATA[Método gráfico]]></category>
		<category><![CDATA[Optimización]]></category>
		<category><![CDATA[Precio dual]]></category>
		<category><![CDATA[Precio sombra]]></category>
		<category><![CDATA[Programación lineal]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Solución gráfica]]></category>
		<guid isPermaLink="false">https://ingenieriaindustrialonline.com/?p=26239</guid>

					<description><![CDATA[<p>¿Qué es el análisis de sensibilidad en programación lineal? Quienes se adentren en los conceptos de la investigación de operaciones, propiamente en los conceptos de la programación lineal, deben considerar que existe algo más allá de la solución óptima. En investigación de operaciones, la solución de un modelo matemático establece una base para la toma &#8230;</p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/analisis-de-sensibilidad-grafica-mediante-el-uso-de-python-caso-1/">Análisis de sensibilidad gráfica mediante el uso de Python (Caso 1)</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h2>¿Qué es el análisis de sensibilidad en programación lineal?</h2>

		<div id="que-es-el-analisis-de-sensibilidad-en-programacion-lineal" data-title="¿Qué es el análisis de sensibilidad en programación lineal?" class="index-title"></div>
	
<p>Quienes se adentren en los conceptos de la investigación de operaciones, propiamente en los conceptos de la <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/programacion-lineal/"><em><strong>programación lineal</strong></em></a>, deben considerar que existe algo más allá de la solución óptima. En investigación de operaciones, la solución de un modelo matemático establece una base para la toma de decisiones; sin embargo, puede considerarse como esencial el <strong>análisis de los resultados obtenidos</strong>.</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/modelamiento.png" alt="modelamiento" width="565" height="61" class="aligncenter wp-image-26253 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/modelamiento.png 565w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/modelamiento-300x32.png 300w" sizes="(max-width: 565px) 100vw, 565px" /></p>
<p>Los resultados de un modelo de programación lineal pueden ofrecer mucha información adicional, <em>inputs </em>del análisis de resultados. Existen a grandes rasgos, dos tipos de análisis complementarios en PL:</p>

		<div class="checklist tie-list-shortcode">
<ul>
<li><strong>Análisis de sensibilidad</strong>: El cual determina las condiciones que mantendrán la solución actual sin cambios.</li>
<li><strong>Análisis postóptimo</strong>: El cual determina la nueva solución óptima cuando cambian los datos del modelo</li>
</ul>

		</div>
	
<p>En este artículo abordaremos el <strong>análisis de sensibilidad</strong>.</p>
<p>El análisis de sensibilidad en programación lineal corresponde al examen detallado de los límites dentro de los cuales los parámetros del modelo (recursos, utilidad o costo), pueden cambiar sin que esto afecte a la solución óptima y a la capacidad de calcular el impacto que tienen dichos cambios sobre la misma. Es decir, a partir de los resultados obtenidos de un modelo, podemos establecer una solución óptima y tenemos un rango en el cual podemos medir el impacto de los cambios en la disponibilidad de los recursos y los cambios en los coeficientes de la función objetivo. El análisis de dichas variaciones y el cálculo de los límites dentro de los cuales estas son válidas, es lo que se conoce como análisis de sensibilidad. Todo el análisis que se encuentre fuera de ese rango requeriría recalcular el modelo.</p>
<p>Como tal, constituye una herramienta importante en el análisis de los resultados obtenidos, sobre todo, en dos casos en particular:</p>
<ol>
<li><strong>La sensibilidad de la solución óptima ante los cambios de la disponibilidad de los recursos (lado derecho de las restricciones)</strong>. Es decir, cuáles serían los límites dentro de los cuáles los resultados del modelo me permiten calcular el impacto que tiene sobre la solución óptima, el aumento o la disminución de la disponibilidad de un recurso.</li>
<li><strong>La sensibilidad de la solución óptima ante los cambios en la utilidad unitaria o el costo unitario (coeficientes de las variables de la función objetivo)</strong>. Es decir, cuáles serían los límites dentro de los cuáles los resultados del modelo me permiten calcular el impacto que tiene sobre la solución óptima, el aumento o la disminución de la utilidad unitaria o el costo unitario asociada a las variables que forman parte de la función objetivo.</li>
</ol>
<h3>Análisis de sensibilidad gráfica, caso 1: Cambios en el lado derecho (Disponibilidad de recursos)</h3>

		<div id="analisis-de-sensibilidad-grafica-caso-1-cambios-en-el-lado-derecho" data-title="Análisis de sensibilidad gráfica, caso 1: Cambios en el lado derecho" class="index-title"></div>
	
<p>Con el propósito de evaluar los resultados obtenidos a través del tratamiento de un problema técnicamente formulado y abordado, utilizaremos un caso descrito en el libro Investigación de Operaciones (9na edición), de Hamdy A. Taha (University of Arkansas, Fayetteville), (Ejemplo 3.6-1):</p>
<blockquote class=" quote-simple "><p>JOBCO fabrica dos productos en dos máquinas. Una unidad del producto 1 requiere 2 horas en la máquina 1, y 1 hora en la máquina 2. Una unidad del producto 2 requiere 1 hora en la máquina 1, y 3 horas en la máquina 2. Los ingresos por unidad de los productos 1 y 2 son de $30 y $20, respectivamente. El tiempo de procesamiento diario total disponible en cada máquina es de 8 horas.</p></blockquote>
<p>Si <em><strong>x1</strong></em> y <em><strong>x2</strong></em> son las cantidades diarias de productos 1 y 2, respectivamente, el modelo se da como:</p>
<p style="text-align: center;"><strong>Zmax</strong> = 30<strong>x1</strong> + 20<strong>x2</strong></p>
<p>Sujeto a:</p>
<p style="text-align: center;">2<strong>x1</strong> + <strong>x2</strong> &lt;= 8 (Máquina 1)</p>
<p style="text-align: center;"><strong>x1</strong> + 3<strong>x2</strong> &lt;= 8 (Máquina 2)</p>
<p style="text-align: center;"><strong>x1</strong>, <strong>x2</strong> &gt;= 0 (No negatividad)</p>
<p>La forma en la que abordamos la <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/metodo-grafico-de-la-programacion-lineal-mediante-el-uso-de-python/"><strong>solución gráfica de un modelo de programación lineal mediante <em>Python</em></strong></a>, está ampliamente documentada. El código en <em>Python </em>que resuelve mediante método gráfico el problema anterior, lo presentamos a continuación:</p>
<hr />
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># Caso JOBCO: Investigación de Operaciones (9na edición), de Hamdy A. Taha 
# (University of Arkansas, Fayetteville), (Ejemplo 3.6-1)
# Autor del código en Python: Bryan Salazar López, Ing. M.Sc. (2021)

#Librerías necesarias
import matplotlib.pyplot as plt
import numpy as np
from shapely.geometry import LineString

#Ecuaciones e intervalos (Para tabular)
x = np.arange(-20, 20, 5)
y = np.arange(-20, 20, 5)
y1 = 8 - (2 * x)
y2 = (8 - x) / 3
y3 = 0 * x
x1 = 0 * y
z = (-30 * x) / 20

#Identificadores para las líneas
primera_linea = LineString(np.column_stack((x, y1)))
segunda_linea = LineString(np.column_stack((x, y2)))
tercera_linea = LineString(np.column_stack((x, y3)))
cuarta_linea = LineString(np.column_stack((x1, y)))
quinta_linea = LineString(np.column_stack((x, z)))

#Graficando las líneas
plt.plot(x, y1, '-', linewidth=2, color='b')
plt.plot(x, y2, '-', linewidth=2, color='g')
plt.plot(x, y3, '-', linewidth=2, color='r')
plt.plot(x1, y, '-', linewidth=2, color='y')
plt.plot(x, z, ':', linewidth=1, color='k')


#Generando las intersecciones (vértices)
primera_interseccion = cuarta_linea.intersection(segunda_linea)
segunda_interseccion = primera_linea.intersection(segunda_linea)
tercera_interseccion = primera_linea.intersection(tercera_linea)
cuarta_interseccion = tercera_linea.intersection(cuarta_linea)
quinta_interseccion = cuarta_linea.intersection(primera_linea)
sexta_interseccion = segunda_linea.intersection(tercera_linea)

#Graficando los vértices
plt.plot(*primera_interseccion.xy, 'o', color='k')
plt.plot(*segunda_interseccion.xy, 'o', color='k')
plt.plot(*tercera_interseccion.xy, 'o', color='k')
plt.plot(*cuarta_interseccion.xy, 'o', color='k')
plt.plot(*quinta_interseccion.xy, 'o', color='silver')
plt.plot(*sexta_interseccion.xy, 'o', color='silver')

#Identificando los valores de las coordenadas x y y de cada vértice
xi1m, yi1m = primera_interseccion.xy
xi2m, yi2m = segunda_interseccion.xy
xi3m, yi3m = tercera_interseccion.xy
xi4m, yi4m = cuarta_interseccion.xy
xi5m, yi5m = quinta_interseccion.xy
xi6m, yi6m = sexta_interseccion.xy

#Cambiamos el formato de matriz a float
xi1 = np.float64(np.array(xi1m))
xi2 = np.float64(np.array(xi2m))
xi3 = np.float64(np.array(xi3m))
xi4 = np.float64(np.array(xi4m))
xi5 = np.float64(np.array(xi5m))
xi6 = np.float64(np.array(xi6m))
yi1 = np.float64(np.array(yi1m))
yi2 = np.float64(np.array(yi2m))
yi3 = np.float64(np.array(yi3m))
yi4 = np.float64(np.array(yi4m))
yi5 = np.float64(np.array(yi5m))
yi6 = np.float64(np.array(yi6m))

#literales de las intersecciones (nombres en el gráfico)
plt.annotate(' A', (xi4, yi4))
plt.annotate(' B', (xi1, yi1))
plt.annotate(' C', (xi2, yi2))
plt.annotate(' D', (xi3, yi3))
plt.annotate(' E', (xi5, yi5))
plt.annotate(' F', (xi6, yi6))

#Evaluando la función objetivo en cada vértice
FOi1 = (xi1 * 30) + (yi1 * 20)
FOi2 = (xi2 * 30) + (yi2 * 20)
FOi3 = (xi3 * 30) + (yi3 * 20)
FOi4 = (xi4 * 30) + (yi4 * 20)

#Calculando el mejor resultado (Maximizar)
ZMAX = max(FOi1, FOi2, FOi3, FOi4)

#Imprimiendo la solución óptima en la consola
print('\n SOLUCIÓN ÓPTIMA')
print('Solución óptima: {} '.format(ZMAX))

#Ordenando las coordenadas de los vértices (Las coordenadas x en m y las coordenadas y en n)
m = [xi1, xi2, xi3, xi4]
n = [yi1, yi2, yi3, yi4]

#Graficando el polígono solución a partir de las coordenadas de los vértices (importante el orden según las manecillas)
plt.fill(m, n, color='silver')

#Identificando el índice del vértice de la mejor solución
dict1 = {0:FOi1, 1:FOi2, 2:FOi3, 3:FOi4}
posicion = max(dict1, key=dict1.get)

#Obteniendo las coordenadas del vértice de la mejor solución de acuerdo al índice del paso anterior
XMAX = m[posicion]
YMAX = n[posicion]

#Imprimiendo las coordenadas del vértice de la mejor solución (variables de decisión)
print('\n VARIABLES DE DECISIÓN')
print('Cantidad de producto X1 a producir: {} unidades'.format(XMAX))
print('Cantidad de producto X2 a producir: {} unidades'.format(YMAX))

#Configuraciones adicionales del gráfico
plt.grid()
plt.xlabel('X1')
plt.ylabel('X2')
plt.title('JOBCO')

plt.show()
</code></pre>
</div>
<p>Al ejecutar este código, de acuerdo a las instrucciones que pueden encontrar en el artículo de<a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/metodo-grafico-de-la-programacion-lineal-mediante-el-uso-de-python/"><strong> introducción al método gráfico mediante Python</strong></a>, obtendrán:</p>
<figure id="attachment_26264" aria-describedby="caption-attachment-26264" style="width: 640px" class="wp-caption aligncenter"><a href="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_metodo_grafico.webp"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_metodo_grafico.webp" alt="jobco_metodo_grafico" width="640" height="480" class="wp-image-26264 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_metodo_grafico.webp 640w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_metodo_grafico-300x225.webp 300w" sizes="(max-width: 640px) 100vw, 640px" /></a><figcaption id="caption-attachment-26264" class="wp-caption-text">Figura 2: Solución gráfica del caso JOBCO obtenida mediante Python</figcaption></figure>
<p>Así mismo, el siguiente resultado en la consola:</p>
<figure id="attachment_26265" aria-describedby="caption-attachment-26265" style="width: 593px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_solucion_optima.png" alt="jobco_solucion_optima" width="593" height="213" class="wp-image-26265 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_solucion_optima.png 593w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_solucion_optima-300x108.png 300w" sizes="(max-width: 593px) 100vw, 593px" /><figcaption id="caption-attachment-26265" class="wp-caption-text">Figura 3: Resultados del caso JOBCO en la consola de Windows (Solución óptima)</figcaption></figure>
<p>&nbsp;</p>
<p>Así entonces, tenemos establecido el código base sobre el cual abordaremos este primer caso de <em>análisis de sensibilidad: cambios en el lado derecho (disponibilidad de recursos)</em>.</p>
<p>La pregunta que pretende responder este caso de análisis de sensibilidad es <em>¿Qué pasaría con la función objetivo si cambia la disponibilidad de alguno de los recursos del problema?</em> El cual corresponde a un planteamiento muy lógico de análisis, puesto que nos ampliaría la información relevante para la toma de decisiones.</p>
<p>Acercando este interrogante a nuestro problema de ejemplo, la cuestión podría ser: <em>¿Qué pasaría con los ingresos totales si la máquina 1 cambia su capacidad?</em></p>
<p>Pensemos por un momento cómo podemos abordar este planteamiento de manera gráfica, pensemos por un momento cómo podemos utilizar nuestro código base para abordar este caso.</p>
<p>Y bien, se nos puede ocurrir en primer lugar, graficar la nueva línea que represente la nueva función de disponibilidad de la máquina 1. Recordemos que si la restricción base es la siguiente:</p>
<p style="text-align: center;">2<strong>x1</strong> + <strong>x2</strong> &lt;= 8 (Máquina 1)</p>
<p>Un incremento en la capacidad (variación), por ejemplo, se representaría mediante la siguiente inecuación:</p>
<p style="text-align: center;">2<strong>x1</strong> + <strong>x2</strong> &lt;= 8 + 1</p>
<p>Esto quiere decir que, la nueva disponibilidad de la <em><strong>máquina 1</strong></em> pasará de 8 horas a 9 horas. Y nuestro razonamiento nos indica que esta función, tendrá una representación distinta a la anterior, por ende sería de mucha utilidad graficarla. Veamos el código para adicionar en Python:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Ecuaciones e intervalos (Para tabular)
x = np.arange(-20, 20, 5)
y = np.arange(-20, 20, 5)
y1 = 8 - (2 * x)
y2 = (8 - x) / 3
y3 = 0 * x
x1 = 0 * y
z = (-30 * x) / 20
y1v = (8 + 1) - (2 * x) #Variación en la máquina 1 (8 + 1)

#Identificadores para las líneas
primera_linea = LineString(np.column_stack((x, y1)))
segunda_linea = LineString(np.column_stack((x, y2)))
tercera_linea = LineString(np.column_stack((x, y3)))
cuarta_linea = LineString(np.column_stack((x1, y)))
quinta_linea = LineString(np.column_stack((x, z)))
sexta_linea = LineString(np.column_stack((x, y1v))) #Nueva línea

#Graficando las líneas
plt.plot(x, y1, '-', linewidth=2, color='b')
plt.plot(x, y2, '-', linewidth=2, color='g')
plt.plot(x, y3, '-', linewidth=2, color='r')
plt.plot(x1, y, '-', linewidth=2, color='y')
#plt.plot(x, z, ':', linewidth=1, color='k') #Podemos ocultar esta línea (FO)
plt.plot(x, y1v, ':', linewidth=1, color='b') #Nueva línea
</code></pre>
</div>
<p>Podemos observar cómo agregamos tan solo 3 líneas de código, mediante la inclusión de la variable <em><strong>y1v </strong></em>(variación de la ecuación de la máquina 1).</p>
<p>La ecuación con la cual se representa la restricción de la máquina 1 corresponde a la siguiente:</p>
<p style="text-align: center;"><strong>y1</strong> = 8 &#8211; (2 * <strong>x</strong>)</p>
<p>Por ende, lo que hacemos es aumentar la capacidad de la máquina 1 (8) a una nueva capacidad (9 = 8 + 1):</p>
<p style="text-align: center;"><strong>y1v</strong> = (8 + 1) &#8211; (2 * <strong>x</strong>)</p>
<p>Las dos líneas de código adicionales graficarán esta nueva ecuación.</p>
<p>Al ejecutar nuestro código tendremos:</p>
<figure id="attachment_26266" aria-describedby="caption-attachment-26266" style="width: 640px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad.png" alt="jobco_analisis_sensibilidad" width="640" height="480" class="wp-image-26266 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad.png 640w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /><figcaption id="caption-attachment-26266" class="wp-caption-text">Figura 3: Representación gráfica de un cambio en la disponibilidad del recurso 1</figcaption></figure>
<p>En este caso, podemos observar cómo una variación en la disponibilidad del recurso 1 (máquina 1), se representa con una línea recta (línea azul punteada) que conserva la misma pendiente de la restricción 1 (línea azul continua), que se mueve paralela a ella, y que en consecuencia, genera una nueva intersección solución (intersección con la restricción 2 &#8211; línea verde).</p>
<p>En este nuevo vértice solución se encuentra un nuevo punto óptimo, es decir, una coordenada diferente en la cual al evaluar la función objetivo, tendremos la respuesta al interrogante planteado: <em>¿Qué pasaría con los ingresos totales si la máquina 1 cambia su capacidad?</em></p>
<p>En consecuencia, utilizaremos nuestro código para:</p>

		<div class="checklist tie-list-shortcode">
<ul>
<li>Establecer gráficamente el nuevo vértice</li>
<li>Nombrarlo gráficamente (Vértice <em>G</em>)</li>
<li>Determinar las coordenadas de este vértice (<em>G</em>)</li>
<li>Evaluar la función objetivo en este nuevo vértice (<em>G</em>)</li>
</ul>

		</div>
	
<p>El siguiente fragmento muestra detalladamente las adiciones al código base:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Generando las intersecciones (vértices)
primera_interseccion = cuarta_linea.intersection(segunda_linea)
segunda_interseccion = primera_linea.intersection(segunda_linea)
tercera_interseccion = primera_linea.intersection(tercera_linea)
cuarta_interseccion = tercera_linea.intersection(cuarta_linea)
quinta_interseccion = cuarta_linea.intersection(primera_linea)
sexta_interseccion = segunda_linea.intersection(tercera_linea)
septima_interseccion = sexta_linea.intersection(segunda_linea) #Nuevo vértice

#Graficando los vértices
plt.plot(*primera_interseccion.xy, 'o', color='k')
plt.plot(*segunda_interseccion.xy, 'o', color='k')
plt.plot(*tercera_interseccion.xy, 'o', color='k')
plt.plot(*cuarta_interseccion.xy, 'o', color='k')
plt.plot(*quinta_interseccion.xy, 'o', color='silver')
plt.plot(*sexta_interseccion.xy, 'o', color='silver')
plt.plot(*septima_interseccion.xy, 'o', color='k') #Graficar nuevo vértice

#Identificando los valores de las coordenadas x y y de cada vértice
xi1m, yi1m = primera_interseccion.xy
xi2m, yi2m = segunda_interseccion.xy
xi3m, yi3m = tercera_interseccion.xy
xi4m, yi4m = cuarta_interseccion.xy
xi5m, yi5m = quinta_interseccion.xy
xi6m, yi6m = sexta_interseccion.xy
xi7m, yi7m = septima_interseccion.xy #Coordenadas del nuevo vértice

#Cambiamos el formato de matriz a float
xi1 = np.float64(np.array(xi1m))
xi2 = np.float64(np.array(xi2m))
xi3 = np.float64(np.array(xi3m))
xi4 = np.float64(np.array(xi4m))
xi5 = np.float64(np.array(xi5m))
xi6 = np.float64(np.array(xi6m))
xi7 = np.float64(np.array(xi7m)) #Nueva coordenada en x
yi1 = np.float64(np.array(yi1m))
yi2 = np.float64(np.array(yi2m))
yi3 = np.float64(np.array(yi3m))
yi4 = np.float64(np.array(yi4m))
yi5 = np.float64(np.array(yi5m))
yi6 = np.float64(np.array(yi6m))
yi7 = np.float64(np.array(yi7m)) #Nueva coordenada en y

#literales de las intersecciones (nombres en el gráfico)
plt.annotate(' A', (xi4, yi4))
plt.annotate(' B', (xi1, yi1))
plt.annotate(' C', (xi2, yi2))
plt.annotate(' D', (xi3, yi3))
plt.annotate(' E', (xi5, yi5))
plt.annotate(' F', (xi6, yi6)) 
plt.annotate(' G', (xi7, yi7)) #Nombrar el nuevo vértice como G

#Evaluando la función objetivo en cada vértice
FOi1 = (xi1 * 30) + (yi1 * 20)
FOi2 = (xi2 * 30) + (yi2 * 20)
FOi3 = (xi3 * 30) + (yi3 * 20)
FOi4 = (xi4 * 30) + (yi4 * 20)
FOi4 = (xi7 * 30) + (yi7 * 20) #Calcular la FO en el nuevo vértice

#Imprimiendo la solución óptima en la consola
print('\n SOLUCIÓN ÓPTIMA')
print('Solución óptima: {} '.format(ZMAX))
print('Función objetivo en punto G: {} '.format(FOi7)) #Mostrar la FO en el punto G
</code></pre>
</div>
<p>Lo que realizamos es muy sencillo, es decir, ya que tenemos una nueva línea, realizamos los mismos procedimientos para identificar y graficar el nuevo vértice generado. Al ejecutar el código obtendremos lo siguiente:</p>
<figure id="attachment_26267" aria-describedby="caption-attachment-26267" style="width: 640px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad1.png" alt="jobco_analisis_sensibilidad1" width="640" height="480" class="wp-image-26267 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad1.png 640w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad1-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /><figcaption id="caption-attachment-26267" class="wp-caption-text">Figura 4: Representación gráfica de un cambio en la disponibilidad del recurso 1 (Nuevo vértice)</figcaption></figure>
<p>&nbsp;</p>
<figure id="attachment_26268" aria-describedby="caption-attachment-26268" style="width: 597px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_solucion_sensibilidad.png" alt="jobco_solucion_sensibilidad" width="597" height="226" class="wp-image-26268 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_solucion_sensibilidad.png 597w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_solucion_sensibilidad-300x114.png 300w" sizes="(max-width: 597px) 100vw, 597px" /><figcaption id="caption-attachment-26268" class="wp-caption-text">Figura 5: Resultados del caso JOBCO en la consola de Windows (Función objetivo en punto G)</figcaption></figure>
<p>&nbsp;</p>
<p>La <em>figura 5</em> ilustra el cambio de la solución óptima cuando se cambia la capacidad de la máquina 1. Si la capacidad diaria se incrementa de 8 a 9 horas, el nuevo óptimo se moverá al punto <strong><em>G</em></strong>. Los resultados que se pueden apreciar en la consola, nos permiten conocer que el valor de la función objetivo evaluada en el punto <em><strong>G </strong>(<strong>z</strong> en <strong>G</strong>)</em> equivale a 142,0. Es decir, un valor superior a 128,0 (<em><strong>z</strong></em> en <em><strong>C</strong></em>).</p>
<p>Con los datos obtenidos podemos establecer en este caso la tasa de cambio en la función objetivo a consecuencia del cambio de la capacidad de la máquina 1 de 8 a 9 horas. Esta tasa de cambio se denomina de diversas formas: <em><strong>valor unitario de un recurso </strong></em>(Taha); <em><strong>precio dual </strong></em>(múltiples solucionadores); <em><strong>shadow price </strong></em>(precio sombra).</p>
<p>Esta tasa representa el impacto del incremento unitario en la capacidad de un recurso en la función objetivo; se calcula de la siguiente manera:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/precio_dual.png" alt="precio_dual" width="517" height="59" class="aligncenter size-full wp-image-26269" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/precio_dual.png 517w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/precio_dual-300x34.png 300w" sizes="(max-width: 517px) 100vw, 517px" /></p>
<p>En nuestro ejemplo se calculará así:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/shadow_price.png" alt="shadow_price" width="346" height="232" class="aligncenter size-full wp-image-26270" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/shadow_price.png 346w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/shadow_price-300x201.png 300w" sizes="(max-width: 346px) 100vw, 346px" /></p>
<p>Esto indica que: <em>Un incremento unitario en la capacidad de la máquina 1, aumentará el ingreso en $14,0</em>. O lo que es igual: <em>Una reducción unitaria en la capacidad de la máquina 1, reducirá el ingreso en $14,0.</em></p>
<p>En lo que concierne a nuestro código e Python, es sencillo, resulta que nosotros ya tenemos la función objetivo evaluada en cada uno de los vértices del gráfico. Así entonces, será cuestión de crear una nueva variable que represente el <em><strong>precio dual </strong></em>y la operación correspondiente:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Precio dual (Restricción 1)
Dual1 = (FOi7 - ZMAX) #Calculamos el precio dual para la restricción 1

print('\n ANÁLISIS DE SENSIBILIDAD')
print('Precio dual de la máquina 1: {} $/h'.format(Dual1)) #Imprimimos el precio dual 1
</code></pre>
</div>
<p>Recordemos que la función objetivo del punto <em><strong>G </strong></em>(<em><strong>z</strong></em> en <em><strong>G</strong>) </em>se representa por la variable<em><strong> FOi7</strong></em> (función objetivo en la intersección 7). La operación efectúa la diferencia entre este valor y la función objetivo en el vértice óptimo base (<em><strong>ZMAX</strong></em>).</p>
<p>Al ejecutar el código obtendremos lo siguiente en la consola de Windows:</p>
<figure id="attachment_26271" aria-describedby="caption-attachment-26271" style="width: 592px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/precio_dual1.png" alt="precio_dual1" width="592" height="262" class="wp-image-26271 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/precio_dual1.png 592w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/precio_dual1-300x133.png 300w" sizes="(max-width: 592px) 100vw, 592px" /><figcaption id="caption-attachment-26271" class="wp-caption-text">Figura 6: Resultados del caso JOBCO (Precio dual máquina 1)</figcaption></figure>
<p>&nbsp;</p>
<p>Ahora bien, debemos recordar que hemos hecho énfasis en que el análisis de sensibilidad del caso 1 (cambios en el lado derecho de las restricciones &#8211; capacidad de los recursos), precisa de unos límites dentro de los cuáles los resultados del modelo me permiten calcular el impacto que tiene sobre la solución óptima. Es decir, para nuestro caso en particular: <em>los límites dentro de los cuales el precio dual para la máquina 1 es 14$/h. </em>Dicho de otra manera, estamos en condiciones de calcular el impacto de una variación en la máquina 1 en los ingresos totales, pero, <em>¿Hasta cuándo esa proyección es válida? </em>Y sí, existen unos límites dentro de los cuales nuestra proyección, tasa o precio dual es válido, por fuera de ellos, sería necesario recalcular.</p>
<p>Revisemos nuevamente la <em>figura 4</em>:</p>
<p><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad1.png" alt="jobco_analisis_sensibilidad1" width="640" height="480" class="aligncenter size-full wp-image-26267" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad1.png 640w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad1-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></p>
<p>Veamos cómo la línea que representa los cambios en la capacidad de la <em><strong>máquina 1 </strong></em>(recurso) se mueve paralela a sí misma, y bien puede hacerlo sobre el segmento de la línea que representa la capacidad de la <em><strong>máquina 2</strong></em> (línea verde, o algebraicamente: <em>línea BF, desde el punto B hasta el punto F</em>). Esa es básicamente la clave para determinar los límites de validez de nuestro precio dual, mejor conocidos como <em><strong>intervalos de factibilidad</strong></em>. Pero <em>¿Cuál es la clave? </em>Sí, la ecuación de la <em><strong>máquina 1</strong></em> puede moverse para formar una intersección con la <em><strong>línea verde</strong></em> (a medida que cambie su capacidad) entre <em><strong>B</strong></em> y <em><strong>F</strong></em>, de manera que en estos vértices se encuentran los límites. Y para ser directos, la idea consiste en calcular la capacidad de la máquina 1 en cada uno de estos vértices. <em>¿Cómo lo hacemos? </em>Veamos:</p>
<p>La ecuación que representa la capacidad de la máquina 1 está dada por:</p>
<p style="text-align: center;">2<strong>x1</strong> + <strong>x2</strong> &lt;= 8 horas (máquina 1)</p>
<p>Por ende, si retiramos el valor de la capacidad del recurso (8 horas), obtendremos la ecuación de la utilización o el uso del recurso:</p>
<p style="text-align: center;">2<strong>x1</strong> + <strong>x2 </strong>= Utilización o uso de la máquina 1</p>
<p>Ahora, teniendo la ecuación de la restricción 1 (máquina 1), podemos evaluar cuál sería su utilización en los vértices <em><strong>B </strong></em>y <em><strong>F</strong></em>. Así encontraríamos los límites de validez para esta restricción (intervalos de factibilidad para nuestro precio dual = 14$/h). Dicho de otro modo, encontraríamos la capacidad mínima y máxima de la máquina 1, para que una variación unitaria en la disponibilidad de la misma (máquina 1), represente una variación de 14$ en la función objetivo. Por fuera de ese intervalo, no podemos asegurar de ninguna manera que el precio dual es de 14$/h, sería necesario recalcular.</p>
<p>Volvamos a la determinación de los límites. Ya tenemos la ecuación de la restricción (la verdad, siempre la hemos tenido), ahora la evaluaremos en los puntos <em><strong>B </strong></em>y <em><strong>F</strong></em>. Para ello es preciso conocer las coordenadas de cada uno de estos puntos. En <em>Python</em> podemos utilizar el siguiente fragmento:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code>#Intervalos de factibilidad
IFmin1 = (2 * xi2) + yi2
IFmax1 = (2 * xi6) + yi6

print('Límite mínimo de factibilidad (PD máquina 1): {} h'.format(IFmin1))
print('Límite máximo de factibilidad (PD máquina 1): {} h'.format(IFmax1))
</code></pre>
</div>
<p>Veamos: El punto <em><strong>B</strong></em> según nuestro código, corresponde a la segunda intersección, por ende sus coordenadas están definidas por las variables <em><strong>xi1 </strong></em>y <em><strong>yi1</strong></em>. Así entonces, evaluamos la capacidad de la <em><strong>máquina 1</strong></em> en estas coordenadas y tenemos el límite mínimo de factibilidad para el precio dual de la máquina 1 <em>(<strong>IFmin1</strong>).</em></p>
<p>Así mismo, el punto <b><i>F </i></b>según nuestro código, corresponde a la sexta intersección, por ende sus coordenadas están definidas por las variables <em><strong>xi6 </strong></em>y <em><strong>yi6</strong></em>. Así entonces, evaluamos la capacidad de la <em><strong>máquina 1</strong></em> en estas coordenadas y tenemos el límite mínimo de factibilidad para el precio dual de la máquina 1 (<em><strong>IFmax1</strong></em>).</p>
<p>Al ejecutar el código obtendremos lo siguiente en la consola de Windows:</p>
<figure id="attachment_26276" aria-describedby="caption-attachment-26276" style="width: 592px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/intervalo_de_factibilidad1_a.png" alt="intervalo_de_factibilidad1_a" width="592" height="327" class="size-full wp-image-26276" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/intervalo_de_factibilidad1_a.png 592w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/intervalo_de_factibilidad1_a-300x166.png 300w" sizes="(max-width: 592px) 100vw, 592px" /><figcaption id="caption-attachment-26276" class="wp-caption-text">Figura 7: Intervalos de factibilidad para la máquina 1 (Caso JOBCO)</figcaption></figure>
<p>&nbsp;</p>
<p>Podemos efectuar nosotros mismos los cálculos correspondientes:</p>
<p>El punto <em><strong>B</strong></em> tiene la siguientes coordenadas (<em><strong>x </strong></em>= 0, <em><strong>y </strong></em>= 2,67)</p>
<p>Evaluando estas coordenadas en la ecuación de la <strong><em>máquina 1</em></strong>, tenemos:</p>
<p style="text-align: center;">2(<strong>0</strong>)+ (<strong>2,67</strong>) = Límite mínimo de factibilidad (máquina 1)</p>
<p style="text-align: center;">2(<strong>0</strong>)+ (<strong>2,67</strong>) = 2,67 horas</p>
<p>El punto <em><strong>F</strong></em> tiene la siguientes coordenadas (<em><strong>x </strong></em>= 8, <em><strong>y </strong></em>= 0)</p>
<p>Evaluando estas coordenadas en la ecuación de la <strong><em>máquina 1</em></strong>, tenemos:</p>
<p style="text-align: center;">2(<strong>8</strong>)+ (<strong>0</strong>) = Límite máximo de factibilidad (máquina 1)</p>
<p style="text-align: center;">2(<strong>8</strong>)+ (<strong>0</strong>) = 16 horas</p>
<p>Ya efectuamos los procedimientos de manera manual y mediante <em>Python</em>. La conclusión es que el precio dual de $14/h permanece válido en el intervalo:</p>
<p style="text-align: center;"><strong>2,67</strong> h &lt;= Capacidad de la <em><strong>máquina 1</strong></em> &lt;= <strong>16</strong> h</p>
<p>Los cambios que se encuentren fuera de esos <em><strong>intervalos de factibilidad</strong> </em>producen un precio dual diferente, y por lo tanto, precisan nuevos cálculos.</p>
<p>En nuestro código, incluiremos las líneas que permitan llegar al precio dual y a los intervalos de factibilidad de la <em><strong>máquina 2</strong>. </em>Al ejecutarlo, obtendremos lo siguiente:</p>
<figure id="attachment_26273" aria-describedby="caption-attachment-26273" style="width: 640px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad2.png" alt="jobco_analisis_sensibilidad2" width="640" height="480" class="wp-image-26273 size-full" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad2.png 640w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/jobco_analisis_sensibilidad2-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /><figcaption id="caption-attachment-26273" class="wp-caption-text">Figura 8: Representación gráfica de un cambio en la disponibilidad del recurso 2 (Nuevo vértice H)</figcaption></figure>
<p>&nbsp;</p>
<figure id="attachment_26277" aria-describedby="caption-attachment-26277" style="width: 548px" class="wp-caption aligncenter"><img decoding="async" src="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/intervalo_de_factibilidad2_b.png" alt="intervalo_de_factibilidad2_b" width="548" height="404" class="size-full wp-image-26277" srcset="https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/intervalo_de_factibilidad2_b.png 548w, https://ingenieriaindustrialonline.com/wp-content/uploads/2021/07/intervalo_de_factibilidad2_b-300x221.png 300w" sizes="(max-width: 548px) 100vw, 548px" /><figcaption id="caption-attachment-26277" class="wp-caption-text">Figura 9: Precio dual e intervalos de factibilidad para la máquina 2 (Caso JOBCO)</figcaption></figure>
<p>&nbsp;</p>
<p>La conclusión es que el precio dual de $2/h permanece válido en el intervalo:</p>
<p style="text-align: center;"><strong>4</strong> h &lt;= Capacidad de la <em><strong>máquina 2</strong></em> &lt;= <strong>24</strong> h</p>
<p>Los precios duales y los intervalos de factibilidad amplían la base que ofrece la solución óptima para la toma de decisiones. <em>Taha </em>propone una serie de preguntas alrededor de este caso (ejercicio 3.6-1), que pueden ser abordadas gracias al análisis de sensibilidad.</p>
<p><em><strong>Pregunta 1</strong></em>: Si JOBCO puede incrementar la capacidad de ambas máquinas, ¿Cuál máquina tendrá la prioridad?</p>
<p><strong>Respuesta: </strong>De acuerdo a los precios duales para las máquinas 1 y 2, cada hora adicional de la máquina 1 incrementa el ingreso total en $14; mientras tanto, cada hora adicional de la máquina 2 incrementa el ingreso total en $2. Por lo tanto, la máquina 1 debe tener la prioridad.</p>
<p><em><strong>Pregunta 2</strong></em>: Se sugiere incrementar las capacidades de las máquinas 1 y 2 al costo adicional de $10/h para cada máquina. ¿Es esto aconsejable?</p>
<p><strong>Respuesta: </strong>De acuerdo a los precios duales para las máquinas 1 y 2.  Los ingresos netos adicionales por hora serían de la siguiente manera:</p>
<p><em>Máquina 1:</em></p>
<p>14$/h (Precio dual máquina 1) &#8211; 10$/h (Costo para aumentar capacidad) = 4 $/h (Ingreso neto)</p>
<p><em>Máquina 2:</em></p>
<p>2$/h (Precio dual máquina 2) &#8211; 10$/h (Costo para aumentar capacidad) = &#8211; 8 $/h (Ingreso neto)</p>
<p>Por consiguiente, solo la <em>máquina 1</em> debe considerarse para el incremento de capacidad.</p>
<p><em><strong>Pregunta 3</strong></em>: Si la capacidad de la máquina 1 se incrementa de 8 a 13 horas, ¿Cómo impactará este incremento al ingreso óptimo?</p>
<p><strong>Respuesta</strong>: Lo primero que debe evaluarse es que la nueva capacidad de la máquina 1 se encuentre dentro del intervalo de factibilidad (2,67 h &#8211; 16 h). Ya que sí se encuentra en dicho intervalo (13 h), el siguiente paso consiste en calcular el <em>cambio en la disponibilidad</em>:</p>
<p style="text-align: center;"><em>Cambio en la capacidad</em> = 13 (Nueva capacidad) &#8211; 8 (Capacidad inicial)</p>
<p style="text-align: center;"><em>Cambio en la capacidad</em> = 5 horas</p>
<p>Por ende, multiplicamos dicho cambio por la tasa de cambio del ingreso (precio dual):</p>
<p style="text-align: center;"><em>Cambio en el ingreso</em> = Precio dual  * Cambio en la capacidad</p>
<p style="text-align: center;"><em>Cambio en el ingreso</em> = 14 $/h * (5 horas)</p>
<p style="text-align: center;"><em>Cambio en el ingreso</em> = 70 $</p>
<p style="text-align: center;"><em>Nuevo ingreso</em> = Ingreso base (Solución óptima) + Cambio en el ingreso</p>
<p style="text-align: center;"><em>Nuevo ingreso</em> = $ 128 + $ 70</p>
<p style="text-align: center;"><em>Nuevo ingreso</em> = $ 198</p>
<p>En el caso de que la nueva capacidad de la máquina se encuentre por fuera del intervalo de factibilidad para dicho recurso, no se dispondría de información suficiente para llegar a una conclusión válida.</p>
<hr />
<p>A continuación, dejamos a disposición el código completo del análisis de sensibilidad gráfica desarrollado en <em>Python</em>:</p>
<div class="hcb_wrap">
<pre class="prism line-numbers lang-python" data-lang="Python"><code># Caso 1: Investigación de Operaciones (9na edición), de Hamdy A. Taha 
# (University of Arkansas, Fayetteville), (Ejemplo 3.6-1)
# Autor del código en Python: Bryan Salazar López, Ing. M.Sc. (2021)

#Librerías necesarias
import matplotlib.pyplot as plt
import numpy as np
from shapely.geometry import LineString

#Ecuaciones e intervalos (Para tabular)
x = np.arange(-20, 20, 5)
y = np.arange(-20, 20, 5)
y1 = 8 - (2 * x)
y2 = (8 - x) / 3
y3 = 0 * x
x1 = 0 * y
z = (-30 * x) / 20
y1v = (8 + 1) - (2 * x) #Variación en la máquina 1 (8 + 1)
y2v = ((8 + 1) - x) / 3 #Variación en la máquina 2 (8 + 1)

#Identificadores para las líneas
primera_linea = LineString(np.column_stack((x, y1)))
segunda_linea = LineString(np.column_stack((x, y2)))
tercera_linea = LineString(np.column_stack((x, y3)))
cuarta_linea = LineString(np.column_stack((x1, y)))
quinta_linea = LineString(np.column_stack((x, z)))
sexta_linea = LineString(np.column_stack((x, y1v))) #Nueva línea Máquina 1 (M1)
septima_linea = LineString(np.column_stack((x, y2v))) #Nueva línea Máquina 2 (M2)

#Graficando las líneas
plt.plot(x, y1, '-', linewidth=2, color='b')
plt.plot(x, y2, '-', linewidth=2, color='g')
plt.plot(x, y3, '-', linewidth=2, color='r')
plt.plot(x1, y, '-', linewidth=2, color='y')
#plt.plot(x, z, ':', linewidth=1, color='k')
plt.plot(x, y1v, ':', linewidth=1, color='b') #Nueva línea M1
plt.plot(x, y2v, ':', linewidth=1, color='g') #Nueva línea M2


#Generando las intersecciones (vértices)
primera_interseccion = cuarta_linea.intersection(segunda_linea)
segunda_interseccion = primera_linea.intersection(segunda_linea)
tercera_interseccion = primera_linea.intersection(tercera_linea)
cuarta_interseccion = tercera_linea.intersection(cuarta_linea)
quinta_interseccion = cuarta_linea.intersection(primera_linea)
sexta_interseccion = segunda_linea.intersection(tercera_linea)
septima_interseccion = sexta_linea.intersection(segunda_linea) #Nuevo vértice M1
octava_interseccion = septima_linea.intersection(primera_linea) #Nuevo vértice M2

#Graficando los vértices
plt.plot(*primera_interseccion.xy, 'o', color='k')
plt.plot(*segunda_interseccion.xy, 'o', color='k')
plt.plot(*tercera_interseccion.xy, 'o', color='k')
plt.plot(*cuarta_interseccion.xy, 'o', color='k')
plt.plot(*quinta_interseccion.xy, 'o', color='silver')
plt.plot(*sexta_interseccion.xy, 'o', color='silver')
plt.plot(*septima_interseccion.xy, 'o', color='k') #Graficar nuevo vértice M1
plt.plot(*octava_interseccion.xy, 'o', color='k') #Graficar nuevo vértice M2

#Identificando los valores de las coordenadas x y y de cada vértice
xi1m, yi1m = primera_interseccion.xy
xi2m, yi2m = segunda_interseccion.xy
xi3m, yi3m = tercera_interseccion.xy
xi4m, yi4m = cuarta_interseccion.xy
xi5m, yi5m = quinta_interseccion.xy
xi6m, yi6m = sexta_interseccion.xy
xi7m, yi7m = septima_interseccion.xy #Coordenadas del nuevo vértice M1
xi8m, yi8m = octava_interseccion.xy #Coordenadas del nuevo vértice M2

#Cambiamos el formato de matriz a float
xi1 = np.float64(np.array(xi1m))
xi2 = np.float64(np.array(xi2m))
xi3 = np.float64(np.array(xi3m))
xi4 = np.float64(np.array(xi4m))
xi5 = np.float64(np.array(xi5m))
xi6 = np.float64(np.array(xi6m))
xi7 = np.float64(np.array(xi7m)) #Nueva coordenada en x (Máquina 1)
xi8 = np.float64(np.array(xi8m)) #Nueva coordenada en x (Máquina 2)
yi1 = np.float64(np.array(yi1m))
yi2 = np.float64(np.array(yi2m))
yi3 = np.float64(np.array(yi3m))
yi4 = np.float64(np.array(yi4m))
yi5 = np.float64(np.array(yi5m))
yi6 = np.float64(np.array(yi6m))
yi7 = np.float64(np.array(yi7m)) #Nueva coordenada en y (Máquina 1)
yi8 = np.float64(np.array(yi8m)) #Nueva coordenada en y (Máquina 2)

#literales de las intersecciones (nombres en el gráfico)
plt.annotate(' A', (xi4, yi4))
plt.annotate(' B', (xi1, yi1))
plt.annotate(' C', (xi2, yi2))
plt.annotate(' D', (xi3, yi3))
plt.annotate(' E', (xi5, yi5))
plt.annotate(' F', (xi6, yi6)) 
plt.annotate(' G', (xi7, yi7)) #Nombrar el nuevo vértice como G
plt.annotate(' H', (xi8, yi8)) #Nombrar el nuevo vértice como H

#Evaluando la función objetivo en cada vértice
FOi1 = (xi1 * 30) + (yi1 * 20)
FOi2 = (xi2 * 30) + (yi2 * 20)
FOi3 = (xi3 * 30) + (yi3 * 20)
FOi4 = (xi4 * 30) + (yi4 * 20)
FOi7 = (xi7 * 30) + (yi7 * 20) #Calcular la FO en el nuevo vértice G
FOi8 = (xi8 * 30) + (yi8 * 20) #Calcular la FO en el nuevo vértice H

#Calculando el mejor resultado (Maximizar)
ZMAX = max(FOi1, FOi2, FOi3, FOi4)

#Imprimiendo la solución óptima en la consola
print('\n SOLUCIÓN ÓPTIMA')
print('Solución óptima: {} '.format(ZMAX))
print('Función objetivo en punto G: {} '.format(FOi7))
print('Función objetivo en punto H: {} '.format(FOi8))

#Ordenando las coordenadas de los vértices (Las coordenadas x en m y las coordenadas y en n)
m = [xi1, xi2, xi3, xi4]
n = [yi1, yi2, yi3, yi4]

#Graficando el polígono solución a partir de las coordenadas de los vértices (importante el orden según las manecillas)
plt.fill(m, n, color='silver')

#Identificando el índice del vértice de la mejor solución
dict1 = {0:FOi1, 1:FOi2, 2:FOi3, 3:FOi4}
posicion = max(dict1, key=dict1.get)

#Obteniendo las coordenadas del vértice de la mejor solución de acuerdo al índice del paso anterior
XMAX = m[posicion]
YMAX = n[posicion]

#Imprimiendo las coordenadas del vértice de la mejor solución (variables de decisión)
print('\n VARIABLES DE DECISIÓN')
print('Cantidad de producto X1 a producir: {} unidades'.format(XMAX))
print('Cantidad de producto X2 a producir: {} unidades'.format(YMAX))

#Precio dual (Restricción 1)
Dual1 = (FOi7 - ZMAX) #Calculamos el precio dual para la restricción 1
Dual2 = (FOi8 - ZMAX) #Calculamos el precio dual para la restricción 1

#Imprimir los precios duales
print('\n ANÁLISIS DE SENSIBILIDAD')
print('Precio dual de la máquina 1: {} $/h'.format(Dual1)) #Imprimimos el precio dual 1
print('Precio dual de la máquina 2: {} $/h'.format(Dual2)) #Imprimimos el precio dual 2

#Intervalos de factibilidad (Máquina 1)
IFmin1 = (2 * xi1) + yi1
IFmax1 = (2 * xi6) + yi6

#Intervalos de factibilidad (Máquina 2)
IFmin2 = xi3 + (3 * yi3)
IFmax2 = xi5 + (3 * yi5)

#Imprimir los intervalos de factibilidad
print('\n INTERVALOS DE FACTIBILIDAD')
print('Límite mínimo de factibilidad (PD máquina 1): {} h'.format(IFmin1))
print('Límite máximo de factibilidad (PD máquina 1): {} h'.format(IFmax1))
print('Límite mínimo de factibilidad (PD máquina 2): {} h'.format(IFmin2))
print('Límite máximo de factibilidad (PD máquina 2): {} h'.format(IFmax2))


#Configuraciones adicionales del gráfico
plt.grid()
plt.xlabel('X1')
plt.ylabel('X2')
plt.title('JOBCO')

plt.show()
</code></pre>
</div>
<hr />
<p>En el próximo artículo desarrollaremos el segundo caso de análisis de sensibilidad: <em>cambios en la utilidad unitaria o el costo unitario.</em></p>
<p>La entrada <a href="https://ingenieriaindustrialonline.com/investigacion-de-operaciones/analisis-de-sensibilidad-grafica-mediante-el-uso-de-python-caso-1/">Análisis de sensibilidad gráfica mediante el uso de Python (Caso 1)</a> se publicó primero en <a href="https://ingenieriaindustrialonline.com">Ingenieria Industrial Online</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://ingenieriaindustrialonline.com/investigacion-de-operaciones/analisis-de-sensibilidad-grafica-mediante-el-uso-de-python-caso-1/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
