Project Python Foundations: FoodHub Data Analysis¶

Context¶

The number of restaurants in New York is increasing day by day. Lots of students and busy professionals rely on those restaurants due to their hectic lifestyles. Online food delivery service is a great option for them. It provides them with good food from their favorite restaurants. A food aggregator company FoodHub offers access to multiple restaurants through a single smartphone app.

The app allows the restaurants to receive a direct online order from a customer. The app assigns a delivery person from the company to pick up the order after it is confirmed by the restaurant. The delivery person then uses the map to reach the restaurant and waits for the food package. Once the food package is handed over to the delivery person, he/she confirms the pick-up in the app and travels to the customer's location to deliver the food. The delivery person confirms the drop-off in the app after delivering the food package to the customer. The customer can rate the order in the app. The food aggregator earns money by collecting a fixed margin of the delivery order from the restaurants.

Objective¶

The food aggregator company has stored the data of the different orders made by the registered customers in their online portal. They want to analyze the data to get a fair idea about the demand of different restaurants which will help them in enhancing their customer experience. Suppose you are hired as a Data Scientist in this company and the Data Science team has shared some of the key questions that need to be answered. Perform the data analysis to find answers to these questions that will help the company to improve the business.

Data Description¶

The data contains the different data related to a food order. The detailed data dictionary is given below.

Data Dictionary¶

  • order_id: Unique ID of the order
  • customer_id: ID of the customer who ordered the food
  • restaurant_name: Name of the restaurant
  • cuisine_type: Cuisine ordered by the customer
  • cost_of_the_order: Cost of the order
  • day_of_the_week: Indicates whether the order is placed on a weekday or weekend (The weekday is from Monday to Friday and the weekend is Saturday and Sunday)
  • rating: Rating given by the customer out of 5
  • food_preparation_time: Time (in minutes) taken by the restaurant to prepare the food. This is calculated by taking the difference between the timestamps of the restaurant's order confirmation and the delivery person's pick-up confirmation.
  • delivery_time: Time (in minutes) taken by the delivery person to deliver the food package. This is calculated by taking the difference between the timestamps of the delivery person's pick-up confirmation and drop-off information

Let us start by importing the required libraries¶

In [133]:
# Installing the libraries with the specified version.
%pip install numpy==2.0.2 pandas==2.2.2 matplotlib==3.10.0 seaborn==0.13.2 -q --user
[notice] A new release of pip is available: 25.3 -> 26.0.1
[notice] To update, run: pip install --upgrade pip
Note: you may need to restart the kernel to use updated packages.

Note:

  • After running the above cell, kindly restart the runtime (for Google Colab) or notebook kernel (for Jupyter Notebook), and run all cells sequentially from the next cell.
  • On executing the above line of code, you might see a warning regarding package dependencies. This error message can be ignored as the above code ensures that all necessary libraries and their dependencies are maintained to successfully execute the code in this notebook.
In [134]:
# import libraries for data manipulation
import numpy as np
import pandas as pd

# import libraries for data visualization
import matplotlib.pyplot as plt
import seaborn as sns

Understanding the structure of the data¶

In [135]:
# uncomment and run the below code snippets if the dataset is present in the Google Drive
# from google.colab import drive
# drive.mount('/content/drive')
In [136]:
# Write your code here to read the data
df = pd.read_csv('./foodhub_order.csv')
In [137]:
# Write your code here to view the first 5 rows
df.head()
Out[137]:
order_id customer_id restaurant_name cuisine_type cost_of_the_order day_of_the_week rating food_preparation_time delivery_time
0 1477147 337525 Hangawi Korean 30.75 Weekend Not given 25 20
1 1477685 358141 Blue Ribbon Sushi Izakaya Japanese 12.08 Weekend Not given 25 23
2 1477070 66393 Cafe Habana Mexican 12.23 Weekday 5 23 28
3 1477334 106968 Blue Ribbon Fried Chicken American 29.20 Weekend 3 25 15
4 1478249 76942 Dirty Bird to Go American 11.59 Weekday 4 25 24

Question 1: How many rows and columns are present in the data? [0.5 mark]¶

In [138]:
# Write your code here
df.shape
Out[138]:
(1898, 9)

Observations:¶

The data contains 1898 rows (orders), with 9 columns of feature information.

Question 2: What are the datatypes of the different columns in the dataset? (The info() function can be used) [0.5 mark]¶

In [139]:
# Write your code here
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1898 entries, 0 to 1897
Data columns (total 9 columns):
 #   Column                 Non-Null Count  Dtype  
---  ------                 --------------  -----  
 0   order_id               1898 non-null   int64  
 1   customer_id            1898 non-null   int64  
 2   restaurant_name        1898 non-null   object 
 3   cuisine_type           1898 non-null   object 
 4   cost_of_the_order      1898 non-null   float64
 5   day_of_the_week        1898 non-null   object 
 6   rating                 1898 non-null   object 
 7   food_preparation_time  1898 non-null   int64  
 8   delivery_time          1898 non-null   int64  
dtypes: float64(1), int64(4), object(4)
memory usage: 133.6+ KB

Observations:¶

The returned datatypes seem appropriate to the columns in the data dictionary.

Question 3: Are there any missing values in the data? If yes, treat them using an appropriate method. [1 mark]¶

In [140]:
# Write your code here
df.isnull().sum()
Out[140]:
order_id                 0
customer_id              0
restaurant_name          0
cuisine_type             0
cost_of_the_order        0
day_of_the_week          0
rating                   0
food_preparation_time    0
delivery_time            0
dtype: int64

Observations:¶

There are no cells with missing data.

Question 4: Check the statistical summary of the data. What is the minimum, average, and maximum time it takes for food to be prepared once an order is placed? [2 marks]¶

In [141]:
# Statistical summary of the data
df.describe()
Out[141]:
order_id customer_id cost_of_the_order food_preparation_time delivery_time
count 1.898000e+03 1898.000000 1898.000000 1898.000000 1898.000000
mean 1.477496e+06 171168.478398 16.498851 27.371970 24.161749
std 5.480497e+02 113698.139743 7.483812 4.632481 4.972637
min 1.476547e+06 1311.000000 4.470000 20.000000 15.000000
25% 1.477021e+06 77787.750000 12.080000 23.000000 20.000000
50% 1.477496e+06 128600.000000 14.140000 27.000000 25.000000
75% 1.477970e+06 270525.000000 22.297500 31.000000 28.000000
max 1.478444e+06 405334.000000 35.410000 35.000000 33.000000

Observations:¶

Based on the food_preparation_time column in the dataset:

Statistic Time Minimum 20 minutes Average 27.37 minutes Maximum 35 minutes So once an order is placed, food preparation takes between 20 and 35 minutes, with an average of approximately 27 minutes.

Question 5: How many orders are not rated? [1 mark]¶

In [142]:
# Write the code here
df[df['rating'] == 'Not given'].shape[0]
Out[142]:
736

Observations:¶

738 of 1898 records have a rating or 'Not given'. This is approximately 38.88% of orders.

Exploratory Data Analysis (EDA)¶

Univariate Analysis¶

Question 6: Explore all the variables and provide observations on their distributions. (Generally, histograms, boxplots, countplots, etc. are used for univariate exploration.) [9 marks]¶

Order ID¶

In [143]:
df['order_id'].nunique()
Out[143]:
1898

Customer ID¶

In [144]:
df['order_id'].nunique()
Out[144]:
1898
Observations:¶

Of 1898 orders, all 1898 are likewise, a unique customer id.

Restaurant Name¶

In [145]:
df['restaurant_name'].nunique()
Out[145]:
178

Observations¶

Data comes from 178 unique restaurants.

Cuisine Type¶

In [146]:
print('Unique cuisine types:', df['cuisine_type'].nunique())
plt.figure(figsize = (15,5))
sns.countplot(data = df, x = 'cuisine_type')
Unique cuisine types: 14
Out[146]:
<Axes: xlabel='cuisine_type', ylabel='count'>
No description has been provided for this image

Observations¶

The top 3 most popular cuisines are American, Japanese, and Italian.

Cost of the order¶

In [147]:
sns.histplot(data=df,x='cost_of_the_order')
plt.show()
sns.boxplot(data=df,x='cost_of_the_order')
plt.show()
No description has been provided for this image
No description has been provided for this image

Observations¶

The most common price range is in the $10-$15 bucket.
The prices are right-skewed, indicating that the distribution is pulled right by higher-cost orders. Most orders (over 50%) fall in 0-$15.

Day of the week¶

In [148]:
print('Days of the week', df['day_of_the_week'].nunique())
sns.countplot(data = df, x = 'day_of_the_week')
Days of the week 2
Out[148]:
<Axes: xlabel='day_of_the_week', ylabel='count'>
No description has been provided for this image

Observations¶

Weekend orders are nearly 2.5X more than weekday orders.

Rating¶

In [149]:
print('Unique ratings:', df['rating'].nunique())
sns.countplot(data = df, x = 'rating')
Unique ratings: 4
Out[149]:
<Axes: xlabel='rating', ylabel='count'>
No description has been provided for this image

Observations¶

The high percentage of "Not Given" ratings made imputation unwise. No orders received a 1 or 2, suggesting that either high customer satisfaction, or that dissatisfied customers simply don't rate.

Food Preparation Time¶

In [150]:
sns.histplot(data=df,x='food_preparation_time')
plt.show()
sns.boxplot(data=df,x='food_preparation_time')
plt.show()
No description has been provided for this image
No description has been provided for this image

Observations¶

The distribution of food preparation time is relatively uniform. The range is spread narrowly between 20 and 35 minuites.

Delivery Time¶

In [151]:
sns.histplot(data=df,x='delivery_time')
plt.show()
sns.boxplot(data=df,x='delivery_time')
plt.show()
No description has been provided for this image
No description has been provided for this image

Observations:¶

Delivery time is only slightly left skewed, meaning the mean is slightly lower than the median, leaning toward shorter delivery times. The times are across only an 18 min spread showing consistency.

Both food preparation and delivery contribute roughly equally to total order time.

Question 7: Which are the top 5 restaurants in terms of the number of orders received? [1 mark]¶

In [152]:
# Write the code here
df['restaurant_name'].value_counts().head(5)
Out[152]:
restaurant_name
Shake Shack                  219
The Meatball Shop            132
Blue Ribbon Sushi            119
Blue Ribbon Fried Chicken     96
Parm                          68
Name: count, dtype: int64

Observations:¶

The restaurants with the most orders, in order are:
Shake Shack
The Meatball Shop
Blue Ribbon Sushi
Blue Ribbon Fried Chicken

Question 8: Which is the most popular cuisine on weekends? [1 mark]¶

In [153]:
df_weekend = df[df['day_of_the_week'].isin(['Weekend'])]
df_weekend['cuisine_type'].value_counts().head(1)
Out[153]:
cuisine_type
American    415
Name: count, dtype: int64

Observations:¶

The most popular cuisine on weekends is American.

Question 9: What percentage of the orders cost more than 20 dollars? [2 marks]¶

In [154]:
df_greater_than_20 = df[df['cost_of_the_order'] > 20]
print('The number of total orders that cost above 20 dollars is:', df_greater_than_20.shape[0])

percentage = (df_greater_than_20.shape[0] / df.shape[0]) * 100
print('The percentage of total orders that cost above 20 dollars is:', percentage)
The number of total orders that cost above 20 dollars is: 555
The percentage of total orders that cost above 20 dollars is: 29.24130663856691

Observations:¶

Orders that cost above $20 are about 29% of the total orders.

Question 10: What is the mean order delivery time? [1 mark]¶

In [155]:
mean_del_time = df['delivery_time'].mean()
print('The mean delivery time is', round(mean_del_time, 2), 'minutes')
The mean delivery time is 24.16 minutes

Observations:¶

The mean delivery time is 24.16 minutes

Question 11: The company has decided to give 20% discount vouchers to the top 3 most frequent customers. Find the IDs of these customers and the number of orders they placed. [1 mark]¶

In [156]:
df['customer_id'].value_counts().head(3)
Out[156]:
customer_id
52832    13
47440    10
83287     9
Name: count, dtype: int64

Observations:¶

The business will offer 20% discount to customer id's:
52832
47440
83287

Multivariate Analysis¶

Question 12: Perform a multivariate analysis to explore relationships between the important variables in the dataset. (It is a good idea to explore relations between numerical variables as well as relations between numerical and categorical variables) [10 marks]¶

Cuisine vs Cost¶

In [157]:
plt.figure(figsize=(15,7))
sns.boxplot(x = "cuisine_type", y = "cost_of_the_order", data = df, palette = 'deep', hue = "cuisine_type")
plt.xticks(rotation = 60)
plt.show()
No description has been provided for this image

Observations¶

The top 6 cuisines (American, Japanese, Italian, Chinese, Mexican, Indian) all have mean costs clustered around $16–$17, suggesting pricing is fairly consistent across popular cuisine types.

