Reflection on Regression and Classification Models with Group Project Data

Analytics Objective: Predicting Retail Sales Revenue

Author

Mickyas Shawel

Published

August 2, 2026

1 Assignment Overview

Prompt: Prepare a Quarto report that applies a complete machine-learning workflow to the group-project data. State the analytics objective, identify the learning type and variable roles, compare alternative methods using 10-fold cross-validation, interpret feature importance, answer the five reflection questions, and publish the QMD and HTML files.

This report uses a customer-oriented approach. The analysis is designed to help a retailer estimate expected revenue before finalizing promotional decisions.

TipCustomer-oriented perspective

Retail decision-makers need reliable estimates of sales revenue so they can coordinate discounts, marketing investments, product strategy, and seasonal planning.

2 Analytics Objective

2.1 Prompt

State the analytics objective for the project. Explain whether the objective uses supervised or unsupervised learning, identify the outcome and feature variables, and name the broader machine-learning category.

2.2 Response

The analytics objective is to build and compare regression models that predict retail sales revenue using promotional, product, location, and calendar information.

The customer-oriented question is:

How accurately can a retailer predict expected sales revenue using information available when planning a promotion?

2.2.1 Learning type

This is supervised learning because the historical dataset contains a known outcome variable that can be used to train and evaluate predictive models.

2.2.2 Broader machine-learning category

This is a regression problem because the outcome is a continuous dollar amount.

2.2.3 Variable roles

Role Variables
Outcome sales_revenue_usd
Numeric features discount_percentage, marketing_spend_usd
Categorical features store_location, product_category, day_of_the_week, holiday_effect, month, season
Identifier variables store_id, product_id
Excluded from prediction units_sold
WarningTarget leakage decision

units_sold is excluded because it is a same-period sales result. A retailer planning a promotion may not know units sold in advance, so using it would make the model look more accurate than it would be in practice.

3 Setup

4 Data Preparation

4.1 Prompt

Set up the QMD file with the required libraries and data. Prepare the data for the machine-learning workflow.

4.2 Import and transform the data

Code
retail <- read_csv(
  "data/Retail_sales.csv",
  show_col_types = FALSE
) |>
  clean_names() |>
  mutate(
    date = as.Date(date),
    month = month(date, label = TRUE, abbr = TRUE),
    year = factor(year(date)),
    season = case_when(
      month(date) %in% c(12, 1, 2) ~ "Winter",
      month(date) %in% c(3, 4, 5) ~ "Spring",
      month(date) %in% c(6, 7, 8) ~ "Summer",
      TRUE ~ "Fall"
    ),
    season = factor(
      season,
      levels = c("Winter", "Spring", "Summer", "Fall")
    ),
    holiday_effect = factor(
      holiday_effect,
      levels = c(FALSE, TRUE),
      labels = c("Non-Holiday", "Holiday")
    ),
    day_of_the_week = factor(
      day_of_the_week,
      levels = c(
        "Monday", "Tuesday", "Wednesday", "Thursday",
        "Friday", "Saturday", "Sunday"
      )
    ),
    store_location = factor(store_location),
    product_category = factor(product_category),
    store_id = factor(store_id),
    product_id = factor(product_id)
  )

glimpse(retail)
Rows: 30,000
Columns: 14
$ store_id            <fct> Spearsland, Spearsland, Spearsland, Spearsland, Sp…
$ product_id          <fct> 52372247, 52372247, 52372247, 52372247, 52372247, …
$ date                <date> 2022-01-01, 2022-01-02, 2022-01-03, 2022-01-04, 2…
$ units_sold          <dbl> 9, 7, 1, 4, 2, 8, 6, 9, 7, 1, 4, 6, 3, 6, 3, 2, 8,…
$ sales_revenue_usd   <dbl> 2741.69, 2665.53, 380.79, 1523.16, 761.58, 3046.32…
$ discount_percentage <dbl> 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 15, 0,…
$ marketing_spend_usd <dbl> 81, 0, 0, 0, 0, 41, 0, 83, 0, 164, 61, 0, 197, 0, …
$ store_location      <fct> Tanzania, Mauritania, Saint Pierre and Miquelon, A…
$ product_category    <fct> Furniture, Furniture, Furniture, Furniture, Furnit…
$ day_of_the_week     <fct> Saturday, Sunday, Monday, Tuesday, Wednesday, Thur…
$ holiday_effect      <fct> Non-Holiday, Non-Holiday, Non-Holiday, Non-Holiday…
$ month               <ord> Jan, Jan, Jan, Jan, Jan, Jan, Jan, Jan, Jan, Jan, …
$ year                <fct> 2022, 2022, 2022, 2022, 2022, 2022, 2022, 2022, 20…
$ season              <fct> Winter, Winter, Winter, Winter, Winter, Winter, Wi…

4.3 Sample characteristics

Code
sample_table <- tibble(
  Measure = c(
    "Observations",
    "Average sales revenue",
    "Median sales revenue",
    "Average discount",
    "Average marketing spend",
    "Product categories",
    "Store locations"
  ),
  Value = c(
    scales::comma(nrow(retail)),
    scales::dollar(mean(retail$sales_revenue_usd, na.rm = TRUE)),
    scales::dollar(median(retail$sales_revenue_usd, na.rm = TRUE)),
    paste0(round(mean(retail$discount_percentage, na.rm = TRUE), 2), "%"),
    scales::dollar(mean(retail$marketing_spend_usd, na.rm = TRUE)),
    as.character(n_distinct(retail$product_category)),
    as.character(n_distinct(retail$store_location))
  )
)

sample_table |>
  kable(caption = "Sample characteristics") |>
  kable_styling(full_width = FALSE)
Sample characteristics
Measure Value
Observations 30,000
Average sales revenue $2,749.51
Median sales revenue $1,902.42
Average discount 2.97%
Average marketing spend $49.94
Product categories 4
Store locations 243

4.4 Revenue distribution

Code
ggplot(retail, aes(x = sales_revenue_usd)) +
  geom_histogram(bins = 35) +
  scale_x_continuous(labels = scales::label_dollar()) +
  labs(
    title = "Distribution of Sales Revenue",
    x = "Sales Revenue",
    y = "Number of Records"
  )
Figure 1: Distribution of sales revenue.

5 Machine-Learning Workflow

5.1 Prompt

Pick an initial machine-learning method and explain why it is appropriate. Complete data splitting, recipe creation, model specification, fitting, and evaluation.

5.2 Initial method

Multiple linear regression is used as the baseline because it is transparent, widely understood, and easy to communicate to business stakeholders. Ridge and Lasso regression are then tested as alternatives because regularization may improve generalization when the model contains many dummy variables.

5.3 Modeling data

Code
model_data <- retail |>
  select(
    sales_revenue_usd,
    discount_percentage,
    marketing_spend_usd,
    store_location,
    product_category,
    day_of_the_week,
    holiday_effect,
    month,
    season,
    year,
    store_id,
    product_id
  )

model_data |>
  slice_head(n = 8) |>
  kable(caption = "Preview of the modeling dataset") |>
  kable_styling(full_width = FALSE)
