Times Series Analysis of Orders and Sales Performance

This section will explore the time series analysis of orders and sales performance for Olist, including month-over-month growth rates, seasonality effects, and the distribution of order volume by day and hour to identify key trends and patterns in customer behavior.

10Project Roadmap: Step-by-Step Navigation
1
Problem Description - Olist E-Commerce Data AnalysisThis article provides a detailed problem description for the Olist E-Commerce Data Analysis project, outlining the tasks assigned by the company, the structure of the analysis, the tools and skills used, acknowledgements, and references.
2
Data NormalizationThis section willl cover the data normalization process for the Olist E-Commerce dataset, including data ingestion, cleaning, star schema modeling, and the creation of an explicit date table to support time-based analysis.
3
Business QuestionsIn this section, business questions will be defined to guide the data analysis process for the Olist E-Commerce dataset. These questions will be categorized into different domains such as customer behavior, sales performance, delivery logistics, seller performance, payments, and customer experience to ensure a comprehensive analysis of the data.
4
Customer Retention AnalysisThis section focuses on analyzing customer retention patterns using the Olist e-commerce dataset. The analysis includes segmenting customers based on their purchasing behavior, understanding trends in customer retention, and exploring the factors that influence customer loyalty.
5
Order and Sales Performance AnalysisThis section will explore the order and sales performance metrics for Olist.
6
Delivery Performance and SLA AnalysisThis section will explore the delivery performance and logistics efficiency of Olist's e-commerce operations, analyzing on-time delivery rates, freight costs, and the decomposition of delivery timelines to identify bottlenecks and regional disparities.
7
Order Fullfilment Efficiency AnalysisThis section will explore how efficiently sellers are fulfilling orders on the Olist platform by analyzing the time gap between order approval and handover to logistics partners, identifying the 'Bottom 10%' of sellers in terms of dispatch efficiency, and providing insights into potential areas for improvement in the fulfillment process.
8
Payments and Risk AnalysisThis section will explore the payment distribution across different methods, analyze the correlation between installment counts and order values, and identify regional payment preferences to understand the risk and revenue implications for Olist's e-commerce platform.
9
Customer Review and Satisfaction AnalysisThis section will explore the effect of delivery delays on customer satisfaction, identify product categories with mismatched delivery speed and review scores, and analyze the distribution of review scores across different product categories to uncover common issues in negative feedback.
10
Times Series Analysis of Orders and Sales PerformanceThis section will explore the time series analysis of orders and sales performance for Olist, including month-over-month growth rates, seasonality effects, and the distribution of order volume by day and hour to identify key trends and patterns in customer behavior.

MoM and Seasonality Analysis

29. What is the Month-over-Month (MoM) growth rate for total revenue across the entire dataset? Can we identify specific months where the business experienced ‘Hyper-Growth’ (>20% increase) versus months of stagnation or contraction?

Solution:

  • To calculate the month-over-month growth rate, the core metrics must first be aggregated by month. This should be done in the first CTE by joining the orders, payments, and calendar tables. The total number of orders and the total revenue must be calculated for each specific year and month. Canceled or unavailable orders must also be filtered out to ensure the revenue figures are perfectly accurate.

  • Next, the historical data must be retrieved to allow for comparison. The lag window function should be used here to fetch the revenue and order volume from both the previous month and the previous year. The data must be ordered chronologically by year and month number to ensure the lag function pulls the correct prior periods for every single row.

  • Once the current and previous metrics are aligned, the actual percentage growth must be calculated. The mathematical formula for percentage change will be applied to both month-over-month and year-over-year revenue. A nullif function must be included during the division step to prevent any mathematical errors just in case the previous month had zero recorded revenue.

  • Finally, the calculated growth rates must be categorized to clearly highlight business performance. A case statement will be used in the main query to assign a growth performance tag, easily identifying periods of hyper-growth above twenty percent, standard positive growth, or stagnation. An additional case statement should also be applied to flag key seasonal periods like Black Friday and the holiday season to provide necessary business context for any major revenue fluctuations.

Show SQL Code

/* growth and seasonality analysis
    objective: calculate mom and yoy growth rates for revenue and order volume
    logic: 
      1. aggregate revenue and order counts by month.
      2. use lag() window function to compare current month vs prior month and prior year.
      3. calculate percentage growth rates and classify performance tiers.
    granularity: year_month
*/

with monthly_sales as (
    -- step 1: aggregate core metrics by month
    select 
        c.year,
        c.month_number,
        c.month_name,
        count(distinct o.order_id) as total_orders,
        sum(p.payment_value) as total_revenue
    from 
        orders o
    inner join 
        payments p on o.order_id = p.order_id
    inner join 
        Calendar c on o.purchase_datekey = c.datekey
    where 
        o.order_status not in ('canceled', 'unavailable')
    group by 
        c.year, c.month_number, c.month_name
),

growth_metrics as (
    -- step 2: use lag() to fetch values from the previous month and previous year
    select 
        *,
        -- previous month values
        prev_month_orders = lag(total_orders) over (order by year, month_number),
        prev_month_revenue = lag(total_revenue) over (order by year, month_number),
        
        -- previous year values (offset of 12 rows)
        prev_year_orders = lag(total_orders, 12) over (order by year, month_number),
        prev_year_revenue = lag(total_revenue, 12) over (order by year, month_number)
    from 
        monthly_sales
),

final_calculations as (
    -- step 3: calculate percentage growth
    select 
        *,
        mom_revenue_growth_pct = cast(100.0 * (total_revenue - prev_month_revenue) / nullif(prev_month_revenue, 0) as decimal(10,2)),
        yoy_revenue_growth_pct = cast(100.0 * (total_revenue - prev_year_revenue) / nullif(prev_year_revenue, 0) as decimal(10,2))
    from 
        growth_metrics
)

-- final output: categorizing growth performance levels and seasonality
select 
    year,
    month_name,
    total_orders,
    total_revenue,
    mom_revenue_growth_pct,
    
    -- identifying growth tiers as requested
    case 
        when mom_revenue_growth_pct > 20 then 'Hyper-Growth (>20%)'
        when mom_revenue_growth_pct > 0 then 'Positive Growth'
        when mom_revenue_growth_pct <= 0 then 'Stagnation / Contraction'
        else 'Baseline (No Prior Month)'
    end as growth_performance_tag,
    
    -- identifying seasonal spikes
    case 
        when month_number = 11 then 'Black Friday Period'
        when month_number = 12 then 'Holiday Season'
        when month_number = 1 then 'New Year Peak'
        else 'Standard Period'
    end as seasonality_tag