French, Thai, and Southern cuisines are the most expensive

Vietnamese and Korean are the least expensive — with mean costs of $12.88 and $14.00, these cuisines tend to be more budget-friendly options.

Cuisine vs Food Preparation Time¶

In [158]:
plt.figure(figsize=(15,7))
sns.boxplot(x = "cuisine_type", y = "food_preparation_time", data = df, palette = 'deep', hue = "cuisine_type")
plt.xticks(rotation = 60)
plt.show()
No description has been provided for this image

Observations¶

Preparation time is consistent across all cuisines. Cuisine type has little to no impact on prep time.

Days of Week vs Delivery time¶

In [159]:
plt.figure(figsize=(15,7))
sns.boxplot(x = "day_of_the_week", y = "delivery_time", data = df, palette = 'deep', hue = "day_of_the_week")
plt.xticks(rotation = 60)
plt.show()
No description has been provided for this image

Observations¶

Weekend deliveries are significantly faster — mean delivery time on weekends is 22.47 mins compared to 28.34 mins on weekdays, a difference of nearly 6 minutes.

Weekday deliveries are slower and more concentrated at the high end — almost 87% of weekday deliveries take 25+ minutes, while weekend deliveries are more evenly spread across the 15–30 minute range.

Weekend has a wider range — delivery times range from 15–30 mins on weekends vs. 24–33 mins on weekdays. No weekday delivery was completed in under 24 minutes.

Far fewer late deliveries on weekends — only 12.4% of weekend orders take more than 28 minutes, compared to 48.1% on weekdays.

Revenue¶

In [160]:
# Cost of order by restaurant name - summary table
df_cost_by_restaurant = df.groupby('restaurant_name')['cost_of_the_order'].agg(
    Total_Revenue='sum',
    Orders='count',
    Avg_Cost_Per_Order='mean'
).sort_values(by='Total_Revenue', ascending=False)

df_cost_by_restaurant['Total_Revenue'] = df_cost_by_restaurant['Total_Revenue'].round(2)
df_cost_by_restaurant['Avg_Cost_Per_Order'] = df_cost_by_restaurant['Avg_Cost_Per_Order'].round(2)

df_cost_by_restaurant.head(14)
Out[160]:
Total_Revenue Orders Avg_Cost_Per_Order
restaurant_name
Shake Shack 3579.53 219 16.34
The Meatball Shop 2145.21 132 16.25
Blue Ribbon Sushi 1903.95 119 16.00
Blue Ribbon Fried Chicken 1662.29 96 17.32
Parm 1112.76 68 16.36
RedFarm Broadway 965.13 59 16.36
RedFarm Hudson 921.21 55 16.75
TAO 834.50 49 17.03
Han Dynasty 755.29 46 16.42
Blue Ribbon Sushi Bar & Grill 666.62 44 15.15
Rubirosa 660.45 37 17.85
Sushi of Gari 46 640.87 37 17.32
Nobu Next Door 623.67 42 14.85
Five Guys Burgers and Fries 506.47 29 17.46
Observations¶

Revenue is driven by order volume, not price. The top 5 restaurants all have very similar average order costs (~$16–$17), yet Shake Shack earns nearly 3x the revenue of Parm purely because of higher order volume.

Higher-priced restaurants (like TAO at $834 total) don't necessarily earn more overall — their lower order volume offsets any per-order price advantage.

Rating vs Delivery Time¶

In [161]:
plt.figure(figsize=(15, 7))
sns.pointplot(x = 'rating', y = 'delivery_time', data = df)
plt.show()
No description has been provided for this image
Observations¶
  • Delivery time has very little impact on customer ratings. The mean delivery time is consistent across all rating levels
  • Unrated orders have a mean delivery time almost identical to rated orders, suggesting that delivery time does not influence customers to leave a rating
  • Delivery time does not appear to be a strong driver of customer satisfaction. Since ratings 3, 4, and 5 all have nearly the same delivery times, other factors — such as food quality, order accuracy, or cost — likely play a bigger role in determining ratings.

Rating vs Food preparation time¶

In [162]:
plt.figure(figsize=(15,7))
sns.boxplot(x = "rating", y = "food_preparation_time", data = df, palette = 'deep', hue = "rating")
plt.xticks(rotation = 60)
plt.show()
No description has been provided for this image
Observation¶

Food preparation time has virtually no impact on ratings. The mean is nearly identical as well as the medians.

Rating vs Cost of Order¶

