<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.3.3">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-08-27T12:49:51+00:00</updated><id>/feed.xml</id><title type="html">Farisayi Dakwa</title><subtitle>Portfolio website/blog for all things data. 
</subtitle><author><name>Farisayi Dakwa</name><email>fariedakwa.fd@gmail.com</email></author><entry><title type="html">GeoAI Aquaculture Pond Identification using Satellite data (Sentinel-1 + Sentinel-2 bands)</title><link href="/blog/Aquaculture-Pond-Identification/" rel="alternate" type="text/html" title="GeoAI Aquaculture Pond Identification using Satellite data (Sentinel-1 + Sentinel-2 bands)" /><published>2026-08-24T00:00:00+00:00</published><updated>2026-08-27T12:45:49+00:00</updated><id>/blog/Aquaculture-Pond-Identification</id><content type="html" xml:base="/blog/Aquaculture-Pond-Identification/"><![CDATA[<p><strong>Failure is an option here. If things are not failing, you are not innovating enough</strong> ~ <em>Elon Musk</em>.</p>

<p><strong>Complete Jupyter Notebook</strong> - <a href="https://github.com/fariedd/GeoAI-Aquaculture-Pond-Identification-Zindi-Competition/blob/main/spectra_bands_prediction_clean.ipynb" target="_blank"><img src="https://img.shields.io/badge/GitHub-View_in_GitHub-blue?logo=GitHub" alt="" /></a></p>

<ul id="markdown-toc">
  <li><a href="#introduction" id="markdown-toc-introduction">Introduction</a>    <ul>
      <li><a href="#how-it-was-evaluated" id="markdown-toc-how-it-was-evaluated">How it was evaluated</a></li>
      <li><a href="#the-problem-statement" id="markdown-toc-the-problem-statement">The problem statement</a></li>
    </ul>
  </li>
  <li><a href="#data--approach" id="markdown-toc-data--approach">Data &amp; Approach</a>    <ul>
      <li><a href="#reshaping-to-long-format" id="markdown-toc-reshaping-to-long-format">Reshaping to long format</a></li>
      <li><a href="#feature-engineering--teaching-the-model-what-water-looks-like" id="markdown-toc-feature-engineering--teaching-the-model-what-water-looks-like">Feature engineering — teaching the model what water looks like</a></li>
      <li><a href="#handling-the-missing-months-honestly" id="markdown-toc-handling-the-missing-months-honestly">Handling the missing months honestly</a></li>
      <li><a href="#a-radar-only-model-for-cloud-obscured-months" id="markdown-toc-a-radar-only-model-for-cloud-obscured-months">A radar-only model for cloud-obscured months</a></li>
      <li><a href="#preventing-leakage-in-the-split" id="markdown-toc-preventing-leakage-in-the-split">Preventing leakage in the split</a></li>
      <li><a href="#aggregating-months-to-a-per-pixel-decision" id="markdown-toc-aggregating-months-to-a-per-pixel-decision">Aggregating months to a per-pixel decision</a></li>
    </ul>
  </li>
  <li><a href="#models" id="markdown-toc-models">Models</a></li>
  <li><a href="#results" id="markdown-toc-results">Results</a>    <ul>
      <li><a href="#validation-per-pixel-on-the-held-out-grouped-split" id="markdown-toc-validation-per-pixel-on-the-held-out-grouped-split">Validation (per-pixel, on the held-out grouped split)</a></li>
      <li><a href="#blind-competition-test-ragged-missing-months-data" id="markdown-toc-blind-competition-test-ragged-missing-months-data">Blind competition test (ragged, missing months data)</a></li>
      <li><a href="#what-i-confirmed-along-the-way" id="markdown-toc-what-i-confirmed-along-the-way">What I confirmed along the way</a></li>
    </ul>
  </li>
  <li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
</ul>

<h2 id="introduction">Introduction</h2>

<p>Aquaculture ponds are used to farm fish and shrimp in controlled water environments. They are easier to manage and more productive than open-water fishing, but to monitor them effectively we first need to know where they are. Satellite imagery offers a reliable way to map these ponds across large areas, supporting water management, environmental monitoring, and policy decisions, especially in regions where ground-level data is hard to come by.</p>

<p>This project was my entry to the <a href="https://zindi.world/competitions/geoai-aquaculture-pond-identification-challenge/leaderboard">Zindi GeoAI Aquaculture Pond Identification Challenge</a>. The goal was to build a machine learning model that predicts whether a given location is an aquaculture pond or another type of land cover, using features derived from satellite data. Each location represents a <strong>10m × 10m patch of ground</strong>, so the model needs to be precise.</p>

<p>The challenge came with a deliberate challenge: the model is <strong>trained on data from one time period and tested on data from a different one</strong>. The solution has to stay accurate as conditions change across seasons and years, not just perform well on familiar data, but perform well on data with missing values. These constraint shaped every decision I made.</p>

<h3 id="how-it-was-evaluated">How it was evaluated</h3>

<p>The leaderboard used a weighted, multi-metric score:</p>

<ul>
  <li><strong>F1-Score (60%)</strong> — a balanced measure of precision and recall, important because aquaculture ponds are a small fraction of all locations.</li>
  <li><strong>ROC-AUC (40%)</strong> — how well the model ranks pond locations above non-pond locations across all thresholds, giving a stable read regardless of class imbalance.</li>
</ul>

<p>The model had to output two things: a <strong>binary target</strong> (pond or other) and a <strong>probability</strong> of being a pond. 
 <strong>Setting a custom probability threshold was forbidden</strong> — the binary target had to come from the default 0.5 cutoff. That rule matters: it means the model can’t be tuned to game F1 by shifting the decision boundary; the probabilities themselves have to be well-placed around 0.5.</p>

<h3 id="the-problem-statement">The problem statement</h3>

<p>Two properties of the data defined the whole problem:</p>

<ul>
  <li>The <strong>test set is blind</strong> — no labels, and I only see my score through the leaderboard.</li>
  <li>The <strong>training data is complete</strong> — no missing values in the bands.</li>
  <li>The <strong>test data has missing values</strong>, especially in the Sentinel-2 optical bands, which are blocked by cloud cover. The Sentinel-1 radar bands are far less affected, because radar penetrates cloud.</li>
</ul>

<p>Training on clean, complete 12-month time series, but I predict on messy data from a different time period.</p>

<h2 id="data--approach">Data &amp; Approach</h2>

<p>Each pixel is described by a full year of monthly observations across twelve bands — two radar (<strong>VH, VV</strong> from Sentinel-1) and ten optical (<strong>blue, green, nir, nira, re1, re2, re3, red, swir1, swir2</strong> from Sentinel-2). In the raw file, those months live side by side as wide columns (<code class="language-plaintext highlighter-rouge">VH_01</code>, <code class="language-plaintext highlighter-rouge">VH_02</code>, …, <code class="language-plaintext highlighter-rouge">VH_12</code>).</p>

<p>The Data:</p>

<div style="overflow-x: auto; width: 100%; margin: 1em 0;">
<table style="border-collapse: collapse; font-size: 11px; white-space: nowrap;">
  <tr>
    <th></th><th>ID</th><th>label</th>
    <th>VH_01</th><th>VV_01</th><th>blue_01</th><th>green_01</th><th>nir_01</th><th>nira_01</th><th>re1_01</th><th>re2_01</th>
    <th>...</th>
    <th>blue_12</th><th>green_12</th><th>nir_12</th><th>nira_12</th><th>re1_12</th><th>re2_12</th><th>re3_12</th><th>red_12</th><th>swir1_12</th><th>swir2_12</th>
  </tr>
  <tr>
    <td>0</td><td>ID_TR_NEW_XVGKFMLNRJ</td><td>0</td>
    <td>-29.099645</td><td>-22.471573</td><td>1665</td><td>1719</td><td>1367</td><td>1270</td><td>1689</td><td>1416</td>
    <td>...</td>
    <td>1639</td><td>1826</td><td>1395</td><td>1384</td><td>1736</td><td>1449</td><td>1436</td><td>1626</td><td>1307</td><td>1216</td>
  </tr>
  <tr>
    <td>1</td><td>ID_TR_NEW_GP8KNSWVP6</td><td>0</td>
    <td>-19.470574</td><td>-10.752340</td><td>1579</td><td>1740</td><td>2245</td><td>2231</td><td>2060</td><td>2147</td>
    <td>...</td>
    <td>1490</td><td>1622</td><td>2090</td><td>2109</td><td>1924</td><td>1941</td><td>2020</td><td>1779</td><td>2298</td><td>2131</td>
  </tr>
  <tr>
    <td>2</td><td>ID_TR_NEW_87X3957MVS</td><td>1</td>
    <td>-20.964854</td><td>-8.792675</td><td>1850</td><td>2345</td><td>3664</td><td>3470</td><td>2935</td><td>3384</td>
    <td>...</td>
    <td>1438</td><td>1673</td><td>1335</td><td>1289</td><td>1588</td><td>1377</td><td>1307</td><td>1503</td><td>1237</td><td>1168</td>
  </tr>
</table>
<p style="font-size: 11px; color: #666; margin-top: 4px;">5 rows × 146 columns</p>
</div>

<h3 id="reshaping-to-long-format">Reshaping to long format</h3>

<p>My first decision was to <strong>collapse the months into a long format</strong>, one row per pixel-month instead of one row per pixel. This does two things at once: it folds the seasonal signal into the training data (the model sees each month as its own observation), and it gives me a natural way to deal with missing months later — I can simply drop the rows that are missing, rather than imputing 240+ columns.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">bands</span> <span class="o">=</span> <span class="p">[</span><span class="sh">"</span><span class="s">VH</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">VV</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">blue</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">green</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">nir</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">nira</span><span class="sh">"</span><span class="p">,</span>
         <span class="sh">"</span><span class="s">re1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">re2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">re3</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">red</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">swir1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">swir2</span><span class="sh">"</span><span class="p">]</span>

<span class="n">all_data</span> <span class="o">=</span> <span class="n">origin_data</span><span class="p">.</span><span class="nf">reset_index</span><span class="p">()</span>          <span class="c1"># a unique id to pivot on
</span><span class="n">long_data</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">wide_to_long</span><span class="p">(</span><span class="n">all_data</span><span class="p">,</span> <span class="n">stubnames</span><span class="o">=</span><span class="n">bands</span><span class="p">,</span>
                            <span class="n">i</span><span class="o">=</span><span class="sh">"</span><span class="s">index</span><span class="sh">"</span><span class="p">,</span> <span class="n">j</span><span class="o">=</span><span class="sh">"</span><span class="s">month</span><span class="sh">"</span><span class="p">,</span> <span class="n">sep</span><span class="o">=</span><span class="sh">"</span><span class="s">_</span><span class="sh">"</span><span class="p">,</span> <span class="n">suffix</span><span class="o">=</span><span class="sa">r</span><span class="sh">"</span><span class="s">\d+</span><span class="sh">"</span><span class="p">)</span>
<span class="n">long_data</span> <span class="o">=</span> <span class="n">long_data</span><span class="p">.</span><span class="nf">reset_index</span><span class="p">()</span>
</code></pre></div></div>

<p>The optical reflectance bands are stored as integers scaled by 10,000, I rescale them back to the 0–1 reflectance range before computing any index that is sensitive to scale.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">optical</span> <span class="o">=</span> <span class="p">[</span><span class="sh">"</span><span class="s">blue</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">green</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">nir</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">nira</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">re1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">re2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">re3</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">red</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">swir1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">swir2</span><span class="sh">"</span><span class="p">]</span>
<span class="n">long_data</span><span class="p">[</span><span class="n">optical</span><span class="p">]</span> <span class="o">=</span> <span class="n">long_data</span><span class="p">[</span><span class="n">optical</span><span class="p">]</span> <span class="o">/</span> <span class="mi">10000</span>
</code></pre></div></div>

<h3 id="feature-engineering--teaching-the-model-what-water-looks-like">Feature engineering — teaching the model what water looks like</h3>

<p>A pond is, reflective body of water. I engineered features that make that signature explicit rather than expecting the model to detect water from raw bands.</p>

<ul>
  <li><strong>NDWI</strong> <em>(green − nir)/(green + nir)</em> — the classic water index; water reflects green and absorbs NIR.</li>
  <li><strong>MNDWI</strong> <em>(green − swir1)/(green + swir1)</em> — a modified version using SWIR, which separates water from built-up land more cleanly.</li>
  <li><strong>VH − VV</strong> — the radar backscatter difference. Because these are in decibels (log space), the ratio becomes a <strong>subtraction</strong>, not a division. Smooth water surfaces scatter radar differently than rough land.</li>
  <li><strong>AWEI (two variants)</strong> — the Automated Water Extraction Index, in no-shadow and shadow-suppressing forms, designed to push the water/land contrast even harder than NDWI.</li>
  <li><strong>EVI</strong> — the Enhanced Vegetation Index, computed <em>after</em> rescaling to reflectance because its <code class="language-plaintext highlighter-rouge">+1</code> term assumes a 0–1 range.</li>
  <li><strong>Otsu water flags</strong> — for each month I compute an Otsu threshold on the radar bands and flag pixels below it as “water”. Otsu finds the natural split between two populations, and these radar histograms are bimodal (water vs land), so the threshold cleanly separates them.</li>
</ul>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">long_data</span><span class="p">[</span><span class="sh">'</span><span class="s">ndwi</span><span class="sh">'</span><span class="p">]</span>  <span class="o">=</span> <span class="p">(</span><span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">green</span><span class="sh">"</span><span class="p">]</span> <span class="o">-</span> <span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">nir</span><span class="sh">"</span><span class="p">])</span>   <span class="o">/</span> <span class="p">(</span><span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">green</span><span class="sh">"</span><span class="p">]</span> <span class="o">+</span> <span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">nir</span><span class="sh">"</span><span class="p">])</span>
<span class="n">long_data</span><span class="p">[</span><span class="sh">'</span><span class="s">mndwi</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">green</span><span class="sh">"</span><span class="p">]</span> <span class="o">-</span> <span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">swir1</span><span class="sh">"</span><span class="p">])</span> <span class="o">/</span> <span class="p">(</span><span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">green</span><span class="sh">"</span><span class="p">]</span> <span class="o">+</span> <span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">swir1</span><span class="sh">"</span><span class="p">])</span>
<span class="n">long_data</span><span class="p">[</span><span class="sh">'</span><span class="s">vh_vv</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="n">long_data</span><span class="p">[</span><span class="sh">'</span><span class="s">VH</span><span class="sh">'</span><span class="p">]</span> <span class="o">-</span> <span class="n">long_data</span><span class="p">[</span><span class="sh">'</span><span class="s">VV</span><span class="sh">'</span><span class="p">]</span>          <span class="c1"># dB → subtraction, not ratio
</span>
<span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">awei_nsh</span><span class="sh">"</span><span class="p">]</span> <span class="o">=</span> <span class="mi">4</span><span class="o">*</span><span class="p">(</span><span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">green</span><span class="sh">"</span><span class="p">]</span> <span class="o">-</span> <span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">swir1</span><span class="sh">"</span><span class="p">])</span> <span class="o">-</span> <span class="p">(</span><span class="mf">0.25</span><span class="o">*</span><span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">nir</span><span class="sh">"</span><span class="p">]</span> <span class="o">+</span> <span class="mf">2.75</span><span class="o">*</span><span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">swir2</span><span class="sh">"</span><span class="p">])</span>
<span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">awei_sh</span><span class="sh">"</span><span class="p">]</span>  <span class="o">=</span> <span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">blue</span><span class="sh">"</span><span class="p">]</span> <span class="o">+</span> <span class="mf">2.5</span><span class="o">*</span><span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">green</span><span class="sh">"</span><span class="p">]</span> <span class="o">-</span> <span class="mf">1.5</span><span class="o">*</span><span class="p">(</span><span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">nir</span><span class="sh">"</span><span class="p">]</span> <span class="o">+</span> <span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">swir1</span><span class="sh">"</span><span class="p">])</span> <span class="o">-</span> <span class="mf">0.25</span><span class="o">*</span><span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">swir2</span><span class="sh">"</span><span class="p">]</span>
<span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">evi</span><span class="sh">"</span><span class="p">]</span>      <span class="o">=</span> <span class="mf">2.5</span> <span class="o">*</span> <span class="p">(</span><span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">nir</span><span class="sh">"</span><span class="p">]</span> <span class="o">-</span> <span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">red</span><span class="sh">"</span><span class="p">])</span> <span class="o">/</span> <span class="p">(</span><span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">nir</span><span class="sh">"</span><span class="p">]</span> <span class="o">+</span> <span class="mi">6</span><span class="o">*</span><span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">red</span><span class="sh">"</span><span class="p">]</span> <span class="o">-</span> <span class="mf">7.5</span><span class="o">*</span><span class="n">long_data</span><span class="p">[</span><span class="sh">"</span><span class="s">blue</span><span class="sh">"</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>

<span class="c1"># per-month Otsu threshold on radar → binary water flag
</span><span class="kn">from</span> <span class="n">skimage.filters</span> <span class="kn">import</span> <span class="n">threshold_otsu</span>
<span class="n">thr</span> <span class="o">=</span> <span class="n">long_data</span><span class="p">.</span><span class="nf">groupby</span><span class="p">(</span><span class="sh">'</span><span class="s">month</span><span class="sh">'</span><span class="p">)[</span><span class="sh">'</span><span class="s">VH</span><span class="sh">'</span><span class="p">].</span><span class="nf">apply</span><span class="p">(</span><span class="k">lambda</span> <span class="n">s</span><span class="p">:</span> <span class="nf">threshold_otsu</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="nf">dropna</span><span class="p">().</span><span class="n">values</span><span class="p">))</span>
<span class="n">long_data</span><span class="p">[</span><span class="sh">'</span><span class="s">vh_water</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="n">long_data</span><span class="p">[</span><span class="sh">'</span><span class="s">VH</span><span class="sh">'</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">long_data</span><span class="p">[</span><span class="sh">'</span><span class="s">month</span><span class="sh">'</span><span class="p">].</span><span class="nf">astype</span><span class="p">(</span><span class="nb">int</span><span class="p">).</span><span class="nf">map</span><span class="p">(</span><span class="n">thr</span><span class="p">)).</span><span class="nf">astype</span><span class="p">(</span><span class="nb">int</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="handling-the-missing-months-honestly">Handling the missing months honestly</h3>

<p>This is where the long format pays off. In the blind test set, cloudy months arrive as fully-NaN rows. Rather than <strong>imputing</strong> those months — fabricating optical values the satellite never saw, I simply <strong>drop</strong> them and predict only on the months that have real data.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">cols</span> <span class="o">=</span> <span class="p">[</span><span class="n">c</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">test_final_data</span><span class="p">.</span><span class="n">columns</span> <span class="k">if</span> <span class="n">c</span> <span class="ow">not</span> <span class="ow">in</span> <span class="p">(</span><span class="sh">'</span><span class="s">index</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">month</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">ID</span><span class="sh">'</span><span class="p">)]</span>
<span class="n">model_input</span> <span class="o">=</span> <span class="n">test_final_data</span><span class="p">.</span><span class="nf">dropna</span><span class="p">(</span><span class="n">how</span><span class="o">=</span><span class="sh">"</span><span class="s">all</span><span class="sh">"</span><span class="p">,</span> <span class="n">subset</span><span class="o">=</span><span class="n">cols</span><span class="p">)</span>   <span class="c1"># drop fully-cloudy months
</span></code></pre></div></div>

<p>The justification is that my model learned “what a pond looks like” from twelve-month data, and filling gaps with borrowed values produces test pixels whose feature distribution differs from anything in training. Dropping-and-aggregating keeps every prediction grounded in something the satellite actually measured..</p>

<h3 id="a-radar-only-model-for-cloud-obscured-months">A radar-only model for cloud-obscured months</h3>

<p>Some missing rows are a special case: the optical bands are missing but the radar bands are present, because radar sees through cloud. Throwing these away wastes real signal and precious data. So alongside the full model I trained a <strong>radar-only model</strong> on just the always-present features (<code class="language-plaintext highlighter-rouge">VH</code>, <code class="language-plaintext highlighter-rouge">VV</code>, <code class="language-plaintext highlighter-rouge">vh_vv</code>, and the radar water flags), and routed each row to the right model.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">radar_cols</span> <span class="o">=</span> <span class="p">[</span><span class="sh">'</span><span class="s">month</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">VH</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">VV</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">vh_vv</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">vv_water</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">vh_water</span><span class="sh">'</span><span class="p">]</span>