Preview of the modeling dataset
sales_revenue_usd discount_percentage marketing_spend_usd store_location product_category day_of_the_week holiday_effect month season year store_id product_id
2741.69 20 81 Tanzania Furniture Saturday Non-Holiday Jan Winter 2022 Spearsland 52372247
2665.53 0 0 Mauritania Furniture Sunday Non-Holiday Jan Winter 2022 Spearsland 52372247
380.79 0 0 Saint Pierre and Miquelon Furniture Monday Non-Holiday Jan Winter 2022 Spearsland 52372247
1523.16 0 0 Australia Furniture Tuesday Non-Holiday Jan Winter 2022 Spearsland 52372247
761.58 0 0 Swaziland Furniture Wednesday Non-Holiday Jan Winter 2022 Spearsland 52372247
3046.32 0 41 Bhutan Furniture Thursday Non-Holiday Jan Winter 2022 Spearsland 52372247
2284.74 0 0 Suriname Furniture Friday Non-Holiday Jan Winter 2022 Spearsland 52372247
3427.11 0 83 Taiwan Furniture Saturday Non-Holiday Jan Winter 2022 Spearsland 52372247

5.4 Train/test split

Code
retail_split <- initial_split(
  model_data,
  prop = 0.80,
  strata = sales_revenue_usd
)

retail_train <- training(retail_split)
retail_test <- testing(retail_split)

tibble(
  Partition = c("Training", "Testing"),
  Observations = c(nrow(retail_train), nrow(retail_test))
) |>
  kable(caption = "Training and testing partitions") |>
  kable_styling(full_width = FALSE)
Training and testing partitions
Partition Observations
Training 23998
Testing 6002

5.5 Ten-fold cross-validation

Code
retail_folds <- vfold_cv(
  retail_train,
  v = 10,
  strata = sales_revenue_usd
)

tibble(
  Folds = nrow(retail_folds),
  Description = "10-fold cross-validation on the training set"
) |>
  kable() |>
  kable_styling(full_width = FALSE)
Folds Description
10 10-fold cross-validation on the training set

5.6 Recipe

Code
retail_recipe <- recipe(
  sales_revenue_usd ~ .,
  data = retail_train
) |>
  update_role(store_id, product_id, new_role = "ID") |>
  step_unknown(all_nominal_predictors()) |>
  step_novel(all_nominal_predictors()) |>
  step_dummy(all_nominal_predictors()) |>
  step_zv(all_predictors()) |>
  step_normalize(all_numeric_predictors())

summary(retail_recipe) |>
  kable(caption = "Recipe variable roles") |>
  kable_styling(full_width = FALSE)
Recipe variable roles
variable type role source
discount_percentage double , numeric predictor original
marketing_spend_usd double , numeric predictor original
store_location factor , unordered, nominal predictor original
product_category factor , unordered, nominal predictor original
day_of_the_week factor , unordered, nominal predictor original
holiday_effect factor , unordered, nominal predictor original
month ordered, nominal predictor original
season factor , unordered, nominal predictor original
year factor , unordered, nominal predictor original
store_id factor , unordered, nominal ID original
product_id factor , unordered, nominal ID original
sales_revenue_usd double , numeric outcome original

6 Baseline Linear Regression

6.1 Model specification

Code
linear_spec <- linear_reg() |>
  set_engine("lm")

linear_workflow <- workflow() |>
  add_recipe(retail_recipe) |>
  add_model(linear_spec)

6.2 Ten-fold cross-validation

Code
linear_results <- fit_resamples(
  linear_workflow,
  resamples = retail_folds,
  metrics = metric_set(rmse, mae, rsq)
)

linear_metrics <- collect_metrics(linear_results)

linear_metrics |>
  select(.metric, mean, std_err) |>
  kable(
    caption = "Cross-validated linear regression performance",
    digits = 3
  ) |>
  kable_styling(full_width = FALSE)
Cross-validated linear regression performance
.metric mean std_err
mae 1856.343 8.201
rmse 2453.425 16.362
rsq 0.084 0.005

7 Alternative Regression Methods

7.1 Prompt

What alternative methods can be used to improve the metrics? Try them.

7.2 Ridge regression

Code
ridge_spec <- linear_reg(
  penalty = tune(),
  mixture = 0
) |>
  set_engine("glmnet")

ridge_workflow <- workflow() |>
  add_recipe(retail_recipe) |>
  add_model(ridge_spec)

