<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Stacy Mumbi</title>
    <description>The latest articles on DEV Community by Stacy Mumbi (@smumbi_).</description>
    <link>https://dev.to/smumbi_</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4071797%2Fad6f237c-97c0-4a01-aa2d-8024c09f823c.png</url>
      <title>DEV Community: Stacy Mumbi</title>
      <link>https://dev.to/smumbi_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/smumbi_"/>
    <language>en</language>
    <item>
      <title>From Messy CSV to Boardroom-Ready: Cleaning, Modelling and Visualizing JCars Logistics' Sales Data in Power BI</title>
      <dc:creator>Stacy Mumbi</dc:creator>
      <pubDate>Wed, 30 Sep 2026 07:18:57 +0000</pubDate>
      <link>https://dev.to/smumbi_/from-messy-csv-to-boardroom-ready-cleaning-modelling-and-visualizing-jcars-logistics-sales-data-11ni</link>
      <guid>https://dev.to/smumbi_/from-messy-csv-to-boardroom-ready-cleaning-modelling-and-visualizing-jcars-logistics-sales-data-11ni</guid>
      <description>&lt;h2&gt;
  
  
  1. Project Background and Business Objective
&lt;/h2&gt;

&lt;p&gt;JCars Logistics imports, sells and delivers vehicles to customers across Kenya. Management requested a Power BI solution to understand the company's overall performance and identify areas that may need attention. The analysis covers sales, revenue, costs and profitability, vehicle performance, branch and regional performance, sales representatives, lead sources, payments, deliveries and logistics, returns and cancellations, customer experience and unusual transactions that may need further investigation.&lt;/p&gt;

&lt;p&gt;This report explains the journey from the raw dataset to the final Power BI solution. It covers data cleaning, data modelling, DAX measures, dashboard development and the main insights and recommendations from the analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Dataset and Grain
&lt;/h2&gt;

&lt;p&gt;The source data was a single raw flat file containing 32 columns and 276 rows. Each row represents one vehicle sold in one sales transaction. This level of detail was kept throughout the project. No aggregation or splitting was needed when creating the fact table.&lt;/p&gt;

&lt;p&gt;The columns were grouped into several main areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transaction information such as Order ID and dates&lt;/li&gt;
&lt;li&gt;Customer information&lt;/li&gt;
&lt;li&gt;Vehicle information&lt;/li&gt;
&lt;li&gt;Location information such as Region, County, City and Branch&lt;/li&gt;
&lt;li&gt;Sales information such as Sales Rep and Lead Source&lt;/li&gt;
&lt;li&gt;Financial information such as prices, costs, discounts, fees and revenue&lt;/li&gt;
&lt;li&gt;Operational information such as payment, delivery, returns and customer ratings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2yzu7wqbf3y5r8jgha6a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2yzu7wqbf3y5r8jgha6a.png" alt="Raw Dataset before Cleaning" width="800" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Screenshot: Raw dataset showing the original columns and sample records before cleaning.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Data Quality Audit: Key Issues Identified
&lt;/h2&gt;

&lt;p&gt;More than ten major data quality issues were identified and resolved during the cleaning process. The most important ones are summarised below. The same cleaning approach was applied consistently across the dataset.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Issue Found&lt;/th&gt;
&lt;th&gt;Why It's a Problem&lt;/th&gt;
&lt;th&gt;How It Was Handled&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Order IDs were recorded in four different formats (&lt;code&gt;LC1000&lt;/code&gt;, &lt;code&gt;LCL-1001&lt;/code&gt;, &lt;code&gt;ORD1004&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;The same type of identifier had different formats, making it difficult to identify records consistently&lt;/td&gt;
&lt;td&gt;The digits were extracted and standardized to &lt;code&gt;ORD-####&lt;/code&gt;. The original value was kept separately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Dates appeared in 5+ formats, including Excel serial numbers such as &lt;code&gt;45671&lt;/code&gt;, as well as invalid dates such as &lt;code&gt;31/02/2026&lt;/code&gt; and &lt;code&gt;April 31 2026&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The dates could not always be read correctly and some of them did not exist on the calendar&lt;/td&gt;
&lt;td&gt;Excel serial numbers were converted using the Excel date system. Day-first formatting was used for ambiguous dates. Invalid or unrecognizable dates such as &lt;code&gt;#DATE!&lt;/code&gt;, "not sure" and impossible calendar dates were changed to null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Customer Age contained impossible values such as &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;5&lt;/code&gt;, &lt;code&gt;121&lt;/code&gt; and &lt;code&gt;-5&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;These values are not realistic for the customer information being analysed&lt;/td&gt;
&lt;td&gt;Unrealistic ages were changed to null. Negative signs were removed from otherwise realistic ages where they appeared to be data entry errors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Monetary columns contained 5 different currencies: KES/KSh, USD ($ and text), EUR, ZAR/R and a corrupted symbol (&lt;code&gt;?&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Values from different currencies cannot be directly compared or added together&lt;/td&gt;
&lt;td&gt;The currency was identified for each value and converted to KES. Values without a currency label were treated as KES based on the assessment instructions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Categorical fields such as Customer Type, Car Make, Car Model, Vehicle Type, Fuel Type, Color, Payment Method/Status, Delivery Status, Returned, Region, County, City and Branch had multiple spelling, casing and abbreviation variations&lt;/td&gt;
&lt;td&gt;The same category could appear as different categories in the analysis&lt;/td&gt;
&lt;td&gt;The values were standardized into one consistent category using evidence from related fields where necessary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Zero values in monetary columns did not always mean the same thing&lt;/td&gt;
&lt;td&gt;A KSh 0 selling price for a paid transaction is unlikely to be valid, while KSh 0 revenue for a cancelled transaction can be reasonable&lt;/td&gt;
&lt;td&gt;Zero values were assessed separately for each column based on the business context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Vehicle Year included &lt;code&gt;1899&lt;/code&gt;, &lt;code&gt;2032&lt;/code&gt;, &lt;code&gt;202A&lt;/code&gt; and &lt;code&gt;Twenty Twenty&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;These values would affect year-based analysis and some were clearly invalid&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;202A&lt;/code&gt; was corrected to &lt;code&gt;2024&lt;/code&gt; based on the Order Date. &lt;code&gt;Twenty Twenty&lt;/code&gt; was changed to &lt;code&gt;2020&lt;/code&gt;. &lt;code&gt;1899&lt;/code&gt; and &lt;code&gt;2032&lt;/code&gt; were changed to null because there was not enough evidence to recover the correct values&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Discount values appeared as percentages, decimals, words and included an outlier of &lt;code&gt;120%&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;A discount above 100% is not a realistic business value&lt;/td&gt;
&lt;td&gt;Values were standardized to decimal format. Negative discounts and discounts above 50% were treated as unrecoverable errors and changed to null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Customer Rating included numbers, &lt;code&gt;"X out of 5"&lt;/code&gt;, &lt;code&gt;"X/5"&lt;/code&gt;, &lt;code&gt;"Excellent"&lt;/code&gt; and invalid values such as &lt;code&gt;-1&lt;/code&gt; and &lt;code&gt;6&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Ratings outside the expected range would affect customer satisfaction analysis&lt;/td&gt;
&lt;td&gt;Text formats were converted to numbers. &lt;code&gt;"Excellent"&lt;/code&gt; was mapped to &lt;code&gt;5&lt;/code&gt; and values outside the 0 to 5 range were changed to null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Some locations were labelled as both a "Branch" and a "Yard", such as "Eldoret Branch" and "Eldoret Yard"&lt;/td&gt;
&lt;td&gt;A branch and a yard can represent different operational locations rather than spelling variations&lt;/td&gt;
&lt;td&gt;The distinction was kept so that branch and yard performance could be analysed separately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Some Sales Rep names contained number-for-letter errors such as &lt;code&gt;"Wanj1ku"&lt;/code&gt; instead of &lt;code&gt;"Wanjiku"&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;These errors created separate and incorrect versions of the same sales representative&lt;/td&gt;
&lt;td&gt;The corrupted characters were corrected and matched against the verified list of 10 sales representatives&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  4. Currency Standardization
&lt;/h2&gt;

&lt;p&gt;All monetary values were converted to Kenya Shillings (KES) so that the report could use one consistent reporting currency. This also followed the assessment instruction that values without a currency label should be treated as KES.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exchange rates used:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Currency&lt;/th&gt;
&lt;th&gt;Rate to KES&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;USD&lt;/td&gt;
&lt;td&gt;129.54&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EUR&lt;/td&gt;
&lt;td&gt;147.84&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZAR&lt;/td&gt;
&lt;td&gt;7.93&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GBP&lt;/td&gt;
&lt;td&gt;Not applicable, investigated and confirmed absent from the dataset&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;?&lt;/code&gt; symbol that appeared before some monetary values was investigated by comparing the affected Unit Selling Price with the Unit Cost in the same row. The values consistently matched USD-level pricing, so the &lt;code&gt;?&lt;/code&gt; symbol was treated as a corrupted USD symbol rather than making an unsupported assumption.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero-value business rules:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unit Selling Price / Unit Cost&lt;/strong&gt;: zero was treated as missing because a vehicle would not normally have zero acquisition or selling value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delivery Fee&lt;/strong&gt;: zero was accepted because free delivery is possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logistics Cost&lt;/strong&gt;: zero was treated as missing where &lt;code&gt;DeliveryStatus&lt;/code&gt; showed that the vehicle had actually been delivered.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revenue Recorded&lt;/strong&gt;: zero was accepted where &lt;code&gt;PaymentStatus&lt;/code&gt; was Cancelled or Refunded. In other cases it was treated as missing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was the formula format I used to work on the monetary columns with different currencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight q"&gt;&lt;code&gt;&lt;span class="n"&gt;fnMoneyKES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;let&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Text.Trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Text.From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;??&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;upperRaw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Text.Upper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;isMissing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"#VALUE!"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"#ERROR"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"-"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;upperRaw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"NULL"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;upperRaw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"TBD"&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Text.Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upperRaw&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"MISSING"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Text.Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upperRaw&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"NOT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;hasM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Text.EndsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upperRaw&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"M"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;currency&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Text.Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upperRaw&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"KES"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Text.Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upperRaw&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"KSH"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"KES"&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Text.Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"$"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Text.Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upperRaw&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"USD"&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Text.Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upperRaw&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"EUR"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"EUR"&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Text.Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upperRaw&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ZAR"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Text.StartsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Text.Trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"R "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ZAR"&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Text.Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"?"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"USD"&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"KES"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;numText&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Text.Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="s"&gt;"9"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;numVal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Number.From&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;otherwise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;isMissing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;numVal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hasM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Number.Abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numVal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;currency&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"USD"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Number.Abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numVal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;129.54&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;currency&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"EUR"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Number.Abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numVal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;147.84&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;currency&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"ZAR"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Number.Abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numVal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;7.93&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Number.Abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numVal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Data Validation Performed
&lt;/h2&gt;

&lt;p&gt;Several validation checks were carried out after the main cleaning process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cross-checked currency assumptions against Unit Cost and Unit Selling Price values in the same row before finalizing the conversion logic.&lt;/li&gt;
&lt;li&gt;Compared Payment Status and Delivery Status for logical consistency. This identified 14 records where the two fields did not agree. There were 10 records showing "Paid but Delivery Cancelled" and 4 showing "Payment Cancelled but Delivered". These records were kept as they were and flagged for investigation rather than being changed without evidence.&lt;/li&gt;
&lt;li&gt;Checked the two blank Vehicle Type records against their Car Model. Each model had a consistent Vehicle Type in the other records, allowing the missing values to be inferred.&lt;/li&gt;
&lt;li&gt;Checked Branch and City relationships before creating the location hierarchy. Each branch consistently belonged to one city.&lt;/li&gt;
&lt;li&gt;Checked Sales Rep and Branch relationships. Sales representatives worked across multiple branches, which supported creating Sales Rep as a separate dimension.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Data Model
&lt;/h2&gt;

&lt;p&gt;I transformed the raw flat file  into a star schema consisting of one fact table and eight dimension tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grain of FactSales:&lt;/strong&gt; one row represents one vehicle sold in one transaction. This is the same grain as the original dataset.&lt;/p&gt;

&lt;h3&gt;
  
  
  FactSales (Fact Table)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OrderID&lt;/td&gt;
&lt;td&gt;Degenerate dimension&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OrderDate, DeliveryDate&lt;/td&gt;
&lt;td&gt;Relate directly to DimDate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CustomerID&lt;/td&gt;
&lt;td&gt;FK → DimCustomer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VehicleID&lt;/td&gt;
&lt;td&gt;FK → DimVehicle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LocationID&lt;/td&gt;
&lt;td&gt;FK → DimLocation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SalesRepID&lt;/td&gt;
&lt;td&gt;FK → DimSalesRep&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PaymentID&lt;/td&gt;
&lt;td&gt;FK → DimPayment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LeadSourceID&lt;/td&gt;
&lt;td&gt;FK → DimLeadSource&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeliveryStatusID&lt;/td&gt;
&lt;td&gt;FK → DimDeliveryStatus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Returned&lt;/td&gt;
&lt;td&gt;Degenerate attribute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UnitsSold, UnitSellingPrice, UnitCost, DiscountPct, DeliveryFee, LogisticsCost, RevenueRecorded, CustomerRating, ReviewCount&lt;/td&gt;
&lt;td&gt;Measures&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Dimension Tables
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Table&lt;/th&gt;
&lt;th&gt;Primary Key&lt;/th&gt;
&lt;th&gt;Columns&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DimDate&lt;/td&gt;
&lt;td&gt;Date (direct relate, no primary key needed)&lt;/td&gt;
&lt;td&gt;Date, Year, Quarter, Month, MonthYear, Day, DayName, WeekdayNumber, IsWeekend&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DimCustomer&lt;/td&gt;
&lt;td&gt;CustomerID&lt;/td&gt;
&lt;td&gt;CustomerName, CustomerType, CustomerAge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DimVehicle&lt;/td&gt;
&lt;td&gt;VehicleID&lt;/td&gt;
&lt;td&gt;CarMake, CarModel, VehicleType, VehicleYear, FuelType, Transmission, Color&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DimLocation&lt;/td&gt;
&lt;td&gt;LocationID&lt;/td&gt;
&lt;td&gt;Region, County, City, Branch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DimSalesRep&lt;/td&gt;
&lt;td&gt;SalesRepID&lt;/td&gt;
&lt;td&gt;SalesRep&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DimPayment&lt;/td&gt;
&lt;td&gt;PaymentID&lt;/td&gt;
&lt;td&gt;PaymentMethod, PaymentStatus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DimLeadSource&lt;/td&gt;
&lt;td&gt;LeadSourceID&lt;/td&gt;
&lt;td&gt;LeadSource&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DimDeliveryStatus&lt;/td&gt;
&lt;td&gt;DeliveryStatusID&lt;/td&gt;
&lt;td&gt;DeliveryStatus&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Relationships:&lt;/strong&gt; all dimensions relate to FactSales as One-to-Many with single-direction cross-filtering. FactSales relates to DimDate twice, using OrderDate as the active relationship and DeliveryDate as the inactive relationship. The DeliveryDate relationship is activated in specific measures using &lt;code&gt;USERELATIONSHIP&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faekjk5w4qkt7p6m4d7ry.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faekjk5w4qkt7p6m4d7ry.png" alt="PowerBI Model View" width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Model View showing FactSales at the centre and the eight dimension tables connected through One-to-Many relationships, including the two DimDate relationships for Order Date and Delivery Date.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modelling limitation:&lt;/strong&gt; the dataset does not contain a unique customer identifier. &lt;code&gt;DimCustomer&lt;/code&gt; was therefore built from distinct combinations of CustomerName, CustomerType and CustomerAge.I did this by selecting all the columns in it and removing duplicates.&lt;/p&gt;

&lt;p&gt;This means that "customer" in this analysis refers to the customer as recorded per transaction rather than a verified master customer record. Two different people with the same name and similar profile  be treated as one customer.&lt;/p&gt;

&lt;p&gt;I  considered this limitation when presenting customer-level findings.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Key DAX Measures
&lt;/h2&gt;

&lt;p&gt;The following are some of the main DAX measures I used in the analysis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total Revenue = SUM(FactSales[RevenueRecorded])

Total Gross Profit =
SUMX(FactSales, FactSales[RevenueRecorded] - (FactSales[UnitsSold] * FactSales[UnitCost]))

Gross Profit Margin % = DIVIDE([Total Gross Profit], [Total Revenue])

Return Rate % =
DIVIDE(
    CALCULATE(COUNTROWS(FactSales), FactSales[Returned] = "Yes"),
    COUNTROWS(FactSales)
)

Logistics Cost % of Revenue = DIVIDE([Total Logistics Cost], [Total Revenue])

Customer Total Revenue =
CALCULATE([Total Revenue], ALLEXCEPT(FactSales, DimCustomer[CustomerName]))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxeca1zhc03ecwx7zhjab.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxeca1zhc03ecwx7zhjab.png" alt="Dax Measures" width="166" height="191"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Examples of the measures I created for the analysis by use of DAX.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Business definition:&lt;/strong&gt; Gross Profit was defined as Revenue minus (Units Sold times Unit Cost).I excluded Delivery Fee and Logistics Cost because I treated them as operating expenses rather than the direct cost of the vehicle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limitation:&lt;/strong&gt; some Unit Cost and Unit Selling Price values could not be recovered during cleaning and were changed to &lt;code&gt;null&lt;/code&gt;. Therefore, Gross Profit and Gross Profit Margin may be affected for transactions where cost or price information was missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Dashboard and Report Structure
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Page&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Executive Dashboard&lt;/td&gt;
&lt;td&gt;High-level summary showing KPIs, revenue trend, branch and make comparison, payment mix and a logistics cost attention indicator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Vehicle/Branch Performance&lt;/td&gt;
&lt;td&gt;Analysis of Make, Model, Vehicle Type and Year performance, branch revenue share and regional drill-down&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. SalesRep/LeadSource Performance&lt;/td&gt;
&lt;td&gt;Sales representative performance and lead source value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Revenue/Logistics&lt;/td&gt;
&lt;td&gt;Payment status and method, delivery performance and logistics cost efficiency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. TopCustomers&lt;/td&gt;
&lt;td&gt;Top 10 customers by revenue and the relationship between revenue and ratings&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Interactivity was also added to the report. Region and Car Make slicers are synced across pages. A Region → County → City → Branch drill-down was created within the matrix visual and visuals on each page can interact through cross-filtering.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqbzw4fmz9ytpi0hd9wcj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqbzw4fmz9ytpi0hd9wcj.png" alt=" " width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Executive Dashboard showing KPI cards, revenue trend, branch and make comparison, payment status mix and logistics cost attention indicator.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv111ee0zequcmke4315w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv111ee0zequcmke4315w.png" alt="Central" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;How the dashboard looks like when the slicer only applies to Central region.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Investigation: Records Requiring Management Attention
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;14 Payment/Delivery Status inconsistencies:&lt;/strong&gt; 10 records show Payment Status = Paid while Delivery Status = Cancelled. Another 4 show Payment Status = Cancelled while Delivery Status = Delivered. These records were not corrected because the data does not show which field is inaccurate. They were instead flagged for manual review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Truck vehicle type shows a -66% gross profit margin:&lt;/strong&gt; this is the vehicle type with the most negative margin in the dataset. It should be investigated to determine whether the result comes from a genuine pricing or cost issue or from remaining data quality problems.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp3rhy1gswlryox86swjh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp3rhy1gswlryox86swjh.png" alt="Gross Profit Margin" width="305" height="151"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;BMW (-37%) and Isuzu (-31%) show negative margins:&lt;/strong&gt; both makes show significant negative margins even though their selling prices are comparable with other makes. The Unit Cost data for these makes should therefore be reviewed to check whether currency conversion or missing-value handling affected the results.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsfgrbjneigz30sndhbm3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsfgrbjneigz30sndhbm3.png" alt="Car Make GPM" width="179" height="175"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Nairobi has the highest average delivery time:&lt;/strong&gt; Nairobi has an average delivery time of 26.29 days compared with the company-wide average of 15.18 days. Since Nairobi hosts the company's headquarters, this difference is worth investigating to understand what may be causing the longer delivery times.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr4wotgto19atzyz78t9f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr4wotgto19atzyz78t9f.png" alt="Delivery Days" width="140" height="123"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Management Insights
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Revenue is concentrated in a small number of vehicle makes and regions.&lt;/strong&gt; 
Toyota accounts for the largest share of revenue among the vehicle makes in the dataset. Rift Valley (KSh 381.9M) and Central (KSh 244.6M) also contribute a large share of the total KSh 1.48bn revenue. This means overall performance is strongly connected to demand for Toyota vehicles and performance in these regions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fofoxu1c913s8fmrt28qn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fofoxu1c913s8fmrt28qn.png" alt="Revenue by Region" width="221" height="137"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl4097jpll6q9kvzbhvv1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl4097jpll6q9kvzbhvv1.png" alt="Revenue by Carmake" width="261" height="163"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Customer revenue is concentrated but not extreme.&lt;/strong&gt; &lt;br&gt;
The top 10 customers out of 276 account for approximately KSh 299.3M, which is around 20% of total revenue. This shows that a meaningful share of revenue comes from a relatively small group of high-value customers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Some vehicle categories are being sold at negative margins.&lt;/strong&gt; &lt;br&gt;
SUVs generated the highest revenue among vehicle types at approximately KSh 845M and had a positive margin of 8%. However, Sedans had a -9% margin, Crossovers -6%, Vans -28% and Trucks -66%. Since these are categories with meaningful sales volumes, the results may point to wider pricing or cost issues rather than a few individual transactions. However, these findings should be considered alongside the data quality issues identified during cleaning.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Customer ratings do not show a clear relationship with transaction value.&lt;/strong&gt;&lt;br&gt;
The scatter plot comparing Customer Rating and Revenue Recorded shows that ratings generally cluster around 3 to 4 across different transaction values. There is no clear upward or downward pattern in the data. Based on this dataset, higher transaction values do not appear to be directly associated with higher customer ratings.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4x7envi1172ygnf5oqhe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4x7envi1172ygnf5oqhe.png" alt="ScatterPlot" width="219" height="149"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Logistics efficiency varies across branches.&lt;/strong&gt; Nairobi HQ has the highest Logistics Cost % of Revenue among the branches. Nairobi also has the longest average delivery time at 26.29 days compared with the overall average of 15.18 days. These two findings together suggest that Nairobi's logistics performance deserves further investigation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Note: Return Rate (31.88%) and Gross Profit Margin (2.53%) were calculated from the cleaned dataset. Both figures should be treated carefully because missing or null values in Unit Cost, Unit Selling Price and Returned may affect the results. Additional source data could therefore change these figures.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Management Recommendations
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Review pricing and costs for Trucks, Vans, Sedans and Crossovers.&lt;/strong&gt; These vehicle categories show negative gross margins at meaningful sales volumes. Management should review their selling prices and acquisition costs to determine whether current pricing is covering the cost of the vehicles.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Review the 10 Paid but Cancelled transactions.&lt;/strong&gt; The 10 records showing Payment Status = Paid and Delivery Status = Cancelled should be reviewed to determine whether customers are owed refunds or whether the delivery status is incorrect.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Investigate Nairobi's delivery times and logistics costs.&lt;/strong&gt; Nairobi has both a relatively high logistics cost share and the longest average delivery time. Management should investigate the reasons behind this before treating it as normal regional variation. Possible areas to review include traffic, yard-to-customer handoffs, delivery scheduling and available resources.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  12. Analyst-Defined Business Questions
&lt;/h2&gt;

&lt;p&gt;In addition to the questions provided in the assessment brief, five additional questions were identified as useful for JCars Logistics management. Three were answered directly by the Power BI analysis.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does higher customer satisfaction translate into higher-value sales, or are they unrelated? &lt;em&gt;(Answered: Section 10, Insight 4)&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frjw9zpae3f8jd09ouink.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frjw9zpae3f8jd09ouink.png" alt=" " width="219" height="149"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which lead source delivers the best return per lead, and is marketing investment allocated accordingly? &lt;em&gt;(Answered via the Avg Revenue per Lead by Lead Source visual)&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4msv8tj69plgw4aly5ds.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4msv8tj69plgw4aly5ds.png" alt=" " width="309" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Are certain branches carrying disproportionate logistics costs relative to the revenue they generate? &lt;em&gt;(Answered: Section 10, Insight 5)&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk51hljnivgu5grlv93u7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk51hljnivgu5grlv93u7.png" alt=" " width="227" height="149"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Does vehicle age affect profitability, suggesting that older stock should be discounted or phased out?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How concentrated is revenue among the top customers, and does this represent a meaningful business risk? &lt;em&gt;(Answered: Section 10, Insight 2)&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  13. Assumptions and Business Rules Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Monetary values without a stated currency were treated as Kenya Shillings based on the assessment instructions.&lt;/li&gt;
&lt;li&gt;Exchange rates used were USD 129.54, EUR 147.84 and ZAR 7.93 to KES.&lt;/li&gt;
&lt;li&gt;Gross Profit = Revenue minus (Units times Unit Cost). Delivery Fee and Logistics Cost were treated as separate operating expenses.&lt;/li&gt;
&lt;li&gt;"County Government" was standardized under "Government" because the dataset did not provide enough information to distinguish between different levels of government.&lt;/li&gt;
&lt;li&gt;"Retail" was kept as its own Customer Type category rather than being combined with "Individual."&lt;/li&gt;
&lt;li&gt;Branch and Yard designations for the same city were kept as separate locations.&lt;/li&gt;
&lt;li&gt;Ambiguous dates were interpreted using the day-first UK/Kenya convention.&lt;/li&gt;
&lt;li&gt;Ratings outside 0 to 5 and discounts below 0% or above 50% were treated as unreliable and changed to null rather than being manually corrected.&lt;/li&gt;
&lt;li&gt;DimCustomer was created using Name + Type + Age because the source data did not contain a unique customer identifier. Customer-level findings should therefore be interpreted with this limitation in mind.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  14. Challenges Encountered and Learning
&lt;/h2&gt;

&lt;p&gt;The biggest challenge in this project was the number and variety of data quality issues spread across the dataset.&lt;/p&gt;

&lt;p&gt;Many cleaning decisions could not be made by simply applying the same rule to every column. Instead, related fields had to be compared to understand what the data was actually showing.&lt;/p&gt;

&lt;p&gt;For example, Unit Cost and Unit Selling Price were compared when resolving unclear currencies. Car Model was compared with Vehicle Type when filling missing Vehicle Type values. Payment Status was also compared with Delivery Status to identify records that needed further investigation.&lt;/p&gt;

&lt;p&gt;For me this project solidified the importance of making data cleaning decisions based on evidence wherever possible. When the available information was not enough to confidently correct a value, the value was left as null or flagged for investigation instead of making an unsupported assumption.&lt;/p&gt;

&lt;p&gt;The process also showed that data cleaning is not just about making a dataset look neat. Every cleaning decision can affect the final analysis and the business conclusions drawn from it. Clearly documenting these decisions is therefore an important part of producing a reliable Power BI solution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/SMumbi5144/Jcars_Logistics_BI.git" rel="noopener noreferrer"&gt;Github Repository Link&lt;/a&gt;&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>data</category>
      <category>management</category>
    </item>
    <item>
      <title>Power BI: Data Modelling, Relationships and Joins</title>
      <dc:creator>Stacy Mumbi</dc:creator>
      <pubDate>Thu, 24 Sep 2026 19:15:04 +0000</pubDate>
      <link>https://dev.to/smumbi_/power-bi-data-modelling-relationships-and-joins-2gg2</link>
      <guid>https://dev.to/smumbi_/power-bi-data-modelling-relationships-and-joins-2gg2</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When I first started working with Power BI, I thought my main job was simply to import a dataset, clean it, create a few charts and build a  dashboard. I did know there was more to all that until I discovered the term &lt;strong&gt;data modelling&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;There were new terms such as tables, relationships, cardinality, joins, keys and something called &lt;strong&gt;Model View&lt;/strong&gt;.At first, it felt confusing. But once I understood how tables connect, Power BI started making much more sense.&lt;/p&gt;

&lt;p&gt;In this article, I will explain data modelling, relationships and joins in Power BI using a simple e-commerce example.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. What is Data Modelling?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Data modelling&lt;/strong&gt; is basically the process of organising your tables and defining how they connect to each other.Instead of putting every piece of information into one big table, we can separate the information into dimension tables.&lt;/p&gt;

&lt;p&gt;For example, imagine an online shop.&lt;/p&gt;

&lt;p&gt;We can have tables like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customers&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;CustomerName&lt;/th&gt;
&lt;th&gt;County&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;Stacy&lt;/td&gt;
&lt;td&gt;Nairobi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C002&lt;/td&gt;
&lt;td&gt;Brian&lt;/td&gt;
&lt;td&gt;Kiambu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C003&lt;/td&gt;
&lt;td&gt;Aisha&lt;/td&gt;
&lt;td&gt;Mombasa&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Products&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ProductID&lt;/th&gt;
&lt;th&gt;ProductName&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;P001&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;Electronics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P002&lt;/td&gt;
&lt;td&gt;Headphones&lt;/td&gt;
&lt;td&gt;Electronics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P003&lt;/td&gt;
&lt;td&gt;Sneakers&lt;/td&gt;
&lt;td&gt;Fashion&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Sales&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SaleID&lt;/th&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;ProductID&lt;/th&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;th&gt;Sales&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S001&lt;/td&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;P001&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;65000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S002&lt;/td&gt;
&lt;td&gt;C002&lt;/td&gt;
&lt;td&gt;P002&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;5000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S003&lt;/td&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;P003&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S004&lt;/td&gt;
&lt;td&gt;C003&lt;/td&gt;
&lt;td&gt;P001&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;65000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We can see that the Sales table doesn't contain the customer's name, county, product name or category.Instead, it contains &lt;code&gt;CustomerID&lt;/code&gt; and &lt;code&gt;ProductID&lt;/code&gt;.These IDs allow Power BI to connect the tables.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What is a Key?
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;key&lt;/strong&gt; is a column that helps us identify and connect records.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CustomerID&lt;/code&gt; identifies a customer.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ProductID&lt;/code&gt; identifies a product.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the Customers table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;CustomerName&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;Stacy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C002&lt;/td&gt;
&lt;td&gt;Brian&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C003&lt;/td&gt;
&lt;td&gt;Aisha&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each &lt;code&gt;CustomerID&lt;/code&gt; appears  once.But in the Sales table, the same &lt;code&gt;CustomerID&lt;/code&gt; can appear many times:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SaleID&lt;/th&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;Sales&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S001&lt;/td&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;65000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S003&lt;/td&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;4500&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This gives us a &lt;strong&gt;one-to-many relationship&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Understanding Relationships
&lt;/h2&gt;

&lt;p&gt;A relationship tells Power BI:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"These two tables are connected through this column."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Customers[CustomerID] -&amp;gt; Sales[CustomerID]&lt;/code&gt;&lt;br&gt;
This tells Power BI which customer made each sale.&lt;/p&gt;

&lt;p&gt;Relationships also  allow filters to propagate from one table to another. This is what makes it possible to use fields from different tables together in a report.&lt;/p&gt;

&lt;p&gt;The model would look something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        Customers
       CustomerID
            |
            | 1
            |
            | *
          Sales
       CustomerID
       ProductID
            |
            | *
            |
            | 1
         Products
        ProductID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a very common structure in Power BI.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;1&lt;/strong&gt; represents the side where the value is unique.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;*&lt;/strong&gt; represents the side where the value can appear multiple times.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. One-to-Many Relationships
&lt;/h2&gt;

&lt;p&gt;The most common relationship we all encounter is the &lt;strong&gt;One-to-Many (1:*)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
One customer can make many purchases.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customers Sales

C001 ──────────────── S001
  │
  └────────────────── S003

C002 ──────────────── S002
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Customer C001 appears once in the Customers table but appears multiple times in the Sales table.&lt;/p&gt;

&lt;p&gt;Therefore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customers = One&lt;/li&gt;
&lt;li&gt;Sales = Many&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Power BI calls this the relationship &lt;strong&gt;cardinality&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Why Do Relationships Matter?
&lt;/h2&gt;

&lt;p&gt;If we create a dashboard containing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total Sales&lt;/li&gt;
&lt;li&gt;Sales by County&lt;/li&gt;
&lt;li&gt;Sales by Product Category&lt;/li&gt;
&lt;li&gt;Sales by Customer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Sales table contains the actual transaction amounts.But the county is stored in the Customers table.The category is stored in the Products table.&lt;br&gt;
The relationships tell Power BI how the tables are connected so that it will be able to know which category or county belongs to each sale.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customers
   ↓
CustomerID
   ↓
Sales
   ↑
ProductID
   ↑
Products
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because these relationships exist, we can use:&lt;code&gt;Customers[County]&lt;/code&gt;together with &lt;code&gt;Sales[Sales]&lt;/code&gt; enabling Power BI to calculate sales by county.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Creating a Relationship in Power BI
&lt;/h2&gt;

&lt;p&gt;In Power BI Desktop, go to &lt;strong&gt;Model View&lt;/strong&gt;.You will see your tables displayed visually.You can then connect matching columns, such as:&lt;br&gt;
&lt;code&gt;Customers[CustomerID]&lt;/code&gt; to &lt;code&gt;Sales[CustomerID]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Power BI can sometimes detect relationships automatically when tables are loaded, but you can also create or edit them manually through &lt;strong&gt;Manage relationships&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When creating a relationship, you may meet terms such as:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cardinality&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One-to-many&lt;/li&gt;
&lt;li&gt;One-to-one&lt;/li&gt;
&lt;li&gt;Many-to-many&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cross-filter direction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This determines how filters move between related tables.For a beginner-friendly model, keeping relationships simple and using a clear model structure is usually easier to understand and maintain.&lt;/p&gt;
&lt;h2&gt;
  
  
  7. What Are Joins?
&lt;/h2&gt;

&lt;p&gt;This is another important concept:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Joins.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A join combines information from two tables based on a matching column.&lt;br&gt;
For example, if we have:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customers&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;CustomerName&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;Stacy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C002&lt;/td&gt;
&lt;td&gt;Brian&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C003&lt;/td&gt;
&lt;td&gt;Aisha&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Sales&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SaleID&lt;/th&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;Sales&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S001&lt;/td&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;65000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S002&lt;/td&gt;
&lt;td&gt;C002&lt;/td&gt;
&lt;td&gt;5000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S003&lt;/td&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;4500&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We can join the tables using &lt;code&gt;CustomerID&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The result could look like:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SaleID&lt;/th&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;CustomerName&lt;/th&gt;
&lt;th&gt;Sales&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S001&lt;/td&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;Stacy&lt;/td&gt;
&lt;td&gt;65000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S002&lt;/td&gt;
&lt;td&gt;C002&lt;/td&gt;
&lt;td&gt;Brian&lt;/td&gt;
&lt;td&gt;5000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S003&lt;/td&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;Stacy&lt;/td&gt;
&lt;td&gt;4500&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now the customer's name has been brought into the Sales table.&lt;/p&gt;
&lt;h2&gt;
  
  
  8.  What's the Difference between Joins and Relationships?
&lt;/h2&gt;

&lt;p&gt;This was one of the things that confused me when I started.Although joins and relationships connect tables, they are not exactly the same thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Join&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A join combines tables during the data preparation stage.In Power BI, this is commonly done in Power Query using &lt;strong&gt;Merge Queries&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Relationship&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A relationship connects tables in the data model.The tables remain separate, but Power BI understands how they are related.&lt;/p&gt;

&lt;p&gt;So to remember simply:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Join&lt;/strong&gt; = combine the data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relationship&lt;/strong&gt; = connect the tables.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  9. Common Join Types
&lt;/h2&gt;

&lt;p&gt;When using Merge Queries in Power Query, you'll see several types of joins .&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inner Join&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This returns only rows where there is a match in both tables.&lt;br&gt;
For example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Table A&lt;/th&gt;
&lt;th&gt;Table B&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C002&lt;/td&gt;
&lt;td&gt;C002&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C003&lt;/td&gt;
&lt;td&gt;C004&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An inner join would return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C001
C002
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The unmatched values are excluded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Left Outer Join&lt;/strong&gt;&lt;br&gt;
This keeps all rows from the left table and brings in matching information from the right table.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Customers&lt;/th&gt;
&lt;th&gt;Customer Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C002&lt;/td&gt;
&lt;td&gt;C002&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C003&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A left join keeps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C001
C002
C003
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though C003 has no match in the second table.This is one of the most commonly useful joins when you want to preserve your main dataset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Right Outer Join&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the opposite of a left join.It keeps all rows from the right table and matching rows from the left table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full Outer Join&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A full outer join keeps all rows from both tables.It is useful when you want to identify both matching and unmatched records.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Where Do Joins Happen in Power BI?
&lt;/h2&gt;

&lt;p&gt;Joins are created in Power Query.&lt;/p&gt;

&lt;p&gt;Power Query can be transformed through:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Home -&amp;gt; Transform data&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then, in Power Query:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Home -&amp;gt; Merge Queries&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You select:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first table&lt;/li&gt;
&lt;li&gt;The second table&lt;/li&gt;
&lt;li&gt;The matching column(s)&lt;/li&gt;
&lt;li&gt;The type of join&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the merge, you can expand the new column to bring the required fields into your table.&lt;/p&gt;

&lt;p&gt;Power Query supports several join kinds, including inner, left outer, right outer, full outer, left anti, right anti, left semi and right semi joins.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. So Should Everything be Joined?
&lt;/h2&gt;

&lt;p&gt;No. &lt;br&gt;
This is where data modelling becomes important.You don't necessarily need to combine every table into one giant table.A well-designed Power BI model often keeps tables separate and connects them using relationships.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                Customers
                    |
                    |
                    ↓
Products -&amp;gt; Sales &amp;lt;- Date
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, Sales is the central table containing transactions, while Customers, Products and Date provide additional information for analysing those transactions.&lt;/p&gt;

&lt;p&gt;This type of structure is closely related to a &lt;strong&gt;star schema&lt;/strong&gt;, which is recommended as a useful modelling approach for Power BI.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. A Simple Example in a Dashboard
&lt;/h2&gt;

&lt;p&gt;Let's say our dashboard has a slicer for:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;County&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We select:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nairobi&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Customers&lt;/code&gt; table contains the county information.&lt;/p&gt;

&lt;p&gt;Because Customers is related to Sales through &lt;code&gt;CustomerID&lt;/code&gt;, the filter can flow to the Sales table.&lt;/p&gt;

&lt;p&gt;Power BI can then update our visual to show something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Total Sales in Nairobi&lt;/strong&gt;&lt;br&gt;
KSh 69,500&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We did not have to  filter every sale ourselves.The relationship did the work behind the scenes.That's the beauty of data modelling. &lt;/p&gt;

&lt;h2&gt;
  
  
  13. A Few Things to Check Before Creating Relationships
&lt;/h2&gt;

&lt;p&gt;Before connecting two tables, check the columns you're using.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Make sure the data types match&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CustomerID&lt;/code&gt; should not be Text in one table and Whole Number in another.&lt;br&gt;
It is recommended that the columns used in a relationship have compatible data types.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Check for duplicates&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The "one" side of a one-to-many relationship needs unique values.&lt;br&gt;
For example, &lt;code&gt;CustomerID&lt;/code&gt; should appear once in the Customers table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Check for missing values&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If Sales contains a &lt;code&gt;CustomerID&lt;/code&gt; that doesn't exist in Customers, you may have an unmatched record.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Choose the correct relationship&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Don't automatically choose many-to-many just because Power BI allows it.&lt;br&gt;
&lt;em&gt;Many-to-many relationships&lt;/em&gt; can be useful, but they require more careful modelling and are sometimes handled using a bridging table.&lt;/p&gt;
&lt;h2&gt;
  
  
  14. My Simple Mental Model
&lt;/h2&gt;

&lt;p&gt;After learning all of this, this is how I think about it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Power Query&lt;/strong&gt;&lt;br&gt;
This is where I clean, merge, change data types, remove errors and prepare tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model View&lt;/strong&gt;&lt;br&gt;
"Let me tell Power BI how my tables are connected."This is where relationships come in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Report View&lt;/strong&gt;&lt;br&gt;
"Now let me analyse and visualise the data."&lt;br&gt;
The three work together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;       POWER QUERY
            ↓
   Clean &amp;amp; Transform
            ↓
      DATA MODEL
            ↓
   Connect Tables
            ↓
       REPORT
            ↓
   Analyse &amp;amp; Visualise

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  15. Final Takeaway
&lt;/h2&gt;

&lt;p&gt;Data modelling can sound complicated at first, but the basic idea is  simple.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Relationships connect tables inside your Power BI model.&lt;/li&gt;
&lt;li&gt;Joins combine data based on matching values, usually during data preparation in Power Query.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Understanding the difference is important because it helps you decide whether you should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Merge two tables&lt;/li&gt;
&lt;li&gt;Keep them separate&lt;/li&gt;
&lt;li&gt;Create a relationship&lt;/li&gt;
&lt;li&gt;Use a one-to-many relationship&lt;/li&gt;
&lt;li&gt;Use another relationship type&lt;/li&gt;
&lt;li&gt;Or rethink the structure of your model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Honestly, once relationships start making sense, Power BI becomes much less intimidating. You stop seeing a bunch of random tables and start seeing a connected data model.&lt;/p&gt;

&lt;p&gt;That is when Power BI starts to feel like a real analytical tool rather than just a dashboard-making app.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>data</category>
      <category>database</category>
    </item>
    <item>
      <title>Building a Power BI Data Model for ShopKe: Understanding Relationships, Schemas and Joins</title>
      <dc:creator>Stacy Mumbi</dc:creator>
      <pubDate>Mon, 14 Sep 2026 04:11:06 +0000</pubDate>
      <link>https://dev.to/smumbi_/building-a-power-bi-data-model-for-shopke-understanding-relationships-schemas-and-joins-4g3c</link>
      <guid>https://dev.to/smumbi_/building-a-power-bi-data-model-for-shopke-understanding-relationships-schemas-and-joins-4g3c</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Say your working as a data analyst at &lt;strong&gt;ShopKe&lt;/strong&gt;, a fictional Kenyan e-commerce company selling electronics, fashion and home products. The management team wants to understand sales performance, customer purchasing behavior and product profitability.&lt;/p&gt;

&lt;p&gt;The company has three datasets namely Customers, Products and Orders. Even though these datasets contain valuable information, they need to be properly organized before creating a Power BI dashboard.&lt;/p&gt;

&lt;p&gt;This is where data modelling becomes important. A well designed data model allows Power BI to connect related tables, perform accurate calculations and present meaningful business insights.&lt;/p&gt;

&lt;p&gt;In this article, I will explore data modelling approaches, fact and dimension tables, relationships, filter directions and Power Query joins using &lt;strong&gt;ShopKe&lt;/strong&gt; as a practical example for better understanding.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Data Modelling in Power BI
&lt;/h2&gt;

&lt;p&gt;Data modelling is the process of organizing data into tables and defining relationships between them so that the data can be analyzed properly.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;good data model&lt;/strong&gt; is important because it supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accurate reporting and DAX calculations.&lt;/li&gt;
&lt;li&gt;Better query performance.&lt;/li&gt;
&lt;li&gt;Scalability as data grows.&lt;/li&gt;
&lt;li&gt;Easier report development.&lt;/li&gt;
&lt;li&gt;Improved readability and maintainability.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, ShopKe may store customer information in one table and sales transactions in another. By connecting them using &lt;strong&gt;CustomerID&lt;/strong&gt;, Power BI can analyze sales by customer without repeating customer details in every transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  1.1 Flat Table
&lt;/h2&gt;

&lt;p&gt;A flat table stores all information in one table.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;OrderID&lt;/th&gt;
&lt;th&gt;CustomerName&lt;/th&gt;
&lt;th&gt;ProductName&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;th&gt;SalesAmount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;O001&lt;/td&gt;
&lt;td&gt;Stacy&lt;/td&gt;
&lt;td&gt;Laptop&lt;/td&gt;
&lt;td&gt;Electronics&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;75,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;O002&lt;/td&gt;
&lt;td&gt;Brian&lt;/td&gt;
&lt;td&gt;Mouse&lt;/td&gt;
&lt;td&gt;Electronics&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;O003&lt;/td&gt;
&lt;td&gt;Stacy&lt;/td&gt;
&lt;td&gt;Shirt&lt;/td&gt;
&lt;td&gt;Fashion&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2,500&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Structure&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────┐
│          SalesData           │
├──────────────────────────────┤
│ OrderID                      │
│ CustomerName                 │
│ ProductName                  │
│ Category                     │
│ Quantity                     │
│ SalesAmount                  │
└──────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;: Simple to understand, easy to import and good for small datasets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages&lt;/strong&gt;: Repeated information increases redundancy and the table may become difficult to maintain as the business grows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use&lt;/strong&gt;: Small datasets, quick analysis and simple reporting requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance and complexity&lt;/strong&gt;: It has few relationship complications, but unnecessary columns and repeated values can increase the amount of data stored.&lt;/p&gt;

&lt;h2&gt;
  
  
  1.2 Star Schema
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;star schema&lt;/strong&gt; contains a central fact table connected directly to multiple dimension tables.&lt;/p&gt;

&lt;p&gt;For ShopKe, FactSales stores transactions, while DimCustomer, DimProduct and DimDate provide descriptive information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbp859fbatv3p9ofpzwh1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbp859fbatv3p9ofpzwh1.png" alt="ShopKe data model" width="800" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ShopKe data model presented as a star schema&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;: Simple to understand, supports readable DAX, reduces unnecessary duplication and  well suited for analytical reporting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages&lt;/strong&gt;: Requires careful relationship design and may involve some repeated descriptive information within dimensions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use&lt;/strong&gt;: Sales, finance, inventory, customer and other business intelligence projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance and complexity&lt;/strong&gt;: A well-designed star schema can support efficient analytical queries and compression while keeping the model relatively easy to navigate.&lt;/p&gt;

&lt;h2&gt;
  
  
  1.3 Snowflake Schema
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;snowflake schema&lt;/strong&gt; is a type of  star schema where dimension tables are further divided into related tables.&lt;/p&gt;

&lt;p&gt;For example, ShopKe may separate product information into Product, Subcategory and Category tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structure&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    ┌──────────────┐
                    │  DimCategory │
                    └──────┬───────┘
                           │ 1
                           │ *
                    ┌──────▼───────┐
                    │DimSubcategory│
                    └──────┬───────┘
                           │ 1
                           │ *
┌─────────────┐     ┌──────▼───────┐
│  FactSales  │ *──1│  DimProduct  │
└─────────────┘     └──────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwfx86u5i2qyglzyr20l6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwfx86u5i2qyglzyr20l6.png" alt="Snowflake Schema" width="799" height="559"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;An example of how a snowflake schema model would look like&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;: Reduces repeated descriptive information and can support difficult hierarchies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages&lt;/strong&gt;: More tables and relationships increase model complexity and may make report development less intuitive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use&lt;/strong&gt;: Complex organizational or geographical hierarchies, normalized data sources and situations where shared reference tables are useful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance and complexity&lt;/strong&gt;: It can reduce redundancy, but more relationships may make the model more complicated. In many cases, a star schema is preferred for simplicity.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Fact Tables and Dimension Tables
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fact tables
&lt;/h3&gt;

&lt;p&gt;A fact table stores measurable business events. In ShopKe, FactSales contains individual sales transactions.&lt;/p&gt;

&lt;p&gt;Example fields include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SalesID&lt;/li&gt;
&lt;li&gt;CustomerID&lt;/li&gt;
&lt;li&gt;ProductID&lt;/li&gt;
&lt;li&gt;DateKey&lt;/li&gt;
&lt;li&gt;Quantity&lt;/li&gt;
&lt;li&gt;SalesAmount&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc0kh5fzjgyrtgavteivs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc0kh5fzjgyrtgavteivs.png" alt="FactSales" width="798" height="163"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The FactSales table&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Dimension tables
&lt;/h2&gt;

&lt;p&gt;Dimension tables store descriptive attributes used to analyze facts.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DimCustomer&lt;/strong&gt; containing customer names and regions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DimProduct&lt;/strong&gt; containing product names and categories.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DimDate&lt;/strong&gt; containing dates, months and years.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Grain of a fact table
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;grain&lt;/strong&gt; describes what one row represents.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One row in &lt;strong&gt;FactSales&lt;/strong&gt; represents one product line in one customer order.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is important because an order may contain several product lines. Therefore, counting rows may not equal counting unique orders.&lt;/p&gt;

&lt;h2&gt;
  
  
  ShopKe's star schema
&lt;/h2&gt;

&lt;p&gt;FactSales connects to the dimensions through keys:&lt;/p&gt;

&lt;p&gt;DimCustomer&lt;code&gt;[CustomerID]&lt;/code&gt; 1 ─── * FactSales&lt;code&gt;[CustomerID]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;DimProduct&lt;code&gt;[ProductID]&lt;/code&gt;   1 ─── * FactSales&lt;code&gt;[ProductID]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;DimDate&lt;code&gt;[DateKey]&lt;/code&gt;        1 ─── * FactSales&lt;code&gt;[DateKey]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;DimLocation&lt;code&gt;[LocationID]&lt;/code&gt; 1 ─── * FactSales&lt;code&gt;[LocationID]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This allows ShopKe to analyze total sales by product, customer or date.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Relationships in Power BI
&lt;/h2&gt;

&lt;p&gt;A &lt;em&gt;relationship&lt;/em&gt; connects tables by use of matching columns. Relationships are necessary because business data is often distributed across several tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Primary keys and foreign keys
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;primary&lt;/strong&gt; key uniquely identifies a row in a table. A &lt;strong&gt;foreign key&lt;/strong&gt; references a key in another table.&lt;/p&gt;

&lt;p&gt;For example, &lt;strong&gt;CustomerID&lt;/strong&gt; is unique in DimCustomer but may repeat in FactSales because one customer can make multiple purchases.&lt;/p&gt;

&lt;p&gt;This creates a &lt;strong&gt;one to many relationship&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Relationship cardinalities
&lt;/h2&gt;

&lt;h3&gt;
  
  
  One to Many (1:*)
&lt;/h3&gt;

&lt;p&gt;One row in a dimension relates to many rows in a fact table.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;DimCustomer&lt;code&gt;[CustomerID]&lt;/code&gt; 1 ─── * FactSales&lt;code&gt;[CustomerID]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is the most common relationship in a star schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  One to One (1:1)
&lt;/h3&gt;

&lt;p&gt;Each row in one table matches at most one row in another table.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;Employee&lt;code&gt;[EmployeeID]&lt;/code&gt; 1 ─── 1 EmployeeDetails&lt;code&gt;[EmployeeID]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It is appropriate when two tables contain separate information about the same entity and both sides have unique keys.&lt;/p&gt;

&lt;h3&gt;
  
  
  Many to Many (:)
&lt;/h3&gt;

&lt;p&gt;Multiple rows in one table can relate to multiple rows in another table.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;Students * ─── * Courses&lt;/p&gt;

&lt;p&gt;One student can take many courses and one course can have many students.&lt;/p&gt;

&lt;p&gt;A bridge table can be used for a clearer design as shown:&lt;/p&gt;

&lt;p&gt;DimCustomer 1 ─── * BridgeCustomerSegment * ─── 1 DimSegment&lt;br&gt;
Referential integrity and unique values&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referential integrity&lt;/strong&gt; means foreign keys should correspond to valid keys in the related dimension, where applicable.&lt;/p&gt;

&lt;p&gt;For example, every CustomerID in FactSales should also exist in DimCustomer. Missing or unmatched keys may indicate data quality problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Active and inactive relationships
&lt;/h2&gt;

&lt;p&gt;An &lt;strong&gt;active relationship&lt;/strong&gt; is used automatically for filter propagation.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;inactive relationship&lt;/strong&gt; is not used automatically but can be activated in a DAX measure.&lt;/p&gt;

&lt;p&gt;For example, FactSales may have &lt;code&gt;OrderDate&lt;/code&gt; and &lt;code&gt;ShipDate&lt;/code&gt;. DimDate can have an &lt;strong&gt;active relationship&lt;/strong&gt; with &lt;code&gt;OrderDate&lt;/code&gt; and an &lt;strong&gt;inactive relationship&lt;/strong&gt; with &lt;code&gt;ShipDate&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Filter Direction
&lt;/h2&gt;

&lt;p&gt;Filter direction determines how &lt;strong&gt;filters move between related tables&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Single-direction filtering
&lt;/h3&gt;

&lt;p&gt;Filters flow in one direction, mostly from dimensions to facts.&lt;/p&gt;

&lt;p&gt;DimProduct ───────────&amp;gt; FactSales&lt;/p&gt;

&lt;p&gt;If a user selects Electronics in a product slicer, Power BI filters FactSales to show sales for electronics products.&lt;/p&gt;

&lt;p&gt;Single-direction filtering is  recommended for star schemas because it provides predictable behavior and reduces ambiguity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bidirectional filtering
&lt;/h3&gt;

&lt;p&gt;Filters flow in both directions.&lt;/p&gt;

&lt;p&gt;DimProduct &amp;lt;──────────&amp;gt; FactSales&lt;/p&gt;

&lt;p&gt;This may be useful in specific scenarios, such as certain bridge-table models.&lt;/p&gt;

&lt;p&gt;However, bidirectional filtering should be used carefully because it can create ambiguous filter paths, unexpected results and unnecessary model complexity.&lt;/p&gt;

&lt;p&gt;For ShopKe, I would use single-direction filtering by default and only introduce bidirectional filtering when there is a clear business requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Joins in Power Query
&lt;/h2&gt;

&lt;p&gt;A join combines rows from two tables using matching columns. In Power Query, this is performed using &lt;strong&gt;Merge Queries&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If ShopKe had the following tables:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DimCustomers&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;CustomerName&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;Stacy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C002&lt;/td&gt;
&lt;td&gt;Brian&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C003&lt;/td&gt;
&lt;td&gt;Aisha&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;DimOrders&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;OrderID&lt;/th&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;Amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;O001&lt;/td&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;75,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;O002&lt;/td&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;4,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;O003&lt;/td&gt;
&lt;td&gt;C002&lt;/td&gt;
&lt;td&gt;3,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;O004&lt;/td&gt;
&lt;td&gt;C004&lt;/td&gt;
&lt;td&gt;2,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;C003 has no order, while C004 appears in Orders but not Customers.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1 Left Outer Join
&lt;/h3&gt;

&lt;p&gt;Returns all rows from the left table and matching rows from the right table.&lt;/p&gt;

&lt;p&gt;Example: Customers LEFT JOIN Orders.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;CustomerName&lt;/th&gt;
&lt;th&gt;OrderID&lt;/th&gt;
&lt;th&gt;Amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;Stacy&lt;/td&gt;
&lt;td&gt;O001&lt;/td&gt;
&lt;td&gt;75,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;Stacy&lt;/td&gt;
&lt;td&gt;O002&lt;/td&gt;
&lt;td&gt;4,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C002&lt;/td&gt;
&lt;td&gt;Brian&lt;/td&gt;
&lt;td&gt;O003&lt;/td&gt;
&lt;td&gt;3,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C003&lt;/td&gt;
&lt;td&gt;Aisha&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Use&lt;/strong&gt;: Finding customers who have not placed orders while retaining all customers.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2 Right Outer Join
&lt;/h3&gt;

&lt;p&gt;Returns all rows from the right table and matching rows from the left table.&lt;/p&gt;

&lt;p&gt;Example: Customers RIGHT JOIN Orders.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;CustomerName&lt;/th&gt;
&lt;th&gt;OrderID&lt;/th&gt;
&lt;th&gt;Amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;Stacy&lt;/td&gt;
&lt;td&gt;O001&lt;/td&gt;
&lt;td&gt;75,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;Stacy&lt;/td&gt;
&lt;td&gt;O002&lt;/td&gt;
&lt;td&gt;4,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C002&lt;/td&gt;
&lt;td&gt;Brian&lt;/td&gt;
&lt;td&gt;O003&lt;/td&gt;
&lt;td&gt;3,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C004&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;td&gt;O004&lt;/td&gt;
&lt;td&gt;2,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Use&lt;/strong&gt;: Identifying orders without matching customer records.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3 Full Outer Join
&lt;/h3&gt;

&lt;p&gt;Returns all rows from both tables, including unmatched records.&lt;/p&gt;

&lt;p&gt;Expected unmatched records:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;CustomerName&lt;/th&gt;
&lt;th&gt;OrderID&lt;/th&gt;
&lt;th&gt;Amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;Stacy&lt;/td&gt;
&lt;td&gt;O001&lt;/td&gt;
&lt;td&gt;75,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;Stacy&lt;/td&gt;
&lt;td&gt;O002&lt;/td&gt;
&lt;td&gt;4,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C002&lt;/td&gt;
&lt;td&gt;Brian&lt;/td&gt;
&lt;td&gt;O003&lt;/td&gt;
&lt;td&gt;3,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C003&lt;/td&gt;
&lt;td&gt;Aisha&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Matching records are also retained.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use&lt;/strong&gt;: Data reconciliation and identifying missing records in either table.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.4 Inner Join
&lt;/h3&gt;

&lt;p&gt;Returns only rows with matching values in both tables.&lt;/p&gt;

&lt;p&gt;Expected output:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;CustomerName&lt;/th&gt;
&lt;th&gt;OrderID&lt;/th&gt;
&lt;th&gt;Amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;Stacy&lt;/td&gt;
&lt;td&gt;O001&lt;/td&gt;
&lt;td&gt;75,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C001&lt;/td&gt;
&lt;td&gt;Stacy&lt;/td&gt;
&lt;td&gt;O002&lt;/td&gt;
&lt;td&gt;4,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C002&lt;/td&gt;
&lt;td&gt;Brian&lt;/td&gt;
&lt;td&gt;O003&lt;/td&gt;
&lt;td&gt;3,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Use&lt;/strong&gt;: Keeping only customers who have matching orders.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.5 Left Anti Join
&lt;/h3&gt;

&lt;p&gt;Returns rows from the left table that have no matching rows in the right table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;: Customers LEFT ANTI JOIN Orders.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;CustomerName&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C003&lt;/td&gt;
&lt;td&gt;Aisha&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Use&lt;/strong&gt;: Finding customers who have never placed an order.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.6 Right Anti Join
&lt;/h3&gt;

&lt;p&gt;Returns rows from the right table that have no matching rows in the left table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;: Customers RIGHT ANTI JOIN Orders.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;OrderID&lt;/th&gt;
&lt;th&gt;CustomerID&lt;/th&gt;
&lt;th&gt;Amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;O004&lt;/td&gt;
&lt;td&gt;C004&lt;/td&gt;
&lt;td&gt;2,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Use&lt;/strong&gt;: Identifying orders with missing customer records.&lt;/p&gt;

&lt;p&gt;In Power Query, a merge may create a nested table column. For joins that return matching records, the column can be expanded to display the required fields.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Power Query Joins vs Power BI Relationships
&lt;/h2&gt;

&lt;p&gt;Even though both  connect tables, they serve different purposes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Power Query Merge&lt;/th&gt;
&lt;th&gt;Power BI Relationship&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stage&lt;/td&gt;
&lt;td&gt;Data preparation&lt;/td&gt;
&lt;td&gt;Data modelling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Purpose&lt;/td&gt;
&lt;td&gt;Combine data&lt;/td&gt;
&lt;td&gt;Connect tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Physically combines columns?&lt;/td&gt;
&lt;td&gt;Yes, in the merged query result&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter propagation&lt;/td&gt;
&lt;td&gt;Not as a model relationship&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Common use&lt;/td&gt;
&lt;td&gt;Bring ProductCategory into a table&lt;/td&gt;
&lt;td&gt;Connect DimProduct to FactSales&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If ShopKe needs to bring &lt;code&gt;ProductCategory&lt;/code&gt; into FactSales, Power Query Merge can combine the columns.However, if DimProduct already contains &lt;code&gt;ProductCategory&lt;/code&gt;, keeping it separate and creating a relationship is better.&lt;/p&gt;

&lt;p&gt;Too much merging can create a wide table with repeated customer and product information. This may cause increased redundancy and  the model being harder to maintain.Keeping fact and dimension tables separate supports a clearer and more scalable star schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Recommended Power BI Model for ShopKe
&lt;/h2&gt;

&lt;p&gt;For ShopKe, I would recommend a star schema with FactSales connected to DimCustomer, DimProduct and DimDate.&lt;/p&gt;

&lt;p&gt;I would use one-to-many relationships from the dimensions to the fact table and single-direction filtering by default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt; : Separating facts and dimensions can support efficient analytical queries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DAX simplicity&lt;/strong&gt; : Measures such as Total Sales can be written directly against FactSales.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Readability&lt;/strong&gt; : Each table has a clear purpose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt; : New transactions, customers and products can be added without redesigning the entire model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintainability&lt;/strong&gt; : Descriptive information can be updated in dimensions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Report development&lt;/strong&gt; : Users can easily combine dimension attributes with fact measures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example measures:&lt;/p&gt;

&lt;p&gt;Total Sales =&lt;br&gt;
SUM(FactSales&lt;code&gt;[SalesAmount]&lt;/code&gt;)&lt;br&gt;
Total Orders =&lt;br&gt;
DISTINCTCOUNT(FactSales&lt;code&gt;[OrderID]&lt;/code&gt;)&lt;br&gt;
Average Order Value =&lt;br&gt;
DIVIDE(&lt;code&gt;[Total Sales]&lt;/code&gt;, &lt;code&gt;[Total Orders]&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F34orhyqm68et0u4m8xcz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F34orhyqm68et0u4m8xcz.png" alt="New Measures" width="222" height="263"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I would use bridge tables for genuine many-to-many scenarios and inactive relationships for alternative date analysis when necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Data modelling is the foundation of a reliable Power BI report. It determines how data is organized, how tables interact and how accurately business questions can be answered.&lt;/p&gt;

&lt;p&gt;As much as  flat tables are simple and snowflake schemas can support complex structures, a &lt;strong&gt;star schema&lt;/strong&gt; is most likely the most suitable choice for a  business intelligence project.&lt;/p&gt;

&lt;p&gt;Through the ShopKe example, we have seen how fact and dimension tables work together, how relationships control filter propagation and how Power Query joins prepare data for analysis.&lt;/p&gt;

&lt;p&gt;The most important lesson is that proper Power BI development is not only about creating visuals. It is about designing a model that is accurate, understandable, scalable and useful for decision-making.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A well designed data model turns disconnected data into meaningful business insights&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>data</category>
      <category>database</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building an Interactive Excel Dashboard for E-commerce Product Analysis: A Case Study of Jumia Products</title>
      <dc:creator>Stacy Mumbi</dc:creator>
      <pubDate>Sat, 05 Sep 2026 13:13:23 +0000</pubDate>
      <link>https://dev.to/smumbi_/building-an-interactive-excel-dashboard-for-e-commerce-product-analysis-a-case-study-of-jumia-468e</link>
      <guid>https://dev.to/smumbi_/building-an-interactive-excel-dashboard-for-e-commerce-product-analysis-a-case-study-of-jumia-468e</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;After completing Excel's coursework this week courtesy of the &lt;strong&gt;LuxDevHQ Data Science, Analytics and AI Programme&lt;/strong&gt;, I decided to put what I had learnt into practice by analyzing Jumia product data. I challenged myself to turn the raw data into something useful for analysis and decision making.&lt;/p&gt;

&lt;p&gt;The dataset contained 115 product listings with information such as prices, discounts, reviews and ratings. However, it was far from ready for data analysis. There were missing values, duplicate records, negative review counts, inconsistent formatting and even one product whose price was given as a range.&lt;/p&gt;

&lt;p&gt;So, this project ended up being about much more than creating a dashboard. I first had to understand the data, identify the issues, make reasonable cleaning decisions, document those decisions and then use the cleaned data to build an interactive Excel dashboard.&lt;/p&gt;

&lt;p&gt;In this article, I'll walk through that process, from the initial data-quality audit to the final dashboard and the insights I found from the analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Dataset and Business Questions
&lt;/h2&gt;

&lt;p&gt;The dataset, &lt;code&gt;Excel_jumia_dataset.csv&lt;/code&gt;, contained six  fields originally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Product&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Current price&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;old price&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Discount&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Review&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Ratingd&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last column, &lt;code&gt;Ratingd&lt;/code&gt;, was misspelled in the original dataset and was later corrected to &lt;code&gt;Rating&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One of the first things I noticed was that the dataset didn't contain actual sales, revenue or units sold data. Because of this, I couldn't use sales as a measure of product performance.&lt;/p&gt;

&lt;p&gt;Instead, I used review count as a &lt;strong&gt;proxy for customer engagement&lt;/strong&gt; throughout the project.&lt;/p&gt;

&lt;p&gt;This  was important to take note of because having &lt;strong&gt;more reviews&lt;/strong&gt; doesn't necessarily mean that a product has &lt;strong&gt;higher sales&lt;/strong&gt;. A product that has been listed for a longer period, for example, may simply have had more time to accumulate reviews.&lt;/p&gt;

&lt;p&gt;With that in mind, I focused on the following questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are bigger discounts associated with more reviews?&lt;/li&gt;
&lt;li&gt;Do highly rated products receive more engagement?&lt;/li&gt;
&lt;li&gt;Does product price have any relationship with rating?&lt;/li&gt;
&lt;li&gt;Which products stand out based on ratings, reviews or discounts?&lt;/li&gt;
&lt;li&gt;Which products might need a different pricing, marketing or listing strategy?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Data-Quality Audit
&lt;/h2&gt;

&lt;p&gt;Before changing anything, I wanted to understand exactly what I was working with.&lt;/p&gt;

&lt;p&gt;The initial audit showed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;115 rows and 6 columns&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;58 blank Review values&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;58 blank Rating values&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3 exact duplicate rows&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;6 repeated product names&lt;/strong&gt; with different prices, discounts, or review counts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;57 negative Review values&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1 row where both Current price and Old price were given as ranges&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Ratings were within the expected &lt;strong&gt;0–5 range&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Discounts were within &lt;strong&gt;0–100%&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;No Current Price was &lt;strong&gt;higher&lt;/strong&gt; than the corresponding Old Price&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One thing that I immediately realised: all &lt;strong&gt;57 non-blank Review values&lt;/strong&gt; were negative.&lt;/p&gt;

&lt;p&gt;That was unusual. Since a review count cannot be negative realistically, I treated this as a likely systematic scraping or formatting issue rather than assuming the values represented something meaningful.&lt;/p&gt;

&lt;p&gt;If only a few values had been negative, I would have been more cautious about changing them. But because every non-blank review value had the same issue, the pattern strongly suggested a consistent data-quality problem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzbnwiqsfctg1wlcrvw09.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzbnwiqsfctg1wlcrvw09.png" alt="Raw data" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The original dataset as imported into the Raw_data sheet. The negative &lt;code&gt;Review&lt;/code&gt; values and misspelled &lt;code&gt;Ratingd&lt;/code&gt; header are visible.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleaning the Data and Why I Made These Decisions
&lt;/h2&gt;

&lt;p&gt;Cleaning the dataset was one of the most important parts of the project. I didn't want to change values simply because they looked inconvenient. For each major cleaning step, I considered what the data was actually telling me and how the decision would affect the analysis.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Fixing the Negative Review Counts
&lt;/h3&gt;

&lt;p&gt;There were 57 negative Review values and all non-blank review values had this issue.&lt;/p&gt;

&lt;p&gt;I created a helper column and used the following formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=IF(E2="","",ABS(VALUE(E2)))

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This converted the negative values into positive review counts while keeping genuinely blank cells blank.&lt;/p&gt;

&lt;p&gt;After checking the results, I pasted the corrected values as values so that the cleaned numbers were no longer dependent on the formula.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Handling Missing Reviews and Ratings
&lt;/h3&gt;

&lt;p&gt;Both &lt;code&gt;Review&lt;/code&gt; and &lt;code&gt;Rating&lt;/code&gt; contained &lt;strong&gt;58 blank values&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I intentionally &lt;strong&gt;left these cells blank&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A blank Review means that the review count wasn't captured. A zero means that the product had zero reviews. These are two completely different situations.&lt;/p&gt;

&lt;p&gt;Replacing the blanks with zero would therefore have affected averages, rankings and other calculations in ways that weren't supported by the original data.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Handling the Price Range
&lt;/h3&gt;

&lt;p&gt;One product, a 1/2/3 Seater Elastic Sofa Cover, had its prices listed as ranges:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current price&lt;/strong&gt;: KSh 1,620–1,980&lt;br&gt;
&lt;strong&gt;Old price&lt;/strong&gt;: KSh 2,200–3,200&lt;/p&gt;

&lt;p&gt;I had three options to choose from:&lt;/p&gt;

&lt;p&gt;Use the lower value&lt;br&gt;
Remove the row&lt;br&gt;
Use the midpoint&lt;/p&gt;

&lt;p&gt;I chose the &lt;strong&gt;midpoint&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Using the lower bound would ignore part of the information provided, while removing the row would mean losing a  useful product just because its price was formatted differently.&lt;/p&gt;

&lt;p&gt;The original range was retained elsewhere so that the decision could still be traced back to the source data.&lt;/p&gt;

&lt;p&gt;There was also an interesting consequence of this decision. When I calculated the discount using the midpoint prices, I got &lt;strong&gt;33%&lt;/strong&gt;, while the seller's advertised discount was &lt;strong&gt;38%&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Rather than replacing the advertised value, I kept both values. The difference is documented as a result of using an estimated midpoint rather than an exact price.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Removing Duplicates
&lt;/h3&gt;

&lt;p&gt;The dataset contained &lt;strong&gt;three rows&lt;/strong&gt; that were &lt;strong&gt;completely identical&lt;/strong&gt; across all six original fields. I removed these as they were &lt;strong&gt;exact duplicates&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;However, there were also &lt;strong&gt;six&lt;/strong&gt; cases where the same product name appeared more than once but the prices, discounts or review counts were different.I kept these records.&lt;/p&gt;

&lt;p&gt;Because Jumia is a marketplace with multiple sellers, the repeated product names could represent different seller listings rather than duplicate records. Removing them without evidence would have meant making an assumption that the data didn't support.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Fixing Text and Formatting Issues
&lt;/h3&gt;

&lt;p&gt;I also made other formatting corrections:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Removed "out of 5" from the &lt;code&gt;Rating&lt;/code&gt; values&lt;/li&gt;
&lt;li&gt;Converted &lt;code&gt;Rating&lt;/code&gt; into a numeric decimal&lt;/li&gt;
&lt;li&gt;Changed &lt;code&gt;Ratingd&lt;/code&gt; to &lt;code&gt;Rating&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Standardized &lt;code&gt;old price&lt;/code&gt; to &lt;code&gt;Old Price&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Converted prices from text into numeric currency values&lt;/li&gt;
&lt;li&gt;Formatted prices as &lt;code&gt;KSh #,##0.00&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl4v2lj6vnrlm4oe1dksu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl4v2lj6vnrlm4oe1dksu.png" alt="Cleaned data" width="800" height="433"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The cleaned tblProducts table showing corrected data types, standardized fields and additional analytical columns.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;All of these cleaning decisions were documented in the &lt;strong&gt;Data Dictionary&lt;/strong&gt; sheet so that the process could be reviewed and traced back to the original data.&lt;/p&gt;
&lt;h2&gt;
  
  
  Adding Enrichment Fields
&lt;/h2&gt;

&lt;p&gt;Once the basic cleaning was complete, I created additional fields to make the dataset more useful for analysis.&lt;/p&gt;
&lt;h3&gt;
  
  
  Discount Amount and Calculated Discount
&lt;/h3&gt;

&lt;p&gt;I calculated the difference between the Old Price and Current Price to get the &lt;strong&gt;Discount Amount&lt;/strong&gt;.I also calculated the corresponding percentage discount and kept it alongside the original &lt;strong&gt;advertised Discount&lt;/strong&gt;.&lt;br&gt;
This made it possible to compare the advertised discount with the discount calculated from the prices.&lt;/p&gt;
&lt;h3&gt;
  
  
  Rating Category
&lt;/h3&gt;

&lt;p&gt;I grouped products into three rating categories:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Poor&lt;/strong&gt;: &amp;lt; 3&lt;br&gt;
&lt;strong&gt;Average&lt;/strong&gt;: 3–4.5&lt;br&gt;
&lt;strong&gt;Excellent&lt;/strong&gt;: &amp;gt; 4.5&lt;/p&gt;

&lt;p&gt;The original brief left ratings from 4.1 to 4.5 without a category. I therefore followed the suggested option of including this range under Average and documented this as a working assumption.&lt;/p&gt;
&lt;h3&gt;
  
  
  Discount Category
&lt;/h3&gt;

&lt;p&gt;Discounts were grouped into:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Low&lt;/strong&gt;: &amp;lt; 20%&lt;br&gt;
&lt;strong&gt;Medium&lt;/strong&gt;: 20–40%&lt;br&gt;
&lt;strong&gt;High&lt;/strong&gt;: &amp;gt; 40%&lt;/p&gt;
&lt;h3&gt;
  
  
  Price Category
&lt;/h3&gt;

&lt;p&gt;Rather than guessing price cutoffs, I used the data itself to determine the categories.&lt;/p&gt;

&lt;p&gt;I calculated the first and third quartiles of Current Price using&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;QUARTILE.INC()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting thresholds were:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Price Q1&lt;/strong&gt;: KSh 493&lt;br&gt;
&lt;strong&gt;Price Q3&lt;/strong&gt;: KSh 1,669.50&lt;/p&gt;

&lt;p&gt;I then used these thresholds  to classify products into &lt;strong&gt;Low, Medium and High&lt;/strong&gt; price categories.&lt;/p&gt;

&lt;h3&gt;
  
  
  Engagement Strength
&lt;/h3&gt;

&lt;p&gt;I also wanted a data-driven way to identify products with relatively high engagement.&lt;/p&gt;

&lt;p&gt;I used the &lt;strong&gt;75th percentile&lt;/strong&gt; of Review count as the threshold:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Review Q3&lt;/strong&gt;: 13 reviews&lt;/p&gt;

&lt;p&gt;Products with 13 or more reviews were therefore classified as having &lt;strong&gt;Strong Engagement&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Status
&lt;/h3&gt;

&lt;p&gt;Because the dataset contained many missing ratings and reviews, I added a &lt;code&gt;Data Status&lt;/code&gt; field showing whether each product was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complete&lt;/li&gt;
&lt;li&gt;Missing rating&lt;/li&gt;
&lt;li&gt;Missing review&lt;/li&gt;
&lt;li&gt;Missing both&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This made it easier to understand the level of data coverage behind the analysis.&lt;/p&gt;

&lt;h3&gt;
  
  
  Combination Flags
&lt;/h3&gt;

&lt;p&gt;Finally, I created &lt;strong&gt;four&lt;/strong&gt; flags to identify products with interesting combinations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High discount + low rating&lt;/li&gt;
&lt;li&gt;High discount + low engagement&lt;/li&gt;
&lt;li&gt;Many reviews + average rating&lt;/li&gt;
&lt;li&gt;Strong engagement + excellent rating&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whenever one of the required values was missing, the formula returned "Missing" rather than making an assumption.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7lwrehww02ddxxb3b72h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7lwrehww02ddxxb3b72h.png" alt="Formula" width="800" height="18"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example of an Excel formula used to classify products into Rating Categories.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  PivotTables and Analysis Workflow
&lt;/h2&gt;

&lt;p&gt;With the data cleaned and enriched, I moved on to the analysis.&lt;br&gt;
One of the main things I wanted to investigate was whether price, discount, rating and review count were related.&lt;/p&gt;

&lt;p&gt;I focused on three relationships:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Discount vs. Reviews&lt;/li&gt;
&lt;li&gt;Rating vs. Reviews&lt;/li&gt;
&lt;li&gt;Current Price vs. Rating&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because &lt;code&gt;Review&lt;/code&gt; and &lt;code&gt;Rating&lt;/code&gt; contained missing values, I didn't want blank cells to interfere with the correlation analysis.&lt;/p&gt;

&lt;p&gt;I therefore created helper ranges using &lt;code&gt;FILTER()&lt;/code&gt; to return only rows where both variables in each pair were available. I then used these complete pairs for the correlation calculations and scatter plots.&lt;/p&gt;

&lt;p&gt;The results were:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Relationship&lt;/th&gt;
&lt;th&gt;Pearson r&lt;/th&gt;
&lt;th&gt;R²&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Discount vs. Reviews&lt;/td&gt;
&lt;td&gt;-0.111&lt;/td&gt;
&lt;td&gt;0.012&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rating vs. Reviews&lt;/td&gt;
&lt;td&gt;0.043&lt;/td&gt;
&lt;td&gt;0.002&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Current Price vs. Rating&lt;/td&gt;
&lt;td&gt;0.110&lt;/td&gt;
&lt;td&gt;0.012&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All three relationships were &lt;strong&gt;very weak&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, the correlation between Discount and Reviews was &lt;strong&gt;-0.111&lt;/strong&gt;, which is very close to zero.&lt;/p&gt;

&lt;p&gt;The R² values were also extremely small, meaning that each variable explained &lt;strong&gt;very little&lt;/strong&gt; of the variation in the other variable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqfjwpsyvlyqjqwmrhkp5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqfjwpsyvlyqjqwmrhkp5.png" alt="Correlation" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Scatter plots for Discount vs. Reviews, Rating vs. Reviews, and Current Price vs. Rating, each with a linear trendline, equation and R² value.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Product Rankings
&lt;/h3&gt;

&lt;p&gt;I also created PivotTables to identify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Top 10 products by Rating&lt;/li&gt;
&lt;li&gt;Bottom 10 products by Rating&lt;/li&gt;
&lt;li&gt;Top 10 products by Reviews&lt;/li&gt;
&lt;li&gt;Top 10 products by Discount&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also used the combination flags to identify products that met the four additional conditions.&lt;/p&gt;

&lt;p&gt;For rating-based rankings, products with missing ratings were excluded.&lt;br&gt;
Where products had the same rating, I used review count as the tie-breaker. For review-count ties, rating was used as the tie-breaker.&lt;/p&gt;

&lt;p&gt;This made the ranking rules explicit rather than leaving the ordering to chance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa3txgi7pu3fezil57ihp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa3txgi7pu3fezil57ihp.png" alt="Top 10 Products by Discount" width="799" height="255"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5knc85c9t7w2nnhpk9y8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5knc85c9t7w2nnhpk9y8.png" alt="Top 10 products by rating" width="800" height="306"&gt;&lt;/a&gt;&lt;br&gt;
_PivotTables used to identify the highest and lowest-performing products across ratings and discounts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Interactive Dashboard
&lt;/h2&gt;

&lt;p&gt;After completing the analysis, I brought the most useful pieces together into a &lt;strong&gt;single-screen dashboard&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I wanted the dashboard to answer the main questions quickly without requiring someone to scroll through multiple sheets.&lt;/p&gt;

&lt;p&gt;The dashboard includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;KPI Cards&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Total Products&lt;/li&gt;
&lt;li&gt;Average Price&lt;/li&gt;
&lt;li&gt;Average Discount&lt;/li&gt;
&lt;li&gt;Average Rating&lt;/li&gt;
&lt;li&gt;Total Reviews&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product Ranking Charts&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Top 10 by Rating&lt;/li&gt;
&lt;li&gt;Top 10 by Reviews&lt;/li&gt;
&lt;li&gt;Top 10 by Discount&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relationship Analysis&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Discount vs. Reviews&lt;/li&gt;
&lt;li&gt;Rating vs. Reviews&lt;/li&gt;
&lt;li&gt;Price vs. Rating&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each scatter plot includes a trendline, equation and R² value.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Category Analysis&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Rating Mix&lt;/li&gt;
&lt;li&gt;Discount Mix&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interactive Slicers&lt;/strong&gt;
The dashboard has &lt;strong&gt;slicers&lt;/strong&gt; for:

&lt;ul&gt;
&lt;li&gt;Rating Category&lt;/li&gt;
&lt;li&gt;Discount Category&lt;/li&gt;
&lt;li&gt;Price Category&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These slicers are connected to the compatible PivotTables and charts, allowing the dashboard to update when a user filters the data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgot19s72tp7by95fo23n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgot19s72tp7by95fo23n.png" alt="Dashboard" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The completed single-screen dashboard combining KPIs, product rankings, relationship analysis, category charts and interactive slicers.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Findings
&lt;/h2&gt;

&lt;p&gt;After cleaning and analyzing the data, a few findings stood out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Bigger Discounts Didn't Necessarily Mean More Engagement&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The correlation between Discount and Review count was &lt;strong&gt;-0.111&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's really close to zero.&lt;/p&gt;

&lt;p&gt;So in this dataset, products with larger discounts did not necessarily have more reviews.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Price and Rating Were Barely Related&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The correlation between Current Price and Rating was &lt;strong&gt;0.110&lt;/strong&gt;.&lt;br&gt;
This is also a &lt;strong&gt;very weak&lt;/strong&gt; relationship.&lt;/p&gt;

&lt;p&gt;In this dataset, highly rated products were not always more expensive or cheaper than products with lower ratings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Some Heavily Discounted Products Still Had Weak Performance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some products combined a &lt;strong&gt;high discount with either a low rating or low engagement&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;These products caught my attention because offering a bigger discount may not solve the underlying problem.&lt;/p&gt;

&lt;p&gt;They could instead be worth investigating from a listing-quality perspective, such as product descriptions, images, presentation or customer expectations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Some Products Had Both Strong Engagement and Excellent Ratings&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There were also 5 products that stood out for having both strong engagement and excellent ratings.They could provide useful examples of what appears to be working well.&lt;/p&gt;

&lt;p&gt;Even though the dataset doesn't contain enough information to explain why these products perform well, they would be good choices for further investigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Business Recommendations
&lt;/h2&gt;

&lt;p&gt;Based on the patterns in the data, I would recommend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Not to rely on discounts alone to drive engagement&lt;/strong&gt;. Since discount and review count were almost not related, sellers could test other approaches such as improving product descriptions, images and overall listing quality.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Investigating heavily discounted products with low ratings or engagement&lt;/strong&gt;. Instead of immediately offering an even bigger discount, it may be better to first understand why the product isn't attracting positive customer feedback.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Studying products with strong engagement and excellent ratings&lt;/strong&gt;. These products could provide useful examples for understanding what works well across listings.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Using data carefully when making decisions&lt;/strong&gt;. The results show patterns in this dataset, but they don't prove that changing one variable will cause another to change.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;This project also has limitations like any other.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The biggest one is that the dataset doesn't contain sales, revenue, units sold or listing-age data. Because of this, &lt;strong&gt;review count&lt;/strong&gt; is only a proxy for engagement and cannot be treated as a measure of sales performance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There is also one product whose price had to be estimated using the midpoint of a range rather than an exact value.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;Rating Category&lt;/strong&gt; assumption for ratings between 4.1 and 4.5. I grouped these under "Average" based on the suggested option in the project brief, but this was just a working assumption .&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The correlations in this analysis describe relationships between variables. Correlation does not imply causation.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;One of my biggest lessons from this project was that &lt;strong&gt;data analysis starts  before the charts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Initially, the dataset looked small and straightforward. But once I started auditing it, I found several issues that could have easily affected the final results if I had jumped straight into analysis. &lt;/p&gt;

&lt;p&gt;The negative review counts were a good example. If I hadn't investigated that pattern, I could have ended up presenting misleading engagement figures.&lt;/p&gt;

&lt;p&gt;I also learned &lt;strong&gt;the importance of documenting assumptions&lt;/strong&gt;. For example, rather than silently changing the price-range row or filling missing values with zeros, I recorded what I did and why.&lt;/p&gt;

&lt;p&gt;Most importantly, this project helped me &lt;strong&gt;see Excel as more than just a spreadsheet tool&lt;/strong&gt;. I was able to use it for the full analytics process that is  data auditing, cleaning, transformation, analysis, visualization and communication , all within one workbook.&lt;/p&gt;

&lt;p&gt;And probably the biggest lesson was that &lt;strong&gt;a good dashboard isn't just about making something look nice&lt;/strong&gt;. The real value comes from making sure that the numbers behind it are trustworthy and that the story you're telling is actually supported by the data.&lt;/p&gt;

&lt;p&gt;I am honestly still in awe of how such a seemingly small dataset could yield so many other columns and useful insights after proper cleaning and  analysis.&lt;br&gt;
From just a few original columns, I was able to create additional fields, uncover patterns, build meaningful visualizations and turn the data into a dashboard that tells a story.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How I love being a data analyst!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Files
&lt;/h2&gt;

&lt;p&gt;The complete project is available here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Repository&lt;/strong&gt;: &lt;a href="https://github.com/SMumbi5144/Jumia_Product_Performance_Dashboard.git" rel="noopener noreferrer"&gt;Jumia_Product_Performance_Dashboard&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Dashboard Worksheet&lt;/strong&gt;: Available in the repository under &lt;code&gt;dashboard&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Raw Dataset&lt;/strong&gt;: Available under &lt;code&gt;data&lt;/code&gt;&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>data</category>
      <category>datascience</category>
      <category>learning</category>
    </item>
    <item>
      <title>Getting Started with Excel for Data Analytics: From Basics to Data Cleaning</title>
      <dc:creator>Stacy Mumbi</dc:creator>
      <pubDate>Sun, 30 Aug 2026 05:47:14 +0000</pubDate>
      <link>https://dev.to/smumbi_/getting-started-with-excel-for-data-analytics-from-basics-to-data-cleaning-54p1</link>
      <guid>https://dev.to/smumbi_/getting-started-with-excel-for-data-analytics-from-basics-to-data-cleaning-54p1</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Data is definitely everywhere because almost all the information we receive everyday can be collected and analysed data, but having a dataset is only the beginning of the data analytics process. Before analysing data and drawing conclusions from it, it is important to make sure that the data is accurate, consistent, complete and in the correct format through a  process called data cleaning.&lt;/p&gt;

&lt;p&gt;During Week 3 of my Data Science and Analytics training at LuxDev Academy, I was introduced to the basics of Microsoft Excel and how it can be used to prepare data for analysis. I used to view Excel as just an application that is used to view spreadsheets, but this week I learnt that it has many useful features for working with real-world datasets. These include sorting, filtering, formatting, removing duplicates, finding and replacing values and using functions such as &lt;strong&gt;TRIM&lt;/strong&gt;, &lt;strong&gt;PROPER&lt;/strong&gt; and &lt;strong&gt;CONCAT&lt;/strong&gt; which are known as &lt;strong&gt;Text Functions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For practice, I decided to work on a synthetic Kenyan &lt;strong&gt;M-Pesa&lt;/strong&gt; transaction dataset generated using &lt;strong&gt;Mockaroo&lt;/strong&gt;. The dataset contains 193 transaction records and includes information such as transaction IDs, transaction dates, sender and receiver names, transaction types, transaction amounts, transaction status, counties and transaction fees.&lt;/p&gt;

&lt;p&gt;Even though the dataset was synthetic and small, it contained several inconsistencies that provided an opportunity to practise the data-cleaning concepts I had learned. In this article, I will walk through how I did the cleaning, from importing the dataset into Excel to identifying and correcting formatting, missing values, duplicates and inconsistencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Generating and Importing the Dataset
&lt;/h2&gt;

&lt;p&gt;I first generated the M-Pesa transactions dataset using &lt;strong&gt;Mockaroo&lt;/strong&gt; and downloaded it as a &lt;strong&gt;CSV(Comma Separated Values)&lt;/strong&gt; file. I chose an M-Pesa dataset because mobile money transactions are really relevant to the Kenyan context as millions of people transact money everyday using M-Pesa and it can contain different types of information that would be useful for data analysis.&lt;/p&gt;

&lt;p&gt;After downloading the CSV file, I opened Microsoft Excel. Instead of opening the file directly through the &lt;strong&gt;File&lt;/strong&gt; section, I practised importing the data through the &lt;strong&gt;Data&lt;/strong&gt; option on the ribbon at the top of the blank workbook: &lt;strong&gt;'Data' then 'Get Data' then 'From File' then 'From Text/CSV'&lt;/strong&gt;. Excel then showed a preview of the data before loading it into the workbook.&lt;/p&gt;

&lt;p&gt;It was a very important first step before beginning any cleaning.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F42m3fquu79dvgy29kqyy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F42m3fquu79dvgy29kqyy.png" alt="_This is how the dataset looked like after loading and before I started cleaning._" width="800" height="426"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;This is how the dataset looked like after loading and before I started cleaning.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Making a Duplicate Sheet Before Cleaning
&lt;/h2&gt;

&lt;p&gt;Before making any changes to the data, I duplicated the worksheet by right-clicking the sheet tab by copying the whole dataset by first using keyboard shortcut &lt;strong&gt;Ctrl+A&lt;/strong&gt; to select the whole dataset,then &lt;strong&gt;Ctrl+C&lt;/strong&gt; to copy, then added a new sheet and then right-clicked in the top-left cell to paste the dataset . This gave me an untouched version of the original dataset to refer back to, while I carried out all my cleaning on the duplicate sheet.&lt;br&gt;
If I made a mistake while cleaning, I still had the original data intact rather than needing to re-download the dataset or opening it from my file explorer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7t2bdqgr12w9h6bsn3im.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7t2bdqgr12w9h6bsn3im.png" alt="_Creating a backup copy of the sheet so the original data stayed untouched._" width="799" height="423"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Creating a backup copy of the sheet so the original data stayed untouched.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Formatting, Sorting and Checking for Duplicates
&lt;/h2&gt;

&lt;p&gt;Once the dataset was loaded, I used &lt;strong&gt;'Home' then 'Format' then 'AutoFit Column Width'&lt;/strong&gt; so that all the values were completely visible, which made it easier to see the data during cleaning.&lt;/p&gt;

&lt;p&gt;I then checked the dataset for duplicate records using &lt;strong&gt;'Data' then 'Data Tools' then 'Remove Duplicates'&lt;/strong&gt;, selecting all columns so that only exact duplicate rows would be removed. Excel returned that no duplicates were found. Even though none were present, the step was important  because checking that a potential problem does not exist is as much a part of data cleaning as fixing one that does.&lt;/p&gt;

&lt;p&gt;I also applied filters via &lt;strong&gt;'Home' then 'Sort &amp;amp; Filter' then 'Filter'&lt;/strong&gt;, which added drop-down buttons to each column header. This made it much faster to look at individual columns. For example, checking 'County' for blanks or checking 'Transaction Status' and 'Transaction Type' for inconsistent entries  without having to scroll through every column.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3dzqla3miy0opq3j6wwn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3dzqla3miy0opq3j6wwn.png" alt="_The Home ribbon tools I relied used for formatting and checking the data._" width="799" height="80"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The Home ribbon tools I  used for formatting and creating the drop-downs.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Checking and Correcting Data Types
&lt;/h2&gt;

&lt;p&gt;I then went column by column checking whether Excel had interpreted each one correctly, since a value can look correct visually while having the wrong  data type.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transaction ID&lt;/strong&gt; - changed from Number to &lt;strong&gt;Text&lt;/strong&gt;, since it is an identifier rather than a value that can be calculated or aggregated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transaction Date&lt;/strong&gt; - changed from General to a proper &lt;strong&gt;Date&lt;/strong&gt; format (year-month-day), making it possible to sort and support  analysis later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sender Name / Receiver Name&lt;/strong&gt; - already correctly identified as text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sender Phone&lt;/strong&gt; - converted to &lt;strong&gt;Text&lt;/strong&gt;, since phone numbers are identifiers not quantities that can be aggregated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transaction Type / Transaction Status&lt;/strong&gt; - already identified as text, but needed further cleaning for consistency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transaction Amount / Transaction Fee&lt;/strong&gt; - formatted from &lt;strong&gt;Number&lt;/strong&gt; to &lt;strong&gt;Currency (KES)&lt;/strong&gt;, since these represent monetary values and needed to  clearly show that.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Handling Missing Values
&lt;/h2&gt;

&lt;p&gt;Using the drop-downs I had applied earlier, I checked each column for blanks. I found missing values in &lt;strong&gt;County&lt;/strong&gt;, as well as in the numeric fields &lt;strong&gt;Transaction Amount&lt;/strong&gt; and &lt;strong&gt;Transaction Fee&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I did not treat all missing values the same way. For the text column, &lt;strong&gt;County&lt;/strong&gt;, I replaced blanks with "Unknown". For the numerical fields, I left the blanks as they were rather than filling in a made-up value as it would have added incorrect information to the dataset rather than  fixing it.&lt;/p&gt;

&lt;p&gt;This taught me that how you handle a missing value should depend on what the column represents and not a single default rule applied everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Standardising Text with Key Functions
&lt;/h2&gt;

&lt;p&gt;Several columns had inconsistent capitalisation or extra spacing, so this was where most of my actual cleaning work happened:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Find and Replace (Ctrl + H)&lt;/strong&gt; - used to correct inconsistent entries in Transaction Type (e.g different casings of "Pay Bill") and Transaction Status (e.g "failed" vs "Failed").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TRIM&lt;/strong&gt; - used in a helper column to remove hidden extra spaces from text values that looked correct but weren't matching due to leading spaces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PROPER&lt;/strong&gt; - used in a helper column to standardise capitalisation in Sender Name and Receiver Name (e.g turning "MUTUA KARANJA" into "Mutua Karanja").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CONCAT&lt;/strong&gt; - used to restore the leading zero on Kenyan phone numbers after removing  apostrophes and the country-code  had left some numbers starting with "1" instead of "0". The formula I used was &lt;code&gt;=CONCAT("0",D2)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each of these, I applied the formula to the first cell in a row, used AutoFill to apply it down the column, then used &lt;strong&gt;Paste Special for Values only&lt;/strong&gt; to convert the formula results into permanent values in a new column before deleting the helper columns. This kept my final dataset clean while preserving the original column structure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F989d7hmacrr5b3u6wst6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F989d7hmacrr5b3u6wst6.png" alt="One of the helper columns" width="262" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;For cleaning the sender name column I created a helper column and used the above formulas to clean it.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  7. What I Learned From the Cleaning Process
&lt;/h2&gt;

&lt;p&gt;Working on this dataset helped me understand that data cleaning is more than  removing duplicates and deleting or filling blank cells.&lt;/p&gt;

&lt;p&gt;This is a brief summary of what I learnt:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The importance of understanding the meaning of each column before deciding how to clean it. A phone number consists of digits, but treating it as numeric wouldn't make sense because there's no meaningful total or average of phone numbers. Also, a Transaction ID may contain only numbers but should be treated as text because it's an identifier, not a value to calculate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Missing values should be handled according to the type and meaning of the data i.e "Unknown" for text fields where appropriate and left blank for numeric fields instead of guessing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sorting and filtering are really important tools as they help you find out what's actually in a column before you touch it. In addition, creating a copy of the original sheet before cleaning meant I could  correct mistakes without risking the original data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Excel usually offers more than one way to solve the same problem like in the case of  PROPER versus Find and Replace and choosing between them often comes down to the type of inconsistency and personal preference.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;My first practical data-cleaning project using Excel gave me a much better understanding of how raw data can be prepared for analysis.&lt;/p&gt;

&lt;p&gt;I started with a synthetic Kenyan M-Pesa transactions dataset generated using Mockaroo and imported it into Excel. Before making any changes, I duplicated the sheet to preserve the original data, then worked through the dataset column by column correcting data types, formatting monetary fields as KES currency, converting dates into the correct format, treating identifiers like Transaction ID and phone numbers as text, handling missing values carefully and standardising text using TRIM, PROPER, Find and Replace and CONCAT.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fog1keqx6myyfnltcqqvw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fog1keqx6myyfnltcqqvw.png" alt="_The dataset after cleaning now with consistent formatting and standardized text_." width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The dataset after cleaning now with consistent formatting and standardized text&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The dataset was not too messy, but that actually made it easier for my first practice exercise. It allowed me to focus on understanding the purpose of each Excel tool rather than becoming overwhelmed by a very complicated dataset.&lt;/p&gt;

&lt;p&gt;I learnt that good data cleaning requires both technical Excel skills and an understanding of the data itself. Excel provides the tools, but I still need to make informed decisions about how those tools should be used.&lt;/p&gt;

&lt;p&gt;As I continue learning data analytics, I hope to build on these skills and become more confident in using Excel for larger and more complex datasets. Week 3 has given me a strong foundation and I now have a better understanding of how important data cleaning is before carrying out analysis and visualisation.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>My First Project:From a Local Folder to GitHub Using Git and SSH</title>
      <dc:creator>Stacy Mumbi</dc:creator>
      <pubDate>Sat, 22 Aug 2026 17:59:57 +0000</pubDate>
      <link>https://dev.to/smumbi_/my-first-projectfrom-a-local-folder-to-github-using-git-and-ssh-407h</link>
      <guid>https://dev.to/smumbi_/my-first-projectfrom-a-local-folder-to-github-using-git-and-ssh-407h</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Introduction&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;It's only been two weeks since I enrolled in the &lt;strong&gt;LuxDev Data Science, Analytics and AI program&lt;/strong&gt; yet I've already learnt so much!&lt;br&gt;
One of the first things I've been able to learn is how to take a project from a local folder in my PC and publish it to GitHub using &lt;strong&gt;Git and SSH&lt;/strong&gt;.&lt;br&gt;
For someone who is still building their skills in data and tech,terms like repositories,commits,staging,remotes and SSH keys were new to me at first. Working on my first project helped me understand these commands and  also what happens behind the scenes.&lt;/p&gt;

&lt;p&gt;So for a start,I decided to document my experience of taking &lt;strong&gt;my first GitHub project from a local folder to GitHub using Git and SSH&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;What are Git and GitHub?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Interestingly, I learnt that Git and GitHub are not the same thing. This is why understanding what each of them means became a very important step in my project.&lt;/p&gt;
&lt;h3&gt;
  
  
  Git
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Git&lt;/strong&gt; is a version control system which runs in my PC and helps me keep track of changes made to files in a project.&lt;br&gt;
As I work,I might change my code,update my analysis,add new files or fix mistakes. Git lets me keep track of the changes I make over time instead of having multiple edited copies of the same file. Each saved version is called a &lt;strong&gt;commit&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  GitHub
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;GitHub&lt;/strong&gt; is an online platform where repositories from Git can be stored and shared.&lt;br&gt;
Git does not need to work with GitHub as I can use it in my PC without uploading my project elsewhere.&lt;br&gt;
However, GitHub becomes useful when I want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep a back up of my projects online&lt;/li&gt;
&lt;li&gt;Show potential employers what I can do&lt;/li&gt;
&lt;li&gt;Share my work &lt;/li&gt;
&lt;li&gt;Collaborate with others &lt;/li&gt;
&lt;li&gt;Build a portfolio &lt;/li&gt;
&lt;li&gt;Contribute to open-source projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a nutshell, this is the journey the local folder in my PC took to reach Github:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Project folder in my PC
         `
Track changes with Git
         `
Commit changes
         `
Push to GitHub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1: Create the Project Files
&lt;/h3&gt;

&lt;p&gt;I first created my project folder in my PC's file explorer and named it   &lt;strong&gt;Kenya_Health_Records_Analysis&lt;/strong&gt;.&lt;br&gt;
Then I opened Git Bash and navigated to it using these commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;Desktop
&lt;span class="nb"&gt;cd &lt;/span&gt;Kenya_Health_Records_Analysis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cd&lt;/code&gt;(change directory) is a command that tells Git to enter a file or folder in simple terms. &lt;br&gt;
I then proceeded to add a data and README.md file in the project folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;data
&lt;span class="nb"&gt;touch &lt;/span&gt;README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mkdir&lt;/code&gt;(make directory) is used to create a new file or folder from the Git Bash terminal but notice I did not use it to create the README.md file because &lt;code&gt;touch&lt;/code&gt; is used to create files with extensions like .md, .py or .ipynb .&lt;br&gt;
I then copied my &lt;em&gt;Kenya_Health_Records.csv&lt;/em&gt; dataset and pasted it into the newly created data file in my file explorer.&lt;br&gt;
To confirm this in Git Bash I ran the &lt;code&gt;ls&lt;/code&gt; command which lists the contents of a file or folder. &lt;br&gt;
This is what I could see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls &lt;/span&gt;data
Kenya_Health_Records.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I went ahead to briefly describe the project in the README.me using the &lt;code&gt;echo&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"# Kenya_Health_Records_Analysis"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;README.md
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;" Using Excel to analyze Kenya health records to uncover,trends,patterns and insights in healthcare data."&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;echo&lt;/code&gt; prints text while &lt;code&gt;&amp;gt;&lt;/code&gt;directs the text into the README.md file however &lt;code&gt;&amp;gt;&lt;/code&gt; can replace what is already in the README.md file that is why in the next line I used &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; which adds content below what is already there.&lt;br&gt;
To view the full README.md I used the &lt;code&gt;cat&lt;/code&gt; command (which displays the contents of a file in the terminal) and ran &lt;code&gt;cat README.md&lt;/code&gt; which retrieved:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Kenya_Health_Records_Analysis&lt;/span&gt;
Using Excel to analyze Kenya health records to uncover,trends,patterns and insights in healthcare data.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Turning the Folder into a Git Repository
&lt;/h3&gt;

&lt;p&gt;I then ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command initializes a new Git repository and Git begins to manage the project. It also added a new hidden &lt;code&gt;.git&lt;/code&gt; file  in the project folder.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Checking the Status of My Project
&lt;/h3&gt;

&lt;p&gt;One of the most useful Git commands is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells me what Git can see or what is happening inside my repo. At first it told me that both the data and &lt;code&gt;README.md&lt;/code&gt; were  untracked files meaning that Git had not been told to include them in the project history or to commit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Staging the Files
&lt;/h3&gt;

&lt;p&gt;To stage both files I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dot tells git to add everything in the current directory.&lt;br&gt;
I then checked the status again using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The files appeared under the changes to be committed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Creating My First Commit
&lt;/h3&gt;

&lt;p&gt;I then created my first commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add Kenya Health Record Analysis"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-m&lt;/code&gt; allows me to provide a message that explains what I changed.&lt;br&gt;
Hence a commit message can explain what happened at different points in time in the project's history if written clearly.&lt;br&gt;
I then ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git log &lt;span class="nt"&gt;--oneline&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This retrieved my unique commit ID and my commit message.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Creating a GitHub Repository
&lt;/h3&gt;

&lt;p&gt;After creating the project locally, I went to GitHub and created a new repository and named it the same way I had in my File Explorer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Kenya_Health_Records_Analysis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This would become an online version of my local Git repository. I also learnt that even though I created the repo and named it the same way,it does not automatically upload my local files.&lt;br&gt;
I still needed to connect my local repo to the GitHub repo and &lt;strong&gt;push&lt;/strong&gt; the changes and this where an SSH Key comes in.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 7: Defining an SSH Key
&lt;/h3&gt;

&lt;p&gt;To me, it sounded like such a complex tech term but it was actually just a simple concept.&lt;br&gt;
&lt;strong&gt;SSH(Secure Shell)&lt;/strong&gt; provides a secure way for my PC to connect with my GitHub account.&lt;br&gt;
So after creating the repo on GitHub I was given two link options to connect to it;&lt;code&gt;HTTPS&lt;/code&gt; and &lt;code&gt;SSH&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 8: Connecting my Local Repository to GitHub
&lt;/h3&gt;

&lt;p&gt;Since I then had both my local Git and GitHub respositories, I needed to connect them using a &lt;strong&gt;remote&lt;/strong&gt;.&lt;br&gt;
A &lt;strong&gt;remote&lt;/strong&gt; is a reference to another repository hosted online.&lt;br&gt;
I then copied the SSH key for my GitHub repository and ran this in Git:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote add origin git@github.com:SMumbi5144/Kenya_Health_Analysis.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;origin&lt;/code&gt; is the name given to the remote repository.&lt;br&gt;
The SSH address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git@github.com:SMumbi5144/Kenya_Health_Records_Analysis.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;tells Git where the remote repo is.&lt;br&gt;
To check if the two repos had connected I ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 9: Pushing the Project to GitHub
&lt;/h3&gt;

&lt;p&gt;Once I had confirmed the two had connected, I pushed my local repo to GitHub by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;git push&lt;/code&gt; - tells Git to upload my commits to a remote repository.&lt;br&gt;
&lt;code&gt;origin&lt;/code&gt; - refers to the GitHub repository I had added as my remote.&lt;br&gt;
&lt;code&gt;-u&lt;/code&gt; - sets an upstream relationship between my local &lt;code&gt;main&lt;/code&gt; branch and the remote &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 10: Seeing My Project on GitHub
&lt;/h3&gt;

&lt;p&gt;This was the most exciting part of the process. For a beginner like me, it was quite the journey.&lt;br&gt;
I refreshed my GitHub and I could then see my local project files online.&lt;br&gt;
I could therefore view the project online,share it with others and continue working on it.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I learnt to Avoid
&lt;/h3&gt;

&lt;p&gt;We all make mistakes as we learn and this project was no exception.&lt;br&gt;
Therefore, in order to successfully upload my local files to my GitHub repository I learnt that I should:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Not forget to initialize Git using &lt;code&gt;git init&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Not forget to stage files using &lt;code&gt;git add .&lt;/code&gt; else any changes I make will not be part of my next commit.&lt;/li&gt;
&lt;li&gt;Never assume commit means upload, to send to GitHub I ought to use &lt;code&gt;git push&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Avoid using unclear commit messages since they will not help me understand the project history easily.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;My first project taught me more than just a few commands. It taught me patience and the importance of paying attention to detail and being systematic since a command will only work efficiently if the correct  command is used. These are very important qualities for a data professional to develop so as to enhance the quality of their work.&lt;/p&gt;

&lt;p&gt;I also learnt some cool concepts such as how a version control system works, how an SSH Key can connect my PC to GitHub and  how I can manage and share my projects. &lt;/p&gt;

&lt;p&gt;As I continue developing my skills at and outside LuxDev Academy, GitHub will help me document my projects and build a portfolio that shows what I can do.&lt;/p&gt;

&lt;p&gt;This first project was the beginning of my journey towards becoming a better data professional.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>git</category>
      <category>github</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
