Simple Trend Analyzer
Calculates direction, percent change, and volatility across a numeric time series.
Function signature
ogAnalyzeSimpleTrend(points = array(), options = array())
Categories
- Analytics and Reports
Parameters
pointsNumeric values or rows with value/date keys.optionsOptional keys: value_key, date_key, minimum_points. Recognized keys: `date_key`, `minimum_points`, `value_key`.Return value
Short public-safe status message.
- count
- first
- last
- minimum
- maximum
- average
- change
- percent_change
- direction
- volatility
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.
*/
/**
* Calculates direction, percent change, and volatility across a numeric time series.
*
* Accepts a simple numeric array or dated rows containing value/date keys. Dated rows
* are sorted before analysis so the first and last values reflect the actual series order.
*
* @param array $points Numeric values or rows with value/date keys.
* @param array $options Optional keys: value_key, date_key, minimum_points.
* @return array Trend report with count, change, percent change, direction, and volatility.
*/
function ogAnalyzeSimpleTrend($points = array(), $options = array()) {
$result = array(
'success' => false,
'message' => '',
'data' => array()
);
if (!is_array($points)) {
$result['message'] = 'Points must be an array.';
return $result;
}
if (!is_array($options)) {
$options = array();
}
$value_key = 'value';
if (!empty($options['value_key'])) {
$value_key = (string)$options['value_key'];
}
$date_key = 'date';
if (!empty($options['date_key'])) {
$date_key = (string)$options['date_key'];
}
$minimum_points = 2;
if (!empty($options['minimum_points'])) {
$minimum_points = (int)$options['minimum_points'];
}
if ($minimum_points < 2) {
$minimum_points = 2;
}
$series = array();
foreach ($points as $index => $point) {
$value = null;
$date_sort = $index;
if (is_array($point)) {
if (array_key_exists($value_key, $point) && is_numeric($point[$value_key])) {
$value = (float)$point[$value_key];
}
if (array_key_exists($date_key, $point)) {
$timestamp = strtotime((string)$point[$date_key]);
if ($timestamp !== false) {
$date_sort = $timestamp;
}
}
} else {
if (is_numeric($point)) {
$value = (float)$point;
}
}
if ($value !== null) {
$series[] = array(
'sort' => $date_sort,
'value' => $value
);
}
}
if (count($series) < $minimum_points) {
$result['message'] = 'Not enough numeric points were supplied.';
return $result;
}
usort($series, function($left, $right) {
if ($left['sort'] == $right['sort']) {
return 0;
}
if ($left['sort'] < $right['sort']) {
return -1;
}
return 1;
});
$values = array();
foreach ($series as $point) {
$values[] = $point['value'];
}
$count = count($values);
$sum = array_sum($values);
$average = $sum / $count;
$first = $values[0];
$last = $values[$count - 1];
$change = $last - $first;
$percent_change = 0;
if ($first != 0) {
$percent_change = ($change / abs($first)) * 100;
}
$variance_total = 0;
foreach ($values as $value) {
$variance_total += pow($value - $average, 2);
}
$volatility = sqrt($variance_total / $count);
$direction = 'flat';
if ($change > 0) {
$direction = 'up';
} elseif ($change < 0) {
$direction = 'down';
}
$result['success'] = true;
$result['message'] = 'Trend analysis completed.';
$result['data'] = array(
'count' => $count,
'first' => $first,
'last' => $last,
'minimum' => min($values),
'maximum' => max($values),
'average' => $average,
'change' => $change,
'percent_change' => $percent_change,
'direction' => $direction,
'volatility' => $volatility
);
return $result;
}