Product Analytics: Conversion, Segments and Substitutes
A product page in a merchandising tool answers a handful of questions: how often the product is bought when shown, how that compares with similar products, who buys it, what it is bought with, and how demand moves over time. Usually each answer is a separate report on a separate pipeline. In Aito v2 each is one query over the raw event tables, so the page can be built from the current data on request.
The queries below run against the live v2 sandbox, for one
product: Pirkka lactose-free semi-skimmed milk drink (6410405216120).
impressions records each time a product was shown and whether it was bought;
visits records each shopping trip with its purchases and a user link.
Figures in the responses are rounded.
Conversion rate
_aggregate computes statistics over the rows that match the where. On a
Boolean column, $sum counts the true values and $mean is the rate:
{
"from": "impressions",
"where": { "product": "6410405216120" },
"aggregate": ["purchase.$sum", "purchase.$mean"]
}
{ "kind": "aggregate", "data": {
"purchase.$sum": 97, "purchase.$sum.samples": 2195,
"purchase.$mean": 0.0442, "purchase.$mean.standardError": 0.0044 } }
The product was shown 2,195 times and bought 97 times, a conversion rate of
4.4%. Without the where, the same query gives the rate across all products:
5.2%.
In SQL, count the rows:
SELECT count(*) FROM impressions WHERE product = '6410405216120' AND purchase = true
Compare it with its category
Open the products as candidates with get and give each one its own
conversion rate with a per-candidate aggregate:
{
"from": "impressions",
"where": { "product.category": "104" },
"get": "product",
"orderBy": { "$mean": { "$context": "purchase" } },
"select": ["name", "$f", { "conversion": { "$mean": { "$context": "purchase" } } }],
"limit": 5
}
{ "total": 42, "hits": [
{ "name": "Pirkka Finnish nonfat milk 1l", "$f": 2221, "conversion": 0.0504 },
{ "name": "Valio semi-skimmed milk 1l", "$f": 2106, "conversion": 0.0461 },
{ "name": "Pirkka Finnish semi-skimmed milk 1l", "$f": 2236, "conversion": 0.0447 },
{ "name": "Pirkka lactose-free semi-skimmed milk drink 1l", "$f": 2195, "conversion": 0.0442 },
{ "name": "Valio eilaβ’ Lactose-free semi-skimmed milk drink 1l", "$f": 2115, "conversion": 0.0312 } ] }
Category 104 has five products. total still counts every product, because
get opens all values of the link; the ones outside the category have $f
of 0 and sort after these five. Within the milks, the lactose-free drink is
fourth of five.
Who buys it
Relate the buyers' user tags. user.tags is read through the visits.user
link:
{
"from": "visits",
"where": { "purchases": { "$has": "6410405216120" } },
"relate": ["user.tags"],
"select": ["related", "lift", "n"]
}
{ "total": 4, "hits": [
{ "related": { "user.tags": { "$has": "male" } }, "lift": 1.03, "n": 733 },
{ "related": { "user.tags": { "$has": "female" } }, "lift": 0.95, "n": 733 },
{ "related": { "user.tags": { "$has": "club-member" } }, "lift": 0.97, "n": 733 },
{ "related": { "user.tags": { "$has": "young" } }, "lift": 0.99, "n": 733 } ] }
Every lift is close to 1: no segment buys this product markedly more or less
than average. That is a useful answer too. For a product with a segment
signal, such as HK Amerikan bacon (6409100046286), the same query gives
young a lift of 1.32.
SELECT * FROM relate('visits', fields => 'user.tags',
to => 'purchases = ''6410405216120''', k => 5)
What it is bought with, and instead of
Relate the other products in the same baskets:
{
"from": "visits",
"where": { "purchases": { "$has": "6410405216120" } },
"relate": ["purchases"],
"select": ["related", "lift"],
"limit": 5
}
{ "total": 5, "hits": [
{ "related": { "purchases": { "$has": "6410405216120" } }, "lift": 7.41 },
{ "related": { "purchases": { "$has": "6408430000128" } }, "lift": 0.43 },
{ "related": { "purchases": { "$has": "6415600501811" } }, "lift": 0.56 },
{ "related": { "purchases": { "$has": "6411401028373" } }, "lift": 1.35 },
{ "related": { "purchases": { "$has": "6410405082657" } }, "lift": 0.57 } ] }
The first hit is the product itself, which is in every basket that matches
the condition. The next is the interesting one: Valio semi-skimmed milk
(6408430000128) appears in these baskets at 0.43 times its usual rate, and
Pirkka semi-skimmed milk (6410405082657) at 0.57. Shoppers who buy the
lactose-free drink tend not to buy regular milk on the same trip: they are
substitutes. Tyrkisk Peber chocolate (6411401028373, lift 1.35) is bought
with it more often than chance. The hits are not ordered by lift, so sort by
lift on the client if that is the order you want.
Which properties drive purchase
relate with $props asks about specific propositions rather than every
value of a field. Here: across all impressions, do the product's category and
its lactose-free tag raise or lower the chance of a purchase?
{
"from": "impressions",
"where": { "purchase": true },
"relate": { "$props": { "product.category": "104", "product.tags": "lactose-free" } },
"select": ["related", "lift", "n"]
}
Both lifts are below 1 (0.91 for the category, 0.87 for the tag): milk, and lactose-free milk in particular, converts slightly less often than the average product in this shop.
Demand over time
get on a column of the queried table counts rows per value. Here, the
number of visits per week that included the product:
{
"from": "visits",
"where": { "purchases": { "$has": "6410405216120" } },
"get": "week",
"select": ["$value", "$f"],
"limit": 20
}
The ten weeks in the data give 6, 12, 13, 10, 12, 4, 12, 11, 11 and 6 visits.
Week 5 stands out: across all products there were 80 visits that week, a
normal week, but only 4 of them included this product. The SQL form is a
GROUP BY:
SELECT week, count(*) AS visits FROM visits
WHERE purchases = '6410405216120' GROUP BY week ORDER BY week
Why Aito for this
- One query per question, on raw events. Conversion, segments,
substitutes and trends come from
impressionsandvisitsas they are, with no aggregation tables to maintain. - Lift, not only counts.
relatereports how much more or less often something happens than chance, which is what separates a substitute from a merely popular product. - One request for the whole page. The queries are independent, so a
product page can send them together with
/api/v2/_batch.
Related: Behavioural analytics Β· Market basket analysis Β· Price and demand Β· Query Reference