<?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: Brian Mugo</title>
    <description>The latest articles on DEV Community by Brian Mugo (@brian_mugo).</description>
    <link>https://dev.to/brian_mugo</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%2F4085324%2F8c07ca5a-5a62-487e-ae37-4fb12c73fa8c.png</url>
      <title>DEV Community: Brian Mugo</title>
      <link>https://dev.to/brian_mugo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/brian_mugo"/>
    <language>en</language>
    <item>
      <title>From Messy Rows to Management Decisions: Building a Power BI Solution for JCars Logistics</title>
      <dc:creator>Brian Mugo</dc:creator>
      <pubDate>Sun, 27 Sep 2026 13:21:26 +0000</pubDate>
      <link>https://dev.to/brian_mugo/-from-messy-rows-to-management-decisions-building-a-power-bi-solution-for-jcars-logistics-25jk</link>
      <guid>https://dev.to/brian_mugo/-from-messy-rows-to-management-decisions-building-a-power-bi-solution-for-jcars-logistics-25jk</guid>
      <description>&lt;p&gt;JCars Logistics sells and delivers vehicles across Kenya. Someone handed me one flat CSV, 276 rows and 32 columns, and asked for three things: an executive dashboard, a detailed report, and recommendations backed by the data.&lt;br&gt;
The dataset had been deliberately messed up. No column could be trusted at face value, and that turned out to be the actual point of the exercise.&lt;br&gt;
This is the story of how it went, in the order it actually happened: understand, audit, clean, validate, model, calculate, visualise, and investigate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Figuring out what a row even means
&lt;/h2&gt;

&lt;p&gt;Before touching anything, I had to answer one question: what does a single row represent?&lt;br&gt;
It turned out to be one sales order, not one vehicle. Units sold ranged from 1 to 5 on a single row, which means a customer could buy several units of the same car in one transaction, and the file never broke that down into individual line items.&lt;br&gt;
That single fact shaped a lot of later decisions. COUNTROWS tells you how many orders happened. SUM(Units Sold) tells you how many cars were sold. Mix those two up, and every number downstream is quietly wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auditing before touching anything
&lt;/h2&gt;

&lt;p&gt;I went column by column looking for missing values, inconsistent formatting, mismatched categories, and business rule violations. A few findings jumped out right away:&lt;br&gt;
Order ID, the supposed primary key, had 19 blanks and 3 duplicates. Sales Rep names appeared 30 different ways for only 10 actual people (double spaces, first name only entries, you name it). Monetary columns mixed four currencies (KES, USD, EUR, ZAR), sometimes with symbols, sometimes with codes, sometimes with nothing at all. Dates arrived in at least seven distinct formats, including Excel serial numbers, and one outright invalid value (2026-13-04) that showed up identically on five unrelated orders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Currency
&lt;/h2&gt;

&lt;p&gt;The assessment gave one rule for free: unmarked values are KES. Everything else had to be converted using a documented, fixed set of rates (USD = 130, EUR = 150, ZAR = 7.5 KES).&lt;br&gt;
Rather than trust the conversion blindly, I validated it. For every foreign currency price, I checked whether Units × Price × (1 − Discount) + Fee matched the row's own recorded revenue. 13 of 17 tied out within half a percent, which was solid enough evidence that both the rates and my currency detection were reasonable.&lt;br&gt;
That same validation technique caught something I did not expect: two cells that were created by my own cleaning logic. A raw unit cost of "0.0M" came out the other side as a flat 1,000,000, not because of anything in the data, but because a multiplication step defaulted to the exchange rate itself when the input was blank. Same story for a logistics cost that became 8 instead of roughly 44,000, because the currency strip logic handled "R" but never handled "ZAR". Both got fixed once the pattern was understood. Both are the kind of bug that's invisible unless you specifically go looking for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reconstructing what looked unrecoverable
&lt;/h2&gt;

&lt;p&gt;19 missing order IDs looked like a dead end. Then I noticed something about the raw file: valid IDs ran in strict sequence, 1000 plus row position, and this held for 254 of 257 of them. That meant every missing ID, and even the 3 wrong duplicates, could be rebuilt with confidence from row order alone.&lt;br&gt;
There was one condition for this to work. Row order had to survive untouched until the ID was generated, which meant adding an Index column immediately after headers were promoted, before a later sort step in the query would have destroyed it.&lt;br&gt;
Dates got a similar treatment. Ambiguous DD/MM vs MM/DD values were resolved not by guessing, but by testing which reading produced a plausible delivery lag (0 to 60 days) against the paired date. Where neither reading worked, I did not force one. Those rows got flagged for review instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deciding what not to fix
&lt;/h2&gt;

&lt;p&gt;Not every irregularity has a defensible fix. Two selling prices sat at roughly 10 times the normal price for their model. One delivery fee was 5 times the next highest genuine value. I had no independent way to confirm what the correct values should have been, so instead of guessing, I left them as recorded and built a flag column to mark them for exclusion from price sensitive analysis.&lt;br&gt;
The same approach was applied to a handful of negative discounts that had been silently stripped of their sign during cleaning. One of them reconciled cleanly against its own recorded revenue once the negative sign was restored. The rest did not reconcile under either sign, so they stayed flagged rather than corrected.&lt;br&gt;
This turned into a single Conflict Flag column threading through the whole model. Every unresolved judgement call lives in one place, so later DAX measures can deliberately include or exclude those rows instead of silently inheriting whatever the raw data happened to say.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validation
&lt;/h2&gt;

&lt;p&gt;Cleaning data does not make it correct. I tested every order's recorded revenue against what its own components implied. 140 of 202 testable rows tied out within half a per cent.&lt;br&gt;
The remaining 62 did not, and they failed in some clean, explainable patterns: exactly half, exactly double, exactly 10% off. That points to a systematic error somewhere rather than 62 unrelated mistakes, but not one I could pin down from the data alone. So that uncertainty is now a documented, flagged part of the model. Not something quietly resolved one way or the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modelling
&lt;/h2&gt;

