<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0"><channel><title> Software Engineering | Yurij Mikhalevich </title><description> photo-rich tech blog </description><link>https://mikhalevi.ch/tag/software-engineering/</link><image><url>https://mikhalevi.ch/media/icon_hu_15f92ecee4d13fc8.png</url><title>Software Engineering | Yurij Mikhalevich</title><link>https://mikhalevi.ch/tag/software-engineering/</link></image><generator>Hugo</generator><lastBuildDate>Sun, 07 Jun 2026 18:29:00 +1000</lastBuildDate><atom:link href="https://mikhalevi.ch/tag/software-engineering/rss.xml" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title> rclip 3: better and 6x faster semantic image search </title><description><![CDATA[ <p>rclip 3 is out. It improves search quality and makes search up to 6x faster.</p>
<p>rclip is a command-line tool for searching local photos by text or by
example image. If you&rsquo;re new to it, start with the intro post <a href="https://mikhalevi.ch/rclip-an-ai-powered-command-line-photo-search-tool/">here</a>.</p> ]]></description><link>https://mikhalevi.ch/rclip-3-better-and-6x-faster-semantic-image-search/</link><guid>https://mikhalevi.ch/rclip-3-better-and-6x-faster-semantic-image-search/</guid><category> Software Engineering </category><dc:creator> Yurij Mikhalevich </dc:creator><pubDate>Sun, 07 Jun 2026 18:29:00 +1000</pubDate><media:content url="https://mikhalevi.ch/rclip-3-better-and-6x-faster-semantic-image-search/featured_hu_d79546df2ec5adc4.webp" medium="image"/><content:encoded><![CDATA[ <img src="https://mikhalevi.ch/rclip-3-better-and-6x-faster-semantic-image-search/featured_hu_d79546df2ec5adc4.webp" alt="rclip 3: better and 6x faster semantic image search"><p>rclip 3 is out. It improves search quality and makes search up to 6x faster.</p>
<p>rclip is a command-line tool for searching local photos by text or by
example image. If you&rsquo;re new to it, start with the intro post <a href="https://mikhalevi.ch/rclip-an-ai-powered-command-line-photo-search-tool/">here</a>.</p>
<p>The major changes that shipped with this version are:</p>
<ul>
<li>rclip now uses <a href="https://github.com/mlfoundations/open_clip/blob/55794d65a14dfc547a9ed3514145dd68ccc939e9/README.md" target="_blank" rel="noopener">OpenCLIP&rsquo;s top-performing ViT-B/32
model</a>,
which improves benchmark accuracy. On ImageNet-1k, rclip 2 reached 58.78%
top-1 accuracy and 84.21% top-5 accuracy, while rclip 3 reaches 68.73%
top-1 accuracy and 89.59% top-5 accuracy.</li>
<li>rclip now searches up to 6x faster (depends on your OS and hardware). For
example, text search on M1 Max went from 2.12 seconds to 0.57 seconds (3.72x speedup
and near-instant feel), while image-to-image search on the same machine went
from 3.84 seconds to 0.63 seconds (a 6.09x speedup!).</li>
</ul>
<p>













<figure class="md-figure" id="figure-rclip-3-text-search-is-now-372x-faster">
  <div class="md-figure__inner" ><img alt="Terminal running the same rclip text search on version 2.1.6 then 3.0.9, with the elapsed-time totals highlighted: 2.12 seconds before and 0.57 seconds after" srcset="
          https://mikhalevi.ch/rclip-3-better-and-6x-faster-semantic-image-search/images/rclip-3-text-to-image-search-speed-improvement_hu_c16b02d3c28f8a7b.webp 268w,
          https://mikhalevi.ch/rclip-3-better-and-6x-faster-semantic-image-search/images/rclip-3-text-to-image-search-speed-improvement_hu_a4865451026faf5b.webp 510w,
          https://mikhalevi.ch/rclip-3-better-and-6x-faster-semantic-image-search/images/rclip-3-text-to-image-search-speed-improvement_hu_ffa72490a974d87.webp 1019w"
          src="https://mikhalevi.ch/rclip-3-better-and-6x-faster-semantic-image-search/images/rclip-3-text-to-image-search-speed-improvement_hu_ffa72490a974d87.webp"
          width="510"
          height="760"
          loading="lazy"
          class="md-img"
          style="background: linear-gradient(37.000000deg, #f1e9c2b3, #f1e9c200 75%),linear-gradient(217.000000deg, #a09775b3, #a0977500 75%); background-size: cover; background-repeat: no-repeat;" data-zoomable /></div><figcaption class="md-figcaption">
      rclip 3 text search is now 3.72x faster
    </figcaption></figure>
</p>
<p>Real-world gains will vary by photo collection.</p>
<p>Before I talk about the implementation details, I invite you to install the
latest version. Follow <a href="https://github.com/yurijmikhalevich/rclip#installation" target="_blank" rel="noopener">the installation instructions from the
README</a> to do so. Since
rclip now uses a new model, it&rsquo;ll have to reindex your images.</p>
<p>Under the hood, these gains came from two independent changes.</p>
<p>The model change was long overdue. The original OpenAI CLIP model was no longer
the best default choice, and the benchmark gains above made the switch worth it.</p>
<p>The runtime change was enabled by replacing the large and slow-to-load PyTorch
with onnxruntime, which loads very fast, packages better, and provides very
similar performance to PyTorch when run on a CPU. Most of the search speed
gains came from getting rid of long PyTorch loading times.</p>
<p>The only caveat with the switch to onnxruntime was that Apple Silicon support
in PyTorch is better during batch processing, so indexing speeds on Apple
Silicon regressed. To fix this, rclip 3 for macOS ships with one more execution
backend: coremltools, which is provided by Apple and optimized for macOS.
coremltools has the same slow startup downside as PyTorch, so rclip 3 uses it
on macOS only for indexing, while defaulting to onnxruntime for search. Thanks
to the coremltools optimizations, indexing speeds on macOS went up. In my
tests, I got 180 images per second, which is 12.5% faster than the 160 images
per second I had been getting with PyTorch.</p>
<p>These improvements also come in a smaller package. For example, the rclip
Windows MSI installer went from 172 MB in 2.1.6 to 59 MB in 3.0.9. rclip snap
went from 224 MB to 57 MB. I realize that these days, a lot of people don&rsquo;t
care about download sizes, but it&rsquo;s hard not to appreciate the software that
needs less: less compute, less space. Also, as a person obsessed with great UX
and responsive apps, I am really happy that downloading and installing rclip
got faster, too.</p>
<p>If you&rsquo;ve been looking for a great <a href="https://mikhalevi.ch/rclip-an-ai-powered-command-line-photo-search-tool/">text-to-image</a> and
<a href="https://mikhalevi.ch/unveiling-rclips-new-power-image-to-image-search/">image-to-image semantic photo search</a> that
lets you <a href="https://mikhalevi.ch/rclip-update-mixing-text-and-image-queries-for-the-ultimate-photo-search/">mix images and text queries</a> and <a href="https://mikhalevi.ch/next-level-terminal-image-search-rclip-gets-previews/">preview image search results in the terminal</a>,
it just got faster. <a href="https://github.com/yurijmikhalevich/rclip#installation" target="_blank" rel="noopener">Install rclip</a>, let it reindex with the new model, and <a href="https://github.com/yurijmikhalevich/rclip/discussions/new/choose" target="_blank" rel="noopener">let me know what you think</a>.</p>
<p>Stay curious!</p>
 ]]></content:encoded></item><item><title> rclip 2: raw image search and performance improvements </title><description><![CDATA[ <p>This week, I released version 2 of rclip. The main breaking change in the major
update is that rclip dropped Python 3.9 support, which could affect you only if
you are installing rclip using <code>pip</code> in a Python 3.9 environment. Even though no
features are associated with the v2 release, I am thrilled about it because it
provides a good opportunity to reflect on the changes made to rclip since the
<a href="https://mikhalevi.ch/rclip-an-ai-powered-command-line-photo-search-tool/">release of version 1</a>.</p> ]]></description><link>https://mikhalevi.ch/rclip-2-raw-image-search-and-performance-improvements/</link><guid>https://mikhalevi.ch/rclip-2-raw-image-search-and-performance-improvements/</guid><category> Software Engineering </category><dc:creator> Yurij Mikhalevich </dc:creator><pubDate>Sun, 02 Mar 2025 12:03:00 +0400</pubDate><media:content url="https://mikhalevi.ch/rclip-2-raw-image-search-and-performance-improvements/featured_hu_9dba097de407ad2b.webp" medium="image"/><content:encoded><![CDATA[ <img src="https://mikhalevi.ch/rclip-2-raw-image-search-and-performance-improvements/featured_hu_9dba097de407ad2b.webp" alt="rclip 2: raw image search and performance improvements"><p>This week, I released version 2 of rclip. The main breaking change in the major
update is that rclip dropped Python 3.9 support, which could affect you only if
you are installing rclip using <code>pip</code> in a Python 3.9 environment. Even though no
features are associated with the v2 release, I am thrilled about it because it
provides a good opportunity to reflect on the changes made to rclip since the
<a href="https://mikhalevi.ch/rclip-an-ai-powered-command-line-photo-search-tool/">release of version 1</a>.</p>
<p>For those unfamiliar with rclip, it is a command line tool that allows you to
search your image or photo collection right in your terminal using a natural
language query or a reference image. To try rclip, follow the installation
instructions on the <a href="https://github.com/yurijmikhalevich/rclip?tab=readme-ov-file#installation" target="_blank" rel="noopener">GitHub
page</a>.</p>
<p>I have already blogged about most of the major features introduced to rclip v1:</p>
<ul>
<li><a href="https://mikhalevi.ch/unveiling-rclips-new-power-image-to-image-search/">image-to-image search</a></li>
<li><a href="https://mikhalevi.ch/rclip-update-mixing-text-and-image-queries-for-the-ultimate-photo-search/">composite queries, including mixed text and image queries</a></li>
<li><a href="https://mikhalevi.ch/next-level-terminal-image-search-rclip-gets-previews/">terminal image previews</a></li>
</ul>
<p>What I didn&rsquo;t blog about is the countless performance improvements, from a few
noticeable indexation speedups to a 50% search time reduction when performing
text-only queries.</p>
<p>I am also excited to announce for the first time that rclip now has experimental
support for raw image search. GitHub user
<a href="https://github.com/abidkhan484" target="_blank" rel="noopener">@abidkhan484</a> added this feature to rclip
during the last Hacktoberfest (speaking of which, rclip will participate in
Hacktoberfest 2025 as well &ndash; join us!). The feature was first released in rclip
v1.11.0, allowing you to search through raw camera images. Currently, only the
ARW and CR2 images are supported. Let me know if you would love rclip to support
any other raw formats!</p>
<p>To use raw search, run rclip with the <code>--experimental-raw-support</code> flag, like
this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">rclip --experimental-raw-support <span class="s2">&#34;orange cat&#34;</span>
</span></span></code></pre></div><p>













<figure class="md-figure" >
  <div class="md-figure__inner" ><img alt="raw image search example" srcset="
          https://mikhalevi.ch/rclip-2-raw-image-search-and-performance-improvements/images/raw-image-search-example_hu_f75aa54e0bdc1849.webp 400w,
          https://mikhalevi.ch/rclip-2-raw-image-search-and-performance-improvements/images/raw-image-search-example_hu_3d14ef4104511c09.webp 760w,
          https://mikhalevi.ch/rclip-2-raw-image-search-and-performance-improvements/images/raw-image-search-example_hu_87050f5d395154c.webp 1520w"
          src="https://mikhalevi.ch/rclip-2-raw-image-search-and-performance-improvements/images/raw-image-search-example_hu_87050f5d395154c.webp"
          width="760"
          height="475"
          loading="lazy"
          class="md-img"
          style="background: linear-gradient(37.000000deg, #f5f0e1b3, #f5f0e100 75%),linear-gradient(109.000000deg, #494440b3, #49444000 75%),linear-gradient(181.000000deg, #a79172b3, #a7917200 75%),linear-gradient(253.000000deg, #687a83b3, #687a8300 75%),linear-gradient(325.000000deg, #887261b3, #88726100 75%); background-size: cover; background-repeat: no-repeat;" data-zoomable /></div></figure>
</p>
<p>With this flag passed, rclip will search through regular and raw images and
surface best matches. Terminal previews are also supported for raw photos.  It
is important to note that rclip will still prefer non-raw versions of the image
when they are available. Suppose it finds two versions of the same image in the
same directory &ndash; for example, IMG_1234.JPG and IMG_1234.ARW &ndash; it will ignore
the raw version.</p>
<p><a href="https://github.com/yurijmikhalevich/rclip?tab=readme-ov-file#installation" target="_blank" rel="noopener">Install
rclip</a>,
try this feature, and <a href="https://github.com/yurijmikhalevich/rclip/discussions/new/choose" target="_blank" rel="noopener">let me know if you have any
feedback</a>.
Stay curious, and enjoy your photo searches with rclip!</p>
 ]]></content:encoded></item><item><title> Next-level terminal image search: rclip gets previews </title><description><![CDATA[ <p>Hi everyone!</p>
<p>I&rsquo;m thrilled to share a long-overdue update about an exciting rclip feature. Now, you can preview search results directly in the terminal!</p>
<p>If you&rsquo;re new to rclip, it&rsquo;s an AI-powered command-line photo search tool – think &ldquo;rgrep&rdquo; for images. Dive into <a href="https://mikhalevi.ch/rclip-an-ai-powered-command-line-photo-search-tool/">my</a> <a href="https://mikhalevi.ch/unveiling-rclips-new-power-image-to-image-search/">previous</a> <a href="https://mikhalevi.ch/rclip-update-mixing-text-and-image-queries-for-the-ultimate-photo-search/">posts</a> and give it a spin to learn more.</p> ]]></description><link>https://mikhalevi.ch/next-level-terminal-image-search-rclip-gets-previews/</link><guid>https://mikhalevi.ch/next-level-terminal-image-search-rclip-gets-previews/</guid><category> Software Engineering </category><dc:creator> Yurij Mikhalevich </dc:creator><pubDate>Sun, 21 Jan 2024 16:24:10 +0000</pubDate><media:content url="https://mikhalevi.ch/next-level-terminal-image-search-rclip-gets-previews/featured_hu_56bd9edda3591528.webp" medium="image"/><content:encoded><![CDATA[ <img src="https://mikhalevi.ch/next-level-terminal-image-search-rclip-gets-previews/featured_hu_56bd9edda3591528.webp" alt="Next-level terminal image search: rclip gets previews"><p>Hi everyone!</p>
<p>I&rsquo;m thrilled to share a long-overdue update about an exciting rclip feature. Now, you can preview search results directly in the terminal!</p>
<p>If you&rsquo;re new to rclip, it&rsquo;s an AI-powered command-line photo search tool – think &ldquo;rgrep&rdquo; for images. Dive into <a href="https://mikhalevi.ch/rclip-an-ai-powered-command-line-photo-search-tool/">my</a> <a href="https://mikhalevi.ch/unveiling-rclips-new-power-image-to-image-search/">previous</a> <a href="https://mikhalevi.ch/rclip-update-mixing-text-and-image-queries-for-the-ultimate-photo-search/">posts</a> and give it a spin to learn more.</p>
<p>Now, let&rsquo;s talk about the new preview feature. To witness the magic, simply add the <code>--preview</code> (or <code>-p</code>) argument to your rclip command, like this: <code>rclip -p kitty</code>. Check out the video below to see it in action.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
			<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/Ay_XfbUFU08?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
		</div>

