Use the tabs below to navigate through the required rubric sections
of this report.
Use the hide/show buttons to toggle between hiding and viewing R
code.
Introduction
The Growth Possibility
Retail promotions are commonly used to stimulate product demand
through an increase in short-term sales to encourage customer
purchasing. However, not all promotions lead to true, lasting
incremental growth. Some promotions may simply prompt customers to buy a
product earlier than they normally would, rather than increasing overall
demand like intended. As a result, future sales may be reduced and
profit margins may be negatively impacted. This behavior, often referred
to as pull-forward purchasing, occurs when customers take advantage of a
promotion to stock-up, resulting in higher sales volume during the
promotion, but lower sales volume afterward.
This analysis examines whether product promotions at Regork result in
sustained increases in sales, or if product promotions primarily reflect
this pull-forward behavior.
Identifying which promotions drive lasting demand represents an
important growth opportunity, as it allows Regork to focus marketing
efforts on products that generate meaningful returns, while reducing
investment in less effective promotions. Understanding this distinction
is essential for improving marketing efficiency, protecting margins, and
supporting long-term revenue growth.
Analytic Methodology Employed
Transaction-level sales data were used to identify promotional
periods for selected high-volume products. For each product, sales
performance was evaluated across three windows: pre-promotion, during
promotion, and post-promotion.
Promotional activity in this dataset is defined at the
product–store–week level; therefore, a promotion flag was constructed to
identify weeks in which a given product was actively promoted.
Total weekly unit sales and revenue were computed for each window,
and percentage changes were calculated to quantify promotional lift and
post-promotion effects. These metrics were used to classify promotions
as generating likely true demand or pull-forward behavior.
Proposed Use of Analysis
This analysis is designed to provide the Regork CEO with a clearer
understanding of how promotional investments translate into long-term
value.
While promotions are frequently evaluated based on short-term sales
lift, this perspective may overlook whether those gains represent true
incremental demand or merely a shift in purchasing timing.
By examining both in-period lift and post-promotion performance, this
analysis seeks to determine which promotions create sustained growth and
which may only accelerate purchases temporarily.
The findings will help inform a more strategic allocation of
promotional resources, enabling Regork to prioritize initiatives that
support durable revenue growth rather than short-term volume spikes.
Packages & Libraries
# Packages and Libraries to be used
library(completejourney)
library(tidyverse)
library(scales)
library(lubridate)
Package Descriptions
# Package Table listing all packages and tables to be used, along with the explanation of their intended use and purpose
package_table <- tibble(
Package = c(
"completejourney",
"dplyr (via tidyverse)",
"tidyr (via tidyverse)",
"ggplot2 (via tidyverse)",
"scales",
"lubridate"
),
Purpose = c(
"Provides transaction, product, and promotion datasets used in the analysis",
"Used for data cleaning, joining tables, filtering observations, and summarizing sales metrics",
"Used to reshape summarized data for comparison across promotion windows",
"Used to create visualizations of promotional performance",
"Used to format percentage metrics and improve axis label readability in visualizations",
"Used to perform date calculations required to define pre-, during-, and post-promotion windows"
)
)
knitr::kable(package_table)
| completejourney |
Provides transaction, product, and promotion datasets
used in the analysis |
| dplyr (via tidyverse) |
Used for data cleaning, joining tables, filtering
observations, and summarizing sales metrics |
| tidyr (via tidyverse) |
Used to reshape summarized data for comparison across
promotion windows |
| ggplot2 (via tidyverse) |
Used to create visualizations of promotional
performance |
| scales |
Used to format percentage metrics and improve axis
label readability in visualizations |
| lubridate |
Used to perform date calculations required to define
pre-, during-, and post-promotion windows |
# Suppress messages and warnings resulting from loading the packages
suppressPackageStartupMessages({
library(completejourney)
library(tidyverse)
library(scales)
library(lubridate)
})
Exploratory Data Analysis
Methodology
This analysis evaluates promotional effectiveness by comparing
product sales before, during, and after promotional weeks. For a
selected group of frequently promoted products, total weekly sales and
unit volume were calculated across three time windows: a pre-promotion
period (the week prior to promotion), the promotion period itself (the
week during the promotion), and a post-promotion period (the week
following the promotion).
Promotional lift was measured as the percentage change in total sales
during the promotion week relative to the pre-promotion baseline
week.
Post-promotion change was calculated in a similar manner to assess
whether sales remained elevated or declined after the promotion
concluded.
These metrics allow for distinction between promotions that generate
sustained demand and those that primarily encourage customers to shift
purchases earlier than they otherwise would.
By examining both short-term lift and post-promotion performance,
this approach provides a practical framework for identifying promotions
that contribute to true incremental growth versus those that reflect
pull-forward purchasing behavior.
Key Definitions
# Table listing all key terms referenced throughout analysis, along with their explanation
key_definitions <- tibble(
Term = c(
"Pre-Promotion",
"Promotion Period",
"Post-Promotion",
"Lift",
"Post-Promotion Change"
),
Definition = c(
"The week immediately preceding the promotion week",
"The week in which the product is promoted",
"The week immediately following the promotion week",
"Percent change in total sales during the promotion week vs the pre-promotion week",
"Percent change in total sales during the post-promotion week vs the pre-promotion week"
)
)
knitr::kable(key_definitions)
| Pre-Promotion |
The week immediately preceding the promotion week |
| Promotion Period |
The week in which the product is promoted |
| Post-Promotion |
The week immediately following the promotion week |
| Lift |
Percent change in total sales during the promotion week
vs the pre-promotion week |
| Post-Promotion Change |
Percent change in total sales during the post-promotion
week vs the pre-promotion week |
Navigation
Use the tabs below to view the datasets used and the variables
created for this analysis.
Datasets
The completejourney package was used to access transaction, product,
and promotion data. These three datasets were the main datasets utilized
throughout this analysis.
Dataset
Descriptions
# Dataset Table listing all datasets to be used, along with the explanation of their intended use and purpose
datasets <- tibble(
Dataset = c(
"Transactions",
"Products",
"Promotions"
),
Description = c(
"Customer-level purchase transactions including product, quantity, sales value, and date",
"Product metadata including category and brand information",
"Promotion calendar indicating when products were promoted"
)
)
knitr::kable(datasets)
| Transactions |
Customer-level purchase transactions including product,
quantity, sales value, and date |
| Products |
Product metadata including category and brand
information |
| Promotions |
Promotion calendar indicating when products were
promoted |
# Load Transactions, Products, and Promotions Datasets
transactions <- get_transactions()
prod <- products
promotions <- get_promotions()
Transactions
Dataset Preview
# Transactions Dataset Preview
transactions %>%
as_tibble() %>%
head(10) %>%
knitr::kable()
| 900 |
330 |
31198570044 |
1095275 |
1 |
0.50 |
0.00 |
0 |
0 |
1 |
2017-01-01 06:53:26 |
| 900 |
330 |
31198570047 |
9878513 |
1 |
0.99 |
0.10 |
0 |
0 |
1 |
2017-01-01 07:10:28 |
| 1228 |
406 |
31198655051 |
1041453 |
1 |
1.43 |
0.15 |
0 |
0 |
1 |
2017-01-01 07:26:30 |
| 906 |
319 |
31198705046 |
1020156 |
1 |
1.50 |
0.29 |
0 |
0 |
1 |
2017-01-01 07:30:27 |
| 906 |
319 |
31198705046 |
1053875 |
2 |
2.78 |
0.80 |
0 |
0 |
1 |
2017-01-01 07:30:27 |
| 906 |
319 |
31198705046 |
1060312 |
1 |
5.49 |
0.50 |
0 |
0 |
1 |
2017-01-01 07:30:27 |
| 906 |
319 |
31198705046 |
1075313 |
1 |
1.50 |
0.29 |
0 |
0 |
1 |
2017-01-01 07:30:27 |
| 1058 |
381 |
31198676055 |
985893 |
1 |
1.88 |
0.21 |
0 |
0 |
1 |
2017-01-01 07:56:33 |
| 1058 |
381 |
31198676055 |
988791 |
1 |
1.50 |
1.29 |
0 |
0 |
1 |
2017-01-01 07:56:33 |
| 1058 |
381 |
31198676055 |
9297106 |
1 |
2.69 |
0.00 |
0 |
0 |
1 |
2017-01-01 07:56:33 |
Products Dataset
Preview
# Products Dataset Preview
products %>%
as_tibble() %>%
head(10) %>%
knitr::kable()
| 25671 |
2 |
GROCERY |
National |
FRZN ICE |
ICE - CRUSHED/CUBED |
22 LB |
| 26081 |
2 |
MISCELLANEOUS |
National |
NA |
NA |
NA |
| 26093 |
69 |
PASTRY |
Private |
BREAD |
BREAD:ITALIAN/FRENCH |
NA |
| 26190 |
69 |
GROCERY |
Private |
FRUIT - SHELF STABLE |
APPLE SAUCE |
50 OZ |
| 26355 |
69 |
GROCERY |
Private |
COOKIES/CONES |
SPECIALTY COOKIES |
14 OZ |
| 26426 |
69 |
GROCERY |
Private |
SPICES & EXTRACTS |
SPICES & SEASONINGS |
2.5 OZ |
| 26540 |
69 |
GROCERY |
Private |
COOKIES/CONES |
TRAY PACK/CHOC CHIP COOKIES |
16 OZ |
| 26601 |
69 |
DRUG GM |
Private |
VITAMINS |
VITAMIN - MINERALS |
300 CT(1) |
| 26636 |
69 |
PASTRY |
Private |
BREAKFAST SWEETS |
SW GDS: SW ROLLS/DAN |
NA |
| 26691 |
16 |
GROCERY |
Private |
PNT BTR/JELLY/JAMS |
HONEY |
12 OZ |
Variables
Use the tabs below to navigate through the variables created for this
analysis. The variables are organized in the order they were constructed
to reflect the progression of the analytic methodology.
High-Impact Products
To keep the analysis focused on products with meaningful promotional
relevance and growth potential, fuel-related items and
coupon/miscellaneous categories were excluded, as these products follow
different purchasing patterns and are not representative of typical
retail promotions.
I first identified the top 10 product categories based on total sales
value in the transactions dataset, ensuring that categories were not
duplicated. Within each of these high-performing categories, I then
selected the top-selling product type based on total sales. Finally, for
each product type, I identified the highest-selling product ID to serve
as a representative item for promotion analysis.
This step-wise approach ensures that the evaluation centers on
revenue-driving retail product areas, while still enabling a direct
match to promotion records. The resulting set of products represents key
contributors to overall sales and provides a practical foundation for
assessing whether promotions generate sustained incremental growth or
primarily encourage customers to shift purchases forward.
The tables below showcase these steps and summarize these selected
products and their contribution to total sales.
Step
1: Find the top 10 product categories by total sales
# Find the top 10 product categories by total sales
top10_categories <- transactions %>%
left_join(products, by = "product_id") %>%
filter(
department != "FUEL",
product_category != "COUPON/MISC ITEMS"
) %>%
group_by(product_category) %>%
summarise(
total_category_sales = sum(sales_value, na.rm = TRUE),
total_category_units = sum(quantity, na.rm = TRUE),
n_transactions = n(),
.groups = "drop"
) %>%
arrange(desc(total_category_sales)) %>%
slice_head(n = 10)
top10_categories %>%
as_tibble() %>%
knitr::kable()
| SOFT DRINKS |
182126.30 |
89496 |
65356 |
| BEEF |
176614.54 |
37343 |
27512 |
| FLUID MILK PRODUCTS |
116360.68 |
66440 |
48467 |
| CHEESE |
107011.61 |
55019 |
42678 |
| FRZN MEAT/MEAT DINNERS |
93023.74 |
45835 |
32415 |
| BAG SNACKS |
84943.85 |
45751 |
38088 |
| BAKED BREAD/BUNS/ROLLS |
82429.70 |
56709 |
47225 |
| BEERS/ALES |
82039.45 |
10983 |
9943 |
| FROZEN PIZZA |
81175.04 |
37404 |
23998 |
| COLD CEREAL |
63009.35 |
23407 |
21397 |
Step
2: Within each top 10 category, find the top-selling product type
# Within each top 10 category, find the top-selling product type
top_product_type_per_category <- transactions %>%
left_join(products, by = "product_id") %>%
semi_join(top10_categories, by = "product_category") %>%
group_by(product_category, product_type) %>%
summarise(
total_product_type_sales = sum(sales_value, na.rm = TRUE),
total_product_type_units = sum(quantity, na.rm = TRUE),
n_transactions = n(),
.groups = "drop"
) %>%
arrange(desc(total_product_type_sales)) %>%
group_by(product_category) %>%
slice_head(n = 1) %>% # keeps only top product_type per category
ungroup() %>%
arrange(desc(total_product_type_sales))
top_product_type_per_category %>%
as_tibble() %>%
knitr::kable()
| FLUID MILK PRODUCTS |
FLUID MILK WHITE ONLY |
91834.49 |
52443 |
37279 |
| SOFT DRINKS |
SOFT DRINKS 12/18&15PK CAN CAR |
89596.77 |
31740 |
22489 |
| BEERS/ALES |
BEERALEMALT LIQUORS |
81279.19 |
10848 |
9812 |
| BEEF |
CHOICE BEEF |
45684.40 |
7921 |
5991 |
| CHEESE |
SHREDDED CHEESE |
38213.73 |
20256 |
15698 |
| BAG SNACKS |
POTATO CHIPS |
28970.23 |
13190 |
12178 |
| COLD CEREAL |
KIDS CEREAL |
25912.96 |
10348 |
9593 |
| FRZN MEAT/MEAT DINNERS |
FRZN SS PREMIUM ENTREES/DNRS/N |
25838.17 |
12040 |
9603 |
| FROZEN PIZZA |
SNACKS/APPETIZERS |
25444.52 |
9318 |
6675 |
| BAKED BREAD/BUNS/ROLLS |
MAINSTREAM WHITE BREAD |
24729.61 |
19273 |
15260 |
Step
3: Within the top-selling product type for each top 10 category, find
the top-selling product using product_id
# Within the top-selling product type for each top 10 category, find the top-selling product using product_id
top_product_id_per_category <- transactions %>%
left_join(products, by = "product_id") %>%
inner_join(top_product_type_per_category, by = c("product_category", "product_type")) %>%
group_by(product_category, product_type, product_id) %>%
summarise(
total_product_id_sales = sum(sales_value, na.rm = TRUE),
total_product_id_units = sum(quantity, na.rm = TRUE),
n_transactions = n(),
.groups = "drop"
) %>%
arrange(desc(total_product_id_sales)) %>%
group_by(product_category) %>%
slice_head(n = 1) %>% # keeps only top product_id per category
ungroup() %>%
arrange(desc(total_product_id_sales))
top_product_id_per_category %>%
as_tibble() %>%
knitr::kable()
| FLUID MILK PRODUCTS |
FLUID MILK WHITE ONLY |
1029743 |
22729.71 |
9264 |
7874 |
| SOFT DRINKS |
SOFT DRINKS 12/18&15PK CAN CAR |
5569230 |
13410.46 |
4413 |
2771 |
| BAKED BREAD/BUNS/ROLLS |
MAINSTREAM WHITE BREAD |
951590 |
6832.25 |
4105 |
3291 |
| BEEF |
CHOICE BEEF |
863447 |
4552.85 |
790 |
617 |
| CHEESE |
SHREDDED CHEESE |
859075 |
4481.14 |
2979 |
2057 |
| BEERS/ALES |
BEERALEMALT LIQUORS |
1065538 |
3314.03 |
207 |
168 |
| BAG SNACKS |
POTATO CHIPS |
9526410 |
2063.83 |
1095 |
996 |
| FROZEN PIZZA |
SNACKS/APPETIZERS |
907631 |
1990.50 |
632 |
498 |
| COLD CEREAL |
KIDS CEREAL |
1054262 |
939.89 |
500 |
439 |
| FRZN MEAT/MEAT DINNERS |
FRZN SS PREMIUM ENTREES/DNRS/N |
12782182 |
453.51 |
166 |
107 |
Promotion Flag
To evaluate promotional performance, a working dataset was
constructed by integrating transaction, product, and promotion
records.
Transactions were first restricted to the selected top-selling
product IDs identified in the High-Impact Products tab.
These transactions were then joined to the promotions dataset using a
composite key of product_id, store_id, and week, reflecting how
promotional activity is defined in the Complete Journey dataset.
A promotion flag (promo_flag) was created to indicate whether a
transaction occurred during a promotional week.
If a transaction matched a promotion record for the same product,
store, and week, it was labeled as promotional (1); otherwise, it was
labeled as non-promotional (0).
This binary indicator enables direct comparison between promotional
and non-promotional sales performance and establishes the foundation for
subsequent lift and window-based analyses.
Promotion Windows
To examine sales behavior surrounding promotional activity, promotion
windows were constructed using the same filtered set of top-selling
product IDs.
Promotion records were first isolated for these products, and
transaction data were aligned at the product–store–week level.
For each documented promotional week, three time periods were
defined: the week immediately prior (pre), the promotional week itself
(during), and the week immediately following (post).
For each product, total sales and unit volume were calculated across
the three time periods (pre, during, and post).
Transactions were labeled accordingly by comparing the transaction
week to the associated promotion week within each store using the binary
promotion indicator (promo_flag) created in the prior step.
Organizing results in this structured way made it possible to assess
whether promotions produced meaningful increases during the promotion
window and whether those gains persisted afterward or declined,
indicating potential pull-forward purchasing.
Step
2: Construct Pre/During/Post promotion windows
# Create pre/during/post windows around each promo week
promo_windows <- promo_products %>%
mutate(
pre_week = promo_week - 1,
post_week = promo_week + 1
) %>%
pivot_longer(
cols = c(pre_week, promo_week, post_week),
names_to = "period",
values_to = "week"
) %>%
mutate(
period = recode(period,
pre_week = "Pre",
promo_week = "During",
post_week = "Post"
)
) %>%
select(product_id, store_id, week, period)
Interpretation
Across most product categories, sales increased during promotional
weeks relative to the pre-promotion period, indicating a positive
short-term promotional lift.
Categories such as Beef, Soft Drinks, and Cold Cereal show
particularly pronounced increases during the promotion window.
However, in several cases, post-promotion sales declined toward or
below pre-promotion levels, as seen in Frozen Pizza, as well as Soft
Drinks and Cold Cereal, suggesting potential pull-forward purchasing
behavior.
Unlike most categories, Frozen Meat/Meat Dinners did not exhibit
promotional lift. Sales declined during and after the promotion period
relative to the pre-promotion baseline, suggesting limited promotional
responsiveness in this category.
Overall, promotions appear to generate immediate sales gains, though
the persistence of those gains varies by category.
Promotion Impact Metrics
To quantify promotional effectiveness, the summarized pre-, during-,
and post-promotion sales and unit totals were reshaped into a
product-level comparison table.
Pre-period performance was used as the baseline for evaluation.
Lift and post-promotion change were calculated relative to the
pre-promotion baseline as follows:
\[
\text{Lift} = \frac{\text{During} - \text{Pre}}{\text{Pre}}
\]
\[
\text{Post-Change} = \frac{\text{Post} - \text{Pre}}{\text{Pre}}
\]
Lift metrics were calculated by measuring the percentage change from
the pre-promotion period to the promotion period, while post-change
metrics were calculated by comparing post-promotion performance to the
same baseline.
These calculations provide a standardized way to assess immediate
promotional impact and determine whether effects persisted or declined
following the promotion.
Create lift metrics and post
change metrics
# Build a product-level "decision table" with lift and post-change metrics
lift_table <- promo_summary %>%
select(product_category, product_type, product_id, period,
total_period_sales, total_period_units) %>%
pivot_wider(
names_from = period,
values_from = c(total_period_sales, total_period_units),
values_fill = 0
) %>%
mutate(
# Unit metrics
unit_lift = if_else(total_period_units_Pre > 0,
(total_period_units_During - total_period_units_Pre) / total_period_units_Pre,
NA_real_),
unit_post_change = if_else(total_period_units_Pre > 0,
(total_period_units_Post - total_period_units_Pre) / total_period_units_Pre,
NA_real_),
# Sales metrics
sales_lift = if_else(total_period_sales_Pre > 0,
(total_period_sales_During - total_period_sales_Pre) / total_period_sales_Pre,
NA_real_),
sales_post_change = if_else(total_period_sales_Pre > 0,
(total_period_sales_Post - total_period_sales_Pre) / total_period_sales_Pre,
NA_real_)
) %>%
# Round and sort for reporting
mutate(
across(c(unit_lift, unit_post_change, sales_lift, sales_post_change),
~ round(.x, 3))
) %>%
arrange(desc(sales_lift))
Key
Insight
The following tables compare selling performance during and after a
promotion to the pre-promotion baseline.
Positive lift means the promotion increased sales or units compared
to before it ran.
Negative post-change suggests potential pull-forward behavior, where
consumers shifted purchases into the promotion period and thus,
purchases declined afterward.
Table
summarizing unit lift and unit post change
# Summary table of unit_lift and unit_post_change from lift_table
lift_table %>%
select(product_category, product_type, product_id, total_period_units_Pre, total_period_units_During, total_period_units_Post, unit_lift, unit_post_change) %>%
DT::datatable(
options = list(
pageLength = 10,
scrollX = TRUE
)
) %>%
DT::formatRound(
columns = c("unit_lift",
"unit_post_change"),
digits = 3
)
Table
summarizing sales lift and sales post change
# Summary table of sales_lift and sales_post_change from lift_table
lift_table %>%
select(product_category, product_type, product_id, total_period_sales_Pre, total_period_sales_During, total_period_sales_Post, sales_lift, sales_post_change) %>%
DT::datatable(
options = list(
pageLength = 10,
scrollX = TRUE
)
) %>%
DT::formatRound(
columns = c("total_period_sales_Pre",
"total_period_sales_During",
"total_period_sales_Post"),
digits = 2
) %>%
DT::formatRound(
columns = c("sales_lift",
"sales_post_change"),
digits = 3
)
Table
summarizing lift values and post change values as percentages
# Report-ready summary table showing unit_lift and unit_post_change, sales_lift and sales_post_change as percentages
lift_table %>%
mutate(
unit_lift = scales::percent(unit_lift, accuracy = 0.1),
unit_post_change = scales::percent(unit_post_change, accuracy = 0.1),
sales_lift = scales::percent(sales_lift, accuracy = 0.1),
sales_post_change = scales::percent(sales_post_change, accuracy = 0.1)
) %>%
select(product_category, product_type, product_id,
unit_lift, unit_post_change, sales_lift, sales_post_change) %>%
knitr::kable()
| BEEF |
CHOICE BEEF |
863447 |
490.5% |
-29.8% |
345.0% |
-20.8% |
| SOFT DRINKS |
SOFT DRINKS 12/18&15PK CAN CAR |
5569230 |
53.9% |
-5.5% |
39.5% |
-5.5% |
| COLD CEREAL |
KIDS CEREAL |
1054262 |
32.4% |
0.0% |
29.5% |
-3.5% |
| FROZEN PIZZA |
SNACKS/APPETIZERS |
907631 |
18.1% |
-21.0% |
13.5% |
-21.2% |
| BEERS/ALES |
BEERALEMALT LIQUORS |
1065538 |
12.4% |
9.7% |
12.5% |
9.6% |
| CHEESE |
SHREDDED CHEESE |
859075 |
14.6% |
2.4% |
10.5% |
3.8% |
| FLUID MILK PRODUCTS |
FLUID MILK WHITE ONLY |
1029743 |
14.6% |
6.3% |
5.6% |
5.0% |
| BAG SNACKS |
POTATO CHIPS |
9526410 |
3.3% |
2.9% |
3.0% |
2.9% |
| BAKED BREAD/BUNS/ROLLS |
MAINSTREAM WHITE BREAD |
951590 |
2.4% |
-1.7% |
0.3% |
0.5% |
| FRZN MEAT/MEAT DINNERS |
FRZN SS PREMIUM ENTREES/DNRS/N |
12782182 |
-15.4% |
-38.5% |
-21.1% |
-33.6% |
Graph
summarizing sales lift and sales post change percentages by
product
# Graph of sales_lift and sales_post_change percentages from lift_table
# Reshape data for plotting
lift_plot_data <- lift_table %>%
arrange(desc(sales_lift)) %>%
select(product_type, sales_lift, sales_post_change) %>%
pivot_longer(
cols = c(sales_lift, sales_post_change),
names_to = "metric",
values_to = "value"
)
ggplot(lift_plot_data,
aes(x = product_type,
y = value,
fill = metric)) +
geom_col(position = "dodge") +
geom_hline(yintercept = 0, linetype = "dashed") +
scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
coord_flip(ylim = c(-0.5, 1)) +
labs(
title = "Sales Lift and Post-Promotion Change by Product",
x = "Product",
y = "Percent Change vs. Pre-Promotion"
) +
theme_minimal()