&lt;p&gt;The temptation with a flat file is to build a flat report. Instead, I split it into a proper star schema: one fact table (Fact_Orders, at order grain) and eight dimension tables (Customer, Sales Rep, Vehicle, Branch, Payment, Delivery Status, Lead Source, Date).&lt;br&gt;
The test I used for every column was simple: does this stay the same when I filter down to one entity, or does it vary?&lt;br&gt;
Unit cost looked like it might belong on the vehicle dimension until I checked. Every single Prado TX order in the dataset had a different unit cost. That is not a vehicle attribute; that is a transaction-level fact, and it stayed on Fact_Orders. The same check ruled out a "Costs" dimension and a "Feedback" dimension entirely.&lt;br&gt;
Dates got special handling: one Dim_Date table, related to Order Date as the active relationship and to Delivery Date as an inactive one, switched on inside specific measures via USERELATIONSHIP whenever delivery based trends were actually needed.&lt;br&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%2Ffazx9vo075kfd51j4qny.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%2Ffazx9vo075kfd51j4qny.png" alt="Model View" width="799" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  DAX
&lt;/h2&gt;

&lt;p&gt;Thirty plus measures later, I went back and specifically re-audited every one for a subtle class of bug: do the numerator and denominator actually draw from the same set of rows?&lt;br&gt;
Total Cost failed this test. It was written as SUMX(Fact_Orders, Units Sold × Unit Cost). Looks correct, but it summed costs across every row that had a cost value, while realised revenue summed across a different set of rows that had a revenue value. 40 rows contributed real cost with zero matching revenue, dragging gross profit into negative territory in aggregate, even though individual transaction margins were fine.&lt;br&gt;
The fix was to filter Total Cost down to only rows with usable revenue on the same row. The same discipline I had already applied to the cleaning phase, just now applied to the calculation layer.&lt;br&gt;
Two smaller measures had the identical issue (Average Order Value, Logistics Cost % of Revenue). Caught the same way, by explicitly checking row set alignment rather than trusting that a measure "looked right" because the syntax was valid.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dashboard and deciding what not to show
&lt;/h2&gt;

&lt;p&gt;The Executive Dashboard ended up as five KPIs, a trend, a branch comparison, and a deliberate "requires attention" row. Not an attempt to cram every measure onto one page. Everything else lives on five detail pages, reachable via drill-through from a branch bar or a flagged order straight into an Investigation page built specifically around the Conflict Flag column.&lt;br&gt;
What this project actually tested&lt;br&gt;
Not whether I could make a chart. Whether I could tell the difference between a value that's wrong, a value that's merely unusual, and a value that's genuinely unknowable from the data available, and build a model honest enough to keep those three categories distinct all the way through to the dashboard.&lt;br&gt;
Rather than quietly picking one interpretation and hiding the uncertainty.&lt;/p&gt;

&lt;p&gt;The whole project has been uploaded and you can view and follow it up on my github &lt;a href="https://github.com/briankamanda/JCARS-Logistics-Power-BI" rel="noopener noreferrer"&gt;Github project&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>data</category>
      <category>dax</category>
      <category>learning</category>
    </item>
    <item>
      <title>POWER BI: Data Modelling, Relationships &amp; Joins</title>
      <dc:creator>Brian Mugo</dc:creator>
      <pubDate>Tue, 15 Sep 2026 09:30:25 +0000</pubDate>
      <link>https://dev.to/brian_mugo/power-bi-data-modelling-relationships-joins-4dfn</link>
      <guid>https://dev.to/brian_mugo/power-bi-data-modelling-relationships-joins-4dfn</guid>
      <description>&lt;p&gt;Building a good Power BI report is not just about visuals; anyone can drag a pie chart or a column bar chart onto a report. What really separates a working report from one that fails often lies beneath the visuals, and that is the data model. This article walks through the four pillars of Power BI data design: data modelling schemas, fact and dimension tables, relationships and filter direction, and joins in Power Query.&lt;/p&gt;

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

&lt;p&gt;Data modelling is the process of organising the tables in your dataset, defining how they connect, and shaping them so that analysis becomes natural rather than forced. This usually happens in two places: Power Query, where raw data is cleaned and shaped before loading, and the Model view in Power BI Desktop, where tables are linked by relationships. &lt;/p&gt;

&lt;p&gt;This matters for a few very practical reasons:&lt;br&gt;
&lt;strong&gt;Reporting accuracy&lt;/strong&gt;. A poorly structured model makes it easy to double count values or filter incorrectly without realising it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DAX simplicity&lt;/strong&gt;. Measures written against a clean model are short and easy to reason about. Measures written against a messy model often need extra logic just to compensate for the structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt;. Power BI's engine compresses and stores data column by column. A model with fewer, narrower, well structured tables compresses better and queries faster than one wide table full of repeated text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scalability&lt;/strong&gt;. A model that works with fifty thousand rows today needs to still work when it grows to five million rows next year. Structure is what allows that growth without a rebuild.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maintainability&lt;/strong&gt;. When a business rule changes (say, a product category gets renamed), you want to update it in one place, not chase it through thousands of duplicated rows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analysis&lt;/strong&gt;. Clear relationships between tables mean you write simpler formulas and avoid messy, massive joins which make analysis of data simpler and faster.&lt;/p&gt;

&lt;p&gt;I will compare three modelling approaches in this article:&lt;br&gt;
&lt;strong&gt;Flat Table&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Star Schema&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Snowflake Schema&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Flat Table
&lt;/h3&gt;

&lt;p&gt;This is a single, wide table that holds everything, such as customer_id, product_id, date and price, etc., all side by side in one table with the rows repeating whatever information it belongs to.&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%2Fx6z4g3u5qcgm3x2mkw6c.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%2Fx6z4g3u5qcgm3x2mkw6c.png" alt="Flat Table" width="799" height="164"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Advantages
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;It is simple to understand and build for a one-off report.&lt;/li&gt;
&lt;li&gt;There are no DAX calculations needed for basic slicing.&lt;/li&gt;
&lt;li&gt;No relationships to manage or configure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Disadvantages
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Deteriorating performance as the table grows into maybe millions of rows.&lt;/li&gt;
&lt;li&gt;Massive repetition of customer names and city as they are repeated on every single line for that customer.&lt;/li&gt;
&lt;li&gt;Harder to maintain, especially if a detail changes, since it may need to be changed across thousands of rows.&lt;/li&gt;
&lt;li&gt;No clear home for shared logic, such as a calendar, and no natural way to handle facts of different grains.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This table is appropriate in the following scenarios: tiny datasets, quick proofs of concept, or single-purpose extracts. It is rarely appropriate for a real business intelligence solution.&lt;/p&gt;