<p>Some might argue that the ability to preview images was in rclip before because you can pipe filenames output by rclip to any other command. You might remember reading a suggestion about piping filenames to an image viewer in <a href="https://github.com/yurijmikhalevich/rclip/blob/bfd0fb7e5f702b7f38d0cc43b814cdeffff2a1d2/README.md#how-do-i-preview-the-results" target="_blank" rel="noopener">rclip&rsquo;s README</a>. It worked, but the command was a bit of a monstrosity. You had to remember the <code>-f</code> argument, deal with <code>xargs</code> intricacies, and know how to call the viewer you want to use.</p>
<p>The other issue with that suggestion is that it doesn&rsquo;t work over SSH. This is especially important to me because I do a lot of searches on a remote machine. And, even if you make it work over SSH somehow, it will be slow because it has to transmit the whole image over the network to display a preview unless you go further and compress it yourself.</p>
<p>Now, these problems are no more. I&rsquo;ve addressed these issues with the new preview feature! No more cumbersome commands. Now, it&rsquo;s as simple as typing <code>rclip -p kitty</code>, and voila – compressed image previews right in your terminal. It also works seamlessly over SSH. No more sluggish previews; just a smooth, optimized experience, even on remote machines.</p>
<p>The feature is compatible with <a href="https://iterm2.com/" target="_blank" rel="noopener">iTerm2</a>, <a href="https://konsole.kde.org/" target="_blank" rel="noopener">Konsole</a> (version 22.04 and higher), <a href="https://wezfurlong.org/wezterm/" target="_blank" rel="noopener">wezterm</a>, <a href="https://mintty.github.io/" target="_blank" rel="noopener">Mintty</a>, and <a href="https://mlterm.sourceforge.net/" target="_blank" rel="noopener">mlterm</a>.</p>
<p>Get the feature by following <a href="https://github.com/yurijmikhalevich/rclip?tab=readme-ov-file#installation" target="_blank" rel="noopener">the instructions in the repository&rsquo;s README</a> to download the latest version of rclip. Try it out and share your thoughts. Stay curious, and enjoy your photo searches with rclip!</p>
 ]]></content:encoded></item><item><title> rclip update: mixing text and image queries for the ultimate photo search </title><description><![CDATA[ <p>Hi! Today, I am introducing a fantastic new feature of rclip (an AI-powered command-line photo search tool I built): the ability to add and subtract different queries. It&rsquo;s fun to play around with different concepts in this way. This feature is also handy when you want to find similar images. If it&rsquo;s the first time you read about rclip, check out <a href="https://mikhalevi.ch/rclip-an-ai-powered-command-line-photo-search-tool/">this</a> and <a href="https://mikhalevi.ch/unveiling-rclips-new-power-image-to-image-search/">this</a> blog posts.</p> ]]></description><link>https://mikhalevi.ch/rclip-update-mixing-text-and-image-queries-for-the-ultimate-photo-search/</link><guid>https://mikhalevi.ch/rclip-update-mixing-text-and-image-queries-for-the-ultimate-photo-search/</guid><category> Software Engineering </category><dc:creator> Yurij Mikhalevich </dc:creator><pubDate>Mon, 31 Jul 2023 12:05:26 +0000</pubDate><media:content url="https://mikhalevi.ch/rclip-update-mixing-text-and-image-queries-for-the-ultimate-photo-search/featured_hu_d48f4d81493059b5.webp" medium="image"/><content:encoded><![CDATA[ <img src="https://mikhalevi.ch/rclip-update-mixing-text-and-image-queries-for-the-ultimate-photo-search/featured_hu_d48f4d81493059b5.webp" alt="rclip update: mixing text and image queries for the ultimate photo search"><p>Hi! Today, I am introducing a fantastic new feature of rclip (an AI-powered command-line photo search tool I built): the ability to add and subtract different queries. It&rsquo;s fun to play around with different concepts in this way. This feature is also handy when you want to find similar images. If it&rsquo;s the first time you read about rclip, check out <a href="https://mikhalevi.ch/rclip-an-ai-powered-command-line-photo-search-tool/">this</a> and <a href="https://mikhalevi.ch/unveiling-rclips-new-power-image-to-image-search/">this</a> blog posts.</p>
<p>The feature works like this. If you want to add two queries together, add a &ldquo;plus&rdquo; sign between them like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">cd</span> photos <span class="o">&amp;&amp;</span> rclip horse + stripes
</span></span></code></pre></div><p>If you want to subtract one query from the other, use the &ldquo;minus&rdquo; sign:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">cd</span> photos <span class="o">&amp;&amp;</span> rclip apple - fruit
</span></span></code></pre></div><p>It works with image queries too:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">cd</span> photos <span class="o">&amp;&amp;</span> rclip <span class="s2">&#34;./new york city.jpg&#34;</span> + night
</span></span></code></pre></div><p>When combining image and text queries, you may notice that sometimes your text queries are seemingly ignored by rclip. This can happen because CLIP (an AI model that rclip uses) weights features extracted from images more than features extracted from text. Luckily, rclip provides a way to fix this. You can control the influence the query has on the result by prefixing it with a multiplier. It can be any positive number, and you can multiply both image and text queries. Here is an example which doubles the influence of the &ldquo;golden retriever&rdquo; query:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">cd</span> photos <span class="o">&amp;&amp;</span> rclip <span class="s2">&#34;2:golden retriever&#34;</span> + <span class="s2">&#34;./swimming pool.jpg&#34;</span>
</span></span></code></pre></div><p>And last but not least, when combining queries like this, you are not limited to two queries. Combine as many as you want. For example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">cd</span> photos <span class="o">&amp;&amp;</span> rclip <span class="s2">&#34;./racing car.jpg&#34;</span> - <span class="s2">&#34;2:sports car&#34;</span> + <span class="s2">&#34;2:snow&#34;</span>
</span></span></code></pre></div><p>If you are interested in seeing how all these queries perform when executed on the 1.28 million images ImageNet-1k dataset, check out the video demo on YouTube:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
			<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/MsTgYdOpgcQ?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
		</div>

<p>This is another rclip feature initially introduced by a GitHub user <a href="https://github.com/ramayer" target="_blank" rel="noopener">ramayer</a>. Huge thanks to ramayer for this incredible contribution! Speaking of contributors, I always appreciate the enthusiasm and fresh perspectives that new contributors bring! If you&rsquo;re considering getting involved in open-source projects and making a positive impact, <a href="https://github.com/yurijmikhalevich/rclip/issues" target="_blank" rel="noopener">a number of issues on the rclip GitHub page</a> could benefit from your attention and expertise. Why not give them a look?</p>
<p>That&rsquo;s all you need to know about the new feature. You can download the latest version of rclip by following <a href="https://github.com/yurijmikhalevich/rclip#installation" target="_blank" rel="noopener">the instructions in the repository&rsquo;s README</a>. Try it out and share your thoughts. Stay curious, and enjoy your photo searches with rclip!</p>
 ]]></content:encoded></item><item><title> Unveiling rclip's new power: image-to-image search </title><description><![CDATA[ <p>If you have been following my work, you are probably already familiar with <a href="https://github.com/yurijmikhalevich/rclip/" target="_blank" rel="noopener">rclip</a>, a command-line image search tool <a href="https://mikhalevi.ch/rclip-an-ai-powered-command-line-photo-search-tool/">I built to solve a specific problem: navigating my personal collection of photos</a>.</p> ]]></description><link>https://mikhalevi.ch/unveiling-rclips-new-power-image-to-image-search/</link><guid>https://mikhalevi.ch/unveiling-rclips-new-power-image-to-image-search/</guid><category> Software Engineering </category><dc:creator> Yurij Mikhalevich </dc:creator><pubDate>Sun, 16 Jul 2023 19:58:11 +0000</pubDate><media:content url="https://mikhalevi.ch/unveiling-rclips-new-power-image-to-image-search/featured_hu_822349821fa7a2bd.webp" medium="image"/><content:encoded><![CDATA[ <img src="https://mikhalevi.ch/unveiling-rclips-new-power-image-to-image-search/featured_hu_822349821fa7a2bd.webp" alt="Unveiling rclip&#39;s new power: image-to-image search"><p>If you have been following my work, you are probably already familiar with <a href="https://github.com/yurijmikhalevich/rclip/" target="_blank" rel="noopener">rclip</a>, a command-line image search tool <a href="https://mikhalevi.ch/rclip-an-ai-powered-command-line-photo-search-tool/">I built to solve a specific problem: navigating my personal collection of photos</a>.</p>
<p>Today, I am excited to introduce a new feature to rclip. This update expands the search capabilities of rclip beyond text queries, allowing you to search for images similar to a given image. You only need to provide an image path or URL, and rclip will find visually similar images. For example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">cd</span> photos <span class="o">&amp;&amp;</span> rclip ./cat.jpg
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># or use URL</span>
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> photos <span class="o">&amp;&amp;</span> rclip https://raw.githubusercontent.com/yurijmikhalevich/rclip/main/tests/e2e/images/cat.jpg
</span></span></code></pre></div><p>This feature was added a while back, but I have only wrapped it up properly recently. It is not just a cool feature; it is special because it was contributed by the first-ever contributor to rclip – a GitHub user <a href="https://github.com/ramayer" target="_blank" rel="noopener">ramayer</a>. ramayer reached out with this incredible proposal, worked on the implementation, and after a bit of back-and-forth feedback, I was delighted to merge the PR into the project. A huge shout-out to ramayer for this incredible contribution!</p>
<p>Check out the video below for a demo:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
			<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/1YQZKeCBxWM?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
		</div>

<p>Open source projects like rclip thrive on the energy and innovation of contributors. If you want to dive into open source and make a difference, <a href="https://github.com/yurijmikhalevich/rclip/issues" target="_blank" rel="noopener">several issues on the rclip GitHub page</a> could use your attention.</p>
<p>That&rsquo;s it for now! <a href="https://github.com/yurijmikhalevich/rclip/#installation" target="_blank" rel="noopener">Test drive this new feature</a> and share your feedback.</p>
<p>Until next time, stay curious!</p>
 ]]></content:encoded></item><item><title> Minerva, a GPT-powered Discord bot built to help students learn software development </title><description><![CDATA[ <p>Meet Minerva, a GPT-powered Discord bot I built to help students in the study group I mentor learn software development and machine learning.</p>
<p>Since 2021, I have been leading a pro-bono study group where I help students from my alma mater develop their skills and learn how to build and ship products. We have a Discord server with channels dedicated to different areas of study and student projects, and we meet face-to-face every week over a video chat. Anything related to any aspect of software development can be discussed there, though we mainly focus on machine learning and web development. In the group, I encourage people to study a lot, make bold mistakes, ask many questions and help each other. But I have noticed that sometimes, our club members hold back from asking &ldquo;simple&rdquo; questions, fearing they might needlessly disturb others. Despite understanding this feeling well, I prefer to avoid this frame of mind because even the simplest of questions can provoke an engaging discussion and deepen the knowledge and understanding of everyone involved. I wanted to empower everyone to seek answers without hesitation, so I built Minerva — an AI assistant based on OpenAI&rsquo;s &ldquo;gpt-3.5-turbo&rdquo; model (the same one used in ChatGPT) — to allow members to ask any questions without worrying about disturbing anyone! The bot lives in our Discord server now. To chat with the bot, the members can send a message, as usual, and mention &ldquo;@Minerva.&rdquo; Check out this interaction to get a feeling of how it works:</p> ]]></description><link>https://mikhalevi.ch/minerva-a-gpt-powered-discord-bot-built-to-help-students-learn-software-development/</link><guid>https://mikhalevi.ch/minerva-a-gpt-powered-discord-bot-built-to-help-students-learn-software-development/</guid><category> Software Engineering </category><dc:creator> Yurij Mikhalevich </dc:creator><pubDate>Sun, 07 May 2023 15:42:38 +0000</pubDate><media:content url="https://mikhalevi.ch/minerva-a-gpt-powered-discord-bot-built-to-help-students-learn-software-development/featured_hu_a5dc9a9d92d420fb.webp" medium="image"/><content:encoded><![CDATA[ <img src="https://mikhalevi.ch/minerva-a-gpt-powered-discord-bot-built-to-help-students-learn-software-development/featured_hu_a5dc9a9d92d420fb.webp" alt="Minerva, a GPT-powered Discord bot built to help students learn software development"><p>Meet Minerva, a GPT-powered Discord bot I built to help students in the study group I mentor learn software development and machine learning.</p>
<p>Since 2021, I have been leading a pro-bono study group where I help students from my alma mater develop their skills and learn how to build and ship products. We have a Discord server with channels dedicated to different areas of study and student projects, and we meet face-to-face every week over a video chat. Anything related to any aspect of software development can be discussed there, though we mainly focus on machine learning and web development. In the group, I encourage people to study a lot, make bold mistakes, ask many questions and help each other. But I have noticed that sometimes, our club members hold back from asking &ldquo;simple&rdquo; questions, fearing they might needlessly disturb others. Despite understanding this feeling well, I prefer to avoid this frame of mind because even the simplest of questions can provoke an engaging discussion and deepen the knowledge and understanding of everyone involved. I wanted to empower everyone to seek answers without hesitation, so I built Minerva — an AI assistant based on OpenAI&rsquo;s &ldquo;gpt-3.5-turbo&rdquo; model (the same one used in ChatGPT) — to allow members to ask any questions without worrying about disturbing anyone! The bot lives in our Discord server now. To chat with the bot, the members can send a message, as usual, and mention &ldquo;@Minerva.&rdquo; Check out this interaction to get a feeling of how it works:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
			<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/H9WEqr7ZgYk?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
		</div>

<p>The bot maintains an independent discussion in each server channel, differentiates between messages sent by different club members, knows how to mention Discord users, and formats the code in the responses using markdown! The perfect mentor, Minerva, prefers to nudge the student in the right direction instead of solving a problem for them.</p>
<p>The other purpose of building the bot was to answer a club member&rsquo;s request to see how the integration with OpenAI&rsquo;s chat completion model work. I have made this bot for the club members with the club members. Most of the development was done live during our weekly face-to-face meetings. The bot&rsquo;s source code is a valuable reference for anyone wanting to build their own Discord AI chatbot and is available <a href="https://github.com/Move-Fast-and-Break-Things-Study-Group/minerva" target="_blank" rel="noopener">on GitHub</a>. If you want to let Minerva into your Discord server, follow the instructions in the <a href="https://github.com/Move-Fast-and-Break-Things-Study-Group/minerva/blob/main/README.md#how-can-i-add-minerva-to-my-discord-guild-server" target="_blank" rel="noopener">repository&rsquo;s README</a>.</p>
<p>I created an avatar for Minerva using OpenAI&rsquo;s DALL·E and Midjourney. DALL·E was used to iterate over ideas. Midjourney was then used with a hybrid (image and text) prompt to perfect the concept. And for the Minerva banner, I used Canva&rsquo;s fantastic Magic Design tool.</p>
 ]]></content:encoded></item><item><title> rclip: an AI-powered command-line photo search tool </title><description><![CDATA[ <p>I have 72,769 photos stored on my NAS and organized into subfolders named according to the pattern &ldquo;YEAR.MM.DD - Event description.&rdquo; This organizational approach is software-agnostic and creates a structure that is easy to navigate. I have followed it for more than a dozen years. However, there are situations when I want to find a particular photo. Looking for it even within a specific subfolder can take a lot of time, especially if that subfolder is packed with many pictures. Another challenging scenario is finding a picture that will fit a particular theme without having a specific one in mind. In both of these scenarios, having content-based search available is helpful. I looked around and didn&rsquo;t find a solution that satisfied me. What to do when you find yourself in a situation like this? Build your own solution!</p> ]]></description><link>https://mikhalevi.ch/rclip-an-ai-powered-command-line-photo-search-tool/</link><guid>https://mikhalevi.ch/rclip-an-ai-powered-command-line-photo-search-tool/</guid><category> Software Engineering </category><dc:creator> Yurij Mikhalevich </dc:creator><pubDate>Wed, 25 Aug 2021 07:02:27 +0000</pubDate><media:content url="https://mikhalevi.ch/rclip-an-ai-powered-command-line-photo-search-tool/featured_hu_319930015a1ed69b.webp" medium="image"/><content:encoded><![CDATA[ <img src="https://mikhalevi.ch/rclip-an-ai-powered-command-line-photo-search-tool/featured_hu_319930015a1ed69b.webp" alt="rclip: an AI-powered command-line photo search tool"><p>I have 72,769 photos stored on my NAS and organized into subfolders named according to the pattern &ldquo;YEAR.MM.DD - Event description.&rdquo; This organizational approach is software-agnostic and creates a structure that is easy to navigate. I have followed it for more than a dozen years. However, there are situations when I want to find a particular photo. Looking for it even within a specific subfolder can take a lot of time, especially if that subfolder is packed with many pictures. Another challenging scenario is finding a picture that will fit a particular theme without having a specific one in mind. In both of these scenarios, having content-based search available is helpful. I looked around and didn&rsquo;t find a solution that satisfied me. What to do when you find yourself in a situation like this? Build your own solution!</p>
<p>Techniques allowing you to search images by their content are not necessarily new, but they usually only allow you to search using a set of predefined keywords and, sometimes, their synonyms. <a href="https://openai.com/blog/clip/" target="_blank" rel="noopener">OpenAI&rsquo;s CLIP</a> takes computer image understanding to a whole new level, allowing us to search for photos using any text query we can think of. This is why I used it to build my tool — rclip. In essence, rclip is a simple interface to the CLIP model. All I had to do was load the model, extract feature vectors from all of the images in the directory, store them somewhere, extract the feature vector from the query, compute similarities between the query feature vector and the image feature vectors, and output the most similar images. The vector extraction is done only once for each image, on the first run, and then the vectors are stored in the SQLite DB. SQLite is not the most performant way to manage that kind of data, but it&rsquo;s a quite convenient one. And, given that the time needed to query the DB on my 73 thousand photos catalog contributes only up to 12% of the whole execution time, I have decided to keep it for the time being.</p>
<p>I use <code>rgrep</code> a lot. <code>rgrep</code> is a simple alias to the <code>grep</code> command-line tool that searches recursively for a substring match in all of the files in the current directory. In its minimal definition (without a bunch of unwanted directories excluded), it looks like this: <code>alias rgrep=&quot;grep -rni&quot;</code>. To use it, open your terminal, navigate to the directory containing the files that you want to grep, type &ldquo;rgrep&rdquo; &ldquo;search query,&rdquo; and hit &ldquo;Enter.&rdquo;</p>
<p><code>rclip</code> implements exactly the same interface, but searches through the images&rsquo; content. The name stands for &ldquo;recursive CLIP.&rdquo; Its usage looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">cd</span> images <span class="o">&amp;&amp;</span> rclip <span class="s2">&#34;search query&#34;</span>
</span></span></code></pre></div><p>













