Tutorial

Introduction to using Recombee

The first part of the tutorial covers sending interactions to the system and getting recommendations based on them.

Video tutorial

Source codes

Text tutorial

Sending interactions

Suppose that I run a website which offers tickets to cultural events and I already have some collected data from the past, namely views of the events and purchases of the tickets by the users. For simplicity I have the interactions in two .json files with the same structure.

purchases.json:

[
    {"user_id": "user-50",  "item_id": "event-276", "timestamp": "2016-04-20T12:50:42+02:00"},
    {"user_id": "user-389", "item_id": "event-73",  "timestamp": "2014-07-20T02:49:45+02:00"},
    {"user_id": "user-204", "item_id": "event-116", "timestamp": "2015-04-22T13:32:32+02:00"},
    ...
]

detail_views.json:

[
    {"user_id": "user-7",   "item_id": "event-12 ", "timestamp": "2016-04-20T13:25:55+02:00"},
    {"user_id": "user-384", "item_id": "event-73",  "timestamp": "2016-04-20T13:07:10+02:00"},
    {"user_id": "user-12",  "item_id": "event-113", "timestamp": "2016-04-20T12:50:42+02:00"},
    ...
]
...

We will use the an SDK for communication with Recombee, as it greatly simplifies the process of the integration. I need the Recombee client so we will import it from the already installed package. We will import also all the classes for the requests in order to spare some typing. Then I create the instance of the client. It is initialized with the ID of my database and the secret token, which I both gained when I created the instant account, so we will just copy and paste them from the UI at admin.recombee.com.

from recombee_api_client.api_client import RecombeeClient
from recombee_api_client.api_requests import *

client = RecombeeClient('events-example', 'PbBaEVxx8ZOj0x3BhGtqfHyi8qQ8rm8rE1JBnSPoCnHwetzO3gjHer96YVAIa14G')
RecombeeClient client = new RecombeeClient("events-example", "PbBaEVxx8ZOj0x3BhGtqfHyi8qQ8rm8rE1JBnSPoCnHwetzO3gjHer96YVAIa14G");
require 'recombee_api_client'
include RecombeeApiClient

client = RecombeeClient.new('events-example', 'jGGQ6ZKa8rQ1zTAyxTc0EMn55YPF7FJLUtaMLhbsGxmvwxgTwXYqmUk5xVZFw98L')
<?php
use Recombee\RecommApi\Client;
use Recombee\RecommApi\Requests as Reqs;
use Recombee\RecommApi\Exceptions as Ex;

$client = new Client('events-example', 'PbBaEVxx8ZOj0x3BhGtqfHyi8qQ8rm8rE1JBnSPoCnHwetzO3gjHer96YVAIa14G');
?>
using Recombee.ApiClient;
using Recombee.ApiClient.ApiRequests;
using Recombee.ApiClient.Bindings;

var client = new RecombeeClient("events-example", "PbBaEVxx8ZOj0x3BhGtqfHyi8qQ8rm8rE1JBnSPoCnHwetzO3gjHer96YVAIa14G");
var recombee = require('recombee-api-client');
var rqs = recombee.requests;

var client = new recombee.ApiClient('events-example', 'PbBaEVxx8ZOj0x3BhGtqfHyi8qQ8rm8rE1JBnSPoCnHwetzO3gjHer96YVAIa14G');

Now I want to upload some interactions to the recommender system. We will start by reading and the purchases from the json file and creating an AddPurchase request object for each purchase in the file. It takes two mandatory parameters - the userId and the itemId. We will set the optional parameter timestamp to time, because the default value is the current time, but I want a particular time from past. And we will also set cascadeCreate to true, in order to create in the system the yet non existing items and users.

from recombee_api_client.api_client import RecombeeClient
from recombee_api_client.api_requests import *
import json

client = RecombeeClient('events-example', 'PbBaEVxx8ZOj0x3BhGtqfHyi8qQ8rm8rE1JBnSPoCnHwetzO3gjHer96YVAIa14G')

requests = []

with open('purchases.json') as f:
    interactions = json.loads(f.read())
    for interaction in interactions:
        r = AddPurchase(interaction['user_id'],
                        interaction['item_id'],
                        timestamp=interaction['timestamp'],
                        cascade_create=True)
RecombeeClient client = new RecombeeClient("events-example", "PbBaEVxx8ZOj0x3BhGtqfHyi8qQ8rm8rE1JBnSPoCnHwetzO3gjHer96YVAIa14G");

JSONParser parser = new JSONParser();
try {
    JSONArray a = (JSONArray) parser.parse(new FileReader("purchases.json"));
    for (Object o : a) {
        JSONObject interaction = (JSONObject) o;
        Date time =  new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX").parse((String)interaction.get("timestamp"));
        Request r = new AddPurchase((String) interaction.get("userId"),
                                      (String) interaction.get("itemId"))
                        .setTimestamp(time).setCascadeCreate(true);
    }
} catch (org.json.simple.parser.ParseException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
} catch (ParseException e) {
    e.printStackTrace();
}
require 'json'