&lt;p&gt;A flat table looks simple but scales badly. Power BI's compression engine works best on narrow, repetitive columns, and a flat table full of repeated text values fights against that. There is also no natural way to build clean relationships later without going back and rebuilding the data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Star Schema
&lt;/h3&gt;

&lt;p&gt;The model places one central fact table in the middle, surrounded by dimension tables, each connected directly to the fact table. When sketched, it looks like a star, hence the name. &lt;/p&gt;

&lt;p&gt;The table is &lt;strong&gt;structured&lt;/strong&gt; in a way that each dimension holds descriptive attributes and its own primary key. Every dimension connects straight to the fact table with a one-to-many relationship.&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%2Fnaicxszsrh82csyejd83.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%2Fnaicxszsrh82csyejd83.png" alt="Star Schema" width="800" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Advantages
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Easy for business users to read and navigate.&lt;/li&gt;
&lt;li&gt;Very DAX friendly since filters travel in a short, predictable path.&lt;/li&gt;
&lt;li&gt;Scales well as new dimensions or facts slot in cleanly.&lt;/li&gt;
&lt;li&gt;Compresses and performs well, since dimensions hold the repeated text and the fact table holds mostly numbers and keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Disadvantages
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Requires some upfront design work to separate facts from dimensions correctly.&lt;/li&gt;
&lt;li&gt;Some duplication still exists inside dimension tables (for example, product name and category repeating per product).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When it is appropriate&lt;/strong&gt;: This is the default recommended approach for the vast majority of Power BI projects, from small departmental reports to large enterprise models.&lt;/p&gt;

&lt;p&gt;Star schemas generally align well with how Power BI's analytical engine works, making them a strong default for reporting models.&lt;/p&gt;

&lt;h3&gt;
  
  
  Snowflake Schema
&lt;/h3&gt;

&lt;p&gt;This is a star schema taken one step further where the dimension tables are split further into smaller related tables. A product dimension might be broken into product, subcategory and category tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structure&lt;/strong&gt;. A fact table connected to dimension tables, some of which are further connected to smaller sub-dimension tables, forming a branching, snowflake-like shape.&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%2F0v8zom5fbut4k3qi68zu.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%2F0v8zom5fbut4k3qi68zu.png" alt="Snowflake Schema" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Advantages
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Reduces duplication even further than a star schema, since shared attributes (like category names) live in one place.&lt;/li&gt;
&lt;li&gt;Can mirror a normalised source database closely, which is sometimes convenient during a migration&lt;/li&gt;
&lt;li&gt;Attributes are grouped logically, which can suit very large attribute sets&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Disadvantages
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;More tables and relationships to manage, hence a harder navigation&lt;/li&gt;
&lt;li&gt;More complex for report authors and also slightly heavier DAX in some cases&lt;/li&gt;
&lt;li&gt;The model view starts to look cluttered, which hurts readability for the next developer.&lt;/li&gt;
&lt;li&gt;Can hurt performance if overused, because Power BI has to resolve more relationship hops.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This type of table is &lt;strong&gt;appropriate&lt;/strong&gt; when a dimension is large and shared across many other dimensions or when strict normalisation is a hard requirement.&lt;/p&gt;

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

&lt;p&gt;After committing to one schema (star or snowflake) everything now depends on correctly telling facts and dimensions apart.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fact Table
&lt;/h3&gt;

&lt;p&gt;These store measurable business events; they are the things that happened: sales, orders, and shipments. Facts are the verbs of the business: what happened, how much, and how many. These tables typically contain:&lt;br&gt;
a) Foreign keys pointing to the relevant dimension tables (productid, customerid, etc.)&lt;br&gt;
b) Measures which are numeric values you want to sum, average or count, such as cost, quantity or revenue.&lt;br&gt;
They are usually long and narrow; they can have millions of rows but few columns.&lt;br&gt;
An important concept is the grain, also referred to as granularity. This is what one row represents. It might be one row per order line, one row per individual transaction, or one row per day per product. Getting the grain wrong, or mixing grains in the same table, is one of the most common causes of confusing totals in a Power BI report. Every measure you write should be interpreted correctly at the grain the table was built at. Examples of fact tables include FactSales, FactOrders and FactTransactions.&lt;/p&gt;

&lt;h4&gt;
  
  
  Dimension Table
&lt;/h4&gt;

&lt;p&gt;A dimension table stores the descriptive attributes that give the fact table its context and meaning. These are the who, what, where and when of the business event. Dimension tables typically contain:&lt;br&gt;
a) A primary key that uniquely identifies each row (CustomerID, ProductID, DateKey).&lt;br&gt;
b) Descriptive attributes, such as names, categories, regions and other text or classification fields that do not change with every transaction.&lt;br&gt;
They are usually short and wide compared to fact tables and may have few rows but very many columns.&lt;br&gt;
Common examples of dimension tables include DimCustomer, DimProduct, DimDate and DimLocation.&lt;/p&gt;

&lt;p&gt;For example&lt;br&gt;
Picture a retail business selling products to customers across different cities. Each sale is linked to a customer, a product, a date, and a store, while the Sales Fact table records the actual transaction. The surrounding dimension tables then provide the context needed to analyse those sales by customer, product, time, and location.&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%2Fj9ewhgkf6sk79ytumnum.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%2Fj9ewhgkf6sk79ytumnum.png" alt="Retail Sale Example" width="799" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The image shows a retail star schema with the Sales Fact table at the centre. It connects to four dimension tables: Date Dimension, Customer Dimension, Store Dimension, and Product Dimension, which provide the details needed to understand each sale by customer, product, time, and location.&lt;/p&gt;

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