<figure class="md-figure" >
  <div class="md-figure__inner" ><img alt="rclip usage demo"
          src="https://mikhalevi.ch/rclip-an-ai-powered-command-line-photo-search-tool/images/4deb9250-c353-11ee-b012-5f1e1a82f88a.gif"
          loading="lazy"
          class="md-img" data-zoomable /></div></figure>
</p>
<p>For the detailed demonstration, watch the video below:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
			<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/tAJHXOkHidw?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
		</div>

<p>That&rsquo;s it! If you want to try <code>rclip</code> yourself, follow the installation instructions described <a href="https://github.com/yurijmikhalevich/rclip#readme" target="_blank" rel="noopener">here</a>.</p>
 ]]></content:encoded></item><item><title> Efficient Routing on a Scale: Winning Solution of the Machine Learning Prague 2021 Hackathon </title><description><![CDATA[ <p>This post is a walkthrough of the winning solution for the <a href="https://www.mlprague.com/" target="_blank" rel="noopener">ML Prague</a> 2021 Hackathon problem that my teammate Michał and I came up with.</p>
<p>We were given anonymized data containing approximately 524k Czech policyholders, 44k clinic departments of 273 different types, and 332 ambulance stations. We were tasked with assigning each of the policyholders to the closest of each of the 273 clinics, in addition to the nearest ambulance station. The closest clinics and ambulance stations were supposed to have been picked by the driving distance. To help us with the routing, we were given $200USD in MS Azure credit, which was enough to make 40k routing requests (or 400k if we were willing to go under the 50 queries per second limit). This amount is nowhere near the one required for a brute-force solution, not to mention that it would take a while. We had five days to come up with the solution. On top of that, we were tasked to present our results using <a href="https://www.sas.com/en%5Fus/software/visual-analytics.html" target="_blank" rel="noopener">SAS Visual Analytics on SAS Vija</a>.</p> ]]></description><link>https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/</link><guid>https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/</guid><category> Software Engineering </category><dc:creator> Yurij Mikhalevich </dc:creator><pubDate>Mon, 10 May 2021 16:48:36 +0000</pubDate><media:content url="https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/featured_hu_ff71d436b952316f.webp" medium="image"/><content:encoded><![CDATA[ <img src="https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/featured_hu_ff71d436b952316f.webp" alt="Efficient Routing on a Scale: Winning Solution of the Machine Learning Prague 2021 Hackathon"><p>This post is a walkthrough of the winning solution for the <a href="https://www.mlprague.com/" target="_blank" rel="noopener">ML Prague</a> 2021 Hackathon problem that my teammate Michał and I came up with.</p>
<p>We were given anonymized data containing approximately 524k Czech policyholders, 44k clinic departments of 273 different types, and 332 ambulance stations. We were tasked with assigning each of the policyholders to the closest of each of the 273 clinics, in addition to the nearest ambulance station. The closest clinics and ambulance stations were supposed to have been picked by the driving distance. To help us with the routing, we were given $200USD in MS Azure credit, which was enough to make 40k routing requests (or 400k if we were willing to go under the 50 queries per second limit). This amount is nowhere near the one required for a brute-force solution, not to mention that it would take a while. We had five days to come up with the solution. On top of that, we were tasked to present our results using <a href="https://www.sas.com/en%5Fus/software/visual-analytics.html" target="_blank" rel="noopener">SAS Visual Analytics on SAS Vija</a>.</p>
<p>If you prefer video to text, here is our presentation for the conference featuring the dashboard:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
			<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/hZILy2awPqs?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
		</div>

<p>The video is denser and less detailed compared to this article.</p>
<p>Before we dive into the code, let&rsquo;s go over the solution&rsquo;s summary.</p>
<p>We’ve noticed that 5,827 of 43,908 clinics lacked data essential for the task: <code>type</code> and <code>department_id</code>. Online research failed to turn up a way to fill in the data, so we dropped the clinics that didn’t have these two fields filled in.</p>
<p>Since ambulance stations and clinics had the same data structure, we introduced a new, special type, <code>'AST'</code>, to identify ambulance stations and concatenated two tables into an &ldquo;organizations&rdquo; table.</p>
<p>Then, we’ve created a <code>parent_orgs</code> table that grouped organizations by their geolocation, preserving pairings between <code>type</code> and <code>department_id</code>, and thus, giving us a list of 10,797 unique geopoints we had to match users with.</p>
<p>The straightforward approach was to iterate over all users to find the closest organizations by computing driving distances between a user and the organizations, which were sorted by straight line distances, stopping when the straight line distance to the next candidate is larger than the driving distance to the best candidate.</p>
<p>But this approach proved inefficient even taking the sorting optimization into account; in a second-best case scenario, we had to compute <code>524102*274</code> routes. The actual best-case scenario number would be lower because a lot of <code>department_id</code>s share the same coordinate.</p>
<p>Instead, we decided to cluster users, compute driving distances to the centroids of the clusters, and approximate actual driving distances and times for each user based on the relation of the <code>direct_distance(user, organization) / direct_distance(cluster_centroid, organization)</code>. The algorithm isn’t perfect but gives us a very close approximation and can be computed in a reasonable time.</p>
<p>We clustered users within areas with a max radius of 500m using the combination of <code>village</code>+<code>village_part</code> to speed up clustering and to limit clusters to a single <code>village_part</code>. This provided us with 47,199 clusters, resulting in much more requests than we were comfortable issuing to MS Azure, considering the provided credit amount. Still, we didn’t want to sacrifice precision, so we set up a local OSRM server and used it to compute driving distances.</p>
<p>For direct distance computation, we used the haversine distance instead of the Vincenty, despite the former being less precise for geodesic computations because we could compute the haversine distance much faster. The error margin it introduces is not significant on the scale that we worked with, and both these distances provide optimistic approximations of the driving distance.</p>
<p>We also noticed that there are many <code>deparment_id</code>s of the same type, sharing the exact location. In these cases, we randomly distributed the corresponding users among them to spread the load between departments.</p>
<p>Alternatively, we could’ve used the fact that some people live on the same street to approximate driving distances, but we believe that small clusters approximate actual distances better.</p>
<p>Possible future improvements:</p>
<ul>
<li>Fill in missing <code>specialty_id</code> and <code>department_id</code>.</li>
<li>Reduce the cluster radius further, allow for more compute time, and re-compute distances with a smaller error margin.</li>
<li>Consider the distance between an ambulance station and a clinic when assigning an ambulance station to a <code>clinic</code>⇔<code>user/cluster</code> pair and choose an ambulance station closer to the clinic if there are multiple stations available within a similarly close distance to the <code>user/cluster</code>, so that the ambulance car can return to the station ASAP.</li>
</ul>
<p>Now, let&rsquo;s dive into the code. The code is in Python and is meant to be executed in a Jupyter Notebook.</p>
<p>According to the task, the output CSV should contain a row for each of the <code>policyholder</code>⇔<code>clinic department</code> pair in the following format:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">OUTPUT_COLUMNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;policyholder_num&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;contractual_specialty_id&#39;</span><span class="p">,</span>  <span class="c1"># clinic department type</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;pzs_department_id&#39;</span><span class="p">,</span>  <span class="c1"># clinic department id</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;km_to_pzs&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;min_to_pzs&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;zzs_department_id&#39;</span><span class="p">,</span>  <span class="c1"># ambulance station department id</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;km_to_zzs&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;min_to_zzs&#39;</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><h2 id="data-exploration">Data Exploration</h2>
<p>Data exploration is the first thing you should always do when faced with an ML or a data analysis challenge. If you overlook and neglect the importance of this step and do not set aside enough time for it, you may miss important insights, and, consequently, you may fail to implement an optimal solution.</p>
<p>We will use <a href="https://pandas.pydata.org/docs/" target="_blank" rel="noopener">pandas</a> to load and analyze the data.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="nn">pd</span>
</span></span></code></pre></div><h3 id="users-policyholders">Users (Policyholders)</h3>
<p>We start by loading and looking at the policyholders. Let&rsquo;s call them &ldquo;users&rdquo; moving forward.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">users</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_csv</span><span class="p">(</span><span class="s1">&#39;./table_POJ.csv&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">users</span><span class="o">.</span><span class="n">head</span><span class="p">()</span>
</span></span></code></pre></div><table>
	<thead>
			<tr>
					<th></th>
					<th>policyholder_num</th>
					<th>village</th>
					<th>village_part</th>
					<th>street</th>
					<th>land_num_type</th>
					<th>land_reg_num</th>
					<th>house_num</th>
					<th>WGS84_N</th>
					<th>WGS84_E</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>0</td>
					<td>XXXXXXXX</td>
					<td>Vysoký Újezd</td>
					<td>Vysoký Újezd</td>
					<td>NaN</td>
					<td>č.p.</td>
					<td>55</td>
					<td>NaN</td>
					<td>49.813014</td>
					<td>14.478450</td>
			</tr>
			<tr>
					<td>1</td>
					<td>XXXXXXXX</td>
					<td>Vysoký Újezd</td>
					<td>Vysoký Újezd</td>
					<td>NaN</td>
					<td>č.p.</td>
					<td>48</td>
					<td>NaN</td>
					<td>49.813251</td>
					<td>14.479900</td>
			</tr>
			<tr>
					<td>2</td>
					<td>XXXXXXXX</td>
					<td>Vysoký Újezd</td>
					<td>Vysoký Újezd</td>
					<td>NaN</td>
					<td>č.p.</td>
					<td>42</td>
					<td>NaN</td>
					<td>49.814633</td>
					<td>14.479510</td>
			</tr>
			<tr>
					<td>3</td>
					<td>XXXXXXXX</td>
					<td>Vysoký Újezd</td>
					<td>Vysoký Újezd</td>
					<td>NaN</td>
					<td>č.p.</td>
					<td>49</td>
					<td>NaN</td>
					<td>49.814687</td>
					<td>14.477156</td>
			</tr>
			<tr>
					<td>4</td>
					<td>XXXXXXXX</td>
					<td>Vysoký Újezd</td>
					<td>Vysoký Újezd</td>
					<td>NaN</td>
					<td>č.p.</td>
					<td>53</td>
					<td>NaN</td>
					<td>49.814780</td>
					<td>14.469591</td>
			</tr>
	</tbody>
</table>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">users</span><span class="o">.</span><span class="n">info</span><span class="p">()</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">&lt;class &#39;pandas.core.frame.DataFrame&#39;&gt;
</span></span><span class="line"><span class="cl">RangeIndex: 524102 entries, 0 to 524101
</span></span><span class="line"><span class="cl">Data columns (total 9 columns):
</span></span><span class="line"><span class="cl"> #   Column            Non-Null Count   Dtype  
</span></span><span class="line"><span class="cl">---  ------            --------------   -----  
</span></span><span class="line"><span class="cl"> 0   policyholder_num  524102 non-null  int64  
</span></span><span class="line"><span class="cl"> 1   village           524102 non-null  object 
</span></span><span class="line"><span class="cl"> 2   village_part      524102 non-null  object 
</span></span><span class="line"><span class="cl"> 3   street            289036 non-null  object 
</span></span><span class="line"><span class="cl"> 4   land_num_type     524102 non-null  object 
</span></span><span class="line"><span class="cl"> 5   land_reg_num      524102 non-null  int64  
</span></span><span class="line"><span class="cl"> 6   house_num         118529 non-null  float64
</span></span><span class="line"><span class="cl"> 7   WGS84_N           524102 non-null  float64
</span></span><span class="line"><span class="cl"> 8   WGS84_E           524102 non-null  float64
</span></span><span class="line"><span class="cl">dtypes: float64(3), int64(2), object(4)
</span></span><span class="line"><span class="cl">memory usage: 36.0+ MB
</span></span></code></pre></div><p>There are lots of null values for streets and house numbers. Fortunately, <a href="https://docs.microsoft.com/en-us/azure/azure-maps/tutorial-ev-routing#calculate-the-route-to-the-closest-charging-station" target="_blank" rel="noopener">Azure API can compute routes between lat,lon pairs</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">users</span><span class="o">.</span><span class="n">nunique</span><span class="p">()</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">policyholder_num    524102
</span></span><span class="line"><span class="cl">village               5342
</span></span><span class="line"><span class="cl">village_part         10706
</span></span><span class="line"><span class="cl">street               23471
</span></span><span class="line"><span class="cl">land_num_type            2
</span></span><span class="line"><span class="cl">land_reg_num          6265
</span></span><span class="line"><span class="cl">house_num              469
</span></span><span class="line"><span class="cl">WGS84_N             523864
</span></span><span class="line"><span class="cl">WGS84_E             523907
</span></span><span class="line"><span class="cl">dtype: int64
</span></span></code></pre></div><p>During this stage of the project, it&rsquo;s essential to understand your data very well.</p>
<p>We can see that <code>village</code>s and <code>village_part</code>s are filled for every user, and they already represent clusters of users, so we may use them for routing optimizations in the future. Also, we can use street info to better approximate driving distances and times for each user.</p>
<h3 id="clinics">Clinics</h3>
<p>Now let&rsquo;s look at the clinics.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">clinics</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_csv</span><span class="p">(</span><span class="s1">&#39;./table_PZS.csv&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">clinics</span><span class="o">.</span><span class="n">head</span><span class="p">()</span>
</span></span></code></pre></div><table>
	<thead>
			<tr>
					<th></th>
					<th>intern_subj_id</th>
					<th>name</th>
					<th>village</th>
					<th>street</th>
					<th>number</th>
					<th>zip</th>
					<th>WGS84_E</th>
					<th>WGS84_N</th>
					<th>company_id</th>
					<th>department_id</th>
					<th>contractual_specialty_id</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>0</td>
					<td>3</td>
					<td>Fakultní nemocnice Bulovka</td>
					<td>Praha</td>
					<td>Budínova</td>
					<td>67</td>
					<td>18000</td>
					<td>14.464154</td>
					<td>50.115453</td>
					<td>8995004</td>
					<td>NaN</td>
					<td>NaN</td>
			</tr>
			<tr>
					<td>1</td>
					<td>4</td>
					<td>Lékárna SALVIA spol. s r.o.</td>
					<td>Bystřice pod Hostýnem</td>
					<td>6. května</td>
					<td>556</td>
					<td>76861</td>
					<td>17.672536</td>
					<td>49.398406</td>
					<td>77610000</td>
					<td>NaN</td>
					<td>NaN</td>
			</tr>
			<tr>
					<td>2</td>
					<td>17</td>
					<td>Krajská zdravotní a.s.</td>
					<td>Chomutov</td>
					<td>Kochova</td>
					<td>1185</td>
					<td>43001</td>
					<td>13.411388</td>
					<td>50.455272</td>
					<td>57995417</td>
					<td>NaN</td>
					<td>NaN</td>
			</tr>
			<tr>
					<td>3</td>
					<td>21</td>
					<td>Endokrinologický ústav</td>
					<td>Praha</td>
					<td>Národní</td>
					<td>139</td>
					<td>11000</td>
					<td>14.415386</td>
					<td>50.081387</td>
					<td>1001000</td>
					<td>1001880.0</td>
					<td>101</td>
			</tr>
			<tr>
					<td>4</td>
					<td>21</td>
					<td>Endokrinologický ústav</td>
					<td>Praha</td>
					<td>Národní</td>
					<td>139</td>
					<td>11000</td>
					<td>14.415386</td>
					<td>50.081387</td>
					<td>1001000</td>
					<td>1001881.0</td>
					<td>104</td>
			</tr>
	</tbody>
</table>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">clinics</span><span class="o">.</span><span class="n">info</span><span class="p">()</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">&lt;class &#39;pandas.core.frame.DataFrame&#39;&gt;
</span></span><span class="line"><span class="cl">RangeIndex: 43908 entries, 0 to 43907
</span></span><span class="line"><span class="cl">Data columns (total 11 columns):
</span></span><span class="line"><span class="cl"> #   Column                    Non-Null Count  Dtype  
</span></span><span class="line"><span class="cl">---  ------                    --------------  -----  
</span></span><span class="line"><span class="cl"> 0   intern_subj_id            43908 non-null  int64  
</span></span><span class="line"><span class="cl"> 1   name                      43908 non-null  object 
</span></span><span class="line"><span class="cl"> 2   village                   43908 non-null  object 
</span></span><span class="line"><span class="cl"> 3   street                    41829 non-null  object 
</span></span><span class="line"><span class="cl"> 4   number                    43908 non-null  int64  
</span></span><span class="line"><span class="cl"> 5   zip                       43908 non-null  int64  
</span></span><span class="line"><span class="cl"> 6   WGS84_E                   43908 non-null  float64
</span></span><span class="line"><span class="cl"> 7   WGS84_N                   43908 non-null  float64
</span></span><span class="line"><span class="cl"> 8   company_id                43908 non-null  int64  
</span></span><span class="line"><span class="cl"> 9   department_id             38081 non-null  float64
</span></span><span class="line"><span class="cl"> 10  contractual_specialty_id  38081 non-null  object 
</span></span><span class="line"><span class="cl">dtypes: float64(3), int64(4), object(4)
</span></span><span class="line"><span class="cl">memory usage: 3.7+ MB
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">clinics</span><span class="o">.</span><span class="n">nunique</span><span class="p">()</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">intern_subj_id              26322
</span></span><span class="line"><span class="cl">name                        22816
</span></span><span class="line"><span class="cl">village                      1722
</span></span><span class="line"><span class="cl">street                       4442
</span></span><span class="line"><span class="cl">number                       2966
</span></span><span class="line"><span class="cl">zip                          1720
</span></span><span class="line"><span class="cl">WGS84_E                     13893
</span></span><span class="line"><span class="cl">WGS84_N                     13893
</span></span><span class="line"><span class="cl">company_id                  26322
</span></span><span class="line"><span class="cl">department_id               38080
</span></span><span class="line"><span class="cl">contractual_specialty_id      273
</span></span><span class="line"><span class="cl">dtype: int64
</span></span></code></pre></div><p>We see that a lot of clinics share their location. This is not surprising given that each row in the table represents an individual clinic department. But, for convenience, we will continue to call them &ldquo;clinics.&rdquo;</p>
<p>Missing streets shouldn&rsquo;t be a big deal since we can calculate distances using <code>lat,lon</code>, and these are filled in for every clinic.</p>
<p>We also notice that a lot of clinics are missing <code>department_id</code> and <code>contractual_specialty_id</code>. If we drop these clinics, we ignore 6k of them, which is a lot.</p>
<p>Notice that <code>department_id</code> and <code>contractual_specialty_id</code> have the same amount of null values. Let&rsquo;s check whether all of the clinics missing one are also missing the other.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="p">(</span><span class="n">clinics</span><span class="p">[</span><span class="s1">&#39;department_id&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">isna</span><span class="p">()</span> <span class="o">==</span> <span class="n">clinics</span><span class="p">[</span><span class="s1">&#39;contractual_specialty_id&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">isna</span><span class="p">())</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">True
</span></span></code></pre></div><p>They are. Let&rsquo;s see what we can do to fill the <code>null</code>s in. The map of the clinics might give us some insight.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">ipyleaflet</span> <span class="k">as</span> <span class="nn">ll</span>
</span></span><span class="line"><span class="cl"><span class="n">CENTER_OF_CZECHIA</span> <span class="o">=</span> <span class="p">(</span><span class="mf">49.856914</span><span class="p">,</span> <span class="mf">15.238706</span><span class="p">)</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">specialful_clinic_markers</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl"><span class="n">specialless_clinic_markers</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">specialful_clinic_icon</span> <span class="o">=</span> <span class="n">ll</span><span class="o">.</span><span class="n">AwesomeIcon</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">&#39;check&#39;</span><span class="p">,</span> <span class="n">marker_color</span><span class="o">=</span><span class="s1">&#39;green&#39;</span><span class="p">,</span> <span class="n">icon_color</span><span class="o">=</span><span class="s1">&#39;white&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">specialless_clinic_icon</span> <span class="o">=</span> <span class="n">ll</span><span class="o">.</span><span class="n">AwesomeIcon</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">&#39;exclamation&#39;</span><span class="p">,</span> <span class="n">marker_color</span><span class="o">=</span><span class="s1">&#39;red&#39;</span><span class="p">,</span> <span class="n">icon_color</span><span class="o">=</span><span class="s1">&#39;white&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">clinics</span><span class="o">.</span><span class="n">iterrows</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">pd</span><span class="o">.</span><span class="n">isna</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="s1">&#39;contractual_specialty_id&#39;</span><span class="p">]):</span>
</span></span><span class="line"><span class="cl">        <span class="n">specialless_clinic_markers</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ll</span><span class="o">.</span><span class="n">Marker</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">location</span><span class="o">=</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="s1">&#39;WGS84_N&#39;</span><span class="p">],</span> <span class="n">row</span><span class="p">[</span><span class="s1">&#39;WGS84_E&#39;</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span><span class="o">=</span><span class="n">specialless_clinic_icon</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">draggable</span><span class="o">=</span><span class="kc">False</span>
</span></span><span class="line"><span class="cl">        <span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">specialful_clinic_markers</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ll</span><span class="o">.</span><span class="n">Marker</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">location</span><span class="o">=</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="s1">&#39;WGS84_N&#39;</span><span class="p">],</span> <span class="n">row</span><span class="p">[</span><span class="s1">&#39;WGS84_E&#39;</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span><span class="o">=</span><span class="n">specialful_clinic_icon</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">draggable</span><span class="o">=</span><span class="kc">False</span>
</span></span><span class="line"><span class="cl">        <span class="p">))</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl"><span class="n">specialful_clinic_cluster</span> <span class="o">=</span> <span class="n">ll</span><span class="o">.</span><span class="n">MarkerCluster</span><span class="p">(</span><span class="n">markers</span><span class="o">=</span><span class="n">specialful_clinic_markers</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">specialless_clinic_cluster</span> <span class="o">=</span> <span class="n">ll</span><span class="o">.</span><span class="n">MarkerCluster</span><span class="p">(</span><span class="n">markers</span><span class="o">=</span><span class="n">specialless_clinic_markers</span><span class="p">)</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">clinics_map</span> <span class="o">=</span> <span class="n">ll</span><span class="o">.</span><span class="n">Map</span><span class="p">(</span><span class="n">center</span><span class="o">=</span><span class="n">CENTER_OF_CZECHIA</span><span class="p">,</span> <span class="n">zoom</span><span class="o">=</span><span class="mi">7</span><span class="p">,</span> <span class="n">min_zoom</span><span class="o">=</span><span class="mi">7</span><span class="p">,</span> <span class="n">max_zoom</span><span class="o">=</span><span class="mi">19</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">clinics_map</span><span class="o">.</span><span class="n">add_control</span><span class="p">(</span><span class="n">ll</span><span class="o">.</span><span class="n">FullScreenControl</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="n">clinics_map</span><span class="o">.</span><span class="n">add_layer</span><span class="p">(</span><span class="n">specialful_clinic_cluster</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">clinics_map</span><span class="o">.</span><span class="n">add_layer</span><span class="p">(</span><span class="n">specialless_clinic_cluster</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">clinics_map</span>
</span></span></code></pre></div><p>













<figure class="md-figure" id="figure-to-make-it-easy-for-your-computer-we-are-displaying-the-screenshot-of-the-map-here">
  <div class="md-figure__inner" ><img alt="To make it easy for your computer, we are displaying the screenshot of the map here" srcset="
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fda700-c353-11ee-b012-5f1e1a82f88a_hu_c5722170da1e2d1f.webp 400w,
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fda700-c353-11ee-b012-5f1e1a82f88a_hu_4b2ffc095b6fbf3f.webp 760w,
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fda700-c353-11ee-b012-5f1e1a82f88a_hu_21c985e82d917da2.webp 1312w"
          src="https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fda700-c353-11ee-b012-5f1e1a82f88a_hu_21c985e82d917da2.webp"
          width="760"
          height="460"
          loading="lazy"
          class="md-img"
          style="background: linear-gradient(37.000000deg, #e0e3d0b3, #e0e3d000 75%),linear-gradient(157.000000deg, #ea9f58b3, #ea9f5800 75%),linear-gradient(277.000000deg, #a75d26b3, #a75d2600 75%); background-size: cover; background-repeat: no-repeat;" data-zoomable /></div><figcaption class="md-figcaption">
      To make it easy for your computer, we are displaying the screenshot of the map here
    </figcaption></figure>
</p>
<p>Neither the map nor the online research we did helped us to fill in the missing data. Given that <code>department_id</code>s and <code>contractual_specialty_id</code>s are essential for the task, we will have to drop the clinics that are missing them. Room for improvement lies within properly filling that data.</p>
<h3 id="ambulance-stations">Ambulance Stations</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">ambulance_stations</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_csv</span><span class="p">(</span><span class="s1">&#39;./table_ZZS.csv&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">ambulance_stations</span><span class="o">.</span><span class="n">head</span><span class="p">()</span>
</span></span></code></pre></div><table>
	<thead>
			<tr>
					<th></th>
					<th>intern_subj_id</th>
					<th>name</th>
					<th>village</th>
					<th>street</th>
					<th>number</th>
					<th>zip</th>
					<th>WGS84_E</th>
					<th>WGS84_N</th>
					<th>company_id</th>
					<th>department_id</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>0</td>
					<td>92</td>
					<td>Zdravotnická záchranná služba hl .m. Prahy</td>
					<td>Praha</td>
					<td>28. pluku</td>
					<td>1393</td>
					<td>10100</td>
					<td>14.464515</td>
					<td>50.070763</td>
					<td>5001000</td>
					<td>5001109</td>
			</tr>
			<tr>
					<td>1</td>
					<td>92</td>
					<td>Zdravotnická záchranná služba hl .m. Prahy</td>
					<td>Praha</td>
					<td>Chelčického</td>
					<td>842</td>
					<td>13000</td>
					<td>14.458671</td>
					<td>50.083906</td>
					<td>5001000</td>
					<td>5001113</td>
			</tr>
			<tr>
					<td>2</td>
					<td>92</td>
					<td>Zdravotnická záchranná služba hl .m. Prahy</td>
					<td>Praha</td>
					<td>Dukelských hrdinů</td>
					<td>342</td>
					<td>17000</td>
					<td>14.431312</td>
					<td>50.096500</td>
					<td>5001000</td>
					<td>5001102</td>
			</tr>
			<tr>
					<td>3</td>
					<td>92</td>
					<td>Zdravotnická záchranná služba hl .m. Prahy</td>
					<td>Praha</td>
					<td>Generála Janouška</td>
					<td>902</td>
					<td>19800</td>
					<td>14.572139</td>
					<td>50.106939</td>
					<td>5001000</td>
					<td>5001110</td>
			</tr>
			<tr>
					<td>4</td>
					<td>92</td>
					<td>Zdravotnická záchranná služba hl .m. Prahy</td>
					<td>Praha</td>
					<td>Heyrovského náměstí</td>
					<td>1987</td>
					<td>16200</td>
					<td>14.340193</td>
					<td>50.086547</td>
					<td>5001000</td>
					<td>5001104</td>
			</tr>
	</tbody>
</table>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">ambulance_stations</span><span class="o">.</span><span class="n">info</span><span class="p">()</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">&lt;class &#39;pandas.core.frame.DataFrame&#39;&gt;
</span></span><span class="line"><span class="cl">RangeIndex: 332 entries, 0 to 331
</span></span><span class="line"><span class="cl">Data columns (total 10 columns):
</span></span><span class="line"><span class="cl"> #   Column          Non-Null Count  Dtype  
</span></span><span class="line"><span class="cl">---  ------          --------------  -----  
</span></span><span class="line"><span class="cl"> 0   intern_subj_id  332 non-null    int64  
</span></span><span class="line"><span class="cl"> 1   name            332 non-null    object 
</span></span><span class="line"><span class="cl"> 2   village         332 non-null    object 
</span></span><span class="line"><span class="cl"> 3   street          307 non-null    object 
</span></span><span class="line"><span class="cl"> 4   number          332 non-null    int64  
</span></span><span class="line"><span class="cl"> 5   zip             332 non-null    int64  
</span></span><span class="line"><span class="cl"> 6   WGS84_E         332 non-null    float64
</span></span><span class="line"><span class="cl"> 7   WGS84_N         332 non-null    float64
</span></span><span class="line"><span class="cl"> 8   company_id      332 non-null    int64  
</span></span><span class="line"><span class="cl"> 9   department_id   332 non-null    int64  
</span></span><span class="line"><span class="cl">dtypes: float64(2), int64(5), object(3)
</span></span><span class="line"><span class="cl">memory usage: 26.1+ KB
</span></span></code></pre></div><p>Some ambulance stations are missing streets, but that&rsquo;s OK since we can calculate driving distances based on <code>lat,lon</code>.</p>
<h2 id="pre-processing">Pre-processing</h2>
<p>Since we were unable to fill in the missing clinics <code>contractual_specialty_id</code>s and <code>department_id</code>s, let&rsquo;s drop all of the clinics that are missing them.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">clinics</span><span class="o">.</span><span class="n">dropna</span><span class="p">(</span><span class="n">subset</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;contractual_specialty_id&#39;</span><span class="p">],</span> <span class="n">inplace</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">clinics</span><span class="o">.</span><span class="n">reset_index</span><span class="p">(</span><span class="n">drop</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">inplace</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">len</span><span class="p">(</span><span class="n">clinics</span><span class="o">.</span><span class="n">index</span><span class="p">),</span> <span class="n">clinics</span><span class="p">[</span><span class="s1">&#39;contractual_specialty_id&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">nunique</span><span class="p">()</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">(38081, 273)
</span></span></code></pre></div><p>Ambulance stations and clinic departments have the same structure and, for the purpose of the task, it would make sense to think of ambulance stations as clinics of a special type. To do this, we introduce a special type <code>'AST'</code>, assign it to the ambulance stations, and concatenate them with the clinics.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">ambulance_stations</span><span class="p">[</span><span class="s1">&#39;contractual_specialty_id&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s1">&#39;AST&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">orgs</span> <span class="o">=</span> <span class="n">clinics</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ambulance_stations</span><span class="p">,</span> <span class="n">ignore_index</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">len</span><span class="p">(</span><span class="n">orgs</span><span class="o">.</span><span class="n">index</span><span class="p">),</span> <span class="n">orgs</span><span class="p">[</span><span class="s1">&#39;contractual_specialty_id&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">nunique</span><span class="p">()</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">(38413, 274)
</span></span></code></pre></div><p>We copy the coordinates to a new <code>location</code> column in <code>(lat, lon)</code> format to make working with them more convenient.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">users</span><span class="p">[</span><span class="s1">&#39;location&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="nb">zip</span><span class="p">(</span><span class="n">users</span><span class="p">[</span><span class="s1">&#39;WGS84_N&#39;</span><span class="p">],</span> <span class="n">users</span><span class="p">[</span><span class="s1">&#39;WGS84_E&#39;</span><span class="p">]))</span>
</span></span><span class="line"><span class="cl"><span class="n">orgs</span><span class="p">[</span><span class="s1">&#39;location&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="nb">zip</span><span class="p">(</span><span class="n">orgs</span><span class="p">[</span><span class="s1">&#39;WGS84_N&#39;</span><span class="p">],</span> <span class="n">orgs</span><span class="p">[</span><span class="s1">&#39;WGS84_E&#39;</span><span class="p">]))</span>
</span></span></code></pre></div><p>As we saw during the clinic analysis, most of the clinic departments share locations and can be uniquely identified by a combination of <code>contractual_specialty_id</code> and <code>department_id</code>. Let&rsquo;s create a table of parent organizations with a row for each set of clinics that share their location.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">orgs</span><span class="p">[</span><span class="s1">&#39;specialty_department&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="nb">zip</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">orgs</span><span class="p">[</span><span class="s1">&#39;contractual_specialty_id&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="n">orgs</span><span class="p">[</span><span class="s1">&#39;department_id&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">parent_orgs</span> <span class="o">=</span> <span class="n">orgs</span><span class="o">.</span><span class="n">groupby</span><span class="p">(</span><span class="s1">&#39;location&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">agg</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;contractual_specialty_id&#39;</span><span class="p">:</span> <span class="nb">set</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;specialty_department&#39;</span><span class="p">:</span> <span class="nb">list</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;location&#39;</span><span class="p">:</span> <span class="s1">&#39;first&#39;</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">parent_orgs</span><span class="o">.</span><span class="n">index</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">parent_orgs</span><span class="o">.</span><span class="n">head</span><span class="p">()</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">10797
</span></span></code></pre></div><table>
	<thead>
			<tr>
					<th>location</th>
					<th>contractual_specialty_id</th>
					<th>specialty_department</th>
					<th>location</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>(48.614218906, 14.311215042)</td>
					<td>{014}</td>
					<td>[(014, 33866100.0), (014, 33866200.0)]</td>
					<td>(48.614218906, 14.311215042)</td>
			</tr>
			<tr>
					<td>(48.617204511, 14.313272811)</td>
					<td>{AST, 709, 799}</td>
					<td>[(709, 32091122.0), (799, 32091822.0), (AST, 3&hellip;</td>
					<td>(48.617204511, 14.313272811)</td>
			</tr>
			<tr>
					<td>(48.617317188, 14.310456114)</td>
					<td>{001, 902}</td>
					<td>[(001, 33709000.0), (902, 33850100.0)]</td>
					<td>(48.617317188, 14.310456114)</td>
			</tr>
			<tr>
					<td>(48.618122407, 14.309835495)</td>
					<td>{903}</td>
					<td>[(903, 33721000.0)]</td>
					<td>(48.618122407, 14.309835495)</td>
			</tr>
			<tr>
					<td>(48.618840446, 14.30797346)</td>
					<td>{014}</td>
					<td>[(014, 33701000.0)]</td>
					<td>(48.618840446, 14.30797346)</td>
			</tr>
	</tbody>
</table>
<p>This leaves us with 10,797 of the &ldquo;actual&rdquo; clinics that we will be computing routes to. We&rsquo;ve stored a set of <code>contractual_specialty_id</code>s to a <code>conctractual_specialty_id</code> column to speed up the check for matching specialty ids.</p>
<p>From the start, we&rsquo;ve aimed for maximum precision. The most precise way to solve the task would be to compute distances for each user individually, so we&rsquo;ve implemented this straightforward solution first to identify the slowest part of the process and work from there.</p>
<p>It turned out that the slowest part is, not surprisingly, routing and that we will have to optimize it. Clustering users, and maybe, clinics, seemed like a good idea to explore.</p>
<p>The most difficult part of the task is to find a performant solution without sacrificing much of the precision.</p>
<h2 id="maps">Maps</h2>
<p>Let&rsquo;s return to the data exploration and plot <code>parent_orgs</code> and <code>users</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">orgs_map</span> <span class="o">=</span> <span class="n">ll</span><span class="o">.</span><span class="n">Map</span><span class="p">(</span><span class="n">center</span><span class="o">=</span><span class="n">CENTER_OF_CZECHIA</span><span class="p">,</span> <span class="n">zoom</span><span class="o">=</span><span class="mi">7</span><span class="p">,</span> <span class="n">min_zoom</span><span class="o">=</span><span class="mi">7</span><span class="p">,</span> <span class="n">max_zoom</span><span class="o">=</span><span class="mi">19</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">orgs_map</span><span class="o">.</span><span class="n">add_control</span><span class="p">(</span><span class="n">ll</span><span class="o">.</span><span class="n">FullScreenControl</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="n">orgs_map</span><span class="o">.</span><span class="n">add_layer</span><span class="p">(</span><span class="n">ll</span><span class="o">.</span><span class="n">MarkerCluster</span><span class="p">(</span><span class="n">markers</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="n">ll</span><span class="o">.</span><span class="n">Marker</span><span class="p">(</span><span class="n">location</span><span class="o">=</span><span class="n">org</span><span class="p">[</span><span class="s1">&#39;location&#39;</span><span class="p">])</span> <span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">org</span> <span class="ow">in</span> <span class="n">parent_orgs</span><span class="o">.</span><span class="n">iterrows</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">]))</span>
</span></span><span class="line"><span class="cl"><span class="n">orgs_map</span>
</span></span></code></pre></div><p>













<figure class="md-figure" >
  <div class="md-figure__inner" ><img alt="" srcset="
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe4340-c353-11ee-b012-5f1e1a82f88a_hu_dadaecc945582ca9.webp 400w,
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe4340-c353-11ee-b012-5f1e1a82f88a_hu_51e3b6d2398c9989.webp 760w,
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe4340-c353-11ee-b012-5f1e1a82f88a_hu_45740db9932c8e9a.webp 1348w"
          src="https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe4340-c353-11ee-b012-5f1e1a82f88a_hu_45740db9932c8e9a.webp"
          width="760"
          height="445"
          loading="lazy"
          class="md-img"
          style="background: linear-gradient(37.000000deg, #dfe3d0b3, #dfe3d000 75%),linear-gradient(217.000000deg, #e4a15bb3, #e4a15b00 75%); background-size: cover; background-repeat: no-repeat;" data-zoomable /></div></figure>
</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">users_map</span> <span class="o">=</span> <span class="n">ll</span><span class="o">.</span><span class="n">Map</span><span class="p">(</span><span class="n">center</span><span class="o">=</span><span class="n">CENTER_OF_CZECHIA</span><span class="p">,</span> <span class="n">zoom</span><span class="o">=</span><span class="mi">7</span><span class="p">,</span> <span class="n">min_zoom</span><span class="o">=</span><span class="mi">7</span><span class="p">,</span> <span class="n">max_zoom</span><span class="o">=</span><span class="mi">19</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">users_map</span><span class="o">.</span><span class="n">add_control</span><span class="p">(</span><span class="n">ll</span><span class="o">.</span><span class="n">FullScreenControl</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="n">users_map</span><span class="o">.</span><span class="n">add_layer</span><span class="p">(</span><span class="n">ll</span><span class="o">.</span><span class="n">MarkerCluster</span><span class="p">(</span><span class="n">markers</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="n">ll</span><span class="o">.</span><span class="n">Marker</span><span class="p">(</span><span class="n">location</span><span class="o">=</span><span class="n">org</span><span class="p">[</span><span class="s1">&#39;location&#39;</span><span class="p">])</span> <span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">org</span> <span class="ow">in</span> <span class="n">users</span><span class="p">[</span><span class="n">users</span><span class="p">[</span><span class="s1">&#39;village&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="s1">&#39;Praha&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">iterrows</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">]))</span>
</span></span><span class="line"><span class="cl"><span class="n">users_map</span>
</span></span></code></pre></div><p>













<figure class="md-figure" >
  <div class="md-figure__inner" ><img alt="" srcset="
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe4341-c353-11ee-b012-5f1e1a82f88a_hu_ca942f58a6f49896.webp 400w,
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe4341-c353-11ee-b012-5f1e1a82f88a_hu_8b6523e6882d81a9.webp 760w,
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe4341-c353-11ee-b012-5f1e1a82f88a_hu_55188c2f3dbe84cd.webp 1306w"
          src="https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe4341-c353-11ee-b012-5f1e1a82f88a_hu_55188c2f3dbe84cd.webp"
          width="760"
          height="461"
          loading="lazy"
          class="md-img"
          style="background: linear-gradient(37.000000deg, #d3d4c6b3, #d3d4c600 75%),linear-gradient(217.000000deg, #dcaf5ab3, #dcaf5a00 75%); background-size: cover; background-repeat: no-repeat;" data-zoomable /></div></figure>
</p>
<p>It&rsquo;s interesting to take a look at the Voronoi regions of different clinic types. They can provide us a way to query for candidates.</p>
<blockquote>
<p>In mathematics, a Voronoi diagram is a partition of a plane into regions close to each of a given set of objects. In the simplest case, these objects are just finitely many points in the plane (called seeds, sites, or generators). For each seed there is a corresponding region, called Voronoi cells, consisting of all points of the plane closer to that seed than to any other.<br>
— <a href="https://en.wikipedia.org/wiki/Voronoi%5Fdiagram" target="_blank" rel="noopener">Voronoi diagram - Wikipedia</a></p>
</blockquote>
<p>To compute the regions, we will use the <a href="https://pypi.org/project/geovoronoi/" target="_blank" rel="noopener">geovoronoi</a> library. Since it uses <a href="https://pypi.org/project/Shapely/" target="_blank" rel="noopener">shapely</a> polygons, we will import the shapely module too. We will also import <a href="https://geopandas.org/" target="_blank" rel="noopener">geopandas</a> to simplify manipulations with the locations in our pandas&rsquo; tables and convert them to shapely format.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">geopandas</span> <span class="k">as</span> <span class="nn">gpd</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">geovoronoi</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">shapely.geometry</span> <span class="k">as</span> <span class="nn">shg</span>
</span></span></code></pre></div><p>Then, let&rsquo;s convert our <code>users</code> and <code>parent_orgs</code> to geo-aware <code>users_geo</code> and <code>orgs_geo</code> tables.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">users_geo</span> <span class="o">=</span> <span class="n">gpd</span><span class="o">.</span><span class="n">GeoDataFrame</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">users</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">crs</span><span class="o">=</span><span class="s1">&#39;EPSG:4326&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">geometry</span><span class="o">=</span><span class="n">gpd</span><span class="o">.</span><span class="n">points_from_xy</span><span class="p">(</span><span class="n">users</span><span class="p">[</span><span class="s1">&#39;WGS84_E&#39;</span><span class="p">],</span> <span class="n">users</span><span class="p">[</span><span class="s1">&#39;WGS84_N&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">org_locs</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="nb">zip</span><span class="p">(</span><span class="o">*</span><span class="n">parent_orgs</span><span class="p">[</span><span class="s1">&#39;location&#39;</span><span class="p">]))</span>
</span></span><span class="line"><span class="cl"><span class="n">orgs_geo</span> <span class="o">=</span> <span class="n">gpd</span><span class="o">.</span><span class="n">GeoDataFrame</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">parent_orgs</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">crs</span><span class="o">=</span><span class="s1">&#39;EPSG:4326&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">geometry</span><span class="o">=</span><span class="n">gpd</span><span class="o">.</span><span class="n">points_from_xy</span><span class="p">(</span><span class="n">org_locs</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">org_locs</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></div><p>We will also need a helper method to convert shapely polygons to leaflet format:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">polygon_to_ll</span><span class="p">(</span><span class="n">polygon</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">polygon</span><span class="p">,</span> <span class="n">shg</span><span class="o">.</span><span class="n">Point</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">[(</span><span class="n">polygon</span><span class="o">.</span><span class="n">y</span><span class="p">,</span> <span class="n">polygon</span><span class="o">.</span><span class="n">x</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">polygon</span><span class="p">,</span> <span class="n">shg</span><span class="o">.</span><span class="n">LineString</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">[(</span><span class="n">point</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">point</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span> <span class="k">for</span> <span class="n">point</span> <span class="ow">in</span> <span class="n">polygon</span><span class="o">.</span><span class="n">coords</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">[(</span><span class="n">point</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">point</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span> <span class="k">for</span> <span class="n">point</span> <span class="ow">in</span> <span class="n">polygon</span><span class="o">.</span><span class="n">exterior</span><span class="o">.</span><span class="n">coords</span><span class="p">]</span>
</span></span></code></pre></div><p>And a simple method to plot regions for an org type:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># a polygon encompassing all of the users</span>
</span></span><span class="line"><span class="cl"><span class="n">all_users</span> <span class="o">=</span> <span class="n">users_geo</span><span class="o">.</span><span class="n">unary_union</span><span class="o">.</span><span class="n">convex_hull</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">plot_voronoi_regions_for_org_by_type</span><span class="p">(</span><span class="n">org_type</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">region_polys</span><span class="p">,</span> <span class="n">region_pts</span> <span class="o">=</span> <span class="n">geovoronoi</span><span class="o">.</span><span class="n">voronoi_regions_from_coords</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">orgs_geo</span><span class="p">[</span><span class="n">orgs_geo</span><span class="p">[</span><span class="s1">&#39;contractual_specialty_id&#39;</span><span class="p">]</span> <span class="o">&amp;</span> <span class="p">{</span><span class="n">org_type</span><span class="p">}][</span><span class="s1">&#39;geometry&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">tolist</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="n">all_users</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">voronoi_map</span> <span class="o">=</span> <span class="n">ll</span><span class="o">.</span><span class="n">Map</span><span class="p">(</span><span class="n">center</span><span class="o">=</span><span class="n">CENTER_OF_CZECHIA</span><span class="p">,</span> <span class="n">zoom</span><span class="o">=</span><span class="mi">7</span><span class="p">,</span> <span class="n">min_zoom</span><span class="o">=</span><span class="mi">7</span><span class="p">,</span> <span class="n">max_zoom</span><span class="o">=</span><span class="mi">19</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">voronoi_map</span><span class="o">.</span><span class="n">add_control</span><span class="p">(</span><span class="n">ll</span><span class="o">.</span><span class="n">FullScreenControl</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="n">voronoi_map</span><span class="o">.</span><span class="n">add_layer</span><span class="p">(</span><span class="n">ll</span><span class="o">.</span><span class="n">Polygon</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">locations</span><span class="o">=</span><span class="p">[</span><span class="n">polygon_to_ll</span><span class="p">(</span><span class="n">poly</span><span class="p">)</span> <span class="k">for</span> <span class="n">poly</span> <span class="ow">in</span> <span class="n">region_polys</span><span class="o">.</span><span class="n">values</span><span class="p">()],</span>
</span></span><span class="line"><span class="cl">        <span class="n">color</span><span class="o">=</span><span class="s2">&#34;green&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">fill_color</span><span class="o">=</span><span class="s2">&#34;green&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">voronoi_map</span>
</span></span></code></pre></div><p>We will plot Voronoi regions for one of the most common types of clinics and one of the rarest.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">plot_voronoi_regions_for_org_by_type</span><span class="p">(</span><span class="s1">&#39;014&#39;</span><span class="p">)</span>
</span></span></code></pre></div><p>













<figure class="md-figure" >
  <div class="md-figure__inner" ><img alt="" srcset="
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe6a50-c353-11ee-b012-5f1e1a82f88a_hu_28ea6a8c10592553.webp 400w,
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe6a50-c353-11ee-b012-5f1e1a82f88a_hu_4ffb6c76a8cf33ea.webp 760w,
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe6a50-c353-11ee-b012-5f1e1a82f88a_hu_c2eb8fddb6b18f16.webp 1448w"
          src="https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe6a50-c353-11ee-b012-5f1e1a82f88a_hu_c2eb8fddb6b18f16.webp"
          width="760"
          height="417"
          loading="lazy"
          class="md-img"
          style="background: linear-gradient(37.000000deg, #d7e4cbb3, #d7e4cb00 75%),linear-gradient(157.000000deg, #077907b3, #07790700 75%),linear-gradient(277.000000deg, #289727b3, #28972700 75%); background-size: cover; background-repeat: no-repeat;" data-zoomable /></div></figure>
</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">plot_voronoi_regions_for_org_by_type</span><span class="p">(</span><span class="s1">&#39;504&#39;</span><span class="p">)</span>
</span></span></code></pre></div><p>













<figure class="md-figure" >
  <div class="md-figure__inner" ><img alt="" srcset="
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe6a51-c353-11ee-b012-5f1e1a82f88a_hu_85762baef8db6ff6.webp 400w,
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe6a51-c353-11ee-b012-5f1e1a82f88a_hu_504e1a5370d2ddba.webp 760w,
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe6a51-c353-11ee-b012-5f1e1a82f88a_hu_be38a887be6bb31a.webp 1452w"
          src="https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe6a51-c353-11ee-b012-5f1e1a82f88a_hu_be38a887be6bb31a.webp"
          width="760"
          height="415"
          loading="lazy"
          class="md-img"
          style="background: linear-gradient(37.000000deg, #caddbeb3, #caddbe00 75%),linear-gradient(157.000000deg, #0f740eb3, #0f740e00 75%),linear-gradient(277.000000deg, #4fa24bb3, #4fa24b00 75%); background-size: cover; background-repeat: no-repeat;" data-zoomable /></div></figure>
</p>
<p>As you can see, some clinics have very sparse coverage; if they represent an essential specialty, there needs to be more of them.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">plot_voronoi_regions_for_org_by_type</span><span class="p">(</span><span class="s1">&#39;AST&#39;</span><span class="p">)</span>
</span></span></code></pre></div><p>













<figure class="md-figure" >
  <div class="md-figure__inner" ><img alt="" srcset="
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe6a52-c353-11ee-b012-5f1e1a82f88a_hu_49fd38fa0cebdb4f.webp 400w,
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe6a52-c353-11ee-b012-5f1e1a82f88a_hu_1168a57356ab1cac.webp 760w,
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe6a52-c353-11ee-b012-5f1e1a82f88a_hu_e120431b90e6a1d9.webp 1452w"
          src="https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe6a52-c353-11ee-b012-5f1e1a82f88a_hu_e120431b90e6a1d9.webp"
          width="760"
          height="413"
          loading="lazy"
          class="md-img"
          style="background: linear-gradient(37.000000deg, #cde1c2b3, #cde1c200 75%),linear-gradient(127.000000deg, #0d750cb3, #0d750c00 75%),linear-gradient(217.000000deg, #46a142b3, #46a14200 75%),linear-gradient(307.000000deg, #86c47ab3, #86c47a00 75%); background-size: cover; background-repeat: no-repeat;" data-zoomable /></div></figure>
</p>
<p>These Voronoi regions provide better insight into the clinic density and distribution.</p>
<h2 id="clusters">Clusters</h2>
<p>Next up is clustering. We decided to cluster users by distance using <code>village</code> information to speed up the clustering.</p>
<h3 id="utils">Utils</h3>
<p><a href="https://en.wikipedia.org/wiki/Vincenty%27s%5Fformulae#Inverse%5Fproblem" target="_blank" rel="noopener">Vincenty&rsquo;s formulae</a> offer one of the most commonly adopted methods to compute the geodesic distance. It&rsquo;s pretty precise but comparably slow in comparison to the great circle distance computation. To compute the latter, we will use the <a href="https://en.wikipedia.org/wiki/Haversine%5Fformula" target="_blank" rel="noopener">haversine formula</a>. The haversine formula is much simpler, and therefore, much faster. Let&rsquo;s see by how much.</p>
<p>We will use <a href="https://sourceforge.net/projects/geographiclib/" target="_blank" rel="noopener">geographiclib</a>&rsquo;s implementation of Vincenty&rsquo;s formula. It&rsquo;s a great library of various geodesic algorithm implementations. For the haversine formula, we will use the python <a href="https://pypi.org/project/haversine/" target="_blank" rel="noopener">haversine</a> library.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">geographiclib.geodesic</span> <span class="kn">import</span> <span class="n">Geodesic</span> <span class="k">as</span> <span class="n">geo</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">haversine</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">point_a</span> <span class="o">=</span> <span class="p">(</span><span class="mf">50.088036196500006</span><span class="p">,</span> <span class="mf">15.820675680125</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">point_b</span> <span class="o">=</span> <span class="p">(</span><span class="mf">50.036845777</span><span class="p">,</span> <span class="mf">15.774167073</span><span class="p">)</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">%</span><span class="n">timeit</span> <span class="n">geo</span><span class="o">.</span><span class="n">WGS84</span><span class="o">.</span><span class="n">Inverse</span><span class="p">(</span><span class="o">*</span><span class="n">point_a</span><span class="p">,</span> <span class="o">*</span><span class="n">point_b</span><span class="p">)</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">76.7 µs ± 1.49 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">%</span><span class="n">timeit</span> <span class="n">haversine</span><span class="o">.</span><span class="n">haversine</span><span class="p">(</span><span class="n">point_a</span><span class="p">,</span> <span class="n">point_b</span><span class="p">,</span> <span class="n">haversine</span><span class="o">.</span><span class="n">Unit</span><span class="o">.</span><span class="n">METERS</span><span class="p">)</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">1.31 µs ± 10.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">geo</span><span class="o">.</span><span class="n">WGS84</span><span class="o">.</span><span class="n">Inverse</span><span class="p">(</span><span class="o">*</span><span class="n">point_a</span><span class="p">,</span> <span class="o">*</span><span class="n">point_b</span><span class="p">)[</span><span class="s1">&#39;s12&#39;</span><span class="p">],</span> <span class="n">haversine</span><span class="o">.</span><span class="n">haversine</span><span class="p">(</span><span class="n">point_a</span><span class="p">,</span> <span class="n">point_b</span><span class="p">,</span> <span class="n">haversine</span><span class="o">.</span><span class="n">Unit</span><span class="o">.</span><span class="n">METERS</span><span class="p">)</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">(6596.260963017945, 6589.524077151891)
</span></span></code></pre></div><p>The difference in performance is staggering when the error margin is negligible. Given that we will only be using this distance for approximations, and both distances are very optimistic approximations of the driving distance, we will stick to the haversine distance going forward.</p>
<p>Before we move on, let&rsquo;s import the progress bar. Things we do from this moment on will take much more time to compute, and it&rsquo;s nice to monitor the progress.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">fastprogress.fastprogress</span> <span class="kn">import</span> <span class="n">progress_bar</span>
</span></span></code></pre></div><p>Below is the method that we will use to compute and plot the clusters.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">plot_user_clusters_by</span><span class="p">(</span><span class="n">key_fn</span><span class="p">,</span> <span class="n">radius_m</span><span class="o">=</span><span class="mi">10_000</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">clusters</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">user</span> <span class="ow">in</span> <span class="n">progress_bar</span><span class="p">(</span><span class="n">users_geo</span><span class="o">.</span><span class="n">iterrows</span><span class="p">(),</span> <span class="n">total</span><span class="o">=</span><span class="nb">len</span><span class="p">(</span><span class="n">users_geo</span><span class="o">.</span><span class="n">index</span><span class="p">)):</span>
</span></span><span class="line"><span class="cl">        <span class="n">key</span> <span class="o">=</span> <span class="n">key_fn</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">user_loc</span> <span class="o">=</span> <span class="n">user</span><span class="p">[</span><span class="s1">&#39;location&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">key</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">clusters</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">clusters</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="p">[[(</span><span class="n">i</span><span class="p">,</span> <span class="n">user_loc</span><span class="p">)]]</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">found_cluster</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="n">cluster</span> <span class="ow">in</span> <span class="n">clusters</span><span class="p">[</span><span class="n">key</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">                <span class="n">first_user_location</span> <span class="o">=</span> <span class="n">cluster</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">                <span class="n">distance</span> <span class="o">=</span> <span class="n">haversine</span><span class="o">.</span><span class="n">haversine</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="n">first_user_location</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">user</span><span class="p">[</span><span class="s1">&#39;location&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                    <span class="n">haversine</span><span class="o">.</span><span class="n">Unit</span><span class="o">.</span><span class="n">METERS</span>
</span></span><span class="line"><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="n">distance</span> <span class="o">&lt;</span> <span class="n">radius_m</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                    <span class="n">found_cluster</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="cl">                    <span class="n">cluster</span><span class="o">.</span><span class="n">append</span><span class="p">((</span><span class="n">i</span><span class="p">,</span> <span class="n">user_loc</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                    <span class="k">break</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="n">found_cluster</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">clusters</span><span class="p">[</span><span class="n">key</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">([(</span><span class="n">i</span><span class="p">,</span> <span class="n">user_loc</span><span class="p">)])</span>
</span></span><span class="line"><span class="cl">                
</span></span><span class="line"><span class="cl">    <span class="n">cluster_geometries</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="n">shg</span><span class="o">.</span><span class="n">MultiPoint</span><span class="p">([(</span><span class="n">point</span><span class="p">[</span><span class="mi">1</span><span class="p">][</span><span class="mi">1</span><span class="p">],</span> <span class="n">point</span><span class="p">[</span><span class="mi">1</span><span class="p">][</span><span class="mi">0</span><span class="p">])</span> <span class="k">for</span> <span class="n">point</span> <span class="ow">in</span> <span class="n">cluster</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">cluster_group</span> <span class="ow">in</span> <span class="n">clusters</span><span class="o">.</span><span class="n">values</span><span class="p">()</span> <span class="k">for</span> <span class="n">cluster</span> <span class="ow">in</span> <span class="n">cluster_group</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;found </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">cluster_geometries</span><span class="p">)</span><span class="si">}</span><span class="s1"> clusters&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="n">cluster_map</span> <span class="o">=</span> <span class="n">ll</span><span class="o">.</span><span class="n">Map</span><span class="p">(</span><span class="n">center</span><span class="o">=</span><span class="n">CENTER_OF_CZECHIA</span><span class="p">,</span> <span class="n">zoom</span><span class="o">=</span><span class="mi">7</span><span class="p">,</span> <span class="n">min_zoom</span><span class="o">=</span><span class="mi">7</span><span class="p">,</span> <span class="n">max_zoom</span><span class="o">=</span><span class="mi">19</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">cluster_map</span><span class="o">.</span><span class="n">add_control</span><span class="p">(</span><span class="n">ll</span><span class="o">.</span><span class="n">FullScreenControl</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="n">cluster_map</span><span class="o">.</span><span class="n">add_layer</span><span class="p">(</span><span class="n">ll</span><span class="o">.</span><span class="n">Polygon</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">locations</span><span class="o">=</span><span class="p">[</span><span class="n">polygon_to_ll</span><span class="p">(</span><span class="n">cluster</span><span class="o">.</span><span class="n">convex_hull</span><span class="p">)</span> <span class="k">for</span> <span class="n">cluster</span> <span class="ow">in</span> <span class="n">cluster_geometries</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="n">color</span><span class="o">=</span><span class="s2">&#34;green&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">fill_color</span><span class="o">=</span><span class="s2">&#34;green&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">cluster_map</span><span class="p">,</span><span class="n">cluster_geometries</span><span class="p">,</span><span class="n">clusters</span>
</span></span></code></pre></div><h3 id="visualizations">Visualizations</h3>
<p>First, we will compute the clusters by user <code>village</code>s.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">key_fn</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">user</span><span class="p">:</span> <span class="n">user</span><span class="p">[</span><span class="s1">&#39;village&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">cluster_map</span><span class="p">,</span> <span class="n">cluster_geometries</span><span class="p">,</span> <span class="n">clusters</span> <span class="o">=</span> <span class="n">plot_user_clusters_by</span><span class="p">(</span><span class="n">key_fn</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">cluster_map</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">found 6285 clusters
</span></span></code></pre></div><p>













<figure class="md-figure" >
  <div class="md-figure__inner" ><img alt="" srcset="
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe9160-c353-11ee-b012-5f1e1a82f88a_hu_ed1fe3b8f3def1ec.webp 400w,
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe9160-c353-11ee-b012-5f1e1a82f88a_hu_abaf873a4e7ee166.webp 760w,
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe9160-c353-11ee-b012-5f1e1a82f88a_hu_da26aac3803b36f8.webp 1446w"
          src="https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe9160-c353-11ee-b012-5f1e1a82f88a_hu_da26aac3803b36f8.webp"
          width="760"
          height="417"
          loading="lazy"
          class="md-img"
          style="background: linear-gradient(37.000000deg, #dee4d1b3, #dee4d100 75%),linear-gradient(157.000000deg, #098509b3, #09850900 75%),linear-gradient(277.000000deg, #057b04b3, #057b0400 75%); background-size: cover; background-repeat: no-repeat;" data-zoomable /></div></figure>
</p>
<p>6,285 clusters is a good amount, but the clusters are quite large. Let&rsquo;s compute clusters based on the user <code>village_part</code>s.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">key_fn</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">user</span><span class="p">:</span> <span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="n">user</span><span class="p">[</span><span class="s2">&#34;village&#34;</span><span class="p">]</span><span class="si">}</span><span class="s1">_</span><span class="si">{</span><span class="n">user</span><span class="p">[</span><span class="s2">&#34;village_part&#34;</span><span class="p">]</span><span class="si">}</span><span class="s1">&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">cluster_map</span><span class="p">,</span> <span class="n">cluster_geometries</span><span class="p">,</span> <span class="n">clusters</span> <span class="o">=</span> <span class="n">plot_user_clusters_by</span><span class="p">(</span><span class="n">key_fn</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">cluster_map</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">found 14728 clusters
</span></span></code></pre></div><p>













<figure class="md-figure" >
  <div class="md-figure__inner" ><img alt="" srcset="
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe9161-c353-11ee-b012-5f1e1a82f88a_hu_21904c591b9e5989.webp 400w,
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe9161-c353-11ee-b012-5f1e1a82f88a_hu_6b37acd15351efa.webp 760w,
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe9161-c353-11ee-b012-5f1e1a82f88a_hu_ee0b9f570508fb24.webp 1450w"
          src="https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe9161-c353-11ee-b012-5f1e1a82f88a_hu_ee0b9f570508fb24.webp"
          width="760"
          height="415"
          loading="lazy"
          class="md-img"
          style="background: linear-gradient(37.000000deg, #dee4d1b3, #dee4d100 75%),linear-gradient(157.000000deg, #078407b3, #07840700 75%),linear-gradient(277.000000deg, #057b05b3, #057b0500 75%); background-size: cover; background-repeat: no-repeat;" data-zoomable /></div></figure>
</p>
<p>15k is a good amount of clusters in terms of the number of requests we can issue to the Azure API, but the clusters are still quite large. Let&rsquo;s explore 500m radius clusters.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">cluser_map</span><span class="p">,</span> <span class="n">cluster_geometries</span><span class="p">,</span> <span class="n">clusters</span> <span class="o">=</span> <span class="n">plot_user_clusters_by</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="k">lambda</span> <span class="n">user</span><span class="p">:</span> <span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="n">user</span><span class="p">[</span><span class="s2">&#34;village&#34;</span><span class="p">]</span><span class="si">}</span><span class="s1">_</span><span class="si">{</span><span class="n">user</span><span class="p">[</span><span class="s2">&#34;village_part&#34;</span><span class="p">]</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">radius_m</span><span class="o">=</span><span class="mi">500</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">cluster_map</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">found 47199 clusters
</span></span></code></pre></div><p>













<figure class="md-figure" >
  <div class="md-figure__inner" ><img alt="" srcset="
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe9162-c353-11ee-b012-5f1e1a82f88a_hu_bfb227270c8ad3b7.webp 400w,
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe9162-c353-11ee-b012-5f1e1a82f88a_hu_bad64fd809a46724.webp 760w,
          https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe9162-c353-11ee-b012-5f1e1a82f88a_hu_98a247a751a764ee.webp 1452w"
          src="https://mikhalevi.ch/efficient-routing-on-a-scale-winning-solution-of-the-machine-learning-prague-2021-hackathon/images/44fe9162-c353-11ee-b012-5f1e1a82f88a_hu_98a247a751a764ee.webp"
          width="760"
          height="415"
          loading="lazy"
          class="md-img"
          style="background: linear-gradient(37.000000deg, #dee4d1b3, #dee4d100 75%),linear-gradient(157.000000deg, #078407b3, #07840700 75%),linear-gradient(277.000000deg, #057b05b3, #057b0500 75%); background-size: cover; background-repeat: no-repeat;" data-zoomable /></div></figure>
</p>
<p>These 47k clusters seem like the way to go for an efficient and precise solution.</p>
<h2 id="routing">Routing</h2>
<p>With $200USD in MS Azure credit, we can run only 40k queries against the routing API if we want to send more than 50 of them per second. And with 47k clusters, we would definitely need to send &gt; 40k requests.</p>
<p>To not sacrifice the precision and to iterate quickly, we set up a local instance of the <a href="http://project-osrm.org/" target="_blank" rel="noopener">open street routing machine</a>. What follows is the code we used to send routing requests to it.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">OSRM_URI</span> <span class="o">=</span> <span class="s1">&#39;http://127.0.0.1:5000&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">MAX_ATTEMPTS</span> <span class="o">=</span> <span class="mi">7</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">osrm_client</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">Session</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">request</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">attempt</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="n">osrm_client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span><span class="o">.</span><span class="n">raise_for_status</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">response</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">attempt</span> <span class="o">&gt;</span> <span class="n">MAX_ATTEMPTS</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">raise</span> <span class="n">e</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">request</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">attempt</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_route</span><span class="p">(</span><span class="n">lat1</span><span class="p">,</span> <span class="n">lon1</span><span class="p">,</span> <span class="n">lat2</span><span class="p">,</span> <span class="n">lon2</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="n">OSRM_URI</span><span class="si">}</span><span class="s1">/route/v1/driving/</span><span class="si">{</span><span class="n">lon1</span><span class="si">}</span><span class="s1">,</span><span class="si">{</span><span class="n">lat1</span><span class="si">}</span><span class="s1">;</span><span class="si">{</span><span class="n">lon2</span><span class="si">}</span><span class="s1">,</span><span class="si">{</span><span class="n">lat2</span><span class="si">}</span><span class="s1">?continue_straight=false&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="n">request</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">route</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()[</span><span class="s1">&#39;routes&#39;</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;[get_route] err&#39;</span><span class="p">,</span> <span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nb">float</span><span class="p">(</span><span class="s1">&#39;inf&#39;</span><span class="p">),</span> <span class="nb">float</span><span class="p">(</span><span class="s1">&#39;inf&#39;</span><span class="p">),</span> <span class="s1">&#39;&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">route</span><span class="p">[</span><span class="s1">&#39;distance&#39;</span><span class="p">],</span> <span class="n">route</span><span class="p">[</span><span class="s1">&#39;duration&#39;</span><span class="p">]</span> <span class="o">/</span> <span class="mi">60</span><span class="p">,</span> <span class="n">route</span><span class="p">[</span><span class="s1">&#39;geometry&#39;</span><span class="p">]</span>
</span></span></code></pre></div><p>Let&rsquo;s make sure that it works and get some sense of the performance.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">get_route</span><span class="p">(</span><span class="mf">50.077033696848886</span><span class="p">,</span> <span class="mf">14.440035214801874</span><span class="p">,</span> <span class="mf">50.070578695594314</span><span class="p">,</span> <span class="mf">14.494946221573803</span><span class="p">)</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">(5248, 7.403333333333333, &#39;evspH{icwAv@aK@{RkGsmDzIalBgBgt@b_@yCpBvL|ExKQtC&#39;)
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">%</span><span class="n">timeit</span> <span class="n">get_route</span><span class="p">(</span><span class="mf">50.077033696848886</span><span class="p">,</span> <span class="mf">14.440035214801874</span><span class="p">,</span> <span class="mf">50.070578695594314</span><span class="p">,</span> <span class="mf">14.494946221573803</span><span class="p">)</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">1.86 ms ± 109 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
</span></span></code></pre></div><h2 id="solution">Solution</h2>
<p>The solution is quite straightforward from that point on. But before we move on, let&rsquo;s import <a href="https://numpy.org/" target="_blank" rel="noopener">numpy</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
</span></span></code></pre></div><p>We will need a <del>fast</del>vectorized method for the haversine distance computation.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">sort_data_haversine</span><span class="p">(</span><span class="n">points</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">distances</span> <span class="o">=</span> <span class="n">haversine</span><span class="o">.</span><span class="n">haversine_vector</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span><span class="p">[</span><span class="s1">&#39;location&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">tolist</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="n">points</span><span class="p">[</span><span class="s1">&#39;location&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">tolist</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="n">unit</span><span class="o">=</span><span class="n">haversine</span><span class="o">.</span><span class="n">Unit</span><span class="o">.</span><span class="n">METERS</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">comb</span><span class="o">=</span><span class="kc">True</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">np</span><span class="o">.</span><span class="n">argsort</span><span class="p">(</span><span class="n">distances</span><span class="p">),</span> <span class="n">distances</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">index</span><span class="p">,</span> <span class="n">distances</span> <span class="o">=</span> <span class="n">sort_data_haversine</span><span class="p">(</span><span class="n">users</span><span class="p">[:</span><span class="mi">100</span><span class="p">],</span> <span class="n">parent_orgs</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">index</span><span class="p">,</span> <span class="n">distances</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">(array([[4989, 4992, 4911, ..., 3372, 3375, 3254],
</span></span><span class="line"><span class="cl">        [4989, 4992, 4911, ..., 3372, 3375, 3254],
</span></span><span class="line"><span class="cl">        [4989, 4992, 4911, ..., 3372, 3375, 3254],
</span></span><span class="line"><span class="cl">        ...,
</span></span><span class="line"><span class="cl">        [4743, 4803, 4799, ..., 3372, 3375, 3254],
</span></span><span class="line"><span class="cl">        [3362, 3146, 3320, ..., 3807, 3375, 3254],
</span></span><span class="line"><span class="cl">        [4827, 4803, 4799, ..., 3372, 3375, 3254]]),
</span></span><span class="line"><span class="cl"> array([[133852.36820813, 133508.21170191, 133514.27117984, ...,
</span></span><span class="line"><span class="cl">         138680.78292498, 134174.19127045, 140410.66063062],
</span></span><span class="line"><span class="cl">        [133888.2414056 , 133543.99335324, 133550.20998843, ...,
</span></span><span class="line"><span class="cl">         138626.18005503, 134154.00442703, 140355.03132468],
</span></span><span class="line"><span class="cl">        [134038.57941885, 133694.36931002, 133700.5206838 , ...,
</span></span><span class="line"><span class="cl">         138486.67124833, 133999.0278078 , 140216.3122906 ],
</span></span><span class="line"><span class="cl">        ...,
</span></span><span class="line"><span class="cl">        [132402.65539979, 132050.23378999, 132072.18742985, ...,
</span></span><span class="line"><span class="cl">         138917.42961128, 137858.06810736, 140528.10016674],
</span></span><span class="line"><span class="cl">        [109703.4194728 , 109348.16998337, 109376.71678102, ...,
</span></span><span class="line"><span class="cl">         161563.91234588, 160672.22237329, 163126.36755815],
</span></span><span class="line"><span class="cl">        [134407.99756303, 134053.95329282, 134079.57745343, ...,
</span></span><span class="line"><span class="cl">         136852.37604113, 136692.76736826, 138434.94454964]]))
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">%</span><span class="n">timeit</span> <span class="n">sort_data_haversine</span><span class="p">(</span><span class="n">users</span><span class="p">[:</span><span class="mi">100</span><span class="p">],</span> <span class="n">parent_orgs</span><span class="p">)</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">75.7 ms ± 63.3 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
</span></span></code></pre></div><p>75ms to compute <code>100*11k</code> distances is pretty good.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">ORG_TYPES</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">orgs</span><span class="p">[</span><span class="s1">&#39;contractual_specialty_id&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">unique</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="nb">len</span><span class="p">(</span><span class="n">ORG_TYPES</span><span class="p">)</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">274
</span></span></code></pre></div><p>Next, we will create a method to find solutions for each of the clusters. To be performant, we look for all types of clinics in a single pass over a sorted list of candidates. It&rsquo;s hard to vectorize the method, so we run it in multiple threads.</p>
<p>Notice that the solution incorporates our knowledge that there are some clinics of the same type that share the exact location. When we encounter this, we randomly distribute users between such clinics to provide better coverage.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">concurrent.futures</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">random</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">pool</span> <span class="o">=</span> <span class="n">concurrent</span><span class="o">.</span><span class="n">futures</span><span class="o">.</span><span class="n">ProcessPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">find_closest_orgs_for_point</span><span class="p">(</span><span class="n">point</span><span class="p">,</span> <span class="n">index</span><span class="p">,</span> <span class="n">distances</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">org_types_left</span> <span class="o">=</span> <span class="n">ORG_TYPES</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">index</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">org_types_left</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span>
</span></span><span class="line"><span class="cl">        <span class="n">parent_org</span> <span class="o">=</span> <span class="n">parent_orgs</span><span class="o">.</span><span class="n">iloc</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="n">haversine_distance</span> <span class="o">=</span> <span class="n">distances</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="n">route</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">specialty_department</span> <span class="ow">in</span> <span class="p">(</span><span class="n">sd</span> <span class="k">for</span> <span class="n">sd</span> <span class="ow">in</span> <span class="n">parent_org</span><span class="p">[</span><span class="s1">&#39;specialty_department&#39;</span><span class="p">]</span> <span class="k">if</span> <span class="n">sd</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="ow">in</span> <span class="n">org_types_left</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="n">specialty</span> <span class="o">=</span> <span class="n">specialty_department</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">            <span class="n">best_route</span> <span class="o">=</span> <span class="n">result</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">specialty</span><span class="p">,</span> <span class="p">((</span><span class="nb">float</span><span class="p">(</span><span class="s1">&#39;inf&#39;</span><span class="p">),),))[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">haversine_distance</span> <span class="o">&gt;</span> <span class="n">best_route</span><span class="p">[</span><span class="mi">0</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">                <span class="n">org_types_left</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">specialty</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="n">route</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">route</span> <span class="o">=</span> <span class="n">get_route</span><span class="p">(</span><span class="o">*</span><span class="n">point</span><span class="p">[</span><span class="s1">&#39;location&#39;</span><span class="p">],</span> <span class="o">*</span><span class="n">parent_org</span><span class="p">[</span><span class="s1">&#39;location&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">route</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">best_route</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span> <span class="ow">or</span> <span class="p">(</span><span class="n">route</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="n">best_route</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="ow">and</span> <span class="n">random</span><span class="o">.</span><span class="n">random</span><span class="p">()</span> <span class="o">&lt;</span> <span class="mf">.5</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">                <span class="n">result</span><span class="p">[</span><span class="n">specialty</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="n">route</span><span class="p">,</span> <span class="n">specialty_department</span><span class="p">,</span> <span class="n">parent_org</span><span class="p">[</span><span class="s1">&#39;location&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">_wrapper</span><span class="p">(</span><span class="n">args</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">find_closest_orgs_for_point</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">find_closest</span><span class="p">(</span><span class="n">points</span><span class="p">,</span> <span class="n">orgs</span><span class="p">:</span> <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">index</span><span class="p">,</span> <span class="n">distances</span> <span class="o">=</span> <span class="n">sort_data_haversine</span><span class="p">(</span><span class="n">points</span><span class="p">,</span> <span class="n">parent_orgs</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">list</span><span class="p">(</span><span class="n">pool</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">_wrapper</span><span class="p">,</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">points</span><span class="o">.</span><span class="n">iloc</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">index</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">distances</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">index</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">]))</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">users_res</span> <span class="o">=</span> <span class="n">find_closest</span><span class="p">(</span><span class="n">users</span><span class="p">[:</span><span class="mi">1</span><span class="p">],</span> <span class="n">parent_orgs</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">list</span><span class="p">(</span><span class="n">users_res</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">values</span><span class="p">())[:</span><span class="mi">5</span><span class="p">]</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[((3473.6,
</span></span><span class="line"><span class="cl">   5.298333333333333,
</span></span><span class="line"><span class="cl">   &#39;_d`oHyxjwAK{@g@a@qA|BeFvCYCSs@pCkGjBcIJsEw@cKZgGz@qAbCkAv@uBXg_@i@iFaCoFiBkJ{GaPg@aDiBq{@cCyVo@iL?qJv@gGZDAj@&#39;),
</span></span><span class="line"><span class="cl">  (&#39;014&#39;, 20216001.0),
</span></span><span class="line"><span class="cl">  (49.816801772, 14.517831226)),
</span></span><span class="line"><span class="cl"> ((3473.6,
</span></span><span class="line"><span class="cl">   5.298333333333333,
</span></span><span class="line"><span class="cl">   &#39;_d`oHyxjwAK{@g@a@qA|BeFvCYCSs@pCkGjBcIJsEw@cKZgGz@qAbCkAv@uBXg_@i@iFaCoFiBkJ{GaPg@aDiBq{@cCyVo@iL?qJv@gGZDAj@&#39;),
</span></span><span class="line"><span class="cl">  (&#39;002&#39;, 20412001.0),
</span></span><span class="line"><span class="cl">  (49.816801772, 14.517831226)),
</span></span><span class="line"><span class="cl"> ((3476.6,
</span></span><span class="line"><span class="cl">   5.341666666666667,
</span></span><span class="line"><span class="cl">   &#39;_d`oHyxjwAK{@g@a@qA|BeFvCYCSs@pCkGjBcIJsEw@cKZgGz@qAbCkAv@uBXg_@i@iFaCoFiBkJ{GaPg@aDiBq{@cCyVo@iLAaJx@wG}@R&#39;),
</span></span><span class="line"><span class="cl">  (&#39;902&#39;, 20721001.0),
</span></span><span class="line"><span class="cl">  (49.817220095, 14.517841305)),
</span></span><span class="line"><span class="cl"> ((16488.6,
</span></span><span class="line"><span class="cl">   28.763333333333332,
</span></span><span class="line"><span class="cl">   &#39;_d`oHyxjwAFrXf@|@rAIhBkKx@w@tDrC~GnVpOkD|EkCSzSqBpF`BzM~BtB~Kh`@|JzQvCn@nBkAwJt\\oJ|LMnAh@xC~EnE|A|DfKxY`ArG[hEuCzH~@lLkBnDwBzAuEmA}AlHwN|BoMsNoBMiAzAIdDzCfNYpGyHfI{JpC}HtFkF\\_VuDuR|@gEsCiCiH~@gM[sC_LeEcDpHm@|UXfH_@bBkExDQtCz@x@jGQxFzBfKfAbDfCvBpEEtCcDdObDvLAh\\t@xOxGtc@xLjd@l@hFXwChFaAbH}HvBqMzE{GdC_H`De@zAjHxC`AfLaGRwHnAoG}AgELmAnDb@`CpF|NdLvS?rCuIxEwEZwR{AiKp@eCh@g_@uC{WaG_V&#39;),
</span></span><span class="line"><span class="cl">  (&#39;709&#39;, 22107293.0),
</span></span><span class="line"><span class="cl">  (49.800521335, 14.42696598)),
</span></span><span class="line"><span class="cl"> ((16488.6,
</span></span><span class="line"><span class="cl">   28.763333333333332,
</span></span><span class="line"><span class="cl">   &#39;_d`oHyxjwAFrXf@|@rAIhBkKx@w@tDrC~GnVpOkD|EkCSzSqBpF`BzM~BtB~Kh`@|JzQvCn@nBkAwJt\\oJ|LMnAh@xC~EnE|A|DfKxY`ArG[hEuCzH~@lLkBnDwBzAuEmA}AlHwN|BoMsNoBMiAzAIdDzCfNYpGyHfI{JpC}HtFkF\\_VuDuR|@gEsCiCiH~@gM[sC_LeEcDpHm@|UXfH_@bBkExDQtCz@x@jGQxFzBfKfAbDfCvBpEEtCcDdObDvLAh\\t@xOxGtc@xLjd@l@hFXwChFaAbH}HvBqMzE{GdC_H`De@zAjHxC`AfLaGRwHnAoG}AgELmAnDb@`CpF|NdLvS?rCuIxEwEZwR{AiKp@eCh@g_@uC{WaG_V&#39;),
</span></span><span class="line"><span class="cl">  (&#39;799&#39;, 22107493.0),
</span></span><span class="line"><span class="cl">  (49.800521335, 14.42696598))]
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">%</span><span class="n">timeit</span> <span class="o">-</span><span class="n">n1</span> <span class="o">-</span><span class="n">r1</span> <span class="n">find_closest</span><span class="p">(</span><span class="n">users</span><span class="p">[:</span><span class="mi">100</span><span class="p">],</span> <span class="n">parent_orgs</span><span class="p">)</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">33.2 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
</span></span></code></pre></div><p>Our implementation can find the solution for 100 users in 33 seconds. We are ready to run it on user clusters, and before we do, we have to compute the centroids of the clusters.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">clusters_flat</span> <span class="o">=</span> <span class="p">[</span><span class="n">cluster</span> <span class="k">for</span> <span class="n">cluster_group</span> <span class="ow">in</span> <span class="n">clusters</span><span class="o">.</span><span class="n">values</span><span class="p">()</span> <span class="k">for</span> <span class="n">cluster</span> <span class="ow">in</span> <span class="n">cluster_group</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">clusters_restructured</span> <span class="o">=</span> <span class="p">[</span><span class="nb">list</span><span class="p">(</span><span class="nb">zip</span><span class="p">(</span><span class="o">*</span><span class="n">cluster</span><span class="p">))</span> <span class="k">for</span> <span class="n">cluster</span> <span class="ow">in</span> <span class="n">clusters_flat</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">clusters_geo</span> <span class="o">=</span> <span class="n">gpd</span><span class="o">.</span><span class="n">GeoDataFrame</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">clusters_restructured</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">crs</span><span class="o">=</span><span class="s1">&#39;EPSG:4326&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">columns</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;users&#39;</span><span class="p">,</span> <span class="s1">&#39;locations&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="n">geometry</span><span class="o">=</span><span class="n">cluster_geometries</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">clusters_geo</span><span class="o">.</span><span class="n">head</span><span class="p">()</span>
</span></span></code></pre></div><table>
	<thead>
			<tr>
					<th></th>
					<th>users</th>
					<th>locations</th>
					<th>geometry</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>0</td>
					<td>(0, 1, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16, 1&hellip;</td>
					<td>((49.813014079, 14.478449504), (49.813251371, &hellip;</td>
					<td>MULTIPOINT (14.47845 49.81301, 14.47990 49.813&hellip;</td>
			</tr>
			<tr>
					<td>1</td>
					<td>(4,)</td>
					<td>((49.814779761, 14.469590894),)</td>
					<td>MULTIPOINT (14.46959 49.81478)</td>
			</tr>
			<tr>
					<td>2</td>
					<td>(13,)</td>
					<td>((49.810529999, 14.460041926),)</td>
					<td>MULTIPOINT (14.46004 49.81053)</td>
			</tr>
			<tr>
					<td>3</td>
					<td>(11629, 11650, 11659, 11673, 11724, 11729, 117&hellip;</td>
					<td>((49.98905532, 14.203808164), (49.993207327, 1&hellip;</td>
					<td>MULTIPOINT (14.20381 49.98906, 14.20444 49.993&hellip;</td>
			</tr>
			<tr>
					<td>4</td>
					<td>(11642, 11653, 11685, 11699, 11714, 11726, 117&hellip;</td>
					<td>((49.993481837, 14.21378659), (49.993255048, 1&hellip;</td>
					<td>MULTIPOINT (14.21379 49.99348, 14.21489 49.993&hellip;</td>
			</tr>
	</tbody>
</table>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">cluster_centroids</span> <span class="o">=</span> <span class="n">clusters_geo</span><span class="o">.</span><span class="n">centroid</span><span class="o">.</span><span class="n">to_frame</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">cluster_centroids</span><span class="p">[</span><span class="s1">&#39;location&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">cluster_centroids</span><span class="o">.</span><span class="n">applymap</span><span class="p">(</span><span class="k">lambda</span> <span class="n">centroid</span><span class="p">:</span> <span class="p">(</span><span class="n">centroid</span><span class="o">.</span><span class="n">y</span><span class="p">,</span> <span class="n">centroid</span><span class="o">.</span><span class="n">x</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">cluster_centroids</span><span class="o">.</span><span class="n">head</span><span class="p">()</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">&lt;ipython-input-56-c0091e7f21d9&gt;:1: UserWarning: Geometry is in a geographic CRS. Results from &#39;centroid&#39; are likely incorrect. Use &#39;GeoSeries.to_crs()&#39; to re-project geometries to a projected CRS before this operation.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  cluster_centroids = clusters_geo.centroid.to_frame()
</span></span></code></pre></div><table>
	<thead>
			<tr>
					<th></th>
					<th>0</th>
					<th>location</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>0</td>
					<td>POINT (14.47861 49.81427)</td>
					<td>(49.81426659066666, 14.478608245733335)</td>
			</tr>
			<tr>
					<td>1</td>
					<td>POINT (14.46959 49.81478)</td>
					<td>(49.814779761, 14.469590894)</td>
			</tr>
			<tr>
					<td>2</td>
					<td>POINT (14.46004 49.81053)</td>
					<td>(49.810529999, 14.460041926)</td>
			</tr>
			<tr>
					<td>3</td>
					<td>POINT (14.20518 49.99149)</td>
					<td>(49.99148887917857, 14.205177951250004)</td>
			</tr>
			<tr>
					<td>4</td>
					<td>POINT (14.21365 49.99239)</td>
					<td>(49.99238546947369, 14.21365326868421)</td>
			</tr>
	</tbody>
</table>
<p>Once they are computed, we are ready to process all of the clusters.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">BATCH_SIZE</span> <span class="o">=</span> <span class="mi">1000</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">cluster_results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">total_iterations</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">ceil</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">cluster_centroids</span><span class="o">.</span><span class="n">index</span><span class="p">)</span> <span class="o">/</span> <span class="n">BATCH_SIZE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">progress_bar</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">cluster_centroids</span><span class="o">.</span><span class="n">index</span><span class="p">),</span> <span class="n">BATCH_SIZE</span><span class="p">),</span> <span class="n">total</span><span class="o">=</span><span class="n">total_iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">batch_results</span> <span class="o">=</span> <span class="n">find_closest</span><span class="p">(</span><span class="n">cluster_centroids</span><span class="p">[</span><span class="n">i</span><span class="p">:</span><span class="n">i</span><span class="o">+</span><span class="n">BATCH_SIZE</span><span class="p">],</span> <span class="n">parent_orgs</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">cluster_results</span> <span class="o">+=</span> <span class="n">batch_results</span>
</span></span></code></pre></div><p>Running the solution on 47k clusters took 3 hours and 40 minutes, a time we are quite happy with.</p>
<p>The last thing left is writing the output CSV. Before we proceed, let&rsquo;s remind ourselves how it should look like.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">OUTPUT_COLUMNS</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[&#39;policyholder_num&#39;,
</span></span><span class="line"><span class="cl"> &#39;contractual_specialty_id&#39;,
</span></span><span class="line"><span class="cl"> &#39;pzs_department_id&#39;,
</span></span><span class="line"><span class="cl"> &#39;km_to_pzs&#39;,
</span></span><span class="line"><span class="cl"> &#39;min_to_pzs&#39;,
</span></span><span class="line"><span class="cl"> &#39;zzs_department_id&#39;,
</span></span><span class="line"><span class="cl"> &#39;km_to_zzs&#39;,
</span></span><span class="line"><span class="cl"> &#39;min_to_zzs&#39;]
</span></span></code></pre></div><p>Because we really care about precision, we will adjust distance and driving times for each user based on the ratio between <code>user</code>⇔<code>clinic</code> and <code>cluster centroid</code>⇔<code>clinic</code> straight line distances.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">csv</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">M_IN_KM</span> <span class="o">=</span> <span class="mi">1000</span>
</span></span><span class="line"><span class="cl"><span class="n">SPEED</span> <span class="o">=</span> <span class="mi">833</span>  <span class="c1"># meters per hour (50kmh), used only when centroid2org distance is 0 (rare)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">adjust_route</span><span class="p">(</span><span class="n">route</span><span class="p">,</span> <span class="n">user_location</span><span class="p">,</span> <span class="n">centroid_location</span><span class="p">,</span> <span class="n">org_location</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">centroid2org_distance</span> <span class="o">=</span> <span class="n">haversine</span><span class="o">.</span><span class="n">haversine</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">centroid_location</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">org_location</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">haversine</span><span class="o">.</span><span class="n">Unit</span><span class="o">.</span><span class="n">METERS</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">user2org_distance</span> <span class="o">=</span> <span class="n">haversine</span><span class="o">.</span><span class="n">haversine</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">user_location</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">org_location</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">haversine</span><span class="o">.</span><span class="n">Unit</span><span class="o">.</span><span class="n">METERS</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">centroid2org_distance</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">user2org_distance</span><span class="p">,</span> <span class="n">user2org_distance</span> <span class="o">/</span> <span class="n">SPEED</span><span class="p">,</span> <span class="n">route</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">ratio</span> <span class="o">=</span> <span class="n">user2org_distance</span> <span class="o">/</span> <span class="n">centroid2org_distance</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">route</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="n">ratio</span><span class="p">,</span> <span class="n">route</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">*</span> <span class="n">ratio</span><span class="p">,</span> <span class="n">route</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s1">&#39;./output.csv&#39;</span><span class="p">,</span> <span class="s1">&#39;w&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">file</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">writer</span> <span class="o">=</span> <span class="n">csv</span><span class="o">.</span><span class="n">writer</span><span class="p">(</span><span class="n">file</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">writer</span><span class="o">.</span><span class="n">writerow</span><span class="p">(</span><span class="n">OUTPUT_COLUMNS</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">cluster_i</span><span class="p">,</span> <span class="n">cluster</span> <span class="ow">in</span> <span class="n">progress_bar</span><span class="p">(</span><span class="n">clusters_geo</span><span class="o">.</span><span class="n">iterrows</span><span class="p">(),</span> <span class="n">total</span><span class="o">=</span><span class="nb">len</span><span class="p">(</span><span class="n">clusters_geo</span><span class="o">.</span><span class="n">index</span><span class="p">)):</span>
</span></span><span class="line"><span class="cl">        <span class="n">cluster_rows</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">        <span class="n">ast_route</span><span class="p">,</span> <span class="n">ast_org</span><span class="p">,</span> <span class="n">ast_location</span> <span class="o">=</span> <span class="n">cluster_results</span><span class="p">[</span><span class="n">cluster_i</span><span class="p">][</span><span class="s1">&#39;AST&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="n">centroid_location</span> <span class="o">=</span> <span class="n">cluster_centroids</span><span class="o">.</span><span class="n">iloc</span><span class="p">[</span><span class="n">cluster_i</span><span class="p">][</span><span class="s1">&#39;location&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">user_i</span> <span class="ow">in</span> <span class="n">cluster</span><span class="p">[</span><span class="s1">&#39;users&#39;</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">            <span class="n">user</span> <span class="o">=</span> <span class="n">users</span><span class="o">.</span><span class="n">iloc</span><span class="p">[</span><span class="n">user_i</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">            <span class="n">user_loc</span> <span class="o">=</span> <span class="n">user</span><span class="p">[</span><span class="s1">&#39;location&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">            <span class="n">adj_ast_route</span> <span class="o">=</span> <span class="n">adjust_route</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">ast_route</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">user_loc</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">centroid_location</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">ast_location</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="n">cluster_result</span> <span class="ow">in</span> <span class="n">cluster_results</span><span class="p">[</span><span class="n">cluster_i</span><span class="p">]</span><span class="o">.</span><span class="n">values</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">                <span class="n">route</span><span class="p">,</span> <span class="n">clinic</span><span class="p">,</span> <span class="n">clinic_location</span> <span class="o">=</span> <span class="n">cluster_result</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="n">clinic</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="s1">&#39;AST&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                    <span class="k">continue</span>
</span></span><span class="line"><span class="cl">                <span class="n">adj_route</span> <span class="o">=</span> <span class="n">adjust_route</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="n">route</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">user_loc</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">centroid_location</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">clinic_location</span>
</span></span><span class="line"><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">cluster_rows</span><span class="o">.</span><span class="n">append</span><span class="p">([</span>
</span></span><span class="line"><span class="cl">                    <span class="n">user</span><span class="p">[</span><span class="s1">&#39;policyholder_num&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                    <span class="n">clinic</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                    <span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="nb">int</span><span class="p">(</span><span class="n">clinic</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span><span class="si">:</span><span class="s1">08d</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">adj_route</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">/</span> <span class="n">M_IN_KM</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">adj_route</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                    <span class="sa">f</span><span class="s1">&#39;</span><span class="si">{</span><span class="nb">int</span><span class="p">(</span><span class="n">ast_org</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span><span class="si">:</span><span class="s1">08d</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">adj_ast_route</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">/</span> <span class="n">M_IN_KM</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">adj_ast_route</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="p">])</span>
</span></span><span class="line"><span class="cl">        <span class="n">writer</span><span class="o">.</span><span class="n">writerows</span><span class="p">(</span><span class="n">cluster_rows</span><span class="p">)</span>
</span></span></code></pre></div><p>As you can see, we came up with an elegant, efficient, and precise solution by thoroughly analyzing the data, clustering the users, setting up the local routing machine, vectorizing and parallelizing the computations, and approximating final driving distances and times for each of the users individually.</p>
<h2 id="result-visualization">Result visualization</h2>
<p>Let us conclude by plotting the results for a few clusters.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">polyline</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">CLUSTER_COLORS</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;green&#39;</span><span class="p">,</span> <span class="s1">&#39;red&#39;</span><span class="p">,</span> <span class="s1">&#39;orange&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">routes_map</span> <span class="o">=</span> <span class="n">ll</span><span class="o">.</span><span class="n">Map</span><span class="p">(</span><span class="n">center</span><span class="o">=</span><span class="n">CENTER_OF_CZECHIA</span><span class="p">,</span> <span class="n">zoom</span><span class="o">=</span><span class="mi">7</span><span class="p">,</span> <span class="n">min_zoom</span><span class="o">=</span><span class="mi">7</span><span class="p">,</span> <span class="n">max_zoom</span><span class="o">=</span><span class="mi">19</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">routes_map</span><span class="o">.</span><span class="n">add_control</span><span class="p">(</span><span class="n">ll</span><span class="o">.</span><span class="n">FullScreenControl</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">CLUSTER_COLORS</span><span class="p">)):</span>
</span></span><span class="line"><span class="cl">    <span class="n">cluster_color</span> <span class="o">=</span> <span class="n">CLUSTER_COLORS</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">cluster_result</span> <span class="o">=</span> <span class="n">cluster_results</span><span class="p">[</span><span class="n">i</span> <span class="o">*</span> <span class="mi">10000</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">j</span><span class="p">,</span> <span class="n">specialty_result</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">cluster_result</span><span class="o">.</span><span class="n">items</span><span class="p">()):</span>
</span></span><span class="line"><span class="cl">        <span class="n">_</span><span class="p">,</span> <span class="n">result</span> <span class="o">=</span> <span class="n">specialty_result</span>
</span></span><span class="line"><span class="cl">        <span class="n">route</span> <span class="o">=</span> <span class="n">polyline</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="n">result</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">2</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">        <span class="n">routes_map</span><span class="o">.</span><span class="n">add_layer</span><span class="p">(</span><span class="n">ll</span><span class="o">.</span><span class="n">Polyline</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">locations</span><span class="o">=</span><span class="n">route</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">color</span><span class="o">=</span><span class="n">cluster_color</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">fill</span><span class="o">=</span><span class="kc">False</span>
</span></span><span class="line"><span class="cl">        <span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="n">routes_map</span><span class="o">.</span><span class="n">add_layer</span><span class="p">(</span><span class="n">ll</span><span class="o">.</span><span class="n">Marker</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">location</span><span class="o">=</span><span class="n">route</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="n">icon</span><span class="o">=</span><span class="n">ll</span><span class="o">.</span><span class="n">AwesomeIcon</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">&#39;hospital-o&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">draggable</span><span class="o">=</span><span class="kc">False</span>
</span></span><span class="line"><span class="cl">        <span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">j</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">routes_map</span><span class="o">.</span><span class="n">add_layer</span><span class="p">(</span><span class="n">ll</span><span class="o">.</span><span class="n">Marker</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">location</span><span class="o">=</span><span class="n">route</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="n">icon</span><span class="o">=</span><span class="n">ll</span><span class="o">.</span><span class="n">AwesomeIcon</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">&#39;users&#39;</span><span class="p">,</span> <span class="n">marker_color</span><span class="o">=</span><span class="n">cluster_color</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">draggable</span><span class="o">=</span><span class="kc">False</span>
</span></span><span class="line"><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl"><span class="n">routes_map</span>
</span></span></code></pre></div><iframe src="https://mikhalevi.ch/content/html/2021-ml-prague-hackathon-results-map.html" style="width: 100%; height: 404px"></iframe>
<p>This map is interactive, and results for each of the clusters are color-coded; feel free to explore it. I hope that you&rsquo;ve enjoyed this article! If you have any thoughts about it, please, reach out to <a href="https://twitter.com/theyurij" target="_blank" rel="noopener">me on Twitter</a>.</p>
<p>I want to end by saying thank you to the ML Prague Conference, SAS, and all other sponsors for the hackathon! I had a lot of fun and am already looking forward to next year! :)</p>
 ]]></content:encoded></item><item><title> Running tfjs-react-native in Android Emulator </title><description><![CDATA[ <p>You are developing a react-native app with Tensorflow JS? Then you may have tried using the <code>tfjs-react-native</code> backend in Android SDK Emulator only to find that it doesn&rsquo;t work.</p>
<p>This happens because <code>tfjs-react-native</code> requires GLES 3 / WebGL 2. You can enable it in Android SDK Emulator via:</p> ]]></description><link>https://mikhalevi.ch/running-tfjs-react-native-in-android-emulator/</link><guid>https://mikhalevi.ch/running-tfjs-react-native-in-android-emulator/</guid><category> Software Engineering </category><dc:creator> Yurij Mikhalevich </dc:creator><pubDate>Sat, 13 Mar 2021 18:16:00 +0000</pubDate><media:content url="https://mikhalevi.ch/running-tfjs-react-native-in-android-emulator/featured_hu_4dd701c5f59ba4d0.webp" medium="image"/><content:encoded><![CDATA[ <img src="https://mikhalevi.ch/running-tfjs-react-native-in-android-emulator/featured_hu_4dd701c5f59ba4d0.webp" alt="Running tfjs-react-native in Android Emulator"><p>You are developing a react-native app with Tensorflow JS? Then you may have tried using the <code>tfjs-react-native</code> backend in Android SDK Emulator only to find that it doesn&rsquo;t work.</p>
<p>This happens because <code>tfjs-react-native</code> requires GLES 3 / WebGL 2. You can enable it in Android SDK Emulator via:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;GLESDynamicVersion = on&#34;</span> &gt;&gt; ~/.android/advancedFeatures.ini
</span></span></code></pre></div><p>I tested this only under Linux. Your host GPU should support at least OpenGL ES 3.2; you can check if it does via:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo apt install mesa-utils
</span></span><span class="line"><span class="cl">glxinfo <span class="p">|</span> grep <span class="s1">&#39;version.*OpenGL&#39;</span>
</span></span></code></pre></div> ]]></content:encoded></item><item><title> A good place to get started with AI and neural nets </title><description><![CDATA[ <p>People often ask me for a good place to get started with AI, machine learning, and neural nets.</p>
<p><a href="https://course.fast.ai/" target="_blank" rel="noopener">course.fast.ai</a> — this is a pretty good place. It&rsquo;s modern (the current version of the course was released this August), centered around the relevant practice, teaches you by doing, going deeper only when needed, and it goes with the book that was released this August, too. Overall, it&rsquo;s a pretty good introduction to the field.</p> ]]></description><link>https://mikhalevi.ch/a-good-place-to-get-started-with-ai-and-neural-nets/</link><guid>https://mikhalevi.ch/a-good-place-to-get-started-with-ai-and-neural-nets/</guid><category> Software Engineering </category><dc:creator> Yurij Mikhalevich </dc:creator><pubDate>Wed, 16 Dec 2020 14:56:00 +0000</pubDate><media:content url="https://mikhalevi.ch/a-good-place-to-get-started-with-ai-and-neural-nets/featured_hu_c2ab086543a3fbd9.webp" medium="image"/><content:encoded><![CDATA[ <img src="https://mikhalevi.ch/a-good-place-to-get-started-with-ai-and-neural-nets/featured_hu_c2ab086543a3fbd9.webp" alt="A good place to get started with AI and neural nets"><p>People often ask me for a good place to get started with AI, machine learning, and neural nets.</p>
<p><a href="https://course.fast.ai/" target="_blank" rel="noopener">course.fast.ai</a> — this is a pretty good place. It&rsquo;s modern (the current version of the course was released this August), centered around the relevant practice, teaches you by doing, going deeper only when needed, and it goes with the book that was released this August, too. Overall, it&rsquo;s a pretty good introduction to the field.</p>
<p>There are also good courses from Andrew Ng on Coursera (his Deep Learning specialization is great), but, in my personal opinion, the fast.ai course is a better introduction to hands-on AI in the modern world.</p>
<p>In conclusion, I&rsquo;ll recommend you to do both fast.ai and Andrew Ng&rsquo;s courses because each of them offers a different perspective. The good idea is to top it all up with some practice that you can find on <a href="https://kaggle.com/" target="_blank" rel="noopener">Kaggle</a> (they also offer some courses on Kaggle Learn, I&rsquo;m not familiar with those, though).</p>
 ]]></content:encoded></item></channel></rss>