from 
    final_calculations
order by 
    year, month_number;

Results:

yearmonth_nametotal_orderstotal_revenuemom_revenue_growth_pctgrowth_performance_tagseasonality_tag
2016September1136.22999572753906Baseline (No Prior Month)Standard Period
2016October29351657.5299947261837819.35Hyper-Growth (>20%)Standard Period
2016December119.6200008392334-99.96Stagnation / ContractionHoliday Season
2017January787137006.76003867388698201.50Hyper-Growth (>20%)New Year Peak
2017February1718283621.93943831325107.01Hyper-Growth (>20%)Standard Period
2017March2617425656.399554163250.08Hyper-Growth (>20%)Standard Period
2017April2377405988.3795110285-4.62Stagnation / ContractionStandard Period
2017May3640582926.16016700343.58Hyper-Growth (>20%)Standard Period
2017June3205499827.47035080194-14.26Stagnation / ContractionStandard Period
2017July3946578858.579521149415.81Positive GrowthStandard Period
2017August4272662071.769808424614.38Positive GrowthStandard Period
2017September4227717195.22036169478.33Positive GrowthStandard Period
2017October4547764785.27968363846.64Positive GrowthStandard Period
2017November74231172639.229781104353.33Hyper-Growth (>20%)Black Friday Period
2017December5620861914.4599985629-26.50Stagnation / ContractionHoliday Season
2018January71871102639.409112945227.93Hyper-Growth (>20%)New Year Peak
2018February6625979966.2302345932-11.13Stagnation / ContractionStandard Period
2018March71681152736.739690676317.63Positive GrowthStandard Period
2018April69191156303.91070481580.31Positive GrowthStandard Period
2018May68331145748.6310662013-0.91Stagnation / ContractionStandard Period
2018June61451020494.2907593064-10.93Stagnation / ContractionStandard Period
2018July62331039880.15903876161.90Positive GrowthStandard Period
2018August6421996896.1512343884-4.13Stagnation / ContractionStandard Period
2018September1166.4600067138672-99.98Stagnation / ContractionStandard Period

Insights:

  • The 2017 Startup Boom: The first half of 2017 is defined by aggressive “Hyper-Growth.” Months like February (+107%), March (+50%), and May (+43%) show huge revenue jumps, proving this was the phase where Olist rapidly acquired its early market share.
  • The Black Friday Rollercoaster: November 2017 was an absolute game-changer. Revenue spiked by 53.3% due to Black Friday sales. However, this created a predictable hangover in December, resulting in a 26.5% contraction as the holiday shopping rush ended.
  • The 2018 Plateau: After recovering with a strong January 2018 (+27.9%), the platform’s growth essentially flattened out. The rest of 2018 is filled with slight contractions (like -11% in February and -10% in June) and very mild growth (like +0.3% in April). The business stopped doubling its size and stabilized at around 1Mto1M to 1.15M per month.
  • Ignoring the Noise: As analysts, we need to ignore the massive percentages at the very start (late 2016) and very end (Sept 2018) of the table. These months only have 1 recorded order, meaning they represent the setup phase and the cutoff date of our dataset, not actual business performance.

Seasonality Effect

30. How does the revenue of specific months compare across different years (e.g., Jan 2017 vs. Jan 2018)? Can we observe a consistent ‘Seasonality Effect’ where certain months (like November) consistently outperform others regardless of the year?

Solution:

  • To analyze the seasonality effect, revenue must first be aggregated by year and month. This should be done in the first CTE by joining the orders, payments, and calendar tables. The total revenue for each month of every year must be calculated while filtering out canceled or unavailable orders to ensure accuracy.

  • Next, the lag window function should be used to compare the revenue of the same month across different years. This will allow for a direct year-over-year comparison for each month, such as January 2017 vs January 2018. Additionally, the average monthly revenue for each year should be calculated to serve as a benchmark for identifying seasonal trends.

  • Once the current month revenue, previous year same month revenue, and yearly average are all aligned, a seasonality index can be calculated. This index will be derived by dividing the current month’s revenue by the yearly average. An index greater than 1.0 will indicate that the month outperformed the year’s average, suggesting a strong seasonal effect.

  • Finally, the seasonality index should be categorized to clearly identify which months are peak performers, standard performers, or low season periods. A case statement will be used to classify months with a seasonality index above 1.2 as ‘Peak Months’, those between 0.9 and 1.2 as ‘Standard Performance’, and those below 0.9 as ‘Low Season / Slack Period’. This final output will help determine if certain months consistently outperform others across different years, confirming the presence of a seasonality effect.

Show SQL Code

/* growth and seasonality analysis
    objective: 
        1. compare specific months across years (e.g., Jan 2017 vs Jan 2018).
        2. calculate a 'seasonality index' to identify recurring monthly trends.
    logic: 
      - aggregate revenue by year and month.
      - use window functions to compare same-month performance across years.
      - calculate the deviation of each month from its year's average to find the 'seasonal push'.
*/

with monthly_revenue_base as (
    -- step 1: aggregate total revenue by year and month
    select 
        c.year,
        c.month_number,
        c.month_name,
        sum(p.payment_value) as monthly_revenue
    from 
        orders o
    inner join 
        payments p on o.order_id = p.order_id
    inner join 
        Calendar c on o.purchase_datekey = c.datekey
    where 
        o.order_status not in ('canceled', 'unavailable')
    group by 
        c.year, c.month_number, c.month_name
),

seasonal_indexing as (
    -- step 2: calculate year-over-year same-month comparisons and yearly averages
    select 
        *,
        -- direct comparison: revenue of the same month in the previous year
        prev_year_same_month_rev = lag(monthly_revenue) over (
            partition by month_number 
            order by year
        ),
        -- average monthly revenue for that specific year (benchmark)
        yearly_avg_monthly_rev = avg(monthly_revenue) over (
            partition by year
        )
    from 
        monthly_revenue_base
)