<span class="c1"># a row is "radar-only" when all optical bands are NaN but radar is present
</span><span class="n">is_radar_only</span> <span class="o">=</span> <span class="n">X</span><span class="p">[</span><span class="n">optical_cols</span><span class="p">].</span><span class="nf">isna</span><span class="p">().</span><span class="nf">all</span><span class="p">(</span><span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span> <span class="o">&amp;</span> <span class="n">X</span><span class="p">[</span><span class="n">radar_cols</span><span class="p">].</span><span class="nf">notna</span><span class="p">().</span><span class="nf">all</span><span class="p">(</span><span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>

<span class="n">proba</span><span class="p">.</span><span class="n">loc</span><span class="p">[</span><span class="n">full</span><span class="p">.</span><span class="n">index</span><span class="p">]</span> <span class="o">=</span> <span class="n">model</span><span class="p">.</span><span class="nf">predict_proba</span><span class="p">(</span><span class="n">full</span><span class="p">)[:,</span> <span class="mi">1</span><span class="p">]</span>                   <span class="c1"># full model
</span><span class="n">proba</span><span class="p">.</span><span class="n">loc</span><span class="p">[</span><span class="n">ro</span><span class="p">.</span><span class="n">index</span><span class="p">]</span>   <span class="o">=</span> <span class="n">radar_model</span><span class="p">.</span><span class="nf">predict_proba</span><span class="p">(</span><span class="n">ro</span><span class="p">[</span><span class="n">radar_cols</span><span class="p">])[:,</span> <span class="mi">1</span><span class="p">]</span>   <span class="c1"># radar-only model
</span></code></pre></div></div>

<p>This means a cloudy month still contributes a prediction based on what radar <em>can</em> see, instead of being silently dropped.</p>

<h3 id="preventing-leakage-in-the-split">Preventing leakage in the split</h3>

<p>Because in the long format a single pixel spans twelve rows(months), an ordinary random tain_test_split would scatter one pixel’s months across both train and test and since a pixel’s months are highly correlated, the model would effectively see the test pixels during training. I used a <strong>grouped, stratified split</strong> so every pixel lands wholly on one side.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">sklearn.model_selection</span> <span class="kn">import</span> <span class="n">StratifiedGroupKFold</span>

<span class="n">sgkf</span> <span class="o">=</span> <span class="nc">StratifiedGroupKFold</span><span class="p">(</span><span class="n">n_splits</span><span class="o">=</span><span class="mi">5</span><span class="p">,</span> <span class="n">shuffle</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">random_state</span><span class="o">=</span><span class="mi">42</span><span class="p">)</span>
<span class="n">train_idx</span><span class="p">,</span> <span class="n">test_idx</span> <span class="o">=</span> <span class="nf">next</span><span class="p">(</span><span class="n">sgkf</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="n">X_10</span><span class="p">,</span> <span class="n">long_y</span><span class="p">,</span> <span class="n">groups</span><span class="o">=</span><span class="n">long_data</span><span class="p">[</span><span class="sh">'</span><span class="s">ID</span><span class="sh">'</span><span class="p">]))</span>

<span class="c1"># verify no pixel appears on both sides
</span><span class="k">assert</span> <span class="nf">len</span><span class="p">(</span><span class="nf">set</span><span class="p">(</span><span class="n">long_data</span><span class="p">.</span><span class="n">iloc</span><span class="p">[</span><span class="n">train_idx</span><span class="p">][</span><span class="sh">'</span><span class="s">ID</span><span class="sh">'</span><span class="p">])</span> <span class="o">&amp;</span> <span class="nf">set</span><span class="p">(</span><span class="n">long_data</span><span class="p">.</span><span class="n">iloc</span><span class="p">[</span><span class="n">test_idx</span><span class="p">][</span><span class="sh">'</span><span class="s">ID</span><span class="sh">'</span><span class="p">]))</span> <span class="o">==</span> <span class="mi">0</span>
</code></pre></div></div>

<h3 id="aggregating-months-to-a-per-pixel-decision">Aggregating months to a per-pixel decision</h3>

<p>The label is per pixel, but the model predicts per month. To collapse a pixel’s monthly predictions into one, I take the <strong>maximum</strong> probability across its  available months (in blind test pixels  had -7 months available). 
The reasoning for taking max month probability is l ask “does this pixel look strongly like a pond in its best available month?” and it works whether a pixel has five months or twelve.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">pixel</span> <span class="o">=</span> <span class="n">X_new</span><span class="p">.</span><span class="nf">groupby</span><span class="p">(</span><span class="sh">'</span><span class="s">ID</span><span class="sh">'</span><span class="p">).</span><span class="nf">agg</span><span class="p">(</span><span class="n">proba</span><span class="o">=</span><span class="p">(</span><span class="sh">'</span><span class="s">proba</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">max</span><span class="sh">'</span><span class="p">),</span> <span class="n">label</span><span class="o">=</span><span class="p">(</span><span class="sh">'</span><span class="s">label</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">first</span><span class="sh">'</span><span class="p">))</span>
<span class="n">pred</span>  <span class="o">=</span> <span class="p">(</span><span class="n">pixel</span><span class="p">[</span><span class="sh">'</span><span class="s">proba</span><span class="sh">'</span><span class="p">]</span> <span class="o">&gt;=</span> <span class="mf">0.5</span><span class="p">).</span><span class="nf">astype</span><span class="p">(</span><span class="nb">int</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="models">Models</h2>

<p>I trained four classifiers, all evaluated per-pixel after max aggregation, and all on the same leakage-safe grouped split:</p>

<ul>
  <li><strong>XGBoost</strong> — gradient-boosted trees; scale-invariant, native NaN handling, my primary workhorse.</li>
  <li><strong>Gradient Boosting</strong> (scikit-learn) — a regularised second boosting model for diversity.</li>
  <li><strong>SVM (SVC)</strong> — inside a pipeline with <code class="language-plaintext highlighter-rouge">StandardScaler</code> and a cyclical sin/cos encoding of month, since a distance-based model needs scaled features and a circular notion of the calendar.</li>
  <li><strong>TabPFN</strong> — a pretrained tabular transformer that needs no tuning and is well-suited to small tabular data like this.</li>
</ul>

<p>The SVM pipeline is worth showing, because it’s the one model where preprocessing genuinely matters:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">month_cyclical</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
    <span class="n">x</span> <span class="o">=</span> <span class="n">x</span><span class="p">.</span><span class="nf">astype</span><span class="p">(</span><span class="nb">float</span><span class="p">)</span>
    <span class="n">radians</span> <span class="o">=</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">np</span><span class="p">.</span><span class="n">pi</span> <span class="o">*</span> <span class="n">x</span> <span class="o">/</span> <span class="mi">12</span>
    <span class="k">return</span> <span class="n">np</span><span class="p">.</span><span class="nf">hstack</span><span class="p">([</span><span class="n">np</span><span class="p">.</span><span class="nf">sin</span><span class="p">(</span><span class="n">radians</span><span class="p">),</span> <span class="n">np</span><span class="p">.</span><span class="nf">cos</span><span class="p">(</span><span class="n">radians</span><span class="p">)])</span>   <span class="c1"># Dec and Jan sit next to each other
</span>
<span class="n">preprocessor</span> <span class="o">=</span> <span class="nc">ColumnTransformer</span><span class="p">(</span>
    <span class="n">transformers</span><span class="o">=</span><span class="p">[(</span><span class="sh">'</span><span class="s">scale</span><span class="sh">'</span><span class="p">,</span> <span class="nc">StandardScaler</span><span class="p">(),</span> <span class="n">scale_cols</span><span class="p">),</span>
                  <span class="p">(</span><span class="sh">'</span><span class="s">month</span><span class="sh">'</span><span class="p">,</span> <span class="nc">FunctionTransformer</span><span class="p">(</span><span class="n">month_cyclical</span><span class="p">),</span> <span class="p">[</span><span class="sh">'</span><span class="s">month</span><span class="sh">'</span><span class="p">])],</span>
    <span class="n">remainder</span><span class="o">=</span><span class="sh">'</span><span class="s">passthrough</span><span class="sh">'</span>
<span class="p">)</span>
<span class="n">model</span> <span class="o">=</span> <span class="nf">make_pipeline</span><span class="p">(</span><span class="n">preprocessor</span><span class="p">,</span> <span class="nc">SVC</span><span class="p">(</span><span class="n">random_state</span><span class="o">=</span><span class="mi">42</span><span class="p">,</span> <span class="n">probability</span><span class="o">=</span><span class="bp">True</span><span class="p">))</span>
<span class="n">model</span><span class="p">.</span><span class="nf">fit</span><span class="p">(</span><span class="n">X_train</span><span class="p">,</span> <span class="n">y_train</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="results">Results</h2>

<h3 id="validation-per-pixel-on-the-held-out-grouped-split">Validation (per-pixel, on the held-out grouped split)</h3>

<p>On clean twelve-month validation data, every model was excellent:</p>

<table>
  <thead>
    <tr>
      <th>Model</th>
      <th>ROC-AUC</th>
      <th>F1</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>XGBoost</td>
      <td>0.9966</td>
      <td>0.9416</td>
    </tr>
    <tr>
      <td>Gradient Boosting</td>
      <td>0.9945</td>
      <td>0.9355</td>
    </tr>
    <tr>
      <td>SVC</td>
      <td>0.9971</td>
      <td>0.9571</td>
    </tr>
    <tr>
      <td>TabPFN</td>
      <td>0.9995</td>
      <td>0.9932</td>
    </tr>
  </tbody>
</table>

<h3 id="blind-competition-test-ragged-missing-months-data">Blind competition test (ragged, missing months data)</h3>

<p>The blind test told the real story and the gap between validation and test is the whole lesson of this project:</p>

<table>
  <thead>
    <tr>
      <th>Model</th>
      <th>ROC-AUC</th>
      <th>F1</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>XGBoost</td>
      <td>0.8725</td>
      <td>0.8489</td>
    </tr>
    <tr>
      <td>Gradient Boosting</td>
      <td>0.8486</td>
      <td>0.8418</td>
    </tr>
    <tr>
      <td>SVC</td>
      <td>0.8924</td>
      <td>0.8454</td>
    </tr>
    <tr>
      <td>TabPFN</td>
      <td>0.8581</td>
      <td>0.8537</td>
    </tr>
    <tr>
      <td><strong>Competition Winner (1st place)</strong></td>
      <td><strong>0.9689</strong></td>
      <td><strong>0.9301</strong></td>
    </tr>
  </tbody>
</table>

<p>My best models generalised from ~0.99 validation to ~0.87–0.89 on the blind set, because the grouped split and drop-don’t-impute strategy meant my validation was never inflated by leakage or fabricated data. 
<strong>My Final rank: 253 / 1314.</strong></p>

<h3 id="what-i-confirmed-along-the-way">What I confirmed along the way</h3>

<p>I also ran <strong>wide-format models with imputed months</strong> (including MICE / predictive-mean-matching) and they consistently scored <em>below</em> the long-format drop-and-aggregate approach. Those models dropped my accuracy from ~0.85 to ~0.75 because imputed months diluted every pixel with predicted-pond signals. That negative result is what gave me confidence the long+max design was the right call.</p>

<h2 id="conclusion">Conclusion</h2>

<p>The models generalised <strong>well but not spectacularly</strong>, and the reason is instructive. My feature engineering captured <em>where</em> water is (spectral water indices, radar backscatter, Otsu flags) far better than <em>when and how it cycle, the seasonality of ponds</em>. The  pond’s <strong>temporal signature</strong>, the fill-and-drain rhythm across the year is exactly what a clean twelve-month training set expresses richly and a test set with missing months doesnt. Also the max approach is likely to be affected by alot of False positives, coz it pick one month which said water and without seasonality it affect hte overall accuracy.</p>

<p>Two directions would close the gap to the leaderboard leaders:</p>

<ol>
  <li><strong>Model the temporal signature in a form that survives missing months</strong> — persistence (fraction of months a pixel reads as water), cycle amplitude, or a fitted seasonal harmonic — rather than fragile summary statistics that mean different things when computed from five months versus twelve.</li>
  <li><strong>Model the missing values in the test set explicitly</strong> as part of the signal, rather than only routing around them.</li>
</ol>

<p>The competition rewarded honesty about the train/test mismatch, and my solution’s clean generalisation reflects that. The remaining distance to the top is a temporal-modelling problem, not a preprocessing one a satisfying place to have landed, because it points clearly at what to build next.</p>]]></content><author><name>Farisayi Dakwa</name><email>fariedakwa.fd@gmail.com</email></author><category term="Data Science" /><category term="Classification" /><category term="Remote Sensing" /><category term="Python" /><category term="Machine Learning" /><summary type="html"><![CDATA[A machine learning solution for the Zindi GeoAI Aquaculture Pond Identification Challenge, detecting aquaculture ponds from Sentinel-1 radar and Sentinel-2 optical satellite data. My solution ranked 253/1314, generalising cleanly from a clean training period to a blind, cloud-affected test period.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="/assets/img/blog/ponds.jpg" /><media:content medium="image" url="/assets/img/blog/ponds.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">PART 2 - Product Sales Trend Analysis for Black Friday week</title><link href="/blog/Sales-Trends-Analysis/" rel="alternate" type="text/html" title="PART 2 - Product Sales Trend Analysis for Black Friday week" /><published>2025-11-06T00:00:00+00:00</published><updated>2025-12-16T07:55:12+00:00</updated><id>/blog/Sales-Trends-Analysis</id><content type="html" xml:base="/blog/Sales-Trends-Analysis/"><![CDATA[<p><strong>People don’t buy what you do; they buy why you do it.</strong> ~ <em>Simon Sinek</em></p>

<ul id="markdown-toc">
  <li><a href="#introduction" id="markdown-toc-introduction">Introduction</a></li>
  <li><a href="#methods" id="markdown-toc-methods">Methods</a>    <ul>
      <li><a href="#data-import" id="markdown-toc-data-import">Data Import</a></li>
      <li><a href="#data-overview" id="markdown-toc-data-overview">Data Overview</a></li>
      <li><a href="#objective-1-day-to-day-sales-trends" id="markdown-toc-objective-1-day-to-day-sales-trends">Objective 1; Day to day sales trends</a></li>
      <li><a href="#objective-4-product-association" id="markdown-toc-objective-4-product-association">Objective 4; Product association</a></li>
      <li><a href="#objective-6-difference-between-black-friday-and-cyber-monday-sales" id="markdown-toc-objective-6-difference-between-black-friday-and-cyber-monday-sales">Objective 6; Difference between Black Friday and Cyber Monday sales</a></li>
      <li><a href="#business-recommendations" id="markdown-toc-business-recommendations">Business Recommendations</a></li>
    </ul>
  </li>
</ul>

<h3 id="introduction">Introduction</h3>

<p>My client is a partnership B2B company looking to leverage data-driven decision-making tools to help businesses understand and capitalize on market trends and drive growth in product sales.
In this project, l analyzed sales for Thanksgiving and Black Friday week of 2021 from Monday 22 November –to Monday 29 November.</p>

<p>The main objectives were:</p>
<ol>
  <li>What product sales trends were observed during the week?</li>
  <li>What are the top-selling products and categories during Thanksgiving week?</li>
  <li>What are the highest commissioned products?</li>
  <li>Which products and product categories are most frequently purchased together?</li>
  <li>How were product sales trends similar or different between Black Friday and Cyber Monday?</li>
  <li>What business recommendations can you provide based on the above analysis?</li>
  <li>How would the product classification model be deployed and productionalised to support client-facing product features?</li>
</ol>

<p>To illustrate the power of sales data analysis l presented my findings in a live R shiny interactive dashboard highlighting the objectives on each tab.</p>

<p><strong>Click to view Sales Presentation</strong> - <a href="https://farisayi-dakwa.shinyapps.io/Presentation-Product-Items-Sales-Analysis/" target="_blank"><img src="https://img.shields.io/badge/View-Dashboard-FF6C00?style=flat-square" alt="" /></a></p>

<h2 id="methods">Methods</h2>

<p>For this project, I utilized Python to summarize, wrangle data, and perform statistical analysis.<strong>Click to view data wrangling and analysis in Python</strong> - <a href="https://github.com/fariedd/Product-text-calssification-and-Sales-Analysis/blob/main/Sale%20Analysis%20notebook.ipynb" target="_blank"><img src="https://img.shields.io/badge/Notebook-View_in_Github-blue?style=plastic&amp;logo=Jupyter" alt="" /></a>
l exported the data into R, I compiled and visualized the insights, presenting them as an organized interactive R shiny dashboard to enhance the user experience for stakeholders <strong>Click to view R Shiny dashboard and visualisation script</strong> - <a href="https://github.com/fariedd/Product-text-calssification-and-Sales-Analysis/blob/main/app.R" target="_blank"><img src="https://img.shields.io/badge/R_shiny-View_in_Github-blue?style=plastic&amp;logo=R" alt="" /></a></p>

<h3 id="data-import">Data Import</h3>

<p>A Naïve Bayes text classifier was used to categorize product items using product descriptions, see more  <a href="https://fariedakwa.netlify.app/blog/product-text-classification/" target="_blank"> here</a></p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#PYTHON CODE BLOCK
# Predict categories for unlabeled data
# Create a pipeline with TF-IDF vectorizer and Naive bayes hadnle textual data and imblanced datasets
</span><span class="kn">from</span> <span class="n">sklearn.naive_bayes</span> <span class="kn">import</span> <span class="n">MultinomialNB</span>
<span class="kn">from</span> <span class="n">sklearn.model_selection</span> <span class="kn">import</span> <span class="n">train_test_split</span>
<span class="kn">from</span> <span class="n">sklearn.feature_extraction.text</span> <span class="kn">import</span> <span class="n">TfidfVectorizer</span>

<span class="kn">from</span> <span class="n">sklearn.metrics</span> <span class="kn">import</span> <span class="n">classification_report</span><span class="p">,</span> <span class="n">accuracy_score</span>
<span class="kn">from</span> <span class="n">sklearn.pipeline</span> <span class="kn">import</span> <span class="n">Pipeline</span>

<span class="n">X_train</span><span class="p">,</span> <span class="n">X_test</span><span class="p">,</span> <span class="n">y_train</span><span class="p">,</span> <span class="n">y_test</span> <span class="o">=</span> <span class="nf">train_test_split</span><span class="p">(</span><span class="n">X</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span><span class="n">test_size</span><span class="o">=</span><span class="mf">0.2</span><span class="p">,</span> <span class="n">random_state</span><span class="o">=</span><span class="mi">42</span><span class="p">)</span><span class="c1">## no need to sub-sample teh data
</span>
<span class="n">pipeline_2</span> <span class="o">=</span> <span class="nc">Pipeline</span><span class="p">([</span>
    <span class="p">(</span><span class="sh">'</span><span class="s">tfidf</span><span class="sh">'</span><span class="p">,</span> <span class="nc">TfidfVectorizer</span><span class="p">(</span><span class="n">max_df</span><span class="o">=</span><span class="mf">0.50</span><span class="p">,</span> 
                              <span class="n">min_df</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">max_features</span><span class="o">=</span><span class="mi">500000</span><span class="p">,</span> 
                              <span class="n">ngram_range</span><span class="o">=</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span> 
                              <span class="n">stop_words</span><span class="o">=</span><span class="sh">'</span><span class="s">english</span><span class="sh">'</span><span class="p">,</span> 
                               <span class="n">use_idf</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> 
                              <span class="n">smooth_idf</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> 
                              <span class="n">sublinear_tf</span><span class="o">=</span><span class="bp">True</span><span class="p">)),</span>   <span class="c1"># Adjust max_features based on your dataset size, ngrams captures bi-words
</span>    <span class="c1">#('svd', TruncatedSVD(n_components=1000)), # Adjust n_components as needed
</span>   <span class="p">(</span><span class="sh">'</span><span class="s">nb</span><span class="sh">'</span><span class="p">,</span> <span class="nc">MultinomialNB</span><span class="p">(</span><span class="n">alpha</span><span class="o">=</span><span class="mf">0.001</span><span class="p">))</span>  <span class="c1"># Naive Bayes classifier
</span><span class="p">])</span>

<span class="c1"># Train the model
</span><span class="n">pipeline_2</span><span class="p">.</span><span class="nf">fit</span><span class="p">(</span><span class="n">X_train</span><span class="p">,</span> <span class="n">y_train</span><span class="p">)</span>

<span class="n">product_sales</span><span class="p">[</span><span class="sh">'</span><span class="s">target_label</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="n">pipeline_2</span><span class="p">.</span><span class="nf">predict</span><span class="p">(</span><span class="n">product_sales</span><span class="p">[</span><span class="sh">'</span><span class="s">item_name</span><span class="sh">'</span><span class="p">])</span><span class="c1"># classify product sales using product description
</span></code></pre></div></div>
<h3 id="data-overview">Data Overview</h3>

<p>The key variables will come from one table containin product sales.</p>

<style>
  table {
    border-collapse: collapse;
    width: 100%; /* Ensures the table takes up the full width, avoiding extra space */
    font-size: 12px;
  }
  th, td {
    border: 1px solid black;
    padding: 5px;
    text-align: left;
  }
</style>

<table>
  <tr>
    <th><strong>Column Name</strong></th>
    <th><strong>Definition</strong></th>
  </tr>
  <tr>
    <td><strong>action_id</strong></td>
    <td><em>Unique identifier for a transaction</em></td>
  </tr>
  <tr>
    <td><strong>sqldate</strong></td>
    <td><em>Date of transaction</em></td>
  </tr>
  <tr>
    <td><strong>item_name</strong></td>
    <td><em>Product item name</em></td>
  </tr>
  <tr>
    <td><strong>country</strong></td>
    <td><em>Country where the transaction took place</em></td>
  </tr>
  <tr>
    <td><strong>payout_type</strong></td>
    <td><em>Terms for commission</em></td>
  </tr>
  <tr>
    <td><strong>saleamt</strong></td>
    <td><em>Sale amount in USD</em></td>
  </tr>
  <tr>
    <td><strong>commission</strong></td>
    <td><em>Commission amount in USD</em></td>
  </tr>
   <tr>
    <td><strong>target_label</strong></td>
    <td><em>Predicted Google Merchant Catalog value</em></td>
  </tr>
</table>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#PYTHON CODE BLOCK
</span><span class="kn">import</span> <span class="n">pandas</span> <span class="k">as</span> <span class="n">pd</span>
<span class="n">product_sales</span><span class="p">.</span><span class="nf">head</span><span class="p">(</span><span class="mi">6</span><span class="p">)</span>
</code></pre></div></div>
<!-- -->

<style>
  table {
    border-collapse: collapse;
    width: 100%;
    font-size: 12px;
    overflow-x: auto;
    display: block;
    white-space: nowrap; /* Prevents line breaks */
  }
  th, td {
    border: 1px solid black;
    padding: 5px;
    text-align: left;
  }
</style>

<table>
  <tr>
    <th>action_id</th>
    <th>sqldate</th>
    <th>item_name</th>
    <th>country</th>
    <th>payout_type</th>
    <th>saleamt</th>
    <th>commission</th>
    <th>target_label</th>
  </tr>
  <tr>
    <td>4270.5079.279362</td>
    <td>2021-11-27</td>
    <td>adidas Supernova Shoes Cloud White 11 Womens</td>
    <td>US</td>
    <td>PCT_OF_SALEAMOUNT</td>
    <td>65.0</td>
    <td>11.05</td>
    <td>Apparel &amp; Accessories &gt; Shoes</td>
  </tr>
  <tr>
    <td>4270.5080.265784</td>
    <td>2021-11-28</td>
    <td>adidas Adilette Comfort Slides Core Black 10 Mens</td>
    <td>US</td>
    <td>PCT_OF_SALEAMOUNT</td>
    <td>24.5</td>
    <td>1.47</td>
    <td>Apparel &amp; Accessories &gt; Shoes</td>
  </tr>
  <tr>
    <td>9800.5081.66990</td>
    <td>2021-11-29</td>
    <td>Pro Tools Ultimate Subscription ExpertPlus Support</td>
    <td>FR</td>
    <td>PCT_OF_SALEAMOUNT</td>
    <td>0.0</td>
    <td>0.00</td>
    <td>Software &gt; Computer Software &gt; Multimedia &amp; Design Software &gt; Music Composition Software</td>
  </tr>
  <tr>
    <td>10310.5077.1677940</td>
    <td>2021-11-26</td>
    <td>Loose Built-In Flex Jeans For Men</td>
    <td>CA</td>
    <td>PCT_OF_SALEAMOUNT</td>
    <td>35.4</td>
    <td>1.06</td>
    <td>Apparel &amp; Accessories &gt; Clothing &gt; Pants</td>
  </tr>
  <tr>
    <td>4270.5081.1564705</td>
    <td>2021-11-29</td>
    <td>adidas Adissage Slides Core Black M 12 / W 13 Unisex</td>
    <td>US</td>
    <td>PCT_OF_SALEAMOUNT</td>
    <td>21.0</td>
    <td>2.52</td>
    <td>Apparel &amp; Accessories &gt; Shoes</td>
  </tr>
</table>

<h3 id="objective-1-day-to-day-sales-trends">Objective 1; Day to day sales trends</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#PYTHON CODE BLOCK
# since we are looking at a week of data the lowet granular value is day and for sales intepretation we need the name of day
#convert date time column
</span><span class="kn">from</span> <span class="n">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
<span class="n">product_sales_new</span><span class="p">[</span><span class="sh">'</span><span class="s">sqldate</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span><span class="n">pd</span><span class="p">.</span><span class="nf">to_datetime</span><span class="p">(</span><span class="n">product_sales_new</span><span class="p">[</span><span class="sh">'</span><span class="s">sqldate</span><span class="sh">'</span><span class="p">],</span> <span class="nb">format</span><span class="o">=</span><span class="sh">'</span><span class="s">%Y-%m-%d</span><span class="sh">'</span><span class="p">)</span>
<span class="n">product_sales_new</span><span class="p">[</span><span class="sh">'</span><span class="s">day</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="n">product_sales_new</span><span class="p">[</span><span class="sh">'</span><span class="s">sqldate</span><span class="sh">'</span><span class="p">].</span><span class="n">dt</span><span class="p">.</span><span class="nf">strftime</span><span class="p">(</span><span class="sh">'</span><span class="s">%d</span><span class="sh">'</span><span class="p">)</span>
<span class="n">product_sales_new</span><span class="p">[</span><span class="sh">'</span><span class="s">day_name</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="n">product_sales_new</span><span class="p">[</span><span class="sh">'</span><span class="s">sqldate</span><span class="sh">'</span><span class="p">].</span><span class="n">dt</span><span class="p">.</span><span class="nf">strftime</span><span class="p">(</span><span class="sh">"</span><span class="s">%A</span><span class="sh">"</span><span class="p">)</span><span class="c1">#day name
</span>
<span class="n">pd</span><span class="p">.</span><span class="nf">set_option</span><span class="p">(</span><span class="sh">'</span><span class="s">display.float_format</span><span class="sh">'</span><span class="p">,</span> <span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="sh">'</span><span class="s">%.0f</span><span class="sh">'</span> <span class="o">%</span> <span class="n">x</span><span class="p">)</span><span class="c1">#supress scientific notation
</span>
<span class="n">day_to_day</span> <span class="o">=</span> <span class="n">product_sales_new</span><span class="p">.</span><span class="nf">groupby</span><span class="p">([</span><span class="sh">'</span><span class="s">sqldate</span><span class="sh">'</span><span class="p">,</span><span class="sh">'</span><span class="s">day</span><span class="sh">'</span><span class="p">,</span><span class="sh">'</span><span class="s">day_name</span><span class="sh">'</span><span class="p">])[</span><span class="sh">'</span><span class="s">saleamt</span><span class="sh">'</span><span class="p">].</span><span class="nf">sum</span><span class="p">().</span><span class="nf">reset_index</span><span class="p">()</span><span class="c1"># day to day data
</span>
</code></pre></div></div>
<p>Visualise wrangled in R</p>

<div class="language-r highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># R CODE BLOCK</span><span class="w">
</span><span class="n">plot_ly</span><span class="p">(</span><span class="n">day_to_day</span><span class="p">,</span><span class="w"> 
        </span><span class="n">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">~</span><span class="n">sqldate</span><span class="p">,</span><span class="w"> 
        </span><span class="n">y</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">~</span><span class="n">saleamt</span><span class="p">,</span><span class="w"> 
        </span><span class="n">type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'scatter'</span><span class="p">,</span><span class="w"> 
        </span><span class="n">mode</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'lines+markers'</span><span class="p">,</span><span class="w"> 
        </span><span class="n">hoverinfo</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'text'</span><span class="p">,</span><span class="w">
        </span><span class="n">hovertemplate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'&lt;b&gt;%{x}&lt;/b&gt;: %{y:.2s} USD&lt;extra&gt;&lt;/extra&gt;'</span><span class="p">)</span><span class="w"> </span><span class="o">%&gt;%</span><span class="w">
  </span><span class="n">layout</span><span class="p">(</span><span class="w">
    </span><span class="n">hovermode</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"x"</span><span class="p">,</span><span class="w">
    </span><span class="n">xaxis</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">list</span><span class="p">(</span><span class="w">
      </span><span class="n">title</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">list</span><span class="p">(</span><span class="n">text</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Thanksgiving Week November 2021'</span><span class="p">,</span><span class="w"> </span><span class="n">standoff</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">20</span><span class="p">),</span><span class="w">
      </span><span class="n">tickformat</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'%A %d %b'</span><span class="p">,</span><span class="w">
      </span><span class="n">dtick</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'D1'</span><span class="p">,</span><span class="w">
      </span><span class="n">showline</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">TRUE</span><span class="p">,</span><span class="w">
      </span><span class="n">linecolor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'black'</span><span class="w">
    </span><span class="p">),</span><span class="w">
    </span><span class="n">yaxis</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">list</span><span class="p">(</span><span class="w">
      </span><span class="n">title</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">list</span><span class="p">(</span><span class="n">text</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Daily Sales Revenue (USD)'</span><span class="p">,</span><span class="w"> </span><span class="n">standoff</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">20</span><span class="p">),</span><span class="w">
      </span><span class="n">showline</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">TRUE</span><span class="p">,</span><span class="w">
      </span><span class="n">linecolor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'black'</span><span class="w">
    </span><span class="p">)</span><span class="w">
  </span><span class="p">)</span><span class="w">
</span></code></pre></div></div>

<iframe src="\Data Science\plot.html" width="100%" height="400"></iframe>

<p><strong>View more in dashboard</strong> -  <a href="https://farisayi-dakwa.shinyapps.io/Presentation-Product-Items-Sales-Analysis/" target="_blank"><img src="https://img.shields.io/badge/View-Dashboard-FF6C00?style=flat-square" alt="" /></a></p>

<p><strong>Objective 2, 3, and 5</strong> required dats wrangling using Python and visulalisation and integration into siny dashboard in R as illustrated above.</p>

<h3 id="objective-4-product-association">Objective 4; Product association</h3>

<p>To determine frequently purchased products, I used a market basket association rule technique with support, confidence, and lift as metrics to quantify the strength of relationships between item sets.</p>

<ul>
  <li><strong>Support</strong>: The support of an item set is the probability of its occurrence out of the total number of transactions. It quantifies the share of transactions containing an item set. For item set A:</li>
</ul>

<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>support</mtext><mo stretchy="false">(</mo><mi>A</mi><mo stretchy="false">)</mo><mo>=</mo><mfrac><mrow><mi>n</mi><mo stretchy="false">(</mo><mi>A</mi><mo stretchy="false">)</mo></mrow><mrow><mi>n</mi><mo stretchy="false">(</mo><mtext>transactions</mtext><mo stretchy="false">)</mo></mrow></mfrac></mrow><annotation encoding="application/x-tex">\text{support}(A) = \frac{n(A)}{n(\text{transactions})}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord text"><span class="mord">support</span></span><span class="mopen">(</span><span class="mord mathnormal">A</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.363em;vertical-align:-0.936em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.427em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">n</span><span class="mopen">(</span><span class="mord text"><span class="mord">transactions</span></span><span class="mclose">)</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">n</span><span class="mopen">(</span><span class="mord mathnormal">A</span><span class="mclose">)</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.936em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span>

<ul>
  <li><strong>Confidence</strong>: For association ( A \rightarrow B ), confidence is the probability of A and B occurring together out of all transactions where A has already occurred:</li>
</ul>

<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>confidence</mtext><mo>=</mo><mfrac><mrow><mi>n</mi><mo stretchy="false">(</mo><mi>A</mi><mo>∩</mo><mi>B</mi><mo stretchy="false">)</mo></mrow><mrow><mi>n</mi><mo stretchy="false">(</mo><mi>A</mi><mo stretchy="false">)</mo></mrow></mfrac><mo>=</mo><mi>P</mi><mo stretchy="false">(</mo><mi>B</mi><mi mathvariant="normal">∣</mi><mi>A</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\text{confidence} = \frac{n(A \cap B)}{n(A)} = P(B|A)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord text"><span class="mord">confidence</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.363em;vertical-align:-0.936em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.427em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">n</span><span class="mopen">(</span><span class="mord mathnormal">A</span><span class="mclose">)</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">n</span><span class="mopen">(</span><span class="mord mathnormal">A</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∩</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord mathnormal" style="margin-right:0.05017em;">B</span><span class="mclose">)</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.936em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.05017em;">B</span><span class="mord">∣</span><span class="mord mathnormal">A</span><span class="mclose">)</span></span></span></span></span>

<p>Given the dataset’s size (200,000 transactions and items), I created a sparse matrix to speed up processing and scaled the data to use the Apriori and FP-Growth algorithms, but the matrix size was too large. I opted to manually calculate support, confidence, and lift for item sets within the data. <strong>View more in dashboard</strong> -  <a href="https://farisayi-dakwa.shinyapps.io/Presentation-Product-Items-Sales-Analysis/" target="_blank"><img src="https://img.shields.io/badge/View-Dashboard-FF6C00?style=flat-square" alt="" /></a></p>

<h3 id="objective-6-difference-between-black-friday-and-cyber-monday-sales">Objective 6; Difference between Black Friday and Cyber Monday sales</h3>

<p>Looking at graphs;
Cyber Monday had more commissioned items than Black Friday,
<strong>Conclusion:</strong> Cyber Monday is focused on online sales, which reach a larger audience with extended shopping time; where affiliates can be used to boost online traffic compared to Black Friday which focusses more on in-store shopping</p>

<p>Sales almost doubled for both Black Friday and Cyber Monday, especially in the the United States but Cyber Monday had more sales,
<strong>Conclusion 1:</strong> Retailers often use Black Friday to attract a large crowd with in-store promotions. These promotions could be extended to Cyber Monday,offer exclusive online deals, sometimes with additional discounts which drive up sales
<strong>Conclusion 2:</strong> Canada conversely had less sales on Cyber monday than Black Friday, which could mean that customers still prefer traditional instore deals than online shopping</p>

<h3 id="business-recommendations">Business Recommendations</h3>

<p><strong>Black Friday and Cyber Monday Sales Spike:</strong></p>

<ul>
  <li>Increase marketing and promotional sales on these days.</li>
  <li>Ensure that inventory levels are sufficient to meet increased demand, especially for US sales where customer spending almost doubles.</li>
  <li>For Cyber Monday, invest in enhancing the online shopping experience. Ensure that websites can handle increased traffic smoothly, which improves user experience and can increase the conversion rate.</li>
</ul>

<p><strong>Top Selling Products and Categories:</strong></p>

<ul>
  <li><strong>Theragun Elite Black, gaming consoles, and Jordan Shoes</strong> are top sellers. To attract customers, push to feature these prominently in marketing campaigns.</li>
  <li><strong>Bowflex C6 Bike</strong> and <strong>Bowflex Cardio Machine Mat</strong> are often bought together. We could offer complementary bundle discounts for these products and other similar products.</li>
</ul>

<p><strong>Data Driven Decision:</strong></p>

<ul>
  <li>Models are only as good as the data used. In this case, the <code class="language-plaintext highlighter-rouge">product_taxanomy</code> still needs more cleaning on the correct cataloguing of items before they can be trained on a model. This will improve the implementation of recommendation tools and product association rules technique to identify items frequently bought-together items and create bundle deals or cross-selling opportunities.</li>
</ul>]]></content><author><name>Farisayi Dakwa</name><email>fariedakwa.fd@gmail.com</email></author><category term="Data Science" /><category term="Dashboard" /><category term="Data Visualisation" /><category term="R" /><category term="Trend Analysis" /><summary type="html"><![CDATA[In this project, I build a interactive R Shiny Dashboard to present Sales Analysis Trends of Thanks giving week of 2021, Insights and Business Recommendation]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="/assets/img/blog/product%20clasification/markus-spiske-sales.jpg" /><media:content medium="image" url="/assets/img/blog/product%20clasification/markus-spiske-sales.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">PART 1 - Product text Classification (TF-IDF + Multinomial Naive Bayes)</title><link href="/blog/Product-Text-Classification/" rel="alternate" type="text/html" title="PART 1 - Product text Classification (TF-IDF + Multinomial Naive Bayes)" /><published>2025-10-05T00:00:00+00:00</published><updated>2025-12-16T07:55:12+00:00</updated><id>/blog/Product-Text-Classification</id><content type="html" xml:base="/blog/Product-Text-Classification/"><![CDATA[<p><strong>Failure is an option here. If things are not failing, you are not innovating enough</strong> ~ <em>Elon Musk</em>.</p>

<p><strong>Complete Jupyter Notebook</strong> - <a href="https://github.com/fariedd/Product-text-calssification-and-Sales-Analysis/blob/main/Product%20Text%20Classification.ipynb" target="_blank"><img src="https://img.shields.io/badge/GitHub-View_in_GitHub-blue?logo=GitHub" alt="" /></a></p>

<ul id="markdown-toc">
  <li><a href="#introduction" id="markdown-toc-introduction">Introduction</a>    <ul>
      <li><a href="#data-overview" id="markdown-toc-data-overview">Data Overview</a></li>
    </ul>
  </li>
  <li><a href="#methods--data-cleaning" id="markdown-toc-methods--data-cleaning">Methods &amp; Data Cleaning</a>    <ul>
      <li><a href="#text-normalisation" id="markdown-toc-text-normalisation"><strong>Text Normalisation</strong></a></li>
    </ul>
  </li>
  <li><a href="#model-training--pipeline" id="markdown-toc-model-training--pipeline">Model Training &amp; Pipeline</a>    <ul>
      <li><a href="#tf-idf-score" id="markdown-toc-tf-idf-score">TF-IDF Score:</a></li>
      <li><a href="#term-frequency-tf" id="markdown-toc-term-frequency-tf">Term Frequency (TF):</a></li>
      <li><a href="#inverse-document-frequency-idf" id="markdown-toc-inverse-document-frequency-idf">Inverse Document Frequency (IDF):</a></li>
      <li><a href="#model-implementation" id="markdown-toc-model-implementation">Model Implementation</a></li>
    </ul>
  </li>
  <li><a href="#results" id="markdown-toc-results">Results</a>    <ul>
      <li><a href="#final-model" id="markdown-toc-final-model">Final Model</a></li>
      <li><a href="#evaluation-and-prediction" id="markdown-toc-evaluation-and-prediction">Evaluation and Prediction</a></li>
    </ul>
  </li>
  <li><a href="#part-2" id="markdown-toc-part-2">PART 2</a></li>
</ul>

<h2 id="introduction">Introduction</h2>

<p>In this project, I developed a deployable machine learning product classification model for a B2B e-commerce company struggling with incomplete category information in their product catalog. The objective was to enhance the accuracy and efficiency of sales product categorization, ultimately improving the user experience on their website. The task involved building a text classification model to predict missing category data based on product descriptions. A significant challenge was dealing with category data in multiple formats, some of which did not align with the <a href="https://support.google.com/merchants/answer/6324436?hl=en#Format&amp;zippy=%2Capparel-products" target="_blank">Google product taxonomy format</a>. I started by analyzing the dataset to identify and extract rows that conformed to the Google taxonomy, applying natural language processing techniques to clean the text data and enrich the training set. The resulting model was trained to accurately classify new products into the correct categories, improving data consistency and streamlining product management.</p>

<h3 id="data-overview">Data Overview</h3>

<p>The key variables to categorise products will come from one table contain incosistantly categorised products that will be cleaned to created a final dataset for modelling.</p>

<style>
  table {
    border-collapse: collapse;
    width: 100%; /* Ensures the table takes up the full width, avoiding extra space */
    font-size: 12px;
  }
  th, td {
    border: 1px solid black;
    padding: 5px;
    text-align: left;
  }
</style>

<table>
  <tr>
    <th><strong>Column Name</strong></th>
    <th><strong>Definition</strong></th>
  </tr>
  <tr>
    <td><strong>item_name</strong></td>
    <td><em>Product item description</em></td>
  </tr>
  <tr>
    <td><strong>original_format_category</strong></td>
    <td><em>Google Merchant Catalog value</em></td>
  </tr>
</table>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">pandas</span> <span class="k">as</span> <span class="n">pd</span>
<span class="n">product_taxanomy</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">read_csv</span><span class="p">(</span><span class="sh">'</span><span class="s">ProductDataChallenge_02_ProductTaxonomy.csv</span><span class="sh">'</span><span class="p">)</span>

<span class="n">product_taxanomy</span><span class="p">.</span><span class="nf">head</span><span class="p">(</span><span class="mi">6</span><span class="p">)</span>
</code></pre></div></div>
<!-- -->

<table>
  <thead>
    <tr>
      <th>item_name</th>
      <th>original_format_category</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Women’s Tune Squad Graphic Lounge Shorts - Yellow L</td>
      <td>Apparel &amp; Accessories &gt; Clothing &gt; Shorts</td>
    </tr>
    <tr>
      <td>Nickelodeon Cra-Z-Sand Tri-Color Bucket of Sand</td>
      <td>Toys &amp; Games &gt; Toys &gt; Art &amp; Drawing Toys &gt; Play Dough &amp; Putty</td>
    </tr>
    <tr>
      <td>Apollo Tools 53pc DT9773 Household Tool Kit with Tool Box Red</td>
      <td>Hardware &gt; Tools &gt; Tool Sets &gt; Hand Tool Sets</td>
    </tr>
    <tr>
      <td>Henckels Forged Classic Christopher Kimball 3pc Starter Knife Set</td>
      <td>Home &amp; Garden &gt; Kitchen &amp; Dining &gt; Kitchen Tools &amp; Utensils &gt; Kitchen Knives</td>
    </tr>
    <tr>
      <td>BOBS from Skechers Blue Printed Dog Walking Kit Medium</td>
      <td>Animals &amp; Pet Supplies &gt; Pet Supplies &gt; Dog Supplies</td>
    </tr>
    <tr>
      <td>ASICS Kid’s PRE EXCITE 8 Pre-School Running Shoe, 1M, Pink</td>
      <td>Apparel &amp; Accessories &gt; Shoes</td>
    </tr>
  </tbody>
</table>

<h2 id="methods--data-cleaning">Methods &amp; Data Cleaning</h2>

<p>The client wants to standadise product categorisation using google taxanomy format. Taxanomy data was downloaded <a href="https://support.google.com/merchants/answer/6324436?sjid=11384066497760467600-EU" target="_blank"> here</a>
Firstly l filtered out products not in google taxanomy format and then remove duplicates.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Clean product taxonomy file match it to google merchant catalogue 
</span><span class="n">google_catalogue</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">read_csv</span><span class="p">(</span><span class="sh">'</span><span class="s">taxonomy-with-ids-template.csv</span><span class="sh">'</span><span class="p">)</span>
<span class="c1"># Combine columns into one with "&gt;" separator, stopping at NaN
</span><span class="k">def</span> <span class="nf">combine_levels</span><span class="p">(</span><span class="n">row</span><span class="p">):</span>
    <span class="n">combined</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">row</span><span class="p">[</span><span class="mi">1</span><span class="p">:]:</span>  <span class="c1"># Skip the first column
</span>        <span class="k">if</span> <span class="n">pd</span><span class="p">.</span><span class="nf">isna</span><span class="p">(</span><span class="n">item</span><span class="p">):</span>
            <span class="k">break</span>
        <span class="n">combined</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="nf">str</span><span class="p">(</span><span class="n">item</span><span class="p">))</span>
    <span class="k">return</span> <span class="sh">'</span><span class="s"> &gt; </span><span class="sh">'</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="n">combined</span><span class="p">)</span>

<span class="n">google_catalogue</span><span class="p">[</span><span class="sh">'</span><span class="s">original_format_category</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="n">google_catalogue</span><span class="p">.</span><span class="nf">apply</span><span class="p">(</span><span class="n">combine_levels</span><span class="p">,</span> <span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
<span class="n">google_catalogue</span><span class="p">.</span><span class="nf">rename</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="p">{</span><span class="sh">"</span><span class="s">1</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">id</span><span class="sh">"</span><span class="p">},</span> <span class="n">inplace</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>

<span class="c1">#combine with product_taxanomy and remove nans for mis classification
</span><span class="n">new_taxanomy</span> <span class="o">=</span><span class="n">product_taxanomy</span><span class="p">.</span><span class="nf">merge</span><span class="p">(</span><span class="n">google_catalogue</span><span class="p">[[</span><span class="sh">'</span><span class="s">id</span><span class="sh">'</span><span class="p">,</span><span class="sh">'</span><span class="s">original_format_category</span><span class="sh">'</span><span class="p">]],</span> <span class="n">on</span><span class="o">=</span><span class="sh">'</span><span class="s">original_format_category</span><span class="sh">'</span><span class="p">,</span> <span class="n">how</span><span class="o">=</span><span class="sh">'</span><span class="s">left</span><span class="sh">'</span><span class="p">)</span>

<span class="c1">#remove categories not in google catalogue format
</span><span class="n">new_taxanomy</span><span class="p">.</span><span class="nf">dropna</span><span class="p">(</span><span class="n">subset</span><span class="o">=</span><span class="p">[</span><span class="sh">"</span><span class="s">id</span><span class="sh">"</span><span class="p">],</span><span class="n">inplace</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
</code></pre></div></div>
<p>l removed duplicate products because l assumed that each product belongs to one category, this could affect the prediction of unseen products</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Check for dupllicates
</span><span class="n">duplicates_2</span> <span class="o">=</span> <span class="n">new_taxanomy</span><span class="p">.</span><span class="nf">groupby</span><span class="p">(</span><span class="sh">'</span><span class="s">item_name</span><span class="sh">'</span><span class="p">).</span><span class="nf">size</span><span class="p">()</span>
<span class="n">duplicates_2</span> <span class="o">=</span><span class="n">duplicates_2</span><span class="p">[</span><span class="n">duplicates_2</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">].</span><span class="nf">sort_values</span><span class="p">(</span><span class="n">ascending</span><span class="o">=</span><span class="bp">False</span><span class="p">).</span><span class="nf">reset_index</span><span class="p">()</span>

<span class="c1">##remove duplicated items
</span><span class="n">new_taxanomy</span> <span class="o">=</span> <span class="n">new_taxanomy</span><span class="p">[</span><span class="o">~</span><span class="n">new_taxanomy</span><span class="p">[</span><span class="sh">'</span><span class="s">item_name</span><span class="sh">'</span><span class="p">].</span><span class="nf">isin</span><span class="p">(</span><span class="n">duplicates_2</span><span class="p">[</span><span class="sh">'</span><span class="s">item_name</span><span class="sh">'</span><span class="p">])]</span>

<span class="c1">#double check
</span><span class="n">zz</span> <span class="o">=</span><span class="n">new_taxanomy</span><span class="p">.</span><span class="nf">groupby</span><span class="p">(</span><span class="sh">'</span><span class="s">item_name</span><span class="sh">'</span><span class="p">).</span><span class="nf">size</span><span class="p">()</span>
<span class="n">zz</span><span class="p">[</span><span class="n">zz</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">].</span><span class="nf">sort_values</span><span class="p">(</span><span class="n">ascending</span><span class="o">=</span><span class="bp">False</span><span class="p">).</span><span class="nf">reset_index</span><span class="p">()</span><span class="c1"># double check
</span></code></pre></div></div>
<h3 id="text-normalisation"><strong>Text Normalisation</strong></h3>

<p>l normalized text before inputting it into a predictive model to ensure consistency and remove noise. This helps the model focus on meaningful patterns, improving accuracy and performance. l preprocessed the data by removing non-alphanumeric characters, stripping whitespace, and converting text to lowercase.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">requests</span>
<span class="kn">import</span> <span class="n">pandas</span> <span class="k">as</span> <span class="n">pd</span>
<span class="kn">import</span> <span class="n">re</span>
<span class="kn">from</span> <span class="n">nltk.tokenize</span> <span class="kn">import</span> <span class="n">word_tokenize</span>
<span class="kn">from</span> <span class="n">nltk.corpus</span> <span class="kn">import</span> <span class="n">stopwords</span>
<span class="kn">import</span> <span class="n">nltk</span>

<span class="c1"># Download necessary NLTK resources
</span><span class="n">nltk</span><span class="p">.</span><span class="nf">download</span><span class="p">(</span><span class="sh">'</span><span class="s">punkt</span><span class="sh">'</span><span class="p">)</span>
<span class="n">nltk</span><span class="p">.</span><span class="nf">download</span><span class="p">(</span><span class="sh">'</span><span class="s">stopwords</span><span class="sh">'</span><span class="p">)</span>

<span class="c1"># Function to clean text
</span><span class="k">def</span> <span class="nf">clean_text</span><span class="p">(</span><span class="n">text</span><span class="p">):</span>
    <span class="n">text</span> <span class="o">=</span> <span class="n">re</span><span class="p">.</span><span class="nf">sub</span><span class="p">(</span><span class="sa">r</span><span class="sh">'</span><span class="s">[^a-zA-Z0-9\s]</span><span class="sh">'</span><span class="p">,</span> <span class="sh">''</span><span class="p">,</span> <span class="n">text</span><span class="p">)</span> <span class="c1"># Remove non-alphanumeric characters
</span>    <span class="n">text</span> <span class="o">=</span><span class="n">text</span><span class="p">.</span><span class="nf">strip</span><span class="p">()</span> <span class="c1">#remove white space
</span>    <span class="n">text</span> <span class="o">=</span> <span class="n">text</span><span class="p">.</span><span class="nf">lower</span><span class="p">()</span> <span class="c1"># Convert text to lowercase
</span>    <span class="k">return</span> <span class="n">text</span>


<span class="n">new_taxanomy</span><span class="p">[</span><span class="sh">'</span><span class="s">new_name</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="n">new_taxanomy</span><span class="p">[</span><span class="sh">'</span><span class="s">item_name</span><span class="sh">'</span><span class="p">].</span><span class="nf">apply</span><span class="p">(</span><span class="n">clean_text</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="model-training--pipeline">Model Training &amp; Pipeline</h2>

<p>l created a two-level preprocessing and modeling pipeline, employing <code class="language-plaintext highlighter-rouge">TfidfVectorizer()</code> for transforming text data into term-frequency inverse document frequency (TF-IDF) vectors and <code class="language-plaintext highlighter-rouge">MultinomialNB()</code> for the Naive Bayes classification. Additionally, l utilized<code class="language-plaintext highlighter-rouge"> GridSearchCV</code> to perform hyperparameter tuning for optimal model parameters. This pipeline ensured efficient text normalization and robust classification performance.</p>

<p><strong>TF-IDF (Term Frequency-Inverse Document Frequency)</strong> is a powerful technique in Natural Language Processing used to convert text data into a statistically interpratable numerical feature vector that reflects the significance of a word within a document relative to a collection of documents, known as a corpus. It assigns a numerical value to each word (<em>TF-IDF score</em>) that quantifies the importance of each term (<em>word</em>) in a document (<em>string of product item name description</em>) by considering its frequency in the document and its rarity across multiple documents. The will then be used by machine learning model to categorise the products and hten convert the numerica feature vector back into text</p>

<h3 id="tf-idf-score">TF-IDF Score:</h3>
<p><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext>TF-IDF</mtext><mo stretchy="false">(</mo><mi>t</mi><mo separator="true">,</mo><mi>d</mi><mo stretchy="false">)</mo><mo>=</mo><mtext>TF</mtext><mo stretchy="false">(</mo><mi>t</mi><mo separator="true">,</mo><mi>d</mi><mo stretchy="false">)</mo><mo>×</mo><mtext>IDF</mtext><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\text{TF-IDF}(t, d) = \text{TF}(t, d) \times \text{IDF}(t)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord text"><span class="mord">TF-IDF</span></span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">d</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord text"><span class="mord">TF</span></span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">d</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord text"><span class="mord">IDF</span></span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mclose">)</span></span></span></span></p>

<h3 id="term-frequency-tf">Term Frequency (TF):</h3>

<p><strong>Definition</strong>: Measures the frequency of a term in a document.</p>

<p><strong>Formula</strong>:</p>

<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>TF</mtext><mo stretchy="false">(</mo><mi>t</mi><mo separator="true">,</mo><mi>d</mi><mo stretchy="false">)</mo><mo>=</mo><mfrac><mrow><mtext>Number of times term </mtext><mi>t</mi><mtext> appears in document </mtext><mi>d</mi></mrow><mrow><mtext>Total number of terms in document </mtext><mi>d</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">\text{TF}(t, d) = \frac{\text{Number of times term } t \text{ appears in document } d}{\text{Total number of terms in document } d}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord text"><span class="mord">TF</span></span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">d</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.0574em;vertical-align:-0.686em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3714em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">Total number of terms in document </span></span><span class="mord mathnormal">d</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">Number of times term </span></span><span class="mord mathnormal">t</span><span class="mord text"><span class="mord"> appears in document </span></span><span class="mord mathnormal">d</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span>

<p><strong>Purpose</strong>: Captures the importance of a term within a specific document.</p>

<h3 id="inverse-document-frequency-idf">Inverse Document Frequency (IDF):</h3>

<p><strong>Definition</strong>: Measures the rarity of a term across all documents in the corpus.</p>

<p><strong>Formula</strong>:</p>

<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mtext>IDF</mtext><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo><mo>=</mo><mi>log</mi><mo>⁡</mo><mrow><mo fence="true">(</mo><mfrac><mtext>Total number of documents</mtext><mrow><mtext>Number of documents containing term </mtext><mi>t</mi></mrow></mfrac><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\text{IDF}(t) = \log \left(\frac{\text{Total number of documents}}{\text{Number of documents containing term } t}\right)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord text"><span class="mord">IDF</span></span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="mop">lo<span style="margin-right:0.01389em;">g</span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">(</span></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3714em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">Number of documents containing term </span></span><span class="mord mathnormal">t</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">Total number of documents</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.8804em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">)</span></span></span></span></span></span></span>

<p><strong>Purpose</strong>: Assigns higher weights to terms that are rare across the corpus, reducing the impact of common terms.</p>

<p>Because of the size of the data…</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#count number of unique values
</span><span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">'</span><span class="s">Number of product categories: </span><span class="si">{</span><span class="n">new_taxanomy</span><span class="p">[</span><span class="sh">"</span><span class="s">target_label</span><span class="sh">"</span><span class="p">].</span><span class="nf">nunique</span><span class="p">()</span><span class="si">}</span><span class="s"> </span><span class="sh">'</span> <span class="p">)</span> 
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">'</span><span class="s">Number of product items: </span><span class="si">{</span><span class="n">new_taxanomy</span><span class="p">[</span><span class="sh">"</span><span class="s">new_name</span><span class="sh">"</span><span class="p">].</span><span class="nf">nunique</span><span class="p">()</span><span class="si">}</span><span class="s"> </span><span class="sh">'</span> <span class="p">)</span> 

<span class="n">Number</span> <span class="n">of</span> <span class="n">product</span> <span class="n">categories</span><span class="p">:</span> <span class="mi">2871</span> 
<span class="n">Number</span> <span class="n">of</span> <span class="n">product</span> <span class="n">items</span><span class="p">:</span> <span class="mi">577600</span> 

</code></pre></div></div>
<p>l used a Multinomial Naive Bayes for product classification because it is both robust (it can handle multidimensional text data) and efficient (it can handle large data and is not computationally intensive). After applying TfidfVectorizer(), which converts text data into numerical TF-IDF vectors, the Multinomial Naive Bayes algorithm becomes more accurate in distinguishing between different product categories based on textual features.</p>

<h3 id="model-implementation">Model Implementation</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#THIS CODE TOOK THREE HOURS TO RUN DO NOT RE-RUN
</span><span class="kn">from</span> <span class="n">sklearn.feature_extraction.text</span> <span class="kn">import</span> <span class="n">TfidfVectorizer</span>
<span class="kn">from</span> <span class="n">sklearn.naive_bayes</span> <span class="kn">import</span> <span class="n">MultinomialNB</span>
<span class="kn">from</span> <span class="n">sklearn.pipeline</span> <span class="kn">import</span> <span class="n">Pipeline</span>
<span class="kn">from</span> <span class="n">sklearn.model_selection</span> <span class="kn">import</span> <span class="n">GridSearchCV</span><span class="p">,</span> <span class="n">train_test_split</span>

<span class="c1"># Split data into training and testing sets
</span><span class="n">X_train</span><span class="p">,</span> <span class="n">X_test</span><span class="p">,</span> <span class="n">y_train</span><span class="p">,</span> <span class="n">y_test</span> <span class="o">=</span> <span class="nf">train_test_split</span><span class="p">(</span><span class="n">X</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span><span class="n">test_size</span><span class="o">=</span><span class="mf">0.2</span><span class="p">,</span> <span class="n">random_state</span><span class="o">=</span><span class="mi">42</span><span class="p">)</span><span class="c1">## no need to sub-sample teh data
# Define the pipeline
</span><span class="n">pipeline_2</span> <span class="o">=</span> <span class="nc">Pipeline</span><span class="p">([</span>
    <span class="p">(</span><span class="sh">'</span><span class="s">tfidf</span><span class="sh">'</span><span class="p">,</span> <span class="nc">TfidfVectorizer</span><span class="p">()),</span>
    <span class="p">(</span><span class="sh">'</span><span class="s">nb</span><span class="sh">'</span><span class="p">,</span> <span class="nc">MultinomialNB</span><span class="p">(</span><span class="n">alpha</span><span class="o">=</span><span class="mf">0.0001</span><span class="p">))</span>
<span class="p">])</span>

<span class="c1"># Define the parameter grid for grid search
</span><span class="n">param_grid</span> <span class="o">=</span> <span class="p">{</span>
    <span class="sh">'</span><span class="s">tfidf__max_features</span><span class="sh">'</span><span class="p">:</span> <span class="p">[</span><span class="mi">200000</span><span class="p">,</span> <span class="mi">350000</span><span class="p">,</span> <span class="mi">500000</span><span class="p">],</span>
    <span class="sh">'</span><span class="s">tfidf__ngram_range</span><span class="sh">'</span><span class="p">:</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="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">)],</span>
    <span class="sh">'</span><span class="s">nb__alpha</span><span class="sh">'</span><span class="p">:</span> <span class="p">[</span><span class="mf">0.0001</span><span class="p">,</span> <span class="mf">0.001</span><span class="p">,</span> <span class="mf">0.01</span><span class="p">,</span> <span class="mf">0.1</span><span class="p">]</span>
<span class="p">}</span>

<span class="c1"># Initialize GridSearchCV
</span><span class="n">grid_search</span> <span class="o">=</span> <span class="nc">GridSearchCV</span><span class="p">(</span><span class="n">pipeline_2</span><span class="p">,</span> <span class="n">param_grid</span><span class="p">,</span> <span class="n">cv</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">n_jobs</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">verbose</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>

<span class="c1"># Fit the model
</span><span class="n">grid_search</span><span class="p">.</span><span class="nf">fit</span><span class="p">(</span><span class="n">X_train</span><span class="p">,</span> <span class="n">y_train</span><span class="p">)</span>

<span class="c1"># Best parameters and best score
</span><span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Best parameters found: </span><span class="sh">"</span><span class="p">,</span> <span class="n">grid_search</span><span class="p">.</span><span class="n">best_params_</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Best cross-validated score: </span><span class="sh">"</span><span class="p">,</span> <span class="n">grid_search</span><span class="p">.</span><span class="n">best_score_</span><span class="p">)</span>

<span class="c1"># Evaluate on the test set
</span><span class="n">accuracy</span> <span class="o">=</span> <span class="n">grid_search</span><span class="p">.</span><span class="nf">score</span><span class="p">(</span><span class="n">X_test</span><span class="p">,</span> <span class="n">y_test</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Test set accuracy: </span><span class="sh">"</span><span class="p">,</span> <span class="n">accuracy</span><span class="p">)</span>

</code></pre></div></div>
<pre>
Best parameters found:  {'nb__alpha': 0.001, 'tfidf__max_features': 500000, 'tfidf__ngram_range': (1, 2)}
Best cross-validated score:  0.8641511636958045
Test set accuracy:  0.8747221573562354
</pre>

<h2 id="results">Results</h2>
<h3 id="final-model">Final Model</h3>

<p>l employed the model with the highest cross-validation and test accuracy</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#Best model parameter
</span><span class="n">pipeline_2</span> <span class="o">=</span> <span class="nc">Pipeline</span><span class="p">([</span>
    <span class="p">(</span><span class="sh">'</span><span class="s">tfidf</span><span class="sh">'</span><span class="p">,</span> <span class="nc">TfidfVectorizer</span><span class="p">(</span><span class="n">max_df</span><span class="o">=</span><span class="mf">0.75</span><span class="p">,</span> 
                              <span class="n">min_df</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">max_features</span><span class="o">=</span><span class="mi">500000</span><span class="p">,</span> 
                              <span class="n">ngram_range</span><span class="o">=</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span> <span class="n">stop_words</span><span class="o">=</span><span class="sh">'</span><span class="s">english</span><span class="sh">'</span><span class="p">,</span> 
                               <span class="n">use_idf</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> 
                              <span class="n">smooth_idf</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> 
                              <span class="n">sublinear_tf</span><span class="o">=</span><span class="bp">True</span><span class="p">)),</span>
    <span class="p">(</span><span class="sh">'</span><span class="s">nb</span><span class="sh">'</span><span class="p">,</span> <span class="nc">MultinomialNB</span><span class="p">(</span><span class="n">alpha</span><span class="o">=</span><span class="mf">0.001</span><span class="p">))</span>
<span class="p">])</span>
</code></pre></div></div>
<h3 id="evaluation-and-prediction">Evaluation and Prediction</h3>

<p>To evaluate our model, l used precision, recall, F1-score, and accuracy. l also calculated macro and micro averages to better understand the model’s performance with unbalanced data. <strong>Macro averages</strong> treat all classes equally, providing a balanced view across classes, while <strong>micro averages</strong> consider the frequency of each class, giving a more holistic measure of the overall performance.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">sklearn.metrics</span> <span class="kn">import</span> <span class="n">accuracy_score</span><span class="p">,</span> <span class="n">precision_score</span><span class="p">,</span> <span class="n">recall_score</span><span class="p">,</span> <span class="n">f1_score</span>
<span class="kn">import</span> <span class="n">pandas</span> <span class="k">as</span> <span class="n">pd</span>

<span class="c1"># Predict on the test set
</span><span class="n">y_pred_2</span> <span class="o">=</span> <span class="n">pipeline_2</span><span class="p">.</span><span class="nf">predict</span><span class="p">(</span><span class="n">X_test</span><span class="p">)</span>

<span class="c1"># Assuming y_test and y_pred_2 are already defined
</span><span class="n">accuracy</span> <span class="o">=</span> <span class="nf">accuracy_score</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span> <span class="n">y_pred_2</span><span class="p">)</span>
<span class="n">precision_macro</span> <span class="o">=</span> <span class="nf">precision_score</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span> <span class="n">y_pred_2</span><span class="p">,</span> <span class="n">average</span><span class="o">=</span><span class="sh">'</span><span class="s">macro</span><span class="sh">'</span><span class="p">,</span> <span class="n">zero_division</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
<span class="n">recall_macro</span> <span class="o">=</span> <span class="nf">recall_score</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span> <span class="n">y_pred_2</span><span class="p">,</span> <span class="n">average</span><span class="o">=</span><span class="sh">'</span><span class="s">macro</span><span class="sh">'</span><span class="p">,</span> <span class="n">zero_division</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
<span class="n">f1_macro</span> <span class="o">=</span> <span class="nf">f1_score</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span> <span class="n">y_pred_2</span><span class="p">,</span> <span class="n">average</span><span class="o">=</span><span class="sh">'</span><span class="s">macro</span><span class="sh">'</span><span class="p">,</span> <span class="n">zero_division</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
<span class="n">precision_micro</span> <span class="o">=</span> <span class="nf">precision_score</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span> <span class="n">y_pred_2</span><span class="p">,</span> <span class="n">average</span><span class="o">=</span><span class="sh">'</span><span class="s">micro</span><span class="sh">'</span><span class="p">,</span> <span class="n">zero_division</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
<span class="n">recall_micro</span> <span class="o">=</span> <span class="nf">recall_score</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span> <span class="n">y_pred_2</span><span class="p">,</span> <span class="n">average</span><span class="o">=</span><span class="sh">'</span><span class="s">micro</span><span class="sh">'</span><span class="p">,</span> <span class="n">zero_division</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
<span class="n">f1_micro</span> <span class="o">=</span> <span class="nf">f1_score</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span> <span class="n">y_pred_2</span><span class="p">,</span> <span class="n">average</span><span class="o">=</span><span class="sh">'</span><span class="s">micro</span><span class="sh">'</span><span class="p">,</span> <span class="n">zero_division</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>

<span class="c1"># Create a dictionary of the metrics
</span><span class="n">metrics_data</span> <span class="o">=</span> <span class="p">{</span>
    <span class="sh">'</span><span class="s">Accuracy</span><span class="sh">'</span><span class="p">:</span> <span class="p">[</span><span class="n">accuracy</span><span class="p">,</span> <span class="n">accuracy</span><span class="p">],</span>  <span class="c1"># Accuracy is the same for both macro and micro
</span>    <span class="sh">'</span><span class="s">Precision</span><span class="sh">'</span><span class="p">:</span> <span class="p">[</span><span class="n">precision_macro</span><span class="p">,</span> <span class="n">precision_micro</span><span class="p">],</span>
    <span class="sh">'</span><span class="s">Recall</span><span class="sh">'</span><span class="p">:</span> <span class="p">[</span><span class="n">recall_macro</span><span class="p">,</span> <span class="n">recall_micro</span><span class="p">],</span>
    <span class="sh">'</span><span class="s">F1 Score</span><span class="sh">'</span><span class="p">:</span> <span class="p">[</span><span class="n">f1_macro</span><span class="p">,</span> <span class="n">f1_micro</span><span class="p">]</span>
<span class="p">}</span>

<span class="c1"># Specify row labels
</span><span class="n">row_labels</span> <span class="o">=</span> <span class="p">[</span><span class="sh">'</span><span class="s">Macro Avg</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">Micro Avg</span><span class="sh">'</span><span class="p">]</span>

<span class="c1"># Convert dictionary to a DataFrame
</span><span class="n">metrics_df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nc">DataFrame</span><span class="p">(</span><span class="n">metrics_data</span><span class="p">,</span> <span class="n">index</span><span class="o">=</span><span class="n">row_labels</span><span class="p">)</span>

<span class="c1"># Display the table
</span><span class="nf">print</span><span class="p">(</span><span class="n">metrics_df</span><span class="p">)</span>

</code></pre></div></div>
<pre>
           Accuracy  Precision  Recall   F1 Score
Macro Avg    0.88      0.62      0.54      0.56
Micro Avg    0.88      0.88      0.88      0.88
</pre>
<p>The low macro averages indicates that the model’s performance is not consistent across all classes. It suggests that some classes may have lower precision, recall, or F1 scores, potentially due to class imbalance or the model struggling with specific categories.</p>

<p>On the other end high micro average means that the model performs well overall when considering all classes and their frequencies. This reflects strong overall accuracy and suggests that the model correctly identifies a large proportion of instances, even if some individual classes are less well-predicted.</p>

<h2 id="part-2">PART 2</h2>

<p>Due to high traffic during Black Friday week, the product classification model was used to categorize products. Sales data was analyzed to reveal valuable trends in buying patterns, such as popular products, peak shopping times, and discrepancies between regional sales.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">pandas</span> <span class="k">as</span> <span class="n">pd</span>
<span class="n">product_sales</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">read_csv</span><span class="p">(</span><span class="sh">'</span><span class="s">ProductDataChallenge_01_ProductSales_thanksgiving_week.csv</span><span class="sh">'</span><span class="p">)</span>

<span class="c1"># Predict categories for unlabeled data
</span><span class="n">product_sales</span><span class="p">[</span><span class="sh">'</span><span class="s">target_label</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="n">pipeline_2</span><span class="p">.</span><span class="nf">predict</span><span class="p">(</span><span class="n">product_sales</span><span class="p">[</span><span class="sh">'</span><span class="s">item_name</span><span class="sh">'</span><span class="p">])</span>
</code></pre></div></div>
<p>An interaction Shiny dashboard was created showcasing Product sales KPIs, trends and business recommendations. For more information click  <a href="https://fariedakwa.netlify.app/blog/sales-trends-analysis/" target="_blank"> here</a></p>]]></content><author><name>Farisayi Dakwa</name><email>fariedakwa.fd@gmail.com</email></author><category term="Data Science" /><category term="Regression" /><category term="Python" /><category term="Machine Learning" /><summary type="html"><![CDATA[In this project, I built a text classifcation model to predict categories of > 700 000 products item descriptions, leveraging the power of Multinomial Naive Bayes to handle large datasets. My model had an accuracy of 88%]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="/assets/img/blog/product%20clasification/texto.jpg" /><media:content medium="image" url="/assets/img/blog/product%20clasification/texto.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Using Random Forest to Predict Customer Churn</title><link href="/blog/BCG-Job-simulation/" rel="alternate" type="text/html" title="Using Random Forest to Predict Customer Churn" /><published>2024-06-30T00:00:00+00:00</published><updated>2024-09-16T13:54:07+00:00</updated><id>/blog/BCG%20Job%20simulation</id><content type="html" xml:base="/blog/BCG-Job-simulation/"><![CDATA[<p><strong>Price sensitivity is the degree to which demand changes when the cost of energy (electricity and gas) changes.</strong> ~ <em>PowerCo</em>.</p>

<p><strong>Complete Project repository:</strong>
<a href="https://github.com/fariedd/BCG-Junior-data-scientist-job-simulation" target="_blank"><img src="https://img.shields.io/badge/GitHub-View_in_GitHub-blue?logo=GitHub" alt="" /></a></p>

<ul id="markdown-toc">
  <li><a href="#business-understanding--problem-framing" id="markdown-toc-business-understanding--problem-framing">Business understanding &amp; Problem framing</a></li>
  <li><a href="#exploratory-data-analysis--data-cleaning" id="markdown-toc-exploratory-data-analysis--data-cleaning">Exploratory data analysis &amp; data cleaning</a>    <ul>
      <li><a href="#data-overview" id="markdown-toc-data-overview">Data Overview</a></li>
      <li><a href="#data-import" id="markdown-toc-data-import">Data Import</a></li>
      <li><a href="#descriptive-statistics" id="markdown-toc-descriptive-statistics">Descriptive statistics</a></li>
    </ul>
  </li>
  <li><a href="#feature-engineering--hypothesis-framing" id="markdown-toc-feature-engineering--hypothesis-framing">Feature engineering &amp; Hypothesis framing</a></li>
  <li><a href="#modelling-and-evaluation" id="markdown-toc-modelling-and-evaluation">Modelling and evaluation</a>    <ul>
      <li><a href="#model-training" id="markdown-toc-model-training">Model Training</a></li>
      <li><a href="#model-evaluation" id="markdown-toc-model-evaluation">Model Evaluation</a></li>
      <li><a href="#model-understanding" id="markdown-toc-model-understanding">Model understanding</a></li>
    </ul>
  </li>
  <li><a href="#insights--recommendations" id="markdown-toc-insights--recommendations">Insights &amp; Recommendations</a></li>
</ul>

<h2 id="business-understanding--problem-framing">Business understanding &amp; Problem framing</h2>

<p>Our client PowerCo - a major gas and electricity utility that supplies to small and medium sized enterprises is concerned about their customers leaving for better offers from other energy providers (<strong>customer churn</strong>). The energy market has had a lot of change in recent years nd there are now more options than ever for customers to choose from. They would like to diagnose the the reason why their customers are churning. One of the leading objective is - <em>How sensitive are PowerCo customers to pricing and how does it influence the customer’s decision stay or leave PowerCo.</em></p>

<p><strong>Data required;</strong></p>

<ol>
  <li>Customer data - historical electricity and gas consumption,  date customer joined (subscribed for services)</li>
  <li>Churn data - indicating if customer churned or not (reclassified as 1 nad 0)</li>
  <li>Historical and seasonal pricing data - the price the client charges its customers and how it changes with demand.</li>
</ol>

<p><strong>Work Plan;</strong></p>

<p>In order to test the hypothesis of how pricing affects churn, we defined what data we need from PowerCo, and what variables best define price sensitivity, prepare the data and engineer features. A binary classificantion model was used and trained to extrapolate the extent to which price sensitivity influences churn.</p>

<h2 id="exploratory-data-analysis--data-cleaning">Exploratory data analysis &amp; data cleaning</h2>
<h3 id="data-overview">Data Overview</h3>

<div style="display: flex; justify-content: space-between;">
    <img src="/assets/img/blog/BCG job simulation/pico1.png" alt="Image 1" style="width: 50%; height: auto;" />
    <img src="/assets/img/blog/BCG job simulation/pico2.png" alt="Image 2" style="width: 50%; height: auto;" />
</div>

<h3 id="data-import">Data Import</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>
<span class="kn">import</span> <span class="n">seaborn</span> <span class="k">as</span> <span class="n">sns</span>
<span class="kn">import</span> <span class="n">pandas</span> <span class="k">as</span> <span class="n">pd</span>
<span class="kn">import</span> <span class="n">numpy</span> <span class="k">as</span> <span class="n">np</span>

<span class="c1"># Shows plots in jupyter notebook
</span><span class="o">%</span><span class="n">matplotlib</span> <span class="n">inline</span>

<span class="c1"># Set plot style
</span><span class="n">sns</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="n">color_codes</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>

<span class="n">client_df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">read_csv</span><span class="p">(</span><span class="sh">'</span><span class="s">client_data.csv</span><span class="sh">'</span><span class="p">)</span>
<span class="n">price_df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">read_csv</span><span class="p">(</span><span class="sh">'</span><span class="s">price_data.csv</span><span class="sh">'</span><span class="p">)</span>

</code></pre></div></div>
<p>For modelling purposes we convert columns into date time</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">client_df</span><span class="p">[</span><span class="sh">'</span><span class="s">date_activ</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">to_datetime</span><span class="p">(</span> <span class="n">client_df</span><span class="p">[</span><span class="sh">'</span><span class="s">date_activ</span><span class="sh">'</span><span class="p">])</span>
<span class="n">client_df</span><span class="p">[</span><span class="sh">'</span><span class="s">date_end</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">to_datetime</span><span class="p">(</span> <span class="n">client_df</span><span class="p">[</span><span class="sh">'</span><span class="s">date_end</span><span class="sh">'</span><span class="p">])</span>
<span class="n">client_df</span><span class="p">[</span><span class="sh">'</span><span class="s">date_modif_prod</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">to_datetime</span><span class="p">(</span> <span class="n">client_df</span><span class="p">[</span><span class="sh">'</span><span class="s">date_modif_prod</span><span class="sh">'</span><span class="p">])</span>
<span class="n">client_df</span><span class="p">[</span><span class="sh">'</span><span class="s">date_renewal</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">to_datetime</span><span class="p">(</span> <span class="n">client_df</span><span class="p">[</span><span class="sh">'</span><span class="s">date_renewal</span><span class="sh">'</span><span class="p">])</span>
<span class="n">client_df</span><span class="p">.</span><span class="nf">head</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>
</code></pre></div></div>
<p>It is useful to first understand the data that you’re dealing with along with the data types of each column and na values in those columns. The data types may dictate how you transform and engineer features.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Check for column data types
</span><span class="nf">print</span><span class="p">(</span><span class="n">client_df</span><span class="p">.</span><span class="nf">info</span><span class="p">())</span>
<span class="nf">print</span><span class="p">(</span><span class="n">price_df</span><span class="p">.</span><span class="nf">info</span><span class="p">())</span>

<span class="c1">#Check for missing values in columns
</span><span class="nf">print</span><span class="p">(</span><span class="n">client_df</span><span class="p">.</span><span class="nf">isna</span><span class="p">().</span><span class="nf">sum</span><span class="p">())</span>
<span class="nf">print</span><span class="p">(</span><span class="n">price_df</span><span class="p">.</span><span class="nf">isna</span><span class="p">().</span><span class="nf">sum</span><span class="p">())</span>

</code></pre></div></div>
<h3 id="descriptive-statistics">Descriptive statistics</h3>

<p>To visualise the statistics of churned customers, we used function ‘plot_stacked_bars’. The code below shows that close to 10% of PowerCo customers churned in the last three months</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="n">churn</span> <span class="o">=</span> <span class="n">client_df</span><span class="p">[[</span><span class="sh">'</span><span class="s">id</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">churn</span><span class="sh">'</span><span class="p">]]</span>
<span class="n">churn</span><span class="p">.</span><span class="n">columns</span> <span class="o">=</span> <span class="p">[</span><span class="sh">'</span><span class="s">Companies</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">churn</span><span class="sh">'</span><span class="p">]</span>
<span class="n">churn_total</span> <span class="o">=</span> <span class="n">churn</span><span class="p">.</span><span class="nf">groupby</span><span class="p">(</span><span class="n">churn</span><span class="p">[</span><span class="sh">'</span><span class="s">churn</span><span class="sh">'</span><span class="p">]).</span><span class="nf">count</span><span class="p">()</span>
<span class="n">churn_percentage</span> <span class="o">=</span> <span class="n">churn_total</span> <span class="o">/</span> <span class="n">churn_total</span><span class="p">.</span><span class="nf">sum</span><span class="p">()</span> <span class="o">*</span> <span class="mi">100</span>
<span class="nf">plot_stacked_bars</span><span class="p">(</span><span class="n">churn_percentage</span><span class="p">.</span><span class="nf">transpose</span><span class="p">(),</span> <span class="sh">"</span><span class="s">Churning status</span><span class="sh">"</span><span class="p">,</span> <span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">),</span> <span class="n">legend_</span><span class="o">=</span><span class="sh">"</span><span class="s">lower right</span><span class="sh">"</span><span class="p">)</span>

</code></pre></div></div>
<p><img src="/assets/img/blog/BCG%20job%20simulation/imago.png" alt="box plot" /></p>

<p><strong>Complete exploratory data analysis script</strong> - <a href="https://github.com/fariedd/BCG-Junior-data-scientist-job-simulation/blob/main/Exploratory%20data%20analysis%20starter.ipynb" target="_blank"><img src="https://img.shields.io/badge/GitHub-View_in_GitHub-blue?logo=GitHub" alt="" /></a></p>

<h2 id="feature-engineering--hypothesis-framing">Feature engineering &amp; Hypothesis framing</h2>

<p><strong>Hypothesis 1</strong>: <em>“The difference between off-peak prices in December and January the preceding year could be a significant feature when predicting customer churn”</em></p>

<p><strong>Load cleaned data set after EDA</strong></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">read_csv</span><span class="p">(</span><span class="sh">'</span><span class="s">C:/Users/farie/Downloads/clean_data_after_eda.csv</span><span class="sh">'</span><span class="p">)</span>
<span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">date_activ</span><span class="sh">"</span><span class="p">]</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">to_datetime</span><span class="p">(</span><span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">date_activ</span><span class="sh">"</span><span class="p">],</span> <span class="nb">format</span><span class="o">=</span><span class="sh">'</span><span class="s">%Y-%m-%d</span><span class="sh">'</span><span class="p">)</span>
<span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">date_end</span><span class="sh">"</span><span class="p">]</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">to_datetime</span><span class="p">(</span><span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">date_end</span><span class="sh">"</span><span class="p">],</span> <span class="nb">format</span><span class="o">=</span><span class="sh">'</span><span class="s">%Y-%m-%d</span><span class="sh">'</span><span class="p">)</span>
<span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">date_modif_prod</span><span class="sh">"</span><span class="p">]</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">to_datetime</span><span class="p">(</span><span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">date_modif_prod</span><span class="sh">"</span><span class="p">],</span> <span class="nb">format</span><span class="o">=</span><span class="sh">'</span><span class="s">%Y-%m-%d</span><span class="sh">'</span><span class="p">)</span>
<span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">date_renewal</span><span class="sh">"</span><span class="p">]</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">to_datetime</span><span class="p">(</span><span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">date_renewal</span><span class="sh">"</span><span class="p">],</span> <span class="nb">format</span><span class="o">=</span><span class="sh">'</span><span class="s">%Y-%m-%d</span><span class="sh">'</span><span class="p">)</span>


</code></pre></div></div>
<p>Calculate the difference between off-peak prices in December and preceding January</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">price_df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">read_csv</span><span class="p">(</span><span class="sh">'</span><span class="s">price_data.csv</span><span class="sh">'</span><span class="p">)</span>
<span class="n">price_df</span><span class="p">[</span><span class="sh">"</span><span class="s">price_date</span><span class="sh">"</span><span class="p">]</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">to_datetime</span><span class="p">(</span><span class="n">price_df</span><span class="p">[</span><span class="sh">"</span><span class="s">price_date</span><span class="sh">"</span><span class="p">],</span> <span class="nb">format</span><span class="o">=</span><span class="sh">'</span><span class="s">%Y-%m-%d</span><span class="sh">'</span><span class="p">)</span>
<span class="n">price_df</span><span class="p">.</span><span class="nf">head</span><span class="p">()</span>

<span class="c1"># Group off-peak prices by companies and month
</span><span class="n">monthly_price_by_id</span> <span class="o">=</span> <span class="n">price_df</span><span class="p">.</span><span class="nf">groupby</span><span class="p">([</span><span class="sh">'</span><span class="s">id</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">price_date</span><span class="sh">'</span><span class="p">]).</span><span class="nf">agg</span><span class="p">({</span><span class="sh">'</span><span class="s">price_off_peak_var</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">mean</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">price_off_peak_fix</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">mean</span><span class="sh">'</span><span class="p">}).</span><span class="nf">reset_index</span><span class="p">()</span>

<span class="c1"># Get january and december prices
</span><span class="n">jan_prices</span> <span class="o">=</span> <span class="n">monthly_price_by_id</span><span class="p">.</span><span class="nf">groupby</span><span class="p">(</span><span class="sh">'</span><span class="s">id</span><span class="sh">'</span><span class="p">).</span><span class="nf">first</span><span class="p">().</span><span class="nf">reset_index</span><span class="p">()</span>
<span class="n">dec_prices</span> <span class="o">=</span> <span class="n">monthly_price_by_id</span><span class="p">.</span><span class="nf">groupby</span><span class="p">(</span><span class="sh">'</span><span class="s">id</span><span class="sh">'</span><span class="p">).</span><span class="nf">last</span><span class="p">().</span><span class="nf">reset_index</span><span class="p">()</span>

<span class="c1"># Calculate the difference
</span><span class="n">diff</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">merge</span><span class="p">(</span><span class="n">dec_prices</span><span class="p">.</span><span class="nf">rename</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="p">{</span><span class="sh">'</span><span class="s">price_off_peak_var</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">dec_1</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">price_off_peak_fix</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">dec_2</span><span class="sh">'</span><span class="p">}),</span> <span class="n">jan_prices</span><span class="p">.</span><span class="nf">drop</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="sh">'</span><span class="s">price_date</span><span class="sh">'</span><span class="p">),</span> <span class="n">on</span><span class="o">=</span><span class="sh">'</span><span class="s">id</span><span class="sh">'</span><span class="p">)</span>
<span class="n">diff</span><span class="p">[</span><span class="sh">'</span><span class="s">offpeak_diff_dec_january_energy</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="n">diff</span><span class="p">[</span><span class="sh">'</span><span class="s">dec_1</span><span class="sh">'</span><span class="p">]</span> <span class="o">-</span> <span class="n">diff</span><span class="p">[</span><span class="sh">'</span><span class="s">price_off_peak_var</span><span class="sh">'</span><span class="p">]</span>
<span class="n">diff</span><span class="p">[</span><span class="sh">'</span><span class="s">offpeak_diff_dec_january_power</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="n">diff</span><span class="p">[</span><span class="sh">'</span><span class="s">dec_2</span><span class="sh">'</span><span class="p">]</span> <span class="o">-</span> <span class="n">diff</span><span class="p">[</span><span class="sh">'</span><span class="s">price_off_peak_fix</span><span class="sh">'</span><span class="p">]</span>
<span class="n">diff</span> <span class="o">=</span> <span class="n">diff</span><span class="p">[[</span><span class="sh">'</span><span class="s">id</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">offpeak_diff_dec_january_energy</span><span class="sh">'</span><span class="p">,</span><span class="sh">'</span><span class="s">offpeak_diff_dec_january_power</span><span class="sh">'</span><span class="p">]]</span>

<span class="c1">#merger files
</span><span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">merge</span><span class="p">(</span><span class="n">df</span><span class="p">,</span> <span class="n">diff</span><span class="p">,</span> <span class="n">on</span><span class="o">=</span><span class="sh">'</span><span class="s">id</span><span class="sh">'</span><span class="p">)</span>
<span class="n">df</span><span class="p">.</span><span class="nf">head</span><span class="p">()</span>
</code></pre></div></div>
<p><strong>Hypothesis 2</strong>: <em>“Average and max price changes between periods (peak and low season) through out the entire year could be a significant feature when predicting customer churn”</em></p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">mean_prices</span> <span class="o">=</span> <span class="n">price_df</span><span class="p">.</span><span class="nf">groupby</span><span class="p">([</span><span class="sh">'</span><span class="s">id</span><span class="sh">'</span><span class="p">]).</span><span class="nf">agg</span><span class="p">({</span>
    <span class="sh">'</span><span class="s">price_off_peak_var</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">mean</span><span class="sh">'</span><span class="p">,</span> 
    <span class="sh">'</span><span class="s">price_peak_var</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">mean</span><span class="sh">'</span><span class="p">,</span> 
    <span class="sh">'</span><span class="s">price_mid_peak_var</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">mean</span><span class="sh">'</span><span class="p">,</span>
    <span class="sh">'</span><span class="s">price_off_peak_fix</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">mean</span><span class="sh">'</span><span class="p">,</span>
    <span class="sh">'</span><span class="s">price_peak_fix</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">mean</span><span class="sh">'</span><span class="p">,</span>
    <span class="sh">'</span><span class="s">price_mid_peak_fix</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">mean</span><span class="sh">'</span>    
<span class="p">}).</span><span class="nf">reset_index</span><span class="p">()</span>

<span class="c1"># Calculate the mean difference between consecutive periods
</span><span class="n">mean_prices</span><span class="p">[</span><span class="sh">'</span><span class="s">off_peak_peak_var_mean_diff</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="n">mean_prices</span><span class="p">[</span><span class="sh">'</span><span class="s">price_off_peak_var</span><span class="sh">'</span><span class="p">]</span> <span class="o">-</span> <span class="n">mean_prices</span><span class="p">[</span><span class="sh">'</span><span class="s">price_peak_var</span><span class="sh">'</span><span class="p">]</span>
<span class="n">mean_prices</span><span class="p">[</span><span class="sh">'</span><span class="s">peak_mid_peak_var_mean_diff</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="n">mean_prices</span><span class="p">[</span><span class="sh">'</span><span class="s">price_peak_var</span><span class="sh">'</span><span class="p">]</span> <span class="o">-</span> <span class="n">mean_prices</span><span class="p">[</span><span class="sh">'</span><span class="s">price_mid_peak_var</span><span class="sh">'</span><span class="p">]</span>
<span class="n">mean_prices</span><span class="p">[</span><span class="sh">'</span><span class="s">off_peak_mid_peak_var_mean_diff</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="n">mean_prices</span><span class="p">[</span><span class="sh">'</span><span class="s">price_off_peak_var</span><span class="sh">'</span><span class="p">]</span> <span class="o">-</span> <span class="n">mean_prices</span><span class="p">[</span><span class="sh">'</span><span class="s">price_mid_peak_var</span><span class="sh">'</span><span class="p">]</span>
<span class="n">mean_prices</span><span class="p">[</span><span class="sh">'</span><span class="s">off_peak_peak_fix_mean_diff</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="n">mean_prices</span><span class="p">[</span><span class="sh">'</span><span class="s">price_off_peak_fix</span><span class="sh">'</span><span class="p">]</span> <span class="o">-</span> <span class="n">mean_prices</span><span class="p">[</span><span class="sh">'</span><span class="s">price_peak_fix</span><span class="sh">'</span><span class="p">]</span>
<span class="n">mean_prices</span><span class="p">[</span><span class="sh">'</span><span class="s">peak_mid_peak_fix_mean_diff</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="n">mean_prices</span><span class="p">[</span><span class="sh">'</span><span class="s">price_peak_fix</span><span class="sh">'</span><span class="p">]</span> <span class="o">-</span> <span class="n">mean_prices</span><span class="p">[</span><span class="sh">'</span><span class="s">price_mid_peak_fix</span><span class="sh">'</span><span class="p">]</span>
<span class="n">mean_prices</span><span class="p">[</span><span class="sh">'</span><span class="s">off_peak_mid_peak_fix_mean_diff</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="n">mean_prices</span><span class="p">[</span><span class="sh">'</span><span class="s">price_off_peak_fix</span><span class="sh">'</span><span class="p">]</span> <span class="o">-</span> <span class="n">mean_prices</span><span class="p">[</span><span class="sh">'</span><span class="s">price_mid_peak_fix</span><span class="sh">'</span><span class="p">]</span>

<span class="n">columns</span> <span class="o">=</span> <span class="p">[</span>
    <span class="sh">'</span><span class="s">id</span><span class="sh">'</span><span class="p">,</span> 
    <span class="sh">'</span><span class="s">off_peak_peak_var_mean_diff</span><span class="sh">'</span><span class="p">,</span>
    <span class="sh">'</span><span class="s">peak_mid_peak_var_mean_diff</span><span class="sh">'</span><span class="p">,</span> 
    <span class="sh">'</span><span class="s">off_peak_mid_peak_var_mean_diff</span><span class="sh">'</span><span class="p">,</span>
    <span class="sh">'</span><span class="s">off_peak_peak_fix_mean_diff</span><span class="sh">'</span><span class="p">,</span> 
    <span class="sh">'</span><span class="s">peak_mid_peak_fix_mean_diff</span><span class="sh">'</span><span class="p">,</span> 
    <span class="sh">'</span><span class="s">off_peak_mid_peak_fix_mean_diff</span><span class="sh">'</span>
<span class="p">]</span>
<span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">merge</span><span class="p">(</span><span class="n">df</span><span class="p">,</span> <span class="n">mean_prices</span><span class="p">[</span><span class="n">columns</span><span class="p">],</span> <span class="n">on</span><span class="o">=</span><span class="sh">'</span><span class="s">id</span><span class="sh">'</span><span class="p">)</span>

</code></pre></div></div>
<p><strong>Hypothesis 3</strong>: <em>“How long a company has been a client of PowerCo (tenure) could be a significant feature when predicting customer churn”</em></p>

<ul>
  <li>months_activ = Number of months active until reference date (Jan 2016)</li>
  <li>months_to_end = Number of months of the contract left until reference date (Jan 2016)</li>
  <li>months_modif_prod = Number of months since last modification until reference date (Jan 2016)</li>
  <li>months_renewal = Number of months since last renewal until reference date (Jan 2016)</li>
</ul>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">convert_months</span><span class="p">(</span><span class="n">reference_date</span><span class="p">,</span> <span class="n">df</span><span class="p">,</span> <span class="n">column</span><span class="p">):</span>
    <span class="sh">"""</span><span class="s">
    Input a column with timedeltas and return months
    </span><span class="sh">"""</span>
    <span class="n">time_delta</span> <span class="o">=</span> <span class="n">reference_date</span> <span class="o">-</span> <span class="n">df</span><span class="p">[</span><span class="n">column</span><span class="p">]</span>
    <span class="n">months</span> <span class="o">=</span> <span class="p">(</span><span class="n">time_delta</span> <span class="o">/</span> <span class="n">np</span><span class="p">.</span><span class="nf">timedelta64</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="sh">'</span><span class="s">M</span><span class="sh">'</span><span class="p">)).</span><span class="nf">astype</span><span class="p">(</span><span class="nb">int</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">months</span>

<span class="c1"># Create reference date
</span><span class="n">reference_date</span> <span class="o">=</span> <span class="nf">datetime</span><span class="p">(</span><span class="mi">2016</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="c1"># Create columns
</span><span class="n">df</span><span class="p">[</span><span class="sh">'</span><span class="s">months_activ</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="nf">convert_months</span><span class="p">(</span><span class="n">reference_date</span><span class="p">,</span> <span class="n">df</span><span class="p">,</span> <span class="sh">'</span><span class="s">date_activ</span><span class="sh">'</span><span class="p">)</span>
<span class="n">df</span><span class="p">[</span><span class="sh">'</span><span class="s">months_to_end</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="o">-</span><span class="nf">convert_months</span><span class="p">(</span><span class="n">reference_date</span><span class="p">,</span> <span class="n">df</span><span class="p">,</span> <span class="sh">'</span><span class="s">date_end</span><span class="sh">'</span><span class="p">)</span>
<span class="n">df</span><span class="p">[</span><span class="sh">'</span><span class="s">months_modif_prod</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="nf">convert_months</span><span class="p">(</span><span class="n">reference_date</span><span class="p">,</span> <span class="n">df</span><span class="p">,</span> <span class="sh">'</span><span class="s">date_modif_prod</span><span class="sh">'</span><span class="p">)</span>
<span class="n">df</span><span class="p">[</span><span class="sh">'</span><span class="s">months_renewal</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="nf">convert_months</span><span class="p">(</span><span class="n">reference_date</span><span class="p">,</span> <span class="n">df</span><span class="p">,</span> <span class="sh">'</span><span class="s">date_renewal</span><span class="sh">'</span><span class="p">)</span>
</code></pre></div></div>
<p>Transform categorical data using one hot encoding (dummy variables)</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">get_dummies</span><span class="p">(</span><span class="n">df</span><span class="p">,</span> <span class="n">columns</span><span class="o">=</span><span class="p">[</span><span class="sh">'</span><span class="s">channel_sales</span><span class="sh">'</span><span class="p">],</span> <span class="n">prefix</span><span class="o">=</span><span class="sh">'</span><span class="s">channel</span><span class="sh">'</span><span class="p">)</span>
<span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">get_dummies</span><span class="p">(</span><span class="n">df</span><span class="p">,</span> <span class="n">columns</span><span class="o">=</span><span class="p">[</span><span class="sh">'</span><span class="s">origin_up</span><span class="sh">'</span><span class="p">],</span> <span class="n">prefix</span><span class="o">=</span><span class="sh">'</span><span class="s">origin_up</span><span class="sh">'</span><span class="p">)</span>
</code></pre></div></div>
<p>Transforming numerical data to remove the effect of skewed distribtion and also improve the speed at which predictive models are able to converge to its best solution.
<b>Note:</b> We cannot apply log to a value of 0, so we will add a constant of 1 to all the values</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Apply log10 transformation
</span><span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">cons_12m</span><span class="sh">"</span><span class="p">]</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">log10</span><span class="p">(</span><span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">cons_12m</span><span class="sh">"</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">cons_gas_12m</span><span class="sh">"</span><span class="p">]</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">log10</span><span class="p">(</span><span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">cons_gas_12m</span><span class="sh">"</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">cons_last_month</span><span class="sh">"</span><span class="p">]</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">log10</span><span class="p">(</span><span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">cons_last_month</span><span class="sh">"</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">forecast_cons_12m</span><span class="sh">"</span><span class="p">]</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">log10</span><span class="p">(</span><span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">forecast_cons_12m</span><span class="sh">"</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">forecast_cons_year</span><span class="sh">"</span><span class="p">]</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">log10</span><span class="p">(</span><span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">forecast_cons_year</span><span class="sh">"</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">forecast_meter_rent_12m</span><span class="sh">"</span><span class="p">]</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">log10</span><span class="p">(</span><span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">forecast_meter_rent_12m</span><span class="sh">"</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">imp_cons</span><span class="sh">"</span><span class="p">]</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">log10</span><span class="p">(</span><span class="n">df</span><span class="p">[</span><span class="sh">"</span><span class="s">imp_cons</span><span class="sh">"</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
</code></pre></div></div>
<p><strong>Complete feature engineering script</strong> - <a href="https://github.com/fariedd/BCG-Junior-data-scientist-job-simulation/blob/main/Feature%20Engineering.ipynb" target="_blank"><img src="https://img.shields.io/badge/GitHub-View_in_GitHub-blue?logo=GitHub" alt="" /></a></p>

<h2 id="modelling-and-evaluation">Modelling and evaluation</h2>
<p>###Import packages for modelling</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">warnings</span>
<span class="n">warnings</span><span class="p">.</span><span class="nf">filterwarnings</span><span class="p">(</span><span class="sh">"</span><span class="s">ignore</span><span class="sh">"</span><span class="p">,</span> <span class="n">category</span><span class="o">=</span><span class="nb">FutureWarning</span><span class="p">)</span>

<span class="kn">from</span> <span class="n">sklearn</span> <span class="kn">import</span> <span class="n">metrics</span>
<span class="kn">from</span> <span class="n">sklearn.model_selection</span> <span class="kn">import</span> <span class="n">train_test_split</span>
<span class="kn">from</span> <span class="n">sklearn.ensemble</span> <span class="kn">import</span> <span class="n">RandomForestClassifier</span>
</code></pre></div></div>
<p>Splitting the dataset into training and test samples</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Make a copy of our data
</span><span class="n">train_df</span> <span class="o">=</span> <span class="n">df</span><span class="p">.</span><span class="nf">copy</span><span class="p">()</span>

<span class="c1"># Separate target variable from independent variables
</span><span class="n">y</span> <span class="o">=</span> <span class="n">df</span><span class="p">[</span><span class="sh">'</span><span class="s">churn</span><span class="sh">'</span><span class="p">]</span>
<span class="n">X</span> <span class="o">=</span> <span class="n">df</span><span class="p">.</span><span class="nf">drop</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="p">[</span><span class="sh">'</span><span class="s">id</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">churn</span><span class="sh">'</span><span class="p">])</span>
<span class="nf">print</span><span class="p">(</span><span class="n">X</span><span class="p">.</span><span class="n">shape</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="n">y</span><span class="p">.</span><span class="n">shape</span><span class="p">)</span>

<span class="n">X_train</span><span class="p">,</span> <span class="n">X_test</span><span class="p">,</span> <span class="n">y_train</span><span class="p">,</span> <span class="n">y_test</span> <span class="o">=</span> <span class="nf">train_test_split</span><span class="p">(</span><span class="n">X</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">test_size</span><span class="o">=</span><span class="mf">0.25</span><span class="p">,</span> <span class="n">random_state</span><span class="o">=</span><span class="mi">42</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="n">X_train</span><span class="p">.</span><span class="n">shape</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="n">y_train</span><span class="p">.</span><span class="n">shape</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="n">X_test</span><span class="p">.</span><span class="n">shape</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="n">y_test</span><span class="p">.</span><span class="n">shape</span><span class="p">)</span>
</code></pre></div></div>
<h3 id="model-training">Model Training</h3>

<ul>
  <li>We use a Random Forest Classifier whixh is an ensemble algorithm</li>
  <li>We add a randomsearch CV which helps fine tune the optimal parameters</li>
  <li>To make random forest more suitable for learning from extremely imbalanced data (<em>only 9% of churned customers in dataset</em>) follows the idea of cost sensitive learning. Since the RF classifier tends to be biased towards the majority class, we placed a heavier penalty on misclassifying the minority class (<em>class_weight = ‘balanced’</em>)</li>
</ul>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">rf</span> <span class="o">=</span> <span class="nc">RandomForestClassifier</span><span class="p">(</span><span class="n">random_state</span> <span class="o">=</span> <span class="mi">42</span><span class="p">)</span><span class="c1">#run model
</span>
<span class="kn">from</span> <span class="n">pprint</span> <span class="kn">import</span> <span class="n">pprint</span> <span class="c1">#to print parameters used by model
# Look at parameters used by our current forest
</span><span class="nf">print</span><span class="p">(</span><span class="sh">'</span><span class="s">Parameters currently in use:</span><span class="se">\n</span><span class="sh">'</span><span class="p">)</span>
<span class="nf">pprint</span><span class="p">(</span><span class="n">rf</span><span class="p">.</span><span class="nf">get_params</span><span class="p">())</span>

<span class="c1">#select the optimal parameters
</span><span class="kn">from</span> <span class="n">scipy.stats</span> <span class="kn">import</span> <span class="n">randint</span>

<span class="n">rs_space</span><span class="o">=</span><span class="p">{</span><span class="sh">'</span><span class="s">max_depth</span><span class="sh">'</span><span class="p">:</span><span class="nf">list</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="nf">arange</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="n">step</span><span class="o">=</span><span class="mi">10</span><span class="p">))</span> <span class="o">+</span> <span class="p">[</span><span class="bp">None</span><span class="p">],</span>
              <span class="sh">'</span><span class="s">n_estimators</span><span class="sh">'</span><span class="p">:</span><span class="n">np</span><span class="p">.</span><span class="nf">arange</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">1000</span><span class="p">,</span> <span class="n">step</span><span class="o">=</span><span class="mi">50</span><span class="p">),</span>
              <span class="sh">'</span><span class="s">max_features</span><span class="sh">'</span><span class="p">:</span><span class="nf">randint</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">7</span><span class="p">),</span>
              <span class="sh">'</span><span class="s">criterion</span><span class="sh">'</span><span class="p">:[</span><span class="sh">'</span><span class="s">gini</span><span class="sh">'</span><span class="p">,</span><span class="sh">'</span><span class="s">entropy</span><span class="sh">'</span><span class="p">],</span>
              <span class="sh">'</span><span class="s">min_samples_leaf</span><span class="sh">'</span><span class="p">:</span><span class="nf">randint</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">4</span><span class="p">),</span>
              <span class="sh">'</span><span class="s">min_samples_split</span><span class="sh">'</span><span class="p">:</span><span class="n">np</span><span class="p">.</span><span class="nf">arange</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="n">step</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
         <span class="p">}</span>


<span class="kn">from</span> <span class="n">sklearn.model_selection</span> <span class="kn">import</span> <span class="n">RandomizedSearchCV</span>

<span class="n">rf</span> <span class="o">=</span> <span class="nc">RandomForestClassifier</span><span class="p">(</span><span class="n">random_state</span> <span class="o">=</span> <span class="mi">42</span><span class="p">,</span> <span class="n">class_weight</span><span class="o">=</span><span class="sh">"</span><span class="s">balanced_subsample</span><span class="sh">"</span><span class="p">)</span><span class="c1">#run model, but we add class weight because there ins an imbalance in values between class of independent variable
</span><span class="n">rf_random</span> <span class="o">=</span> <span class="nc">RandomizedSearchCV</span><span class="p">(</span><span class="n">rf</span><span class="p">,</span> <span class="n">rs_space</span><span class="p">,</span> <span class="n">n_iter</span><span class="o">=</span><span class="mi">500</span><span class="p">,</span> <span class="n">scoring</span><span class="o">=</span><span class="sh">'</span><span class="s">accuracy</span><span class="sh">'</span><span class="p">,</span> <span class="n">n_jobs</span><span class="o">=-</span><span class="mi">1</span><span class="p">,</span> <span class="n">cv</span><span class="o">=</span><span class="mi">3</span><span class="p">)</span>
<span class="n">model_random</span> <span class="o">=</span> <span class="n">rf_random</span><span class="p">.</span><span class="nf">fit</span><span class="p">(</span><span class="n">X_train</span><span class="p">,</span><span class="n">y_train</span><span class="p">)</span>
</code></pre></div></div>
<h3 id="model-evaluation">Model Evaluation</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1">#check model performance
</span><span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">tuned hpyerparameters :(best parameters) </span><span class="sh">"</span><span class="p">,</span><span class="n">model_random</span><span class="p">.</span><span class="n">best_params_</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">accuracy :</span><span class="sh">"</span><span class="p">,</span><span class="n">model_random</span><span class="p">.</span><span class="n">best_score_</span><span class="p">)</span>

<span class="c1">##evaluate model with test data
</span><span class="nf">evaluator</span><span class="p">(</span><span class="n">model_random</span><span class="p">,</span><span class="n">X_test</span><span class="p">,</span><span class="n">y_test</span><span class="p">)</span>
<span class="n">y_pred</span> <span class="o">=</span> <span class="n">model_random</span><span class="p">.</span><span class="nf">predict</span><span class="p">(</span><span class="n">X_test</span><span class="p">)</span>
<span class="nf">plot_confusion_matrix</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span> <span class="n">y_pred</span><span class="p">)</span>
</code></pre></div></div>
<div style="display: flex; justify-content: space-between;">
    <img src="/assets/img/blog/BCG job simulation/metro.png" alt="Image 1" style="width: 70%; height: auto;" />
</div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="bp">True</span> <span class="n">positives</span><span class="p">:</span> <span class="mi">21</span>
<span class="bp">False</span> <span class="n">positives</span><span class="p">:</span> <span class="mi">5</span>
<span class="bp">True</span> <span class="n">negatives</span><span class="p">:</span> <span class="mi">3281</span>
<span class="bp">False</span> <span class="n">negatives</span><span class="p">:</span> <span class="mi">345</span>

<span class="n">Accuracy</span><span class="p">:</span> <span class="mi">90</span><span class="o">%</span>
<span class="n">Precision</span><span class="p">:</span> <span class="mi">82</span><span class="o">%</span>
<span class="n">Recall</span><span class="p">:</span> <span class="mi">5</span><span class="o">%</span>

</code></pre></div></div>

<p>Looking at these results there are a few things to point out:</p>

<ul>
  <li>Within the test set about 10% of the rows are churners (churn = 1).</li>
  <li>Looking at the true negatives, we have 3281 out of 3286. This means that out of all the negative cases (churn = 0), we predicted 3282 as negative (hence the name True negative). This is great!</li>
  <li>Looking at the false negatives, this is where we have predicted a client to not churn (churn = 0) when in fact they did churn (churn = 1). This number is quite high at 345, we want to get the false negatives to as close to 0 as we can, so this would need to be addressed when improving the model.</li>
  <li>Looking at false positives, this is where we have predicted a client to churn when they actually didnt churn. For this value we can see there are 5 cases, which is great!</li>
  <li>With the true positives, we can see that in total we have 366 clients that churned in the test dataset. However, we are only able to correctly identify 21 of those 366, which is very poor.</li>
  <li>Looking at the accuracy score, this is very misleading! Hence the use of precision and recall is important. The accuracy score is high, but it does not tell us the whole story.</li>
  <li>Looking at the precision score, this shows us a score of 0.82 which is not bad, but could be improved.</li>
  <li>However, the recall shows us that the classifier has a very poor ability to identify positive samples. This would be the main concern for improving this model!</li>
</ul>

<p>So overall, we’re able to very accurately identify clients that do not churn, but we are not able to predict cases where clients do churn! What we are seeing is that a high % of clients are being identified as not churning when they should be identified as churning. This in turn tells me that the current set of features are not discriminative enough to clearly distinguish between churners and non-churners.</p>

<h3 id="model-understanding">Model understanding</h3>

<p>A simple way of understanding the results of a model is to look at feature importances. Feature importances indicate the importance of a feature within the predictive model, there are several ways to calculate feature importance, but with the Random Forest classifier, we’re able to extract feature importances using the built-in method on the trained model. In the Random Forest case, the feature importance represents the number of times each feature is used for splitting across all trees.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">feature_importances</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nc">DataFrame</span><span class="p">({</span>
    <span class="sh">'</span><span class="s">features</span><span class="sh">'</span><span class="p">:</span> <span class="n">X_train</span><span class="p">.</span><span class="n">columns</span><span class="p">,</span>
    <span class="sh">'</span><span class="s">importance</span><span class="sh">'</span><span class="p">:</span> <span class="n">model</span><span class="p">.</span><span class="n">best_estimator_</span><span class="p">.</span><span class="n">feature_importances_</span>
<span class="p">}).</span><span class="nf">sort_values</span><span class="p">(</span><span class="n">by</span><span class="o">=</span><span class="sh">'</span><span class="s">importance</span><span class="sh">'</span><span class="p">,</span> <span class="n">ascending</span><span class="o">=</span><span class="bp">True</span><span class="p">).</span><span class="nf">reset_index</span><span class="p">()</span>

<span class="n">plt</span><span class="p">.</span><span class="nf">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">15</span><span class="p">,</span> <span class="mi">25</span><span class="p">))</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">title</span><span class="p">(</span><span class="sh">'</span><span class="s">Feature Importances</span><span class="sh">'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">barh</span><span class="p">(</span><span class="nf">range</span><span class="p">(</span><span class="nf">len</span><span class="p">(</span><span class="n">feature_importances</span><span class="p">)),</span> <span class="n">feature_importances</span><span class="p">[</span><span class="sh">'</span><span class="s">importance</span><span class="sh">'</span><span class="p">],</span> <span class="n">color</span><span class="o">=</span><span class="sh">'</span><span class="s">b</span><span class="sh">'</span><span class="p">,</span> <span class="n">align</span><span class="o">=</span><span class="sh">'</span><span class="s">center</span><span class="sh">'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">yticks</span><span class="p">(</span><span class="nf">range</span><span class="p">(</span><span class="nf">len</span><span class="p">(</span><span class="n">feature_importances</span><span class="p">)),</span> <span class="n">feature_importances</span><span class="p">[</span><span class="sh">'</span><span class="s">features</span><span class="sh">'</span><span class="p">])</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">xlabel</span><span class="p">(</span><span class="sh">'</span><span class="s">Importance</span><span class="sh">'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">show</span><span class="p">()</span>
</code></pre></div></div>
<div style="display: flex; justify-content: space-between;">
    <img src="/assets/img/blog/BCG job simulation/feature.png" alt="Image 1" style="width: 100%; height: 50%;" />
</div>

<p><strong>Complete modelling and evaluation script</strong> - <a href="https://github.com/fariedd/BCG-Junior-data-scientist-job-simulation/blob/main/Modelling%20and%20Evaluation.ipynb" target="_blank"><img src="https://img.shields.io/badge/GitHub-View_in_GitHub-blue?logo=GitHub" alt="" /></a></p>
<h2 id="insights--recommendations">Insights &amp; Recommendations</h2>

<p>Churn is high in SME with 9.7% across 14606 customers for PowerCo 
The model was able to predict churn but  Yearly consumption, forecasted consumption
and net margin were important. However the price sensitivity features are scattered around and were not a main driver for churn.
More modelling, fine tuning of features and inclusion of more variables is required to potentially improve the model.</p>

<p>A discount strategy of 20% would be effective but only if targeted appropriately.
We recommended offering discount to only to high value customers with high churn probability.</p>]]></content><author><name>Farisayi Dakwa</name><email>fariedakwa.fd@gmail.com</email></author><category term="Data Science" /><category term="Python" /><category term="Machine Learning" /><category term="Business Understanding" /><category term="Hypothesis framing" /><summary type="html"><![CDATA[Junior Data Scientist Job simulation at Boston Consulting Group (BCG), using feature engineering and machine learning models to determine the influence of price sensitivity on custormer churn.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="/assets/img/blog/BCG%20job%20simulation/vizo%20image.jpg" /><media:content medium="image" url="/assets/img/blog/BCG%20job%20simulation/vizo%20image.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Predict SpaceX rocket landing success using Machine Learning</title><link href="/blog/SpaceX-landing-prediction/" rel="alternate" type="text/html" title="Predict SpaceX rocket landing success using Machine Learning" /><published>2024-04-30T00:00:00+00:00</published><updated>2024-09-16T13:54:07+00:00</updated><id>/blog/SpaceX-landing-prediction</id><content type="html" xml:base="/blog/SpaceX-landing-prediction/"><![CDATA[<p><a href="https://github.com/fariedd/IBM-DataScience-SpaceX-Capestone" target="_blank"><img src="https://img.shields.io/badge/GitHub-View_in_GitHub-blue?logo=GitHub" alt="" /></a></p>

<p><strong>Failure is an option here. If things are not failing, you are not innovating enough</strong> ~ <em>Elon Musk</em>.</p>
<ul id="markdown-toc">
  <li><a href="#project-overview" id="markdown-toc-project-overview">Project Overview</a>    <ul>
      <li><a href="#problem-statement" id="markdown-toc-problem-statement">Problem Statement</a></li>
    </ul>
  </li>
  <li><a href="#methods" id="markdown-toc-methods">Methods</a>    <ul>
      <li><a href="#model-assesment" id="markdown-toc-model-assesment">Model Assesment</a></li>
    </ul>
  </li>
  <li><a href="#results" id="markdown-toc-results">Results</a>    <ul>
      <li><a href="#models-perfomance" id="markdown-toc-models-perfomance">Models Perfomance</a></li>
    </ul>
  </li>
  <li><a href="#discussion-growth--next-steps" id="markdown-toc-discussion-growth--next-steps">Discussion, Growth &amp; Next Steps</a></li>
</ul>

<h2 id="project-overview">Project Overview</h2>
<h3 id="problem-statement">Problem Statement</h3>

<p>Going to space has always been a very expensive venture and SpaceX advertises Falcon 9 rocket launches on its website with a cost of 62 million dollars while other providers cost upward of 165 million dollars each, much of the savings is because since 2014 Space X has manage to successfully recover first stage rocket launched vehicles. This has made Space X rockets comparatively cheaper because their rockets are reusable. Although the landing success rate has improved over the years, not all rocket have landed successfully. Space X has been collecting data on its landing activities over the years, therefore if we can determine if the first stage will land, we can determine the cost of a launch. This information can be used if an alternate company wants to bid against SpaceX for a rocket launch.</p>

<h2 id="methods">Methods</h2>
<p>We will be predicting the landing success of Falcon 9 rocket launches and Falcon heavy launches from 2010 to 2021.
Data was collected by webscraping (<em>Beautifulsoup</em>) tables of SpaceX historical launch records from a <a href="https://en.wikipedia.org/w/index.php?title=List_of_Falcon_9_and_Falcon_Heavy_launches&amp;oldid=1027686922">Wikipedia</a> page. <a href="https://github.com/fariedd/IBM-DataScience-SpaceX-Capestone/blob/main/jupyter-labs-webscraping%20(1).ipynb" target="_blank"><img src="https://img.shields.io/badge/Jupyter-Open_Notebook-blue?logo=Jupyter" alt="" /></a>.</p>

<p>More data on rocket landing outcomes were extracted using SpaceX resting APIs <a href="https://github.com/fariedd/IBM-DataScience-SpaceX-Capestone/blob/main/jupyter-labs-spacex-data-collection-api_.ipynb" target="_blank"><img src="https://img.shields.io/badge/Jupyter-Open_Notebook-blue?logo=Jupyter" alt="" /></a>.</p>

<p>Exploratory data analysis and preparatory feature engineering were implemented on the collected data to also visiualise landing outcome of rocket launches using Pandas and Matplotlib.Machine learning models typically cannot process string variables, so when there are text data within a data set, we would encode them (change them to numbers, typically binary) so that the algorithm can assess the relationship between the variable and the output. We used One Hot Encoding (OHE) from the sklearn.preprocessing library.
 <a href="https://github.com/fariedd/IBM-DataScience-SpaceX-Capestone/blob/main/jupyter-labs-eda-dataviz%20(1).ipynb" target="_blank"><img src="https://img.shields.io/badge/Jupyter-Open_Notebook-blue?logo=Jupyter" alt="" /></a>.</p>

<p>The detailed landing outcomes of the rockets were reclassified to 1 (landed) and 0 (did not land)
 for analysis, missing values and outliers were removed {add data wrangling notebook}.</p>

<p>We then implemented four classification machine learning models, Logistic regression, Support vector machine, Decision tree and K-nearest neighbor. Before modelling all variables were standardised using a StandardScaler so that variables measured at different scales may be reduced to the same scale and contribute equally to model fitting, with landing outcome as the dependant variable.</p>

<p>The hyperparameters for each model were optimised using GridSearchCV, it evaluates the model for each combination of hyperparameters using the cross-validation method (cv =10) and chooses the combination with the best performance for each model before fitting.</p>

<p>The data was split into training and testing data, and a confusion matrix was created to show how the trained models can accurately predict the testing data (show false positve vs false negative).  <a href="https://github.com/fariedd/IBM-DataScience-SpaceX-Capestone/blob/main/SpaceX_Machine_Learning_Prediction_Part_5.jupyterlite%20(1).ipynb" target="_blank"><img src="https://img.shields.io/badge/Jupyter-Open_Notebook-blue?logo=Jupyter" alt="" /></a>.</p>

<h4 id="model-assesment">Model Assesment</h4>

<p>Model perfomance and comparison were determined using accuracy score, precision score, recall score and F1 score.</p>

<ul>
  <li>
    <p><strong>Accuracy</strong>: This metric meassures how a machine learning model correctly predicts the outcome values in the test data (new data) by dividing the number of correct predictions by the total number of predictions. The accuracy score is measured on a scale of 0 to 1 or as a percentage. The higher the accuracy, the better.</p>
  </li>
  <li>
    <p><strong>Precision</strong>: This metric measures how a machine learning model correctly predicts the positive outcome values (true postives) in a test data (in this case if the rocket landed successfully).  Answers the question: of all observations predicted as positive, what proportion was actually positive?. The accuracy score is measured on a scale of 0 to 1 or as a percentage. The higher the accuracy, the better.</p>
  </li>
  <li>
    <p><strong>Recall</strong>: This metric measures how often a machine learning model correctly identifies positive instances (true positives) from all the actual positive samples in a test data (in this case if the rocket landed successfully). Answers the question: of all positive observations, how many did we predict as positive? The accuracy score is measured on a scale of 0 to 1 or as a percentage. The higher the accuracy, the better.</p>
  </li>
  <li>
    <p><strong>F1 Score</strong>: This is the harmonic mean of Precision and Recall. A good F1-score comes when there is a balance between precision and recall.</p>
  </li>
</ul>

<h2 id="results">Results</h2>

<p>The goals of the project was:</p>

<ul>
  <li>Build a model that would accurately predict the landing outcome of rockets (success/fail) . If we can determine if the first stage  will land, we can determine the cost of a launch.</li>
  <li>This information can be used if an alternate company wants to bid against SpaceX for a rocket launch.</li>
</ul>

<p>The landing success of Space X rockets has continued to increase since 2013.
<br />
<img src="/assets/img/blog/predict-spaceX-landind/image.png" alt="" /></p>

<p class="figcaption">Trend in landing success rate of Space X Falcon 9 rockets since 2010, success rate has had a positive upward trend since 2013.</p>
<p><br /></p>

<h4 id="models-perfomance">Models Perfomance</h4>

<p>Based upon these,all models can predict the landing success with an accuracy of ~80 across all matrics except for Decision Trees which perfomed lower but not singifcantly so</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Model</th>
      <th style="text-align: left">Accuracy</th>
      <th style="text-align: left">Precision</th>
      <th style="text-align: left">Recall</th>
      <th style="text-align: left">F1-Score</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">Logistic Regression (Log_Reg)</td>
      <td style="text-align: left">0.83</td>
      <td style="text-align: left">0.80</td>
      <td style="text-align: left">1.0</td>
      <td style="text-align: left">0.89</td>
    </tr>
    <tr>
      <td style="text-align: left">Support Vector Machine (SVM)</td>
      <td style="text-align: left">0.83</td>
      <td style="text-align: left">0.80</td>
      <td style="text-align: left">1.0</td>
      <td style="text-align: left">0.89</td>
    </tr>
    <tr>
      <td style="text-align: left">Decision tree</td>
      <td style="text-align: left">0.78</td>
      <td style="text-align: left">0.75</td>
      <td style="text-align: left">1.0</td>
      <td style="text-align: left">0.85</td>
    </tr>
    <tr>
      <td style="text-align: left">K-Nearest Neighbor (KNN)</td>
      <td style="text-align: left">0.83</td>
      <td style="text-align: left">0.80</td>
      <td style="text-align: left">1.0</td>
      <td style="text-align: left">0.89</td>
    </tr>
  </tbody>
</table>

<p><br />
<img src="/assets/img/blog/predict-spaceX-landind/accuracy.png" alt="" /></p>

<p class="figcaption">Model prediction accuracy for all built classification models, in a bar chart.</p>
<p><br /></p>

<p><strong>Confusion Matrix</strong></p>

<h2 id="discussion-growth--next-steps">Discussion, Growth &amp; Next Steps</h2>

<p>The way we have coded this up is very much for the “proof of concept”.  In practice, we would definitely have to use a large data set and incooperate more variables into the model that could improve the predictions. Payloadmass of the rockets was an influential contributer to hte predictions of this model, (in practice we will have to remove and add some variables to see if htey affect the data). In this case removing payloadmass had a significant effect on the predictions, which hints at other factors being added to hte rocket influencing landing success of rockets. Alose inplace of Decision trees we can incooperate a random forest which offer a more complex visualisation option (nodes of classification).
 the last section of the code (where we submit a search) isolated, and running from all of the saved objects that we need - we wouldn’t include it in a single script like we have here.</p>

<p>This was tested only in two  categories (success or fail), we would want to test on a broader array of categories - most likely having a saved network for more specific conditions that describe  and categorise failure and successful landing of rockets in more detail.</p>]]></content><author><name>Farisayi Dakwa</name><email>fariedakwa.fd@gmail.com</email></author><category term="Data Science" /><category term="Python" /><category term="Machine Learning" /><category term="Web Scraping" /><category term="API" /><summary type="html"><![CDATA[In this project l built and trained four machine learning models which can predict if the first stage landing of SpaceX's falcon 9 rockets are successful with a combine accuracy of ~80%.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="/assets/img/blog/predict-spaceX-landind/space_x-min.jpg" /><media:content medium="image" url="/assets/img/blog/predict-spaceX-landind/space_x-min.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Enhancing Targeting Accuracy using ML</title><link href="/blog/enhance-target-accuracy/" rel="alternate" type="text/html" title="Enhancing Targeting Accuracy using ML" /><published>2022-09-01T00:00:00+00:00</published><updated>2022-10-31T04:53:04+00:00</updated><id>/blog/enhance-target-accuracy</id><content type="html" xml:base="/blog/enhance-target-accuracy/"><![CDATA[<p><a href="https://github.com/ibiene-ds/enhancing-targeting-accuracy/blob/main/Enhancing%20Targeting%20Accuracy%20using%20ML.ipynb"><img src="https://img.shields.io/badge/Jupyter-Open_Notebook-blue?logo=Jupyter" alt="" /></a>
<a href="https://github.com/ibiene-ds/enhancing-targeting-accuracy"><img src="https://img.shields.io/badge/GitHub-View_in_GitHub-blue?logo=GitHub" alt="" /></a></p>

<ul id="markdown-toc">
  <li><a href="#project-overview-and-results" id="markdown-toc-project-overview-and-results">Project Overview and Results</a></li>
  <li><a href="#data-overview" id="markdown-toc-data-overview">Data Overview</a></li>
  <li><a href="#data-import" id="markdown-toc-data-import">Data Import</a></li>
  <li><a href="#preprocessing" id="markdown-toc-preprocessing">Preprocessing</a>    <ul>
      <li><a href="#missing-values" id="markdown-toc-missing-values">Missing Values</a></li>
      <li><a href="#outliers" id="markdown-toc-outliers">Outliers</a></li>
    </ul>
  </li>
  <li><a href="#split-out-data-for-modelling" id="markdown-toc-split-out-data-for-modelling">Split Out Data for Modelling</a></li>
  <li><a href="#model-1-logistic-regression" id="markdown-toc-model-1-logistic-regression">Model 1: Logistic Regression</a>    <ul>
      <li><a href="#encoding-categorical-variables" id="markdown-toc-encoding-categorical-variables">Encoding Categorical Variables</a></li>
      <li><a href="#feature-selection" id="markdown-toc-feature-selection">Feature Selection</a></li>
      <li><a href="#model-training" id="markdown-toc-model-training">Model Training</a></li>
      <li><a href="#model-assessment" id="markdown-toc-model-assessment">Model Assessment</a>        <ul>
          <li><a href="#predict-on-the-test-set" id="markdown-toc-predict-on-the-test-set">Predict On The Test Set</a></li>
          <li><a href="#confusion-matrix" id="markdown-toc-confusion-matrix">Confusion Matrix</a></li>
          <li><a href="#classification-performance-metrics" id="markdown-toc-classification-performance-metrics">Classification Performance Metrics</a></li>
        </ul>
      </li>
      <li><a href="#finding-the-optimal-classification-threshold" id="markdown-toc-finding-the-optimal-classification-threshold">Finding The Optimal Classification Threshold</a></li>
    </ul>
  </li>
  <li><a href="#model-2-decision-trees" id="markdown-toc-model-2-decision-trees">Model 2: Decision Trees</a>    <ul>
      <li><a href="#encode-categorical-variables" id="markdown-toc-encode-categorical-variables">Encode Categorical Variables</a></li>
      <li><a href="#model-training-1" id="markdown-toc-model-training-1">Model Training</a></li>
      <li><a href="#model-assessment-1" id="markdown-toc-model-assessment-1">Model Assessment</a></li>
      <li><a href="#plot-decision-tree" id="markdown-toc-plot-decision-tree">Plot Decision Tree</a></li>
      <li><a href="#finding-the-best-max-depth-to-prevent-overfitting" id="markdown-toc-finding-the-best-max-depth-to-prevent-overfitting">Finding the Best Max Depth to Prevent Overfitting</a></li>
    </ul>
  </li>
  <li><a href="#model-3-random-forest-ensemble" id="markdown-toc-model-3-random-forest-ensemble">Model 3: Random Forest (Ensemble)</a></li>
  <li><a href="#model-4-k-nearest-neighbors-knn" id="markdown-toc-model-4-k-nearest-neighbors-knn">Model 4: K Nearest Neighbors (KNN)</a></li>
  <li><a href="#results" id="markdown-toc-results">Results</a>    <ul>
      <li><a href="#key-drivers-of-delivery-club-signups" id="markdown-toc-key-drivers-of-delivery-club-signups">Key Drivers of Delivery Club Signups</a></li>
    </ul>
  </li>
  <li><a href="#application" id="markdown-toc-application">Application</a></li>
  <li><a href="#growth--next-steps" id="markdown-toc-growth--next-steps">Growth &amp; Next Steps</a></li>
</ul>

<h2 id="project-overview-and-results">Project Overview and Results</h2>

<p><strong>Problem Statement</strong>: Our client, a grocery retailer, sent out mailers in a marketing campaign for their new <em>delivery club</em>.  This cost customers $100 per year for membership, and offered free grocery deliveries, rather than the normal cost of $10 per delivery. For this, they sent mailers to their entire customer base (apart from a control group) but this proved expensive.  <strong>For the next batch of communications, they would like to save costs by <em>only</em> mailing customers that were likely to sign up.</strong> Based on the results of the last campaign and the customer data available, we will look to understand the <em>probability</em> of customers signing up for the <em>delivery club</em>.  This would allow the client to mail a more targeted selection of customers, lowering costs, and improving ROI.</p>

<p>The goal of this project is to build a model that would accurately predict the customers that would sign up for our client’s <em>delivery club</em> services.  This would allow for a much more targeted approach when running the next iteration of the delivery club campaign.  A secondary goal is to understand what the drivers for delivery club signups are, so the client can get closer to the customers that need or want this service, and enhance their messaging.</p>

<p>The data was cleaned, preprocessed and split into training and testing sets; four different models were used in the modelling process and based on the goals of the project, the model of choice is the <strong>Random Forest</strong> model as it was:</p>
<ul>
  <li>the most consistent performant on the test set across classification accuracy, precision, recall, and F1-score, and</li>
  <li>the feature importance and permutation importance allow the client an understanding of the key drivers behind <em>delivery club</em> signups. See the table below for a summary of all model results.
<br />
<br />
<img src="/assets/img/blog/enhance-target-accuracy/model_summary.png" alt="alt text" /></li>
</ul>

<h2 id="data-overview">Data Overview</h2>
<p>We will be predicting the binary <em>signup flag</em> from the <em>campaign_data</em> table in the client database. The key variables to predict this will come from different tables, which we have joined and aggregated to come up with the final dataset. We focused on aggregating data 3 months before the last campaign date.</p>

<p>The final table of data for modelling looks something like this:</p>

<table class="smaller">
  <thead>
    <tr>
      <th><strong>Variable Name</strong></th>
      <th><strong>Variable Type</strong></th>
      <th><strong>Description</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>signup_flag</td>
      <td>Dependent</td>
      <td>A binary variable showing if the customer signed up for the delivery club in the last campaign</td>
    </tr>
    <tr>
      <td>distance_from_store</td>
      <td>Independent</td>
      <td>The distance in miles from the customers home address, and the store</td>
    </tr>
    <tr>
      <td>gender</td>
      <td>Independent</td>
      <td>The gender provided by the customer</td>
    </tr>
    <tr>
      <td>credit_score</td>
      <td>Independent</td>
      <td>The customers most recent credit score</td>
    </tr>
    <tr>
      <td>total_sales</td>
      <td>Independent</td>
      <td>Total spend by the customer in ABC Grocery - 3 months pre campaign</td>
    </tr>
    <tr>
      <td>total_items</td>
      <td>Independent</td>
      <td>Total products purchased by the customer in ABC Grocery - 3 months pre campaign</td>
    </tr>
    <tr>
      <td>transaction_count</td>
      <td>Independent</td>
      <td>Total unique transactions made by the customer in ABC Grocery - 3 months pre campaign</td>
    </tr>
    <tr>
      <td>product_area_count</td>
      <td>Independent</td>
      <td>The number of product areas within ABC Grocery the customers has shopped into - 3 months pre campaign</td>
    </tr>
    <tr>
      <td>average_basket_value</td>
      <td>Independent</td>
      <td>The average spend per transaction for the customer in ABC Grocery - 3 months pre campaign</td>
    </tr>
  </tbody>
</table>

<h2 id="data-import">Data Import</h2>

<p>The first step is to import important packages such as pickle (we saved our final table as a pickle file), pandas, matplotlib etc.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1"># import required packages
</span><span class="kn">import</span> <span class="n">pandas</span> <span class="k">as</span> <span class="n">pd</span>
<span class="kn">import</span> <span class="n">pickle</span>
<span class="kn">import</span> <span class="n">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>
<span class="kn">import</span> <span class="n">numpy</span> <span class="k">as</span> <span class="n">np</span>
<span class="kn">from</span> <span class="n">sklearn.linear_model</span> <span class="kn">import</span> <span class="n">LogisticRegression</span>
<span class="kn">from</span> <span class="n">sklearn.utils</span> <span class="kn">import</span> <span class="n">shuffle</span>
<span class="kn">from</span> <span class="n">sklearn.model_selection</span> <span class="kn">import</span> <span class="n">train_test_split</span><span class="p">,</span> <span class="n">cross_val_score</span><span class="p">,</span> <span class="n">KFold</span>
<span class="kn">from</span> <span class="n">sklearn.metrics</span> <span class="kn">import</span> <span class="n">confusion_matrix</span><span class="p">,</span> <span class="n">accuracy_score</span><span class="p">,</span> <span class="n">precision_score</span><span class="p">,</span> <span class="n">recall_score</span><span class="p">,</span> <span class="n">f1_score</span>
<span class="kn">from</span> <span class="n">sklearn.preprocessing</span> <span class="kn">import</span> <span class="n">OneHotEncoder</span>
<span class="kn">from</span> <span class="n">sklearn.feature_selection</span> <span class="kn">import</span> <span class="n">RFECV</span>
<span class="kn">import</span> <span class="n">plotly.express</span> <span class="k">as</span> <span class="n">px</span>

<span class="c1"># import modelling data
</span><span class="n">data_for_model</span> <span class="o">=</span> <span class="n">pickle</span><span class="p">.</span><span class="nf">load</span><span class="p">(</span><span class="nf">open</span><span class="p">(</span><span class="sh">"</span><span class="s">data/delivery_club_modelling.p</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">rb</span><span class="sh">"</span><span class="p">))</span>

</code></pre></div></div>
<p>For modelling purposes, the <em>customer_id</em> does not add value to our model, so we remove it from the dataset. It is also necessary to shuffle our data to introduce randomness into the dataset (if it isn’t randomized already).</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># drop uneccessary columns
</span><span class="n">data_for_model</span><span class="p">.</span><span class="nf">drop</span><span class="p">(</span><span class="sh">"</span><span class="s">customer_id</span><span class="sh">"</span><span class="p">,</span> <span class="n">axis</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">inplace</span> <span class="o">=</span> <span class="bp">True</span><span class="p">)</span>

<span class="c1"># shuffle data
</span><span class="n">data_for_model</span> <span class="o">=</span> <span class="nf">shuffle</span><span class="p">(</span><span class="n">data_for_model</span><span class="p">,</span> <span class="n">random_state</span> <span class="o">=</span> <span class="mi">42</span><span class="p">)</span>
</code></pre></div></div>
<p>Since this is a classification problem, it is a good idea to understand the class balance in the dataset. This would affect what metrics would be most useful in assessing our model.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># assess class balance of dependent variable
</span><span class="n">data_for_model</span><span class="p">[</span><span class="sh">"</span><span class="s">signup_flag</span><span class="sh">"</span><span class="p">].</span><span class="nf">value_counts</span><span class="p">(</span><span class="n">normalize</span> <span class="o">=</span> <span class="bp">True</span><span class="p">)</span>
</code></pre></div></div>
<p>The result of the code above tells us that <em>69%</em> of customers did not sign up and <em>31%</em> did. This tells us that while the data isn’t perfectly balanced at 50:50, it isn’t <em>too</em> imbalanced either.  Because of this, and as you will see, we make sure to not rely on classification accuracy alone when assessing results - we also analyze Precision, Recall, and F1-Score to give us a complete picture.</p>

<h2 id="preprocessing">Preprocessing</h2>

<h3 id="missing-values">Missing Values</h3>
<p>The number of missing values in the data was extremely low (&lt;1%), so instead of applying any imputation (i.e. mean, most common value), we will just remove those rows.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1"># remove rows where values are missing
</span><span class="n">data_for_model</span><span class="p">.</span><span class="nf">isna</span><span class="p">().</span><span class="nf">sum</span><span class="p">()</span>
<span class="n">data_for_model</span><span class="p">.</span><span class="nf">dropna</span><span class="p">(</span><span class="n">how</span> <span class="o">=</span> <span class="sh">"</span><span class="s">any</span><span class="sh">"</span><span class="p">,</span> <span class="n">inplace</span> <span class="o">=</span> <span class="bp">True</span><span class="p">)</span>

</code></pre></div></div>

<h3 id="outliers">Outliers</h3>
<p>Outliers investigation/removal is an important step in preprocessing data as outliers could hamper the ability of models to generalize. Let’s use the <em>describe()</em> method to investigate the spread of values for our inputs.</p>

<table class="smaller scroll-table">
  <thead>
    <tr>
      <th style="text-align: center"><strong>metric</strong></th>
      <th style="text-align: center"><strong>distance_from_store</strong></th>
      <th style="text-align: center"><strong>credit_score</strong></th>
      <th style="text-align: center"><strong>total_sales</strong></th>
      <th style="text-align: center"><strong>total_items</strong></th>
      <th style="text-align: center"><strong>transaction_count</strong></th>
      <th style="text-align: center"><strong>product_area_count</strong></th>
      <th style="text-align: center"><strong>average_basket_value</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">mean</td>
      <td style="text-align: center">2.61</td>
      <td style="text-align: center">0.60</td>
      <td style="text-align: center">968.17</td>
      <td style="text-align: center">143.88</td>
      <td style="text-align: center">22.21</td>
      <td style="text-align: center">4.18</td>
      <td style="text-align: center">38.03</td>
    </tr>
    <tr>
      <td style="text-align: center">std</td>
      <td style="text-align: center">14.40</td>
      <td style="text-align: center">0.10</td>
      <td style="text-align: center">1073.65</td>
      <td style="text-align: center">125.34</td>
      <td style="text-align: center">11.72</td>
      <td style="text-align: center">0.92</td>
      <td style="text-align: center">24.24</td>
    </tr>
    <tr>
      <td style="text-align: center">min</td>
      <td style="text-align: center">0.00</td>
      <td style="text-align: center">0.26</td>
      <td style="text-align: center">2.09</td>
      <td style="text-align: center">1.00</td>
      <td style="text-align: center">1.00</td>
      <td style="text-align: center">1.00</td>
      <td style="text-align: center">2.09</td>
    </tr>
    <tr>
      <td style="text-align: center">25%</td>
      <td style="text-align: center">0.73</td>
      <td style="text-align: center">0.53</td>
      <td style="text-align: center">383.94</td>
      <td style="text-align: center">77.00</td>
      <td style="text-align: center">16.00</td>
      <td style="text-align: center">4.00</td>
      <td style="text-align: center">21.73</td>
    </tr>
    <tr>
      <td style="text-align: center">50%</td>
      <td style="text-align: center">1.64</td>
      <td style="text-align: center">0.59</td>
      <td style="text-align: center">691.64</td>
      <td style="text-align: center">123.00</td>
      <td style="text-align: center">23.00</td>
      <td style="text-align: center">4.00</td>
      <td style="text-align: center">31.07</td>
    </tr>
    <tr>
      <td style="text-align: center">75%</td>
      <td style="text-align: center">2.92</td>
      <td style="text-align: center">0.67</td>
      <td style="text-align: center">1121.53</td>
      <td style="text-align: center">170.50</td>
      <td style="text-align: center">28.00</td>
      <td style="text-align: center">5.00</td>
      <td style="text-align: center">46.43</td>
    </tr>
    <tr>
      <td style="text-align: center">max</td>
      <td style="text-align: center">400.97</td>
      <td style="text-align: center">0.88</td>
      <td style="text-align: center">7372.06</td>
      <td style="text-align: center">910.00</td>
      <td style="text-align: center">75.00</td>
      <td style="text-align: center">5.00</td>
      <td style="text-align: center">141.05</td>
    </tr>
  </tbody>
</table>

<p>Another fun way of visualizing the spread of data is using a box plot to visually check where there are obvious outliers.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">data_num</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="nf">select_dtypes</span><span class="p">(</span><span class="sh">'</span><span class="s">number</span><span class="sh">'</span><span class="p">)</span>
<span class="n">data_num</span><span class="p">.</span><span class="nf">drop</span><span class="p">([</span><span class="sh">"</span><span class="s">signup_flag</span><span class="sh">"</span><span class="p">],</span> <span class="n">axis</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">inplace</span> <span class="o">=</span> <span class="bp">True</span><span class="p">)</span>
<span class="n">data_num</span><span class="p">.</span><span class="nf">plot</span><span class="p">(</span><span class="n">kind</span><span class="o">=</span> <span class="sh">'</span><span class="s">box</span><span class="sh">'</span><span class="p">,</span> <span class="n">subplots</span><span class="o">=</span> <span class="bp">True</span><span class="p">,</span> <span class="n">layout</span> <span class="o">=</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">),</span> <span class="n">sharex</span><span class="o">=</span> <span class="bp">False</span><span class="p">,</span> <span class="n">sharey</span><span class="o">=</span> <span class="bp">False</span><span class="p">,</span> <span class="n">figsize</span> <span class="o">=</span><span class="p">(</span><span class="mi">16</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span> <span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">show</span><span class="p">()</span>
</code></pre></div></div>
<p><img src="/assets/img/blog/enhance-target-accuracy/boxplot.png" alt="box plot" /></p>

<p>From the boxplots above, outliers seem to exist in all columns except for credit score and product area count. To remove these outliers, we will use the “boxplot approach” where we remove any rows where the values are outside of 2 x interquartile range.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">outlier_investigation</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="nf">describe</span><span class="p">()</span>
<span class="n">outlier_columns</span> <span class="o">=</span> <span class="p">[</span><span class="sh">'</span><span class="s">distance_from_store</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">total_sales</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">total_items</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">transaction_count</span><span class="sh">'</span><span class="p">,</span><span class="sh">'</span><span class="s">average_basket_value</span><span class="sh">'</span><span class="p">]</span>

<span class="k">for</span> <span class="n">column</span> <span class="ow">in</span> <span class="n">outlier_columns</span><span class="p">:</span>
    <span class="n">lower_quartile</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">column</span><span class="p">].</span><span class="nf">quantile</span><span class="p">(</span><span class="mf">0.25</span><span class="p">)</span>
    <span class="n">upper_quartile</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">column</span><span class="p">].</span><span class="nf">quantile</span><span class="p">(</span><span class="mf">0.75</span><span class="p">)</span>
    <span class="n">iqr</span> <span class="o">=</span> <span class="n">upper_quartile</span> <span class="o">-</span> <span class="n">lower_quartile</span>
    <span class="n">iqr_extended</span> <span class="o">=</span> <span class="n">iqr</span> <span class="o">*</span> <span class="mi">2</span>
    <span class="n">min_border</span> <span class="o">=</span> <span class="n">lower_quartile</span> <span class="o">-</span> <span class="n">iqr_extended</span>
    <span class="n">max_border</span> <span class="o">=</span> <span class="n">upper_quartile</span> <span class="o">+</span> <span class="n">iqr_extended</span>
    
    <span class="n">outliers</span> <span class="o">=</span> <span class="n">data</span><span class="p">[(</span><span class="n">data</span><span class="p">[</span><span class="n">column</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">min_border</span><span class="p">)</span><span class="o">|</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="n">column</span><span class="p">]</span> <span class="o">&gt;</span> <span class="n">max_border</span><span class="p">)].</span><span class="n">index</span>
    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="si">{</span><span class="nf">len</span><span class="p">(</span><span class="n">outliers</span><span class="p">)</span><span class="si">}</span><span class="s"> outliers detected in column </span><span class="si">{</span><span class="n">column</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>

    <span class="n">data_for_model</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="nf">drop</span><span class="p">(</span><span class="n">outliers</span><span class="p">)</span>
    
</code></pre></div></div>

<h2 id="split-out-data-for-modelling">Split Out Data for Modelling</h2>
<p>Now that we have cleaned our data, we can split into X (input) and y(output) variables, and then split into train and test sets to ensure that we can validate the accuracy of the predictions on data not used in the training.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">X</span> <span class="o">=</span> <span class="n">data_for_model</span><span class="p">.</span><span class="nf">drop</span><span class="p">(</span><span class="sh">"</span><span class="s">signup_flag</span><span class="sh">"</span><span class="p">,</span> <span class="n">axis</span> <span class="o">=</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">y</span> <span class="o">=</span> <span class="n">data_for_model</span><span class="p">.</span><span class="n">signup_flag</span>
<span class="n">X_train</span><span class="p">,</span> <span class="n">X_test</span><span class="p">,</span> <span class="n">y_train</span><span class="p">,</span> <span class="n">y_test</span> <span class="o">=</span> <span class="nf">train_test_split</span><span class="p">(</span><span class="n">X</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">test_size</span> <span class="o">=</span> <span class="mf">0.2</span><span class="p">,</span> <span class="n">random_state</span> <span class="o">=</span> <span class="mi">23</span><span class="p">,</span> <span class="n">stratify</span> <span class="o">=</span> <span class="n">y</span><span class="p">)</span>

</code></pre></div></div>
<p>In the code above, we have elected to use 80% of our data for training (and use 20% for assessing the accuracy of the model). The argument <em>stratify=y</em> is to ensure that both our training and test sets have the same proportion of customers who did and did not sign up for delivery club - ensuring a robust dataset both for training and testing the model.</p>

<h2 id="model-1-logistic-regression">Model 1: Logistic Regression</h2>

<h3 id="encoding-categorical-variables">Encoding Categorical Variables</h3>
<p>Machine learning models typically cannot process string variables, so when there are text data within a data set, we would encode them (change them to numbers, typically binary) so that the algorithm can assess the relationship between the variable and the output. We could use <em>One Hot Encoding (OHE)</em> or <em>Label Binarizer</em> from the <em>sklearn.preprocessing</em> library.</p>

<p>An extremely efficient way of encoding categorical variables and scaling the inputs is by using <em>DataFrameMapper</em> from <em>sklearn.pandas</em> library. All the variables are scaled in one step.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Instantiate the mapper
</span><span class="n">mapper</span> <span class="o">=</span> <span class="nc">DataFrameMapper</span><span class="p">([</span>
    <span class="p">([</span><span class="sh">'</span><span class="s">distance_from_store</span><span class="sh">'</span><span class="p">],</span> <span class="nc">StandardScaler</span><span class="p">()),</span> 
      <span class="p">(</span><span class="sh">'</span><span class="s">gender</span><span class="sh">'</span><span class="p">,</span> <span class="nc">LabelBinarizer</span><span class="p">()),</span> 
      <span class="p">([</span><span class="sh">'</span><span class="s">credit_score</span><span class="sh">'</span><span class="p">],</span> <span class="nc">StandardScaler</span><span class="p">()),</span>
      <span class="p">([</span><span class="sh">'</span><span class="s">total_sales</span><span class="sh">'</span><span class="p">],</span><span class="nc">StandardScaler</span><span class="p">()),</span>
       <span class="p">([</span><span class="sh">'</span><span class="s">total_items</span><span class="sh">'</span><span class="p">],</span> <span class="nc">StandardScaler</span><span class="p">()),</span>
      <span class="p">([</span><span class="sh">'</span><span class="s">transaction_count</span><span class="sh">'</span><span class="p">],</span><span class="nc">StandardScaler</span><span class="p">()),</span> 
      <span class="p">([</span><span class="sh">'</span><span class="s">product_area_count</span><span class="sh">'</span><span class="p">],</span><span class="nc">StandardScaler</span><span class="p">()),</span> 
       <span class="p">([</span><span class="sh">'</span><span class="s">average_basket_value</span><span class="sh">'</span><span class="p">],</span> <span class="nc">StandardScaler</span><span class="p">())],</span> <span class="n">df_out</span> <span class="o">=</span> <span class="bp">True</span><span class="p">)</span>

<span class="c1"># Fit the mapper to training dataset only  
</span><span class="n">mapper</span><span class="p">.</span><span class="nf">fit</span><span class="p">(</span><span class="n">X_train</span><span class="p">)</span>

<span class="c1"># Transform both train and test sets
</span><span class="n">Z_train</span> <span class="o">=</span> <span class="n">mapper</span><span class="p">.</span><span class="nf">transform</span><span class="p">(</span><span class="n">X_train</span><span class="p">)</span>
<span class="n">Z_test</span> <span class="o">=</span> <span class="n">mapper</span><span class="p">.</span><span class="nf">transform</span><span class="p">(</span><span class="n">X_test</span><span class="p">)</span>

</code></pre></div></div>

<h3 id="feature-selection">Feature Selection</h3>
<p>Feature Selection is the process used to select the input variables that are most important to your Machine Learning task.  It can be a very important addition or at least, a consideration, in certain scenarios.  The potential benefits of Feature Selection are:</p>

<ul>
  <li><strong>Improved Model Accuracy</strong> - eliminating noise can help true relationships stand out</li>
  <li><strong>Lower Computational Cost</strong> - our model becomes faster to train, and faster to make predictions</li>
  <li><strong>Explainability</strong> - understanding &amp; explaining outputs for stakeholders &amp; customers becomes much easier</li>
</ul>

<p>Feature selection can either be done by <em>common sense</em> - common sense feature selection can be as simple as taking out variables we know would <em>absolutely</em> not contribute to the model - like <em>customer_id</em> which we took out in the first few steps. We can also use tools like  <em>Correlation Matrix</em> showing variable relationships, or <em>Univariate Testing</em> which helps us understand statistical relationships between variables or even more powerful approaches like <em>Recursive Feature Elimination (RFE)</em> which is an approach that starts with all input variables, and then iteratively removes those with the weakest relationships with the output variable.</p>

<p>For our task, we applied a variation of Recursive Feature Elimination called <em>Recursive Feature Elimination With Cross Validation (RFECV)</em> where we split the data into many “chunks” and iteratively trains &amp; validates models on each “chunk” separately.  This means that each time we assess different models with different variables included, or eliminated, the algorithm also knows how accurate each of those models was.  From the suite of model scenarios that are created, the algorithm can determine which provided the best accuracy, and thus can infer the best set of input variables to use!</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1"># Instantiate RFECV and the model to be used
</span><span class="n">clf</span> <span class="o">=</span> <span class="nc">LogisticRegression</span><span class="p">(</span><span class="n">random_state</span> <span class="o">=</span> <span class="mi">43</span><span class="p">,</span> <span class="n">max_iter</span> <span class="o">=</span> <span class="mi">1000</span><span class="p">)</span>
<span class="n">feature_selector</span> <span class="o">=</span> <span class="nc">RFECV</span><span class="p">(</span><span class="n">clf</span><span class="p">)</span>

<span class="c1"># Fit RFECV to training data
</span><span class="n">fit</span> <span class="o">=</span> <span class="n">feature_selector</span><span class="p">.</span><span class="nf">fit</span><span class="p">(</span><span class="n">Z_train</span><span class="p">,</span> <span class="n">y_train</span><span class="p">)</span>

<span class="c1"># Extract and print the optimal no of features
</span><span class="n">optimal_feature_count</span> <span class="o">=</span> <span class="n">feature_selector</span><span class="p">.</span><span class="n">n_features_</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Optimal no of features is </span><span class="si">{</span><span class="n">optimal_feature_count</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>

<span class="c1">#limit train and test data to only include selected variables 
</span><span class="n">Z_train</span> <span class="o">=</span> <span class="n">Z_train</span><span class="p">.</span><span class="n">loc</span><span class="p">[:,</span> <span class="n">feature_selector</span><span class="p">.</span><span class="nf">get_support</span><span class="p">()]</span>
<span class="n">Z_test</span> <span class="o">=</span> <span class="n">Z_test</span><span class="p">.</span><span class="n">loc</span><span class="p">[:,</span> <span class="n">feature_selector</span><span class="p">.</span><span class="nf">get_support</span><span class="p">()]</span>

</code></pre></div></div>

<p>The below code then produces a plot that visualizes the cross-validated classification accuracy with each potential number of features</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="n">plt</span><span class="p">.</span><span class="n">style</span><span class="p">.</span><span class="nf">use</span><span class="p">(</span><span class="sh">'</span><span class="s">seaborn-poster</span><span class="sh">'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">plot</span><span class="p">(</span><span class="nf">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nf">len</span><span class="p">(</span><span class="n">fit</span><span class="p">.</span><span class="n">cv_results_</span><span class="p">[</span><span class="sh">'</span><span class="s">mean_test_score</span><span class="sh">'</span><span class="p">])</span> <span class="o">+</span> <span class="mi">1</span><span class="p">),</span> <span class="n">fit</span><span class="p">.</span><span class="n">cv_results_</span><span class="p">[</span><span class="sh">'</span><span class="s">mean_test_score</span><span class="sh">'</span><span class="p">],</span> <span class="n">marker</span> <span class="o">=</span> <span class="sh">"</span><span class="s">o</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">ylabel</span><span class="p">(</span><span class="sh">"</span><span class="s">Classification Accuracy</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">xlabel</span><span class="p">(</span><span class="sh">"</span><span class="s">Number of Features</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">title</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Feature Selection using RFECV </span><span class="se">\n</span><span class="s"> Optimal number of features is </span><span class="si">{</span><span class="n">optimal_feature_count</span><span class="si">}</span><span class="s"> (at score of </span><span class="si">{</span><span class="nf">round</span><span class="p">(</span><span class="nf">max</span><span class="p">(</span><span class="n">fit</span><span class="p">.</span><span class="n">cv_results_</span><span class="p">[</span><span class="sh">'</span><span class="s">mean_test_score</span><span class="sh">'</span><span class="p">]),</span><span class="mi">4</span><span class="p">)</span><span class="si">}</span><span class="s">)</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">tight_layout</span><span class="p">()</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">show</span><span class="p">()</span>

</code></pre></div></div>

<p>This creates the plot below, which shows us that the highest cross-validated classification accuracy (0.904) is when we include seven of our original input variables.  The variable that has been dropped is <em>total_sales</em> but from the chart, we can see that the difference is negligible.  However, we will continue with the selected seven!</p>

<p>[]!(/assets/img/blog/enhance-target-accuracy/log-reg-feature-selection-plot.png)</p>

<h3 id="model-training">Model Training</h3>
<p>Instantiating and training our Logistic Regression model is done using the code below.  We use the <em>random_state</em> parameter to ensure reproducible results, meaning any refinements can be compared to past results.  We also specify <em>max_iter = 1000</em> to allow the solver more attempts at finding an optimal regression line, as the default value of 100 was not enough.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1"># instantiate our model object
</span><span class="n">clf</span> <span class="o">=</span> <span class="nc">LogisticRegression</span><span class="p">(</span><span class="n">random_state</span> <span class="o">=</span> <span class="mi">42</span><span class="p">,</span> <span class="n">max_iter</span> <span class="o">=</span> <span class="mi">1000</span><span class="p">)</span>

<span class="c1"># fit our model using our training &amp; test sets
</span><span class="n">clf</span><span class="p">.</span><span class="nf">fit</span><span class="p">(</span><span class="n">X_train</span><span class="p">,</span> <span class="n">y_train</span><span class="p">)</span>

</code></pre></div></div>

<h3 id="model-assessment">Model Assessment</h3>

<h5 id="predict-on-the-test-set">Predict On The Test Set</h5>

<p>To assess how well our model is predicting on new data - we use the trained model object (here called <em>clf</em>) and ask it to predict the <em>signup_flag</em> variable for the test set.</p>

<p>In the code below we create one object to hold the binary 1/0 predictions, and another to hold the actual prediction probabilities for the positive class.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1"># predict on the test set
</span><span class="n">y_pred_class</span> <span class="o">=</span> <span class="n">clf</span><span class="p">.</span><span class="nf">predict</span><span class="p">(</span><span class="n">X_test</span><span class="p">)</span>
<span class="n">y_pred_prob</span> <span class="o">=</span> <span class="n">clf</span><span class="p">.</span><span class="nf">predict_proba</span><span class="p">(</span><span class="n">X_test</span><span class="p">)[:,</span><span class="mi">1</span><span class="p">]</span>
</code></pre></div></div>
<h5 id="confusion-matrix">Confusion Matrix</h5>

<p>A Confusion Matrix provides us with a visual way to understand how our predictions match up against the actual values for those test set observations.</p>

<p>The below code creates the Confusion Matrix using the <em>confusion_matrix</em> functionality from within scikit-learn and then plots it using matplotlib.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1"># create the confusion matrix
</span><span class="n">conf_matrix</span> <span class="o">=</span> <span class="nf">confusion_matrix</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span> <span class="n">y_pred_class</span><span class="p">)</span>

<span class="c1"># plot the confusion matrix
</span><span class="n">plt</span><span class="p">.</span><span class="n">style</span><span class="p">.</span><span class="nf">use</span><span class="p">(</span><span class="sh">"</span><span class="s">seaborn-poster</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">matshow</span><span class="p">(</span><span class="n">conf_matrix</span><span class="p">,</span> <span class="n">cmap</span> <span class="o">=</span> <span class="sh">"</span><span class="s">coolwarm</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">gca</span><span class="p">().</span><span class="n">xaxis</span><span class="p">.</span><span class="nf">tick_bottom</span><span class="p">()</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">title</span><span class="p">(</span><span class="sh">"</span><span class="s">Confusion Matrix</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">ylabel</span><span class="p">(</span><span class="sh">"</span><span class="s">Actual Class</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">xlabel</span><span class="p">(</span><span class="sh">"</span><span class="s">Predicted Class</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">for </span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">),</span> <span class="n">corr_value</span> <span class="ow">in</span> <span class="n">np</span><span class="p">.</span><span class="nf">ndenumerate</span><span class="p">(</span><span class="n">conf_matrix</span><span class="p">):</span>
    <span class="n">plt</span><span class="p">.</span><span class="nf">text</span><span class="p">(</span><span class="n">j</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">corr_value</span><span class="p">,</span> <span class="n">ha</span> <span class="o">=</span> <span class="sh">"</span><span class="s">center</span><span class="sh">"</span><span class="p">,</span> <span class="n">va</span> <span class="o">=</span> <span class="sh">"</span><span class="s">center</span><span class="sh">"</span><span class="p">,</span> <span class="n">fontsize</span> <span class="o">=</span> <span class="mi">20</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">show</span><span class="p">()</span>

</code></pre></div></div>

<p><br />
<img src="/assets/img/blog/enhance-target-accuracy/log-reg-confusion-matrix.png" alt="alt text" /></p>

<p>The aim is to have a high proportion of observations falling into the top left cell (predicted non-signup and actual non-signup) and the bottom right cell (predicted signup and actual signup).</p>

<p>Since the proportion of signups in our data was around 30:70 we will next analyze not only Classification Accuracy, but also Precision, Recall, and F1-Score which will help us assess how well our model has performed in reality.</p>

<h5 id="classification-performance-metrics">Classification Performance Metrics</h5>

<ul>
  <li><strong>Accuracy</strong>: A very intuitive metric, the accuracy measures the number of correct classification predictions out of all predictions made. This number could be very misleading when you have very imbalanced data. An example of imbalanced data is a dataset where we have a 98%/2% split across 2 classes (98% of patients without a disease and 2% with a disease). If our model predicts no one has the disease, we get an accuracy of 98% which wouldn’t be a great model in the real world.</li>
  <li><strong>Precision</strong>: Answers the question: of all observations predicted as positive, what proportion was actually positive?</li>
  <li><strong>Recall</strong>: Answers the question: of all positive observations, how many did we predict as positive?  <br />
Usually, trying to improve <em>Precision</em> has the opposite effect on <em>Recall</em> and vice-versa. So what metric to focus on is purely based on the project goals.</li>
  <li><strong>F1-Score</strong>: This is the harmonic mean of Precision and Recall. A good F1-score comes when there is a balance between precision and recall.</li>
</ul>

<p><br />
In the code below, we utilize in-built functionality from scikit-learn to calculate these four metrics.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1"># classification accuracy
</span><span class="n">accuracy_lr</span> <span class="o">=</span> <span class="nf">round</span><span class="p">(</span><span class="nf">accuracy_score</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span> <span class="n">y_pred_class</span><span class="p">)</span><span class="o">*</span><span class="mi">100</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Accuracy score is </span><span class="si">{</span><span class="n">accuracy_lr</span><span class="si">}</span><span class="s">, meaning the model coorectly predicts </span><span class="si">{</span><span class="n">accuracy_lr</span><span class="si">}</span><span class="s">% of the test set observations</span><span class="sh">"</span><span class="p">)</span>

<span class="c1"># precision
</span><span class="n">precision_lr</span> <span class="o">=</span> <span class="nf">round</span><span class="p">(</span><span class="nf">precision_score</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span> <span class="n">y_pred_class</span><span class="p">)</span><span class="o">*</span><span class="mi">100</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Precision score is </span><span class="si">{</span><span class="n">precision_lr</span><span class="si">}</span><span class="s">, meaning for our predicted delivery club signups, the model was correct </span><span class="si">{</span><span class="n">precision_lr</span><span class="si">}</span><span class="s">% of the time</span><span class="sh">"</span><span class="p">)</span>

<span class="c1"># recall
</span><span class="n">recall_lr</span> <span class="o">=</span> <span class="nf">round</span><span class="p">(</span><span class="nf">recall_score</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span> <span class="n">y_pred_class</span><span class="p">)</span><span class="o">*</span><span class="mi">100</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Recall score is </span><span class="si">{</span><span class="n">recall_lr</span><span class="si">}</span><span class="s">, meaning of allactual delivery club signups, the model correctly predicts </span><span class="si">{</span><span class="n">recall_lr</span><span class="si">}</span><span class="s">% of the time</span><span class="sh">"</span><span class="p">)</span>

<span class="c1"># f1-score
</span><span class="n">f1_lr</span> <span class="o">=</span> <span class="nf">round</span><span class="p">(</span><span class="nf">f1_score</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span> <span class="n">y_pred_class</span><span class="p">),</span> <span class="mi">2</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">F1 score is </span><span class="si">{</span><span class="n">f1_lr</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="finding-the-optimal-classification-threshold">Finding The Optimal Classification Threshold</h3>

<p>By default, most pre-built classification models &amp; algorithms will just use a 50% probability to discern between a positive class prediction (delivery club signup) and a negative class prediction (delivery club non-signup).</p>

<p>Just because 50% is the default threshold <em>does not mean</em> it is the best one for our task.</p>

<p>Here, we will test many potential classification thresholds, and plot the Precision, Recall &amp; F1-Score, and find an optimal solution!</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># set up the list of thresholds to loop through
</span><span class="n">thresholds</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">arange</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mf">0.01</span><span class="p">)</span>

<span class="c1"># create empty lists to append the results to
</span><span class="n">precision_scores</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">recall_scores</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">f1_scores</span> <span class="o">=</span> <span class="p">[]</span>

<span class="c1"># loop through each threshold - fit the model - append the results
</span><span class="k">for</span> <span class="n">threshold</span> <span class="ow">in</span> <span class="n">thresholds</span><span class="p">:</span>
    
    <span class="n">pred_class</span> <span class="o">=</span> <span class="p">(</span><span class="n">y_pred_prob</span> <span class="o">&gt;=</span> <span class="n">threshold</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1</span>
    
    <span class="n">precision</span> <span class="o">=</span> <span class="nf">precision_score</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span> <span class="n">pred_class</span><span class="p">,</span> <span class="n">zero_division</span> <span class="o">=</span> <span class="mi">0</span><span class="p">)</span>
    <span class="n">precision_scores</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">precision</span><span class="p">)</span>
    
    <span class="n">recall</span> <span class="o">=</span> <span class="nf">recall_score</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span> <span class="n">pred_class</span><span class="p">)</span>
    <span class="n">recall_scores</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">recall</span><span class="p">)</span>
    
    <span class="n">f1</span> <span class="o">=</span> <span class="nf">f1_score</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span> <span class="n">pred_class</span><span class="p">)</span>
    <span class="n">f1_scores</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">f1</span><span class="p">)</span>
    
<span class="c1"># extract the optimal f1-score (and it's index)
</span><span class="n">max_f1</span> <span class="o">=</span> <span class="nf">max</span><span class="p">(</span><span class="n">f1_scores</span><span class="p">)</span>
<span class="n">max_f1_idx</span> <span class="o">=</span> <span class="n">f1_scores</span><span class="p">.</span><span class="nf">index</span><span class="p">(</span><span class="n">max_f1</span><span class="p">)</span>



<span class="c1"># plot the results
</span><span class="n">plt</span><span class="p">.</span><span class="n">style</span><span class="p">.</span><span class="nf">use</span><span class="p">(</span><span class="sh">"</span><span class="s">seaborn-poster</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">plot</span><span class="p">(</span><span class="n">thresholds</span><span class="p">,</span> <span class="n">precision_scores</span><span class="p">,</span> <span class="n">label</span> <span class="o">=</span> <span class="sh">"</span><span class="s">Precision</span><span class="sh">"</span><span class="p">,</span> <span class="n">linestyle</span> <span class="o">=</span> <span class="sh">"</span><span class="s">--</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">plot</span><span class="p">(</span><span class="n">thresholds</span><span class="p">,</span> <span class="n">recall_scores</span><span class="p">,</span> <span class="n">label</span> <span class="o">=</span> <span class="sh">"</span><span class="s">Recall</span><span class="sh">"</span><span class="p">,</span> <span class="n">linestyle</span> <span class="o">=</span> <span class="sh">"</span><span class="s">--</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">plot</span><span class="p">(</span><span class="n">thresholds</span><span class="p">,</span> <span class="n">f1_scores</span><span class="p">,</span> <span class="n">label</span> <span class="o">=</span> <span class="sh">"</span><span class="s">F1</span><span class="sh">"</span><span class="p">,</span> <span class="n">linewidth</span> <span class="o">=</span> <span class="mi">5</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">title</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Finding the Optimal Threshold for Classification Model </span><span class="se">\n</span><span class="s"> Max F1: </span><span class="si">{</span><span class="nf">round</span><span class="p">(</span><span class="n">max_f1</span><span class="p">,</span><span class="mi">2</span><span class="p">)</span><span class="si">}</span><span class="s"> (Threshold = </span><span class="si">{</span><span class="nf">round</span><span class="p">(</span><span class="n">thresholds</span><span class="p">[</span><span class="n">max_f1_idx</span><span class="p">],</span><span class="mi">2</span><span class="p">)</span><span class="si">}</span><span class="s">)</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">xlabel</span><span class="p">(</span><span class="sh">"</span><span class="s">Threshold</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">ylabel</span><span class="p">(</span><span class="sh">"</span><span class="s">Assessment Score</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">legend</span><span class="p">(</span><span class="n">loc</span> <span class="o">=</span> <span class="sh">"</span><span class="s">lower left</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">tight_layout</span><span class="p">()</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">show</span><span class="p">()</span>

</code></pre></div></div>
<p><img src="/assets/img/blog/enhance-target-accuracy/log-reg-optimal-threshold-plot.png" alt="alt text" title="Logistic Regression Optimal Threshold Plot" /></p>

<p>Along the x-axis of the above plot we have the different classification thresholds that were testing.  Along the y-axis we have the performance score for each of our three metrics.  As per the legend, we have Precision as a blue dotted line, Recall as an orange dotted line, and F1-Score as a thick green line.  You can see the interesting “zero-sum” relationship between Precision &amp; Recall <em>and</em> you can see that the point where Precision &amp; Recall meet is where F1-Score is maximised.</p>

<p>As you can see at the top of the plot, the optimal F1-Score for this model 0.78 and this is obtained at a classification threshold of 0.44.  This is higher than the F1-Score of 0.734 that we achieved at the default classification threshold of 0.50!</p>

<h2 id="model-2-decision-trees">Model 2: Decision Trees</h2>
<p>The same steps in Logistic Regression will be used for the Decision Trees - with some minor differences.</p>

<h3 id="encode-categorical-variables">Encode Categorical Variables</h3>

<p>Using the same method as in Logistic Regression, we encode the <em>gender</em> variable using DataFrameMapper and LabelBinzrizer. Since this is a decision tree, we do not need to scale the numerical variables, so <em>None</em> is an argument in the mapper, indicating that no transformation is to be done on the numerical variables.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="n">mapper_dt</span> <span class="o">=</span> <span class="nc">DataFrameMapper</span><span class="p">([</span>
    <span class="p">([</span><span class="sh">'</span><span class="s">distance_from_store</span><span class="sh">'</span><span class="p">],</span> <span class="bp">None</span><span class="p">),</span> 
      <span class="p">(</span><span class="sh">'</span><span class="s">gender</span><span class="sh">'</span><span class="p">,</span> <span class="nc">LabelBinarizer</span><span class="p">()),</span> 
      <span class="p">([</span><span class="sh">'</span><span class="s">credit_score</span><span class="sh">'</span><span class="p">],</span> <span class="bp">None</span><span class="p">),</span>
      <span class="p">([</span><span class="sh">'</span><span class="s">total_sales</span><span class="sh">'</span><span class="p">],</span><span class="bp">None</span><span class="p">),</span>
       <span class="p">([</span><span class="sh">'</span><span class="s">total_items</span><span class="sh">'</span><span class="p">],</span> <span class="bp">None</span><span class="p">),</span>
      <span class="p">([</span><span class="sh">'</span><span class="s">transaction_count</span><span class="sh">'</span><span class="p">],</span><span class="bp">None</span><span class="p">),</span> 
      <span class="p">([</span><span class="sh">'</span><span class="s">product_area_count</span><span class="sh">'</span><span class="p">],</span><span class="bp">None</span><span class="p">),</span> 
       <span class="p">([</span><span class="sh">'</span><span class="s">average_basket_value</span><span class="sh">'</span><span class="p">],</span> <span class="bp">None</span><span class="p">)],</span> <span class="n">df_out</span> <span class="o">=</span> <span class="bp">True</span><span class="p">)</span>

<span class="n">mapper_dt</span><span class="p">.</span><span class="nf">fit</span><span class="p">(</span><span class="n">X_train</span><span class="p">)</span>

<span class="n">Z_train_dt</span> <span class="o">=</span> <span class="n">mapper_dt</span><span class="p">.</span><span class="nf">transform </span><span class="p">(</span><span class="n">X_train</span><span class="p">)</span>
<span class="n">Z_test_dt</span> <span class="o">=</span> <span class="n">mapper_dt</span><span class="p">.</span><span class="nf">transform</span><span class="p">(</span><span class="n">X_test</span><span class="p">)</span>

</code></pre></div></div>

<h3 id="model-training-1">Model Training</h3>
<p>The code below instantiates the Decision Tree model with a random state specified for reproducibility and a maximum depth to prevent over-fitting.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Instantiate our model object
</span><span class="n">dt</span> <span class="o">=</span> <span class="nc">DecisionTreeClassifier</span><span class="p">(</span><span class="n">random_state</span> <span class="o">=</span> <span class="mi">42</span><span class="p">,</span> <span class="n">max_depth</span> <span class="o">=</span> <span class="mi">5</span><span class="p">)</span>

</code></pre></div></div>
<h3 id="model-assessment-1">Model Assessment</h3>
<p>As before, we would generate the predicted class on the test data, and calculate the accuracy, precision, recall and F1-score.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># create the confusion matrix
</span><span class="n">conf_matrix</span> <span class="o">=</span> <span class="nf">confusion_matrix</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span> <span class="n">y_pred_class_dt</span><span class="p">)</span>

<span class="c1"># plot the confusion matrix
</span><span class="n">plt</span><span class="p">.</span><span class="n">style</span><span class="p">.</span><span class="nf">use</span><span class="p">(</span><span class="sh">"</span><span class="s">seaborn-poster</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">matshow</span><span class="p">(</span><span class="n">conf_matrix</span><span class="p">,</span> <span class="n">cmap</span> <span class="o">=</span> <span class="sh">"</span><span class="s">coolwarm</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">gca</span><span class="p">().</span><span class="n">xaxis</span><span class="p">.</span><span class="nf">tick_bottom</span><span class="p">()</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">title</span><span class="p">(</span><span class="sh">"</span><span class="s">Confusion Matrix</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">ylabel</span><span class="p">(</span><span class="sh">"</span><span class="s">Actual Class</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">xlabel</span><span class="p">(</span><span class="sh">"</span><span class="s">Predicted Class</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">for </span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">),</span> <span class="n">corr_value</span> <span class="ow">in</span> <span class="n">np</span><span class="p">.</span><span class="nf">ndenumerate</span><span class="p">(</span><span class="n">conf_matrix</span><span class="p">):</span>
    <span class="n">plt</span><span class="p">.</span><span class="nf">text</span><span class="p">(</span><span class="n">j</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">corr_value</span><span class="p">,</span> <span class="n">ha</span> <span class="o">=</span> <span class="sh">"</span><span class="s">center</span><span class="sh">"</span><span class="p">,</span> <span class="n">va</span> <span class="o">=</span> <span class="sh">"</span><span class="s">center</span><span class="sh">"</span><span class="p">,</span> <span class="n">fontsize</span> <span class="o">=</span> <span class="mi">20</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">show</span><span class="p">()</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="n">accuracy_dt</span> <span class="o">=</span> <span class="nf">round</span><span class="p">(</span><span class="nf">accuracy_score</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span> <span class="n">y_pred_class_dt</span><span class="p">)</span><span class="o">*</span><span class="mi">100</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Accuracy score is </span><span class="si">{</span><span class="n">accuracy_dt</span><span class="si">}</span><span class="s">, meaning the model correctly predicts </span><span class="si">{</span><span class="n">accuracy_dt</span><span class="si">}</span><span class="s">% of the test set observations</span><span class="sh">"</span><span class="p">)</span>

<span class="c1"># precision
</span><span class="n">precision_dt</span> <span class="o">=</span>  <span class="nf">round</span><span class="p">(</span><span class="nf">precision_score</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span> <span class="n">y_pred_class_dt</span><span class="p">)</span><span class="o">*</span><span class="mi">100</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Precision score is </span><span class="si">{</span><span class="n">precision_dt</span><span class="si">}</span><span class="s">, meaning for our predicted delivery club signups, the model was correct </span><span class="si">{</span><span class="n">precision_dt</span><span class="si">}</span><span class="s">% of the time</span><span class="sh">"</span><span class="p">)</span>

<span class="c1"># recall
</span><span class="n">recall_dt</span><span class="o">=</span> <span class="nf">round</span><span class="p">(</span><span class="nf">recall_score</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span> <span class="n">y_pred_class_dt</span><span class="p">)</span><span class="o">*</span><span class="mi">100</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Recall score is </span><span class="si">{</span><span class="n">recall_dt</span><span class="si">}</span><span class="s">, meaning of actual delivery club signups, the model correctly predicts </span><span class="si">{</span><span class="n">recall_dt</span><span class="si">}</span><span class="s">% of the time</span><span class="sh">"</span><span class="p">)</span>

<span class="c1"># f1-score
</span><span class="n">f1_dt</span> <span class="o">=</span> <span class="nf">round</span><span class="p">(</span><span class="nf">f1_score</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span> <span class="n">y_pred_class_dt</span><span class="p">),</span> <span class="mi">2</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">F1 score is </span><span class="si">{</span><span class="n">f1_dt</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>

</code></pre></div></div>
<h3 id="plot-decision-tree">Plot Decision Tree</h3>

<p>For Decision Trees, we can literally plot out the <em>Decision Tree</em> which shows us how the splits were made.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">plt</span><span class="p">.</span><span class="nf">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">25</span><span class="p">,</span><span class="mi">15</span><span class="p">))</span>
<span class="n">tree</span> <span class="o">=</span> <span class="nf">plot_tree</span><span class="p">(</span><span class="n">dt</span><span class="p">,</span>
                 <span class="n">feature_names</span> <span class="o">=</span> <span class="n">X</span><span class="p">.</span><span class="n">columns</span><span class="p">,</span>
                 <span class="n">filled</span> <span class="o">=</span> <span class="bp">True</span><span class="p">,</span>
                 <span class="n">rounded</span> <span class="o">=</span> <span class="bp">True</span><span class="p">,</span>
                 <span class="n">fontsize</span> <span class="o">=</span> <span class="mi">16</span><span class="p">)</span>
</code></pre></div></div>
<p><img src="/assets/img/blog/enhance-target-accuracy/clf-tree-nodes-plot.png" alt="alt text" /></p>

<h3 id="finding-the-best-max-depth-to-prevent-overfitting">Finding the Best Max Depth to Prevent Overfitting</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">max_depth_list</span> <span class="o">=</span> <span class="nf">list</span><span class="p">(</span><span class="nf">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">20</span><span class="p">))</span>
<span class="n">accuracy_scores</span> <span class="o">=</span> <span class="p">[]</span>

<span class="c1"># loop through each possible depth, train and validate model, append test set f1-score
</span><span class="k">for</span> <span class="n">depth</span> <span class="ow">in</span> <span class="n">max_depth_list</span><span class="p">:</span>
    
    <span class="n">dt</span> <span class="o">=</span> <span class="nc">DecisionTreeClassifier</span><span class="p">(</span><span class="n">max_depth</span> <span class="o">=</span> <span class="n">depth</span><span class="p">,</span> <span class="n">random_state</span> <span class="o">=</span> <span class="mi">42</span><span class="p">)</span>
    <span class="n">dt</span><span class="p">.</span><span class="nf">fit</span><span class="p">(</span><span class="n">Z_train_dt</span><span class="p">,</span><span class="n">y_train</span><span class="p">)</span>
    <span class="n">y_pred_dt</span> <span class="o">=</span> <span class="n">dt</span><span class="p">.</span><span class="nf">predict</span><span class="p">(</span><span class="n">Z_test_dt</span><span class="p">)</span>
    <span class="n">accuracy</span> <span class="o">=</span> <span class="nf">f1_score</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span><span class="n">y_pred_dt</span><span class="p">)</span>
    <span class="n">accuracy_scores</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">accuracy</span><span class="p">)</span>
    
<span class="c1"># store max accuracy, and optimal depth    
</span><span class="n">max_accuracy</span> <span class="o">=</span> <span class="nf">max</span><span class="p">(</span><span class="n">accuracy_scores</span><span class="p">)</span>
<span class="n">max_accuracy_idx</span> <span class="o">=</span> <span class="n">accuracy_scores</span><span class="p">.</span><span class="nf">index</span><span class="p">(</span><span class="n">max_accuracy</span><span class="p">)</span>
<span class="n">optimal_depth</span> <span class="o">=</span> <span class="n">max_depth_list</span><span class="p">[</span><span class="n">max_accuracy_idx</span><span class="p">]</span>

<span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nc">DataFrame</span><span class="p">({</span><span class="sh">'</span><span class="s">max_depth_list</span><span class="sh">'</span><span class="p">:</span> <span class="n">max_depth_list</span><span class="p">,</span> <span class="sh">'</span><span class="s">accuracy_scores</span><span class="sh">'</span><span class="p">:</span> <span class="n">accuracy_scores</span><span class="p">})</span>
                   
<span class="c1"># plot accuracy by max depth
</span><span class="n">px</span><span class="p">.</span><span class="nf">line</span><span class="p">(</span><span class="n">df</span><span class="p">,</span> <span class="n">x</span> <span class="o">=</span> <span class="sh">"</span><span class="s">max_depth_list</span><span class="sh">"</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="sh">"</span><span class="s">accuracy_scores</span><span class="sh">"</span><span class="p">,</span> 
        <span class="n">title</span> <span class="o">=</span> <span class="sa">f</span><span class="sh">"</span><span class="s">Accuracy (F1 score) by Max Depth </span><span class="se">\n</span><span class="s"> Optimal Tree Depth: </span><span class="si">{</span><span class="n">optimal_depth</span><span class="si">}</span><span class="s"> (F1 Score: </span><span class="si">{</span><span class="nf">round</span><span class="p">(</span><span class="n">max_accuracy</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span><span class="si">}</span><span class="s">)</span><span class="sh">"</span><span class="p">)</span>
           
</code></pre></div></div>
<p><img src="/assets/img/blog/enhance-target-accuracy/clf-tree-max-depth-plot.png" alt="alt text" /></p>

<p>The plot above shows us that the maximum F1-score on the test set is found when applying a maximum depth value of 5.</p>

<h2 id="model-3-random-forest-ensemble">Model 3: Random Forest (Ensemble)</h2>
<p>The same process of instantiating, fitting and assessing the model is followed. For code, go to the <a href="https://github.com/ibiene-ds/enhancing-targeting-accuracy/blob/main/Enhancing%20Targeting%20Accuracy%20using%20ML.ipynb">Jupyter Notebook</a></p>

<h2 id="model-4-k-nearest-neighbors-knn">Model 4: K Nearest Neighbors (KNN)</h2>
<p>The same process of instantiating, fitting and assessing the model is followed. For code, go to the <a href="https://github.com/ibiene-ds/enhancing-targeting-accuracy/blob/main/Enhancing%20Targeting%20Accuracy%20using%20ML.ipynb">Jupyter Notebook</a></p>

<h2 id="results">Results</h2>

<p>The goals of the project were:</p>
<ul>
  <li>Build a model that would accurately predict the customers that would sign up for the <em>delivery club</em>.  This would allow for a much more targeted approach when running the next iteration of the campaign.</li>
  <li>Understand what the drivers for this are, so the client can get closer to the customers that need or want this service, and enhance their messaging.</li>
</ul>

<p>Based upon these, the chosen model is the Random Forest as it was:</p>
<ul>
  <li>the most consistently performant on the test set across classification accuracy, precision, recall, and F1-score, and</li>
  <li>the feature importance and permutation importance allow the client an understanding of the key drivers behind <em>delivery club</em> signups. See the table below for a summary of all model results.
<br />
<br />
<img src="/assets/img/blog/enhance-target-accuracy/model_summary.png" alt="alt text" />
<br /> 
<br /></li>
</ul>

<h3 id="key-drivers-of-delivery-club-signups">Key Drivers of Delivery Club Signups</h3>

<p>As well, the feature importances and permutation importances of the various input variables are shown below. This shows the key drivers behind the delivery club signups, which in this case are: Distance from Store and Transaction Count.</p>

<p><img src="/assets/img/blog/enhance-target-accuracy/rf-classification-feature-importance.png" alt="alt text" />
<br />
<br />
<img src="/assets/img/blog/enhance-target-accuracy/rf-classification-permutation-importance.png" alt="alt text" /></p>

<h2 id="application">Application</h2>

<p>We now have a model object, and the required pre-processing steps to use this model for the next <em>delivery club</em> campaign.  When this is ready to launch we can aggregate the necessary customer information and pass it through, obtaining predicted probabilities for each customer signing up.</p>

<p>Based upon this, we can work with the client to discuss where their budget can stretch to, and contact only the customers with a high propensity to join.  This will drastically reduce marketing costs, and result in a much-improved ROI.</p>

<h2 id="growth--next-steps">Growth &amp; Next Steps</h2>

<p>While predictive accuracy was relatively high - other modelling approaches could be tested, especially those somewhat similar to Random Forest, for example, XGBoost and LightGBM to see if even more accuracy could be gained.</p>

<p>We could even look to tune the hyperparameters of the Random Forest, notably regularisation parameters such as tree depth, as well as potentially training on a higher number of Decision Trees in the Random Forest.</p>

<p>From a data point of view, further variables could be collected, and further feature engineering could be undertaken.</p>]]></content><author><name>Farisayi Dakwa</name><email>fariedakwa.fd@gmail.com</email></author><category term="Data Science" /><category term="Classification" /><category term="Python" /><category term="Machine Learning" /><summary type="html"><![CDATA[The goal for the project is to build a model that would accurately predict the customers that would sign up for our client's *delivery club* services. We use different classification algorithms to perform this task and understand what the main drivers are.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="/assets/img/blog/enhance-target-accuracy/classification-title-img.png" /><media:content medium="image" url="/assets/img/blog/enhance-target-accuracy/classification-title-img.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>