Interpretation
This chart shows how each product’s promotional lift compares to its
post-promotion performance, relative to the same baseline for each, the
pre-promotion baseline.
Most products experience positive lift during the promotional period,
meaning sales increased while the promotion was active.
However, several products like Choice Beef, Snacks/Appetizers, and
Soft Drinks show negative post-promotion change, indicating that sales
dropped below baseline after the promotion ended.
In contrast, products like Beer/Ale/Malt Liquors, Shredded Cheese,
and Fluid Milk White Only maintain small positive gains after the
promotion period.
Overall, the chart illustrates that while promotions generally
increase short-term sales, post-promotion performance varies
meaningfully across products.
Summary
Business Question
Retail promotions are a critical lever for driving short-term sales
growth, but not all promotional activity generates true incremental
demand. In many cases, promotions may simply shift purchases forward,
creating temporary spikes in sales followed by post-promotion declines.
This dynamic can inflate performance metrics during the promotional
window while masking limited long-term impact.
The central business question addressed in this analysis is: Which
product promotions generate sustained incremental sales growth, and
which primarily shift purchases forward without increasing overall
demand?
By distinguishing between these two outcomes, Regork can better
allocate promotional resources toward strategies that enhance long-term
revenue and profitability rather than short-term volume gains.
Analytic Methodology Employed
This analysis utilized the Complete Journey dataset, integrating
transaction-level sales data with product records and promotional
records to evaluate promotional performance across selected high-volume
product categories.
Promotional activity in this dataset is defined at the
product–store–week level; therefore, a promotion flag was constructed to
identify weeks in which a given product was actively promoted.
Using these promotion indicators, structured comparison windows were
defined surrounding promotional activity.
For each selected product, total weekly sales and unit volume were
calculated across three time windows: a pre-promotion period (the week
prior to promotion), the promotion period itself (the week during the
promotion), and a post-promotion period (the week following the
promotion).
For each product, two key performance metrics were calculated:
Promotional lift was measured as the percentage change in total sales
during the promotion week relative to the pre-promotion baseline
week.
Post-promotion change was calculated in a similar manner to assess
whether sales remained elevated or declined after the promotion
concluded.
These metrics allow for direct comparison of short-term promotional
impact and subsequent sales behavior, supporting evaluation of whether
promotions generate sustained incremental growth or primarily shift
purchasing behavior across time periods.
Analytic Insights
This analysis demonstrates that high promotional lift does not
necessarily translate into sustained growth.
To synthesize findings, I developed a Promotion Impact Decision Map,
a scatter plot visualizing immediate promotional lift on the horizontal
axis and post-promotion performance on the vertical axis.
This framework enabled clear segmentation of products into those
generating sustained growth, those producing temporary lift, and those
experiencing negative post-promotion effects.
Promotion Impact Decision Map
# Scatter plot of sales_lift and sales_post_change percentages from lift_table
ggplot(lift_table,
aes(x = sales_lift,
y = sales_post_change,
color = product_type)) +
geom_point(size = 4, alpha = 0.9) +
geom_vline(xintercept = 0, linetype = "dashed") +
geom_hline(yintercept = 0, linetype = "dashed") +
scale_x_continuous(labels = percent_format(accuracy = 1)) +
scale_y_continuous(labels = percent_format(accuracy = 1)) +
labs(
title = "Promotion Impact Decision Map",
subtitle = "Right = greater lift during promotion | Up = sustained gains after promotion",
x = "Sales Lift During Promotion (%)",
y = "Sales Change After Promotion (%)",
color = "Product"
) +
theme_minimal()

Several products exhibited strong increases in sales during the
promotion period, but experienced measurable declines afterward,
indicating forward-shifted demand rather than true incremental
consumption. In these cases, promotions appear to accelerate purchasing
behavior without expanding overall demand.
Specifically, Choice Beef generated extremely high in-period lift,
but experienced a substantial post-promotion decline, suggesting strong
pull-forward behavior rather than sustained demand.
Similarly, Snacks/Appetizers and Soft Drinks show positive lift
during promotion, but negative post-period performance, indicating
temporary volume shifts.
Conversely, a smaller subset of products showed both positive
promotional lift and continued strength in the post-promotion period.
These products represent true incremental growth drivers, suggesting
that promotional exposure increased consumption, or customer
acquisition, in a more lasting manner.
For example, Beer/Ale/Malt Liquors, Shredded Cheese, and Fluid Milk
White Only all demonstrate both positive lift and modest post-promotion
gains, suggesting stronger demand impact.
Overall, the visualization suggests that not all promotions create
lasting value, reinforcing the need for a more selective,
performance-based promotional strategy.
For instance, Frozen Premium Entrees/Dinners shows negative lift and
a sharp post-promotion decline, indicating that current promotions may
not be effective for this category.
The findings also suggest that promotional outcomes vary meaningfully
by product category. Staple categories tended to revert quickly to
baseline levels after promotions ended, while more discretionary or
indulgent categories displayed greater volatility, with sharper
increases and sharper declines.
These patterns indicate that product characteristics and purchase
behavior play a significant role in determining whether promotions
create sustainable value.
Analytic Implications
From a strategic perspective, Regork should move beyond evaluating
promotions based solely on short-term lift.
While many products show strong in-period gains, the scatter analysis
demonstrates that not all of those gains translate into sustained
demand. Promotions that generate both positive lift and stable or
positive post-promotion performance should be prioritized, as they
reflect true incremental growth, rather than shifted purchasing
behavior.
For products that consistently experience post-promotion declines,
the promotional approach may need to be adjusted. This could include
reducing discount depth, spacing promotions further apart, or
integrating cross-category strategies to discourage forward buying.
More broadly, incorporating post-promotion performance into the
evaluation process would allow Regork to make more disciplined,
data-informed promotional decisions. By focusing on long-term impact
rather than short-term volume spikes, leadership can better align
promotional investments with sustainable revenue growth and overall
profitability objectives.
Limitations
This analysis is descriptive in nature and does not establish causal
relationships.
It does not control for seasonality, competitive activity,
promotional depth, or differences in display or coupon mechanics.
Additionally, the fixed pre- and post-promotion windows may not fully
capture longer-term behavioral adjustments.
Future work could incorporate price elasticity modeling,
customer-level segmentation, and predictive modeling to better forecast
which promotions are likely to generate sustained incremental
demand.
Incorporating additional variables such as discount magnitude and
promotional channel would further strengthen strategic
recommendations.
Despite these limitations, the analysis provides a structured and
actionable framework for distinguishing between temporary promotional
spikes and lasting growth effects.