penalty_grid <- tibble(
  penalty = 10 ^ seq(-4, 0, length.out = 8)
)

ridge_results <- tune_grid(
  ridge_workflow,
  resamples = retail_folds,
  grid = penalty_grid,
  metrics = metric_set(rmse, mae, rsq)
)

ridge_best <- select_best(ridge_results, metric = "rmse")

ridge_best |>
  kable(caption = "Best Ridge penalty") |>
  kable_styling(full_width = FALSE)
Best Ridge penalty
penalty .config
1e-04 pre0_mod1_post0

7.3 Lasso regression

Code
lasso_spec <- linear_reg(
  penalty = tune(),
  mixture = 1
) |>
  set_engine("glmnet")

lasso_workflow <- workflow() |>
  add_recipe(retail_recipe) |>
  add_model(lasso_spec)

lasso_results <- tune_grid(
  lasso_workflow,
  resamples = retail_folds,
  grid = penalty_grid,
  metrics = metric_set(rmse, mae, rsq)
)

lasso_best <- select_best(lasso_results, metric = "rmse")

lasso_best |>
  kable(caption = "Best Lasso penalty") |>
  kable_styling(full_width = FALSE)
Best Lasso penalty
penalty .config
1 pre0_mod8_post0

8 Model Comparison

8.1 Prompt

Compare all methods using 10-fold cross-validation. Which method is best? Does the result make sense?

Code
linear_compare <- linear_metrics |>
  filter(.metric == "rmse") |>
  transmute(
    Model = "Linear Regression",
    RMSE = mean,
    Standard_Error = std_err
  )

ridge_compare <- show_best(
  ridge_results,
  metric = "rmse",
  n = 1
) |>
  transmute(
    Model = "Ridge Regression",
    RMSE = mean,
    Standard_Error = std_err
  )

lasso_compare <- show_best(
  lasso_results,
  metric = "rmse",
  n = 1
) |>
  transmute(
    Model = "Lasso Regression",
    RMSE = mean,
    Standard_Error = std_err
  )

model_comparison <- bind_rows(
  linear_compare,
  ridge_compare,
  lasso_compare
) |>
  arrange(RMSE)

model_comparison |>
  mutate(
    RMSE = scales::dollar(RMSE),
    Standard_Error = scales::dollar(Standard_Error)
  ) |>
  kable(caption = "Ten-fold cross-validated model comparison") |>
  kable_styling(full_width = FALSE)
Ten-fold cross-validated model comparison
Model RMSE Standard_Error
Lasso Regression $2,452.29 $16.45
Ridge Regression $2,452.86 $16.48
Linear Regression $2,453.43 $16.36
Code
model_comparison |>
  ggplot(aes(x = reorder(Model, RMSE), y = RMSE)) +
  geom_col() +
  geom_errorbar(
    aes(
      ymin = RMSE - Standard_Error,
      ymax = RMSE + Standard_Error
    ),
    width = 0.15
  ) +
  coord_flip() +
  scale_y_continuous(labels = scales::label_dollar()) +
  labs(
    title = "Cross-Validated RMSE",
    x = NULL,
    y = "Mean RMSE"
  )
Figure 2: Cross-validated RMSE by model.

The preferred model is the method with the lowest cross-validated RMSE. A regularized method may outperform ordinary linear regression because the recipe creates many indicator variables and some predictors may contain overlapping information.

9 Final Model Evaluation

9.1 Finalize the selected model

Code
best_model_name <- model_comparison |>
  slice_min(RMSE, n = 1, with_ties = FALSE) |>
  pull(Model)

if (best_model_name == "Linear Regression") {
  final_workflow <- linear_workflow
} else if (best_model_name == "Ridge Regression") {
  final_workflow <- finalize_workflow(
    ridge_workflow,
    ridge_best
  )
} else {
  final_workflow <- finalize_workflow(
    lasso_workflow,
    lasso_best
  )
}

