diff --git a/Math/Finance/Forecasting/ExponentialSmoothing.php b/Math/Finance/Forecasting/ExponentialSmoothing.php index ea4d2b761..107efdfb6 100644 --- a/Math/Finance/Forecasting/ExponentialSmoothing.php +++ b/Math/Finance/Forecasting/ExponentialSmoothing.php @@ -4,5 +4,12 @@ namespace phpOMS\Math\Finance\Forecasting; class ExponentialSmoothing { + private $data = []; + private $cycle = []; + + public function __construct(array $data) + { + + } } diff --git a/Math/Finance/Forecasting/ExponentialSmoothing/Brown.php b/Math/Finance/Forecasting/ExponentialSmoothing/Brown.php new file mode 100644 index 000000000..dd1a86a34 --- /dev/null +++ b/Math/Finance/Forecasting/ExponentialSmoothing/Brown.php @@ -0,0 +1,197 @@ +data = $data; + $this->cycle = $cycle; + } + + public function setCycle(int $cycle) /* : void */ + { + $this->cycle = $cycle; + } + + public function getRMSE() : float + { + return $this->getRMSE; + } + + public function getMSE() : float + { + return $this->mse; + } + + public function getMAE() : float + { + return $this->mae; + } + + public function getSSE() : float + { + return $this->sse; + } + + public function getErrors() : array + { + return $this->errors; + } + + public function getForecast(int $future = 1, int $smoothing = SmoothingType::CENTERED_MOVING_AVERAGE) : array + { + $trendCycle = $this->getTrendCycle($this->$cycle); + $seasonality = $this->getSeasonality($trendCycle); + $seasonalityIndexMap = $this->generateSeasonalityMap($this->cycle, $seasonality); + $adjustedSeasonalityIndexMap = $this->generateAdjustedSeasonalityMap($this->cycle, $seasonalityIndexMap); + $seasonalIndex = $this->getSeasonalIndex($this->cycle, $adjustedSeasonalityIndexMap); + $adjustedData = $this->getAdjustedData($this->cycle, $seasonalIndex); + $optimizedForecast = $this->getOptimizedForecast($future, $adjustedData); + + return $this->getReseasonalized($optimizedForecast, $seasonalIndex); + } + + private function getTrendCycle(int $cycle) : array + { + $centeredMovingAverage = []; + + $length = count($this->data); + for($i = $cycle; $i < $length - $cycle; $i++) { + $centeredMovingAverage[$i] = Average::arithmetic(array_slice($this->data, $i - $cycle, $cycle)); + } + + return $centeredMovingAverage; + } + + private function getSeasonality(array $trendCycle) : array + { + $seasonality = []; + foreach($trendCycle as $key => $value) { + $seasonality[$key] = $this->data[$key]/$value; + } + + return $seasonality; + } + + private function generateSeasonality(int $cycle, array $seasonality) : array + { + $map = []; + foreach($seasonality as $key => $value) { + $map[$key % $cycle][] = $value; + } + + foreach($map as $key => $value) { + $map[$key] = Average::arithmeticMean($value); + } + + return $map; + } + + private function generateAdjustedSeasonalityMap(int $cycle, array $seasonality) : array + { + $total = array_sum($seasonality); + + foreach($seasonality as $key => $value) { + $seasonality[$key] = $cycle * $value / $total; + } + + return $seasonality; + } + + private function getSeasonalIndex(int $cycle, array $seasonalityMap) : array + { + $index = []; + + foreach($this->data as $key => $value) { + $index[$key] = $seasonalityMap[$key % $cycle]; + } + + return $index; + } + + private function getAdjustedData(int $cycle, array $seasonalIndex) : array + { + $adjusted = []; + + foreach($this->data as $key => $value) { + $adjusted[$key] = $this->data[$key] / $seasonalIndex[$key]; + } + + return $adjusted; + } + + private function forecast(int $future, float $alpha, array $data, array &$error) : array + { + $forecast = []; + $length = count($data) + $future; + + $forecast[0] = $data[0]; + $forecast[1] = $data[1]; + + $error[0] = 0; + $error[1] = $data[1] - $forecast[1]; + + for($i = 2; $i < $length; $i++) { + $forecast[$i] = 2 * $data[$i-1] - $data[$i - 2] - 2 * (1 - $alpha) * $error[$i-1] + pow(1-$alpah, 2) * $error[$i - 2]; + $error[$i] = $data[$i] - $forecast[$i]; + } + + return $forecast; + } + + private function getOptimizedForecast(int $future, array $adjustedData) : array + { + $rmse = 0; + $alpha = 0.00; + $forecast = []; + + while($alpha < 1) { + $error = []; + $tempForecast = $this->forecast($future, $alpha, $adjustedData, $error); + $alpha += 0.01; + + $tempRMSE = Error::getRootMeanSquaredError($error); + + if($tempRMSE < $this->rmse) { + $this->rmse = $tempRMSE; + $forecast = $tempForecast; + } + } + + $this->errors = $error; + $this->mse = Error::getMeanSquaredError($error); + $this->mae = Error::getMeanAbsoluteError($error); + $this->sse = Error::getSumSquaredError($error); + + return $forecast; + } + + private function getReseasonalized(array $forecast, array $seasonalIndex) : array + { + $reSeasonalized = []; + + foreach($forecast as $key => $value) { + $reSeasonalized[$key] = $value * $seasonalIndex[$key]; + } + + return $reSeasonalized; + } +} diff --git a/Math/Finance/Forecasting/ExponentialSmoothing/Holt.php b/Math/Finance/Forecasting/ExponentialSmoothing/Holt.php new file mode 100644 index 000000000..e69de29bb diff --git a/Math/Finance/Forecasting/SmoothingType.php b/Math/Finance/Forecasting/SmoothingType.php new file mode 100644 index 000000000..71491bdfa --- /dev/null +++ b/Math/Finance/Forecasting/SmoothingType.php @@ -0,0 +1,34 @@ + + * @author Dennis Eichhorn + * @copyright 2013 Dennis Eichhorn + * @license OMS License 1.0 + * @version 1.0.0 + * @link http://orange-management.com + */ +namespace phpOMS\Math\Finance\Forecasting; + +use phpOMS\Datatypes\Enum; + +/** + * Smoothing enum. + * + * @category Framework + * @package phpOMS\Html + * @author OMS Development Team + * @author Dennis Eichhorn + * @license OMS License 1.0 + * @link http://orange-management.com + * @since 1.0.0 + */ +abstract class SmoothingType extends Enum +{ + /* public */ const CENTERED_MOVING_AVERAGE = 1; +} diff --git a/Math/Statistic/Forecast/Error.php b/Math/Statistic/Forecast/Error.php index f0f1551d9..6ab77f78e 100644 --- a/Math/Statistic/Forecast/Error.php +++ b/Math/Statistic/Forecast/Error.php @@ -136,6 +136,7 @@ class Error */ public static function getRootMeanSquaredError(array $errors) : float { + // sqrt(Average::getVariance($error)+pow(Average::arithmeticMean($error), 2)); return sqrt(Average::arithmeticMean(self::square($errors))); }