How to Use Billing vs Shipping Address Analysis in Squarespace: Step-by-Step Tutorial
Updated October 2026
Introduction to Billing vs Shipping Address Analysis
Understanding the difference between billing and shipping addresses in your Squarespace orders isn't just about logistics. It is a window into customer behavior. When a customer's billing address differs from their shipping address, the reason can be a gift, a business buyer, a customer with more than one home, or a billing address that is simply out of date. The addresses alone cannot say which, but the share of orders that differ is a useful number to track.
Billing vs shipping address variance analysis measures how frequently customers choose to ship products to addresses different from their payment card's billing address. A higher rate is consistent with more gift or business shipping, and a lower one with more orders shipped to the buyer's own address, but confirming either needs evidence beyond the addresses, such as a gift option or message chosen at checkout, or a customer survey. Watching how the rate moves from month to month can show when it rises, which is a prompt to investigate, not a forecast.
This tutorial calculates the share of orders whose shipping address differs from the billing address, step by step with pandas, and shows how to state how precise that share is and how to check the matching rule.
Prerequisites and Data Requirements
- A Squarespace Commerce site with order history and permission to export orders
- Python with pandas, or a spreadsheet if you prefer to build the same comparison there
- Enough orders for the precision you need. There is no universal minimum. The rate is a proportion, and Step 3 shows how wide its interval is for your order count: a few dozen orders give a wide interval, a few thousand a narrow one.
Export Your Squarespace Orders
Export your orders as a CSV by following Squarespace's help page on exporting Commerce orders. Its field list includes billing columns (Billing Name, Billing Address1, Billing Address2, Billing City, Billing Zip, Billing Province, Billing Country, Billing Phone) and the matching shipping columns. The page states that order-wide details, such as the shipping address, appear "only in the first row with that order ID" and that the other rows show line item details only. An order with three items is therefore three rows with the address on one of them, and counting rows instead of orders would overweight large orders.
Step-by-Step Analysis Process
Step 1: Load the Export
The code below builds a small synthetic export in the same layout. It is made up for this tutorial and does not come from any store; every figure in the outputs on this page comes from it. Orders 3001 and 3011 have a second row with blank address fields.
import pandas as pd
# Synthetic orders made up for this tutorial. Column names follow the Squarespace order export;
# the values do not come from any store. Order-wide fields (the addresses) are filled only on the
# first row of each order, as the export does; later rows of the same order carry line item details only.
cols = ["Order ID", "Billing Address1", "Billing City", "Billing Province", "Billing Zip", "Billing Country",
"Shipping Address1", "Shipping City", "Shipping Province", "Shipping Zip", "Shipping Country"]
orders = [
# same address, with spelling differences that normalization should absorb
(3001, "12 Oak Street", "Austin", "TX", "78701", "US", "12 Oak St.", "Austin", "TX", "78701", "US"),
(3002, "40 Pine Avenue", "Denver", "CO", "80202", "US", "40 Pine Ave", "Denver", "CO", "80202", "US"),
(3003, "7 Lake Road", "Boston", "MA", "02108", "US", "7 Lake Rd", "Boston", "MA", "02108", "US"),
(3004, "88 Elm Street", "Seattle", "WA", "98101", "US", "88 Elm Street", "Seattle", "WA", "98101", "US"),
(3005, "5 Cedar Lane", "Miami", "FL", "33101", "US", "5 Cedar Lane", "Miami", "FL", "33101", "US"),
(3006, "301 Main Street", "Chicago", "IL", "60601", "US", "301 Main Street", "Chicago", "IL", "60601", "US"),
(3007, "19 Birch Drive", "Portland", "OR", "97201", "US", "19 Birch Drive", "Portland", "OR", "97201", "US"),
(3008, "250 Hill Street", "Atlanta", "GA", "30301", "US", "250 Hill Street", "Atlanta", "GA", "30301", "US"),
# same city, different street
(3009, "12 Oak Street", "Austin", "TX", "78701", "US", "900 River Road", "Austin", "TX", "78702", "US"),
(3010, "40 Pine Avenue", "Denver", "CO", "80202", "US", "15 Park Avenue", "Denver", "CO", "80203", "US"),
# different city, same country
(3011, "7 Lake Road", "Boston", "MA", "02108", "US", "3 Shore Road", "Providence", "RI", "02903", "US"),
(3012, "88 Elm Street", "Seattle", "WA", "98101", "US", "61 Sun Street", "Phoenix", "AZ", "85001", "US"),
(3013, "5 Cedar Lane", "Miami", "FL", "33101", "US", "22 Bay Lane", "Tampa", "FL", "33602", "US"),
# different country
(3014, "301 Main Street", "Chicago", "IL", "60601", "US", "10 King Street", "Toronto", "ON", "M5H 2N2", "CA"),
# no shipping address captured (for example a digital product)
(3015, "19 Birch Drive", "Portland", "OR", "97201", "US", None, None, None, None, None),
(3016, "250 Hill Street", "Atlanta", "GA", "30301", "US", None, None, None, None, None),
]
rows = pd.DataFrame(orders, columns=cols)
# Second line items of orders 3001 and 3011: every address field is blank, as in the export.
extra = pd.DataFrame({"Order ID": [3001, 3011]})
export = pd.concat([rows, extra], ignore_index=True).sort_values("Order ID", kind="stable").reset_index(drop=True)
print("Rows in the export:", len(export), "| distinct orders:", export["Order ID"].nunique())
print(export[["Order ID", "Billing City", "Shipping City"]].head(5).to_string(index=False))
Output:
Rows in the export: 18 | distinct orders: 16
Order ID Billing City Shipping City
3001 Austin Austin
3001 NaN NaN
3002 Denver Denver
3003 Boston Boston
3004 Seattle Seattle
Step 2: One Row Per Order, Then Compare
Group by Order ID so that each order has one billing and one shipping address. Then standardize spelling (case, punctuation, "Street" against "St") and classify each order from the largest difference to the smallest. Orders with no shipping address, such as a digital product, are set aside rather than counted as "different". Squarespace's province and country values can also come in different spellings in your own file (for example "California" and "CA"); add them to the standardizing step if your data has both.
import re
# One row per order: the address fields live on the first row of each Order ID.
# groupby(...).first() takes the first non-blank value per column, so it also skips the blank rows.
orders = export.groupby("Order ID", as_index=False).first()
ABBREV = {"street": "st", "avenue": "ave", "road": "rd", "drive": "dr", "lane": "ln", "apartment": "apt"}
def norm(value):
if pd.isna(value):
return None
words = re.sub(r"[^\w\s]", " ", str(value).lower()).split()
return " ".join(ABBREV.get(w, w) for w in words)
for part in ["Address1", "City", "Province", "Zip", "Country"]:
orders["b_" + part] = orders["Billing " + part].map(norm)
orders["s_" + part] = orders["Shipping " + part].map(norm)
def classify(r):
if r["s_Address1"] is None or r["b_Address1"] is None:
return "excluded: address missing"
if r["s_Country"] != r["b_Country"]:
return "different country"
if (r["s_City"], r["s_Province"]) != (r["b_City"], r["b_Province"]):
return "different city"
if (r["s_Address1"], r["s_Zip"]) != (r["b_Address1"], r["b_Zip"]):
return "same city, different street"
return "same address"
orders["status"] = orders.apply(classify, axis=1)
print(orders["status"].value_counts().to_string())
Output:
status
same address 8
different city 3
same city, different street 2
excluded: address missing 2
different country 1
The five groups add up to the 16 orders from Step 1. In this tutorial a shipping address "differs" when any compared part differs after standardizing, which includes a different street in the same city. If you want a stricter definition, for example different city only, change the final condition and say which one you used.
Step 3: Calculate the Rate and How Precise It Is
The rate is the orders with a different shipping address divided by the orders that have both addresses. Report the excluded orders next to it, because an exclusion that is not random changes the answer. Compare the excluded orders with the rest (for example by product type) before assuming they do not matter. The code also adds a 95% Wilson interval for the proportion, which shows how far the rate could move with this many orders.
import math
used = orders[~orders["status"].str.startswith("excluded")]
n = len(used)
diff = int((used["status"] != "same address").sum())
rate = diff / n
# Wilson 95% interval for a proportion: how far the rate could move with this many orders.
z = 1.96
centre = (rate + z**2 / (2 * n)) / (1 + z**2 / n)
half = z * math.sqrt(rate * (1 - rate) / n + z**2 / (4 * n**2)) / (1 + z**2 / n)
print("Orders:", len(orders), "| excluded for missing address:", len(orders) - n, "| used:", n)
print("Orders with a different shipping address:", diff, "of", n, f"= {100 * rate:.1f}%")
print(f"95% interval for the rate: {100 * (centre - half):.1f}% to {100 * (centre + half):.1f}%")
Output:
Orders: 16 | excluded for missing address: 2 | used: 14
Orders with a different shipping address: 6 of 14 = 42.9%
95% interval for the rate: 21.4% to 67.4%
With 14 orders the rate is 42.9%, but the interval runs from 21.4% to 67.4%, so these data cannot separate a quarter of orders from two thirds. The interval narrows as the order count rises.
Step 4: Read the Result Carefully
- A mismatch is not a gift. Of the 6 different-address orders in the example, 2 stay in the same city and 1 crosses a border, but nothing in the file says why. To call a group gifts, use a gift option or message captured at checkout, or ask the customers.
- No standard rate exists. What counts as high depends on what you sell and to whom. Compare your own rate across months and product types instead of against an outside number.
- Seasonality: to see whether the rate rises in particular months, group by the order date column in your file (check its name) and show the interval for each month. Months with few orders will be too noisy to read.
Step 5: Check the Matching Rule
The rule can fail in two directions: it can call two spellings of one address "different", or call two different addresses "the same". Checking only the orders it flagged as different measures just the first error. Draw a random sample from each group, the same-address group and the different-address group, open those orders and judge them by eye. Count the misclassified orders in each group separately: a count such as "1 of 20 flagged orders was really the same address, 0 of 20 matched orders were really different" is more useful than one accuracy figure, and a sample of 20 still leaves a wide margin. Fix the standardizing step where you find a pattern (units, PO boxes, abbreviations) and rerun.
Run It on Your Own Squarespace Export
Export your orders from Squarespace as a CSV and upload it at MCP Analytics. The analysis runs a comparison of billing and shipping addresses on your file and shows the result with the method used. You choose the columns; nothing connects to your Squarespace account.
Common Issues and Solutions
Issue 1: The Rate Looks Too High or Too Low
- Count orders, not rows. If you skipped the group-by in Step 2, the blank address rows are counted as missing or as different.
- A very low rate can mean the shipping address is filled in from the billing address at checkout, or that most orders are local pickup or digital. A very high rate can come from spelling differences the standardizing step missed.
- Read 20 to 50 orders from the group that looks wrong before changing anything.
Issue 2: Many Orders Are Excluded for a Missing Address
Report the count next to the rate. Then compare the excluded and the included orders (product type, order value, month). If they differ, the rate describes only the included orders, and the page or report should say so. An exclusion share alone does not tell you whether the remaining orders represent the store.
Issue 3: Mixed Spellings of States or Countries
Add the variants you see in your file to the standardizing step (for example a table that maps full names to codes), then rerun and recheck a sample.
Next Steps with Squarespace Analytics
- Split the rate by product type or order value and show the interval for each group.
- If you want to study gifting, add a checkout question or gift option and compare it with the address result.
- Track the rate monthly and look into months where it moves outside its interval.
Explore more: Squarespace Analytics: all tools, tutorials, and guides →