best_model_name
[1] "Lasso Regression"

9.2 Held-out test set

Code
final_fit <- last_fit(
  final_workflow,
  split = retail_split,
  metrics = metric_set(rmse, mae, rsq)
)

collect_metrics(final_fit) |>
  select(.metric, .estimate) |>
  kable(
    caption = "Final test-set performance",
    digits = 3
  ) |>
  kable_styling(full_width = FALSE)
Final test-set performance
.metric .estimate
rmse 2462.667
mae 1835.843
rsq 0.100

9.3 Actual versus predicted revenue

Code
final_predictions <- collect_predictions(final_fit)

final_predictions |>
  sample_n(min(3000, nrow(final_predictions))) |>
  ggplot(aes(x = sales_revenue_usd, y = .pred)) +
  geom_point(alpha = 0.25) +
  geom_abline(
    slope = 1,
    intercept = 0,
    linetype = 2
  ) +
  scale_x_continuous(labels = scales::label_dollar()) +
  scale_y_continuous(labels = scales::label_dollar()) +
  labs(
    title = "Actual Versus Predicted Revenue",
    x = "Actual Revenue",
    y = "Predicted Revenue"
  )
Figure 3: Actual versus predicted revenue on the held-out test set.

10 Feature Importance

10.1 Prompt

Produce a feature-importance chart. Is the result reasonable? Why or why not?

Code
final_training_fit <- fit(
  final_workflow,
  data = retail_train
)

importance_table <- final_training_fit |>
  extract_fit_parsnip() |>
  tidy() |>
  filter(term != "(Intercept)") |>
  mutate(importance = abs(estimate)) |>
  slice_max(importance, n = 15)

importance_table |>
  ggplot(aes(
    x = reorder(term, importance),
    y = importance
  )) +
  geom_col() +
  coord_flip() +
  labs(
    title = "Top Predictive Features",
    x = NULL,
    y = "Absolute Standardized Coefficient"
  )
Figure 4: Top standardized coefficients in the selected model.

The result is reasonable when variables connected to promotional intensity, product differences, location, and seasonal timing appear among the largest coefficients. However, coefficient importance is predictive rather than causal.

CautionInterpretation limitation

A feature with a large coefficient contributes strongly to prediction after preprocessing, but the model does not prove that changing that feature will cause revenue to change.

11 Module 4 Reflection Questions

11.1 Question 1: Why is a train/test split necessary?

A train/test split separates model development from final evaluation. The training set is used to learn the model, while the test set estimates how accurately the finished workflow performs on unseen observations.

11.2 Question 2: Why use 10-fold cross-validation?

Ten-fold cross-validation provides a more stable estimate than a single validation split. Each observation in the training data is used for validation once and for model fitting nine times.

11.3 Question 3: What is the purpose of a recipe?

A recipe stores preprocessing steps in a reproducible workflow. It also prevents leakage by learning preprocessing information from the analysis portion of each resample before applying it to the assessment portion.

11.4 Question 4: How do Ridge and Lasso differ?

Ridge regression shrinks coefficients toward zero using an (L_2) penalty but usually retains every predictor. Lasso regression uses an (L_1) penalty and can reduce some coefficients exactly to zero.

11.5 Question 5: Which metrics should guide model selection?

RMSE is the primary metric because it is expressed in revenue units and penalizes large errors. MAE provides an intuitive average absolute error, while (R^2) describes how much variation the model explains.

12 Overall Reflection

The exercise demonstrates that model quality depends on more than selecting an algorithm. A professional workflow requires a customer-oriented objective, realistic feature selection, separation of training and testing data, reproducible preprocessing, cross-validation, model comparison, and careful interpretation.

The decision to exclude units_sold is especially important. Including it would likely improve prediction metrics, but the model would be less useful for planning if units sold are unknown when the prediction is needed.

13 Conclusion

