The business problem
US companies spend $75 billion annually on loyalty programs. Bond research shows that 60% of this spend is deadweight loss: rewards given to customers who would have made the purchase regardless. The remaining 40% drives genuinely incremental behavior. Identifying which rewards, for which customers, at what time, actually change behavior is the key to loyalty program ROI.
Traditional loyalty analytics use simple metrics: reward earn rate, redemption rate, and member spend lift. These aggregate metrics hide the individual-level incrementality that determines true program ROI. A customer who earns 5x points but would have bought the same items anyway generates zero incremental value.
Why flat ML fails
- No counterfactual reasoning: Flat models predict “will this customer purchase?” but not “will this customer purchase because of the reward?” The graph provides the control group: similar customers without the reward offer.
- No referral network: Loyalty programs with referral bonuses create network effects. One customer's loyalty generates referred customers, multiplying the reward's value. Flat models miss this amplification.
- No tier dynamics: Customers near a tier threshold exhibit different reward sensitivity than those mid-tier. The tier graph captures this positional context.
- No reward interaction: Multiple simultaneous rewards (points bonus + free shipping + exclusive access) interact. The reward graph captures which combinations drive behavior vs creating diminishing returns.
The relational schema
Node types:
Customer (id, tier, points_balance, tenure, ltv)
Reward (id, type, value, expiry, conditions)
Tier (id, name, threshold, benefits)
Product (id, category, price, reward_eligible)
Promotion (id, type, discount, target_segment)
Edge types:
Customer --[earned]--> Reward (timestamp, source)
Customer --[redeemed]--> Reward (timestamp, for_product)
Customer --[in_tier]--> Tier
Customer --[purchased]--> Product (amount, timestamp)
Customer --[referred]--> Customer (signup_date)
Promotion --[targeted]--> Customer (channel, timestamp)The loyalty graph captures earn/redeem behavior, tier dynamics, referral networks, and promotion targeting. All the signals needed for incrementality estimation.
PyG architecture: HeteroConv for incrementality estimation
import torch
import torch.nn.functional as F
from torch_geometric.nn import SAGEConv, HeteroConv, Linear
class LoyaltyGNN(torch.nn.Module):
def __init__(self, hidden_dim=128):
super().__init__()
self.customer_lin = Linear(-1, hidden_dim)
self.reward_lin = Linear(-1, hidden_dim)
self.tier_lin = Linear(-1, hidden_dim)
self.product_lin = Linear(-1, hidden_dim)
self.conv1 = HeteroConv({
('customer', 'earned', 'reward'): SAGEConv(
hidden_dim, hidden_dim),
('customer', 'redeemed', 'reward'): SAGEConv(
hidden_dim, hidden_dim),
('customer', 'in_tier', 'tier'): SAGEConv(
hidden_dim, hidden_dim),
('customer', 'purchased', 'product'): SAGEConv(
hidden_dim, hidden_dim),
('customer', 'referred', 'customer'): SAGEConv(
hidden_dim, hidden_dim),
}, aggr='sum')
self.conv2 = HeteroConv({
('customer', 'earned', 'reward'): SAGEConv(
hidden_dim, hidden_dim),
('customer', 'in_tier', 'tier'): SAGEConv(
hidden_dim, hidden_dim),
('customer', 'purchased', 'product'): SAGEConv(
hidden_dim, hidden_dim),
('customer', 'referred', 'customer'): SAGEConv(
hidden_dim, hidden_dim),
}, aggr='sum')
# Two heads: purchase prediction with and without reward
self.with_reward = Linear(hidden_dim, 1)
self.without_reward = Linear(hidden_dim, 1)
def forward(self, x_dict, edge_index_dict):
x_dict['customer'] = self.customer_lin(
x_dict['customer'])
x_dict['reward'] = self.reward_lin(x_dict['reward'])
x_dict['tier'] = self.tier_lin(x_dict['tier'])
x_dict['product'] = self.product_lin(x_dict['product'])
x_dict = {k: F.relu(v) for k, v in
self.conv1(x_dict, edge_index_dict).items()}
x_dict = self.conv2(x_dict, edge_index_dict)
h = x_dict['customer']
p_with = torch.sigmoid(self.with_reward(h).squeeze(-1))
p_without = torch.sigmoid(
self.without_reward(h).squeeze(-1))
# Incrementality = P(purchase|reward) - P(purchase|no reward)
return p_with, p_without, p_with - p_withoutDual-head architecture estimates purchase probability with and without reward. The difference is the incremental lift, identifying which rewards truly change behavior.
Expected performance
- Aggregate metrics (heuristic): ~50 AUROC
- LightGBM (flat-table): 62.44 AUROC
- GNN (HeteroConv): 75.83 AUROC
- KumoRFM (zero-shot): 76.71 AUROC
Or use KumoRFM in one line
PREDICT incremental_purchase FOR customer
USING customer, reward, tier, purchase, promotionOne PQL query. KumoRFM identifies which rewards drive incremental behavior vs deadweight spend.