client = RecombeeClient.new('events-example', 'jGGQ6ZKa8rQ1zTAyxTc0EMn55YPF7FJLUtaMLhbsGxmvwxgTwXYqmUk5xVZFw98L')

file = File.read('purchases.json')
JSON.parse(file).each do |interaction|
  user_id = interaction['user_id']
  item_id = interaction['item_id']
  time = interaction['timestamp']
  r = AddPurchase.new(user_id, item_id, 'timestamp' => time, 'cascadeCreate' => true)
end
<?php

    $client = new Client('events-example', 'PbBaEVxx8ZOj0x3BhGtqfHyi8qQ8rm8rE1JBnSPoCnHwetzO3gjHer96YVAIa14G');

    $str = file_get_contents('purchases.json');

    foreach(json_decode($str, true) as $interacion) {
        $user_id = $interacion['user_id'];
        $item_id = $interacion['item_id'];
        $time = $interacion['timestamp'];

        $r = new Reqs\AddPurchase($user_id, $item_id,
                                        ['timestamp' => $time, 'cascadeCreate' => true]);
    }

?>
var client = new RecombeeClient("events-example", "PbBaEVxx8ZOj0x3BhGtqfHyi8qQ8rm8rE1JBnSPoCnHwetzO3gjHer96YVAIa14G");

string[] allLines = File.ReadAllLines(@"purchases.csv");

var parsed = from line in allLines
            let row = line.Split(',')
            select new
            {
                UserId = row[0],
                ItemId = row[1],
                Timestamp = DateTime.Parse(row[2], null, System.Globalization.DateTimeStyles.RoundtripKind)
            };
var purchases = parsed.Select(x => new AddPurchase(x.UserId, x.ItemId, timestamp: x.Timestamp, cascadeCreate: true));
var interactions = require('./purchases.json');

var requests = interactions.map((interaction) => {
  var userId = interaction['user_id'];
  var itemId = interaction['item_id'];
  var time = interaction['timestamp'];

  return new rqs.AddPurchase(userId, itemId, {timestamp: time, cascadeCreate: true});
});

I could send the purchases one by one, using the send method of the client.

client.send(r)
client.send(r);
client.send(r)
<?php

        $client->send($r);
?>
foreach(AddPurchase purchase in purchases) client.Send(purchase);
purchases.forEach((purchase) => client.send(purchase));

Sending individual requests is very beneficial when you have the recommender already deployed in production, as the system can immediately modify its recommendations using the just received interaction.

But uploading larger data from the past with individual requests would be quite slow, and therefore for sending the list of purchases we will use the Batch request, which can encapsulate many requests into a single request.

requests = []

with open('purchases.json') as f:
    interactions = json.loads(f.read())
    for interaction in interactions:
        r = AddPurchase(interaction['user_id'],
                        interaction['item_id'],
                        timestamp=interaction['timestamp'],
                        cascade_create=True)
        requests.append(r)

br = Batch(requests)
client.send(br)
ArrayList<Request> interactions = new ArrayList<>();

try {

        ...

        Request r = new AddPurchase((String) interaction.get("userId"),
                                    (String) interaction.get("itemId"))
                        .setTimestamp(time).setCascadeCreate(true);
        interactions.add(r);
        ...
}
...

client.send(new Batch(interactions));
interactions = []

file = File.read('purchases.json')
JSON.parse(file).each do |interaction|
  user_id = interaction['user_id']
  item_id = interaction['item_id']
  time = interaction['timestamp']
  r = AddPurchase.new(user_id, item_id, 'timestamp' => time, 'cascadeCreate' => true)
  interactions.push(r)
end

br = Batch.new(interactions)
client.send(br)
<?php

    $requests = array();
    $str = file_get_contents('purchases.json');

    foreach(json_decode($str, true) as $interacion) {
        $user_id = $interacion['user_id'];
        $item_id = $interacion['item_id'];
        $time = $interacion['timestamp'];

        $r = new Reqs\AddPurchase($user_id, $item_id,
                                        ['timestamp' => $time, 'cascadeCreate' => true]);
        array_push($requests, $r);
    }

    $br = new Reqs\Batch($requests);
    $client->send($br);

?>
client.Send(new Batch(purchases));
client.send(new rqs.Batch(requests), (err, responses) => {
    console.log(responses);
});

Now let’s run the script and see the result in the web interface.

You should see the uploaded items in the Catalog listing. You can also check interactions of an item by clicking its id.

Items in the catalog listing

Now I’ll change the code to send the detail views by changing the name of file with interactions (purchases.csv to detail_views.csv ) and the name of the class from AddPurchase to AddDetailView.

r = AddDetailView(interaction['user_id'],
                interaction['item_id'],
                timestamp=interaction['timestamp'],
                cascade_create=True)
Request r = new AddDetailView((String) interaction.get("userId"),
                                              (String) interaction.get("itemId"))
                                .setTimestamp(time).setCascadeCreate(true);
r = AddDetailView.new(user_id, item_id, 'timestamp' => time, 'cascadeCreate' => true)
<?php

$r = new Reqs\AddDetailView($user_id, $item_id,
                                ['timestamp' => $time, 'cascadeCreate' => true]);