&lt;p&gt;Real business data lives in separate tables. Sales may be in one system, customers in another and products in the third. A relationship in Power BI is a connection link between two tables based on a shared column, which tells Power BI to match rows in one table to rows in another table. Without relationships we would have to merge everything in a single table, which is exactly what we want to avoid. Also selecting a product category would have no effect on the sales number at all. The relationship is what makes filtering, aggregating, and cross-table DAX calculations possible in the first place.&lt;/p&gt;

&lt;p&gt;Relationships are in cardinalities. Cardinality describes the shape of the relationship, specifically how many matching rows exist on each side.&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%2Fwhsgo5gdp6klia0rdmp4.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%2Fwhsgo5gdp6klia0rdmp4.png" alt="Relationship Types" width="800" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-to-Many (1:*)&lt;/strong&gt;. This is the most common relationship. One row on the dimension side matches many rows on the fact side. One customer places many orders; one product appears on many order lines. The primary key sits on one side, the foreign key on the other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-to-One (1:1)&lt;/strong&gt;. Each row on one side matches exactly one row on the other, typically because two tables describe the same entity, such as employee and person, country and country flag, or employee and employee parking pass. It is used sparingly, usually when a set of columns has been deliberately split into a second table for organisational or security reasons. If the two tables could easily be one, then they should be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Many-to-Many (&lt;em&gt;:&lt;/em&gt;)&lt;/strong&gt;. Many rows on one side can match many rows on the other. A student may enrol in many courses, and a course has many students. This is run well in Power BI but should be used carefully since it can lead to unexpectedly duplicated results if the underlying business logic is not well understood. It is safer to introduce a bridging table that breaks it into a one-to-many relationship instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Primary Keys, Foreign Keys and Related Concepts
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Primary Key&lt;/strong&gt;. A column (or set of columns) that uniquely identifies each row in a table such as CustomerID is the primary key of DimCustomer. It should uniquely identify each row and should not have blank values.&lt;br&gt;
&lt;strong&gt;Foreign Key&lt;/strong&gt;. A column in one table that references a primary key in another, such as a customerID, can appear in FactSales, but there it is a foreign key and is expected to repeat many times.&lt;br&gt;
&lt;strong&gt;Unique Values&lt;/strong&gt;. A primary key column must contain unique values for a relationship to behave predictably. This is exactly why CustomerID is unique in Dim Customer but appears repeatedly in FactSales.&lt;br&gt;
&lt;strong&gt;Cardinality&lt;/strong&gt;. As covered above, this defines the shape of the match between the two sides of a relationship.&lt;br&gt;
&lt;strong&gt;Referential integrity&lt;/strong&gt;. The assumption that every foreign key value actually exists on the primary side. Orphan keys (a sale pointing to a customer who is not in DimCustomer) cause blank rows or missing matches unless you clean them in Power Query.&lt;br&gt;
&lt;strong&gt;Active and inactive relationships&lt;/strong&gt;. A table pair can have more than one relationship, but only one can be active (the solid line). The others stay inactive (dashed lines) and are only used on demand with DAX functions such as USERELATIONSHIP.&lt;/p&gt;

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

&lt;p&gt;When a user clicks a slicer, Power BI does not move any data. It sends a filter through the relationship lines to decide which rows of the fact table take part in the calculation. Direction controls where that filter is allowed to go.&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%2Fs32fybj8ixlfa2hdfm5a.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%2Fs32fybj8ixlfa2hdfm5a.png" alt="Filter Direction" width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Single Direction Filtering
&lt;/h4&gt;

&lt;p&gt;In a standard one-to-many relationship, filters flow from the "one" side to the "many" side by default. This means selecting a value in a dimension table filters the connected fact table, but not the other way around. &lt;/p&gt;

&lt;h4&gt;
  
  
  Bidirectional Filtering
&lt;/h4&gt;

&lt;p&gt;Filters travel both ways. This enables features such as slicers cross-highlighting each other, and it can make visuals feel interactive. However, it should be used carefully for two main reasons: &lt;strong&gt;ambiguous filter paths&lt;/strong&gt;, i.e., two competing routes from a dimension to a fact table, leaving the engine unsure which to use, which produces warnings and unreliable results. &lt;strong&gt;Unnecessary model complexity&lt;/strong&gt;: Turning on bidirectional filtering everywhere, just in case, makes a model much harder to reason about and can quietly slow down report performance since Power BI now has more filter paths to evaluate for every single visual.&lt;/p&gt;

&lt;p&gt;Always leave relationships as single-direction by default, and only switch a specific relationship to bidirectional when there is a clear, tested reason to do so.&lt;/p&gt;

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

&lt;p&gt;A join, inside Power Query, is a way of combining two tables based on matching values in a shared column, using the Merge Queries feature. Unlike a model relationship, a Power Query join actually produces a new, physically combined table as an output of the query.&lt;br&gt;
To demonstrate each join type, imagine two simple tables:&lt;/p&gt;

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

&lt;p&gt;CustomerID  Name&lt;br&gt;
1   Neal&lt;br&gt;
2   Joe&lt;br&gt;
3   Cruz&lt;/p&gt;

&lt;p&gt;Orders&lt;/p&gt;

