Creating a Hotel Revenue Management System from Scratch

Using Python and machine learning to create a foundation for hotels to effectively manage their revenue

edubu2
8 min readMar 26, 2021

By Elliot Wilens, Data Scientist & Revenue Manager

The St. Regis Deer Valley (Park City, Utah)

This is a project that’s been on my mind since the day I started learning Python*. I’ve spent quite a bit of time throughout my career using hotel Revenue Management systems, and was always curious about how they worked. So I built one.

It all started with a list of reservations. It’s not easy finding a publicly available hotel data set— I’d almost given up on the idea for something else to use as my Final Project for Metis Data Science Bootcamp. Then I came across this dataset from ScienceDirect. There’s over 100k reservations in there for two hotels — surely enough to predict cancellations…but would the data be comprehensive enough to reproduce historical room sales and model demand? There was only one way to find out (but the answer is yes)…

*Want to learn Python? Start with Corey Schafer’s YouTube channel

A quick look into Revenue Management

Revenue management is required (or should be, anyways) when a company’s product inventory is perishable. Not quite perishable the way bananas are, though. If a grocery store runs out of bananas, it can just buy more. In the hotel industry, much like entertainment and air travel, there’s a certain number of rooms a hotel can sell on any given night. Once they’re all gone, the hotel can’t sell any more. What I’m getting at, is this: Price Matters.

“Revenue Management is selling the right room, at the right price, to the right customer, at the right time”

A strong revenue management system provides hotels with all the information they need to execute this process successfully. That’s what I set out to build.

The Dataset

As I mentioned earlier, the dataset was found here from ScienceDirect.com. It contained a set of reservations for two anonymized hotels. Hotel 1 is a resort hotel with 187 rooms (capacity was calculated, not provided). Hotel 2 is a city hotel with 226 rooms. Here are the features that were included:

 'IsCanceled',
'LeadTime',
'ArrivalDateYear',
'ArrivalDateMonth',
'ArrivalDateWeekNumber',
'ArrivalDateDayOfMonth',
'StaysInWeekendNights',
'StaysInWeekNights',
'Adults',
'Children',
'Babies',
'Meal',
'Country',
'MarketSegment',
'DistributionChannel',
'IsRepeatedGuest',
'PreviousCancellations', # guest statistic
'PreviousBookingsNotCanceled', # guest statistic
'ReservedRoomType',
'AssignedRoomType',
'BookingChanges',
'DepositType',
'Agent',
'Company',
'DaysInWaitingList',
'CustomerType', # Transient/Transient-Party/Group/Contract
'ADR', # Average Daily Rate (rate paid by guest)
'RequiredCarParkingSpaces',
'TotalOfSpecialRequests',
'ReservationStatus', # IsCanceled/Checkout/No-show
'ReservationStatusDate'

Project Overview

The first step is knowing how many rooms we have left to sell. We call this remaining supply. The (simplified) supply calculation goes like this:

Remaining Supply = Capacity — Rooms Booked + Predicted Cancellations

So, we need to predict cancellations. It’s critical we get this right. Over-predict cancellations, and we end up ‘walking’ our guests, or turning them away from our hotel when they arrive with a reservation. Under-forecast cancellations and we don’t sell out the hotel (thus losing $$).

Once that’s finished, we need to create a transient demand forecast for upcoming hotel dates, using historical data as features. What does that mean? “Transient” is hotel lingo for non-group business (people that book online without special rates). “Demand” is quantified in rooms — how many people will book rooms at the current rate?

Modeling Cancellations

Our target variable, the value we’re trying to predict, is will_cancel. There are two possible options: yes or no. That makes for what’s called a binary classification problem.

Since all of the reservations were anonymized, there was only so much I could do in terms of feature engineering. Besides one-hot encoding my categorical variables (for example, adding an IsCanceled, true/false column), here are the ones I added:

- TotalPreviousBookings (PreviousBookingsNotCanceled + PreviousCancellations)
- LOS (StaysInWeekNights + StaysInWeekEndNights)
- Company Listed (boolean True/False)

Model Selection

Since booking patterns vary significantly by hotel, I trained separate models for each one. I tried logistic regression, ensembling decision trees (Random Forest), and gradient boosting (XGBoost) algorithms to predict cancellations for each hotel. Not surprisingly, XGBoost won out, in terms of cross-validation scoring.

Precision, Recall and F-Beta Scores

The next step of modeling cancellations was determining the right F-beta metric to use. I decided to stick with the standard F-1 scoring, which in theory, perfectly balances precision and recall.

  • Precision = True Positives / All Positive Predictions
  • Recall = True Positives / Actual Positives

In my case, the positive class are cancellations (‘will_cancel’), and the negative class are reservations that will not cancel (‘will_come’).

We optimize for precision (F-0.5 score) when it’s more important for our positive predictions to be correct. In terms of hotel cancellations, optimizing for precision could lead to under-forecasting cancellations and therefore lost revenue for the hotel.

Optimizing for recall (F-2 score) is necessary when it’s critical to capture all true cases, and false positives aren’t such a big deal. In my case, false positives mean walked guests. Walked guests means angry guests that may not come back. It also means I have to buy pizza for the front desk agents that had to relocate the guests.