?>
var detailViews = parsed.Select(x => new AddDetailView(x.UserId, x.ItemId, timestamp: x.Timestamp, cascadeCreate: true));
return new rqs.AddDetailView(userId, itemId, {timestamp: time, cascadeCreate: true});

After running the changed code, detail views should appear in the interface.

You can also see the visualization in the KPI console, which is updated up to every few minutes.

Interactions in the catalog listing

Recommending items to user

Now it’s time to get some recommendations based on the uploaded interactions.

Suppose that user-27 just came to my website. I want to immediately show him 5 events that he will most likely favor. I’ll use the RecommendItemsToUser for this task.

recommended = client.send(RecommendItemsToUser('user-27', 5))
print(recommended)
RecommendationResponse recommended = client.send(new RecommendItemsToUser("user-27", 5));
for(Recommendation rec: recommended) System.out.println(rec.getId());
recommended = client.send(RecommendItemsToUser.new('user-27', 5))
puts(recommended)
<?php

$recommended = $client->send(new Reqs\RecommendItemsToUser('user-27', 5));
echo 'User based recommendation for "user-27": ' . print_r($recommended, true) . "\n";

?>
Console.WriteLine("Recommendations for \"user-27\":");
RecommendationResponse recommended = client.Send(new RecommendItemsToUser("user-27", 5));
foreach(Recommendation rec in recommended.Recomms) Console.WriteLine(rec.Id);
client.send(new rqs.RecommendItemsToUser('user-27', 5), (err, recommendations) => {
  console.log(recommendations);
});

An object with recommended items in field recomms is returned. For example:

{
  "recommId": "c386301b-8f9d-4841-9b83-9e7f1f6bb463",
  "recomms": [
    {
      "id": "event-5"
    },
    {
      "id": "event-17"
    },
    {
      "id": "event-32"
    },
    {
      "id": "event-19"
    },
    {
      "id": "event-92"
    }
  ]
}

Now I can show these recommended events to user-27 at my homepage.

Recommending items to item

Let’s say that the user likes the recommended events, and clicked one of them, namely event-32, to see the details. The page of the event contains box with related events, which are obtained by requesting the RecommendItemsToItem. The id of the user is passed in the request as well, to make the related items personalized for user-27.

recommended = client.send(RecommendItemsToItem('event-32', 'user-27', 5))
print(recommended)
recommended = client.send(new RecommendItemsToItem("event-32", "user-27", 5));
for(Recommendation rec: recommended) System.out.println(rec.getId());
recommended = client.send(RecommendItemsToItem.new('event-32', 'user-27', 5))
puts(recommended)
<?php

$recommended = $client->send(new Reqs\RecommendItemsToItem('event-32', 'user-27', 5));
echo 'Related items to "event-32" for "user-27": ' . print_r($recommended, true) . "\n";

?>
Console.WriteLine("Items related to \"event-32\" for \"user-27\":");
recommended = client.Send(new RecommendItemsToItem("event-32", "user-27", 5));
foreach(Recommendation rec in recommended.Recomms) Console.WriteLine(rec.Id);
client.send(new rqs.RecommendItemsToItem('event-32', 'user-27', 5),
  (err, recommendations) => {
    console.log(recommendations);
});

Returning:

{
  "recommId": "a7add465-d29d-4a1b-9e38-4923378ec0b5",
  "recomms": [
    {
      "id": "event-59"
    },
    {
      "id": "event-19"
    },
    {
      "id": "event-17"
    },
    {
      "id": "event-38"
    },
    {
      "id": "event-3"
    }
  ]
}

Recommending users to item

Other two supported recommendation endpoints, RecommendUsersToItem and RecommendUsersToUser return users instead of items. For example if I want to know which users would be most likely interested in attending event-42, I’ll call the RecommendUsersToItem.

recommended = client.send(RecommendUsersToItem('event-42', 5))
print(recommended)
recommended = client.send(new RecommendUsersToItem("event-42",  5));
for(Recommendation rec: recommended) System.out.println(rec.getId());
recommended = client.send(RecommendUsersToItem.new('event-42', 5))
puts(recommended)
<?php

$recommended = $client->send(new Reqs\RecommendUsersToItem('event-42', 5));
echo 'Users who should be interested in "event-42": ' . print_r($recommended, true) . "\n";

?>
Console.WriteLine("Users who should be interested in to \"event-42\":");
recommended = client.Send(new RecommendUsersToItem("event-42", 5));
foreach(Recommendation rec in recommended.Recomms) Console.WriteLine(rec.Id);
client.send(new rqs.RecommendUsersToItem('event-42', 5), (err, recommendations) => {
  console.log(recommendations);
});

Returning:

{
  "recommId": "a7add465-d29d-4a1b-9e38-4923378ec0b5",
  "recomms": [
    {
      "id": "user-47"
    },
    {
      "id": "user-95"
    },
    {
      "id": "user-10"
    },
    {
      "id": "user-50"
    },
    {
      "id": "user-54"
    }
  ]
}

As you can see sending interactions and getting recommendations is very easy, and took just few lines of code. In the next tutorial I’ll show you how to send properties of the items to Recombee and how to use these properties in filtering and boosting according to your business rules.