In [163]:
plt.figure(figsize=(15,7))
sns.pointplot(x = 'rating', y = 'cost_of_the_order', data = df)
plt.show()
No description has been provided for this image

Observation¶

  • There is a slight positive trend, where higher cost orders tend to receive slighly higher ratings. However, the differences are very small.

  • "Not given" ratings have the lowest average cost ($16.09). Customers who spend less may be slightly less engaged and less likely to leave a rating.

Heatmaps¶

In [164]:
col_list = ['cost_of_the_order', 'food_preparation_time', 'delivery_time']
plt.figure(figsize=(15, 7))
sns.heatmap(df[col_list].corr(), annot=True, vmin=-1, vmax=1, fmt=".2f", cmap="Spectral")
plt.show()
No description has been provided for this image

Observations¶

The heatmap shows that cost, food preparation time, and delivery time operate independently of each other. None of these variables influence or predict the others. This suggests the company's operational processes (cooking and delivery) are not affected by order value, and the two time components (prep and delivery) are driven by separate factors.

Question 13: The company wants to provide a promotional offer in the advertisement of the restaurants. The condition to get the offer is that the restaurants must have a rating count of more than 50 and the average rating should be greater than 4. Find the restaurants fulfilling the criteria to get the promotional offer. [3 marks]¶

In [165]:
# ratings that are not Not given
df_rated = df[df['rating'] != 'Not given'].copy()
# convert rating to int
df_rated['rating'] = df_rated['rating'].astype('int')
#restaurant names with their rating counts
df_rating_count = df_rated.groupby(['restaurant_name'])['rating'].count().sort_values(ascending = False).reset_index()
df_rating_count.head()
Out[165]:
restaurant_name rating
0 Shake Shack 133
1 The Meatball Shop 84
2 Blue Ribbon Sushi 73
3 Blue Ribbon Fried Chicken 64
4 RedFarm Broadway 41
In [166]:
# restaurant names that have rating counts greater than 50
rest_names = df_rating_count[df_rating_count['rating'] > 50]['restaurant_name']
# data of the restaurants that have rating counts greater than 50 and mean rating of the restaurants with rating counts greater than 50
df_data_greater_than_50 = df_rated[df_rated['restaurant_name'].isin(rest_names)].copy()
df_mean_rating = df_data_greater_than_50.groupby('restaurant_name')['rating'].mean().sort_values(ascending=False)

# restaurants with average rating greater than 4
df_avg_rating_greater_than_4 = df_mean_rating[df_mean_rating > 4].sort_values(ascending=False).reset_index()
print(df_avg_rating_greater_than_4)
             restaurant_name    rating
0          The Meatball Shop  4.511905
1  Blue Ribbon Fried Chicken  4.328125
2                Shake Shack  4.278195
3          Blue Ribbon Sushi  4.219178

Question 14: The company charges the restaurant 25% on the orders having cost greater than 20 dollars and 15% on the orders having cost greater than 5 dollars. Find the net revenue generated by the company across all orders. [3 marks]¶

In [167]:
def compute_rev(x):
    if x > 20:
        return x*0.25
    elif x > 5:
        return x*0.15
    else:
        return x*0

df['Revenue'] = df['cost_of_the_order'].apply(compute_rev) ## Write the apprpriate column name to compute the revenue
print('Total Revenue:', df['Revenue'].sum())
Total Revenue: 6166.303

Observations:¶

The company's total reveue for all orders is $6,166.303

Question 15: The company wants to analyze the total time required to deliver the food. What percentage of orders take more than 60 minutes to get delivered from the time the order is placed? (The food has to be prepared and then delivered.) [2 marks]¶

In [168]:
df['total_time'] = df['food_preparation_time'] + df['delivery_time']
df_greater_than_60 = df[df['total_time'] > 60]
print('The number of total orders that took more than 60 minutes is:', df_greater_than_60.shape[0])

percentage = (df_greater_than_60.shape[0]/df.shape[0])*100
print('The percentage of total orders that took more than 60 minutes is:', round(percentage, 2),'%')
The number of total orders that took more than 60 minutes is: 200
The percentage of total orders that took more than 60 minutes is: 10.54 %

Observations:¶

Orders taking more than 60% are a relatively small percentage, 10.54%.

Question 16: The company wants to analyze the delivery time of the orders on weekdays and weekends. How does the mean delivery time vary during weekdays and weekends? [2 marks]¶

