Finally we have functional solution. When we run task agent and processing engine executes all steps, contact’s facets will be populated with RFM values and the number of the corresponding cluster contact belongs to.
Each of R, F and M values is in a range from 1 to 3.
But what these numbers and their combinations mean and how we can use it? Below is a table with descriptions and recommendations for each combination (small cubes from image above).
Leaving customers
RFM | Group | Recommendations |
111 | Lost | Most likely these clients have already left. It makes sense to send them an automatic chain of letters with an offer to return. |
112 | Single | |
113 | ||
121 | Leaving | These customers have already done some important actions on your service. You can try to get them back. |
122 | ||
123 | ||
131 | Leaving - Permanent | These customers you need to try to be sure to return. Offer them bonuses and loyalty programs. |
132 | Leaving - Permanent good | |
133 | Leaving - Permanent VIP |
Sleeping customers
RFM | Group | Recommendations |
211 | Sleeping | These customers remember your service. Try to engage them with promotions. |
212 | ||
213 | ||
221 | Sleeping - Rare with low value | Maybe they're former clients. Find out why they are going to leave or left. We send them newsletters with interesting promotions. |
222 | Sleeping - Rare with middle value | |
223 | Sleeping - Rare with high value | |
231 | Sleeping - Permanent with low value | These customers remember your service. Try to engage them with promotions. |
232 | Sleeping - Permanent with middle value | |
233 | Sleeping - Permanent with high value |
Regular customers
RFM | Group | Recommendations |
311 | Novice - Low value | We send a chain of letters with a description of the benefits and answers to questions. |
312 | Novice with middle value | |
313 | Novice with high value | We send a chain of letters with a description of the benefits and answers to questions. Add an interesting offer to keep the interest. |
321 | Regular with low value | We should try to increase their involvement. We send them mails with related products and services. |
322 | Regular - Middle value | |
323 | Regular - High value | These are good clients. Do not bore them with mailing. Only send a normal mails. |
331 | Very regular - Low value | You can try to engage customers even more. We send them links to related products and services. |
332 | Very regular - Middle value | These are the best customers. You can try to sell them a new product or service. Send them special offers |
333 | VIP |
So first we will create our custom segmentation rule and use it in List Manager to create segmented lists of users based on their RFM values.
First we will create new template for RFM pattern:
Next we will create a folder with all of our RFM combinations:
And then we will create rule condition “Where contacts RFM is”:
where the contact RFM value is [RfmId,Tree,root=/sitecore/system/Settings/Foundation/ProcessingEngine/RFM Patterns,rfm]
Source code of RfmMatch condition:
public class RfmMatch : ICondition, IContactSearchQueryFactory
{
public Guid RfmId { get; set; }
public bool Evaluate(IRuleExecutionContext context)
{
var contact = context.Fact();
var facet = contact.GetFacet(RfmContactFacet.DefaultFacetKey);
if (facet == null) return false;
var rfm = GetRfm();
if (rfm == null) return false;
return facet.R == rfm.R && facet.F == rfm.F && facet.M == rfm.M;
}
public Expression< />> CreateContactSearchQuery(IContactSearchQueryContext context)
{
var rfm = GetRfm();
if (rfm == null) return x => false;
return contact => contact.GetFacet(RfmContactFacet.DefaultFacetKey).R == rfm.R
&& contact.GetFacet(RfmContactFacet.DefaultFacetKey).F == rfm.F
&& contact.GetFacet(RfmContactFacet.DefaultFacetKey).M == rfm.M;
}
public CustomerBusinessValue GetRfm()
{
int r, f, m;
Item rfmPattern = Database.GetDatabase("master").GetItem(new ID(RfmId));
if (rfmPattern == null) return null;
if (int.TryParse(rfmPattern["R"], out r))
{
if (int.TryParse(rfmPattern["F"], out f))
{
if(int.TryParse(rfmPattern["M"], out m))
{
return new CustomerBusinessValue
{
R = r,
F = f,
M = m
};
}
}
}
return null;
}
}
Now we can navigate to List Manager and create segmented list, for example for VIP customers. We can use this list to send them special offers.
Also we can create similar rule condition for personalization:
Source code of PersonalizeRfm rule condition:
public class PersonalizeRfm : StringOperatorCondition where T : RuleContext
{
public Guid RfmId { get; set; }
protected override bool Execute(T ruleContext)
{
if (!Tracker.Current.IsActive || Tracker.Current.Contact == null) return false;
var xConnectFacets = Tracker.Current.Contact.GetFacet("XConnectFacets");
if (xConnectFacets.IsEmpty || xConnectFacets.Facets == null ||
!xConnectFacets.Facets.ContainsKey(RfmContactFacet.DefaultFacetKey)) return false;
RfmContactFacet facet = xConnectFacets.Facets[RfmContactFacet.DefaultFacetKey] as RfmContactFacet;
if (facet == null) return false;
var rfm = GetRfm();
if (rfm == null) return false;
return facet.R == rfm.R && facet.F == rfm.F && facet.M == rfm.M;
}
public CustomerBusinessValue GetRfm()
{
int r, f, m;
Item rfmPattern = Database.GetDatabase("master").GetItem(new ID(RfmId));
if (rfmPattern == null) return null;
if (int.TryParse(rfmPattern["R"], out r))
{
if (int.TryParse(rfmPattern["F"], out f))
{
if (int.TryParse(rfmPattern["M"], out m))
{
return new CustomerBusinessValue
{
R = r,
F = f,
M = m
};
}
}
}
return null;
}
}
For demo purposes we create two renderings “Demo” and “Demo VIP”, both will be rendered with different static images. Let`s personalize renderings for Home page. Scenario: if current contact matches VIP rfm-pattern then we show him “Demo VIP” rendering otherwise “Demo”.
When we open Home page in browser we will see “Demo” rendering, because our user is not VIP:
Next we choose any VIP user from previously created segmented list, copy his email and submit form by clicking ‘Identify’ button. Once page is reload we will see “Demo VIP” rendering content:
But we have 27 RFM patterns and it is not convenient for content managers to make personalization, targeting or advertising campaign based on 27 rules. Exactly for this we use clustering. Our ML engine divided all customers in several clusters based on similarity of their purchasing ability. In our demo scenario we use 5 clusters, this number can be changed according to marketing needs.
Let`s check how ML divides customer into clusters. When ML completes train of model we will calculate Evaluation metrics of resulted train model and run test data prediction to see statistics and cluster groups. It help us to understand what kind of customers belongs to concrete cluster.
As you see customers were divided into following clusters:
Cluster 4: Lost, Single, Sleeping, Novise with small monetary value (the worst customers)
Cluster 3: Leaving, Sleeping with average monetary and frequency but very rare (a little bit better than cluster 4)
Cluster 1: Single, Leaving, Sleeping, Novise with biggest monetary value but very rare (better than cluster 3)
Cluster 2: Sleeping, Regular, VIP with biggest monetary value (our VIP customers)
Cluster 5: Regular (our regular customers)
And now we can create rule conditions similar to RFM patterns, but for Cluster number instead of RFM:
Source code of ClusterMatch rule condition:
public class ClusterMatch : ICondition, IContactSearchQueryFactory
{
public NumericOperationType Comparison { get; set; }
public int Number { get; set; }
public bool Evaluate(IRuleExecutionContext context)
{
var contact = context.Fact();
var facet = contact.GetFacet(RfmContactFacet.DefaultFacetKey);
if (facet == null) return false;
int cluster = facet.Cluster;
return Comparison.Evaluate(cluster, Number);
}
public Expression< />> CreateContactSearchQuery(IContactSearchQueryContext context)
{
return contact => Comparison.Evaluate(contact.GetFacet(RfmContactFacet.DefaultFacetKey).Cluster, Number);
}
}
Source code of PersonalizeCluster rule condition:
public class PersonalizeCluster : StringOperatorCondition where T : RuleContext
{
public int Number { get; set; }
protected override bool Execute(T ruleContext)
{
if (!Tracker.Current.IsActive || Tracker.Current.Contact == null) return false;
var xConnectFacets = Tracker.Current.Contact.GetFacet("XConnectFacets");
if (xConnectFacets.IsEmpty || xConnectFacets.Facets == null ||
!xConnectFacets.Facets.ContainsKey(RfmContactFacet.DefaultFacetKey)) return false;
RfmContactFacet facet = xConnectFacets.Facets[RfmContactFacet.DefaultFacetKey] as RfmContactFacet;
if (facet == null) return false;
return facet.Cluster == Number;
}
}
Table of contents Dive into Sitecore Cortex and Machine Learning - Introduction
Source code https://github.com/x3mxray/Cortex.Demo.RFM