<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Leah Nguyen on Medium]]></title>
        <description><![CDATA[Stories by Leah Nguyen on Medium]]></description>
        <link>https://medium.com/@ndleah?source=rss-7ee083e5e515------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*7AlVKpuAYuOy7wD9LLwYTg.png</url>
            <title>Stories by Leah Nguyen on Medium</title>
            <link>https://medium.com/@ndleah?source=rss-7ee083e5e515------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 16 May 2026 22:57:28 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@ndleah/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[How We Built the AWS Data & Analytics Platform (Part 1)]]></title>
            <link>https://blog.dataengineerthings.org/how-we-built-the-aws-data-analytics-platform-part-1-b4c798d17094?source=rss-7ee083e5e515------2</link>
            <guid isPermaLink="false">https://medium.com/p/b4c798d17094</guid>
            <category><![CDATA[sap]]></category>
            <category><![CDATA[data-engineer]]></category>
            <category><![CDATA[amazon-web-services]]></category>
            <category><![CDATA[data-lake]]></category>
            <category><![CDATA[aws]]></category>
            <dc:creator><![CDATA[Leah Nguyen]]></dc:creator>
            <pubDate>Fri, 11 Jul 2025 11:14:04 GMT</pubDate>
            <atom:updated>2025-07-11T23:51:52.928Z</atom:updated>
            <content:encoded><![CDATA[<h4><strong>The story of our first data platform capability</strong></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ddQPWTi9PtM-5jGq" /><figcaption>Me and my super duper awesome aka smart data team at AWS Summit Sydney 2025</figcaption></figure><p>At my current company, one of Australia’s leading beverage companies, data is at the heart of nearly everything we do. In fact, about 80% of all the data we handle comes directly from SAP systems. These SAP sources aren’t limited to just one system; we’re talking about multiple interconnected subsystems like SAP S4/HANA for core transactions, Cloud for Customer (C4C) for CRM and sales data, Integrated Business Planning (IBP) for forecasting and supply chain, SAP Concur for travel and expense management and <strong>more</strong>. Each subsystem generates critical insights, but they also present unique integration challenges.</p><p>Here’s the story of how we built a scalable, AWS-based Data &amp; Analytics (D&amp;A) platform from scratch, the architectural decisions we made, and how it all comes together behind the scenes.</p><h3>Why it is important to have a unified data platform for us?</h3><p>A few years back, our analytics setup relied primarily on business warehouse that hosted within SAP environment, which was excellent for structured, SAP-only data but struggled significantly when faced with broader integration requirements. The limitations became clear as we started receiving increasing volumes of data from non-SAP sources:</p><ul><li>Third-party vendor market research data,</li><li>Static files via SharePoint,</li><li>And more</li></ul><p>We needed a solution flexible enough to seamlessly handle structured, semi-structured, and unstructured data alike, whether sourced from SAP or elsewhere.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cIIV3ynHbZgKh3jr1SYpFw.png" /></figure><h3>Designing the right architecture</h3><p>Our initial considerations revolved around two central challenges:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/0*sJAN7FFHlIy6XWbv" /><figcaption>credit @<a href="https://dataedo.com/@piotr-kononow">Piotr Kononow</a></figcaption></figure><h4>#1 - How could we reliably ingest data from diverse sources into a centralized repository?</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/900/0*w-Sv2y-eVakhmSCz" /><figcaption>credit @<a href="https://dataedo.com/@piotr-kononow">Piotr Kononow</a></figcaption></figure><p>As I mentioned from the get-go, our team deals with SAP and non-SAP data alike, and every source comes with its own ingestion mechanism.</p><blockquote>For instance, within the SAP landscape alone, we pull transactional data from SAP <strong>S4/HANA</strong> and CRM data from SAP Cloud for Customer (<strong>C4C</strong>) using OData services, which is the API-flavoured object of SAP to make connection to other system, securely. But that’s just part of the story!</blockquote><blockquote>SAP Concur, another key SAP subsystem we rely on for travel and expense management data, doesn’t send data via API at all. Instead, we pull encrypted files directly from an SFTP server.</blockquote><blockquote>On top of that, external vendors push their market data directly to our platform. Other teams might share data with us through static files uploaded to SharePoint, while yet others store their data in simple CSV files hosted on third-party SFTP servers or being sent to us via email.</blockquote><p>Now, if the variety in ingestion mechanisms wasn’t challenging enough, the types of files we receive are just as diverse. We handle everything from strictly structured data ( csv , xlxs), to semi-structured data (think jsonor xml).</p><blockquote>One project I’m personally working on receives data in an encrypted, unstructured format called .jsonl.pgp — which, in layman terms, a JSON lines file encrypted with <strong>P</strong>retty <strong>G</strong>ood <strong>P</strong>rivacy.</blockquote><blockquote>Each unique scenario forced us to thoughtfully design our ingestion workflows. Whether we were pushing or pulling data, structured or unstructured, encrypted or plain text, we had to ensure every ingestion pipeline reliably moved data into a central <strong>Data Lake</strong>.</blockquote><h4>#2 - Once we landed data into AWS, how would we transform and prepare it efficiently for business users?</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/900/0*cWHxCv5woECKuuAg" /><figcaption>credit @<a href="https://dataedo.com/@piotr-kononow">Piotr Kononow</a></figcaption></figure><p>Now, once we successfully landed all this data into this Data Lake area, another big question remained: How do we turn this raw, often messy data into something genuinely useful and understandable for the business?</p><p>Let’s take SAP S4/HANA data as an example. If you’ve ever peeked at raw SAP tables, you probably know exactly what I mean. Technical field names can feel cryptic and confusing at best. Instead of friendly terms like “Customer ID” or “Material ID,” SAP tends to spit out table headers like kunnr for customer ID or matnr for material number.</p><p>While these abbreviations might be perfectly logical for SAP consultants or anyone fluent in German (because yes, they’re mostly acronyms based on German terminology), they can leave business users scratching their heads. I mean, no offense to our German friends, but MATNR doesn’t exactly scream “material number” if you’re not familiar with the term, right?</p><p>So part of our data processing challenge involved transforming these obscure, technical column names into clear, business-friendly language. It wasn’t enough just to land the data safely into Data Lake, we had to actively interpret and reshape it.</p><p>AWS emerged as our top choice precisely because it addressed these critical pain points. However, creating an effective architecture wasn’t as simple as plugging in a few AWS services. To handle these challenges, our platform architecture was divided into clear stages: <strong>Data Ingestion</strong>, <strong>Data Processing/Loading</strong>, and <strong>Data Warehousing</strong>.</p><p>And the story begins from here.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YMs6Fsp5wuzoXlx8x_BFeQ.png" /></figure><h3>Data Ingestion: Gathering Data from Everywhere</h3><p>The raw-data pipeline is organised around three independent state machines: <strong>Ingestion</strong>, <strong>Processing</strong>, and <strong>Loading</strong>. Each state machine runs on AWS Step Functions, which gives us deterministic task ordering, built-in retries, and centralised error handling while keeping the JSON definition of every workflow in version control.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/494/1*RTVxQBSCRiMSyHHDXTcviQ.png" /></figure><p>We manage every ingestion workflow with <a href="https://aws.amazon.com/step-functions/"><strong>AWS Step Functions</strong></a>. Each workflow is defined as a state machine that lists the exact sequence of tasks, the success criteria for each task, and the error-handling branch if any step fails.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/250/1*khLHhsCt6Bldf2no4RwCpg.png" /></figure><p>When a new load is triggered, the state machine starts by invoking an <a href="https://docs.aws.amazon.com/lambda/latest/dg/welcome.html"><strong>AWS Lambda function</strong></a> that collects connection metadata: endpoint URLs, authentication tokens, and the specific objects or tables requested for that run. The Lambda function captures the run-time metadata and passes a reference to the next state.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/271/1*AdeF5z8o4zFy36oXECuhng.png" /></figure><p>The next task in the state machine launches an <a href="https://docs.aws.amazon.com/glue/latest/dg/what-is-glue.html"><strong>AWS Glue</strong></a> job. Glue connects to the source system using the metadata provided, extracts the data, and writes the raw payload to an S3 prefix named ingested. All payloads are stored in JSON to preserve schema details exactly as received.</p><p>After Glue finishes, Step Functions routes execution to another Lambda function that validates the job result.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/250/1*BPrWSM61ZaduWNoywO6inQ.png" /></figure><p>The validator checks row counts, file size, and schema conformance, then updates an execution log in DynamoDB.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/266/1*E9kTGXrlpHh7wtS4tVZ7dw.png" /></figure><p>If validation fails or the Glue job errors or times out, the state machine switches to a failure branch that publishes an alert through <a href="https://docs.aws.amazon.com/sns/latest/dg/welcome.html"><strong>Amazon SNS</strong></a>. The alert contains the run-ID and error details so support engineers can diagnose the issue quickly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/692/1*3MVM7uiAPj5ySL-06us46A.png" /></figure><p>If validation succeeds, Step Functions marks the ingestion workflow as complete and emits a success event on <a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-what-is.html"><strong>Amazon EventBridge</strong></a>. Downstream processing pipelines subscribe to this event and begin the processing stage.</p><h4>Processing: same state machine, new purpose</h4><p>The Processing pipeline re-uses the exact Step Functions template we built for Ingestion — metadata Lambda, Glue task, completion Lambda, then a split to SNS (failure) or EventBridge (success). Nothing changes in the orchestration layer; the distinction lives entirely inside the Glue script.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/576/1*8kpO2EReVYBbr6KFv-1heA.png" /></figure><p>The script does three things:</p><ol><li>First, it scans every JSON object we just landed under the ingested/ prefix.</li><li>Second, it appends a single column, ingested_time, stamped at UTC so we can trace every record back to a specific run without touching the source system.</li><li>Third, it writes the result to the formatted/ prefix as compressed Parquet, preserving column types and order while cutting query latency on Redshift by more than half.</li></ol><p>All retry policies, timeouts, and alert routes inherit from the shared state-machine definition, which keeps operational behaviour consistent across every stage of the raw-data pipeline.</p><h4>Loading: finishing the trip in Redshift</h4><p>The third state machine, <strong>Loading</strong>, mirrors the same Step Functions skeleton we used for Ingestion and Processing : initial Lambda for run-time metadata, a single Glue task, completion Lambda, then a fan-out to SNS or EventBridge. Again, orchestration is unchanged; only the Glue logic shifts.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/757/1*Bey8ynNDeSzYTG0xsa-T0Q.png" /></figure><p>The loader starts by picking up the Parquet files we just wrote to the formatted/ prefix. The cleaned dataset is streamed directly into a staging table in Redshift staging.&lt;table_name&gt;, using Amazon Redshift’s native COPY command behind the scenes. All temp operations land in an S3 scratch directory so we avoid cluttering the warehouse with intermediate files.</p><p>Operational behaviour stays consistent with the earlier pillars. Any error triggers SNS with a run ID and the stack trace so on-call can jump straight to the failing task.</p><p>With this final load step in place, the raw-data pipeline closes its loop: JSON lands in ingested/, gets normalised to Parquet in formatted/, and arrives in Redshift staging ready for type casts, SCD logic, and business modelling. End-to-end latency for a typical source drop is now measured in minutes, not hours, and alignment across the three state machines lets us operate the stack with one shared runbook.</p><h3>Lessons Learned from Building the Raw-Data Pipeline</h3><h4>1. One state-machine per stage keeps troubleshooting narrow</h4><p>Splitting the flow into three independent Step Functions — Ingestion, Processing, Loading — gave us clean blast-radius boundaries. If Loading fails, we re-run only that state machine; Ingestion and Processing stay green. Sainsbury’s Data &amp; Analytics adopted the same approach after discovering that chaining ten Lambdas in one giant workflow made it “very difficult to find single points of failure” and polluted their S3 buckets with half-finished files</p><h4>2. Treat metadata capture as a first-class task</h4><p>Our first Lambda in every state machine writes a snapshot of connection details, source object names, and run IDs to S3. That decision paid off during audits: we can reproduce any historical load without digging through CloudWatch logs. AWS prescriptive guidance flags the same pattern as a prerequisite for reconstructing failed runs and for partitioning large data sets later.</p><h4>3. Visual orchestration beats log digging</h4><p>Step Functions’ runtime graph immediately shows which state failed (green vs red). That visibility slashed mean-time-to-diagnose during the first month of go-live. Sainsbury’s team called the visual trace “the lovely graphic” that pinpoints failing states faster than combing through Lambda logs.</p><h3>What’s Next</h3><p>Part 2 will cover how we take the raw Redshift staging tables you’ve just read about and turn them into fully modelled, Slowly-Changing-Dimension-aware business layers — with dbt. Stay tuned!</p><h3><em>Gratitude corner</em> 💙</h3><p><em>A huge thank-you to </em><strong><em>Harold</em></strong><em> and </em><strong><em>Jamie</em></strong><em>, if you guys are reading this :) hands-down the best managers a data nerd could hope for. Your support and mentorship made staying up late to finish this blog post on a Friday night feel more like a passion project.</em></p><p><em>I’m equally grateful to </em><strong><em>Gurpreet, Michael, Praveen, </em></strong><em>and </em><strong><em>Kazuma</em></strong><em> for answering every “Is this an AWS thing or am I just tired?” question on my zero-to-hero cloud journey. Couldn’t have done it or kept my sanity without all of you.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b4c798d17094" width="1" height="1" alt=""><hr><p><a href="https://blog.dataengineerthings.org/how-we-built-the-aws-data-analytics-platform-part-1-b4c798d17094">How We Built the AWS Data &amp; Analytics Platform (Part 1)</a> was originally published in <a href="https://blog.dataengineerthings.org">Data Engineer Things</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Databases, Warehouses & Lakes: A Kitchen Tour]]></title>
            <link>https://blog.dataengineerthings.org/databases-warehouses-lakes-a-kitchen-tour-2a99f175baf1?source=rss-7ee083e5e515------2</link>
            <guid isPermaLink="false">https://medium.com/p/2a99f175baf1</guid>
            <category><![CDATA[etl]]></category>
            <category><![CDATA[data-engineer]]></category>
            <category><![CDATA[database]]></category>
            <category><![CDATA[data-warehouse]]></category>
            <category><![CDATA[data-lake]]></category>
            <dc:creator><![CDATA[Leah Nguyen]]></dc:creator>
            <pubDate>Tue, 27 May 2025 11:13:01 GMT</pubDate>
            <atom:updated>2025-05-27T12:57:33.165Z</atom:updated>
            <content:encoded><![CDATA[<h4>If you know your way around a fridge, you’re five minutes from grasping modern data storage — let me show you how.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*HY0_u-dA7kWSUu5G" /></figure><blockquote>I was pushing my trolley through Woolies (short for <strong>Woolworths</strong>, Australia’s everyday supermarket) when a thought popped up between the bananas and the bread: Whenever I’m coaching newcomers to data, three terms reliably trip them up — <strong>database</strong>, <strong>data warehouse</strong>, and <strong>data lake</strong>. They appear in every blog post and architecture diagram, yet most explanations assume you were born fluent in cloud-native jargon.</blockquote><blockquote>I used to get lost, too, until I realised all three ideas match the way I store food. If you can picture the rhythm of a fridge, a pantry, and a walk-in freezer, you can picture the rhythm of modern data storage.</blockquote><blockquote>No doctorate required.</blockquote><h3>Why a kitchen in the first place?</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*7GFJXAWgojQ8Ak7Z" /></figure><p>Every household sorts food into three destinations:</p><ol><li>Milk and leftovers live in the refrigerator because breakfast happens on autopilot.</li><li>Bulk goods line orderly pantry shelves so you can see, at a glance, whether you have enough rice for the next fortnight.</li><li>The “maybe one day” supplies — frozen berries, mystery dumplings, that discounted brisket — hibernate in the deep-freeze until inspiration strikes.</li></ol><p><strong>Data travels the same route.</strong></p><p>Some pieces must be lightning-fast, some must be neatly organised for long-range analysis, and the rest simply need a cheap berth until you decide what to cook. Once you grasp those storage instincts, most LinkedIn buzzwords start translating themselves.</p><h3>The fridge: databases and the speed of breakfast</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*MnDup1weSvcQImKS" /></figure><p>A <strong>database</strong> — PostgreSQL, MySQL, SQL Server, take your pick — is the digital “refrigerator”. It handles thousands of mini-transactions every second: checking a bank balance, updating a rideshare driver’s location, or logging the precise moment you tap “Skip intro” on Netflix. It achieves that speed by enforcing a rigorous <strong>schema-on-write</strong> rule; every new row must slide neatly into a fixed table blueprint, the same way every carton in your fridge claims a specific shelf.</p><p>That structure unlocks the famous <strong>ACID guarantees:</strong> (<strong>a)</strong>tomic, (<strong>c)</strong>onsistent, (<strong>i)</strong>solated, and <strong>(d)</strong>urable changes, which is a grand way of promising your money won’t evaporate if the power cuts out mid-transfer. In short, a database keeps the essentials cool, structured, and instantly reachable, because nobody will wait five seconds for a splash of milk.</p><h3>The pantry: warehouses and the luxury of hindsight</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*7unVIinEw92dzn7y" /></figure><p>A fridge collapses the moment you stuff six months of groceries inside. A million question: “How did lager sales trend across Australia last year?” — need the breathing room of a <strong>data warehouse</strong>. Platform such as Snowflake, Redshift, BigQuery, Synapse, each organises historical facts into columnar files and spins up armies of query workers through <strong>massively parallel processing</strong>. Those soldiers race across millions of rows, returning answers in the time it takes to sip a coffee.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*GffGooqPD7LUpCHN" /></figure><p>Warehouses prefer <strong>star</strong> and <strong>snowflake</strong> schemas: a central fact table surrounded by dimensions for product, customer, date — like pantry bins labelled grains ➜ rice ➜ basmati.</p><p>Most teams restock overnight via ETL, though near-real-time streams are creeping in wherever dashboards must be as fresh as cold brew. Either way, the pantry balances cost and order so analysts can slice trends without hogging the fridge every time they run a monthly report.</p><h3>The walk-in freezer: data lakes and the luxury of raw potential</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*2__9wwT-_OsjHKvy" /></figure><p>Past the pantry door lies the <strong>data lake</strong>, a warehouse-sized freezer where anything remotely promising can be tossed — server logs, sensor feeds, high-res images, unfathomably wide CSVs. Cloud object stores such as S3 or ADLS let you dump everything first and worry about structure later, a philosophy called <strong>schema-on-read</strong>. This freedom is catnip for machine-learning teams that crave petabytes of untouched signal and for compliance officers who must archive transactions just in case regulators come knocking.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*tLfW5hVtZSdqr7bs" /></figure><p>Of course, freedom courts chaos. A lake without metadata quickly degrades into the dreaded <strong>data swamp</strong>, that murky realm of files named final_FINAL_v2.csv. Unless you tag and catalogue aggressively, you’ll spend Friday night spelunking for a single fragment you swear existed. “Lakehouse” tools—Databricks, Iceberg, Redshift Spectrum—attempt to bolt warehouse-style query engines onto those cheap ice blocks, essentially fitting the freezer with LED shelving so you can both store and cook in the same room. Clever, but still evolving.</p><h3>Choosing the right shelf for the job</h3><p>Choosing a storage layer is easier than assembling flat-pack furniture</p><ul><li>If an interaction demands instant feedback — authorising a credit-card payment — reach for the fridge.</li><li>If leadership wants a Monday-morning KPI deck, load the pantry and let its parallel engines chew through history.</li><li>Training a recommendation model on three years of clickstream chaos? Shovel everything into the freezer first, then carve smaller, curated slices back into the pantry when analysis time arrives.</li></ul><p>Trying to cram all three workloads into a single layer is like balancing ice-cream tubs on a hot stovetop — messy, stressful, and destined for tears.</p><h3>The quick reality check</h3><p>Yes, technically everything could live in the lake! But storing tonight’s butter in a chest freezer three floors down is a lifestyle choice, not a best practice. Each storage layer maximises a different balance of cost, speed, and structure. Use the right shelf for the right need, and both dinner and data arrive on schedule.</p><h3>One-minute recap</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*VVz1GElj-b7HABQf" /></figure><ul><li><strong>Database ≅ Fridge</strong> — ultra-fast, strictly organised, and designed for high-volume transactions.</li><li><strong>Warehouse ≅ Pantry</strong> — neatly modelled history that turns sprawling data into trend-friendly bites.</li><li><strong>Lake ≅ Freezer</strong> — dirt-cheap vault for raw information; label it diligently or risk a swamp.</li></ul><p>Master these three storage instincts and the rest of data-engineering lingo starts reading like everyday recipes.</p><p>Curious about the eternal ETL vs. ELT debate? That deep-dive is coming soon — tap <em>Follow</em> so it pops into your feed the minute it’s ready.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2a99f175baf1" width="1" height="1" alt=""><hr><p><a href="https://blog.dataengineerthings.org/databases-warehouses-lakes-a-kitchen-tour-2a99f175baf1">Databases, Warehouses &amp; Lakes: A Kitchen Tour</a> was originally published in <a href="https://blog.dataengineerthings.org">Data Engineer Things</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[dbt Incremental — The Right Way]]></title>
            <link>https://medium.com/data-science/dbt-incremental-the-right-way-63f931263f4a?source=rss-7ee083e5e515------2</link>
            <guid isPermaLink="false">https://medium.com/p/63f931263f4a</guid>
            <category><![CDATA[sql]]></category>
            <category><![CDATA[analytics-engineering]]></category>
            <category><![CDATA[dbt]]></category>
            <category><![CDATA[data-science]]></category>
            <dc:creator><![CDATA[Leah Nguyen]]></dc:creator>
            <pubDate>Fri, 21 Jul 2023 13:37:02 GMT</pubDate>
            <atom:updated>2023-07-24T13:42:18.830Z</atom:updated>
            <content:encoded><![CDATA[<h3>dbt Incremental — The Right Way</h3><h4>From Full-Load Pain to Incremental Gain (and a Few Mistakes Along the Way)</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*OiMdjXNJZYtqdQAG" /><figcaption>Photo by <a href="https://unsplash.com/es/@luk10?utm_source=medium&amp;utm_medium=referral">Lukas Tennie</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>When my team at GlamCorner began transitioning from traditional MySQL databases to ELT on a Postgres database with dbt as the transformation and modeling layer, we were overjoyed. We set up dbt projects and profiles, dedicated macros for our models, and built more data marts to serve downstream needs. We thought we were done — I thought we were done until we hit our first bump: model run time. In this article, I explain how I overcame one of the toughest performance challenges at the time by adopting dbt incremental, making mistakes (like who doesn’t?), and learning valuable lessons along the way.</p><h3>The Evolving Monster</h3><p>At GlamCorner, we’re in the circular fashion game. Our “back-end” team plays with RFID scanners in the warehouse, scanning items in and out like pros. We also use fancy platforms like Zendesk and Google Analytics to make our customers feel extra special. And to top it off, we’ve got our own in-house inventory system — thanks to our brilliant software engineers — that links all our front-end and back-end systems together. It’s like a match made in heaven. But as we grow and add more years of operation, our database is getting bigger and bigger. And let’s just say the traditional full-table load is starting to feel a bit like a pain in the you-know-what.</p><h3>The Pain</h3><p>You either understand the pain of “I want the data to be ready by 9am” or you don’t.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/520/1*M3MPbyz7eH3cEWqx7vfYDw.png" /><figcaption>Image by the author</figcaption></figure><p>The team’s put the efforts to create a flawless <strong>(E)</strong>xtract and <strong>(L)</strong>oad, we gather and toast. Then one day, the <strong>(T)</strong>ransformation decided like “Nah, that’s not how it works around here” and decided to spin up the total runtime from 10 minutes to 90 minutes. I may exaggerate on the 10 to 90 minutes part because yes, everything has its own reason, but the fear of the business team knocking on your door at 8.55 am in the morning when you haven’t even started your first cup of coffee, just to ask: “Where is the newest data?” is hell of the ride to work every day. This is like dumping all the hard work in the trash and I, myself, cannot accept that reality.</p><p>Let’s go back to the thing I said: everything has its own reason, and why the fairytale once was taken 10 minutes of my time now has become a red horn demon of 90 minutes. To illustrate this, let’s take the example of the <strong>fct_booking</strong> data figure. This table contains all booking information taken from the website each day. Each <strong>booking_id</strong> represents one order that was booked on the website.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TgpHehXWK_zf2RljzdrlyA.png" /><figcaption>Image by the author</figcaption></figure><p>Every day, around 4 orders are added to the booking table, which already contains 80 orders. When this model is run using dbt, it delete the entire table from the last day, replaces all of them with 84 records, including the old and new orders (80 orders from historical cumulative data + 4 new orders added for the latest day). To add to the list, for every new 4 records added, the query time increases by around 0.5 seconds.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*D7l86rco3ZrkmyW93lHOrw.png" /><figcaption>Image by the author</figcaption></figure><blockquote>Now, imagine that 4 orders are equivalent to 4000 per day and 80 orders actually represent 800,000 records. Can you guess how much time it will take to transform the fct_bookings table, and where we will be in, for example, 3 months?</blockquote><blockquote><strong>Well,</strong> <strong>I’ll leave the math for you.</strong></blockquote><h3>The Golden Egg</h3><p>So, after aimlessly wandering through dbt Community threads and halfheartedly skimming through dbt documentation (I mean, who hasn’t done that?), I stumbled upon the holy grail of dbt Incremental. It’s like finding a needle in a haystack, except the needle is golden and the haystack is made of code.</p><p>In layman’s terms, dbt Incremental means that you don’t have to go through the hassle of processing all data from the beginning. You just process the new and modified data, saving you time and resources. It’s like a shortcut that actually works and won’t get you in trouble with your boss.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kJ-xZpol9bb8Sw7yYbHb9A.png" /><figcaption>Image by the author</figcaption></figure><p><em>If you want to know more about the nitty-gritty details of dbt Incremental, then check out this blog and document:</em></p><ul><li><a href="https://towardsdatascience.com/the-power-of-dbt-incremental-models-for-big-data-c8ba821eb078">The power of dbt incremental models for Big Data</a></li><li><a href="https://docs.getdbt.com/docs/build/incremental-models">Configure incremental models | dbt Developer Hub</a></li></ul><p>To set up this model in your dbt model, you need to add a config block at the beginning of your model script, keeping these two components in mind:</p><ul><li><strong>Materialized:</strong> By default, a dbt model’s materialized view is equal to ‘table’ when there is no configuration. To set the incremental mode, set the materialized view to ‘incremental’. For more information on other dbt materializations, please visit:</li></ul><p><a href="https://docs.getdbt.com/docs/build/materializations">Materializations | dbt Developer Hub</a></p><ul><li><strong>Unique_key:</strong> Although setting up a unique key is optional according to the dbt documentation, it is extremely important to rationally consider how you want to set this up. Essentially, the unique key will be the main driver that lets dbt know if the record should be added or changed. Some questions to keep in mind are:</li><li>Is the unique key really unique?</li><li>Is it a combination of two or more columns?</li></ul><p>Failing to set up a unique key can lead to missing data and ambiguous values, <strong>so be careful!</strong></p><p>Here is an example of how the config block is set up for a single unique key:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/bddf772657924ee8f67f2255aa5808ba/href">https://medium.com/media/bddf772657924ee8f67f2255aa5808ba/href</a></iframe><p>In the case the unique key is the combination of several columns, you can tweak the config to be:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/78e1233db19f1d1592e0598bfed3b744/href">https://medium.com/media/78e1233db19f1d1592e0598bfed3b744/href</a></iframe><blockquote><strong>Note:</strong> if you’re using BigQuery or Snowflake to store your data, you might have the option of tuning more extra configs like setting up sync_mode. But since my company’s database is built on Redshift, specifically Postgres, we don’t have those fancy gears.</blockquote><p>Once that’s taken care of, there’s just one more important step we need to add to our dbt incremental models&#39; script: a conditional block for the <strong>is_incremental()</strong> macro.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fa8f148149ae8512dc991a81f142e6f0/href">https://medium.com/media/fa8f148149ae8512dc991a81f142e6f0/href</a></iframe><p>The <strong>is_incremental()</strong> macro returns <em>True</em> if the following conditions are met:</p><ul><li>The destination table already exists in the database.</li><li>dbt is not running in <strong>full-refresh</strong> mode.</li><li>The running model is configured with <strong>materialized=’incremental’</strong></li></ul><p>Note that the SQL in your model needs to be valid regardless of whether <strong>is_incremental()</strong> evaluates to <strong><em>True</em></strong> or <strong><em>False</em></strong>.</p><p>Returning to the example of fct_booking, here is the original query:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/43b4f8ecb2cfb85c0839c192b8962401/href">https://medium.com/media/43b4f8ecb2cfb85c0839c192b8962401/href</a></iframe><p>After applying the incremental setup described above, we have a model that includes the unique key, a tag for the model, and a conditional block for the <strong>is_incremental()</strong> macro as follows:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6d3ab9640caff848b6c9820a8cae8366/href">https://medium.com/media/6d3ab9640caff848b6c9820a8cae8366/href</a></iframe><p>As seen in the code, the unique_key has been set to the <strong>booking_id</strong>, as one booking_id corresponds to one order.</p><p>To make it fancier, I have also added a model tag as <strong>incremental_model</strong> for any other model that I integrate with an incremental materialized. The main driver is that, often when things go wrong with dbt model incremental, they often go wrong ‘in bulk’. Thus, to refresh them without affecting other models and don’t have to remember every single model with incremental mode enabled, I can run the above code instead of having to specify each model name with incremental mode separately.</p><pre>dbt run — select tag:incremental_model --full-fresh</pre><p>Also note that if the incremental model is set up incorrectly and updates incorrect data in the production table, I would need to run the model again using the --full-refresh command. However, you should keep in mind that running it in full load refresh instead of incremental mode will be slower, so remember to pick the right time to do it (<em>tips: don’t do it at 9 am in the morning</em>).</p><h3>The Slap Back</h3><p>Up until this point, life was good again! I set up the table flawlessly, and the performance query significantly improved. Finally, I can sleep at night. My hand can touch the grass, and dbt incremental grant miss little Leah — a dream come true. However, not long after, a guy from the Finance team rushed to my desk with a report in his hand and aggressively claimed, “You gave me the wrong data!”</p><p>It turned out that the incremental models accidentally skipped many orders in a day and then went to the next day. “How on earth could this happen? I followed the expert tutorial — this can’t be wrong!” I whispered in my head. Except there is something going on upstream that I might have missed. After some digging, the issue came to light.</p><p>Every day, a data extraction and load process takes place at midnight to synchronize all the data up until that moment. This synchronization typically occurs at midnight, but its timing can be influenced by factors such as start spinup time and package cache. It’s important to note that the Extract part of the process may begin slightly after midnight.</p><p>Consider a scenario where the extract starts at 12:02 am and someone decides to make a booking around 12:01 am. In this situation, the data will also include a small portion of the orders from that day, which is referred to as “late arrival data” in more technical terms.</p><p>However, there’s a drawback with the current logic of the WHERE filter. The filter’s efficiency is compromised because it only appends new records from the latest date value of <strong>created_at</strong>. This means that it won’t capture all the data for the entire day.</p><p>In order to fix this, we will twist this logic a little bit:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/604c06ed0ac9146dcf198be2ba338139/href">https://medium.com/media/604c06ed0ac9146dcf198be2ba338139/href</a></iframe><p>The new filter involves syncing all data from the past 7 days. Any new data will be added to the existing dataset, while any old data with updated field values will be replaced.</p><h3>The Tradeoff</h3><p>As you’ve been following along, you might be wondering, “How many days should I go back using the is_incremental filter? And why did I choose 7 days for my case? What if I need data for the last 30 days?” Well, the answer is not straightforward — it depends on your specific scenario.</p><p>In my situation, I ensure that each day should have at least one order. Since there could be internal changes in the data during the last 7 days, I set my filter to append new and update existing data within that timeframe. However, if you feel confident about your query performance and want to go back further, say the last 365 days, you are free to do so! Just be mindful that there are tradeoffs to consider.</p><p>The primary reason for using an incremental model is to reduce costs in terms of model run performance. However, scanning through a larger dataset for the last 7 days could slow down performance, depending on the size of your data and your company’s specific use case. It’s essential to strike the right balance based on your needs.</p><p>For a more general approach, I recommend using 7 days as a standard rule. You can set up data update schedules on a weekly or annual basis for full-refreshes of the dbt incremental models. This approach allows you to account for unexpected issues, as no matter how well your setup is, there may still be occasional downtimes.</p><p>In my use case, I typically schedule the incremental run on a full-refresh during the weekend when there are fewer operational tasks. However, this schedule can be customized according to your team’s requirements.</p><p>Remember, the key is to find the right tradeoff between data freshness and query performance, ensuring that your data remains accurate and up-to-date while optimizing your model’s efficiency.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=63f931263f4a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-science/dbt-incremental-the-right-way-63f931263f4a">dbt Incremental — The Right Way</a> was originally published in <a href="https://medium.com/data-science">TDS Archive</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Yet Another Article about Star and Snowflake Schema]]></title>
            <link>https://medium.com/@ndleah/yet-another-article-about-star-and-snowflake-schema-c909892be45c?source=rss-7ee083e5e515------2</link>
            <guid isPermaLink="false">https://medium.com/p/c909892be45c</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[data-modeling]]></category>
            <category><![CDATA[data-engineer]]></category>
            <category><![CDATA[database]]></category>
            <dc:creator><![CDATA[Leah Nguyen]]></dc:creator>
            <pubDate>Wed, 19 Jul 2023 14:21:31 GMT</pubDate>
            <atom:updated>2023-07-19T14:21:31.427Z</atom:updated>
            <content:encoded><![CDATA[<h4>Unraveling the Mysteries of Star Schema and Snowflake Schema</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/1*Xhjouse1ZfIwaRu931zgSg.png" /></figure><h3>Introduction</h3><p>In the ever-evolving landscape of data management, two prominent database modeling techniques, Star Schema and Snowflake Schema, have captured the attention of data enthusiasts and organizations alike. Despite being well-established concepts, they continue to bewilder and confuse many individuals due to their nuanced differences. In this essay, we will explore the essence of Star and Snowflake Schema, delve into their practical applications through a fictional company called “DataCo,” and shed light on why some organizations choose Star Schema while others opt for Snowflake Schema.</p><h3>Defining Star Schema and Snowflake Schema</h3><p><strong>Star Schema</strong> and<strong> Snowflake Schema</strong> are both data warehousing designs aimed at organizing data efficiently.</p><p>In a <strong>Star Schema</strong>, data is structured around a central fact table, while multiple dimension tables surround it, forming a star-like shape. Each dimension table represents a specific attribute or category related to the central fact table, facilitating easier query performance and analysis.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/340/1*jLBQo7YwoqAJfuDz_JtT7w.png" /><figcaption>Star Schema Design</figcaption></figure><p>On the other hand, <strong>Snowflake Schema</strong> is an <strong><em>extension</em></strong> of the Star Schema, characterized by breaking down dimension tables into more normalized sub-dimensions. This normalization reduces data redundancy and optimizes storage.</p><p>For further read about normalization and denormalization, you can visit my previous blog on this topic here</p><p><a href="https://medium.com/@ndleah/data-modeling-love-breakups-and-complicated-relationships-a-hilarious-guide-8569f08dc032">“Data Modeling: Love, Breakups, and Complicated Relationships — A Hilarious Guide!”</a></p><p>As a result, the Snowflake Schema looks like a snowflake, with the fact table in the center and the normalized dimension tables branching out.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/764/1*59O7Wp52_jsSwoyorUtBPw.png" /><figcaption>Snowflake Schema Design</figcaption></figure><h3>Use Case Scenario: DataCo’s Sales Analytics</h3><p>Imagine “DataCo,” a fictional online retailer, eager to understand its sales performance for strategic decision-making. To analyze sales data efficiently, they must design a suitable database schema for their data warehousing.</p><h4>⭐ Application of Star Schema at DataCo</h4><p>DataCo chooses Star Schema for their sales analytics. They create a fact table that stores crucial sales data like order numbers, product IDs, and transaction dates. Surrounding this fact table, they establish dimension tables such as “Product,” “Customer,” and “Time” which contain detailed information about products, customers, and time periods respectively.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xf8xx7oWp-q1hHKzeqvlzg.png" /><figcaption>DataCo — Star Schema</figcaption></figure><p>When DataCo needs to analyze sales based on specific products or customer segments, the Star Schema allows for swift queries by directly connecting the required dimension tables to the fact table. This design streamlines data retrieval, making it ideal for real-time reporting and business intelligence purposes.</p><h4>❄️ Application of Snowflake Schema at DataCo</h4><p>Now, DataCo aims to expand its sales analytics to encompass a deeper level of data granularity. They choose Snowflake Schema for its ability to normalize data and reduce redundancy.</p><p>With Snowflake Schema, DataCo further breaks down dimension tables, like <strong>Product</strong> and <strong>Customer</strong>, into sub-dimensions. For example, the <strong>Product</strong> dimension can be divided into <strong>Product Category</strong> and <strong>Product Subcategory</strong> tables. Similarly, the <strong>Customer</strong> dimension can be split into <strong>Customer Details</strong> and <strong>Customer Location</strong> tables.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gJ1rqnyen9W4FgEA0xdxfw.png" /><figcaption>DataCo — Snowflake Schema</figcaption></figure><p>While the Snowflake Schema demands more complex joins between tables compared to the Star Schema, it results in significant storage savings and facilitates easier maintenance. This schema is particularly useful when DataCo wants to scale its database for massive amounts of data without compromising performance.</p><h3>Main Differences and Reasons for Choosing Star Schema and Snowflake Schema</h3><ol><li><strong>Complexity —</strong> Star Schema is simpler to implement and query due to its denormalized structure, making it more suitable for smaller data volumes and real-time reporting. On the other hand, Snowflake Schema’s normalization increases complexity but offers scalability advantages, making it suitable for handling vast data and long-term data management.</li><li><strong>Query Performance —</strong> Star Schema often outperforms Snowflake Schema in query response time because of its reduced joins between tables. However, Snowflake Schema can still achieve excellent performance with proper indexing and optimization.</li><li><strong>Storage Efficiency —</strong> Snowflake Schema excels in storage efficiency due to normalization, eliminating redundant data. This makes it an excellent choice for large enterprises dealing with vast amounts of data.</li><li><strong>Maintainability —</strong> Star Schema is easier to maintain due to its straightforward design, but it may suffer from data duplication. Snowflake Schema, despite being more complex, offers better maintainability by reducing redundancy and adhering to database normalization principles.</li></ol><p>In conclusion, Star Schema and Snowflake Schema are two powerful database modeling techniques that can significantly impact an organization’s data analytics capabilities. Star Schema’s simplicity and real-time reporting advantages make it a compelling choice for smaller businesses and quick data insights. On the other hand, Snowflake Schema’s advanced normalization and scalability make it an attractive option for larger enterprises dealing with extensive data volumes and long-term data storage needs.</p><p>Understanding the nuances of these schemas can help organizations design robust and efficient data warehousing solutions tailored to their unique needs. By leveraging the strengths of both Star and Snowflake Schemas, companies like DataCo can extract valuable insights from their data, empowering them to make informed decisions and gain a competitive edge in their industry.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c909892be45c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Brief Guide to Database Normalization]]></title>
            <link>https://medium.com/@ndleah/a-brief-guide-to-database-normalization-5ac59f093161?source=rss-7ee083e5e515------2</link>
            <guid isPermaLink="false">https://medium.com/p/5ac59f093161</guid>
            <category><![CDATA[normalization]]></category>
            <category><![CDATA[data-modeling]]></category>
            <category><![CDATA[database]]></category>
            <dc:creator><![CDATA[Leah Nguyen]]></dc:creator>
            <pubDate>Tue, 18 Jul 2023 15:08:19 GMT</pubDate>
            <atom:updated>2025-07-14T02:07:13.039Z</atom:updated>
            <content:encoded><![CDATA[<h4>Understanding the Basics and Advanced Levels of Database Normalization</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2tHEzpv47QLGx2wHN_ZJpA.jpeg" /></figure><p>If you’re new to database design, you’ve probably heard about database normalization. This is the process of organizing data in a database so that it is consistent, efficient, and easy to manage. There are several levels of normalization, each with their own benefits and use cases. In this article, we’ll cover the basics of normalization, including first normal form (1NF), second normal form (2NF), third normal form (3NF), and other advanced normal forms.</p><h3>Normal Form</h3><p>Concept of <strong>normalization</strong> and <strong>normal forms</strong> were introduced, after the invention of the relational model. Database normalization is an essential procedure to avoid inconsistency in a relational database management system. It should be performed in the design phase. To achieve this, redundant fields should be refactored into smaller pieces.</p><p><strong>Normals forms</strong> are defined structures for relations with set of constraints that relations must satisfy in order to detect data redundancy and correct anomalies. There can be following anomalies while performing a database operation:</p><ul><li><strong>insert:</strong> data is known but can not be inserted</li><li><strong>update:</strong> updating data requires modifications in multiple tuples (rows)</li><li><strong>delete:</strong> deleting some data causes some other data to be lost</li></ul><p>First Normal Form has initial constraints, further normal forms like 2NF, 3NF, BCNF, 4NF, 5NF would add new constraints cumulatively. In other words, every 2NF is also in 1NF; every relation in 3NF is also in 2NF. If all group of relations are represented as sets, following figure can be drawn:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/523/1*Xcfjy-EnEZKWmxxLStmi0A.png" /></figure><h3>First Normal Form (1NF)</h3><p><strong>First normal form (1NF)</strong> is the simplest level of normalization. It involves ensuring that each table in the database has a primary key and that each column in the table contains atomic values. In other words, each row in the table should have a unique identifier, and each value in the table should be indivisible.</p><blockquote>Let’s take an example to understand this better. Consider a table that stores information about employees. The table might have columns like employee_id, name, address, and phone_number. However, the address column could contain multiple values, like street name, city, state, and zip code.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5IzcZ7zI4akepxF4Pt6m3w.png" /><figcaption>Example Table</figcaption></figure><blockquote>To bring this table to 1NF, we need to split the address column into separate columns, each containing a single value.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*67BH2RjG4VZAn8sjnLOPrg.png" /><figcaption>1NF Output</figcaption></figure><h3>Second Normal Form (2NF)</h3><p><strong>Second normal form (2NF) </strong>builds on the foundation of 1NF and involves ensuring that each non-key column in a table is dependent on the primary key. In other words, there should be no partial dependencies in the table.</p><blockquote>Let’s continue with our employee table example. Suppose we add a column for department to the table. If we find that the value in the department column is dependent on the employee_id and name columns, but not on the phone_number column, we need to split the table into two tables, one for employee information and one for department information.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mdPhWT6tr18bKj4mXNwxBQ.png" /><figcaption>2NF Output</figcaption></figure><h3>Third Normal Form (3NF)</h3><p><strong>Third normal form (3NF)</strong> builds on the foundation of 2NF and involves ensuring that each non-key column in a table is not transitively dependent on the primary key. In other words, there should be no transitive dependencies in the table.</p><blockquote>Let’s take another example. Consider a table that stores information about books. The table might have columns like book_id, title, author, and publisher.</blockquote><blockquote>However, the publisher column could be dependent on the author column, rather than on the book_id column. To bring this table to 3NF, we need to split it into two tables, one for book information and one for author information.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YFSuF-sGzyNp40RsUreOVQ.png" /><figcaption>3NF Output</figcaption></figure><h3>BCNF — <strong>Boyce-Codd Normal Form</strong></h3><p><strong>Boyce-Codd Normal Form (BCNF)</strong> is a higher level of normalization than 3NF. It is used to eliminate the possibility of functional dependencies between non-key attributes. A table is in BCNF if and only if every determinant in the table is a candidate key.</p><blockquote>To understand BCNF better, consider a table that stores information about students and their courses. The table might have columns like student_id, course_id, instructor, and instructor_office. In this table, the determinant is course_id, and the non-key attribute is instructor. However, a course can have multiple instructors, so there is a possibility of functional dependencies between non-key attributes. To bring this table to BCNF, we need to split it into two tables, one for course information and one for instructor information.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bD032SNd-49-LQgmJOIStQ.png" /><figcaption>BCNF Output</figcaption></figure><h3><strong>Fourth Normal Form (4NF)</strong></h3><p><strong>Fourth Normal Form (4NF)</strong> is used to eliminate the possibility of multi-valued dependencies in a table. A multi-valued dependency occurs when one or more attributes are dependent on a part of the primary key, but not on the entire primary key.</p><blockquote>To understand 4NF better, consider a table that stores information about employees and their skills. The table might have columns like employee_id, skill, and proficiency_level. In this table, the primary key is a combination of employee_id and skill. However, the proficiency level is dependent on the skill, but not on the entire primary key. To bring this table to 4NF, we need to split it into two tables, one for employee information and one for skill information.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SYq3zac5iFfATP91CiXFUw.png" /><figcaption>4NF Output</figcaption></figure><h3>Fifth Normal Form (5NF)</h3><p><strong>Fifth normal form (5NF) </strong>is the highest level of normalization and is also known as Project-Join Normal Form (PJNF). It is used to handle complex many-to-many relationships in a database.</p><p>In a many-to-many relationship, where each table has a composite primary key, it is possible for a non-trivial functional dependency to exist between the primary key and a non-key attribute. 5NF deals with these situations by decomposing the tables into smaller tables that preserve the relationships between the attributes.</p><blockquote>To understand this better, consider a database that stores information about movies and their actors. The tables might have columns like movie_id, actor_id, character_name, and salary. In this database, it is possible for a non-trivial functional dependency to exist between the primary key (movie_id, actor_id) and the salary attribute.</blockquote><blockquote>To bring this database to 5NF, we need to decompose the tables into smaller tables. For example, we might create tables for movies, actors, and characters, and then use a join table to connect them. Each table would have a single primary key, and the join table would include foreign keys to the other tables.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KlUJX0U6DxspNH1Qdj924A.png" /><figcaption>5NF Output</figcaption></figure><h3>Reflection</h3><p>Today, many organizations rely on databases to store, manage, and retrieve their data. In order to ensure that the data is organized in a way that is both efficient and consistent, normalization is often used. There are several levels of normalization that can be applied, with 1NF, 2NF, and 3NF being the most commonly used.</p><p>In addition to 1NF, 2NF, and 3NF, there are also advanced normalization techniques such as Boyce-Codd Normal Form (BCNF), Fourth Normal Form (4NF), and Fifth Normal Form (5NF). BCNF is used to eliminate the possibility of functional dependencies between non-key attributes. 4NF is used to eliminate the possibility of multi-valued dependencies in a table. 5NF, also known as Project-Join Normal Form (PJNF), is used to handle complex many-to-many relationships in a database.</p><p>While these levels of normalization can provide further data consistency and management benefits, they can also result in more complex table relationships, slower queries, and larger numbers of tables. Therefore, it’s important to carefully consider the use cases and benefits of each technique before applying them in database design.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*D4kA1faz1mvK5QePXPtpdA.png" /><figcaption>Normal Forms Comparison Table</figcaption></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5ac59f093161" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Data Modeling: Love, Breakups, and Complicated Relationships — A Hilarious Guide!]]></title>
            <link>https://blog.dataengineerthings.org/data-modeling-love-breakups-and-complicated-relationships-a-hilarious-guide-8569f08dc032?source=rss-7ee083e5e515------2</link>
            <guid isPermaLink="false">https://medium.com/p/8569f08dc032</guid>
            <category><![CDATA[data-modeling]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[data-science]]></category>
            <dc:creator><![CDATA[Leah Nguyen]]></dc:creator>
            <pubDate>Sat, 15 Jul 2023 14:02:01 GMT</pubDate>
            <atom:updated>2023-07-29T04:49:06.127Z</atom:updated>
            <content:encoded><![CDATA[<h3>Data Modeling: Love, Breakups, and Complicated Relationships — A Hilarious Guide!</h3><h4>How to Relationally Date Your Data</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*BzXRT8qBcBtZ2-Nlzd5Sew.png" /><figcaption>Image by the author</figcaption></figure><p>Welcome to the wild and wacky world of data modeling! When I try to picture it in a “non-technical” way, it’s like a whimsical adventure where we play matchmaker for our data, creating connections and relationships that would make Cupid proud.</p><p>Imagine a dating app, but instead of finding your soulmate, you’re finding the perfect match for your data. Buckle up and get ready to swipe right on some hilarious concepts of relational data modeling.</p><h3>Normalization: The Art of Breaking Up</h3><p>Ah, the early stages of dating. You don’t want to rush into a committed relationship and risk having your heart (or in this case, your data) broken. That’s where normalization comes in! It’s like a breakup, but a friendly one. We break up our data into smaller, more manageable tables, so they can keep their options open. No one wants to be stuck in a suffocating relationship with one massive table, right? It’s all about flexibility and giving your data the freedom to explore.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/815/1*yZLWT6o_9JnJSXGsXFOexA.png" /><figcaption>Image by the author</figcaption></figure><blockquote>For instance, picture a table of customers. Each customer has a phone number, email address, and mailing address. Instead of cramming all that info into one overwhelming table, we break it down into smaller, more digestible tables. We create a table for phone numbers, another for email addresses, and one more for mailing addresses. Now, if a customer changes their phone number, you only need to update one table instead of the whole enchilada. Talk about efficient dating!</blockquote><h3>Denormalization: Getting Back Together</h3><p>Sometimes, though, breaking up is harder than we thought. You might start missing the good old days when everything was in one place. That’s when denormalization swoops in like a knight in shining armor. It’s all about reconciliation, my friend! We merge those smaller tables back together, like rekindling an old flame. Suddenly, your data is reunited, stronger than ever.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/758/1*gyjx7S16lnHfyFKafrBpjw.png" /><figcaption>Image by the author</figcaption></figure><blockquote>Imagine you have a table of orders, each with a customer ID, a product ID, and a quantity. On the other hand, you have separate tables for customers and products. Rather than playing the “join the tables” game every time you want to know what a customer ordered, you can denormalize like a boss. Add the customer’s name and the product’s name to the order table. Boom! Now you only need to consult one table to get all the juicy details. It’s like having a cozy reunion with all your data snuggled up in one place.</blockquote><h3>Fact/Dimension Tables: It’s Complicated</h3><p>Life isn’t always about dating just one person. Sometimes, you’re in a full-on relationship with a whole group of people. That’s when things get a bit complicated, my friend. Enter the fact/dimension tables! It’s like managing a bustling love triangle, but with data. Intriguing, right?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/663/1*IPDPvpWPB9zzW6oH5G6Rvg.png" /><figcaption>Image by the author</figcaption></figure><p>The fact tables hold the raw data, like sales or inventory, while the dimension tables provide the juicy metadata. Think dates, locations, or product information. It’s like balancing multiple partners without the drama. Smooth, huh?</p><blockquote>Imagine you have a fact table of sales. Each sale has a date, a product ID, a store ID, and a quantity. You also have dimension tables for dates, products, and stores. Instead of throwing everything into one chaotic table, you use the fact table as the link to the dimension tables. It’s like playing matchmaker for your data. They all get along, and you get the information you need without the headache. Complicated relationships can work, my friend, especially in the world of data modeling!</blockquote><h3>Different Schema Models: It’s Not You, It’s Me</h3><p>Just like in dating, we all have different preferences when it comes to organizing our data. It’s not about finding “the one” schema model, but rather finding what works best for you. Let’s explore some of the options together, shall we?</p><p>First up, we have the <strong>star schema</strong>. It’s like a simple, straightforward date night. Perfect for easy queries and reporting. Everything revolves around one central table, like sales, and branches out to other related tables, such as customers, products, and dates. It’s like a star-studded romantic comedy where everyone has their role.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Eohfu_BNTZtRWLJyAs5_Pg.png" /><figcaption>Image by the author</figcaption></figure><p>Then we have the <strong>snowflake schema</strong>. It’s for those who love a bit of complexity and enjoy diving deep into their data. You take the star schema and add more intricate details, like regions and stores. It’s like a snowflake falling from the sky, each branch leading to more data. It may be chilly, but it’s worth it!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/950/1*I5PlzMlsk0_H8DSuW37GEA.png" /><figcaption>Image by the author</figcaption></figure><p>Lastly, we have the fact constellation schema. It’s like the grand finale, designed for huge data sets and multiple fact tables. You want to gather all the information you can get, right? Picture a constellation in the sky, connecting different points of data in a mesmerizing dance.</p><p>So there you have it, the wonderful world of relational data modeling with a humorous twist! With these concepts in your arsenal, you’ll be able to create loving relationships between your data and make them giggle with joy for years to come. Happy data matchmaking!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8569f08dc032" width="1" height="1" alt=""><hr><p><a href="https://blog.dataengineerthings.org/data-modeling-love-breakups-and-complicated-relationships-a-hilarious-guide-8569f08dc032">Data Modeling: Love, Breakups, and Complicated Relationships — A Hilarious Guide!</a> was originally published in <a href="https://blog.dataengineerthings.org">Data Engineer Things</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How I Went from Clueless to Confused in the Ever-Changing World of Data Engineering]]></title>
            <link>https://medium.com/@ndleah/how-i-went-from-clueless-to-confused-in-the-ever-changing-world-of-data-engineering-6b85dffb627f?source=rss-7ee083e5e515------2</link>
            <guid isPermaLink="false">https://medium.com/p/6b85dffb627f</guid>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[database]]></category>
            <category><![CDATA[big-data]]></category>
            <dc:creator><![CDATA[Leah Nguyen]]></dc:creator>
            <pubDate>Wed, 12 Jul 2023 07:32:17 GMT</pubDate>
            <atom:updated>2023-07-12T07:32:17.958Z</atom:updated>
            <content:encoded><![CDATA[<h4>From Noob to Hero, and Back Again</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*vx-FI3aWmaEbCyISz9-vIA.png" /></figure><p>Picture this: It’s a sunny day in the world of data professionals. Birds are chirping, and everyone is sipping their morning coffee while trying to catch up on the latest data engineering news. But wait, what’s that? Oh, it’s just another article proclaiming the arrival of a groundbreaking data tool that will revolutionize the industry. <strong>Sigh… Here we go again.</strong></p><h3>The Trend’s whack-a-mole</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*15qvCga4sMN2dxEQOo8Z5A.jpeg" /><figcaption>2023 MAD (Machine Learning, Artificial Intelligence &amp; Data)</figcaption></figure><p>Tell me, looking at the picture above, what do you see? Ah, behold the magnificent picture that represents the ever-so- “<strong>MAD</strong>” landscape of Machine Learning, Artificial Intelligence, and Data! It has managed to capture the hearts of two distinct types of individuals, each with their unique reactions. On one side, we have the enthusiasts, brimming with excitement, their minds racing with the countless tools they can devour and master. On the other side, we have the unfortunate souls who gaze upon this visual chaos, clutching a bucket in hand, as if ready to unleash the contents of their stomachs. I must confess, I’ve personally experienced both of these extreme states.</p><p>Initially, this bizarre realm appeared as a fascinating wonderland, where I, a mere nut crack in the vast expanse of data, could find my place. But as time went on, and the updates came in at an ever-increasing pace, I found myself growing more and more disenchanted with this so-called wonderland. Hadoop, Kafka, Docker, Kubernetes — the list goes on and on. And just when you think you’ve caught up with the latest tool, it slips away and another one comes crashing down. It’s like trying to catch a wave in the ocean. It’s a never-ending cycle of hype, disappointment, and confusion. But hey, at least I get to add more buzzwords to my LinkedIn profile, right?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*MZXKYlLV8GoVt_7jqYJQ_w.png" /></figure><p>Remember when Hadoop was hailed as the savior of big data? It was supposed to be the Twitter killer of the data world. But then came Spark, swooping in like Threads, stealing the spotlight and leaving Hadoop in the shadows. And let’s not forget about poor Flink, who was briefly hailed as the next big thing until something shinier came along.</p><p>In the midst of this chaos, I’ve come to realize that chasing every new tool is a fool’s errand.</p><h3><strong>Here’s How to Win</strong></h3><h3><strong>1. Prioritize the purpose behind the tools</strong></h3><p>Instead of chasing every new tool, ask yourself why you need it. Is it to cut down on costs? Improve efficiency? Or just to impress your boss at the next meeting? By understanding the purpose behind the tools, you can better evaluate which ones are truly necessary and which ones are just adding to the noise.</p><blockquote><strong>Example:</strong> f you’re working on a project that requires analyzing large amounts of data, you may want to invest in a tool like Apache Spark that can handle big data processing. On the other hand, if you’re working on a small project with a limited budget, a simpler tool like Microsoft Excel may be sufficient for your needs. By prioritizing the purpose behind the tools, you can make better decisions about which tools to invest your time and resources in.</blockquote><h3><strong>2. Master the fundamentals</strong></h3><p>No matter how many new tools emerge, the most foundational ones are often the most important. Focus on mastering the basics like SQL, Python, and Excel. This will give you a strong foundation of knowledge and understanding that will help you navigate the ever-changing landscape of data engineering.</p><h3><strong>3. Take a break from chasing the next big thing</strong></h3><p>Oh, how we love being told <strong>we need more tools</strong>. Because obviously, we don’t already have enough on our plates. The media insists on bombarding us with countless reasons why we need certain tools and how they can enhance our data game. Yawn. It’s like we hear the same phrase over and over again. And yet, we blindly follow with the thought, “Yeah, we <strong><em>might </em></strong>need that!” Been there, done that, and it always ends up being a waste of time.</p><p><strong>Here’s the catch:</strong> if you don’t feel like you need it, you don’t need it. I know, I know, it sounds too simple. But trust me, it works. Instead of mindlessly following the latest trends, ask yourself why you shouldn’t. It’s like a breath of fresh air in the midst of all the hype. So go ahead, take a deep breath, and embrace the power of saying no to unnecessary tools.</p><h3>5. Embrace a sense of humor</h3><p>When you feel like you’re drowning in a sea of buzzwords and acronyms, it’s easy to get frustrated and overwhelmed. A little humor goes a long way in keeping you sane. So, next time someone tries to one-up you with their knowledge of the latest tool, just casually drop a phrase like</p><blockquote><strong>“Oh, yeah, I remember using that back when it was still in beta.”</strong></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6b85dffb627f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Predict Next Month Transaction with Linear Regression (Final)]]></title>
            <link>https://medium.com/@ndleah/predict-next-month-transaction-with-linear-regression-final-3c4d6b62793b?source=rss-7ee083e5e515------2</link>
            <guid isPermaLink="false">https://medium.com/p/3c4d6b62793b</guid>
            <category><![CDATA[data-modeling]]></category>
            <category><![CDATA[linear-regression]]></category>
            <category><![CDATA[feature-selection]]></category>
            <category><![CDATA[machine-learning]]></category>
            <dc:creator><![CDATA[Leah Nguyen]]></dc:creator>
            <pubDate>Fri, 28 Oct 2022 22:31:22 GMT</pubDate>
            <atom:updated>2022-10-28T22:42:59.026Z</atom:updated>
            <content:encoded><![CDATA[<h4>Feature Selection and Data Modelling</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*AkUt5UmKdIsaIxNT" /><figcaption>Photo by <a href="https://unsplash.com/@markuswinkler?utm_source=medium&amp;utm_medium=referral">Markus Winkler</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>A Rewind</h3><p>In this project, we have followed the CRISP-DM management approach to construct a comprehensive ML project framework. Some of the covered topics from the previous parts are illustrated as follows:</p><p>✅ Business understanding</p><p>✅ Data understanding</p><p>✅ Data preparation</p><p>❌ Modelling</p><p>❌ Evaluation</p><p>❌ Deployment</p><p>In this article, I will cover the rest of the approach, including — <strong>Modelling</strong>, <strong>Evaluation</strong> and <strong>Deployment</strong></p><p>Review previous parts —</p><ul><li><a href="https://medium.com/@ndleah/predict-next-month-transaction-with-linear-regression-part-1-917a79b6ae0c">Predict Next Month Transaction with Linear Regression (Part 1)</a></li><li><a href="https://medium.com/@ndleah/predict-next-month-transaction-with-linear-regression-part-2-7c7e6106b5e">Predict Next Month Transaction with Linear Regression (Part 2)</a></li></ul><p>GitHub code repository —</p><p><a href="https://github.com/ndleah/transactions">GitHub - ndleah/transactions: 🪙 Linear regression model, predict monthly transaction amount</a></p><h3><strong>Feature Selection Technique (FST)</strong></h3><p>In machine learning, it is essential to provide a pre-processed and high-quality input dataset in order to achieve better results. Typically, the dataset consists of half <strong>noisy</strong>,<strong> irrelevant</strong> data, and half <strong>useful data</strong>.</p><p>The massive amount of data slows down the training process of the model, and if there is noise or irrelevant data, the model may not accurately predict and perform. In order to eliminate these noises and unimportant data features from the dataset, Feature selection techniques need to be adopted and used wisely in order to retain only the best feature for the Machine Learning model.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/550/1*LQV6UVulmVG7U3FEVJcglg.png" /><figcaption>Feature Selection Techniques</figcaption></figure><p>Some benefits of using feature selection in machine learning:</p><ul><li><strong>It helps in avoiding the curse of dimensionality.</strong></li><li><strong>It helps in the simplification of the model so that it can be easily interpreted by the researchers.</strong></li><li><strong>It reduces the training time.</strong></li><li><strong>It reduces overfitting hence enhances the generalization.</strong></li></ul><p>From <a href="https://medium.com/@ndleah/predict-next-month-transaction-with-linear-regression-part-2-7c7e6106b5e">Part 2</a>, we have discovered that the problem for this project is a Supervised Regression problem. This can be overcome by deploying a <strong>Linear Regression model</strong>. Therefore, this section will focus on explaining different terms and techniques used for Feature Selection for Supervised models with <strong>Wrapper Method</strong>.</p><h3><strong>A closer look at FST — Wrapper Method</strong></h3><p>Wrapper methodology approaches feature selection as a search problem, in which different combinations are created, evaluated, and compared to other combinations. It iteratively trains the algorithm using the subset of features.</p><p>Features are added or subtracted based on the model’s output, and the model has trained again with this feature set.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/340/1*wpukVhPeML8CQuKzBMVRyw.png" /></figure><p>Some techniques of wrapper methods are:</p><ul><li><strong>Forward selection</strong> — Forward selection is an iterative process, which begins with an empty set of features. After each iteration, it keeps adding on a feature and evaluates the performance to check whether it is improving the performance or not. The process continues until the addition of a new variable/feature does not improve the performance of the model.</li><li><strong>Backward elimination</strong> — Backward elimination is also an iterative approach, but it is the opposite of forward selection. This technique begins the process by considering all the features and removing the least significant feature. This elimination process continues until removing the features does not improve the performance of the model.</li><li><strong>Exhaustive Feature Selection —</strong> Exhaustive feature selection is one of the best feature selection methods, which evaluates each feature set as brute force. It means this method tries &amp; to make each possible combination of features and return the best-performing feature set.</li><li><strong>Recursive Feature Elimination — </strong>Recursive feature elimination is a recursive greedy optimization approach, where features are selected by recursively taking a smaller and smaller subset of features. Now, an estimator is trained with each set of features, and the importance of each feature is determined using <em>coef_attribute</em> or through a <em>feature_importances_attribute.</em></li></ul><h3>Modelling</h3><p>The scope of this project consists of 2 areas of ML modelling:</p><ul><li><strong>Basic Model Fitting — </strong>Developing a linear regression model with monthly_amount as the target for industry = 1 and location = 1.</li><li><strong>Avanced Model Fitting — </strong>Developing a linear regression model with monthly_amount as the target for all industries and locations.</li></ul><h3>Basic Model Fitting</h3><p>In this section, numerous Multiple Linear Regression (MLR) models will be developed and assessed with various combinations of predictor variables, which are filtered by <strong>Location 1</strong> &amp; <strong>Industry 1</strong>. As for the approach, I will adopt the <strong>stepwise model selection</strong> method as <strong>backward elimination</strong>.</p><h4>Model 1 — Full Model</h4><p>Firstly, I start with a full model which is a model with all possible co-variants or predictors included, and then I will drop variables one at a time until a parsimonious model is reached. Noted that even though we start the model with all variables, I will exclude the location, industry and year as we only filter by Location 1, Industry 1 and the year of 2013-2015, which can overfit our MLR model.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cClnZ6L9X-lBWe18_BuDsQ.png" /><figcaption>Basic Model Fitting: Model 1 — Output</figcaption></figure><p>The month number variable is introduced to accommodate for the seasonality of the sales amount. As summarized in the linear model with the formula formula = monthly_amount ~ date + month_number, this model performs quite impressively with the Adjusted R-Square equivalent to 0.7457. In other words, this indicates that approximately 74,57% observations in the training set are explained by the model.</p><h4>Model 2 — Fit the model with month_number variable</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WdxsMrmqzrL0VM-5By-5vg.png" /><figcaption>Basic Model Fitting: Model 2— Output</figcaption></figure><p>Based on the Multiple R-squared value, our Model 2 can only account for approximately 54% of the variance. This indicates that fitting the month_number alone provide a moderate predictor of monthly_amount which specifically perform worse than the first model. We can also get confirmation by looking at the p-value of 0.02583 which tells us that the month predictors are unlikely to be a good fit to the data.</p><h4>Model 3: Fit the model with date variable</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8w9WDp9cNvWA7caxZwA_PA.png" /><figcaption>Basic Model Fitting: Model 3 — Output</figcaption></figure><p>With the third one where we fit only the date variable to the model, it even get a worse performance with only 36% of the variability in the average monthly sales amount is explained by it, leaving a whopping of unexplained 64% variance.</p><blockquote>In conclusion, <strong>Model 1</strong> provide the best fit so far compared to the other 2 combinations. Thus, I will use this model for making a prediction for monthly_amount in December 2016.</blockquote><p>After having chosen <strong>Model 1</strong> as the final model for the Basic Model Fitting, my next step is to create a new data frame specifying only 2016 records. I then made the prediction for the transaction amount in December 2016.</p><p>I will examine whether our December 2016 forecast reasonable by plotting a line plot with the predicted data.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oVjf4Q0hsjQW8XkaLNZz9A.png" /></figure><p>We thencan quantify the residuals by calculating a number of commonly used evaluation metrics. I’ll focus on the following three:</p><ul><li><strong>Mean Square Error (MSE):</strong> The mean of the squared differences between predicted and actual values. This yields a relative metric in which the smaller the value, the better the fit of the model.</li><li><strong>Root Mean Square Error (RMSE):</strong> The square root of the MSE. This yields an absolute metric in the same unit as the label. The smaller the value, the better the model.</li><li><strong>Coefficient of Determination (usually known as R-squared or R2):</strong> A relative metric in which the higher the value, the better the fit of the model. In essence, this metric represents how much of the variance between predicted and actual label values the model is able to explain.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*F8ka1_Na7a5HveiXk8rqtA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IOrPXtAt3z8J7Ip_zRmLAw.png" /></figure><p>The performance of prediction is significantly lower than the model’s performance on train data. The R2 is 0.55 lower than 0.83, which presents its low fit to the actual data. The prediction error RMSE is 11293, representing an error rate of ~ 6%, which is still good.</p><h3>Advanced Model Fitting</h3><p>We want to apply our model (mean amount + date + month number) across all industries and geographical locations. To do this, I will construct a loop function as calculate_predictions to run everything through.</p><p>To be more specific, the loop function will do the following tasks:</p><ol><li>Train the model for each industry and location.</li><li>Include a column for December 2016 in the table.</li><li>Calculate the mean square error (MSE) and root mean square error (RMSE).</li><li>Make a December 2016 prediction.</li><li>Consolidate all data into a dataframe.</li></ol><p>We were running all locations and industries through the below model:</p><ul><li>mean_monthly ~ time_number</li></ul><p>In that, time_number represent the date order.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RDbYesZZxqfXsiWLtoy0gg.png" /><figcaption>Worst industries and locations — assessed by RMSE score</figcaption></figure><p>Among data sets, we picked two worst performed industries and locations by its highest RMSE:</p><ul><li><strong>Industry 6 &amp; Location 1</strong></li><li><strong>Industry 10 &amp; Location 8</strong></li></ul><p>In order to find out potential reasons that lead to poor performance of these locations, I will retrain the model based on these 2 industries and locations and then plot the model in order to see how they are performing.</p><p>Let’s take a look at the diagnosis plots for these 2 components:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kEpu8S4HJrSbwuFfQ7mIvw.png" /><figcaption><em>The diagnostic plot — Indsutry 6 &amp; Location 1</em></figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yndet9Bf2QlYlrVQoggFpw.png" /><figcaption><em>The diagnostic plot — </em>Industry 10 &amp; Location 8</figcaption></figure><p>We can see that, both models have outliers records. For industry 6 and location 1, there are 1 and 2 points which are far, above and below, from the model, respectively. For industry 10 and location 8, there 3 outliers exist above the plotted line.</p><p>To confirm our theory, I plot another linear model plot for industry 10 &amp; location 8 and industry 6 &amp; location 1.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*p9gXla45VFbvlEW_RoLrJg.png" /></figure><p>In this plot, we can have a clearer view of outstanding outliers contained in both models. Additionally, it is also observed from the plot that the fitted line cannot catch the constant up-and-down trend of the monthly mean due to seasonality, which could be a reason for poor performance. By developing more advanced models that could account for those fluctuations and removing these outliers can likely lead to a more accurate and powerful model.</p><p>Code of the project and relevant files-</p><p><a href="https://github.com/ndleah/transactions">GitHub - ndleah/transactions: 🪙 Linear regression model, predict monthly transaction amount</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3c4d6b62793b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Predict Next Month Transaction with Linear Regression (Part 2)]]></title>
            <link>https://medium.com/@ndleah/predict-next-month-transaction-with-linear-regression-part-2-7c7e6106b5e?source=rss-7ee083e5e515------2</link>
            <guid isPermaLink="false">https://medium.com/p/7c7e6106b5e</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[exploratory-data-analysis]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[data-visualization]]></category>
            <category><![CDATA[feature-engineering]]></category>
            <dc:creator><![CDATA[Leah Nguyen]]></dc:creator>
            <pubDate>Sat, 18 Jun 2022 03:44:53 GMT</pubDate>
            <atom:updated>2022-06-18T03:44:53.857Z</atom:updated>
            <content:encoded><![CDATA[<h4>Exploratory Data Analysis and Feature Engineering</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*MI3v6YgaWGdSx8fh" /><figcaption>Photo by <a href="https://unsplash.com/@cgower?utm_source=medium&amp;utm_medium=referral">Christopher Gower</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>In Part 1 of this blog, we worked on basic data analysis and understanding the transaction dataset following the CRISP-DM methodology. We found out that our problem is the supervised regression problem from looking at the data type of the target variable — monthly amount.</p><p>For the Part 1, please visit-</p><p><a href="https://medium.com/@ndleah/predict-next-month-transaction-with-linear-regression-part-1-917a79b6ae0c">Predict Next Month Transaction with Linear Regression (Part 1)</a></p><p>In this part, we will continue to work on the Exploratory Data Analysis of the data, which will help to uncover business insights for the later modelling stage as well as perform feature engineering for variables selection.</p><p>You can view all my code using for this project on <a href="https://github.com/ndleah/transactions">GitHub</a>.</p><h3>Exploratory Data Analysis (Part 2) — The Business Insights</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yw29g9IF7m2roUoWURpWsQ.png" /><figcaption>Transaction amount vs. transaction number trend over time</figcaption></figure><p>The number of transactions and the total amount of sales rose sharply throughout the years, from 2013 to 2017. The seasonal trend can be found in the total amount of sales while the up trend for the number of transactions is quite smooth.</p><p>We could see that there is a seasonal pattern, although the trend is not clear yet. To investigate more, we would make a yearly polar plot:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8KmLu1d90ftq-FzaFgNs6A.png" /><figcaption>Seasonal Trend Over the Years</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zUqsObgkB8LYh3B77qz8sA.png" /></figure><p>A closer examination reveals that the total volume of transaction amount increases significantly from January to October and subsequently decreases from November to the end of the year. This pattern can be ascribed to the fact that people trade less during the holidays, particularly during the month surrounding big holidays like Christmas and New Year.</p><p>This, however, might be based on a variety of different factors rather than on individual conclusions about each region or industry. As a result, additional information is required to substantiate these hypotheses.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XYNBWDioJZlnQuTpmaALMg.png" /><figcaption>Transaction amount by Location vs. Industry</figcaption></figure><p>When looking at the monthly amount by location and industry, it is not surprising that total sales of locations 1 and 2 increased significantly compared to other locations. Meanwhile, in terms of industry, industry 2, 3 and 1 shows rapid growth over the years while others’ progress is quite slow.</p><h3>Data Preparation</h3><h4>Feature Engineering</h4><p>When the data has been fully understood, data scientists generally need to go back to the data collection and data cleaning phases of the data science pipeline so as to transform the data set as per the expected business outcomes. To expand the information that is already at hand and better represent the information we have, the best practice is to perform <strong>Data Preparation</strong> or <em>Feature Engineering</em>, meaning the creation of new features from the ones already existing.</p><p>In this case study, the data will need to be modified as we will be applying a linear regression model later on.</p><pre><em># write a reusable function</em><br>aggregate_transactions &lt;- <strong>function</strong>(df) {<br>  <br>  <em># aggregate the data, grouping by date, industry and location, </em><br>  <em># and calculating the mean monthly_amount</em><br>  output = df %&gt;%<br>    group_by(date, industry, location) %&gt;%<br>    summarize(monthly_amount = mean(monthly_amount, na.rm = TRUE))<br>  <br>  <em># create a column for the month number and another one for year number</em><br>  output = output %&gt;%<br>    <em># create new column for month number</em><br>    mutate(month_number = format(as.Date(date), &quot;%m&quot;)) %&gt;%<br>    <em># create new column for month number</em><br>    mutate(year_number = format(as.Date(date), &quot;%Y&quot;))<br>  <br>  <em># Make sure the new columns are of the correct type</em><br>  output$month_number = as.character(output$month_number)<br>  output$year_number = as.character(output$year_number)<br>  <br>  transform(output, month_number = as.integer(month_number), year_number = as.integer(year_number))<br>  <strong>return</strong>(output)<br>}<br><br><em># create a new variable that store new df with transformed features</em><br>aggregated_transactions &lt;- aggregate_transactions(df)<br><em># A tibble: 3,886 x 6</em><br><em># Groups:   date, industry [470]</em><br><em># date       industry location monthly_amount month_number year_number</em><br><em># &lt;date&gt;     &lt;chr&gt;    &lt;chr&gt;             &lt;dbl&gt; &lt;chr&gt;        &lt;chr&gt;      </em><br><em>#   1 2013-01-01 1        1               136081. 01           2013       </em><br><em># 2 2013-01-01 1        10              188735. 01           2013       </em><br><em># 3 2013-01-01 1        2               177840. 01           2013       </em><br><em># 4 2013-01-01 1        3               141632. 01           2013       </em><br><em># 5 2013-01-01 1        4               221058. 01           2013       </em><br><em># 6 2013-01-01 1        5               178138. 01           2013       </em><br><em># 7 2013-01-01 1        6               133400. 01           2013       </em><br><em># 8 2013-01-01 1        7               231599. 01           2013       </em><br><em># 9 2013-01-01 1        8               143778. 01           2013       </em><br><em># 10 2013-01-01 1        9               157416. 01           2013       </em><br><em># ... with 3,876 more rows</em><br><br><br><em># turn the df into a Markdown table format </em><br>rmarkdown::paged_table(aggregated_transactions)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WDGQri7gqCzwPgbAstlBpg.png" /><figcaption>A snapshot of new feature engineering variables</figcaption></figure><p>An aggregated data set using the fields date, industry and location, with a mean of the monthly amount is created. There are a total of 3,886 rows with each row presenting a mean of a monthly amount ranging from 2013 to 2016.</p><h4>Train-Test split</h4><p>Now that we have a new adjusted data set, I’m going to split the data into train and the test set for the aim of building a prediction model. The train set includes three years of data from 2013 to 2016 while the test set includes one last year of data, 2016.</p><p>Additionally, we have 2 requirements for this assignment, which are:</p><ol><li><strong>Basic Model Fitting</strong>: Developing a linear regression model with monthly_amount as the target for industry = 1 and location = 1.</li><li><strong>Advanced Model Fitting</strong>: Developing a linear regression model with monthly_amount as the target for all industries and locations.</li></ol><p>I will generate an additional data set that filters only Industry 1 and Location 1 records. The train and split test for the Advanced Model Fitting section can be kept the same as there are no further adjustments needed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*E0VXhHUTtEM9VKtBpq0_GA.png" /></figure><p>As new dataset is created, I will also use it to create a line plot of the variable monthly_amount for industry = 1 and location = 1 with the purpose of gaining more insights from targeted areas.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aIi2gqH33ogSFtTpEN8wUA.png" /></figure><p>It is clear from the graph that there is a seasonality trend observed from the mean transaction amount of Industry 1 &amp; Location 1. More specifically, a downtrend at the end of the year followed by an up trend at the beginning of the year is presented with the months of December and January are low months for this industry and location, and the sales bounce back from March to June. This pattern of fluctuation is repeated during the year and in the time span of 3 years from 2013 to 2017. On average, the monthly mean amount of sales is increasing slowly over time.</p><p>However, it is worth mentioning that the year-end trend in 2016 was upward, which was the inverse of previous years. As a result, we will need to take a closer look at this occurrence by examining the amount of money moved by month for each year using the graphic below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*I8zRgaxpFJuFngXo5VZ7IA.png" /></figure><p>As can be seen, the anomalous increase towards the end of 2016 was previously noticed as a result of a lack of transaction data in December 2016. As a result, we discovered another insight based on facts observed from the trend chart above.</p><p>Code of the project and relevant files-</p><p><a href="https://github.com/ndleah/transactions">GitHub - ndleah/transactions: 🪙 Linear regression model, predict monthly transaction amount</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7c7e6106b5e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Predict Next Month Transaction with Linear Regression (Part 1)]]></title>
            <link>https://medium.com/@ndleah/predict-next-month-transaction-with-linear-regression-part-1-917a79b6ae0c?source=rss-7ee083e5e515------2</link>
            <guid isPermaLink="false">https://medium.com/p/917a79b6ae0c</guid>
            <category><![CDATA[exploratory-data-analysis]]></category>
            <category><![CDATA[data-wrangling]]></category>
            <category><![CDATA[linear-regression]]></category>
            <category><![CDATA[machine-learning]]></category>
            <dc:creator><![CDATA[Leah Nguyen]]></dc:creator>
            <pubDate>Sat, 11 Jun 2022 03:39:09 GMT</pubDate>
            <atom:updated>2022-06-18T03:53:46.726Z</atom:updated>
            <content:encoded><![CDATA[<h4>Basic Exploration of the dataset</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*V-wyCavYLSQC5lNF" /><figcaption>Photo by <a href="https://unsplash.com/@joshappel?utm_source=medium&amp;utm_medium=referral">Josh Appel</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>In<strong><em>troduction</em></strong></h3><p>This article aims to analyse and provide insights from the monthly transaction data set to understand the customer transaction patterns better. The article also offers a study on the linear regression model, an essential concept in the field of machine learning and explains how this model can assist in the decision-making process of identifying trends in bank transactions within the years 2013–2016.</p><p>To well capture this information, the CRISP-DM management model is adopted to provide a structured planning approach to a data mining project with 6 high-level phases. In particular, these phases assist companies in comprehending the data mining process and serve as a road map for planning and executing a data mining project (Medeiros, 2021).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/319/1*2MNtsFaEYAWcm-ArHwfUaw.png" /><figcaption>Cross-Industry Standard Process for Data Mining (CRISP-DM project, 2000)</figcaption></figure><p>This study explores each of the six phases and the tasks associated with each in the following orders:</p><ol><li>Business understanding</li><li>Data understanding</li><li>Data preparation</li><li>Modelling</li><li>Evaluation</li><li>Deployment</li></ol><p>In the scope of this article, I will cover the first 2 points of the CRISP-DM: <strong>Business Understanding</strong> and <strong>Data Understanding </strong>(EDA — Part 1).</p><p>You can view all my code using for this project on <a href="https://github.com/ndleah/transactions">GitHub</a>.</p><h3><strong>Business Understanding</strong></h3><p><strong>Business Understanding</strong> is the first taken step in the CRISP-DM methodology. In this stage, the main task is to understand the purpose of the analysis and to provide a clear and crisp definition of the problem in respect of understanding the <em>Business objectives</em> and <em>Data mining objectives</em>.</p><p>In our case study, the posed question-related Business object paraphrased from the sales manager’s request is:</p><blockquote>What is driving the trends and increasing total monthly revenue?</blockquote><p>On the other hand, we wish to achieve the data mining object by applying data visualization tools to identify any underlying patterns from the dataset.</p><h3>Data Understanding</h3><p>Following that, the <strong>Data Understanding</strong> phase is where we focus on understanding the data collected to support the Business Understanding and resolve the business challenge (Wijaya, 2021). Data preprocessing and data visualization techniques play an essential role in this. Thus, I’m going to divide the section into 2 main components:</p><ol><li>Exploratory Data Analysis (Part 1) — The Dataset, including:</li></ol><ul><li><strong><em>Stage 1:</em></strong><em> Basic Exploration</em></li><li><strong><em>Stage 2:</em></strong><em> Univariate, Bivariate &amp; Multivariate Analysis</em></li></ul><p>2. Exploratory Data Analysis (Part 2) — The Business Insights</p><p>The data was imported into the software package R to construct visualizations representing the findings found during the analysis.</p><h3>Exploratory Data Analysis (Part 1) — The Dataset</h3><h4>Stage 1: Basic Exploration</h4><p>First, I will run the libraries which will be necessary for reading &amp; manipulating our data and then conducting the graphs.</p><pre><em>##----------------------------------------------------------------</em><br><em>##  Load the Libraries                                          --</em><br><em>##----------------------------------------------------------------</em><br><strong>library</strong>(here)            <em># assess the file path</em><br><strong>library</strong>(DataExplorer)    <em># EDA visualizations</em><br><strong>library</strong>(tidyverse)       <em># data wrangling</em><br><strong>library</strong>(kableExtra)      <em># write table</em><br><strong>library</strong>(bannerCommenter) <em># create comment banner</em><br><strong>library</strong>(ggplot2)         <em># data visualization</em><br><strong>library</strong>(forecast)        <em># times-series forecasting</em><br><strong>library</strong>(ggradar)         <em># plot seasonal trend</em><br><strong>library</strong>(sqldf)           <em># using SQL</em><br><strong>library</strong>(dplyr)           <em># data processing</em><br><strong>library</strong>(ggpubr)          <em># combine plots into single page</em><br>theme_set(theme_pubr())<br><strong>library</strong>(reshape2)        <em># transpose table</em><br><strong>library</strong>(fmsb)            <em># create radar chart</em><br><strong>library</strong>(modelr)          <em># computing regression model performance metrics</em><br><strong>library</strong>(caret)           <em># streamline the model training process</em><br><strong>library</strong>(xts)             <em># convert df to ts object</em></pre><p>Once libraries are loaded, we explore the data with the goal of understanding its dimensions, data types, and distribution of values. In this assignment, a time series data set of financial transactions was used as the major source of data. The attributes information is specifically presented as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*o4CJn7fYPHWHVWuXJ1T44A.png" /><figcaption>Data Description</figcaption></figure><p>After having a good idea of the data description, I want to have an understanding of what the data look like in general. TheDataExplorer package can help to retrieve that piece of information within a few lines of code:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/588/1*c3_miQaEglIvKN2Cnkk6kQ.png" /><figcaption>Data preview</figcaption></figure><p>As apparent from the table, the data records 470,000+ observations across 5 columns, which are equivalent to 94,000+ bank transactions. The 5 features contained in this data set including date, customer_id, industry, location, monthly_amount, clearly indicate the total transaction amounts for customers each month spanning a 3-year period over a range of industries and locations. Therefore, no further justification needs to be made on column names.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*U1eVnZM15AodDEHsPl2-nw.png" /><figcaption>Data columns inspection</figcaption></figure><p>It is also worthwhile to note that features are made up in multiple formats that include both numerical and time-series data. However, the output shows that the date column has the wrong data type which will need to be converted to date format later.</p><p>Additionally, I investigate further by looking at the response field. Recall from the business question, we would expect to use themonthly_amount column as the target field since our goal is to get the predicted value of the monthly transaction value next month. Since the observation in this column is continuous, thus, I can conclude that our problem is defined as the supervised regression problem. Having known this information is extremely essential to selecting the right Machine Learning model in the later stage of this report.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kDoA7RcBh5IzzlX9cKJJsQ.png" /><figcaption>Plot missing values</figcaption></figure><p>From the plot, it shows that there are no missing values on any fields of data. Nevertheless, some data sets define missing observations in categorical/character columns as a new category such as &quot;NA&quot;, &quot;NULL&quot;, etc. so there are chances that we possibly miss these observations, which can lay a tremendous negative impact on the real data distribution. Consequently, a further address on the missing values of our categorical columns need to be made in order to confirm this observation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*daR4rtIoSV4xIo9-LEwW3w.png" /></figure><p>The code output below interprets that there is no new missing value category exists in categorical columns. Thus, we can confirm our hypothesis that there is no missing values from both numerical and categorical columns in this data set. Furthermore, it also indicates that there are 1 row that contain odd value in monthly_amount column that will need to be resolved.</p><h4>Stage 2: Univariate, Bivariate &amp; Multivariate Analysis</h4><p>To evaluate the impact of each feature in the phenomenon, a univariate, bivariate, and multivariate analysis is performed with all features.</p><blockquote><strong><em>Univariate: Check the distribution of each field</em></strong></blockquote><p>The univariate analysis is the study of the data distribution. In research from Sharma (2020), the distributions of the independent variable and the target variable are assumed to be crucial components in building linear models. Therefore, understanding the skewness of data helps us in creating better models.</p><p>Firstly, I will plot a histogram to check which group of industry and location statistically contribute the most to the significant difference.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GWKX3ncUOLjjxDPUc4uBCQ.png" /><figcaption>Distribution histogram</figcaption></figure><p>As can be seen from the plot, the location 1 and 2 made the top contributions for the industry column while the industry 2 and industry 1 occupied for the highest frequency distribution for the location. These results imply that the model can perform better at predicting the total transaction amount for next month with location 1, 2 and/or industry 1, 2.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Qe6MgT8Pm1mIIevkYx_PPw.png" /><figcaption>Boxplot to check for outliers when plotting Monthly Amount against Location &amp; Industry</figcaption></figure><p>Next, the boxplot of sale transactions by the industry and location presents their high variance with a considerable amount of outliers. The median amount of spending per customer for industry 6 and 9 are highest, over 500,000 while the lowest ones belong to industry 1 and 10, at less than 200,000. In terms of locations, most of the locations had a median amount of spending of less than 500,000.</p><blockquote><strong><em>Bivariate Analysis: Relationship between each column and target field &amp; Collinearity</em></strong></blockquote><p>After having known the distribution of our transaction dataset, it is essential to also check for correlation and collinearity assumptions between fields in the Bivariate Analysis. Some basic transformations regarding data types are performed beforehand for the sake of plotting visualizations.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jFmRyJuR9ekHMKBpVawVrQ.png" /><figcaption>Correlation plot</figcaption></figure><p>Having known this information is essentially important to gain a better understanding of the transaction data set and provide great insights for transforming data in the later stage.</p><p>For the Part 2, please visit-</p><p><a href="https://medium.com/@ndleah/predict-next-month-transaction-with-linear-regression-part-2-7c7e6106b5e">Predict Next Month Transaction with Linear Regression (Part 2)</a></p><h3>References</h3><ol><li>Medeiros, L. (2021, December 19). The CRISP-DM methodology — Lucas Medeiros. Medium. <a href="https://medium.com/@lucas.medeiross/the-crisp-dm-methodology-d1b1fc2dc653">https://medium.com/@lucas.medeiross/the-crisp-dm-methodology-d1b1fc2dc653</a></li><li>Sharma, A. (2020, December 23). What is Skewness in Statistics? | Statistics for Data Science. Analytics Vidhya. <a href="https://www.analyticsvidhya.com/blog/2020/07/what-is-skewness-statistics/Wijaya">https://www.analyticsvidhya.com/blog/2020/07/what-is-skewness-statistics/Wijaya</a>, C. Y. (2021, December 19).</li><li>CRISP-DM Methodology For Your First Data Science Project. Medium. <a href="https://towardsdatascience.com/crisp-dm-methodology-for-your-first-data-science-project-769f35e0346c">https://towardsdatascience.com/crisp-dm-methodology-for-your-first-data-science-project-769f35e0346c</a></li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=917a79b6ae0c" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>