I want a perfect balance between precision and recall. This way, hopefully, the incorrect predictions will balance out. Thankfully, F-1 scores do this for us.

Cancellation Model Results

The results, in terms of F-1 scores, were as follows:

Hotel 1: 0.78
Hotel 2: 0.82

The accuracy score is 81% (both hotels combined). That means the model made the correct prediction 81% of the time.

Here’s what the predictions look like in practice:

top left: True Negatives | top right: False Positives | bottom left: False Negatives | bottom right: True Negatives

These are pretty good scores, especially considering that group cancellations are also being predicted. In practice, group room forecasts would be done by a human at the group level, since guests from certain groups cancel more than others. External factors, such as a new meeting planner, can drastically change the way groups utilize their rooms. So, somebody in direct contact with the group should handle those (with revenue management’s guidance, of course). Anyways, since I can’t get this information from an anonymized dataset, I moved forward without it.

Transforming Reservations into Hotel Day-Level Statistics

This needs to happen before I could model demand. Our reservations data looks something like this:

Reservations table schema

I needed to count how many rooms have been sold for each upcoming date. Only then could I start adding features that would help predict demand. But how did I do this, using only historical data? The answer is quite simple, actually. Time travel! All I had to do was go back in time to August 1, 2017, and count how many rooms were sold for each day in August, at that point in time.

Say a hotel had only booked the two reservations listed in the table above. The resulting table, after the transformation, would look like this:

On-the-books (OTB) table schema

Modeling Demand

As a reminder, the target variable I’m predicting is the number of transient rooms that will be booked from this point (August 1, 2017), forward, for each upcoming date.

Feature Engineering

Here are the key features I created:

  • Remaining Supply: # of rooms available for sale
  • Rooms OTB (On-the-books): # of rooms already sold
  • Recent Pickup: # of rooms sold in the last 5, 15, and 30 days
  • Pace: This year’s variance to same-time last-year, for all of the above stats. This tells the hotel how it’s trending for each date compared to last year
  • Gap to Last-Year-Actuals: # rooms left to book to reach last year’s final sales

Model Selection

I trained a few different models and stacked them up against each other. The first was simple linear regression, as well as using Lasso and Ridge regularization techniques. I did not have high hopes, as remaining demand didn’t seem to be correlated (linearly, anyways) with any of my features.

Random Forest and XGBoost Regressors did a much better job predicting demand. Random Forest had a slight edge in the cross-validation results, presumably due to the small sample size (XGBoost tends to be a ‘data-hungry’ algorithm).

Demand Model Results

The results of the Random Forest models were as follows:

HOTEL 1 (Resort Hotel, 187 rooms)
R² score: 0.79
MAE (Mean Absolute Error): 2.1 Room Nights
HOTEL 2 (City Hotel, 226 rooms)
R² score: 0.76
MAE (Mean Absolute Error): 3.1 Room Nights

Cool. What the heck does that mean? The R² score for Hotel 1 tells me that the features I engineered explained 79% of the variance in transient demand. And an MAE of 2.1 tells me that for any given night, my prediction is likely to be off by 2.1 rooms on average. Not bad for an anonymous hotel with 187 rooms!

The model did a better job predicting weekday demand than weekends, due to inconsistent demand patterns in the training set. You can see here that there is much higher variance on weekends than week days…

Hotel 1 (Resort Hotel, 187 rooms)
Hotel 2 (City Hotel, 226 rooms)

With more data, these outliers could be smoothed out. However, I only had one year of actual data to use, so any one-off events last year that brought people to the hotel were seen as patterns by the model. Even still, the results were better than I expected.

Rate Recommendations

Everything was going really well up to this point. I had a solid cancellation forecast, replicated the hotel’s actual sales data, time traveled, and made about as good of a demand forecast as I could, given I didn’t even know which country these hotels are in.

The ultimate gold star would have come from making price recommendations based on the remaining supply and transient demand forecasts. Now they could never have been good price recommendations, since I had no contextual information about competitor pricing. Any good market-based pricing model would use that to create several key features.

I estimated daily pricing based on reservations data (not ideal), and used that as a feature to predict demand. I’d hoped that I could update that price to a bunch of different values, use the model to make the demand predictions, and use whichever selling price resulted in the greatest predicted revenue increase.

It didn’t work. The model didn’t have any reason to know that as price increases, demand decreases. For that, I would need…you guessed it. Historical pricing data for my hotels and their competition. So, the model would recommend the highest possible price for every night, and there would be no change in demand (but huge predicted revenue growth).

Conclusion

My system, as-is, can help hotels in several ways:

  • Pricing Insights: hotels can identify days where demand exceeds supply, so they can yield out discounts and/or increase rates to ensure they’re getting the most for each room night.
  • Calculated and Effective Overselling: No more 3% rule (something I’ve heard front desk agents throw around when they’re trying to figure out whether or not they’ll have to walk anyone).
  • Staff Scheduling & Financial Planning: It’s helpful for hotels to know how many people will be in the hotel.

Thanks for reading! If you want more details, check out the project repository on my GitHub.

--

--