&lt;p&gt;OrderID CustomerID  Amount&lt;br&gt;
101 1   500&lt;br&gt;
102 1   250&lt;br&gt;
103 4   800&lt;br&gt;
Notice that CustomerID 4 in orders has no match in Customers, and CustomerID 3 (Cruz) in Customers has no matching order. This mismatch is exactly what makes the different join types behave differently.&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%2Fyxtf1glugq86gj3535af.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%2Fyxtf1glugq86gj3535af.png" alt="Join Types" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inner Join&lt;/strong&gt;&lt;br&gt;
Keeps only the rows where the join column has a match in both tables. Records retained: Only customers who have at least one matching order and only orders that have a matching customer. Example output: Neal's two orders (CustomerID 1) appear, matched with his name. Cruz and the CustomerID 4 order are both dropped, since neither has a match on the other side&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Left Outer Join&lt;/strong&gt;&lt;br&gt;
Keeps every row from the first (left) table and attaches matching data from the second table where it exists. Records retained: All of the customers, plus matched order details where available. Example output: Neal appears twice (once per order), Joe appears with no order data attached, and Cruz appears with blank order fields, since she has no matching order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Right Outer Join&lt;/strong&gt;&lt;br&gt;
The mirror image of a left join. Keeps every row from the second (right) table and attaches matching data from the first table where it exists. Records retained: All of the orders, plus matched customer details where available. Example output: All three order rows appear. The CustomerID 4 order shows up with blank customer fields, since no customer with that ID exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full Outer Join&lt;/strong&gt;&lt;br&gt;
Keeps every row from both tables, matching them where possible and leaving blanks where there is no match on either side. Records retained: Everything from Customers and everything from Orders combined. Example output: Neal's two orders, Joe with no order, Cruz with no order, and the unmatched CustomerID 4 order, all appear in the same result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Left Anti-Join&lt;/strong&gt;&lt;br&gt;
Keeps only the rows from the left table that have no match at all in the right table. Records retained: Customers with zero orders. Example output: Only Cruz appears, since she is the only customer with no matching order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Right Anti-Join&lt;/strong&gt;&lt;br&gt;
The mirror image. Keeps only the rows from the right table that have no match in the left table. Records retained: Orders with no matching customer. Example output: Only the CustomerID 4 order appears, since that customer does not exist in the Customers table.&lt;br&gt;
Anti-joins are useful for data quality checks, for example, finding orphaned records or verifying that referential integrity holds before loading data into a star schema.&lt;/p&gt;

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

&lt;p&gt;Beginners often ask why they should bother with relationships when Merge Queries can combine tables. The two operations live at different stages of the workflow and solve different problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Merge physically combines tables. Relationships connect tables logically
&lt;/h3&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%2Fujqqjghk6w7ecvaxropz.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%2Fujqqjghk6w7ecvaxropz.png" alt="Merge vs Relationship" width="799" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Power Query merge physically combines data&lt;/strong&gt;. Columns from the second table are copied into the first, row by row. If the key repeats, rows multiply, and the result is one wider table loaded into the model. It happens during data preparation, before anything reaches the report.&lt;br&gt;
&lt;strong&gt;A model relationship does not combine anything&lt;/strong&gt;. Tables stay separate in memory, each holding its own data at its own grain. The relationship is a logical line the engine follows at query time to filter and aggregate across tables. It happens after loading, in the Model view.&lt;/p&gt;

&lt;p&gt;A merge makes sense when you specifically need a flattened, denormalised table for a particular purpose, for example, preparing a lookup table for a specific export or doing a one-off data quality comparison.&lt;br&gt;
However, merging tables together aggressively, as a habit, slowly turns a clean star schema back into something closer to a flat table. Every unnecessary merge reintroduces duplication, increases file size, and removes the very separation between facts and dimensions that made the model efficient and easy to filter in the first place. Keeping fact and dimension tables separate is preferable in a BI model because each table has one job, one grain, and one place to maintain each attribute. Relationships give you the analytical connection without the physical duplication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommended Power BI Model
&lt;/h2&gt;

&lt;p&gt;For the majority of real-world business intelligence projects, a star schema is the right default choice, with one-to-many relationships flowing from each dimension to the fact table and single-direction filtering used everywhere unless a specific, tested scenario genuinely requires otherwise.&lt;/p&gt;

&lt;p&gt;The reasoning:&lt;br&gt;
&lt;strong&gt;Query and report performance&lt;/strong&gt;. Star schemas are what Power BI's VertiPaq engine is built to optimise. Short relationship paths mean faster filter resolution than either a flat table (which cannot filter at all in the same way) or a snowflake schema (which adds extra hops).&lt;br&gt;
&lt;strong&gt;DAX simplicity&lt;/strong&gt;. With single-direction relationships, measures like Total Sales, Sales YTD and Average Order Value stay short and readable. Ambiguity disappears, so there is nothing to debug.&lt;br&gt;
&lt;strong&gt;Model readability&lt;/strong&gt;. Anyone opening the model view can see, at a glance, what the fact table is and what each dimension describes. A flat table hides this entirely, and a snowflake schema spreads it across more tables than necessary.&lt;br&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;. A star schema handles growth gracefully. Adding a new dimension, or millions more fact rows, does not require restructuring anything.&lt;br&gt;
&lt;strong&gt;Model complexity&lt;/strong&gt;. A star schema keeps the number of relationships and hops to a minimum, which keeps the whole model easier to test, document and hand over to someone else later.&lt;/p&gt;

&lt;p&gt;A snowflake schema still has its place, particularly when a dimension is unusually large and shared across many other dimensions, or when a project has a hard requirement to mirror a normalised source system closely. A flat table can be acceptable for a genuinely small, one-off, throwaway piece of analysis. But as a general-purpose foundation for a Power BI solution meant to grow, get shared, and get trusted by a business, the star schema, built with one-to-many relationships and single-direction filtering as the default, remains the most balanced and dependable choice.&lt;/p&gt;

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

&lt;p&gt;In Power BI, data modelling is more than just an intellectual exercise; it's what makes a report trustworthy or untrustworthy. Use Power Query merges just for true data preparation, link everything using one-to-many single-direction connections, keep the fact table short and honest about its grain, let dimensions represent the business, and let the star schema handle the hard lifting. If you do this, DAX becomes easier to write, performance becomes easier to manage, and the model can still make sense to the person who inherits it in two years.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>data</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Jumia Product Performance Analysis: From Raw Data to an Excel Dashboard</title>
      <dc:creator>Brian Mugo</dc:creator>
      <pubDate>Mon, 07 Sep 2026 03:47:03 +0000</pubDate>
      <link>https://dev.to/brian_mugo/jumia-product-performance-analysis-from-raw-data-to-an-excel-dashboard-53i1</link>
      <guid>https://dev.to/brian_mugo/jumia-product-performance-analysis-from-raw-data-to-an-excel-dashboard-53i1</guid>
      <description>&lt;p&gt;I recently completed a data analysis project using a Jumia product dataset, and honestly, it looked pretty simple when I first started.&lt;br&gt;
The goal was to take the raw product data, clean it, find some useful patterns and then turn everything into an interactive Excel dashboard.&lt;br&gt;
What I learned pretty quickly is that the dashboard is actually one of the last things you should worry about. There is quite a bit that needs to happen before you get there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting With the Data
&lt;/h2&gt;

&lt;p&gt;The dataset had 115 product records with information such as product name, current price, old price, discount, reviews and ratings.&lt;br&gt;
Before doing any analysis, I had to clean the data.&lt;br&gt;
There were duplicate records, inconsistent rating formats, missing ratings and reviews, negative review values, spelling issues, extra spaces and even a product where the price was given as a range instead of one value.&lt;br&gt;
I kept the original data untouched and worked on a separate cleaned dataset. I also recorded the cleaning decisions because I wanted to be able to explain what I changed and why.&lt;br&gt;
One thing I was careful about was missing values. A product with a missing number of reviews or rating does not mean it has zero reviews or ratings, so I did not just replace those values with zero but rather noted that it had missing reviews/ratings.&lt;br&gt;
After cleaning the data and removing duplicates, I had 112 products for the main analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Revelation?
&lt;/h2&gt;

&lt;p&gt;One of the questions I wanted to answer was whether bigger discounts resulted in more customer engagement.&lt;br&gt;
The answer was not what I expected.&lt;br&gt;
The correlation between discount percentage and reviews was approximately negative 0.17. That is a weak negative relationship.&lt;br&gt;
Medium discount products averaged about 15.9 reviews, while high discount products averaged about 10.8 reviews.&lt;br&gt;
So based on this dataset, bigger discounts did not seem to result in more reviews.&lt;br&gt;
I also looked at product price and ratings. High price products had an average rating of about 4.08, compared with 3.64 for low price products.&lt;br&gt;
However, the correlation between price and rating was only about 0.11. So although there was a difference in the average ratings, the overall relationship was still weak.&lt;br&gt;
This was a good reminder for me that seeing two things move together does not automatically mean there is a strong relationship between them.&lt;/p&gt;

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

&lt;p&gt;Once the cleaning and analysis was done, I used Excel PivotTables, PivotCharts and slicers to build the dashboard.&lt;br&gt;
The dashboard brings together product ratings, reviews, discounts, prices, engagement and product rankings.&lt;br&gt;
I also added slicers for rating category, discount category and price category so the data could be explored from different angles.&lt;br&gt;
The main point was not just making something that looked nice. I wanted the dashboard to make it easier to look at the data and actually understand what was going on.&lt;/p&gt;

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

&lt;p&gt;This project taught me that data analysis is not just about making charts.&lt;br&gt;
A lot of the real work happens before you even get to the dashboard.&lt;br&gt;
Cleaning the data, checking formulas, deciding how to handle missing values and questioning the results all turned out to be important.&lt;br&gt;
I also became more careful about correlation. A correlation can tell you that two variables have some kind of relationship, but it does not mean that one caused the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;I started with a messy spreadsheet and ended up with a cleaned dataset, analysis and an interactive Excel dashboard.&lt;br&gt;
It was a really useful practical exercise and another step in my journey into data science.&lt;br&gt;
I am still learning, and I know there is a lot more for me to learn, but projects like this are helping me understand how everything connects.&lt;br&gt;
You clean the data, ask questions, analyse the results and then try to communicate what the data is actually saying.&lt;br&gt;
And sometimes the hardest part of a data project is not building the dashboard.&lt;br&gt;
It is making sure you can trust the data behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Project
&lt;/h2&gt;

&lt;p&gt;You can view the complete project and documentation, on my GitHub repository &lt;a href="https://github.com/briankamanda/jumia-product-performance-dashboard" rel="noopener noreferrer"&gt;Jumia Product Performance Dashboard&lt;/a&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>career</category>
      <category>learning</category>
      <category>github</category>
    </item>
    <item>
      <title>Getting Started with Excel for Data Analytics: From Basics to Data Cleaning</title>
      <dc:creator>Brian Mugo</dc:creator>
      <pubDate>Fri, 28 Aug 2026 08:05:45 +0000</pubDate>
      <link>https://dev.to/brian_mugo/getting-started-with-excel-for-data-analytics-from-basics-to-data-cleaning-280</link>
      <guid>https://dev.to/brian_mugo/getting-started-with-excel-for-data-analytics-from-basics-to-data-cleaning-280</guid>
      <description>&lt;p&gt;&lt;strong&gt;Data analysis&lt;/strong&gt; is the process of examining, cleaning, transforming, and interpreting data to find useful information required for decision-making. In simple terms, we can say that it is the process of taking raw data, organising it, and finally turning it into useful information for better insight.&lt;br&gt;
It is important to prepare raw data since if you try to interpret messy data, you will also get misleading results. Raw data usually is inconsistent, inaccurate, and not well structured. Common issues with raw data might include, but are not limited to, &lt;em&gt;duplicate records&lt;/em&gt;, &lt;em&gt;incorrect dates&lt;/em&gt;, &lt;em&gt;incorrect data types&lt;/em&gt;, &lt;em&gt;missing values&lt;/em&gt;, and &lt;em&gt;incorrect spellings&lt;/em&gt;, among others.&lt;br&gt;
Excel is especially useful for beginners since it provides an easy and accessible way to learn fundamental concepts for analysis before progressing to more advanced tools that require one to have programming knowledge, such as &lt;strong&gt;SQL, R&lt;/strong&gt; and &lt;strong&gt;Python&lt;/strong&gt;.&lt;br&gt;
I will be demonstrating the different concepts learnt during the first week of Excel by using them to clean a raw dataset, prepare the information for analysis, identify patterns after the data has been cleaned and present the visualisation and insights I will derive from interpreting that data.&lt;/p&gt;
&lt;h2&gt;
  
  
  Excel Fundamentals for Data Analytics
&lt;/h2&gt;