-- final output: quantifying the seasonality effect
select 
    year,
    month_name,
    cast(monthly_revenue as decimal(15,2)) as current_month_revenue,
    
    -- yoy comparison for the specific month
    cast(100.0 * (monthly_revenue - prev_year_same_month_rev) / nullif(prev_year_same_month_rev, 0) as decimal(10,2)) as month_specific_yoy_growth_pct,
    
    -- seasonality index: revenue / yearly average
    -- index > 1.0 means the month outperforms the year's average
    cast(monthly_revenue / nullif(yearly_avg_monthly_rev, 0) as decimal(10,2)) as seasonality_index,
    
    -- classification of the month's role in the business cycle
    case 
        when (monthly_revenue / yearly_avg_monthly_rev) >= 1.2 then 'Peak Month (Strong Seasonality)'
        when (monthly_revenue / yearly_avg_monthly_rev) between 0.9 and 1.2 then 'Standard Performance'
        when (monthly_revenue / yearly_avg_monthly_rev) < 0.9 then 'Low Season / Slack Period'
        else 'Baseline'
    end as seasonal_classification
from 
    seasonal_indexing
order by 
    year, month_number;

Results:

yearmonth_namecurrent_month_revenuemonth_specific_yoy_growth_pctseasonality_indexseasonal_classification
2016September136.230.01Low Season / Slack Period
2016October51657.532.99Peak Month (Strong Seasonality)
2016December19.620.00Low Season / Slack Period
2017January137006.760.23Low Season / Slack Period
2017February283621.940.48Low Season / Slack Period
2017March425656.400.72Low Season / Slack Period
2017April405988.380.69Low Season / Slack Period
2017May582926.160.99Standard Performance
2017June499827.470.85Low Season / Slack Period
2017July578858.580.98Standard Performance
2017August662071.771.12Standard Performance
2017September717195.22526359.111.21Peak Month (Strong Seasonality)
2017October764785.281380.491.29Peak Month (Strong Seasonality)
2017November1172639.231.98Peak Month (Strong Seasonality)
2017December861914.464392939.871.46Peak Month (Strong Seasonality)
2018January1102639.41704.811.15Standard Performance
2018February979966.23245.521.03Standard Performance
2018March1152736.74170.811.21Peak Month (Strong Seasonality)
2018April1156303.91184.811.21Peak Month (Strong Seasonality)
2018May1145748.6396.551.20Standard Performance
2018June1020494.29104.171.07Standard Performance
2018July1039880.1679.641.09Standard Performance
2018August996896.1550.571.04Standard Performance
2018September166.46-99.980.00Low Season / Slack Period

Insights:

  • Massive Scaling: Comparing the same months across years shows the true scale of growth. January 2018 revenue was 704% higher than January 2017. Every month in 2018 outperformed the previous year by a huge margin.
  • November is the Undisputed King: With a Seasonality Index of 1.98, November performs nearly twice as well as the average month. This confirms that Black Friday is the single most important event for the platform’s annual revenue.
  • Shift to Year-Round Demand: In 2017, the first half of the year was considered “Low Season.” By 2018, months like March and April became “Peak Months,” showing that Olist successfully transitioned into a year-round shopping destination.
  • Predictable Maturity: The Seasonality Index for 2018 stayed very stable (between 1.03 and 1.21). This indicates that the business became much more predictable as it matured, with less chaotic fluctuations than the previous year.
  • The High-Revenue Window: A clear “Peak” cluster exists from September to January. This five-month window is the most critical period for the business, likely combining Black Friday, Christmas, and New Year sales.

Distribution of Order Volume by Day and Hour

31. What is the distribution of order volume across different Days of the Week and Hours of the Day? Specifically, do we see a ‘Lunchtime Spike’ (12 PM - 2 PM) or an ‘Evening Spike’ (8 PM - 10 PM)?

Solution:

  • To analyze the distribution of order volume across different days of the week and hours of the day, the order data must first be aggregated by extracting the day of the week and hour from the order purchase timestamp. This should be done in a CTE by using date functions to derive these time components. The total number of orders for each combination of day and hour must be calculated while filtering out canceled or unavailable orders.

  • Then we can analyze the aggregated data to identify specific patterns. A case statement should be used to classify hours between 12 PM and 2 PM as ‘Lunchtime Spike Window’ and hours between 8 PM and 10 PM as ‘Evening Spike Window’. All other hours can be classified as ‘Standard Time’.

  • Finally, the results should be ordered by the day of the week and hour to clearly visualize the distribution of order volume throughout the week and identify any consistent spikes during lunchtime or evening hours. This analysis will help determine if there are specific times when customer engagement is higher, which can inform marketing strategies and operational planning.

Show SQL Code

/* growth and seasonality analysis
    objective: 
        1. compare specific months across years (e.g., Jan 2017 vs Jan 2018).
        2. calculate a 'seasonality index' to identify recurring monthly trends.
        3. analyze order distribution by Day of the Week and Hour of Day.
    logic: 
      - aggregate revenue by year and month for high-level trends.
      - use window functions for year-over-year same-month comparisons.
      - extract time-of-day and day-of-week metrics to find peak engagement windows.
*/

with monthly_revenue_base as (
    -- step 1: aggregate total revenue by year and month
    select 
        c.year,
        c.month_number,
        c.month_name,
        sum(p.payment_value) as monthly_revenue
    from 
        orders o
    inner join 
        payments p on o.order_id = p.order_id
    inner join 
        Calendar c on o.purchase_datekey = c.datekey
    where 
        o.order_status not in ('canceled', 'unavailable')
    group by 
        c.year, c.month_number, c.month_name
),

seasonal_indexing as (
    -- step 2: calculate year-over-year same-month comparisons and yearly averages
    select 
        *,
        -- direct comparison: revenue of the same month in the previous year
        prev_year_same_month_rev = lag(monthly_revenue) over (
            partition by month_number 
            order by year
        ),
        -- average monthly revenue for that specific year (benchmark)
        yearly_avg_monthly_rev = avg(monthly_revenue) over (
            partition by year
        )
    from 
        monthly_revenue_base
),

-- step 3: analyzing hourly and weekly volume distribution
time_of_day_stats as (
    select 
        datename(dw, order_purchase_timestamp) as day_of_week,
        datepart(dw, order_purchase_timestamp) as day_num, -- used for sorting
        datepart(hour, order_purchase_timestamp) as hour_of_day,
        count(order_id) as total_orders
    from 
        orders
    where 
        order_status not in ('canceled', 'unavailable')
    group by 
        datename(dw, order_purchase_timestamp),
        datepart(dw, order_purchase_timestamp),
        datepart(hour, order_purchase_timestamp)
)

-- final output: combining the original seasonality analysis with the new time-of-day insights
-- part 1: monthly seasonality (existing)
select 
    year,
    month_name,
    cast(monthly_revenue as decimal(15,2)) as current_month_revenue,
    
    -- yoy comparison for the specific month
    cast(100.0 * (monthly_revenue - prev_year_same_month_rev) / nullif(prev_year_same_month_rev, 0) as decimal(10,2)) as month_specific_yoy_growth_pct,
    
    -- seasonality index
    cast(monthly_revenue / nullif(yearly_avg_monthly_rev, 0) as decimal(10,2)) as seasonality_index,
    
    case 
        when (monthly_revenue / yearly_avg_monthly_rev) >= 1.2 then 'Peak Month (Strong Seasonality)'
        when (monthly_revenue / yearly_avg_monthly_rev) between 0.9 and 1.2 then 'Standard Performance'
        when (monthly_revenue / yearly_avg_monthly_rev) < 0.9 then 'Low Season / Slack Period'
        else 'Baseline'
    end as seasonal_classification
from 
    seasonal_indexing

union all

-- part 2: placeholder/separator row for clear data interpretation if exported
select 
    null, '--- INTRA-DAY ANALYSIS ---', null, null, null, null

union all

-- part 3: day/hour distribution
select 
    day_num as year, -- repurposing columns for the union's schema
    concat(day_of_week, ' at ', hour_of_day, ':00') as month_name,
    total_orders as current_month_revenue,
    null as month_specific_yoy_growth_pct,
    null as seasonality_index,
    case 
        when hour_of_day between 12 and 14 then 'Lunchtime Spike Window'
        when hour_of_day between 20 and 22 then 'Evening Spike Window'
        else 'Standard Time'
    end as seasonal_classification
from 
    time_of_day_stats
order by 
    year, month_name;

Results:

yearmonth_namecurrent_month_revenuemonth_specific_yoy_growth_pctseasonality_indexseasonal_classification
--- INTRA-DAY ANALYSIS ---
1Sunday at 0:00260.00Standard Time
1Sunday at 1:00140.00Standard Time
1Sunday at 10:00524.00Standard Time
1Sunday at 11:00715.00Standard Time
1Sunday at 12:00669.00Lunchtime Spike Window
1Sunday at 13:00714.00Lunchtime Spike Window
1Sunday at 14:00676.00Lunchtime Spike Window
1Sunday at 15:00707.00Standard Time
1Sunday at 16:00706.00Standard Time
1Sunday at 17:00792.00Standard Time
1Sunday at 18:00888.00Standard Time
1Sunday at 19:00892.00Standard Time
1Sunday at 2:0068.00Standard Time
1Sunday at 20:00956.00Evening Spike Window
1Sunday at 21:00876.00Evening Spike Window
1Sunday at 22:00864.00Evening Spike Window
1Sunday at 23:00603.00Standard Time
1Sunday at 3:0043.00Standard Time
1Sunday at 4:0027.00Standard Time
1Sunday at 5:0027.00Standard Time
1Sunday at 6:0033.00Standard Time
1Sunday at 7:00105.00Standard Time
1Sunday at 8:00203.00Standard Time
1Sunday at 9:00343.00Standard Time
2Monday at 0:00322.00Standard Time
2Monday at 1:00134.00Standard Time
2Monday at 10:001020.00Standard Time
2Monday at 11:001068.00Standard Time
2Monday at 12:00961.00Lunchtime Spike Window
2Monday at 13:001013.00Lunchtime Spike Window
2Monday at 14:001082.00Lunchtime Spike Window
2Monday at 15:001065.00Standard Time
2Monday at 16:001076.00Standard Time
2Monday at 17:00974.00Standard Time
2Monday at 18:00915.00Standard Time
2Monday at 19:00933.00Standard Time
2Monday at 2:0064.00Standard Time
2Monday at 20:001016.00Evening Spike Window
2Monday at 21:001103.00Evening Spike Window
2Monday at 22:00982.00Evening Spike Window
2Monday at 23:00708.00Standard Time
2Monday at 3:0036.00Standard Time
2Monday at 4:0021.00Standard Time
2Monday at 5:0022.00Standard Time
2Monday at 6:0065.00Standard Time
2Monday at 7:00159.00Standard Time
2Monday at 8:00475.00Standard Time
2Monday at 9:00765.00Standard Time
3Tuesday at 0:00304.00Standard Time
3Tuesday at 1:00157.00Standard Time
3Tuesday at 10:00950.00Standard Time
3Tuesday at 11:001040.00Standard Time
3Tuesday at 12:00889.00Lunchtime Spike Window
3Tuesday at 13:001036.00Lunchtime Spike Window
3Tuesday at 14:001106.00Lunchtime Spike Window
3Tuesday at 15:001026.00Standard Time
3Tuesday at 16:001074.00Standard Time
3Tuesday at 17:00956.00Standard Time
3Tuesday at 18:00868.00Standard Time
3Tuesday at 19:00909.00Standard Time
3Tuesday at 2:0080.00Standard Time
3Tuesday at 20:00970.00Evening Spike Window
3Tuesday at 21:001017.00Evening Spike Window
3Tuesday at 22:00955.00Evening Spike Window
3Tuesday at 23:00685.00Standard Time
3Tuesday at 3:0028.00Standard Time
3Tuesday at 4:0028.00Standard Time
3Tuesday at 5:0024.00Standard Time
3Tuesday at 6:0071.00Standard Time
3Tuesday at 7:00219.00Standard Time
3Tuesday at 8:00515.00Standard Time
3Tuesday at 9:00853.00Standard Time
4Wednesday at 0:00392.00Standard Time
4Wednesday at 1:00175.00Standard Time
4Wednesday at 10:001029.00Standard Time
4Wednesday at 11:001046.00Standard Time
4Wednesday at 12:00912.00Lunchtime Spike Window
4Wednesday at 13:001009.00Lunchtime Spike Window
4Wednesday at 14:001039.00Lunchtime Spike Window
4Wednesday at 15:00971.00Standard Time
4Wednesday at 16:001024.00Standard Time
4Wednesday at 17:00958.00Standard Time
4Wednesday at 18:00837.00Standard Time
4Wednesday at 19:00833.00Standard Time
4Wednesday at 2:0081.00Standard Time
4Wednesday at 20:00892.00Evening Spike Window
4Wednesday at 21:00953.00Evening Spike Window
4Wednesday at 22:00872.00Evening Spike Window
4Wednesday at 23:00612.00Standard Time
4Wednesday at 3:0031.00Standard Time
4Wednesday at 4:0033.00Standard Time
4Wednesday at 5:0026.00Standard Time
4Wednesday at 6:0093.00Standard Time
4Wednesday at 7:00210.00Standard Time
4Wednesday at 8:00508.00Standard Time
4Wednesday at 9:00817.00Standard Time
5Thursday at 0:00354.00Standard Time
5Thursday at 1:00167.00Standard Time
5Thursday at 10:00970.00Standard Time
5Thursday at 11:00945.00Standard Time
5Thursday at 12:00963.00Lunchtime Spike Window
5Thursday at 13:00968.00Lunchtime Spike Window
5Thursday at 14:00968.00Lunchtime Spike Window
5Thursday at 15:00916.00Standard Time
5Thursday at 16:001066.00Standard Time
5Thursday at 17:00893.00Standard Time
5Thursday at 18:00776.00Standard Time
5Thursday at 19:00814.00Standard Time
5Thursday at 2:0074.00Standard Time
5Thursday at 20:00829.00Evening Spike Window
5Thursday at 21:00832.00Evening Spike Window
5Thursday at 22:00850.00Evening Spike Window
5Thursday at 23:00541.00Standard Time
5Thursday at 3:0039.00Standard Time
5Thursday at 4:0031.00Standard Time
5Thursday at 5:0028.00Standard Time
5Thursday at 6:0083.00Standard Time
5Thursday at 7:00217.00Standard Time
5Thursday at 8:00497.00Standard Time
5Thursday at 9:00750.00Standard Time
6Friday at 0:00418.00Standard Time
6Friday at 1:00210.00Standard Time
6Friday at 10:00942.00Standard Time
6Friday at 11:00983.00Standard Time
6Friday at 12:00843.00Lunchtime Spike Window
6Friday at 13:00989.00Lunchtime Spike Window
6Friday at 14:00950.00Lunchtime Spike Window
6Friday at 15:00969.00Standard Time
6Friday at 16:00960.00Standard Time
6Friday at 17:00807.00Standard Time
6Friday at 18:00716.00Standard Time
6Friday at 19:00772.00Standard Time
6Friday at 2:0070.00Standard Time
6Friday at 20:00730.00Evening Spike Window
6Friday at 21:00714.00Evening Spike Window
6Friday at 22:00695.00Evening Spike Window
6Friday at 23:00507.00Standard Time
6Friday at 3:0048.00Standard Time
6Friday at 4:0040.00Standard Time
6Friday at 5:0035.00Standard Time
6Friday at 6:0096.00Standard Time
6Friday at 7:00204.00Standard Time
6Friday at 8:00493.00Standard Time
6Friday at 9:00758.00Standard Time
7Saturday at 0:00312.00Standard Time
7Saturday at 1:00171.00Standard Time
7Saturday at 10:00648.00Standard Time
7Saturday at 11:00701.00Standard Time
7Saturday at 12:00681.00Lunchtime Spike Window
7Saturday at 13:00702.00Lunchtime Spike Window
7Saturday at 14:00670.00Lunchtime Spike Window
7Saturday at 15:00716.00Standard Time
7Saturday at 16:00688.00Standard Time
7Saturday at 17:00694.00Standard Time
7Saturday at 18:00707.00Standard Time
7Saturday at 19:00746.00Standard Time
7Saturday at 2:0066.00Standard Time
7Saturday at 20:00723.00Evening Spike Window
7Saturday at 21:00651.00Evening Spike Window
7Saturday at 22:00543.00Evening Spike Window
7Saturday at 23:00425.00Standard Time
7Saturday at 3:0042.00Standard Time
7Saturday at 4:0025.00Standard Time
7Saturday at 5:0023.00Standard Time
7Saturday at 6:0052.00Standard Time
7Saturday at 7:00104.00Standard Time
7Saturday at 8:00248.00Standard Time
7Saturday at 9:00426.00Standard Time
2016December19.620.00Low Season / Slack Period
2016October51657.532.99Peak Month (Strong Seasonality)
2016September136.230.01Low Season / Slack Period
2017April405988.380.69Low Season / Slack Period
2017August662071.771.12Standard Performance
2017December861914.464392939.871.46Peak Month (Strong Seasonality)
2017February283621.940.48Low Season / Slack Period
2017January137006.760.23Low Season / Slack Period
2017July578858.580.98Standard Performance
2017June499827.470.85Low Season / Slack Period
2017March425656.400.72Low Season / Slack Period
2017May582926.160.99Standard Performance
2017November1172639.231.98Peak Month (Strong Seasonality)
2017October764785.281380.491.29Peak Month (Strong Seasonality)
2017September717195.22526359.111.21Peak Month (Strong Seasonality)
2018April1156303.91184.811.21Peak Month (Strong Seasonality)
2018August996896.1550.571.04Standard Performance
2018February979966.23245.521.03Standard Performance
2018January1102639.41704.811.15Standard Performance
2018July1039880.1679.641.09Standard Performance
2018June1020494.29104.171.07Standard Performance
2018March1152736.74170.811.21Peak Month (Strong Seasonality)
2018May1145748.6396.551.20Standard Performance
2018September166.46-99.980.00Low Season / Slack Period

Insights:

  • Monday is the Main Event: Monday is the busiest day of the week for Olist. It hits peak volumes at 2:00 PM and 9:00 PM. Overall, sales are significantly higher during the workweek (Monday–Friday) than on weekends, showing that customers prefer shopping during work hours or their commute.
  • The Lunchtime Surge: There is a very consistent “Lunchtime Spike” every single weekday. Between 12:00 PM and 2:00 PM, order counts surge as people shop on their breaks. On Tuesdays, for example, volume jumps from 853 at 9:00 AM to a peak of 1,106 by 2:00 PM.
  • The Evening Peak: Regardless of the day, the highest activity happens during the “Evening Spike” between 8:00 PM and 10:00 PM. Monday at 9:00 PM is the busiest single hour of the entire week with 1,103 orders.
  • Weekend Drop-off: Shopping activity cools down on Saturdays and Sundays. While the evening spike still exists on Sunday (956 orders at 8:00 PM), the overall baseline is much lower. Saturday is the quietest day on the platform across almost every hour.
  • Late-Night Dormancy: Across all days, shopping almost completely stops between 2:00 AM and 6:00 AM, with most hours dropping below 40 orders. This is the ideal window for system maintenance since traffic is at its lowest.

Cumulative Revenue and Growth Trajectory

32. What is the cumulative revenue generated over time since the inception of the platform? By plotting the running total of sales, can we visually confirm the business’s transition from ‘Linear Growth’ to ‘Exponential Growth’ (The Hockey Stick Curve)?

Solution:

  • To track cumulative revenue and observe the hockey stick growth curve, the total revenue must first be aggregated by year and month. This should be done in the first CTE by joining the orders, payments, and calendar tables, ensuring that canceled or unavailable orders are strictly filtered out.

  • Next, a running total of the monthly revenue must be calculated. Window functions should be applied here to continuously sum the monthly revenue over time, ordered chronologically by year and month. This cumulative revenue metric will clearly show the overall growth trajectory of the business from its inception. In the exact same step, historical year-over-year comparisons and yearly averages will also be established to maintain the baseline seasonality analysis.

  • To find the peak customer engagement windows, the order distribution by day of the week and hour of the day must be analyzed. A separate CTE should be created to extract the specific day name and hour from the order purchase timestamps. The total number of orders must then be counted for each specific hour of every day to find intraday shopping patterns.

  • Finally, these distinct sets of insights must be combined into a single final output. The monthly seasonality and cumulative growth metrics should be selected first. Then, a union all statement must be used to append the day and hour distribution below the monthly data. A case statement will be applied here to tag specific times as lunchtime or evening spike windows. This unified approach will allow the business to view both high-level exponential growth and granular daily shopping habits in one comprehensive report.

Show SQL Code

/* growth and seasonality analysis
    objective: 
        1. compare specific months across years (e.g., Jan 2017 vs Jan 2018).
        2. calculate a 'seasonality index' to identify recurring monthly trends.
        3. analyze order distribution by Day of the Week and Hour of Day.
        4. calculate cumulative revenue to track "The Hockey Stick" growth curve.
    logic: 
      - aggregate revenue by year and month for high-level trends.
      - use window functions for year-over-year and running total calculations.
      - extract time-of-day and day-of-week metrics to find peak engagement windows.
*/

with monthly_revenue_base as (
    -- step 1: aggregate total revenue by year and month
    select 
        c.year,
        c.month_number,
        c.month_name,
        sum(p.payment_value) as monthly_revenue
    from 
        orders o
    inner join 
        payments p on o.order_id = p.order_id
    inner join 
        Calendar c on o.purchase_datekey = c.datekey
    where 
        o.order_status not in ('canceled', 'unavailable')
    group by 
        c.year, c.month_number, c.month_name
),

seasonal_indexing as (
    -- step 2: calculate YoY comparisons, yearly averages, and cumulative running total
    select 
        *,
        -- direct comparison: revenue of the same month in the previous year
        prev_year_same_month_rev = lag(monthly_revenue) over (
            partition by month_number 
            order by year
        ),
        -- average monthly revenue for that specific year (benchmark)
        yearly_avg_monthly_rev = avg(monthly_revenue) over (
            partition by year
        ),
        -- cumulative revenue: running total since the platform inception
        running_total_revenue = sum(monthly_revenue) over (
            order by year, month_number
        )
    from 
        monthly_revenue_base
),

-- step 3: analyzing hourly and weekly volume distribution
time_of_day_stats as (
    select 
        datename(dw, order_purchase_timestamp) as day_of_week,
        datepart(dw, order_purchase_timestamp) as day_num, -- used for sorting
        datepart(hour, order_purchase_timestamp) as hour_of_day,
        count(order_id) as total_orders
    from 
        orders
    where 
        order_status not in ('canceled', 'unavailable')
    group by 
        datename(dw, order_purchase_timestamp),
        datepart(dw, order_purchase_timestamp),
        datepart(hour, order_purchase_timestamp)
)

-- final output: combining the original seasonality analysis with the new running total and time-of-day insights
-- part 1: monthly seasonality and cumulative growth
select 
    year,
    month_name,
    cast(monthly_revenue as decimal(15,2)) as current_month_revenue,
    cast(running_total_revenue as decimal(15,2)) as cumulative_revenue,
    
    -- yoy comparison for the specific month
    cast(100.0 * (monthly_revenue - prev_year_same_month_rev) / nullif(prev_year_same_month_rev, 0) as decimal(10,2)) as month_specific_yoy_growth_pct,
    
    -- seasonality index
    cast(monthly_revenue / nullif(yearly_avg_monthly_rev, 0) as decimal(10,2)) as seasonality_index,
    
    case 
        when (monthly_revenue / yearly_avg_monthly_rev) >= 1.2 then 'Peak Month (Strong Seasonality)'
        when (monthly_revenue / yearly_avg_monthly_rev) between 0.9 and 1.2 then 'Standard Performance'
        when (monthly_revenue / yearly_avg_monthly_rev) < 0.9 then 'Low Season / Slack Period'
        else 'Baseline'
    end as seasonal_classification
from 
    seasonal_indexing

union all

-- part 2: placeholder/separator row
select 
    null, '--- INTRA-DAY ANALYSIS ---', null, null, null, null, null

union all

-- part 3: day/hour distribution
select 
    day_num as year,
    concat(day_of_week, ' at ', hour_of_day, ':00') as month_name,
    total_orders as current_month_revenue,
    null as cumulative_revenue,
    null as month_specific_yoy_growth_pct,
    null as seasonality_index,
    case 
        when hour_of_day between 12 and 14 then 'Lunchtime Spike Window'
        when hour_of_day between 20 and 22 then 'Evening Spike Window'
        else 'Standard Time'
    end as seasonal_classification
from 
    time_of_day_stats
order by 
    year, month_name;

Results:

yearmonth_namecurrent_month_revenuecumulative_revenuemonth_specific_yoy_growth_pctseasonality_indexseasonal_classification
--- INTRA-DAY ANALYSIS ---
1Sunday at 0:00260.00Standard Time
1Sunday at 1:00140.00Standard Time
1Sunday at 10:00524.00Standard Time
1Sunday at 11:00715.00Standard Time
1Sunday at 12:00669.00Lunchtime Spike Window
1Sunday at 13:00714.00Lunchtime Spike Window
1Sunday at 14:00676.00Lunchtime Spike Window
1Sunday at 15:00707.00Standard Time
1Sunday at 16:00706.00Standard Time
1Sunday at 17:00792.00Standard Time
1Sunday at 18:00888.00Standard Time
1Sunday at 19:00892.00Standard Time
1Sunday at 2:0068.00Standard Time
1Sunday at 20:00956.00Evening Spike Window
1Sunday at 21:00876.00Evening Spike Window
1Sunday at 22:00864.00Evening Spike Window
1Sunday at 23:00603.00Standard Time
1Sunday at 3:0043.00Standard Time
1Sunday at 4:0027.00Standard Time
1Sunday at 5:0027.00Standard Time
1Sunday at 6:0033.00Standard Time
1Sunday at 7:00105.00Standard Time
1Sunday at 8:00203.00Standard Time
1Sunday at 9:00343.00Standard Time
2Monday at 0:00322.00Standard Time
2Monday at 1:00134.00Standard Time
2Monday at 10:001020.00Standard Time
2Monday at 11:001068.00Standard Time
2Monday at 12:00961.00Lunchtime Spike Window
2Monday at 13:001013.00Lunchtime Spike Window
2Monday at 14:001082.00Lunchtime Spike Window
2Monday at 15:001065.00Standard Time
2Monday at 16:001076.00Standard Time
2Monday at 17:00974.00Standard Time
2Monday at 18:00915.00Standard Time
2Monday at 19:00933.00Standard Time
2Monday at 2:0064.00Standard Time
2Monday at 20:001016.00Evening Spike Window
2Monday at 21:001103.00Evening Spike Window
2Monday at 22:00982.00Evening Spike Window
2Monday at 23:00708.00Standard Time
2Monday at 3:0036.00Standard Time
2Monday at 4:0021.00Standard Time
2Monday at 5:0022.00Standard Time
2Monday at 6:0065.00Standard Time
2Monday at 7:00159.00Standard Time
2Monday at 8:00475.00Standard Time
2Monday at 9:00765.00Standard Time
3Tuesday at 0:00304.00Standard Time
3Tuesday at 1:00157.00Standard Time
3Tuesday at 10:00950.00Standard Time
3Tuesday at 11:001040.00Standard Time
3Tuesday at 12:00889.00Lunchtime Spike Window
3Tuesday at 13:001036.00Lunchtime Spike Window
3Tuesday at 14:001106.00Lunchtime Spike Window
3Tuesday at 15:001026.00Standard Time
3Tuesday at 16:001074.00Standard Time
3Tuesday at 17:00956.00Standard Time
3Tuesday at 18:00868.00Standard Time
3Tuesday at 19:00909.00Standard Time
3Tuesday at 2:0080.00Standard Time
3Tuesday at 20:00970.00Evening Spike Window
3Tuesday at 21:001017.00Evening Spike Window
3Tuesday at 22:00955.00Evening Spike Window
3Tuesday at 23:00685.00Standard Time
3Tuesday at 3:0028.00Standard Time
3Tuesday at 4:0028.00Standard Time
3Tuesday at 5:0024.00Standard Time
3Tuesday at 6:0071.00Standard Time
3Tuesday at 7:00219.00Standard Time
3Tuesday at 8:00515.00Standard Time
3Tuesday at 9:00853.00Standard Time
4Wednesday at 0:00392.00Standard Time
4Wednesday at 1:00175.00Standard Time
4Wednesday at 10:001029.00Standard Time
4Wednesday at 11:001046.00Standard Time
4Wednesday at 12:00912.00Lunchtime Spike Window
4Wednesday at 13:001009.00Lunchtime Spike Window
4Wednesday at 14:001039.00Lunchtime Spike Window
4Wednesday at 15:00971.00Standard Time
4Wednesday at 16:001024.00Standard Time
4Wednesday at 17:00958.00Standard Time
4Wednesday at 18:00837.00Standard Time
4Wednesday at 19:00833.00Standard Time
4Wednesday at 2:0081.00Standard Time
4Wednesday at 20:00892.00Evening Spike Window
4Wednesday at 21:00953.00Evening Spike Window
4Wednesday at 22:00872.00Evening Spike Window
4Wednesday at 23:00612.00Standard Time
4Wednesday at 3:0031.00Standard Time
4Wednesday at 4:0033.00Standard Time
4Wednesday at 5:0026.00Standard Time
4Wednesday at 6:0093.00Standard Time
4Wednesday at 7:00210.00Standard Time
4Wednesday at 8:00508.00Standard Time
4Wednesday at 9:00817.00Standard Time
5Thursday at 0:00354.00Standard Time
5Thursday at 1:00167.00Standard Time
5Thursday at 10:00970.00Standard Time
5Thursday at 11:00945.00Standard Time
5Thursday at 12:00963.00Lunchtime Spike Window
5Thursday at 13:00968.00Lunchtime Spike Window
5Thursday at 14:00968.00Lunchtime Spike Window
5Thursday at 15:00916.00Standard Time
5Thursday at 16:001066.00Standard Time
5Thursday at 17:00893.00Standard Time
5Thursday at 18:00776.00Standard Time
5Thursday at 19:00814.00Standard Time
5Thursday at 2:0074.00Standard Time
5Thursday at 20:00829.00Evening Spike Window
5Thursday at 21:00832.00Evening Spike Window
5Thursday at 22:00850.00Evening Spike Window
5Thursday at 23:00541.00Standard Time
5Thursday at 3:0039.00Standard Time
5Thursday at 4:0031.00Standard Time
5Thursday at 5:0028.00Standard Time
5Thursday at 6:0083.00Standard Time
5Thursday at 7:00217.00Standard Time
5Thursday at 8:00497.00Standard Time
5Thursday at 9:00750.00Standard Time
6Friday at 0:00418.00Standard Time
6Friday at 1:00210.00Standard Time
6Friday at 10:00942.00Standard Time
6Friday at 11:00983.00Standard Time
6Friday at 12:00843.00Lunchtime Spike Window
6Friday at 13:00989.00Lunchtime Spike Window
6Friday at 14:00950.00Lunchtime Spike Window
6Friday at 15:00969.00Standard Time
6Friday at 16:00960.00Standard Time
6Friday at 17:00807.00Standard Time
6Friday at 18:00716.00Standard Time
6Friday at 19:00772.00Standard Time
6Friday at 2:0070.00Standard Time
6Friday at 20:00730.00Evening Spike Window
6Friday at 21:00714.00Evening Spike Window
6Friday at 22:00695.00Evening Spike Window
6Friday at 23:00507.00Standard Time
6Friday at 3:0048.00Standard Time
6Friday at 4:0040.00Standard Time
6Friday at 5:0035.00Standard Time
6Friday at 6:0096.00Standard Time
6Friday at 7:00204.00Standard Time
6Friday at 8:00493.00Standard Time
6Friday at 9:00758.00Standard Time
7Saturday at 0:00312.00Standard Time
7Saturday at 1:00171.00Standard Time
7Saturday at 10:00648.00Standard Time
7Saturday at 11:00701.00Standard Time
7Saturday at 12:00681.00Lunchtime Spike Window
7Saturday at 13:00702.00Lunchtime Spike Window
7Saturday at 14:00670.00Lunchtime Spike Window
7Saturday at 15:00716.00Standard Time
7Saturday at 16:00688.00Standard Time
7Saturday at 17:00694.00Standard Time
7Saturday at 18:00707.00Standard Time
7Saturday at 19:00746.00Standard Time
7Saturday at 2:0066.00Standard Time
7Saturday at 20:00723.00Evening Spike Window
7Saturday at 21:00651.00Evening Spike Window
7Saturday at 22:00543.00Evening Spike Window
7Saturday at 23:00425.00Standard Time
7Saturday at 3:0042.00Standard Time
7Saturday at 4:0025.00Standard Time
7Saturday at 5:0023.00Standard Time
7Saturday at 6:0052.00Standard Time
7Saturday at 7:00104.00Standard Time
7Saturday at 8:00248.00Standard Time
7Saturday at 9:00426.00Standard Time
2016December19.6251813.380.00Low Season / Slack Period
2016October51657.5351793.762.99Peak Month (Strong Seasonality)
2016September136.23136.230.01Low Season / Slack Period
2017April405988.381304086.860.69Low Season / Slack Period
2017August662071.773627770.841.12Standard Performance
2017December861914.467144305.034392939.871.46Peak Month (Strong Seasonality)
2017February283621.94472442.080.48Low Season / Slack Period
2017January137006.76188820.140.23Low Season / Slack Period
2017July578858.582965699.070.98Standard Performance
2017June499827.472386840.490.85Low Season / Slack Period
2017March425656.40898098.480.72Low Season / Slack Period
2017May582926.161887013.020.99Standard Performance
2017November1172639.236282390.571.98Peak Month (Strong Seasonality)
2017October764785.285109751.341380.491.29Peak Month (Strong Seasonality)
2017September717195.224344966.06526359.111.21Peak Month (Strong Seasonality)
2018April1156303.9111535951.32184.811.21Peak Month (Strong Seasonality)
2018August996896.1515738970.5550.571.04Standard Performance
2018February979966.239226910.67245.521.03Standard Performance
2018January1102639.418246944.44704.811.15Standard Performance
2018July1039880.1614742074.4079.641.09Standard Performance
2018June1020494.2913702194.24104.171.07Standard Performance
2018March1152736.7410379647.41170.811.21Peak Month (Strong Seasonality)
2018May1145748.6312681699.9596.551.20Standard Performance
2018September166.4615739137.01-99.980.00Low Season / Slack Period

