Exploring picking strategies in warehouse operations, simulation in R
Ajay Kalyankar
Supply Chain Leader, transforming logistics & operations | D2C | Enterprise business | ML/AI enthusiast
In today’s fast-paced logistics and supply chain environment, warehouse efficiency is critical to meeting customer demands and maintaining profitability. The customer expectations for a wider catalogue for selection, faster deliveries and almost zero error in delivery, are becoming the new normal. One of the key aspects of warehouse management is the picking process, where items are retrieved from storage locations to fulfil customer orders. Picking strategies significantly impact the speed, accuracy, and cost of order fulfillment. In this post, we’ll explore different picking strategies and demonstrate how to simulate these processes using R, specifically leveraging the "tidyverse" and "purrr" libraries.
What’s a picking strategy ?
When a customer places an order online, the order is routed to one of the warehouses or fulfilment centres (FC). The choice of the warehouse could be based on various criteria such as inventory availability, delivery times, cost of processing etc. To fulfil this order, the warehouse adopts various picking strategies which are methodologies used to retrieve items from the warehouse against the customer order. The choice of strategy depends on various factors, including the type of products stored, order volume, and warehouse layout. Here are some common picking strategies:
Order picking:
Batch Picking:
Zone Picking:
Cluster Picking:
Simulating Picking Strategies with R
Simulations can provide valuable insights into how different picking strategies perform in specific warehouse layouts. Let’s take an example of simulating an order picking and batch picking strategy using R.
Assumptions considered for inventory and order profile are as below -
Below code defines the SKU count and warehouse layout
# Load necessary libraries
library(tidyverse)
library(purrr)
library(EnvStats) # For rpareto function
# Define the number of SKUs
total_skus <- 19500
# Define warehouse layout for 18000 SKUs
warehouse_layout <- tibble(
SKU = 1:total_skus,
Aisle = sample(1:100, total_skus, replace = TRUE),
Bin = sample(1:100, total_skus, replace = TRUE)
)
# Define the Pareto distribution for SKUs
top_skus <- ceiling(0.2 * total_skus) # Top 20% SKUs
Orders are created with Pareto distribution so that the total quantity is 6.5L units
# Function to generate orders with an 80/20 Pareto distribution of SKUs
generate_pareto_order <- function(order_size = 325, target_quantity = 650000, num_orders=1) {
# Calculate total quantity per order
#order_size <- 250
#target_quantity <- 550000
#num_orders <- 1
total_order_quantity <- target_quantity / num_orders
# Select SKUs based on Pareto principle
sku_indices <- c(
sample(1:top_skus, size = order_size * 0.8, replace = TRUE),
sample((top_skus + 1):total_skus, size = order_size * 0.2, replace = TRUE)
) %>% sample() # Shuffle the indices
# Generate Pareto-distributed quantities and scale to match total_order_quantity
quantities <- rpareto(325, location = 1, shape = 1.16) %>%
ceiling()
# Scale quantities to sum up to total_order_quantity
scale_factor <- total_order_quantity / sum(quantities)
quantities <- ceiling(quantities * scale_factor)
# Create the order as a tibble
tibble(SKU = warehouse_layout$SKU[sku_indices], Quantity = quantities)
#sample_order <- tibble(SKU = warehouse_layout$SKU[sku_indices], Quantity = quantities)
#sample_order %>% sum(Quantity)
}
# Example order with 250 SKUs using 80/20 Pareto-distributed quantities
order <- generate_pareto_order(order_size = 325)
# Sample order data
print(head(order))
Manhattan distance method is used to calculate distance between two inventory locations. Objective is the minimise distance while calculating optimal pick-path.
# Function to find the nearest location
find_nearest_location <- function(current_location, sku_locations) {
sku_locations %>%
rowwise() %>%
mutate(Distance = abs(Aisle - current_location[1]) + abs(Bin - current_location[2])) %>%
ungroup() %>%
slice_min(Distance) %>%
select(Aisle, Bin) %>%
unlist() %>%
as.numeric()
}
# Function to calculate Manhattan distance
manhattan_distance <- function(loc1, loc2) {
sum(abs(loc1 - loc2))
}
Calculating picking distance
# Function to simulate order picking
simulate_picking <- function(order) {
current_location <- c(Aisle = 1, Bin = 1)
total_distance <- order %>%
mutate(Distance = map2_dbl(SKU, Quantity, ~{
sku_locations <- warehouse_layout %>% filter(SKU == .x)
nearest_location <- find_nearest_location(current_location, sku_locations)
distance <- manhattan_distance(current_location, nearest_location)
current_location <<- nearest_location # Update current location
distance # Multiply distance by the quantity of SKU
})) %>%
summarise(Distance = sum(Distance))%>%
pull(Distance)
total_distance
}
Simulating picking distance for single order
# Simulate picking for the generated order
total_distance <- simulate_picking(order)
total_time <- total_distance * 2 # Assume 2 seconds per unit distance
cat("Total distance traveled:", total_distance, "\n")
Simulating for multiple orders with Order picking strategy
# Function to run simulation for multiple orders
simulate_multiple_orders <- function(num_orders) {
total_order_quantity <- 650000 / num_orders # qty per order
orders <- replicate(num_orders, generate_pareto_order(order_size = 325,target_quantity = total_order_quantity), simplify = FALSE)
distances <- map_dbl(orders, simulate_picking)
distances
}
# Run simulation for 100 orders with 250 SKUs each
simulation_results <- simulate_multiple_orders(120)
# Analyze results
avg_distance <- mean(simulation_results)
total_order_wise_pick_distance<- sum(simulation_results)
avg_time <- avg_distance * 2
cat("Average distance traveled:", avg_distance, "\n")
cat("Total distance traveled in order wise pick scenario:", total_order_wise_pick_distance, "\n")
# Visualization
qplot(simulation_results, geom = "histogram", bins = 30, fill = I("#3d403e"),
xlab = "Total distance traveled", ylab = "Frequency", main = "Distribution of Picking Distances (250 SKUs per Order, 80/20 Pareto)") + theme_minimal()
Simulating with Batch picking strategy
simulate_batch_picking <- function(num_batches,orders_per_batch) {
num_orders <- num_batches * orders_per_batch
total_order_quantity <- 650000 / num_orders # qty per order
batches <- replicate(num_batches, {
orders <- replicate(orders_per_batch, generate_pareto_order(order_size = 325,target_quantity = total_order_quantity), simplify = FALSE)
batch <- bind_rows(orders) %>%
group_by(SKU)%>%
summarise(Quantity = sum(Quantity))
distances <- simulate_picking(batch)
distances
}, simplify = TRUE)
}
simulation_results_batch_pick <- simulate_batch_picking(num_batches = 10,orders_per_batch = 12)
total_distance_batch_pick <- sum(simulation_results_batch_pick)
cat("Total distance travelled in case of batch pick scenario:", total_distance_batch_pick)
final_results <- data.frame(pick_strategy = c('order-wise pick','batch-wise pick'),
distance = c(total_order_wise_pick_distance,total_distance_batch_pick))
final_results %>%
ggplot(aes(distance,pick_strategy, label = distance))+geom_col()+
geom_label()+
xlim(0,2800000)+
scale_x_continuous(labels = scales::comma)+
labs(title = 'Batch wise picking is ~20% more efficient (10 batch and 10 orders / batch)',
x = "Distance", y = NULL)+
theme_minimal()
In this case, batch picking gives ~25% better efficiency for picking, but with additional effort of order wise sorting later, which is usually less effort as compared to picking.
Key Takeaways
Conclusion
Choosing the right picking strategy is crucial for optimizing warehouse operations. By leveraging simulations in R, warehouse managers can make data-driven decisions that enhance efficiency and reduce costs. Whether you’re dealing with a small-scale operation or a large, complex warehouse, understanding and simulating different picking strategies can lead to significant improvements in your order fulfillment process.