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 .csv files with the same structure.

purchases.csv:

user-145      event-112       2016-04-20T12:47:29+02:00
user-7        event-12        2016-04-20T12:52:11+02:00
user-77       event-9         2016-04-20T14:13:47+02:00
user-19       event-159       2016-04-20T15:40:01+02:00
...

detail_views.csv:

user-7        event-12        2016-04-20T12:50:42+02:00
user-384      event-5         2016-04-20T13:06:03+02:00
user-12       event-159       2016-04-20T13:07:10+02:00
user-456      event-113       2016-04-20T13:25:55+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 the purchases from the csv file line by line 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 csv

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

with open('purchases.csv') as csvfile:
    csvreader = csv.reader(csvfile)
    for row in csvreader:
        user_id = row[0]
        item_id = row[1]
        time = row[2]
        r = AddPurchase(user_id, item_id, timestamp=time, cascade_create=True)
RecombeeClient client = new RecombeeClient("events-example", "PbBaEVxx8ZOj0x3BhGtqfHyi8qQ8rm8rE1JBnSPoCnHwetzO3gjHer96YVAIa14G");

String line = "";

try (BufferedReader br = new BufferedReader(new FileReader("purchases.csv"))) {

    while ((line = br.readLine()) != null) {

        String[] row = line.split(",");

        String userId = row[0];
        String itemId = row[1];
        Date time =  new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX").parse(row[2]);
        Request r = new AddPurchase(userId, itemId).setTimestamp(time).setCascadeCreate(true);
    }

} catch (IOException e) {
    e.printStackTrace();
} catch (ParseException e) {
    e.printStackTrace();
}
require 'csv'

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

CSV.foreach("purchases.csv") do |row|
  user_id = row[0]
  item_id = row[1]
  time = row[2]
  r = AddPurchase.new(user_id, item_id, 'timestamp' => time, 'cascadeCreate' => true)
end
<?php

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

if (($handle = fopen("purchases.csv", "r")) !== FALSE) {
    while (($row = fgetcsv($handle)) !== FALSE) {
        $user_id = $row[0];
        $item_id = $row[1];
        $time = $row[2];

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

?>
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 fs = require('fs');
var parse = require('csv-parse');

fs.readFile('purchases.csv', function (err, data) {
  parse(data, {delimiter: ','}, function(err, rows) {
    var purchases = rows.map((row) => {
      var userId = row[0];
      var itemId = row[1];
      var time = row[2];

      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.

purchases = []
with open('purchases.csv') as csvfile:
    ...
        r = AddPurchase(user_id, item_id, timestamp=time, cascade_create=True)
        purchases.append(r)

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

try (BufferedReader br = new BufferedReader(new FileReader("purchases.csv"))) {

        ...

        Request r = new AddPurchase(userId, itemId).setTimestamp(time).setCascadeCreate(true);
        purchases.add(r);
        ...
}
...

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

CSV.foreach("purchases.csv") do |row|
  user_id = row[0]
  item_id = row[1]
  time = row[2]
  r = AddPurchase.new(user_id, item_id, 'timestamp' => time, 'cascadeCreate' => true)
  purchases.push(r)
end

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

$purchases = array();

if (($handle = fopen("purchases.csv", "r")) !== FALSE) {
    ...

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

    ...
}

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

?>
client.Send(new Batch(purchases));
client.send(new rqs.Batch(purchases), (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(user_id, item_id, timestamp=time, cascade_create=True)
Request r = new AddDetailView(userId, 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

Getting recommendations

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 UserBasedRecommendation for this task.

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

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

?>
Console.WriteLine("User based recommendation for \"user-27\":");
IEnumerable<Recommendation> recommended = client.Send(new UserBasedRecommendation("user-27", 5));
foreach(Recommendation rec in recommended) Console.WriteLine(rec.Id);
client.send(new rqs.UserBasedRecommendation('user-27', 5), (err, recommendations) => {
  console.log(recommendations);
});

It returns IDs of the 5 recommended items:

['event-5', 'event-17', 'event-32', 'event-19', 'event-92']

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

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 ItemBasedRecommendation. I want the related items to be personalized for user-27, so I pass the targetUserId.

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

$recommended = $client->send(new Reqs\ItemBasedRecommendation('event-32', 5, ['targetUserId' => 'user-27']));
echo 'Item based recommendation based on "event-32" for "user-27": ' . implode(',',$recommended) . "\n";

?>
Console.WriteLine("Item based recommendation based on \"event-32\" for \"user-27\":");
recommended = client.Send(new ItemBasedRecommendation("event-32", 5, targetUserId: "user-27"));
foreach(Recommendation rec in recommended) Console.WriteLine(rec.Id);
client.send(new rqs.ItemBasedRecommendation('event-32', 5, {targetUserId: 'user-27'}),
  (err, recommendations) => {
    console.log(recommendations);
});

Returning:

['event-59', 'event-19', 'event-17', 'event-38', 'event-3']

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.