Insights:

  • Hockey Stick Growth Confirmed: The cumulative revenue curve shows a classic “hockey stick” shape. The platform started extremely slowly in 2016, but the slope turned sharply upward in mid-2017. It took 7 months to reach the first 1Mintotalsales,butby2018,thebusinesswashittingthatsame1M in total sales, but by 2018, the business was hitting that same 1M milestone every single month.
  • Rapid Scaling Milestones: The business reached a 5McumulativerevenuemarkinOctober2017.Incredibly,itonlytookanother5monthstodoublethattotalto5M cumulative revenue mark in October 2017. Incredibly, it only took another 5 months to double that total to 10M by March 2018. This acceleration proves that Olist successfully transitioned from a struggling startup phase into a high-growth scale-up.
  • The November 2017 Catalyst: November 2017 wasn’t just a good month; it was a permanent game-changer. The massive revenue spike during that period permanently steepened the cumulative growth line. After this point, the business never returned to its lower 2017 baselines, maintaining a much higher trajectory throughout 2018.
  • Final Revenue Snapshot: By the end of the recorded data in August 2018, the platform had generated over $15.7M in total revenue. The trajectory remained strong and steep right until the data cutoff, suggesting that the business was still in a healthy expansion phase.

Conclusion

What We Did:

  • Analyzed Month-over-Month (MoM) revenue growth to pinpoint hyper-growth phases versus periods of stabilization.
  • Calculated a Seasonality Index to determine the baseline importance of each month in the yearly cycle.
  • Performed a Year-over-Year (YoY) comparison to measure the scale of expansion between 2017 and 2018.
  • Conducted an Intra-day Analysis of order volumes by hour and day to identify peak shopping windows.
  • Tracked Cumulative Revenue to visualize the business’s overall growth trajectory.

What We Obtained (Key Findings):

  • The “Hockey Stick” Effect: Olist successfully moved from a slow startup phase in 2016 to a high-speed scale-up in 2017. The cumulative revenue trajectory turned sharply exponential in late 2017, reaching a total of over $15.7M by the end of the study period.
  • Maturity & Stability: While 2017 was a year of chaotic growth, 2018 showed a mature plateau. Revenue stabilized at a predictable baseline of ~$1M per month, indicating that the platform successfully retained its market share.
  • November is the Revenue Engine: November is the undisputed peak of the year. With a seasonality index of 1.98, it brings in nearly double the revenue of an average month, confirming that Black Friday is the most critical event for Olist’s financial health.
  • The Weekday Workflow: Shopping is a “workweek” activity. Monday is the highest-volume day, and sales consistently dip during the weekends.
  • Dual-Spike Daily Behavior: Customers follow a predictable daily routine across all states, with a primary Lunchtime Spike (12 PM - 2 PM) and a secondary Evening Spike (8 PM - 10 PM). Monday at 9 PM represents the absolute peak of customer activity.