Skip to content
← Back to Functions
Code

Retention Curve Builder

Builds day/week/month retention curves from activity events.

Function signature

ogBuildRetentionCurve(events = array(), options = array())

Categories

  • Analytics and Reports

Parameters

eventsEvents with user_id, cohort_date, and activity_date keys.optionsOptions: max_periods. Recognized keys: `max_periods`.

Return value

Short public-safe status message.

  • cohorts

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, policy arrays, URLs, signatures, 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.
 */

/**
 * Builds a simple retention curve from user activity events.
 *
 * @param array $events Events with user_id, cohort_date, and activity_date keys.
 * @param array $options Options: max_periods.
 * @return array Retention curve rows keyed by cohort date.
 */
function ogBuildRetentionCurve($events = array(), $options = array()) {
	$result = array(
		'success' => false,
		'message' => '',
		'data' => array()
	);

	if (!is_array($events)) {
		$result['message'] = 'Events must be an array.';
		return $result;
	}

	if (!is_array($options)) {
		$options = array();
	}

	$max_periods = 12;
	if (!empty($options['max_periods'])) {
		$max_periods = (int)$options['max_periods'];
	}
	if ($max_periods < 1) {
		$max_periods = 12;
	}

	$cohorts = array();
	foreach ($events as $event) {
		if (!is_array($event) || empty($event['user_id']) || empty($event['cohort_date']) || empty($event['activity_date'])) {
			continue;
		}

		$user_id = (string)$event['user_id'];
		$cohort_time = strtotime((string)$event['cohort_date']);
		$activity_time = strtotime((string)$event['activity_date']);
		if (empty($cohort_time) || empty($activity_time) || $activity_time < $cohort_time) {
			continue;
		}

		$cohort = date('Y-m-d', $cohort_time);
		$period = (int)floor(($activity_time - $cohort_time) / 86400);
		if ($period > $max_periods) {
			continue;
		}

		if (empty($cohorts[$cohort])) {
			$cohorts[$cohort] = array('users' => array(), 'periods' => array());
		}
		$cohorts[$cohort]['users'][$user_id] = true;
		if (empty($cohorts[$cohort]['periods'][$period])) {
			$cohorts[$cohort]['periods'][$period] = array();
		}
		$cohorts[$cohort]['periods'][$period][$user_id] = true;
	}

	$curve = array();
	foreach ($cohorts as $cohort => $data) {
		$cohort_size = count($data['users']);
		$curve[$cohort] = array('cohort_size' => $cohort_size, 'periods' => array());
		for ($period = 0; $period <= $max_periods; $period++) {
			$retained = 0;
			if (!empty($data['periods'][$period])) {
				$retained = count($data['periods'][$period]);
			}
			$percent = 0;
			if ($cohort_size > 0) {
				$percent = ($retained / $cohort_size) * 100;
			}
			$curve[$cohort]['periods'][$period] = array('retained' => $retained, 'percent' => $percent);
		}
	}

	$result['success'] = true;
	$result['message'] = 'Retention curve built.';
	$result['data'] = array('cohorts' => $curve);

	return $result;
}