<?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 Ester on Medium]]></title>
        <description><![CDATA[Stories by Ester on Medium]]></description>
        <link>https://medium.com/@ester-pinna?source=rss-1488a88dbadb------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*eizdRBBifJTqS3tr3EgElQ.jpeg</url>
            <title>Stories by Ester on Medium</title>
            <link>https://medium.com/@ester-pinna?source=rss-1488a88dbadb------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 15:13:10 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@ester-pinna/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[RFM and Churn Rate analysis to maximize customer value over time with Power BI]]></title>
            <link>https://ester-pinna.medium.com/rfm-and-churn-rate-analysis-to-maximize-customer-value-over-time-with-power-bi-23d35935233a?source=rss-1488a88dbadb------2</link>
            <guid isPermaLink="false">https://medium.com/p/23d35935233a</guid>
            <category><![CDATA[rfm-analysis]]></category>
            <category><![CDATA[customer-lifetime-value]]></category>
            <category><![CDATA[power-bi]]></category>
            <category><![CDATA[churn-rate]]></category>
            <category><![CDATA[dax]]></category>
            <dc:creator><![CDATA[Ester]]></dc:creator>
            <pubDate>Tue, 20 Aug 2024 12:38:48 GMT</pubDate>
            <atom:updated>2024-08-20T12:45:46.476Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="RFM and Churn Rate analysis to maximize customer value over time with Power BI" src="https://cdn-images-1.medium.com/max/1024/1*rOICmzHp7SZLe1qi9zTRIQ.png" /></figure><p>Understanding <strong>how your customers evolve over time</strong> is key to driving sustainable growth.</p><p>In this case study, I dive into a comprehensive analysis using <strong>Power BI</strong> to explore <strong>RFM segmentation</strong>, track <strong>churn rates</strong>, and measure <strong>customer value</strong> across their lifecycle.</p><p>By focusing on these critical metrics, <strong>the report provides actionable insights that can help</strong> shape more <strong>effective marketing strategies and boost overall customer retention.</strong></p><h3><strong>Project Overview</strong></h3><p>This Power BI report is designed to help business owners gain immediate clarity on their customer segments, understand the shifts in customer behavior over time, and identify key opportunities for maximizing customer lifetime value. Here’s <strong>what the report covers</strong>:</p><ol><li><strong>RFM Analysis and Customer Segmentation</strong>: I used the RFM (Recency, Frequency, Monetary) model to <strong>segment customers and monitor their migration between segments</strong>. This analysis reveals how customers move from being “New” to potentially becoming “Top” clients — or, conversely, how they risk slipping into inactivity.</li><li><strong>Churn Rate and Retention Rate</strong>: I focused on understanding churn and retention rates. By analyzing <strong>how these metrics fluctuate over time</strong>, the report provides a clear view of when and why customers are likely to leave, offering actionable <strong>insights to reduce churn and enhance loyalty</strong>.</li><li><strong>Customer Lifetime Value and Average Order Value</strong>: Tracking metrics like Customer Lifetime Value (CLV), Average Order Value (AOV), and purchase frequency over time, <strong>I identified when customers reach their peak value </strong>and <strong>how long they typically stay engaged</strong>.</li></ol><h3><strong>Interactivity and Design Choices</strong></h3><p>The entire <strong>report is designed to be interactive</strong>. I used buttons, slicers, and filters to give users the flexibility to explore the data from multiple angles. I carefully selected the right visualizations to ensure they effectively communicated the insights.</p><h3><strong>Technical Skills Applied</strong></h3><p>This project was a great opportunity to hone my Power BI skills, especially with DAX formulas, and using Power BI’s interactive features like buttons and slicers to make the report dynamic. <strong>The data was originally loaded from CSV files exported directly from Shopify</strong>, but <strong>I’m working on setting up an automatic update</strong> process <strong>using the Shopify API</strong> — work in progress :)</p><p><strong>The most challenging part?</strong></p><p>Visualizing the RFM Migration Analysis in a way that updates automatically over time and reflects changes based on the selected date range. But after some trial and error, I nailed it!</p><h3><strong>Key Visualizations and Insights</strong></h3><h4><strong>Sales and Transactions Over Time</strong></h4><p>This first page of the report provides a quick, high-level overview for business owners to <strong>assess performance at a glance</strong>.</p><p>It showcases <strong>total sales and transaction count over time</strong>, plus some <strong>key KPIs </strong>like the average order value (AOV). The interactive date slicer allows for dynamic analysis, making it easy to track progress and spot key trends within a selected period.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7cn2ZK0h_VEciI8_w537UA.png" /><figcaption>Overview-Key KPIs</figcaption></figure><h4><strong>Customer Status</strong></h4><p>I then moved on to analyze the customer status and their <strong>purchasing behavior</strong>. I examined the number of <strong>active customers</strong> and how many were just <strong>prospects</strong> who had not yet made a purchase.</p><p>The <strong>analysis of purchase frequency</strong> revealed that a large portion of customers have only made a single purchase.</p><p>I also compared <strong>first-time versus returning customers</strong> <strong>in terms of transaction volume and Average Order Value</strong> (AOV), providing insights into which segment drives more value and engagement over time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WLNrDy1qwILAcD4pI9GWXw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*03Z-vqj0HNIbS0lRrRB9bg.png" /><figcaption>Customer Status and Purchase beahaviour</figcaption></figure><h4>RFM segmentation</h4><p>I segmented the customers using the RFM model (<strong>Recency, Frequency, Monetary</strong>). It categorizes customers based on how recently they made a purchase, how often they purchase, and how much they spend.</p><p>This segmentation <strong>helps identify where customers stand</strong> in their lifecycle <strong>and tailor marketing strategies</strong> accordingly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*76FRF118Qfp-8U6xhzGvog.png" /><figcaption>RFM segmentation</figcaption></figure><h4>Standard Deviation</h4><p>I calculated and visualized with some box and whisker box plots the <strong>quartiles for recency, frequency, and monetary</strong> value to understand the distribution and spread of customer behavior across these dimensions.</p><p>This analysis highlights the range and variation within each RFM metric, helping to identify outliers and the characteristics of the top-performing customer segments.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yUViocGfPNxAae9DVdkSaQ.png" /><figcaption>Standard deviation</figcaption></figure><h4>RFM Analysis over time</h4><p>I analyzed various <strong>RFM combinations to apply targeted email marketing strategies.</strong></p><p>For example:<br>“NEW” customers (Recency &lt; 180 days &amp; Frequency = 1), <br>“TOP” customers (Recency &lt; 180 days &amp; Frequency &gt; 3 &amp; Monetary &gt; €150),<br>“TOP AT RISK” customers (Recency ≥ 180 days &amp; Frequency &gt; 2 &amp; Monetary &gt; €100)</p><p>will each receive tailored campaigns, such as re-engagement emails for “TOP AT RISK” customers.</p><p><strong>The treemap visually highlights where customers are concentrated </strong>within these RFM combinations.</p><p><strong>The stacked column chart shows how these segments have evolved over time.</strong> Whith this plot we can see, for instance that initially, almost all customers were “NEW” (brown), while the highest percentage of “TOP” customers (yellow) occurred around 2022–2023.</p><p>I also added buttons so users can easily toggle which RFM segments they want to visualize.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BS7PaBxTBEDVJtTmUJfGFg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*R8XpLxzcRasgY7wokRdlmQ.png" /><figcaption>RFM Analysis — Treemap (on the left) and Stacked column chart (on the right)</figcaption></figure><h4>RFM Migration Analysis</h4><p>The most challenging part of the analysis was fulfilling the client’s specific request to <strong>visualize customer migration between RFM segments to assess the effectiveness of the email marketing strategies.</strong></p><p>I have chosen the <strong>Sankey diagram</strong> that <strong>clearly illustrates the flow and volume</strong> of customers moving from one segment to another.</p><p>Report users can select the time period through the slicer, and I added a button that automatically filters the diagram to show only the key RFM combinations under analysis.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2F9WbzGbW2-BdsMrqo_F4g.png" /><figcaption>RFM Migration Analysis</figcaption></figure><h4>Customer Lifetime Value</h4><p>In this section, I highlighted some of the most critical KPIs for the business: <strong>Customer Lifetime Value</strong> (CLV), <strong>Average Order Value</strong> (AOV), <strong>average order frequency</strong>, and <strong>average customer lifespan</strong>.</p><p>Beyond just presenting these overall values, I also visualized both <strong>AOV and CLV over time</strong>, allowing for analysis of their trends.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ALubscCLsopba5OzzPD5KQ.png" /><figcaption>Customer value</figcaption></figure><h4>Churn Rate and Retention Rate</h4><p>In this section, I focused on analyzing the current Churn Rate and Retention Rate, tracking <strong>how these metrics have evolved over time</strong>.</p><p>The line and clustered column chart illustrates the monthly flow of new customers versus those who have churned month by month.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HCGBBk9QWugdS6H34gLYtA.png" /><figcaption>Churn rate — Line and clustered column chart</figcaption></figure><h4>Correlation Analyses</h4><p>I concluded with some additional <strong>correlation analyses</strong>. First, I explored the <strong>relationship between customer lifespan and Average Order Value</strong> (AOV), identifying <strong>when customers are most valuable</strong> over time.</p><p>Next, I examined the <strong>correlation between the number of orders, average customer lifespan, and churn rate</strong>. The product sold requires multiple uses (and therefore purchases) for its effects to become visible, which is reflected in the chart: the churn rate decreases as the number of orders increases.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iMynNCMrhoHGRPkfqIW2QA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kEo2iQbjFN9PTdk__Ac-bA.png" /><figcaption>Correlation analysis</figcaption></figure><p>This Power BI case study highlights the power of data analysis in understanding customer behavior and improving business outcomes. By leveraging the RFM model and visualizing key metrics, I was able to uncover significant trends and insights that can guide strategic decisions.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=23d35935233a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Calculating Monthly Retention Rate]]></title>
            <link>https://ester-pinna.medium.com/calculating-monthly-retention-rate-36adfd0bedad?source=rss-1488a88dbadb------2</link>
            <guid isPermaLink="false">https://medium.com/p/36adfd0bedad</guid>
            <category><![CDATA[sql]]></category>
            <category><![CDATA[retention-rate]]></category>
            <category><![CDATA[retention]]></category>
            <category><![CDATA[loyalty]]></category>
            <category><![CDATA[business-analyst]]></category>
            <dc:creator><![CDATA[Ester]]></dc:creator>
            <pubDate>Sat, 20 Jan 2024 10:51:52 GMT</pubDate>
            <atom:updated>2024-01-20T10:51:52.776Z</atom:updated>
            <content:encoded><![CDATA[<h4>With SQL</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*El4nrM78O2V_IAYiRKXniA.png" /><figcaption>This guide focuses on calculating the monthly retention rate using SQL</figcaption></figure><p>In the dynamic environment of customer-centric businesses, measuring customer engagement over time is crucial. One significant metric for this is the monthly retention rate.</p><p>Retention rate measures the percentage of customers who remain engaged with a business over time.</p><p>A high retention rate is good for the company because it’s cheaper to retain a customer for a longer period than try to acquire a new one and it’s also good for the customers because if they choose to stay, they are probably happier.</p><h4>Scenario</h4><p>Let’s pretend that “QuickMeal”, a popular food delivery app, contacts you to unserstand their month-on-month loyalty retention.</p><p>For the analysis we’ll use a fictitious table with QuickMeal’s orders data (well, they’re really too few to be true, but it simpifies the understanding process).</p><pre>+------------+---------+<br>| order_date | user_id |<br>+------------+---------+<br>| 2024-01-10 |       1 |<br>| 2024-01-15 |       2 |<br>| 2024-01-20 |       3 |<br>| 2024-02-05 |       1 |<br>| 2024-02-10 |       2 |<br>| 2024-02-15 |       4 |<br>| 2024-02-20 |       5 |<br>| 2024-03-01 |       1 |<br>| 2024-03-05 |       3 |<br>| 2024-03-10 |       4 |<br>| 2024-03-15 |       6 |<br>| 2024-03-20 |       7 |<br>| 2024-04-02 |       2 |<br>| 2024-04-08 |       3 |<br>| 2024-04-14 |       5 |<br>| 2024-04-20 |       8 |<br>| 2024-05-05 |       1 |<br>| 2024-05-10 |       4 |<br>| 2024-05-15 |       6 |<br>| 2024-05-20 |       9 |<br>+------------+---------+</pre><h4>Analysis</h4><p><strong>The retention rate formula</strong> is the number of unique users active in both the current and previous months divided by the number of unique users active in the previous month. That is:</p><p><strong>retention rate = </strong>Number of Retained Customers/Number of Previous Month Customers</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ve3Yb-_8PKBmo9snINHSWQ.png" /></figure><p>So, first, we need to identify the unique customers in each month. To do this we can write:</p><pre>--who are the customers in each month?<br>SELECT DISTINCT<br>    DATE_TRUNC(&#39;MONTH&#39;,order_date)::DATE AS quickmeal_month,<br>    user_id<br>FROM orders</pre><p>Then we need to determine how many of them were present in the previous month and how many of continued to engage in the current month.</p><p>Imagine the output table necessary to extract these data:</p><p>we’ll need a column for each distinct month, a column listing the customers in that month and a third column listing the customers who are present in both the current month and the preceding month.</p><p>To do so, we’ll use a LEFT JOIN on the same table (a self-join) just created with the query above.</p><p>We use a LEFT JOIN because it ensures that all months will be included in the final table (those will be our ‘previous month customers’ and by joining the table with itself, we can compare the user_id from one month (previous) with the user_id from the next month (current).</p><p>Let’s do it.</p><p>First we transform the query above in a CTE so we can use it later:</p><pre>--we put a name to the CTE so we can use it later<br>WITH monthly_activity AS(<br>  SELECT DISTINCT<br>    DATE_TRUNC(&#39;MONTH&#39;,order_date)::DATE AS quickmeal_month,<br>    user_id<br>  FROM orders<br>)</pre><p>So we can create this new table:</p><pre>SELECT *<br>FROM monthly_activity AS pma --&#39;pma&#39; stands for prior month activity<br>LEFT JOIN monthly_activity AS cma --&#39;cma&#39; stands for current month activity<br>  ON (pma.quickmeal_month + INTERVAL &#39;1 month&#39;) = cma.quickmeal_month --the current month is nevertheless the prior one + 1 month<br>  AND pma.user_id = cma.user_id --common users in both months;</pre><p>we can figure a preview of the output like that:</p><pre><br>                   previous<br> previous_month    user_id    current_month   retained<br>+-----------------+---------+--------------- +---------+<br>| quickmeal_month | user_id | quickmeal_month| user_id |<br>+-----------------+---------+--------------- +---------+<br>|   2024-01-01    |       1 |   2024-02-01   |       1 |<br>|   2024-01-01    |       2 |   NULL         |    NULL |<br>|   2024-01-01    |       3 |   NULL         |    NULL |<br>|   2024-02-01    |       1 |   2024-03-01   |       1 |<br>|   2024-02-01    |       2 |   NULL         |    NULL |<br>|   ...           |     ... |   ...          |     ... |<br>+-----------------+---------+----------------+---------+</pre><p>Now, let’s proceed to the final query for calculating the retention rate. We simply extract the element that we want in the final table with the SELECT clause:</p><pre>--who are the customers in each month?<br>WITH monthly_activity AS(<br>  SELECT DISTINCT<br>    DATE_TRUNC(&#39;MONTH&#39;,order_date)::DATE AS quickmeal_month,<br>    user_id<br>  FROM orders<br>)<br><br>--month-on-month retention rate<br>SELECT <br>  pma.quickmeal_month,<br>  COUNT(DISTINCT pma.user_id) AS previous_users, --number of customers in the previous month<br>  COUNT(DISTINCT cma.user_id) AS retained_users, --number of customers retained in each month<br>  ROUND(COUNT(DISTINCT cma.user_id)::NUMERIC / COUNT(pma.user_id) * 100, 0) AS retention_rate<br>FROM monthly_activity AS pma --casting with ::NUMERIC is used to avoid an insignificant result(*)<br>LEFT JOIN monthly_activity AS cma <br>  ON (pma.quickmeal_month + INTERVAL &#39;1 month&#39;) = cma.quickmeal_month <br>  AND pma.user_id = cma.user_id <br>GROUP BY pma.quickmeal_month<br>ORDER BY pma.quickmeal_month;</pre><ul><li><em>::NUMERIC &gt;&gt; casting is used in the calculation just to avoid a not very significant result: in fact in SQL the division between two integer always returns an integer. This can lead to loss of precision due to integer division, where the fractional part of the result is truncated.</em></li></ul><p>That’s it!</p><p>Thank you for reading, and best of luck with your business analyses!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=36adfd0bedad" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Data Analysis and Strategies for E-commerce]]></title>
            <link>https://ester-pinna.medium.com/data-analysis-and-strategies-for-e-commerce-d032255880e0?source=rss-1488a88dbadb------2</link>
            <guid isPermaLink="false">https://medium.com/p/d032255880e0</guid>
            <category><![CDATA[data-analysis]]></category>
            <category><![CDATA[e-commerce-business]]></category>
            <category><![CDATA[power-bi]]></category>
            <category><![CDATA[sql]]></category>
            <category><![CDATA[business-analysis]]></category>
            <dc:creator><![CDATA[Ester]]></dc:creator>
            <pubDate>Wed, 17 Jan 2024 09:45:22 GMT</pubDate>
            <atom:updated>2024-01-20T08:26:34.137Z</atom:updated>
            <content:encoded><![CDATA[<h4>Leveraging SQL and Power BI for Business Analysis</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QgY0aGUzymyrHKXYxNcimA.png" /></figure><p>Welcome to the vibrant world of Brazilian <strong>e-commerce</strong>, where Olist stands out as an innovative and dynamic entity.</p><p>This case study delves into the intricate workings of Olist, a Brazilian marketplace that bridges small businesses across Brazil with a vast network of online sales channels. Utilizing real, anonymized data, this study offers a comprehensive look into <strong>key facets of e-commerce operations.</strong></p><p>Our journey will explore various critical elements of <strong>business analytics</strong>, shedding light on the following areas:</p><ul><li><strong>Revenue Analysis</strong>: Understanding the sources and trends of Olist’s revenue.</li><li><strong>Average Order Value (AOV)</strong>: Examining the average spending per order and its implications for sales strategies.</li><li><strong>Customer Lifetime Value (CLV)</strong>: Evaluating the long-term value of customer relationships and its impact on business growth.</li><li><strong>Customer Segmentation:</strong> Applying techniques like RFM (Recency, Frequency, Monetary) analysis to categorize customers based on their purchasing behaviors.</li><li><strong>Churn Rate Analysis:</strong> Identifying the rate at which customers stop purchasing and strategizing on retention.</li><li><strong>Correlation Studies:</strong> Investigating relationships between various business aspects such as shipping costs, product characteristics, and customer reviews.</li></ul><p>This educational journey aims not just to present data but to transform it into actionable strategies.</p><h3>Company</h3><p><strong>Olist is a Brazilian marketplace</strong> that acts as a powerful link between small businesses spread throughout Brazil and a vast network of online sales channels. With a single contract, <strong>these sellers can sell their products through the Olist Store, benefiting from simplified logistic</strong> management thanks to Olist’s logistic partners.</p><p>More information is available on Olist’s website: <a href="http://www.olist.com/">www.olist.com</a>.</p><p>This case study delves into a public Brazilian dataset, made freely available under a <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">Creative Commons license</a> on the <a href="https://www.kaggle.com/datasets/olistbr/brazilian-ecommerce">Kaggle </a>platform.</p><p>The real data is anonymized and concerns orders made at the Olist Store. <strong>The dataset includes details on 100,000 orders placed between 2016 and 2018.</strong> This information allows for the analysis of orders from multiple perspectives.</p><p>My study focuses on some key business questions that Olist poses to optimize and continuously improve its operations and customer satisfaction.</p><h3>Goal</h3><p>During this analysis, I will answer <strong>the fundamental business question</strong>:</p><p><em>How can Olist optimize its operational and marketing strategy to improve profitability, increase customer satisfaction, and reduce delivery times, based on the analysis of historical sales data, customer reviews, and logistic metrics?</em></p><h3>Database Structure</h3><p>The <strong>Olist database is composed of different datasets</strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*6B_SxlrWMgspI2YI.png" /><figcaption>Source: <a href="https://www.kaggle.com/datasets/olistbr/brazilian-ecommerce">Kaggle Olist Store</a></figcaption></figure><h3>Database Glossary</h3><p><strong>Each entry in the glossary describes in detail the fields present in each of the datasets</strong> that make up the database. This knowledge is <strong>essential to ensure correct interpretation of the data</strong> during analysis.</p><p>I have also renamed each database in a simpler way, by removing the words “olist” and “dataset” from the name of each dataset.</p><p><strong>1. olist_order_customer_dataset</strong></p><p>Renamed: order_customer</p><p>&gt; customerd_id =&gt; key to the orders dataset. Each order has a unique customer_id</p><p>&gt; customers_unique_id =&gt; unique identifier of a customer</p><p>&gt; customers_zip_code_prefix =&gt; first five digits of customer zip code</p><p>&gt; customer_city =&gt; customer city name</p><p>&gt; customer_state =&gt; customer state</p><p><strong>2. olist_order_dataset</strong></p><p>Renamed: order</p><p>&gt; order_id =&gt; unique identifier of the order</p><p>&gt; customer_id =&gt; key to the customer dataset. Each order has a unique customer_id</p><p>&gt; order_status =&gt; reference to the order status (delivered, shipped, etc)</p><p>&gt; order_purchase_timestamp =&gt; shows the purchase timestamp</p><p>&gt; order_approved_at =&gt; shows the payments approval timestamp</p><p>&gt; order_delivered_carrier_state =&gt; shows the order posting timestamp. When it was handled to the logistic partner</p><p>&gt; order_delivered_customer_date =&gt; shows the actual order delivery date to the customer</p><p>&gt; order_estimated_delivery_date =&gt; shows the estimated delivery date that was informed to customer at the purchase moment</p><p><strong>3. olist_order_reviews_dataset</strong></p><p>Renamed: order_reviews</p><p>&gt; review_id =&gt; unique review identifier</p><p>&gt; order_id =&gt; unique order identifier</p><p>&gt; review_score =&gt; note ranging from 1 to 5 given by the customer on a satisfaction survey</p><p>&gt; review_comment_title =&gt; comment title from the review left by the customer, in Portuguese</p><p>&gt; review_comment_message =&gt; comment message from the review left by the customer, in Portuguese</p><p>&gt; review_creation_date =&gt; shows the date in which the satisfaction survey was sent to the customer</p><p>&gt; review_answer_timestamp =&gt; shows satisfaction survey answer timestamp</p><p><strong>4. olist_order_payments_dataset</strong></p><p>Renamed: order_payments</p><p>&gt; order_id =&gt; unique identifier of an order</p><p>&gt; payment_sequential =&gt; a customer may pay an order with more than one payment method. if he does so, a sequence will be created to accomodate all payments</p><p>&gt; payment_type =&gt; method of payment chosen by the customer</p><p>&gt; payment_installments =&gt; number of installments chosen by the customer</p><p>&gt; payment_value =&gt; transaction value. This field tells us the total cost paid by any customer along with the shipping charges. That means the payment value is the summation of price and freight value.</p><p><strong>5. olist_order_items_dataset</strong></p><p>Renamed: order_items</p><p>&gt; order_id =&gt; order unique identifier</p><p>&gt; order_item_id =&gt; sequential number identifying number of items included in the same order</p><p>&gt; product_id =&gt; product unique identifier</p><p>&gt; seller_id =&gt; seller unique identifier</p><p>&gt; shipping_limit_date =&gt; shows the seller shipping limit date for handling the order over to the logistics partner</p><p>&gt; price =&gt; item price</p><p>&gt; freight_value =&gt; item freight value (if an order has more than one item the freight value is splitted between items)</p><p><strong>6. olist_products_dataset</strong></p><p>Renamed: products</p><p>&gt; product_id =&gt; unique product identifier</p><p>&gt; product_category_name =&gt; root category of product, in Portuguese</p><p>&gt; product_name_lenght =&gt; number of characters extracted from the product name</p><p>&gt; product_description_lenght =&gt; number of characters extracted from the product product_description_lenght</p><p>&gt; product_photos_qty =&gt; number of product published product_photos_qty</p><p>&gt; product_weight_g =&gt; product weight measured in grams</p><p>&gt; product_length_cm =&gt; product length measured in centimeters</p><p>&gt; product_height_cm =&gt; product height measured in centimeters</p><p>&gt; product_width_cm =&gt; product width measured in centimeters</p><p><strong>7. olist_sellers_dataset</strong></p><p>Renamed: sellers</p><p>&gt; seller_id =&gt; seller unique identifier</p><p>&gt; seller_zip_code_prefix =&gt; first 5 digits of seller zip codes</p><p>&gt; seller_city =&gt; seller city name</p><p>&gt; seller_state =&gt; seller state</p><p><strong>8. olist_geolocation_dataset</strong></p><p>Renamed: geolocation</p><p>&gt; geolocation_zip_code_prefix =&gt; first 5 digits of zip code</p><p>&gt; geolocation_lat =&gt; latitude</p><p>&gt; geolocation_lng =&gt; longitude</p><p>&gt; geolocation_city =&gt; city name</p><p>&gt; geolocation_state =&gt; state</p><p><strong>9. product_category_name_translation</strong></p><p>Renamed: category_name_translation</p><p>&gt; product_category_name =&gt; category name in Portuguese</p><p>&gt; product_category_name_english =&gt; category name in english</p><p><em>The English category name has been directly integrated into the “products” dataset, allowing us to eliminate this dataset from our analysis.</em></p><h3>ER Schema Processing</h3><p>To<strong> understand the schema of the datasets</strong> in the database, I use the INFORMATION_SCHEMA.COLUMNS view according to this model:</p><pre>SELECT <br>  column_name,<br>  data_type<br>FROM `&lt;your-gcp-project-id&gt;`.&lt;your-bigquery-dataset-name&gt;.INFORMATION_SCHEMA.COLUMNS<br>WHERE table_name=&quot;&lt;your-bigquery-table-name&gt;&quot;</pre><pre>--Database Schema<br>SELECT<br>  column_name,<br>  data_type<br>FROM `my-case-studies-270287.brazilian_marketplace.INFORMATION_SCHEMA.COLUMNS`<br>WHERE table_name = &quot;customers&quot;;</pre><p>This process is applied for each of the datasets (tables) present in the database.</p><p>I use a tool like <a href="http://quickdbd/">QuickDBD</a> to<strong> obtain the ER (Entity-Relationship) schema of the database</strong> along with its primary keys.</p><p>This schema is <strong>used to graphically represent the datasets and the relationships between them.</strong></p><p>Here is the complete procedure used to obtain the ER schema on QuickDBD:</p><ol><li>Retrieve the schema of BigQuery tables. Write the query following this schema:</li></ol><pre>SELECT<br>column_name, data_type<br>FROM<br>`&lt;your-gcp-project-id&gt;`.&lt;your-bigquery-dataset-name&gt;.INFORMATION_SCHEMA.COLUMNS<br>WHERE<br>table_name=&quot;&lt;your-bigquery-table-name&gt;&quot;</pre><p>2. Run the query above</p><p>3. Click “SAVE RESULTS” and then click “Copy to Clipboard”</p><p>4. Paste back to QDB, click “EDIT” and then “Untabify”</p><p>5. Repeat Step 2 for the rest of your tables to be included.</p><p>By doing this, I obtain an <strong>overview of the connections</strong> between the various datasets and their primary keys:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*x0nvkDFe6WbbRoCM.jpeg" /><figcaption>Created with <a href="http://quickdbd/">QuickDBD</a></figcaption></figure><p>Our database also includes the “product_category_name_translation” dataset, which provides English translations for various product categories.</p><p>To make accessing this information more efficient and reduce database complexity, I have chosen to integrate the English translations directly into the “products” table.</p><p>This step eliminates the need for a separate table for translations, thus reducing redundancy and simplifying future database queries.</p><pre>--Add a new column for English category name to the products table<br>ALTER TABLE brazilian_marketplace.products<br>ADD COLUMN product_category_name_eng STRING;<br><br><br>--Update the English category name in products table using the translation table<br>UPDATE brazilian_marketplace.products AS pc<br>SET pc.product_category_name_eng = pct.product_category_name_english<br>FROM brazilian_marketplace.product_category_name_translation AS pct<br>WHERE pc.product_category_name = pct.product_category_name;</pre><h3>Data Cleaning</h3><h3>Check for Missing Values</h3><p><strong>We look for <em>null </em>values</strong>:</p><pre>--Check for Missing Values<br>SELECT *<br>FROM `my-case-studies-270287.brazilian_marketplace.products`<br>WHERE product_category_name_eng IS NULL;</pre><p>And so on for each column of the table, for all tables.</p><p>In the absence of further indications, most <em>null </em>values cannot be fixed.</p><p>However, there are some missing translations (<em>null</em> values) for some product categories:</p><p>For the category “portateis_cozinha_e_preparadores_de_alimentos,” I assign the corresponding English category name “kitchen_and_food_preparation_portable_devices”;</p><p>For the category “pc_gamer,” I assign the corresponding English category name “gaming_pc.”</p><p>The following queries are used to assign the English category name:</p><pre>--Display rows where the English category name is null but the original category name is not null<br>SELECT product_category_name_eng, product_category_name<br>FROM `my-case-studies-270287.brazilian_marketplace.products`<br>WHERE product_category_name_eng IS NULL<br>AND product_category_name IS NOT NULL;<br><br><br>--Update the English category name where missing, based on specific Portuguese category names<br>UPDATE `my-case-studies-270287.brazilian_marketplace.products`<br>SET product_category_name_eng =<br>  CASE<br>    WHEN product_category_name = &quot;portateis_cozinha_e_preparadores_de_alimentos&quot;<br>      THEN &quot;kitchen_and_food_preparation_portable_devices&quot;<br>    WHEN product_category_name = &quot;pc_gamer&quot;<br>      THEN &quot;gaming_pc&quot;<br>    ELSE product_category_name_eng<br>  END<br>WHERE product_category_name_eng IS NULL;</pre><p><em>Null </em>English category names remain where they are also missing in the original language. We cannot replace these <em>null </em>values, as we have no further indications.</p><h3>Checking for Duplicate Data</h3><p>In each table of the database, it is essential to <strong>verify the presence of duplicate rows</strong>. However, in this specific case, such verification is superfluous because each table has a column containing unique data, ensuring the uniqueness of each row.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/357/0*zcxQtvOYIaU_zw1L.jpeg" /></figure><h3>Data Analysis</h3><h3>Profitability Analysis</h3><h3>Total Revenue</h3><p><strong>To accurately determine Olist’s revenue, it is crucial to identify which orders actually contribute to the final revenue.</strong> This process involves analyzing the status of each order to ensure that only those that are completed and delivered are included in the total revenue calculation.</p><pre>--Type of distinct order statuses<br>SELECT DISTINCT order_status<br>FROM `my-case-studies-270287.brazilian_marketplace.orders`;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/263/0*-llzGg-4mJjFzTVa" /></figure><p>There are 8 different order statuses.</p><p><strong>Only the orders with the status “delivered” contribute to the revenue</strong>.</p><pre>--Total revenue<br>SELECT ROUND(SUM(op.payment_value),0) AS total_revenue<br>FROM `my-case-studies-270287.brazilian_marketplace.orders` AS o<br>INNER JOIN `my-case-studies-270287.brazilian_marketplace.order_payments` AS op<br>  ON o.order_id = op.order_id<br>WHERE o.order_status = &quot;delivered&quot;;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/188/0*zYeMUsrIsWbzodL1" /></figure><p>We have a total revenue of €15,422,462. But over what time period?</p><p>To <strong>determine the time period under review</strong>, I proceed with the calculation of the date of the first and last purchase and then the time span under review, in terms of days, weeks, months, years:</p><pre>--Time Span in Days<br>SELECT<br>  MIN(order_purchase_timestamp) AS started_time,<br>  MAX(order_purchase_timestamp) AS ended_time,<br>  DATETIME_DIFF(DATETIME(MAX(order_purchase_timestamp)), DATETIME(MIN(order_purchase_timestamp)), DAY) AS difference<br>FROM `my-case-studies-270287.brazilian_marketplace.orders`;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/587/0*NAZodluUMoOlqLui" /></figure><p>We have <strong>data for the period from September 4, 2016, to October 17, 2018</strong>, a time span of <strong>773 days.</strong></p><pre>--Time Span in Weeks<br>SELECT<br>  MIN(order_purchase_timestamp) AS started_time,<br>  MAX(order_purchase_timestamp) AS ended_time,<br>  DATETIME_DIFF(DATETIME(MAX(order_purchase_timestamp)), DATETIME(MIN(order_purchase_timestamp)), WEEK) AS difference<br>FROM `my-case-studies-270287.brazilian_marketplace.orders`;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/588/0*u-fRcbeNl9PBrBP8" /></figure><p><strong>It’s 110 weeks.</strong></p><pre>--Time Span in Months<br>SELECT<br>  MIN(order_purchase_timestamp) AS started_time,<br>  MAX(order_purchase_timestamp) AS ended_time,<br>  DATETIME_DIFF(DATETIME(MAX(order_purchase_timestamp)), DATETIME(MIN(order_purchase_timestamp)), MONTHS) AS difference<br>FROM `my-case-studies-270287.brazilian_marketplace.orders`;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/584/0*wXDg20QYtTT1_Paz" /></figure><p><strong>It’s 25 months.</strong></p><pre>--Time Span in Years<br>SELECT<br>  MIN(order_purchase_timestamp) AS started_time,<br>  MAX(order_purchase_timestamp) AS ended_time,<br>  DATETIME_DIFF(DATETIME(MAX(order_purchase_timestamp)), DATETIME(MIN(order_purchase_timestamp)), YEAR) AS difference<br>FROM `my-case-studies-270287.brazilian_marketplace.orders`;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/585/0*2U2KiBBweWVJA_JE" /></figure><p><strong>It’s 2 years.</strong></p><p>Therefore, the period is from September 4, 2016, to October 17, 2018, totaling 773 days (about 2 years and 43 days).</p><h3>Analysis of Periodic Average Revenue</h3><p>I can now calculate the average daily, weekly, monthly, and annual revenue:</p><p>The<strong> average daily revenue</strong> is calculated as follows:</p><pre>--Average revenue per day<br>SELECT ROUND(15422462 / 773, 0) AS avg_revenue_per_day;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/208/0*jbyUc0tp1rhtFaOj" /></figure><p>The average daily revenue is €19,951.</p><p>The<strong> average weekly revenue</strong> is calculated as follows:</p><pre>--Average revenue per week<br>SELECT ROUND(15422462 / 110, 0) AS avg_revenue_per_week;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/229/0*7HWyU1Un6Xk7yrB9" /></figure><p>The average weekly revenue is €140,204.</p><p>The<strong> average monthly revenue</strong> is calculated as follows:</p><pre>--Average revenue per month<br>SELECT ROUND(15422462 / 25, 0) AS avg_revenue_per_month;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/219/0*oU4r4Mbaxawcucio" /></figure><p>The average monthly revenue is €616,898.</p><p>The<strong> average annual revenue</strong> is calculated as follows:</p><pre>--Average revenue per year<br>SELECT ROUND(15422462 / 2, 0) AS avg_revenue_per_year;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/234/0*a03hLGqWizX4XrXL" /></figure><p>The average annual revenue is €7,711,231.</p><p><strong>These data are useful</strong> when monitoring the annual, monthly, weekly, and daily trends t<strong>o understand whether the performance is above or below average,</strong> and thus make timely adjustments.</p><p>Let’s now see how revenues have varied over the considered time period:</p><h3>Revenue Trends Over Time</h3><p>Let’s examine the annual and quarterly revenue trends.</p><p>For the <strong>revenue annual trend</strong>:</p><pre>--Changing over years<br>SELECT<br>  EXTRACT(YEAR FROM DATETIME(o.order_purchase_timestamp)) AS year,<br>  ROUND(SUM(op.payment_value),0) AS annual_total_revenue<br>FROM `my-case-studies-270287.brazilian_marketplace.orders` AS o<br>INNER JOIN `my-case-studies-270287.brazilian_marketplace.order_payments` AS op<br>  ON o.order_id = op.order_id<br>WHERE o.order_status = &quot;delivered&quot;<br>GROUP BY year<br>ORDER BY year;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/303/0*gzcXD3cUmjIFYPX9" /></figure><p>This shows a consistent increase in revenue over the years.</p><p>For the <strong>revenue quarterly trend</strong>:</p><pre>--Changing over quarters<br>SELECT<br>  EXTRACT(YEAR FROM DATETIME(o.order_purchase_timestamp)) AS year,<br>  EXTRACT(QUARTER FROM DATETIME(o.order_purchase_timestamp)) AS quarter,<br>  ROUND(SUM(op.payment_value), 0) AS quarter_total_revenue<br>FROM `my-case-studies-270287.brazilian_marketplace.orders` AS o<br>INNER JOIN `my-case-studies-270287.brazilian_marketplace.order_payments` AS op<br>  ON o.order_id = op.order_id<br>WHERE o.order_status = &quot;delivered&quot;<br>GROUP BY year, quarter<br>ORDER BY year, quarter;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/413/0*mUIuhWVZIWPaZap9" /></figure><p>Here, we observe a consistent growth across quarters within the period.</p><h3>Seasonality of Sales</h3><p>A thorough analysis of the seasonality of sales is not possible for the years 2016 and 2018 due to <strong>incomplete data</strong>, as some months are missing:</p><pre>--Changing over months<br>SELECT<br>  EXTRACT(YEAR FROM DATETIME(o.order_purchase_timestamp)) AS year,<br>  EXTRACT(QUARTER FROM DATETIME(o.order_purchase_timestamp)) AS quarter,<br>  EXTRACT(MONTH FROM DATETIME(o.order_purchase_timestamp)) AS month,<br>  ROUND(SUM(op.payment_value),0) AS month_total_revenue<br>FROM `my-case-studies-270287.brazilian_marketplace.orders` AS o<br>INNER JOIN `my-case-studies-270287.brazilian_marketplace.order_payments` AS op<br>  ON o.order_id = op.order_id<br>WHERE o.order_status = &quot;delivered&quot;<br>GROUP BY year, quarter, month<br>ORDER BY year, quarter, month;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/502/0*5jUd1y5Th5qWc4Eb" /></figure><p>However, for the year with complete data — 2017 — <strong>we notice a revenue peak</strong> in November, likely linked to<strong> Black Friday</strong> promotional sales.</p><p>This trend is more clearly visualized in the Power BI chart:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*NzJze4zUwzRV4ooa" /></figure><p>Additionally, we can derive an <strong>average monthly revenue</strong>:</p><pre>--Changing over months<br>SELECT<br>  EXTRACT(MONTH FROM DATETIME(o.order_purchase_timestamp)) AS month,<br>  ROUND(AVG(op.payment_value),0) AS month_avg_revenue<br>FROM `my-case-studies-270287.brazilian_marketplace.orders` AS o<br>INNER JOIN `my-case-studies-270287.brazilian_marketplace.order_payments` AS op<br>  ON o.order_id = op.order_id<br>WHERE o.order_status = &quot;delivered&quot;<br>GROUP BY month<br>ORDER BY month;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/279/0*jIxz_ifCIEZJLHtU" /></figure><p>This analysis highlights that <strong>some months</strong> — April, September, and October <strong>— have the highest revenues.</strong></p><p>This trend is particularly evident in the graph below, which illustrates the monthly revenue distribution.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/621/0*4rIdAgt1LlyqdBDu" /><figcaption>*<strong>Note</strong>: The graph is taken directly from BigQuery’s “preview chart” function.</figcaption></figure><h3>Top Popular Products and Categories</h3><p>Let’s analyze <strong>the 10 most popular products in terms of quantity sold</strong>.</p><p>We only have the product IDs available, which, while not immediately meaningful to us, are certainly useful for the company to match each ID to its specific product.</p><p>Additionally, we observe<strong> the 5 most popular product categories, both in terms of the number of products sold and the revenues generated.</strong></p><p>This analysis will help us understand <strong>which products and categories drive sales and contribute most significantly to the company’s revenue.</strong></p><h3>Top Popular Products in Terms of Items Sold:</h3><pre>--Counting the Top 10 Best-Selling Products<br>SELECT oi.product_id, COUNT(oi.product_id) AS n_of_products_sold, p.product_category_name, p.product_category_name_eng<br>FROM `my-case-studies-270287.brazilian_marketplace.orders` AS o<br>INNER JOIN `my-case-studies-270287.brazilian_marketplace.order_items` AS oi<br>  ON o.order_id = oi.order_id<br>INNER JOIN `my-case-studies-270287.brazilian_marketplace.products` AS p<br>  ON oi.product_id = p.product_id<br>WHERE o.order_status = &quot;delivered&quot;<br>GROUP BY oi.product_id, p.product_category_name, p.product_category_name_eng<br>ORDER BY n_of_products_sold DESC<br>LIMIT 10;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/748/0*Pk-gcQb90_FaBPN9" /></figure><p><strong>The most sold product</strong> is identified by the ID aca2eb7d00ea1a7b8ebd4e68314663af, belonging to the furniture_decor category.</p><h3>Top Popular Categories in Terms of Items Sold:</h3><pre>--Counting the Top 5 Best-Selling Categories<br>WITH total_products_sold AS (<br>    SELECT COUNT(*) AS total_sold<br>    FROM `my-case-studies-270287.brazilian_marketplace.orders` AS o<br>    INNER JOIN `my-case-studies-270287.brazilian_marketplace.order_items` AS oi<br>      ON o.order_id = oi.order_id<br>    WHERE o.order_status = &quot;delivered&quot;<br>)<br><br><br>SELECT p.product_category_name_eng,<br>  COUNT(o.order_id) AS n_of_products_per_category_sold,<br>  ROUND((COUNT(o.order_id)/<br>                          (SELECT total_sold<br>                          FROM total_products_sold))*100, 2) AS percent_sold_per_category<br>FROM `my-case-studies-270287.brazilian_marketplace.orders` AS o<br>INNER JOIN `my-case-studies-270287.brazilian_marketplace.order_items` AS oi<br>  ON o.order_id = oi.order_id<br>INNER JOIN `my-case-studies-270287.brazilian_marketplace.products` AS p<br>  ON oi.product_id = p.product_id<br>WHERE o.order_status = &quot;delivered&quot;<br>GROUP BY p.product_category_name_eng<br>ORDER BY n_of_products_per_category_sold DESC<br>LIMIT 5;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/586/0*khyAWlVHKHxjBttE" /></figure><p><strong>The most popular categories</strong>, in order of revenue generated, are</p><p>bed_bath_table,</p><p>health_beauty,</p><p>sports_leisure.</p><h3>Top Popular Categories in Terms of Items Sold and Revenues Generated</h3><pre>--Comparing Number of Items Sold and Sales Volumes of the Most Popular Categories<br>WITH total_revenue_gained AS (<br>  SELECT SUM(op.payment_value) AS total_revenue<br>  FROM `my-case-studies-270287.brazilian_marketplace.orders` AS o<br>  INNER JOIN `my-case-studies-270287.brazilian_marketplace.order_payments` AS op<br>    ON o.order_id = op.order_id<br>  WHERE o.order_status = &quot;delivered&quot;<br>),<br><br><br>total_products_sold AS (<br>  SELECT COUNT(*) AS total_sold<br>  FROM `my-case-studies-270287.brazilian_marketplace.orders` AS o<br>  INNER JOIN `my-case-studies-270287.brazilian_marketplace.order_items` AS oi<br>    ON o.order_id = oi.order_id<br>  WHERE o.order_status = &quot;delivered&quot;<br>)<br><br><br><br><br>SELECT p.product_category_name_eng,<br>  COUNT(p.product_category_name_eng) AS n_of_products_per_category_sold,<br>  ROUND(SUM(op.payment_value),0) AS revenue_per_category_sold,<br>  ROUND((COUNT(o.order_id)/<br>                          (SELECT total_sold<br>                          FROM total_products_sold))*100, 2) AS percent_sold_per_category,<br>  ROUND((SUM(op.payment_value)/<br>                             (SELECT total_revenue<br>                             FROM total_revenue_gained))*100, 2) AS percent_revenue_per_category<br>FROM `my-case-studies-270287.brazilian_marketplace.orders` AS o<br>INNER JOIN `my-case-studies-270287.brazilian_marketplace.order_items` AS oi<br>  ON o.order_id = oi.order_id<br>INNER JOIN `my-case-studies-270287.brazilian_marketplace.products` AS p<br>  ON oi.product_id = p.product_id<br>INNER JOIN `my-case-studies-270287.brazilian_marketplace.order_payments` AS op<br>  ON o.order_id = op.order_id<br>WHERE o.order_status = &quot;delivered&quot;<br>GROUP BY p.product_category_name_eng<br>ORDER BY n_of_products_per_category_sold DESC<br>LIMIT 5;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*qT1LyaLlW3X2UQcJ" /></figure><p>From the analysis results, we can observe:</p><p><strong>Most Popular Category in Terms of Number of Items Sold</strong></p><ul><li>The category “bed_bath_table” emerges as the most popular in terms of items sold, with 11,650 items. This accounts for over 10% of the total sales, indicating that more than one out of every ten items sold belongs to this category.</li></ul><p><strong>Most Popular Category in Terms of Revenue</strong></p><ul><li>Although “bed_bath_table” also leads in revenue percentage (10.98%), the “health_beauty” category nearly matches it in overall revenue (10.51%) despite a lower quantity of items sold. This suggests that items in the “health_beauty” category might have a higher average price compared to “bed_bath_table”.</li><li>A similar observation is noted for the “computers_accessories” category. Although it accounts for 10.05% of revenue, it represents only 7.17% of sales, indicating that, like “health_beauty”, it may have higher-priced items on average</li></ul><p><strong>Knowing which products and categories are most sold can be extremely useful for optimizing inventory and shaping marketing strategies</strong>. For instance, Olist could focus on stocking higher quantities of popular categories while also considering pricing strategies that reflect the demand and popularity of these categories.</p><p>This approach could help in maximizing revenue and ensuring customer satisfaction by readily having in-demand products available.</p><h3>AOV — Calculation of the Average Order Value</h3><pre>--AOV = revenue_tot / num_ordini<br>WITH tot_revenue AS (<br>  SELECT ROUND(SUM(op.payment_value),0) AS total_revenue<br>  FROM `my-case-studies-270287.brazilian_marketplace.orders` AS o<br>  INNER JOIN `my-case-studies-270287.brazilian_marketplace.order_payments` AS op<br>    ON o.order_id = op.order_id<br>  WHERE o.order_status = &quot;delivered&quot;<br>  ),<br><br><br>tot_orders AS (<br>  SELECT COUNT(DISTINCT order_id) AS n_of_orders<br>  FROM `my-case-studies-270287.brazilian_marketplace.orders`<br>)<br><br><br>SELECT<br>  ROUND((SELECT total_revenue<br>  FROM tot_revenue)/<br>                (SELECT n_of_orders<br>                FROM tot_orders),0) AS AOV;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/167/0*uOI4Ao3rTj1qkhpB" /></figure><p><strong>The Average Order Value</strong> is €155.</p><h3>CLV — Assessing Customer Lifetime Value Over Time</h3><p>To calculate the Customer Lifetime Value (CLV), I’ve chosen three methods:</p><ul><li><strong>Average Monthly CLV for the Entire Customer Base.</strong></li></ul><p>This approach provides an aggregated and periodic view of the CLV. It is <strong>useful for monitoring trends over time</strong>, assessing the effectiveness of marketing campaigns or strategic changes, and for making periodic comparisons (for example, monthly or yearly).</p><ul><li><strong>CLV for Each Individual Customer.</strong></li></ul><p>This approach is useful for understanding the value of each customer individually and can be <strong>used to segment customers</strong>, identify high-value customers, and customize marketing or sales strategies based on the value of each customer.</p><ul><li><strong>CLV as an Average of All Customers.</strong></li></ul><p>This method is useful for a general assessment of the value of the customer portfolio. It can be <strong>used for strategic planning and as a benchmark</strong> to evaluate the effectiveness of marketing and sales initiatives. It helps to determine the reasonable level of investment in customer acquisition and retention based on the average value that a customer brings over their lifetime.</p><p>For example, the Customer Acquisition Cost (CAC) should always be lower than the CLV.</p><p><strong>To calculate the customer lifetime value, I will first need to calculate other metrics</strong>.</p><p>Here is a list:</p><p><strong>Average Purchase Value</strong> = total revenue/total number of purchases</p><p><strong>Average Purchase Frequency Rate</strong> = total number of purchases/total number customers</p><p><strong>Average Customer Value</strong> = Average Purchase Value*Average Purchase Frequency Rate</p><p><strong>Average Customer Lifespan</strong> = sum of customer lifespans/total number of customers</p><p>At this point, we can calculate the Customer Lifetime Value as follows:</p><p><strong>Customer Lifetime Value </strong>= Average Customer Value*Average Customer Lifespan</p><h3>Average Monthly CLV for the Entire Customer Base</h3><p>(step by step)</p><pre>--Customer Lifetime Value as a Monthly Average<br><br><br>/* Step 1: Calculate Total Revenue and Total Number of Purchases<br>Calculate the total revenue and the total number of purchases on a monthly basis. */<br>WITH monthly_revenue_purchases AS (<br>  SELECT<br>    DATE_TRUNC(o.order_purchase_timestamp, MONTH) AS month,<br>    SUM(op.payment_value) AS total_revenue,<br>    COUNT(o.order_id) AS total_purchases<br>  FROM `brazilian_marketplace.orders` AS o<br>  INNER JOIN `brazilian_marketplace.order_payments` AS op<br>    ON o.order_id = op.order_id<br>  WHERE o.order_status = &quot;delivered&quot;<br>  GROUP BY month<br>),<br><br><br>/* Step 2: Calculate the Average Purchase Value (APV)<br>Calculate the APV as the total revenue divided by the total number of purchases. */<br>average_purchase_value AS (<br>  SELECT<br>    month,<br>    total_revenue/total_purchases AS average_purchase_value<br>    FROM monthly_revenue_purchases<br>),<br><br><br>/* Step 3: Calculate Total Number of Customers<br>Determine the total number of unique customers each month. */<br>monthly_customers AS (<br>  SELECT<br>    DATE_TRUNC(o.order_purchase_timestamp, MONTH) AS month,<br>    COUNT(DISTINCT c.customer_unique_id) AS total_customers<br>  FROM `brazilian_marketplace.orders` AS o<br>  INNER JOIN `brazilian_marketplace.customers` AS c<br>    ON o.customer_id = c.customer_id<br>  WHERE o.order_status = &quot;delivered&quot;<br>  GROUP BY month<br>),<br><br><br>/* Step 4: Calculate the Average Purchase Frequency Rate (APFR)<br>Calculate the APFR as the total number of purchases divided by the total number of customers. */<br>purchase_frequency AS (<br>  SELECT<br>    mrp.month,<br>    mrp.total_purchases/mc.total_customers AS average_purchase_frequency_rate<br>  FROM monthly_revenue_purchases AS mrp<br>  INNER JOIN monthly_customers AS mc<br>    ON mrp.month =  mc.month<br>),<br><br><br>/* Step 5: Calculate the Average Customer Value (ACV)<br>Multiply the APV by the APFR to obtain the ACV. */<br>average_customer_value AS (<br>  SELECT<br>    av.month,<br>    av.average_purchase_value*pf.average_purchase_frequency_rate AS average_customer_value<br>  FROM average_purchase_value AS av<br>  INNER JOIN purchase_frequency AS pf<br>    ON av.month = pf.month<br>),<br><br><br>/* Step 6: Calculate the Average Customer Lifespan (ACL)<br>For the ACL, you&#39;ll need the total duration of each customer&#39;s relationship and its average. */<br>customer_lifespans AS (<br>customer_lifespans AS (<br>  SELECT<br>    c.customer_unique_id,<br>    DATE_DIFF(MAX(DATETIME(o.order_purchase_timestamp)), MIN(DATETIME(o.order_purchase_timestamp)), MONTH) AS customer_lifespan<br>  FROM `brazilian_marketplace.orders` AS o<br>  INNER JOIN `brazilian_marketplace.customers` AS c<br>    ON o.customer_id = c.customer_id<br>  WHERE o.order_status = &quot;delivered&quot;<br>  GROUP BY c.customer_unique_id<br>),<br><br><br>average_customer_lifespan AS (<br>  SELECT<br>    AVG(customer_lifespan) AS average_customer_lifespan<br>  FROM<br>    customer_lifespans<br>)<br><br><br>/* Step 7: Calculate the Customer Lifetime Value (CLV)<br>Finally, multiply the ACV by the ACL to obtain the CLV. */<br>SELECT<br>  acv.month,<br>  acv.average_customer_value * acl.average_customer_lifespan AS customer_lifetime_value<br>FROM<br>  average_customer_value AS acv<br>CROSS JOIN<br>  average_customer_lifespan AS acl<br>ORDER BY month;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/430/0*Ys087MFvomgciPwH" /></figure><p>We then obtain the <strong>average monthly CLV.</strong></p><h3>CLV for Each Individual Customer</h3><pre>--Customer Lifetime Value for Each Individual Customer<br>WITH customer_orders AS (<br>    SELECT<br>        c.customer_unique_id,<br>        COUNT(o.order_id) AS total_orders,<br>        ROUND(AVG(p.payment_value),2) AS avg_order_value<br>    FROM<br>        `brazilian_marketplace.customers`AS c<br>    JOIN<br>        `brazilian_marketplace.orders` AS o<br>      ON c.customer_id = o.customer_id<br>    JOIN<br>        `brazilian_marketplace.order_payments` AS p ON o.order_id = p.order_id<br>    WHERE<br>        o.order_status = &#39;delivered&#39;<br>    GROUP BY<br>        c.customer_unique_id<br>),<br>customer_lifetime AS (<br>    SELECT<br>        customer_unique_id,<br>        DATE_DIFF(MAX(DATETIME(o.order_purchase_timestamp)), MIN(DATETIME(o.order_purchase_timestamp)), MONTH) AS customer_lifetime_months<br>    FROM<br>        `brazilian_marketplace.orders` AS o<br>    JOIN<br>        `brazilian_marketplace.customers` AS c<br>      ON o.customer_id = c.customer_id<br>    GROUP BY<br>        customer_unique_id<br>)<br>SELECT<br>  co.customer_unique_id,<br>  co.avg_order_value,<br>  co.total_orders,<br>  cl.customer_lifetime_months,<br>  ROUND((co.avg_order_value * co.total_orders * cl.customer_lifetime_months),2) AS clv<br>FROM<br>  customer_orders AS co<br>JOIN<br>  customer_lifetime AS cl<br>  ON co.customer_unique_id = cl.customer_unique_id<br>ORDER BY clv DESC;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/733/0*D2eHgob8lXyKCbKu" /><figcaption>*<strong>Note</strong>: The image above shows only a preview of the result, as the rows are 93,357, which is the number of unique customers. Also, I have sorted the table by the CLV column in descending order; otherwise, the preview would have shown only those customers with a CLV of zero.</figcaption></figure><p>We obtain<strong> the CLV for each customer.</strong></p><h3>CLV as an Average of All Customers</h3><pre>--Average Customer Lifetime Value<br>WITH customer_orders AS (<br>    SELECT<br>        c.customer_unique_id,<br>        COUNT(o.order_id) AS total_orders,<br>        AVG(p.payment_value) AS avg_order_value<br>    FROM<br>        `brazilian_marketplace.customers`AS c<br>    JOIN<br>        `brazilian_marketplace.orders` AS o<br>      ON c.customer_id = o.customer_id<br>    JOIN<br>        `brazilian_marketplace.order_payments` AS p ON o.order_id = p.order_id<br>    WHERE<br>        o.order_status = &#39;delivered&#39;<br>    GROUP BY<br>        c.customer_unique_id<br>),<br>customer_lifetime AS (<br>    SELECT<br>        customer_unique_id,<br>        DATE_DIFF(MAX(DATETIME(o.order_purchase_timestamp)), MIN(DATETIME(o.order_purchase_timestamp)), MONTH) AS customer_lifetime_months<br>    FROM<br>        `brazilian_marketplace.orders` AS o<br>    JOIN<br>        `brazilian_marketplace.customers` AS c<br>      ON o.customer_id = c.customer_id<br>    GROUP BY<br>        customer_unique_id<br>),<br>customer_lifetime_value_per_customer AS (<br>    SELECT<br>        co.customer_unique_id,<br>        co.avg_order_value,<br>        co.total_orders,<br>        cl.customer_lifetime_months,<br>        co.avg_order_value * co.total_orders * cl.customer_lifetime_months AS clv<br>    FROM<br>        customer_orders AS co<br>    JOIN<br>        customer_lifetime AS cl<br>      ON co.customer_unique_id = cl.customer_unique_id<br>)<br>SELECT<br>  AVG(clv) AS avg_clv<br>FROM customer_lifetime_value_per_customer;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/693/0*C6xl8ZAcj-ot5ic0" /></figure><p>From this result, we get the following:</p><p><strong>Average Customer Lifetime Value (CLV):</strong> amounts to €29.21, representing the average expenditure each customer is expected to make over the course of their interaction with the company. This data is crucial for defining customer acquisition strategies, as it suggests that the cost to acquire a single customer (Customer Acquisition Cost, CAC) should ideally be less than this figure.</p><p><strong>Average receipt: </strong>on average, each order made by customers is valued at about €157.65. That is the average receipt, which is comparable to the AOV we saw earlier; the slight difference between the two values is due to the method of calculating.</p><p><strong>Average number of orders:</strong> on average, each customer has made about 1.08 orders. This number close to 1 suggests that most customers have made only one purchase.</p><p><strong>Average Lifespan</strong>: average duration of the customer relationship is about 0.09 months, or approximately 2.7 days. This very low value indicates that most customer relationships do not extend beyond the initial month.</p><h3>Segmentazione dei Clienti con RFM</h3><p>I have decided to<strong> segment customers based on the potential value they could bring to the company.</strong></p><p>To understand their potential value, I will evaluate three characteristics:</p><p><strong>Recency(R)</strong>: the <strong>time since a customer last made a purchase</strong>. Customers who have made a purchase more recently are considered more valuable, as they are more likely to make a purchase again soon.</p><p><strong>Frequency(F)</strong>: the <strong>number of purchases </strong>a customer has made<strong> over a period</strong>. Customers who make frequent purchases are considered more valuable, as they are more likely to continue making purchases in the future.</p><p><strong>Monetary(M)</strong> value: <strong>the total amount of money a customer has spent</strong>. Customers who spend more money are considered more valuable, as they will likely generate more revenue for the business.</p><p>I am therefore segmenting customers based on what is called “<strong>RFM segmentation</strong>” (i.e., based on various combinations of the factors just listed).</p><p>I calculate the <strong>Recency </strong>for each customer:</p><pre>--recency<br>--How many days have passed since the last purchase for each customer?<br>WITH report_end AS (<br>  SELECT MAX(order_purchase_timestamp) AS ended_time<br>FROM `brazilian_marketplace.orders`<br>)<br>SELECT<br>  c.customer_unique_id,<br>  DATE_DIFF((SELECT ended_time FROM report_end), MAX(o.order_purchase_timestamp), DAY) as recency,<br>FROM `brazilian_marketplace.customers` AS c<br>INNER JOIN `brazilian_marketplace.orders` AS o<br> ON c.customer_id = o.customer_id<br>WHERE o.order_status = &quot;delivered&quot;<br>GROUP BY c.customer_unique_id;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/378/0*o-QO7n-IlR73OUwz" /></figure><p>The Recency column shows the days passed since the last purchase.</p><p>I calculate the <strong>Frequency </strong>for each customer:</p><pre>--frequency<br>--How often has the customer made a purchase?<br>SELECT<br>  c.customer_unique_id,<br>  COUNT(o.order_id) as frequency,<br>FROM `brazilian_marketplace.customers` AS c<br>INNER JOIN `brazilian_marketplace.orders` AS o<br> ON c.customer_id = o.customer_id<br>WHERE o.order_status = &quot;delivered&quot;<br>GROUP BY c.customer_unique_id;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/372/0*VGE0snAmJhDdwu0R" /></figure><p>The Frequency column shows the number of orders for each individual customer.</p><p>I calculate the <strong>Monetary </strong>Value for each customer:</p><pre>--monetary<br>--What is the value contributed by each individual customer?<br>SELECT<br>  c.customer_unique_id,<br>  SUM(op.payment_value) as monetary<br>FROM `brazilian_marketplace.customers` AS c<br>INNER JOIN `brazilian_marketplace.orders` AS o<br> ON c.customer_id = o.customer_id<br>INNER JOIN `brazilian_marketplace.order_payments` AS op<br> ON o.order_id = op.order_id<br>WHERE o.order_status = &quot;delivered&quot;<br>GROUP BY c.customer_unique_id;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/360/0*DlfGDsZvWqND0oO9" /></figure><p>The Monetary column shows the total monetary contribution from each customer (i.e., the revenue generated by each customer).</p><p><strong>We can also combine the three queries into a single table</strong> using a CTE, so we can call it up more quickly during analysis:</p><pre>--Combine the three queries -- recency, frequency, monetary -- into a single table<br>WITH report_end AS (<br>  SELECT<br>MAX(order_purchase_timestamp) AS ended_time<br>  FROM `brazilian_marketplace.orders`<br>),<br><br><br>rfm_customers AS (<br>  SELECT<br>    c.customer_unique_id,<br>    DATE_DIFF((SELECT ended_time FROM report_end), MAX(o.order_purchase_timestamp), DAY) as recency, --Recency calculation<br>    COUNT(o.order_id) AS frequency, --Frequency calculation<br>    ROUND(SUM(op.payment_value)) AS monetary --Monetary calculation<br>  FROM `brazilian_marketplace.customers` AS c<br>  INNER JOIN `brazilian_marketplace.orders` AS o<br>    ON c.customer_id = o.customer_id<br>  INNER JOIN `brazilian_marketplace.order_payments` AS op<br>    ON o.order_id = op.order_id<br>  WHERE o.order_status = &quot;delivered&quot;<br>  GROUP BY c.customer_unique_id<br>)<br><br><br><br><br>-- We will now only refers to this CTE<br>SELECT * FROM rfm_customers<br><br></pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/570/0*I8fxc7mh6cNWCoGt" /></figure><p><strong>To segment our customers, we will use quintile division</strong> (i.e., dividing into 5 groups)<strong> for each of the three RFM values</strong> (Recency, Frequency, Monetary).</p><p>This process creates a grid of <strong>125 </strong>(5x5x5) <strong>possible combinations</strong>.</p><p><strong>Each of these combinations represents a different customer segment.</strong></p><p>Among these combinations, we might identify customers who have contributed the highest monetary value (the highest Monetary value), those who made their last purchase a long time ago (the lowest Recency value), or those who make purchases most frequently (the highest Frequency value).</p><p>We can also identify customers with combinations of factors, such as those who have contributed the highest monetary value and have the highest frequency — which would be our best customers — and those, for example, who have a low purchase frequency but have spent a lot in a few purchases.</p><p><strong>The combinations we consider will depend on our business strategies.</strong></p><p><strong>Suppose we want to identify customer groups</strong> that bring the highest value to the business, the most loyal ones, those “at risk,” and the most promising ones:</p><ul><li><strong>High Value</strong>: Customers with <strong>the highest monetary value and purchase frequency.</strong></li><li><strong>Loyal</strong>: Customers with <strong>the highest purchase frequency</strong>.</li><li><strong>At Risk</strong>: Customers with <strong>the most outdated last purchase</strong>.</li><li><strong>Promising</strong>: Customers with <strong>the most recent purchase</strong>.</li></ul><p>To identify these groups, I will first divide the customers into quintiles for each of the 3 factors (Recency, Frequency, Monetary) using the NTILE() function.</p><pre>--I divide my customers into 5 groups based on the three RFM factors<br>quintiles_customers AS (<br>  SELECT<br>  customer_unique_id,<br>  NTILE(5) OVER (ORDER BY recency) AS recency_quintile,<br>  NTILE(5) OVER (ORDER BY frequency) AS frequency_quintile,<br>  NTILE(5) OVER (ORDER BY monetary) AS monetary_quintile<br>FROM<br>  rfm_customers<br>)<br><br><br><br>--We will now only refers to this CTE<br>SELECT * FROM quintiles_customers</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/575/0*6MFTncm0kXgQ96rB" /></figure><p>Thus, each unique customer will have a value from 1 to 5 for each of the 3 RFM factors depending on the quintile they belong to for that single factor.</p><p><strong>We can now find customers with the sought-after combinations of characteristics:</strong></p><ul><li>High Value: Customers in the top quartile for monetary value and frequency and any quartile for Recency.</li></ul><p>That is: monetary_quintile &gt;= 4 AND frequency_quintile &gt;= 4</p><ul><li>Loyal: Customers in the top quartile for frequency and any quartile for monetary value and Recency.</li></ul><p>That is: frequency_quintile &gt;= 4</p><ul><li>At Risk: Customers in the bottom quartile for Recency and any quartile for frequency and monetary value.</li></ul><p>That is: recency_quintile &lt;= 1</p><ul><li>Promising: Customers in the top quartile for Recency and any quartile for frequency and monetary value.</li></ul><p>That is: recency_quintile &gt;= 4</p><pre>--Customer Segmentation<br>SELECT<br>  customer_unique_id,<br>  CASE<br>    WHEN monetary_quintile &gt;= 4 AND frequency_quintile &gt;= 4 THEN &quot;High Value&quot;<br>    WHEN frequency_quintile &gt;= 4 THEN &quot;Loyal&quot;<br>    WHEN recency_quintile &lt;= 1 THEN &quot;At Risk&quot;<br>    WHEN recency_quintile &gt;= 4 THEN &quot;Promising&quot;<br>  END AS segment<br>FROM quintiles_customers;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/427/0*6eNx23LTC7Ws3w3T" /></figure><p><strong>Each customer is assigned to a group according to the given segmentation.</strong></p><p>Of course, we will also have <em>null </em>values for all those combinations that do not meet any of the 4 identified combinations. They can be useful in the future when we decide to add other customer segments to test a strategy.</p><p><strong>Among the segments we are not analyzing but may decide to target in the future</strong> with a dedicated strategy are:</p><ul><li>New or Occasional Customers: These customers might have a low frequency (lower quintiles for F) but have made purchases recently (higher quintiles for R). They can have either a high or low monetary value.</li><li>Dormant Customers: Customers with low Recency and Frequency values but who have had a high monetary value in the past. These customers have made purchases in the past but have not made any recent purchases.</li><li>Sporadic Low-Value Customers: Customers with low frequency and low monetary value, but with a recent purchase. They could be new customers or customers who make purchases only occasionally.</li><li>Low-Value Loyal Customers: Customers with high frequency (purchase often) but low monetary value. These customers are regular but spend less per purchase.</li><li>High-Value One-Time Customers: Customers with a low frequency but who have spent a lot in a few purchases. They could be customers who make large-sized purchases but not frequently.</li><li>Lost Customers: Customers with low values in both Recency and Frequency, and possibly in Monetary as well. They have made purchases in the past but are no longer active.</li></ul><p><strong>Now, let’s return to the 4 identified groups (“High Value”, “Loyal”, “At Risk”, “Promising”) to see how to visualize them graphically.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/641/0*a7UeI6_qxaryMW4u" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/639/0*OMIWlvHHthrXje4R" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/630/0*cMJ_46glta3Ftl4b" /></figure><h3>Churn Rate — Customer Turnover Rate</h3><p>The churn rate is the percentage of customers who stop purchasing from a store — in our case, the Olist marketplace — during a specified period.</p><p>First, we need to understand <strong>what time frame to look at</strong>: are we interested in analyzing the monthly churn rate? Yearly?</p><pre>--Calculation of the average customer lifespan within my customer base to determine the time period for analyzing the churn rate<br>WITH lifespan_per_customer AS (<br>  SELECT<br>  c.customer_unique_id,<br>  DATE_DIFF(MAX(DATETIME(o.order_purchase_timestamp)), MIN(DATETIME(o.order_purchase_timestamp)), DAY) AS customer_lifespan<br>  FROM `brazilian_marketplace.customers` AS c<br>INNER JOIN `brazilian_marketplace.orders` AS o<br>  USING (customer_id)<br>WHERE o.order_status = &quot;delivered&quot;<br>GROUP BY c.customer_unique_id<br>ORDER BY customer_lifespan DESC<br>)<br><br><br>SELECT ROUND(AVG(customer_lifespan),2) AS avg_customer_lifespan<br>FROM lifespan_per_customer;</pre><p><strong>In this case, we have a database that covers a period of a year and a few months,</strong> so it wouldn’t make sense to analyze the annual churn rate.</p><p>We know the average customer lifespan is 2.7 days, so it would make sense to analyze the churn rate on a weekly or monthly basis.</p><p><strong>We will look at the monthly churn rate of customers.</strong></p><pre>--Comparing the number of active customers each month with the previous month<br>WITH monthly_customers AS (<br>SELECT<br>  DATETIME_TRUNC(o.order_purchase_timestamp, MONTH) AS month,<br>  COUNT(DISTINCT c.customer_unique_id) AS month_customers<br>FROM `brazilian_marketplace.customers` AS c<br>INNER JOIN `brazilian_marketplace.orders` AS o<br>  USING(customer_id)<br>WHERE order_status = &quot;delivered&quot;<br>GROUP BY month<br>ORDER BY month ASC<br>),<br><br><br>previous_monthly_customers AS (<br>  SELECT<br>    month,<br>    month_customers,<br>    LAG(month_customers, 1) OVER(ORDER BY month) AS previous_month_customers<br>  FROM monthly_customers<br>  ORDER BY month ASC<br>),<br><br><br>comparison_monthly_customers AS (<br>  SELECT<br>    month,<br>    month_customers,<br>    previous_month_customers,<br>    month_customers - previous_month_customers AS monthly_variation,<br>  FROM previous_monthly_customers<br>  ORDER BY month ASC<br>)<br><br><br>--Calculating the monthly variation rate in the number of customers<br> SELECT<br>    month,<br>    month_customers,<br>    previous_month_customers,<br>    monthly_variation,<br>    ROUND((monthly_variation/previous_month_customers)*100, 2) AS monthly_variation_rate<br>FROM comparison_monthly_customers<br>ORDER BY month ASC</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/884/0*MfuhV0nEZoxNA76m" /><figcaption><strong>NOTE</strong>: In this case, in the monthly_variation_rate column, we have negative numbers when the variation represents a churn rate, and a positive number when the variation represents a growth rate.</figcaption></figure><p>Let’s transform the last query into a CTE, name it “variation_rate_per_month” so that we can use it to calculate the average rate of variation.</p><pre>. . .<br>variation_rate_per_month AS(<br>   SELECT<br>    month,<br>    month_customers,<br>    previous_month_customers,<br>    monthly_variation,<br>    ROUND((monthly_variation/previous_month_customers)*100, 2) AS monthly_variation_rate<br>FROM comparison_monthly_customers<br>ORDER BY month ASC<br>)<br><br><br>SELECT ROUND(AVG(monthly_variation_rate),2) AS avg_variation_rate<br>FROM variation_rate_per_month;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/208/0*XTI38M7JLzsFM62a" /></figure><p>We notice that an average variation rate of <strong>+4,455.01% is not a churn rate but rather an extremely surprising growth rate</strong>, perhaps because the available data covers the very first months of Olist’s activity.</p><h3>Correlation between Shipping Cost and Product Characteristics</h3><p>For this analysis, I used the Power BI visualization tool.</p><p>I analyzed the <strong>correlation between the average freight value and some product characteristics, such as weight and volume.</strong></p><h3>Correlation between Average Shipping Cost and Product Weight</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*s1eHQW-gAkxI_CkU" /></figure><p>From the graph, we can see that there is a <strong>positive correlation between the weight of the product and the freight value</strong>: as the weight of the product increases, so does the freight value. However, this relationship does not seem to be strongly linear, suggesting that <strong>factors other than weight influence freight value.</strong></p><p>We also observe that most of the data points are concentrated in the lower-left corner, indicating that <strong>most products have a low weight</strong>.</p><p>There are also some <strong>outliers</strong>, particularly some points that indicate a very high shipping value compared to weight. This could be due to special cases where shipping costs are abnormally high, perhaps due to international shipping, remote areas, or premium shipping services.</p><p>We also see that while the weights of the products seem to cover a very wide range, the shipping costs seem to stay below a certain threshold.</p><h3>Correlation between Average Shipping Cost and Product Volume</h3><p>I add a column in PowerBI to calculate the volume:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/818/0*-sGAqSn02TEY0WRV" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*jfjaXmqMff7ka9df" /></figure><p>There does <strong>not seem to be a strong linear correlation between the volume of the product and the average freight value</strong>. While some points indicate that the shipping value increases with volume, there are many exceptions, and the data is quite scattered.</p><p>As in the previous graph, most of the data is concentrated in the lower-left part of the graph, suggesting that for<strong> many products, both volume and freight value are relatively low</strong>.</p><p>As before, there are also some <strong>outliers</strong>, which seem to suggest that the freight value depends on factors other than just volume.</p><p>This suggests that other factors not represented in this graph influence the freight value.</p><h3>Correlation between Average Shipping Cost, Volume, and Weight of the Product</h3><p>Let’s see the <strong>correlation between weight, volume, an freight value</strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*7Id-5UY3lQmnNd9-" /></figure><p>Most of the data is still concentrated in the lower-left part of the graph, indicating that <strong>both lower volumes and weights tend to have lower freight values.</strong></p><p>There are some <strong>outliers </strong>with high freight values scattered along the x-axis, suggesting that factors other than weight and volume can influence the freight value.</p><p><strong>To better understand the relationship, it would be useful to examine other factors</strong>, such as shipping distance.</p><h3>Review Score and Influencing Factors</h3><h3>Average Review Score</h3><pre>--Calculating the average review score<br>SELECT ROUND(AVG(review_score),2) AS avg_reviews_score<br>FROM `brazilian_marketplace.order_reviews`;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/165/0*9OnYuAnHDzLjPt5R" /></figure><p>The <strong>average score</strong> is 4.09, which is very good considering that the maximum score is 5.</p><h3>Factors Influencing Review Score</h3><h4>Review Score by Product Category</h4><pre>--Product Category<br>SELECT<br>    p.product_category_name,<br>    ROUND(AVG(r.review_score),2) AS avg_review_score<br>FROM<br>    `brazilian_marketplace.order_reviews` AS r<br>JOIN<br>    `brazilian_marketplace.orders` AS o ON r.order_id = o.order_id<br>JOIN<br>    `brazilian_marketplace.order_items` AS i ON o.order_id = i.order_id<br>JOIN<br>    `brazilian_marketplace.products` AS p ON i.product_id = p.product_id<br>GROUP BY<br>    p.product_category_name<br>ORDER BY<br>    avg_review_score DESC;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/346/0*z4m0wTCrywBp54Er" /></figure><p>We can see the<strong> categories with the worst reviews</strong>.</p><p>Let’s now see if<strong> delivery delay can influence the review score</strong>.</p><p><strong>Are the delivery timeframes of the product respected?</strong></p><h4>Delivery Delays and Impact on Reviews</h4><p>Firstly, we see that over 93% of products are delivered within the deadline:</p><pre>--Time Delivery<br>WITH product_delivery AS (<br>  SELECT<br>  order_estimated_delivery_date,<br>  order_delivered_customer_date,<br>    CASE<br>      WHEN DATE_DIFF(order_delivered_customer_date, order_estimated_delivery_date, DAY) &gt; 0 THEN &quot;Delay&quot;<br>      ELSE &quot;In Time&quot;<br>  END delivery<br>FROM `brazilian_marketplace.orders`<br>WHERE order_delivered_customer_date IS NOT NULL<br>  AND order_status = &quot;delivered&quot;<br>)<br>SELECT<br>  delivery,<br>  COUNT(delivery) AS order_number_status_delivery,<br>  ROUND((COUNT(delivery) / (SELECT COUNT(*) FROM product_delivery) * 100), 2) AS order_percent_status_delivery<br>FROM product_delivery<br>GROUP BY delivery;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/469/0*pQ_B2KBgvaZ-yD3q" /></figure><p>We also note that <strong>the more the days of delay increase, the lower the review score</strong> becomes:</p><pre>--Is the review score related to the days of delay in delivery?<br>SELECT<br>  orew.review_score,<br>  AVG(DATE_DIFF(order_delivered_customer_date, order_estimated_delivery_date, DAY)) AS avg_days_of_delay<br>FROM `brazilian_marketplace.order_reviews` AS orew<br>INNER JOIN `brazilian_marketplace.orders` AS o<br>  ON orew.order_id = o.order_id<br>WHERE order_delivered_customer_date IS NOT NULL<br>  AND order_status = &quot;delivered&quot;<br>  AND DATE_DIFF(order_delivered_customer_date, order_estimated_delivery_date, DAY) &gt; 0<br>GROUP BY orew.review_score;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/277/0*WjUojPumra0ThFkZ" /></figure><p>Therefore, it is important for Olist to strive to reduce delivery delays in order to increase the overall review score.</p><h3>Strategic Recommendations</h3><h3>Inventory Management Suggestions</h3><p><strong>Stock Optimization</strong></p><p>Olist could consider<strong> increasing stock for categories with higher sales volumes</strong> (such as bed_bath_table or health_beauty).</p><p><strong>For less frequently sold categories, inventory could be reduced</strong> to minimize storage costs and invest more in the more popular categories.</p><h3>Pricing Strategy</h3><p><strong>Price Increase for Popular Products</strong></p><p>Olist could <strong>consider raising prices for more popular categories</strong>, especially if the demand appears to be inelastic and resistant to small price increases.</p><p><strong>Discounts and Promotions</strong></p><p><strong>For less popular categories</strong>, Olist could use <strong>discounts or promotions</strong> to stimulate demand.</p><h3>Sales and Promotional Campaign Planning</h3><p><strong>Targeted Campaigns</strong></p><p>Olist could further <strong>encourage the purchase of popular products with targeted marketing</strong> <strong>campaigns</strong>.</p><p><strong>Bundles and Cross-Promotions</strong></p><p>Olist could explore the possibility of <strong>creating bundle offers or cross-promotions that combine popular products with less sold ones</strong> to increase exposure of the latter.</p><h3>Customization of Marketing and Sales Strategies</h3><p>Develop personalized marketing campaigns for different segments identified by the RFM analysis:</p><p><strong>High Value Customers</strong>:</p><p>Offer personalized deals to <strong>encourage purchases</strong>, perhaps in their favorite categories; activate a “<strong>VIP program</strong>” with dedicated discounts, such as free shipping, early access to sales events.</p><p>Communication: They are the right customers who can help in optimizing services/products offered; ask for their feedback through a survey.</p><p><strong>Loyalty Customers:</strong></p><p>Implement a<strong> loyalty program</strong> that rewards customers based on purchase frequency (for example, with a points program or discounts); implement a <strong>referral program</strong> (“invite a friend” in exchange for rewards in the form of discounts).</p><p>Communication: Newsletters or product suggestions based on their purchase history.</p><p><strong>Risk Customers:</strong></p><p><strong>Send a discount to be used shortly</strong> to make an immediate purchase.</p><p>Communication: Send them newsletters or activate re-engagement marketing campaigns, highlighting the novelties since their last purchase.</p><p><strong>Promising Customers:</strong></p><p><strong>Discounts and welcome offers on their next purchase</strong>.</p><p>Communication: A welcome email sequence educating the customer about the features and benefits of the marketplace. Suggest the purchase of other related products to their first purchase.</p><h3>Reducing Churn Rate</h3><p>It is necessary to <strong>further investigate the churn rate over time</strong>, particularly among new customers or those with low purchase frequency.</p><h3>Continuous Improvement Based on Customer Feedback</h3><p><strong>Use customer reviews to identify areas for improvement </strong>in products or services.</p><h3>Conclusion</h3><p>In conclusion, <strong>this case study reveals how</strong> <strong>Olist</strong>, through accurate data analysis,<strong> can implement strategies for steady growth while maintaining focus on customer satisfaction and loyalty</strong>.</p><p>These insights open new avenues for optimizing operations and for a stronger positioning in the Brazilian e-commerce market.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d032255880e0" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>