An Introduction to Food Delivery Time Prediction | by Jye Sawtell-Rickson | Towards Data Science

Delivery time prediction has long been a part of city logistics, but refining accuracy has recently become very important for services such as Deliveroo, Foodpanda and Uber Eats which deliver food on-demand.
These services and similar ones must receive an order and have it delivered within ~30 minutes to appease their users. In these situations +/- 5 minutes can make a big difference so it’s very important for customer satisfaction that the initial prediction is highly accurate and that any delays are communicated effectively.
In this article, I’ll discuss my experience building a (real-world) delivery time prediction model for a food delivery startup and how it came to give better predictions than our trained operations team.
This article will touch on the technical topics of machine learning while focusing on the business knowledge required to create a well-functioning predictive model. Links to external resources for further reading will also be included for your reading pleasure.
Our Roadmap:
📝 Problem Statement
🔢 The Data
⚒️ Toolkit
📈 Model Building
🤔 Improving Estimates
To start, let’s formulate a problem statement that we can work with. This will help us to stay focused on the goal.
We’d like to build a model which will take data about a food delivery as input and then output the desired pick-up time to be communicated to drivers.
Some other things to note:
The deliveries are large and can be assumed to require cars.
The suppliers (or suppliers) range from restaurants to caterers and can have very different characteristics.
Drivers can be assumed to arrive at the supplier at exactly the dictated pick-up time. In reality this isn’t always true, but with a sufficient number of drivers this can be kept to a minimal amount.
Food should not be delivered too late as the customer will be left waiting and angry and not too early as it will have to sit out before the customer is ready to eat it.
As an input to our model, we have three categories of data that are typical for this sort of problem:
Orders: what are people ordering? what supplier are they ordering from? what time is the delivery for?
Customers: who is ordering? where are they located?
Delivery Results: for previous deliveries, how did we perform? how long did each step take?
An example of the data available for a delivery is:
Delivery Team    : Deliveries 'R' UsOrder_ID         : 12345Agent_Name       : Bob DriverSupplier         : Delicious DonutsCustomer         : Big Corporation Inc.Pickup_Address   : Shop 1, Busy Street, 23456, Hong KongDelivery_Address : Level 9, Skyscraper, 34567, Hong KongDelivery_Time    : 29/05/2016 08:00:00Order_Size       : $500
Since we have a lot of text, we’ll need to process the data first to get it in a machine-ready format.
The addresses can be processed using regular expressions to get more structured information. The key points are: floor, building, postcode, area, country.
After the processing above, the building, postcode, areas and postcodes can be combined with the other variables to perform one-hot-encoding. One hot encoding will create many binary variables for each possible text. These numeric variables can then be fed into our models. You can read more of an introduction on one-hot-encoding here.
To train the model we will use data from previous deliveries, this includes the predicted time for delivery and the actual time for delivery (the target variable for our exercise). For the purpose of this article, we will work with around 1000 data points.
Before going any deeper, we should consider what models will be the best to solve the type of problem we have. The below diagram from the Scikit-Learn documentation gives a great way to decide which algorithm you should use.
Let’s have a look at what we know about our problem:
As previously mentioned, we have 1000+ data points for training.
The output of the model should be a numeric value.
Due to the one-hot-encoding, we’re left with a fairly large number of binary variables which means it’s likely that many features are important.
With this in mind, we can follow along the chart above to arrive at Ridge Regression and SVR with a Linear Kernel. For the sake of simplicity, we’ll just work with Ridge Regression as well as add a Random Forest Regressor to see how it performs differently with the data.
If you’re interested in some more background, you can read up on the algorithms on their respective pages in the Scikit-Learn documentation: Ridge Regression, SVR (Support Vector Regression) and Random Forest Regression.
We should also pay attention to the metric that we will use to judge our model’s effectiveness.
A common metric that we can use to get a quick idea of our performance is the Mean Absolute Error (MAE). This tells us the average of the difference between our delivery time estimates and the actual delivery time.
Going further, we can define two more domain-specific metrics:
Basically on-time (< 5 mins late)
OK, we’re a little late (< 10 mins late)
These two metrics tell us how often we’ll upset users. If we’re 15 minutes late or 25 minutes late, it doesn’t really matter to the user, we’re just really late. MAE doesn’t realise this, and will count one worse than the other but in terms of the business, it’s effectively the same thing and it’s important our model understands that.
Note: We’ll actually use the inverse of these metrics (i.e. deliveries over 5 mins late) as they are a little easier to work with.
So a quick recap: we know what we need to do, we know what data we have to do it with and we know they way we’re going to do it — so let’s get doing!
Let’s think about what is involved in food delivery. We can break it down into three principal components:
Pick-up the food from the supplier.Drive from the supplier to the customer.Drop-off the food to the customer.
Pick-up the food from the supplier.
Drive from the supplier to the customer.
Drop-off the food to the customer.
We can predict the time taken for each component and then add these together to get the final delivery time.
Delivery Time = Pick-up Time + Point-to-Point Time + Drop-off Time
Alternatively, we could train a model to predict the entire delivery time. The reason we should break this up is to create specialty models which will have superior ability over one generic model. If we replace the models with people it makes sense. In your business development team you would have a person who knows the suppliers really well and how long they might take for delivery. In your client service team you would have someone who knows each client well and can predict how long the delivery might take based on their location and building. We’re structuring our process with this in mind.
Now that we have three stages, let’s dive into them and build our models.
When a driver arrives at a destination, he is required to find a carpark, pick-up the food and make it back to the car. Let’s explore the potential complications that could occur during this process.
No park: For restaurants located in the city centre this can be significantly more difficult compared to those located in more residential areas. To capture this information we can use the postcode or the latitude/longitude information. I found that postcodes were particularly valuable since they capture high-level generic information due to their specific structure.
Difficult access: Restaurants will tend to be fairly easy to access since they rely on that to serve customers, but caterers can be in industrial areas as well as not having an obvious entrance making it more difficult. Similar to the above we can use location information while adding the category of the supplier.
Food not ready: As the order sizes can be quite large, it’s not uncommon for food to be unprepared. To deal with this the caterer can be contacted on the day of the pick-up to check food will be ready on time. The response to these messages would be a useful flag to understand our timing. Whether or not the particular supplier has a history would also be useful input. Finally, the order size is a good indicator; not much can go wrong when preparing a small amount of food but when it’s for 100 pax there’s a lot more potential for something to go wrong.
Something missing from the order: Driver should check order on arrival and in some cases they may find something missing. This means they must wait for the extra food to be prepared. Again, this probably depends on the supplier and how busy they are at the time of pick-up.
For this part of the problem we’ll use the following data points:
Supplier         : Delicious DonutsCustomer         : Big Corporation Inc.Pickup_Address   : Shop 1, Busy Street, 23456, Hong KongDelivery_Time    : 29/05/2016 08:00:00Order_Size       : $500
After performing the processing as described above, the data can then be fed into our Scikit-Learn models resulting in the following:
Baseline Results:Mean Absolute Error: 544 secondsGreater than 5 mins late: 55%Greater than 10 mins late: 19%Random Forest Results:Mean Absolute Error: 575 secondsGreater than 5 mins late: 42%Greater than 10 mins late: 22%Linear Regressor Results:Mean Absolute Error: 550 secondsGreater than 5 mins late: 44%Greater than 10 mins late: 17%Ensemble Results:Mean Absolute Error: 549 secondsGreater than 5 mins late: 45%Greater than 10 mins late: 17%
It can be seen that it if we use the basic metric of Mean Absolute Error (MAE) all of the models perform worse than the baseline model. However, if we look at the metrics which are more relevant to the food delivery business we see that each model has its benefits.
The Random Forest model reduces the orders greater than 5 minutes late by almost 25%. The Linear Regressor does best in reducing the orders which are 10 minutes late, by around 10%. Interestingly, the ensemble model does fairly well in both metrics but notably, it has the smallest MAE out of the three models.
When choosing the correct model, the business goals come first, dictating our choice of important metrics and thus, correct model. With this motivation, we’ll stick with the Ensemble model.
Estimating the travel time between destinations is a tricky task for many reasons; there are thousands of routes to choose from, traffic conditions that are constantly changing, road closures and accidents; all of which provide so much unpredictability into any model you could create.
Luckily for us, there are a bunch of people who have thought long and hard about this problem and collect millions of data points to help better understand the environment. In comes Google.
To predict point-to-point travel time we will call the Google Maps API with the known pick-up and drop-off points. The results returned include parameters taking into account traffic and worse-case-scenarios which we can be used as input to the model. A call to the API is quite simple, it looks like this:
gmaps = googlemaps.Client(key='MY_API_KEY')params = {'mode': 'driving',          'alternatives': False,          'avoid': 'tolls',          'language': 'en',          'units': 'metric',          'region': country}directions_result = gmaps.directions(pick_up_location,                                     drop_off_location,                                     arrival_time=arrival_time,                                     **params)
With this, we can get a highly accurate result for a problem that has already been well thought-through. It’s important not to redo work,
Always think if there is already a good enough solution to your problem, don’t reinvent the wheel!
Previous to implementing this model, our organisation was using the public Google Maps interface to predict the delivery times. As such, the performance of our new model for this part of the journey is identical to the baseline. As such, we won’t calculate any comparisons here.
An important side-note is that addresses aren’t always properly formed! When querying Google Maps there’s always a chance that they’ll respond saying “Uh...I don’t know”, to which we must have an answer. This is a great example of how humans and machines must work together. Whenever an address is not properly formed it’s possible for an operator to step in and clarify the address or even call up the customer to clarify the information.
With that, we can move on the third part of our model.
Once the driver arrives at the location they must find a park, get the food to the right person and get a final sign-off. Potential issues along the way include:
Carpark has height restrictions: It’s simple, but many deliveries happen in vans and in this case, there may be height restrictions stopping drivers from using the main entrance.
Building has no carpark: This is an obvious one. No carpark means that the driver has to dedicate time driving around and looking for somewhere to leave his vehicle while doing the delivery.
Building security issue: Most modern office buildings require a security card to gain access. In the best case, drivers can simply be swiped through, but in the worst case, drivers can be required to go to a separate location, sign-up for a card and then use a back entrance.
Customer on a high floor or in a tricky location: It may sound ridiculous but some office buildings are very difficult to navigate with different lifts for different floors and for smaller companies they can be hidden among a maze of hallways.
With all of these, postal code is a great indicator as it tells us if we can expect a business district or a residential area. We can also deep-dive further on the address. For example, if we have Level 40 it’s quite obvious the delivery is in a big tower block and will likely have a complicated security process.
For this part of the problem we’ll use the following data points.
Customer         : Big Corporation Inc.Delivery_Address : Level 9, Skyscraper, 34567, Hong KongDelivery_Time    : 29/05/2016 08:00:00Order_Size       : $500
After performing the typical processing and running our models we see the following:
Baseline Results:Mean Absolute Error: 351 secondsGreater than 5 mins late: 35%Greater than 10 mins late: 0%Random Forest Results:Mean Absolute Error: 296 secondsGreater than 5 mins late: 15%Greater than 10 mins late: 7%Linear Regressor Results:Mean Absolute Error: 300 secondsGreater than 5 mins late: 14%Greater than 10 mins late: 5%Ensemble Results:Mean Absolute Error: 293 secondsGreater than 5 mins late: 13%Greater than 10 mins late: 6%
Here, we’re able to see a reduction in MAE of 17%, — much better than before. Similarly, we see reductions in the orders greater than 5 mins late by a humungous 63%. On the other hand, the model does increase the number of orders late from 0% to 6%. Again, we’re faced with a trade-off between models which both have their merits and we must let our business KPIs be the deciding factors.
We can now combine our models together with the following formula:
Delivery Time = Pick-up Time + Point-to-Point Time + Drop-off Time                 + Hyperparamater
Note the addition of a hyperparameter this time. We’ll add this in to take into account any other weird effects that we see in the data such as the pick-up time always been set 5 minutes earlier to meet the expectations of drivers. We’ll set this parameter in order to minimise the final error metrics after calculating the combined results.
Putting it all together, we get the final result:
Baseline Results:Mean Absolute Error: 1429 secondsGreater than 5 mins late: 60%Greater than 10 mins late: 19%Ensemble Results:Mean Absolute Error: 1263 secondsGreater than 5 mins late: 41%Greater than 10 mins late: 12%
So our final model has a significant improvement over the baseline model in all metrics — we did it!
Most importantly, we’ve seen a reduction in orders greater than 10 mins late of almost 40%! We were able to achieve this by working on three separate models which were optimised to their own specific business problem. Combining them and fine-tuning at the last stage made sure we produced a model which is on the level of trained professionals.
Having a model which gives even more accuracy would be great, but it would also require a lot more effort due to the law of diminishing returns. Instead, there are other solutions if we think more about the business case and less about machine learning.
As you may recall from the start, this model is for catering delivery, which means we know the delivery time in advance. One simple way to avoid late deliveries is to take whatever value the model outputs and increase it to make sure that we’re early enough.
This is not a great solution because being too early also has a problem in that the food could go bad or be unprepared. However, it does bring up the point that we should consider the distribution of ‘lateness’ and ‘earliness’. Maybe it is better if we’re 10 minutes early than 5 minutes late. With the metrics used up until this point, we didn’t optimise for such scenarios and so we can reconsider this and optimise accordingly.
Another thing to consider is how the model is presented to users. If a user is told you’ll arrive at 2pm and you don’t, they’ll be frustrated. If you tell someone you’ll be there from 1:45pm-2pm then you can aim to deliver at 1:50pm and if you’re a little late you have leeway.
It’s always important to consider machine learning problems as a solution to an actual real-world problem — this context can often lead to ‘improvements’ that would otherwise be impossible from the model itself.
One of the key wins for this model was being able to effectively differentiate between the different preparation times of the suppliers. That tells us that the suppliers must have very different performance.
Digging deeper, it was found that drivers were spending 3x the time at a few suppliers. With this is information, moving back to the business world, it is possible to discuss with the suppliers and identify the problems which can reduce the time spent at these suppliers and by reducing the variation, improve the overall performance of the model.
We started with a problem statement for predicting food delivery timings. We decided what data we would use and what sort of processing we should do. We thought about what would be the best models to solve our particular problem. We then broke our problem down into three components to build targeted models and finally put them together to create a fully-functioning predictive model. We didn’t stop there though, we used our business knowledge to improve the effectiveness of our model.
This resulted in a reduction of 40% in orders that were over 10 minutes late which can be translated directly into dollars saved in refunds. It also helped to lighten the load on the operations team meaning they could focus on more meaningful tasks such as improving supplier efficiencies.
As outlined, this is the general procedure that can be used to apply machine learning to solve business problems. It’s an approach that makes sure to get the basics right before later optimising for the extra few percent points of improvement. It’s one that I’ve used successfully and I hope you can too.
If you enjoyed reading this, please don’t forget to leave a clap or two, or three... 👏👏👏🔄
If you’d like to read about why using high-tech (like in this article) isn’t always the best solution, check this out: