Most Shopify owners watch one number: total revenue. Average Order Value (AOV) tells you something different: whether revenue comes from many small orders or fewer large ones, and whether a bundle, upsell or free-shipping offer changed what people spend per order. This page shows how to read AOV, how the MCP Analytics AOV report is laid out, and a worked example you can run yourself.
You have two routes. You can follow the worked example below in Python on your own export. Or you can upload your Shopify orders CSV to MCP Analytics: tested statistical methods run on it, and the same data gives the same numbers every time. Start with how to analyze a Shopify orders export.
What Is Average Order Value?
Average Order Value is the mean amount a customer spends per order: total revenue divided by the number of orders. For example, $50,000 from 1,000 orders is an AOV of $50,000 / 1,000 = $50.
The single number hides a lot. AOV is a mean (an average that adds everything up and divides). One very large order can pull it up. That is why you should also look at the median (the middle order when all orders are sorted) and at how order values are spread out. The worked example below shows one bulk order moving AOV by more than a dollar.
AOV separates traffic from spend per order. If you buy more ads and get more orders, but each order is smaller, revenue per order falls. If order count is flat and AOV rises, you are getting more from the same traffic. AOV alone cannot tell you why it moved. To link a change to a cause you need a comparison, such as before and after a change, with a check for chance.
When to Use AOV Analysis
- After a pricing or merchandising change. Example: you launched a bundle of three candles. Did order values move after launch, and is the move bigger than normal month-to-month noise?
- To see your seasonal pattern. Many stores sell differently in holiday months. Your own history is the only reliable guide for your store, so look at AOV by month across at least one full year.
- To compare discounted and full-price orders. Do orders with a discount code spend more or less per order?
- To set a realistic target. If your monthly AOV moves around by a few dollars on its own, a target inside that range will not tell you anything. Use the month-to-month spread to judge what counts as a real change.
What Data Do You Need?
You need a Shopify orders export. In Shopify admin, open Orders, filter to the date range you want, click Export, and choose a CSV file. Shopify's help page Exporting orders lists every column in the file.
One detail matters for AOV. Shopify's page says orders with several line items show the extra items on separate lines, with many fields left blank on those lines. So you must roll the file up to one row per order before you average, or multi-item orders are counted more than once.
The MCP Analytics AOV report maps these seven Shopify columns (names as they appear in the export):
| Shopify column | What it is used for |
|---|---|
Name | The order number. Used to group line items into one order. |
Total | The total cost of the order. This is what AOV averages. |
Subtotal | Order value before shipping and tax. |
Discount Amount | Discount applied. Splits orders into discounted and not discounted. |
Created at | When the order was placed. Used for day of week. |
Lineitem quantity | Units on each line. Summed to items per order. |
Lineitem price | Price of each line item. |
As a rule of thumb, use a few hundred orders over several months. With only a handful of orders per month, monthly averages jump around and small changes cannot be told apart from noise. The worked example shows how to measure that noise instead of guessing.
How to Read the Report
The stored sample of the MCP Analytics AOV report has seven sections, in this order. Here is what each one shows.
Analysis Overview
The question, the company name and the one setting, confidence_level (0.95 in the sample). Check that the question is the one you asked.
Data Preprocessing
How each of the seven columns was mapped, how many rows came in and how many were kept after rows with a missing or zero total were dropped. These are rows from your file, counted before line items are rolled up into orders, so they can be higher than your order count. The order count is in the Executive Summary.
Executive Summary
A short written summary of the patterns below, with the key numbers (AOV, median, total orders, revenue, discount use, items per order), for people who will not read the charts. You still decide what to do with it.
Order Value Distribution
A histogram (a bar chart of how many orders fall in each price range). Order values are often right-skewed: many orders at lower values and a long tail of large ones. When the tail is long, the mean sits above the median. Look at where orders bunch up: a free-shipping or gift threshold just above a big bunch reaches the most customers.
AOV by Day of Week
The mean order value for each weekday. Treat small gaps between days with caution unless each day has many orders.
Discount Impact on AOV
Mean order value for orders with and without a discount. This is a comparison, not a test of cause: shoppers who use codes may differ from those who do not.
Order Size Distribution
How many orders have 1, 2, 3 or more items, and the average value at each size. It shows whether bigger baskets are worth proportionally more or are mostly cheap add-ons.
Worked Example: Did a Free-Shipping Offer Raise AOV?
This example uses a synthetic (made-up) store so anyone can re-run it and get the same numbers. The script draws six months of orders (January to June 2026) with a fixed random seed of 42. Items per order and item prices are random. From 1 April a free-shipping-over-$75 offer starts: in April to June, half of the orders that land between $60 and $75 get one extra item worth $15 to $30. One bulk order of $1,450 is added in May. Every number below is copied from the printed output.
import numpy as np, pandas as pd
from scipy import stats
rng = np.random.default_rng(42)
# Synthetic store: 6 months of orders (Jan-Jun 2026). A free-shipping-over-$75
# offer starts 1 April. Items per order and item prices are drawn at random.
rows = []
oid = 1000
for month in range(1, 7):
n = rng.integers(140, 181)
for _ in range(n):
day = rng.integers(1, 29)
items = rng.choice([1, 2, 3, 4], p=[0.55, 0.28, 0.12, 0.05])
total = rng.lognormal(mean=3.4, sigma=0.45, size=items).sum()
if month >= 4 and 60 <= total < 75 and rng.random() < 0.5:
total += rng.uniform(15, 30); items += 1 # some buyers add an item to reach $75
rows.append((f"#{oid}", f"2026-{month:02d}-{day:02d}", round(total, 2), int(items)))
oid += 1
# one bulk outlier order
rows.append((f"#{oid}", "2026-05-14", 1450.00, 30))
df = pd.DataFrame(rows, columns=["Name", "Created at", "Total", "Items"])
df.to_csv("synthetic_shopify_orders.csv", index=False)
df["Created at"] = pd.to_datetime(df["Created at"])
print("orders:", len(df), " overall AOV (mean):", round(df.Total.mean(), 2), " median:", round(df.Total.median(), 2))
q1, q3 = df.Total.quantile([.25, .75]); iqr = q3 - q1; hi = q3 + 3 * iqr
out = df[df.Total > hi]
print(f"Q1={q1:.2f} Q3={q3:.2f} IQR fence (Q3 + 3*IQR): {hi:.2f} orders above fence: {len(out)} largest: {df.Total.max():.2f}")
clean = df[df.Total <= hi].copy()
print("AOV after removing orders above fence:", round(clean.Total.mean(), 2))
m = clean.groupby(clean["Created at"].dt.month).agg(orders=("Total", "size"), aov=("Total", "mean"), median=("Total", "median"))
print(m.round(2).to_string())
before = clean[clean["Created at"].dt.month <= 3].Total.values
after = clean[clean["Created at"].dt.month >= 4].Total.values
diff = after.mean() - before.mean()
boot = [rng.choice(after, after.size).mean() - rng.choice(before, before.size).mean() for _ in range(5000)]
lo, hi95 = np.percentile(boot, [2.5, 97.5])
t = stats.ttest_ind(after, before, equal_var=False)
print(f"before n={before.size} AOV={before.mean():.2f}; after n={after.size} AOV={after.mean():.2f}; diff={diff:.2f}")
print(f"bootstrap 95% interval for diff: {lo:.2f} to {hi95:.2f}; Welch t={t.statistic:.2f} p={t.pvalue:.4f}")
share = lambda x: np.mean((x >= 60) & (x < 75))
print(f"share of orders $60-$74.99: before {share(before):.3f} after {share(after):.3f}")
print(clean.assign(items=clean.Items.clip(upper=4)).groupby("items").Total.agg(["size", "mean"]).round(2).to_string())
Run with Python 3, numpy 2.3.5, pandas 2.3.3 and scipy 1.16.3: python3 aov_example.py. Output:
orders: 974 overall AOV (mean): 57.74 median: 44.54
Q1=29.55 Q3=77.63 IQR fence (Q3 + 3*IQR): 221.89 orders above fence: 1 largest: 1450.00
AOV after removing orders above fence: 56.31
orders aov median
Created at
1 143 58.61 48.41
2 178 53.97 42.43
3 151 58.34 42.85
4 152 54.55 44.86
5 177 58.01 47.64
6 172 54.84 42.56
before n=472 AOV=56.77; after n=501 AOV=55.87; diff=-0.90
bootstrap 95% interval for diff: -5.39 to 3.73; Welch t=-0.39 p=0.6938
share of orders $60-$74.99: before 0.117 after 0.060
size mean
items
1 521 33.35
2 268 65.44
3 117 96.30
4 67 128.48
What the output says
- One order can move the average. Across 974 orders the mean is $57.74 but the median is $44.54. The $1,450 bulk order is the only one above the outlier fence of $221.89. Removing it drops AOV to $56.31. Check large orders before you trust a monthly AOV.
- Monthly AOV moves on its own. With no change in the store, January to March AOV ran $58.61, $53.97 and $58.34. A swing of $4.64 between months is normal noise here.
- The offer changed where orders land. Orders between $60 and $74.99 fell from 11.7% before the offer to 6.0% after. Shoppers just under $75 added items.
- The offer did not show up in AOV. AOV was $56.77 before (472 orders) and $55.87 after (501 orders), a change of -$0.90. The bootstrap 95% interval for that change runs from -$5.39 to $3.73 and includes zero. Welch's t-test gives p = 0.6938. The extra items reach only a small share of orders, so the lift is too small to separate from the month-to-month noise in this sample.
- Bigger baskets are worth more here. Mean order value was $33.35 for 1 item, $65.44 for 2, $96.30 for 3 and $128.48 for 4 or more. In this synthetic store that is built in, because each order total is the sum of its item prices. In a real store, add-ons or bundle discounts can bend this line.
Two terms used above. A bootstrap interval resamples the orders with replacement thousands of times (5,000 here) and records the difference each time; the middle 95% of those differences is the interval. Welch's t-test compares two means without assuming the two groups have the same spread (see the SciPy documentation for ttest_ind). The outlier fence Q3 + 3 × IQR is the "outer fence" in the NIST/SEMATECH e-Handbook, section 7.1.6; IQR is the interquartile range, Q3 minus Q1.
The lesson: a before-and-after AOV comparison is only as strong as the noise around it. Look at the shape of the distribution near your threshold, not just the average, and report an interval with any change you claim. Before and after is also not a controlled test: anything else that changed in April (season, ads, stock) is mixed into the result.
When to Use Something Else
If you want to group customers by how recently, how often and how much they buy, use RFM segmentation. AOV describes orders; RFM describes customers.
If you want to estimate how demand changes when you change a price, use price elasticity analysis. AOV shows the result of your current prices, not what a new price would do.
If you want to compare spending by region or country, use geographic analysis.
If you need to forecast future revenue, use a time series forecast. AOV analysis looks back; a forecast looks ahead.
The Code Behind the Report
Every MCP Analytics report names its method and includes the R code that produced it, so you or an analyst can check and reproduce each step. The code is fixed, not rewritten for each run, so the same data gives the same numbers. For AOV, line items are first rolled up to one row per order using the order number, then the mean, median, minimum, maximum and standard deviation of order totals are computed, along with the breakdowns by weekday, discount and order size.
Sources
- Shopify Help Center, Exporting orders (export steps, column names, line items on separate rows).
- NIST/SEMATECH e-Handbook of Statistical Methods, 7.1.6 What are outliers in the data? (inner and outer fences).
- SciPy, scipy.stats.ttest_ind (Welch's t-test with
equal_var=False).
Your turn
Bring your own data and the question you actually need answered.
MCP AnalyticsData science by email Send us your data and your question, and we will reply. ds@mcpanalytics.ai