&lt;p&gt;Before working with any raw dataset, it is important to understand the features of Excel that make it useful for data analysis. These fundamentals provide the foundation for data entry, organising, cleaning and presenting the data.&lt;br&gt;
I started by understanding how data is organised in cells, rows, columns and ranges. These may seem like very basic concepts, but it is very important to understand how data is organised in a &lt;strong&gt;worksheet&lt;/strong&gt;, especially when dealing with large datasets. I also learnt the different types of data such as dates, numbers, currency and text and how to properly format them. This was useful because some data looked correct at first glance before formatting, but it was actually wrong, such as dates entered as text data. &lt;strong&gt;Sorting and filtering&lt;/strong&gt; was also very important when dealing with different data types. I was able to custom sort multiple columns and filter records based on the respective values that they contained, which made it easier to examine. &lt;br&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%2Fu72qo0hv77jgp4gvx9h1.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%2Fu72qo0hv77jgp4gvx9h1.png" alt="Custom sort and filter" width="800" height="390"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Data validation&lt;/strong&gt; is another concept I found very useful, since it can be used to apply specific rules to cells or ranges such as allowing users to select only values from a predefined list. This prevents incorrect data entry when maintaining a dataset over time.&lt;/p&gt;
&lt;h2&gt;
  
  
  Quality Issues Noted in the Dataset
&lt;/h2&gt;

&lt;p&gt;I learnt all this using a dirty HR dataset that contained multiple columns such as &lt;em&gt;Employee ID, First Name, Last Name, Department, Salary, Hire Date, Bonus, Gender, Age, Marital Status&lt;/em&gt; and &lt;em&gt;Performance Score,&lt;/em&gt; among others. &lt;br&gt;
The dataset was dirty by all standards, as it contained various inconsistencies, including inconsistent employee IDs, duplicates, incorrect formats, missing values, inconsistent capitalisation, invalid values, inconsistent categories, wrong date formats, spelling errors and multiple pseudo-blank categories. This allowed me to practise almost every data cleaning technique I learnt during the week. &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%2Fsnbfxrr1r20yhev0wo9v.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%2Fsnbfxrr1r20yhev0wo9v.png" alt="Original dirty data sample" width="799" height="392"&gt;&lt;/a&gt;&lt;br&gt;
This data is very important to an organisation but cannot be used to generate useful insights. Salary comparisons between departments, performance by department, and reliably reporting the hiring dates or even marital status would be a headache using this data. Thereby, it is important that such data be cleaned so as to assist in decision-making by the management/stakeholders.&lt;/p&gt;
&lt;h2&gt;
  
  
  Preparing/Cleaning the Data
&lt;/h2&gt;

&lt;p&gt;The next step after identifying the quality issues was to prepare and clean the data before attempting any analysis with the data.&lt;/p&gt;
&lt;h3&gt;
  
  
  Inconsistent Employee IDs
&lt;/h3&gt;

&lt;p&gt;A few employee IDs were inconsistent with the rest of the data, so I had to standardise that column data. &lt;br&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%2F2c6xfmps0zem0vslqptr.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%2F2c6xfmps0zem0vslqptr.png" alt="Inconsistent Employee ID" width="799" height="120"&gt;&lt;/a&gt;&lt;br&gt;
Here, I used the &lt;strong&gt;RIGHT()&lt;/strong&gt; function to extract the rightmost characters since all the IDs had five characters at the end, hence &lt;code&gt;=RIGHT(A2, 5)&lt;/code&gt;. This worked, and it removed the &lt;strong&gt;EMP-&lt;/strong&gt; at the beginning of the employee IDs.&lt;/p&gt;
&lt;h3&gt;
  
  
  Incorrect Formats
&lt;/h3&gt;

&lt;p&gt;In the salary column there were multiple incorrect formats where some values were entered as text data and others had a wrong currency sign. I first converted those values entered as text to the numbers format and then formatted the whole column from the format in the ribbon and changed it from a &lt;em&gt;number format&lt;/em&gt; to a &lt;em&gt;currency format&lt;/em&gt;, and then I went ahead and chose the correct currency symbol, which made the column become standardised.&lt;/p&gt;
&lt;h3&gt;
  
  
  Inconsistent Capitalisation
&lt;/h3&gt;

&lt;p&gt;In the name columns, there were multiple instances where the names were inconsistent in capitalisation; some were written in all uppercase while others were written entirely in lowercase. &lt;br&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%2Firu15t2z2usagzesma44.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%2Firu15t2z2usagzesma44.png" alt="Inconsistent names" width="425" height="413"&gt;&lt;/a&gt;&lt;br&gt;
I used the text function &lt;strong&gt;PROPER()&lt;/strong&gt; and autofilled the whole columns so as to change any inconsistencies down the name columns &lt;code&gt;=PROPER(A2)&lt;/code&gt;, &lt;code&gt;=PROPER(B2)&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Invalid Values
&lt;/h3&gt;

&lt;p&gt;There were also cases of invalid values, especially in some columns such as the age and hire date columns. There were employees listed as being four years old, while others were listed as having been hired in the year 1900, which cannot be the case; therefore, I had to replace the invalid values with blanks because they appeared to be cases of incorrect data entry. This is why data validation is actually important in such scenarios since it prevents invalid future entries in the dataset.&lt;/p&gt;
&lt;h3&gt;
  
  
  Inconsistent Categories
&lt;/h3&gt;

&lt;p&gt;There were a lot of inconsistent categories in this dataset. Most of these occurred within the same columns where you would find for example, in the department column, instances of HR, H.R, Human Resources, IT, I.T. and Info Tech. In the office location column, you would find Nairobi, Nairobii, Tokio and Tokyo. These were meant to refer to the same thing but were entered differently. I therefore used find and replace to standardise the entries so that each category had a consistent representation. This made it easier whenever you wanted to filter employees by department, office location and other categorical variables. &lt;br&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%2Fm2fk12qddwwf4704oelh.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%2Fm2fk12qddwwf4704oelh.png" alt="Cleaned categories" width="800" height="699"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Duplicate Records
&lt;/h3&gt;

&lt;p&gt;The dataset also contained duplicate records where some employees appeared more than once. I used Excel's &lt;strong&gt;Remove Duplicates&lt;/strong&gt; feature under the &lt;strong&gt;Data&lt;/strong&gt; tab to identify and remove these duplicates after I checking to ensure they were actually duplicates and not just employees with matching details.&lt;/p&gt;
&lt;h2&gt;
  
  
  Analysis, Visualisation &amp;amp; Insights
&lt;/h2&gt;

&lt;p&gt;After cleaning the dataset, I could now begin analysing it with more confidence that obvious inconsistencies would not affect the results. I used formulas and pivot tables to compare the salaries in different departments such as finance and HR using &lt;strong&gt;SUMIF&lt;/strong&gt;. I went further and compared the salaries in these departments by gender using &lt;strong&gt;SUMIFS&lt;/strong&gt;. For example, I used the following formulas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=SUMIF(E2:E870,"HR",F2:F870)
=SUMIF(E2:E870,"Finance",F2:F870)
=SUMIFS(G2:G870,E2:E870,"HR",J2:J870,"F")
=SUMIFS(G2:G870,E2:E870,"HR",J2:J870,"M")
=SUMIFS(G2:G870,E2:E870,"Finance",J2:J870,"F")
=SUMIFS(G2:G870,E2:E870,"Finance",J2:J870,"M")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;SUMIF&lt;/strong&gt; formulas allowed me to calculate the total salaries for each department, while &lt;strong&gt;SUMIFS&lt;/strong&gt; allowed me to apply multiple criteria at the same time, in this case, department and gender.&lt;br&gt;
I also created pivot tables to analyse the same information, making the comparison easier to interpret visually. &lt;br&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%2F8c4kj32hdg67cca0jq9w.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%2F8c4kj32hdg67cca0jq9w.png" alt="Salary comparison by department and gender" width="800" height="264"&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%2Fi9yy8l8upuxwokp2z72l.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%2Fi9yy8l8upuxwokp2z72l.png" alt="Pivot table comparing salaries by department and gender" width="799" height="437"&gt;&lt;/a&gt;&lt;br&gt;
The analysis revealed that the HR department had a higher total salary expenditure than the finance department. It also showed that the female employees accounted for higher total salaries than the male employees in both departments. However, the HR department also had more employees overall than the finance department, which suggests that the difference in salaries between the two departments may partly be influenced by the difference in the number of employees rather than individual salaries.&lt;/p&gt;

&lt;p&gt;This analysis proved that Excel is a vital tool in analysing data since it provides a basis for further analysis. It also highlighted the importance of clean data since the analysis does not begin with creating charts or tables but rather with the preparation and cleaning of the dataset. This exercise gave me a practical understanding of how Excel can be used to transform raw data into meaningful information. Also, analysis is not just about formulas and visualisations but about asking the right questions and interpreting the results carefully.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>learning</category>
      <category>data</category>
      <category>analytics</category>
    </item>
    <item>
      <title>Understanding The Git Workflow: Working Directory, Staging, Commit and Push.</title>
      <dc:creator>Brian Mugo</dc:creator>
      <pubDate>Sun, 23 Aug 2026 19:35:47 +0000</pubDate>
      <link>https://dev.to/brian_mugo/understanding-the-git-workflow-working-directory-staging-commit-and-push-4i1n</link>
      <guid>https://dev.to/brian_mugo/understanding-the-git-workflow-working-directory-staging-commit-and-push-4i1n</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Git&lt;/strong&gt; is the most widely used &lt;em&gt;version control system&lt;/em&gt; which tracks the changes to files over a period of time. This happens in the &lt;strong&gt;working directory&lt;/strong&gt; which is where one makes changes to the files and you then move to the &lt;strong&gt;staging area&lt;/strong&gt; where you prepare the changes before committing them.&lt;br&gt;
The &lt;strong&gt;local repository&lt;/strong&gt; is where your project is stored locally on your computer whereas the &lt;strong&gt;remote repository&lt;/strong&gt; is where your project is hosted on the internet and allows collaboration by multiple people.&lt;/p&gt;
&lt;h2&gt;
  
  
  Git Workflow
&lt;/h2&gt;

&lt;p&gt;The Git workflow is now the process of moving changes from the working directory to the staging area then to the local repository and finally to the remote repository.&lt;/p&gt;
&lt;h3&gt;
  
  
  Working Directory
&lt;/h3&gt;

&lt;p&gt;This is where one creates or modifies files; for example&lt;br&gt;
creating a file: &lt;code&gt;touch README.md&lt;/code&gt; or modifying a file.&lt;br&gt;
You then run &lt;code&gt;git status&lt;/code&gt; to confirm you are in a git repository and that the README.md file now shows as an untracked file. If it says 'not a git repository' then run &lt;code&gt;git init&lt;/code&gt; first.&lt;/p&gt;
&lt;h3&gt;
  
  
  Staging Area
&lt;/h3&gt;

&lt;p&gt;Once you have made changes you then tell Git the changes that you want to include in the next commit: If you want to add a single file, use &lt;code&gt;git add filename&lt;/code&gt;, for example &lt;code&gt;git add README.md&lt;/code&gt;. To stage everything, use &lt;code&gt;git add .&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Local Repository
&lt;/h3&gt;

&lt;p&gt;Once satisfied with what you have staged you then go ahead and create a commit: &lt;code&gt;git commit -m "Add README.md"&lt;/code&gt; which creates a local commit in your repo's history.&lt;/p&gt;
&lt;h3&gt;
  
  
  Remote Repository
&lt;/h3&gt;

&lt;p&gt;Finally your local commit is ready to be sent to a remote repository such as &lt;strong&gt;GitHub&lt;/strong&gt; by pushing it: &lt;code&gt;git push&lt;/code&gt;.&lt;br&gt;
There is another command &lt;code&gt;git pull&lt;/code&gt; which brings changes from the remote repository into your local repository.&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%2Fay7yioc1xwtaby8s6ddt.jpg" 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%2Fay7yioc1xwtaby8s6ddt.jpg" alt="Git Workflow" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;When I tried pushing my first repo things did not run as smoothly as my explanation makes it seem. Because I was writing my Git commands the wrong way, I was not leaving spaces for instance &lt;code&gt;git add.&lt;/code&gt; instead of &lt;code&gt;git add .&lt;/code&gt; where they are supposed to be, other times I was forgetting the hyphens and using lowercase letters where they were supposed to be uppercase such as the example below where the first command is how I wrote it instead of writing it as the second command line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen t ed25519 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"my email address"&lt;/span&gt;
ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; ed25519 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"my email address"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These mistakes, though, made me understand that one has to get everything right when writing the Git commands. This is vital for the whole process of pushing from local repository to the remote repository and pulling from the remote repository to the local repository. If the commands are not written the right way, you will not be able to successfully complete the process.&lt;/p&gt;

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