Introduction
Last school year, I worked as a barista at a zero consumer waste cafe. Despite being actively committed to environmental sustainability, even this cafe was plagued by a problem affecting food businesses everywhere: food waste. Every time I threw out moldy bagels, I wished that we could know the future and forecast the daily demand.
Large cafe chains and grocery stores can track and manage every single thing that happens in their business, allowing them to reduce food waste and therefore maximise profits. Smaller businesses like local cafes and restaurants don't have the same opportunity. Without a team of data scientists at their disposal, how are they supposed to use tools meant for warehouses?
I built my demand forecast project to meet this need. It predicts future sales by using statistical methods and machine learning on point-of-sale (POS) data, taking advantage of the data that small businesses do have to guide food purchasing and production decisions. As a proof-of-concept (POC), I analysed 5 years of real-world data from a real-life cafe and designed models to accurately predict the underlying patterns in their sales. Because whether it's food or data, why throw it away when we can use it for good?
Working with the data
Collection
For the POC, I gathered data from the same cafe I'd worked at. They agreed to share their data in exchange for any insights I might glean through this project. The data had to be exported in pieces from the POS system, and I later read every CSV file into the same pandas dataframe. I wrote the processing code so that it could also be used with other datasets from the same POS system (Square). The dataset consisted of more than 600,000 sales over a 5-year period.
The cafe was on-campus and opened according to the academic calendar of the University, adding an extra challenge to prediction. There were large gaps in the time series that corresponded to the dates that the University was closed. However, these dates were not consistent year to year because the academic calendar doesn't align with the standard calendar. This means that patterns existed according to both the time of the calendar year and the time of the school year.
I also wanted to see whether reactions and comments on the cafe's Facebook posts were related to sales. I had hoped to scrape this data automatically, but setting that up would have taken too much of my limited time for a feature which might not even be useful. Instead, I went through every post and entered its reaction count and comment count into a spreadsheet. This didn't take as long as you might think because I just typed and scrolled quickly.
I extensively explored the data in order to see what I was dealing with. I graphed the sales data and used pivot tables to visualise and rearrange it in different ways. This revealed many fascinating insights about the business, including practices which were most effective and areas where revenue was being lost, which I communicated to the managers. It also helped me understand how to clean the data and approach the modelling.
Identifying items
The dataset contained 230 "unique" items. Of course, many of these items were duplicates, or effectively so. Every time an item was relabelled (including punctuation and capitalisation changes) or re-categorised, it became a "new" item in the dataset. Yearly management turnover made this worse because each manager reorganised and relabelled items while trying to improve the system.
Not all duplicates were easy to determine. If two sandwiches have the same ingredients with only one difference, are they the same sandwich? Does it matter what the ingredient is? What about the time periods during which each sandwich was sold, and whether or not these overlap? How can I determine the ingredients from just the item label anyway? What about the fact that the same item can change over time, without changing labels?
Many of the 230 items were short-term promotional items with nondescript labels that told me nothing about the type of item they were. When even the current managers didn't know what some of the numerous items were, I had to scour the cafe's social media posts to find out... But even that was hard, because promotional items weren't always announced, and certainly not always in the text portion of a post.
Even if I knew that a certain promotional item was a latte, how would I categorise it? If I categorised it as a promotional drink, would that inaccurately lower latte sales? If I categorised it as a latte, would that inaccurately increase latte sales? If that promotional latte didn't exist, how many of those sales would have gone to lattes, and how many would have gone to other items?
Despite the huge number of item labels, in many other cases I didn't have enough labels to tell apart items that should be considered unique. Consider a simplified version of what I called the "soup-salad-mac dilemma".
The cafe sells soup, salad, and mac and cheese. At first, soup and salad are the same price, and mac and cheese is more expensive. Because the cafe only cares about ringing in orders, and not collecting clean data, it creates two labels: "soup/salad" and "mac and cheese".
Later on, soup becomes more expensive, so they can't keep soup and salad in the same button. Soup and mac and cheese are served from the same warming pot, so out of a desire for simplicity, they make both items the same price. Then, they rename the two existing buttons to "soup" and "salad", and ring up mac and cheese as soup.
Is it possible to tell the difference between soup, salad, and mac and cheese? If I group all three items together, am I making assumptions about the relative demands of each item? How do I understand the demands of soup and mac and cheese when I don't know which item was in the pot on any given day?
Even within specific items, there may be valuable information that isn't present in the dataset. Consider bagels.
If the cafe sells 6 different bagels that can each come with 6 different cream cheeses, 4 additional spreads, and 4 different toppings, it might be useful to know the relative demands of each item so they could figure out how much to purchase, and whether any options should be cut from the menu.
Unfortunately, this is only possible if this item data is specified in buttons on the POS system. If all I know is "bagel", there's nothing I can do.
The data cleaning process required me to make a lot of decisions about how to deal with messy, incomplete, and contradictory data.
Sales versus demand
The fact that I only have sales data and not purchase/production data (remember, my project is targeted towards places that lack purchase/production data) means that I miss a lot of context about the data. If I'm using sales as a proxy for demand, how do I know for sure that the number of items sold on a given day is the number that were demanded?
What if there was more demand than the available stock, and the item ran out before demand could be entirely filled? What if an item sells more than it should because people buy it as a second choice after a different item they'd prefer runs out?
You might think that a solution is to consider something "out of stock" once sales become zero... But how do I know if an item is out of stock or if people simply stop purchasing it?
What about items that are out of stock in the morning, but are re-stocked whenever the daily deliveries for those items come in? What if an item is in stock in the morning, goes out of stock, and then becomes re-stocked later in the day?
What if a staff member thinks an item is out of stock, but later a different staff member finds more in the back room? What if a variant of an item (like a popular bagel spread) goes out of stock but is later re-stocked, and the rate of purchase of the main item ("bagel") changes accordingly, but at no point reaches zero?
Over-stocking means that an item remains available throughout the day, which produces a useful demand signal but creates waste. Under-stocking may prevent waste, but if an item sells out early then the sales data will teach the model to predict the artificially low number that happened to be available.
Marking items down for quick sale creates the opposite problem: an over-stocked day can appear to be a day of unusually high demand if the discount isn't represented in the POS data.
Preparation
I consolidated sales by day after processing because sales don't occur at consistent periods of time. Instead of having a continuous time series, I had a series of timestamped events that I needed to discretise. Essentially, I approximated the original series by binning it.
I also consolidated sales by hour for visualisation purposes, but I found that it was too spiky for good prediction results. Considering that cafes generally only purchase items and ingredients on a daily basis, I decided that daily binning made the most sense.
Finally, I standardised the data. In order to keep things simple for the POC, I decided to focus on the one major item which was both frequently sold and frequently wasted: bagels. I assigned the data from the final term of the most recent year to be the test set, and all prior data to be the training set. From there it was pretty straightforward.
Building the forecast
Model research
I spent a lot of time researching different machine learning models for time series analysis problems. My problem is specifically a multivariate time series analysis problem because at each timestamp (day or hour) there are corresponding values for multiple categories (items). I sought to understand what's been done before and the different situations each model is best suited for.
I developed a few baseline models based on the cafe's current practices as well as what a data-minded human would be able to accomplish. I also included naive prediction and linear regression models as baselines in order to check that the more complex models would actually perform significantly better than these simple ones.
In the end, I settled on SARIMAX and XGBoost as models best suited to my problem. I also decided to try Google's AutoML and Facebook's Prophet as out-of-the-box solutions. Prophet was created to deal with what was essentially my problem but on Facebook's scale, so I was optimistic about it. I didn't think that RNNs would be able to hold enough information in their "memory" to perform well, but I also decided to implement an LSTM out of curiosity.
Feature engineering
As a human, I could be highly confident that there would be seasonality throughout the dataset. Therefore, I decided to supplement the dataset with features that would help the models understand this effect. All these features proved helpful, according to my analysis.
Because I could expect that Mondays are probably similar to Mondays and so on, I added a feature for the day of the week. Because people's purchasing patterns are often affected by the weather, I added a feature for the average temperature that day.
Again, the particular dataset I was looking at was from a cafe that was open 13 weeks twice a year according to the academic calendar, so I added two more features to deal with this additional effect. These were a feature for the week of the year, starting with the first week of school each year instead of the 1st of January, and a feature for whether it was Fall or Winter term.
I added the reaction and comment counts as features, and they were highly correlated with increased sales. But what did that correlation mean?
Did the posts draw more people to the cafe? Did they advertise discounts, specials, or new items that independently increased demand? Were they simply topical on days which would have been busy anyway, such as a post wishing students good luck on their exams? Or was it some combination of all three?
There was also a problem with timing. I could only see the final reaction and comment counts for each post, not how many accumulated each day. Someone might engage with a post, or discover it through someone else's engagement, days later. They might not even visit the cafe on the same day that they see the post.
Most of the effect would probably occur within a day or two, but I couldn't completely derive the daily time series after the fact. Correlation alone couldn't distinguish between these explanations, but the data was highly predictive so I used it anyway.
Model evaluation
I calculated the root-mean-square error (RMSE) for every model and graphed each model's predictions against the ground truth (observed sales). It was a good idea to graph the results because a few models that had low RMSEs were actually wildly inaccurate.
This is because the RMSE summarises the magnitude of the differences from the ground truth in a single number, but what I really wanted was a model that followed the shape of the ground truth closely. I wanted a model that would predict sudden increases and decreases even if it happened to be off by a day or two, rather than a model that "played it safe". I also considered two error measures of my own: food waste (overprediction) and lost sales (underprediction).
Ultimately, Prophet was the superior model. It managed to predict the true values very closely and quickly picked up on upswings and downswings in sales. The AutoML model (I don't know the type of model that was chosen because Google hides that information) was also quite good. It overpredicted less than Prophet, but it had a significant underprediction problem.
Considering that food does not immediately go bad at the end of a single day, and therefore minor overprediction will not necessarily result in food waste, I decided that Prophet had better overall performance. Additionally, Prophet is free and open-source, while AutoML is paid and opaque. However, I recognise that my results could be specific to the train/test data that was used, and a more thorough analysis would have to look at the results for all item types well into the future.
The LSTM was by far the worst-performing model, simply outputting the average value every single day — a truly flat line. SARIMAX performed almost as badly, predicting an only slightly wavering line. Both models were effectively ignoring the data because they were underfitting; they had high bias and low variance.
This leads me to suspect that I'd need to spend more time tuning the hyperparameters for both models to achieve useful results. However, if my eventual goal for this project is to build something that can work out-of-the-box for any cleaned dataset, I'll probably stick with Prophet for now.
No matter which model I used, I was predicting sales rather than true demand. This means that every model would learn the cafe's purchasing behaviour along with its customers' behaviour. If bagels regularly ran out at a particular point in the purchasing cycle, the model might accurately predict those lower sales without recognising the unfilled demand behind them.
One possible way to estimate true demand would be to model the typical shape of sales throughout the day instead of treating the observed daily total as ground truth. Even if sales ended early on some training days because an item sold out, the model could learn the uninterrupted shape from the remaining days instead of treating every low daily total as low demand.
For a future day, the model could predict the complete curve, then integrate it to estimate the total demand. This approach would still need enough days without stock-outs to learn the underlying shape, but it could be more resilient to occasional interruptions in the training data.
Once a business changed its purchasing behaviour in response to the forecasts, the data distribution would change too. The model would adjust as new data accumulated, but its early predictions would still reflect the old purchasing constraints. I would need to account for this transition rather than treating historical sales as fixed ground truth.
From project to product
When I discussed this project at events, I received unexpected interest from small business owners, people who knew small business owners, and venture capitalists. The venture capitalists often called it a startup. However, I don't think it gets to be called a startup unless I seriously pursue it as a business, and I don't plan to do so at this time.
Still, what would it take to turn the project into a product?
The business case
I'm interested in the intersection between "AI for good" and building something financially viable. In this case, the two goals align because companies don't want to waste either. For food businesses, over-purchasing creates food waste while under-purchasing creates lost sales. More accurate purchasing decisions should therefore help both the environment and businesses' margins.
Unfortunately, the businesses with the greatest need may be the hardest ones to serve. Every store has its own menu, suppliers, purchasing schedule, and operational quirks. Larger businesses have much better inventory data, but they may already have prediction capabilities of their own. They may also have a much more complicated supply chain between prediction and purchase order.
Improving the inputs
A real product couldn't ask businesses to repeatedly export CSV files from Square. It would need to pull data directly from multiple POS systems and, where possible, inventory or enterprise resource planning (ERP) systems. It would also need to automatically gather weather and social media data. Since most businesses don't follow an academic calendar, they would need a way to mark their own notable dates.
However, automating data collection would not fix the underlying data quality problems. After-the-fact cleaning can only do so much when multiple products share one button, variants aren't recorded, or labels change over time. The product would have to help each business reorganise its POS system to properly capture the necessary detail. Then it would need to collect enough clean data before it could start making useful predictions.
This process wouldn't necessarily make the POS more difficult to use. I found opportunities to make the cafe's system easier to navigate and more useful for analytics. For example, it was already disorienting to search for a non-drink item only to discover that it had been placed in the drinks category.
Still, this kind of onboarding and implementation work would be high-touch and expensive, which complicates the business case for selling to small businesses. In future, sufficiently capable language models might be able to inspect the existing labels, recommend a better arrangement, and apply the changes through the POS system's API. This would make the up-front onboarding much easier to scale.
Scaling the predictions
The POC predicted bagel sales, but a useful product would eventually need to predict demand for every purchased ingredient. The cafe didn't just need to know how many bagels to buy. It needed to know which specific types of bagels, spreads (including cream cheeses), and toppings to buy.
Breaking menu items into their ingredients creates an explosion of possible combinations. The effects aren't independent either: people might like a certain cream cheese on one type of bagel but not another. Training a separate time series model for every ingredient also creates an obvious scaling problem.
I became interested in tabular deep learning as a possible way to learn across many products at once. I experimented with a tabular model, but the model I could train with my free Colab account didn't perform well. However, I think there's a lot of promise in this approach, and I would be interested in pursuing it as a longer-term bet if I ever turned this into a startup.
Platform risk
Square itself may be better positioned to build this product. It already has direct access to the data, a relationship with the businesses using it, and the ability to integrate forecasting without any ingestion lag. If an independent product became successful, Square could either build the same thing or acquire the company behind it. Personally, if I start a company, I want to have ambitions of carrying it forward indefinitely rather than building it to sell to a larger company.
Conclusion
I expected to spend most of this project actually building machine learning models. Instead, I spent most of my time researching approaches, extracting data, and cleaning up all the weird labels. I hadn't expected to perform so much "data janitor" duty, but it was actually pretty fun.
The model showed that I could produce useful demand forecasts from the imperfect sales data a small business already had. More importantly, the project showed me how much work surrounds the model. A good prediction isn't useful unless the underlying data represents reality and the result can be translated into an actual purchasing decision.