Personalised Product Recommendations
A recommendation is a ranking of items by the probability that a person acts
on them. Most systems get there by exporting interaction logs, training a
model offline and serving its output from a cache. In Aito v2 the ranking is
one query over the log itself: recommend names the link to rank, goal
names the outcome to maximise, and where carries whatever you know about the
person and the session.
The queries below run against the live v2 sandbox.
impressions records each time a product was shown in a context, and
whether it was bought (purchase). Through the links, context.user is the
shopper and context.basket is what was already in the basket. Probabilities
in the responses are rounded.
Recommend for a user
{
"from": "impressions",
"where": { "context.user": "larry" },
"recommend": "product",
"goal": { "purchase": true },
"select": ["$value", "name", "$p"],
"limit": 5
}
{ "total": 42, "hits": [
{ "$value": "6410405207722", "name": "Pirkka paper towel 4 rl", "$p": 0.123 },
{ "$value": "6411401028373", "name": "Karl Fazer 200g Tyrkisk Peber chocolate", "$p": 0.113 },
{ "$value": "6415600501811", "name": "Coca-Cola 1,5l soft drink", "$p": 0.104 },
{ "$value": "6410405093677", "name": "Pirkka iceberg salad Finland 100g 1st class", "$p": 0.102 },
{ "$value": "6410405216120", "name": "Pirkka lactose-free semi-skimmed milk drink 1l", "$p": 0.092 } ] }
Every one of the 42 products is a candidate. $p is the probability that the
product is bought when shown to Larry, so the ranking favours what converts
for him, not only what he buys most often. select can name the product's own
columns (name) because product is a link.
In SQL the same ranking is a table function:
SELECT * FROM recommend('impressions', 'product', goal => 'purchase = true',
given => 'context.user = ''larry''', k => 5)
Leave out what is already in the basket
A condition on the recommended field restricts the candidates. To exclude
items, list them under $and with $not:
{
"from": "impressions",
"where": {
"context.user": "larry",
"product": { "$and": [ { "$not": "6410405207722" }, { "$not": "6415600501811" } ] }
},
"recommend": "product",
"goal": { "purchase": true },
"select": ["$value", "name", "$p"],
"limit": 4
}
total drops to 40, and the paper towels and the cola are gone. The remaining
products keep the probabilities they had: exclusion removes candidates, it
does not re-score them. An empty $and list constrains nothing, so a client
can send the basket as it is, even when it is empty.
Use the basket as evidence
What is already in the basket says something about what comes next. With rye bread in Larry's basket, and the bread itself excluded:
{
"from": "impressions",
"where": {
"context.user": "larry",
"context.basket": { "$has": "6437002001454" },
"product": { "$not": "6437002001454" }
},
"recommend": "product",
"goal": { "purchase": true },
"select": ["$value", "name", "$p"],
"limit": 5
}
{ "total": 41, "hits": [
{ "$value": "6411401028373", "name": "Karl Fazer 200g Tyrkisk Peber chocolate", "$p": 0.162 },
{ "$value": "6415600501811", "name": "Coca-Cola 1,5l soft drink", "$p": 0.121 },
{ "$value": "6407870071224", "name": "Atria Gotler ham sausage 300g", "$p": 0.116 },
{ "$value": "6410405207722", "name": "Pirkka paper towel 4 rl", "$p": 0.096 },
{ "$value": "6410405093677", "name": "Pirkka iceberg salad Finland 100g 1st class", "$p": 0.094 } ] }
Ham sausage enters the top five and the chocolate moves up. Remove
context.user and the same query serves an anonymous shopper from the basket
alone: for that case the top recommendation is the weekend newspaper (0.152).
SELECT * FROM recommend('impressions', 'product', goal => 'purchase = true',
given => 'context.user = ''larry'' AND context.basket = ''6437002001454'' AND product <> ''6437002001454''',
k => 5)
In SQL, = against the basket array is membership, the same as $has.
Explain a recommendation
Add $why to the select:
{
"from": "impressions",
"where": { "context.user": "larry" },
"recommend": "product",
"goal": { "purchase": true },
"select": ["$value", "name", "$p", "$why"],
"limit": 1
}
For the paper towels the factor tree starts from the base purchase rate of an
impression (0.052), multiplies in a lift of 2.01 because this product converts
better than the average product, and a lift of 1.21 from context.user: "larry". The remaining factors are each close to 1. A reviewer can see which part is popularity and which part
is personal.
Why Aito for this
- No training step. A purchase written to
impressionschanges the next recommendation. - Context is a condition. The user, the basket, the weekday or the search
phrase are all
whereconditions on the same query, and any of them can be left out. - Probabilities, not scores.
$pis a probability of the goal, so it can be thresholded, compared across users and explained.
Related: Smart search Β· Cart autofill Β· Behavioural analytics Β· Query Reference