The report compares linear, Ridge, and Lasso regression using a shared recipe and 10-fold cross-validation. The model with the lowest cross-validated RMSE is evaluated on a held-out test set. Feature importance then helps translate the predictive model into information a retail decision-maker can understand.

14 Appendix

14.1 Appendix A: GitHub repository

Replace the link after creating the repository:

View the GitHub repository

14.2 Appendix B: Published HTML report

Replace the link after enabling GitHub Pages:

Open the published report

14.3 Appendix C: Session information

Code
sessionInfo()
R version 4.5.1 (2025-06-13)
Platform: aarch64-apple-darwin20
Running under: macOS Tahoe 26.5

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: America/Los_Angeles
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] kableExtra_1.4.0   knitr_1.51         vip_0.4.1          glmnet_5.0        
 [5] Matrix_1.7-4       janitor_2.2.1      yardstick_1.3.2    workflowsets_1.1.1
 [9] workflows_1.3.0    tune_2.0.1         tailor_0.1.0       rsample_1.3.2     
[13] recipes_1.3.1      parsnip_1.4.1      modeldata_1.5.1    infer_1.1.0       
[17] dials_1.4.2        scales_1.4.0       broom_1.0.12       tidymodels_1.4.1  
[21] lubridate_1.9.5    forcats_1.0.1      stringr_1.6.0      dplyr_1.2.0       
[25] purrr_1.2.1        readr_2.1.6        tidyr_1.3.2        tibble_3.3.1      
[29] ggplot2_4.0.2      tidyverse_2.0.0   

loaded via a namespace (and not attached):
 [1] conflicted_1.2.0    rlang_1.3.0         magrittr_2.0.4     
 [4] snakecase_0.11.1    furrr_0.3.1         otel_0.2.0         
 [7] compiler_4.5.1      systemfonts_1.3.1   vctrs_0.7.1        
[10] lhs_1.2.0           crayon_1.5.3        pkgconfig_2.0.3    
[13] shape_1.4.6.1       fastmap_1.2.0       backports_1.5.0    
[16] labeling_0.4.3      rmarkdown_2.30      prodlim_2025.04.28 
[19] tzdb_0.5.0          bit_4.6.0           xfun_0.56          
[22] cachem_1.1.0        jsonlite_2.0.0      parallel_4.5.1     
[25] R6_2.6.1            stringi_1.8.7       RColorBrewer_1.1-3 
[28] parallelly_1.46.1   rpart_4.1.24        Rcpp_1.1.1         
[31] iterators_1.0.14    future.apply_1.20.1 splines_4.5.1      
[34] nnet_7.3-20         timechange_0.4.0    tidyselect_1.2.1   
[37] rstudioapi_0.18.0   yaml_2.3.12         timeDate_4052.112  
[40] codetools_0.2-20    listenv_0.10.0      lattice_0.22-9     
[43] withr_3.0.2         S7_0.2.1            evaluate_1.0.5     
[46] future_1.69.0       survival_3.8-6      xml2_1.5.2         
[49] pillar_1.11.1       foreach_1.5.2       generics_0.1.4     
[52] vroom_1.7.0         hms_1.1.4           globals_0.19.0     
[55] class_7.3-23        glue_1.8.0          tools_4.5.1        
[58] data.table_1.18.2.1 gower_1.0.2         grid_4.5.1         
[61] ipred_0.9-15        cli_3.6.5           DiceDesign_1.10    
[64] textshaping_1.0.4   viridisLite_0.4.3   svglite_2.2.2      
[67] lava_1.8.2          gtable_0.3.6        GPfit_1.0-9        
[70] digest_0.6.39       htmlwidgets_1.6.4   farver_2.1.2       
[73] memoise_2.0.1       htmltools_0.5.9     lifecycle_1.0.5    
[76] hardhat_1.4.2       sparsevctrs_0.3.6   bit64_4.6.0-1      
[79] MASS_7.3-65