RFM Customer Segmenter
Segments customers using recency, frequency, and monetary scores.
Function signature
ogSegmentCustomersRfm(customers = array(), options = array())
Categories
- Ecommerce Workflows
Parameters
customersCustomer rows with recency, frequency, and monetary fields.optionsOptional scoring thresholds. Recognized keys: `reference_time`.Return value
Short public-safe status message.
- segments
- skipped
Compatibility
Existing function name and call order preserved; metadata signature corrected to source.
Minimum PHP version: 7.4
Security notes
Validate request method, identity, permissions, and caller-owned allowlists before use; keep secrets and internal paths out of public output.
Code
<?php
/*
* Copyright (c) 2026 Jeffery L. Paris <jparis@phpog.com>.
* Free for personal and internal use. Paid project use requires visible credit
* to Jeffery L. Paris. Corporate use requires a paid license fee unless a
* separate written license states otherwise.
*/
/**
* Segments customers using recency, frequency, and monetary scores.
*
* @param array $customers Customer rows with recency, frequency, and monetary fields.
* @param array $options Optional scoring thresholds.
* @return array Customer rows with RFM scores and segment labels.
*/
function ogSegmentCustomersRfm($customers = array(), $options = array()) {
$result = array(
'success' => false,
'message' => '',
'data' => array()
);
if (!is_array($customers)) {
$result['message'] = 'Customers must be an array.';
return $result;
}
if (!is_array($options)) {
$options = array();
}
$reference_time = time();
if (!empty($options['reference_time'])) {
$reference_time = (int)$options['reference_time'];
}
$segments = array();
$skipped = 0;
foreach ($customers as $customer) {
if (!is_array($customer)) {
$skipped++;
continue;
}
$last_order_time = 0;
if (!empty($customer['last_order_at'])) {
$parsed_time = strtotime((string)$customer['last_order_at']);
if ($parsed_time !== false) {
$last_order_time = $parsed_time;
}
}
$order_count = 0;
if (!empty($customer['order_count']) && is_numeric($customer['order_count'])) {
$order_count = (int)$customer['order_count'];
}
$revenue_minor_units = 0;
if (!empty($customer['revenue_minor_units']) && is_numeric($customer['revenue_minor_units'])) {
$revenue_minor_units = (int)$customer['revenue_minor_units'];
}
if ($last_order_time <= 0) {
$skipped++;
continue;
}
$recency_days = (int)floor(($reference_time - $last_order_time) / 86400);
$recency_score = 1;
if ($recency_days <= 30) {
$recency_score = 5;
} elseif ($recency_days <= 90) {
$recency_score = 4;
} elseif ($recency_days <= 180) {
$recency_score = 3;
} elseif ($recency_days <= 365) {
$recency_score = 2;
}
$frequency_score = 1;
if ($order_count >= 20) {
$frequency_score = 5;
} elseif ($order_count >= 10) {
$frequency_score = 4;
} elseif ($order_count >= 5) {
$frequency_score = 3;
} elseif ($order_count >= 2) {
$frequency_score = 2;
}
$monetary_score = 1;
if ($revenue_minor_units >= 100000) {
$monetary_score = 5;
} elseif ($revenue_minor_units >= 50000) {
$monetary_score = 4;
} elseif ($revenue_minor_units >= 20000) {
$monetary_score = 3;
} elseif ($revenue_minor_units >= 5000) {
$monetary_score = 2;
}
$total_score = $recency_score + $frequency_score + $monetary_score;
$label = 'Needs attention';
if ($total_score >= 13) {
$label = 'Champion';
} elseif ($total_score >= 10) {
$label = 'Loyal';
} elseif ($total_score >= 7) {
$label = 'Promising';
}
$segments[] = array(
'customer' => $customer,
'recency_days' => $recency_days,
'recency_score' => $recency_score,
'frequency_score' => $frequency_score,
'monetary_score' => $monetary_score,
'total_score' => $total_score,
'segment' => $label
);
}
$result['success'] = true;
$result['message'] = 'RFM segmentation completed.';
$result['data'] = array('segments' => $segments, 'skipped' => $skipped);
return $result;
}