In [169]:
print('The mean delivery time on weekdays is around',
      round(df[df['day_of_the_week'] == 'Weekday']['delivery_time'].mean()),
     'minutes')
print('The mean delivery time on weekends is around',
      round(df[df['day_of_the_week'] == 'Weekend']['delivery_time'].mean()),
     'minutes')
The mean delivery time on weekdays is around 28 minutes
The mean delivery time on weekends is around 22 minutes

Observations:¶

Delivery time is longer on the weekdays, by 6min. But we don't have enough information to say why.

Conclusion and Recommendations¶

Question 17: What are your conclusions from the analysis? What recommendations would you like to share to help improve the business? (You can use cuisine type and feedback ratings to drive your business recommendations.) [6 marks]¶

Conclusions:¶

  • Weekend dominance: Weekend orders outnumber weekday orders by approximately 2.5x, indicating that weekends are the primary revenue-generating period for FoodHub.

  • American cuisine leads demand: American is the most ordered cuisine overall and on weekends, followed by Japanese and Italian. These three cuisines account for a large share of total order volume.

  • Pricing is budget-friendly: Over 50% of orders fall below $15, and the mean order cost is approximately $16.50. Only 29% of orders exceed $20, suggesting the customer base is price-sensitive.

  • Top restaurants drive revenue concentration: Shake Shack alone generated $3,579 in revenue — significantly more than the next closest restaurant. The top 4 restaurants (Shake Shack, The Meatball Shop, Blue Ribbon Sushi, Blue Ribbon Fried Chicken) also qualified for the promotional offer with rating counts above 50 and average ratings above 4, indicating that high volume and high satisfaction go hand in hand.

  • Delivery is faster on weekends despite higher volume: Mean weekend delivery time is ~22 minutes versus ~28 minutes on weekdays — a 6-minute gap. This may be due to lighter traffic conditions on weekends.

  • Food preparation time is consistent: Prep time ranges narrowly between 20–35 minutes across all cuisine types, with a mean of ~27 minutes. This suggests standardized kitchen workflows regardless of cuisine.

  • High rate of unrated orders: Approximately 38.8% of orders received no rating. Among rated orders, no customer gave a 1 or 2 rating, which could indicate either genuinely high satisfaction or that dissatisfied customers simply choose not to rate.

  • Total delivery time is generally efficient: Only 10.54% of orders exceed 60 minutes total (prep + delivery), showing that the vast majority of customers receive food within an hour.

  • Company revenue model favors higher-cost orders: With a 25% margin on orders above $20 and 15% on orders between $5–$20, FoodHub generated $6,166.30 in total revenue. Increasing the share of orders above $20 would significantly boost margins.

Recommendations:¶

  • Scale weekend operations: Ensure more delivery personnel and restaurant readiness on weekends to handle the higher volume and maintain the already-strong delivery times.

  • Improve weekday delivery efficiency: Investigate ways to reduce the 28-minute average weekday delivery time — such as optimizing delivery routes during peak traffic hours, pre-positioning drivers in high-demand areas, or allocating more delivery personnel on weekdays.

  • Increase the rating response rate: With ~39% of orders unrated, introduce post-delivery nudges such as in-app prompts, small incentives (discount codes or loyalty points), or simplified one-tap rating to encourage more feedback. A higher response rate will give a more accurate picture of customer satisfaction.

  • Promote high-performing restaurants: The four restaurants qualifying for the promotional offer (Shake Shack, The Meatball Shop, Blue Ribbon Sushi, Blue Ribbon Fried Chicken) should be featured prominently in the app and marketing campaigns.

  • Drive higher average order value: Since the company earns a higher margin (25% vs. 15%) on orders above $20 — and only 29% currently exceed that threshold — introduce combo deals, bundle offers, or minimum-order promotions to encourage customers to spend more per order.

  • Expand cuisine variety in underserved categories: Vietnamese and Korean cuisines offer lower average costs, which may appeal to budget-conscious customers. Partnering with more restaurants in these categories could attract new customer segments.

  • Invest in loyalty programs for top customers: Expand the 20% discount voucher program into a broader loyalty or rewards tier system to increase repeat orders across a wider customer base.

  • Investigate the 60+ minute orders: While only 10.54% of orders exceed 60 minutes, these outliers likely drive the most dissatisfaction. Analyze whether the bottleneck is food preparation or delivery for these cases and target improvements accordingly.