Ship Till X
Shipping Orders Based on Cutoff Times and Shipping Days
By Steve Ewing in Business
October 24, 2024
Business Question
Take all the orders for the last fiscal year. For weeks 1-18 there is 5 day a week shipping and for weeks 19-52 there is 6 day a week shipping. Assuming we ship all orders that arrive before a given cutoff that day and all orders that arrive after the cutoff the next business day what percent of the weekly orders fall on each day of the week?
library(tidyverse)
library(arrow)
library(hms)
library(tinytable)
orders <- arrow::read_parquet("./data/orders_recent.parquet") |>
mutate(
date_of_date_purchased = as.Date(date_purchased),
time_of_date_purchased = as_hms(format(date_purchased, "%H:%M:%S"))
) |>
left_join(fiscal_calendar, by = c("date_of_date_purchased" = "date")) |>
filter(fiscal_year == 2024) |>
select(
orders_id,
date_of_date_purchased,
time_of_date_purchased,
fiscal_week = fiscl_week,
day_of_week
)
head(orders)
## # A tibble: 6 × 5
## orders_id date_of_date_purchased time_of_date_purchased fiscal_week
## <int> <date> <time> <dbl>
## 1 18519115 2023-10-01 20:11:14 1
## 2 18519117 2023-10-01 20:13:16 1
## 3 18519119 2023-10-01 20:13:35 1
## 4 18519121 2023-10-01 20:21:15 1
## 5 18519123 2023-10-01 20:21:47 1
## 6 18519125 2023-10-01 20:22:33 1
## # ℹ 1 more variable: day_of_week <ord>
# Define the cutoff times for Weeks 1-18 (Monday to Friday)
cutoff_times_1_18 <- c(
"08:00:00", # Monday (1)
"10:00:00", # Tuesday (2)
"12:00:00", # Wednesday (3)
"14:00:00", # Thursday (4)
"14:00:00" # Friday (5)
)
# Define the cutoff times for Weeks 19-52 (Monday to Saturday)
cutoff_times_19_52 <- c(
"08:00:00", # Monday (1)
"10:00:00", # Tuesday (2)
"12:00:00", # Wednesday (3)
"12:00:00", # Thursday (4)
"14:00:00", # Friday (5)
"14:00:00" # Saturday (6)
)
Munging
First write and apply the function to adjust the shipping day
# Adjust shipping day function with variable cutoff times for each period
adjust_shipping_day <- function(day_of_week, time_of_day, fiscal_week) {
# Determine the shipping days and cutoff times based on fiscal week
if (fiscal_week <= 18) {
shipping_days <- 1:5 # Monday to Friday
cutoff_times <- cutoff_times_1_18
} else {
shipping_days <- 1:6 # Monday to Saturday
cutoff_times <- cutoff_times_19_52
}
# Check if the current day is a shipping day
if (day_of_week %in% shipping_days) {
# Get the cutoff time for the current day
cutoff_time <- as_hms(cutoff_times[which(shipping_days == day_of_week)])
# If placed after the cutoff time, shift to the next day
if (time_of_day > cutoff_time) {
next_day <- day_of_week + 1
} else {
next_day <- day_of_week
}
} else {
# If current day is not a shipping day, start from the next day
next_day <- day_of_week + 1
}
# Wrap around if next_day exceeds 7 (Sunday)
if (next_day > 7) {
next_day <- next_day - 7
}
# Find the next available shipping day
next_shipping_day <- shipping_days[shipping_days >= next_day][1]
# If no shipping day is found, wrap around to the first shipping day
if (is.na(next_shipping_day)) {
next_shipping_day <- shipping_days[1]
}
return(next_shipping_day)
}
# Create a vector of day labels starting from Monday (1) to Sunday (7)
day_labels <- c("Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday")
# Apply the adjusted shipping day function to orders
orders <- orders |>
mutate(
day_of_week = wday(date_of_date_purchased, label = FALSE, week_start = 1),
time_of_date_purchased = as_hms(time_of_date_purchased),
adjusted_day = mapply(
adjust_shipping_day,
day_of_week,
time_of_date_purchased,
fiscal_week
),
adjusted_day_label = day_labels[adjusted_day]
)
Now, we group the orders by the adjusted shipping day and calculate the percentage distribution.
# Calculate distributions for Weeks 1-18
distribution_1_18 <- orders |>
filter(fiscal_week <= 18) |>
mutate(week_set = "Weeks 1-18") |>
group_by(adjusted_day, adjusted_day_label, week_set) |>
summarize(order_count = n(), .groups = "drop") |>
ungroup() |> # Remove grouping to calculate total orders across all days
mutate(
total_orders = sum(order_count),
percentage = round((order_count / total_orders) * 100, 2)
)
# Calculate distributions for Weeks 19-52
distribution_19_52 <- orders |>
filter(fiscal_week >= 19) |>
mutate(week_set = "Weeks 19-52") |>
group_by(adjusted_day, adjusted_day_label, week_set) |>
summarize(order_count = n(), .groups = "drop") |>
ungroup() |> # Remove grouping
mutate(
total_orders = sum(order_count),
percentage = round((order_count / total_orders) * 100, 2)
)
# Combine the distributions
combined_distribution <- bind_rows(distribution_1_18, distribution_19_52) |>
select(!total_orders) |> # Remove the total orders column
select(!order_count) # Remove the order count column)
# Display the final distribution
tt(combined_distribution,
theme = "striped")
adjusted_day | adjusted_day_label | week_set | percentage |
---|---|---|---|
1 | Monday | Weeks 1-18 | 29.72 |
2 | Tuesday | Weeks 1-18 | 18.91 |
3 | Wednesday | Weeks 1-18 | 18.45 |
4 | Thursday | Weeks 1-18 | 17.12 |
5 | Friday | Weeks 1-18 | 15.80 |
1 | Monday | Weeks 19-52 | 19.91 |
2 | Tuesday | Weeks 19-52 | 18.37 |
3 | Wednesday | Weeks 19-52 | 18.22 |
4 | Thursday | Weeks 19-52 | 15.06 |
5 | Friday | Weeks 19-52 | 16.85 |
6 | Saturday | Weeks 19-52 | 11.59 |