Jump to content

Recommended Posts

I'm trying to write a class that will return details for an exponential "best-fit", using a least squares fitting method, having modified each entry in the array of y values to its log.

 

class exponentialBestFit {

private $_xValues			= array();

private $_yValues			= array();

private $_goodnessOfFit 	= 1;

private $_stdevOfResiduals	= 0;

private $_slope				= 0;

private $_increment			= 0;


public function getValueOfYForX($xValue) {
	return exp($xValue * $this->_slope + $this->_intersect);
}	//	function getValueOfYForX()

public function getSlope() {
	return exp($this->_slope);
}	//	function getSlope()

public function getIntersect() {
	return $this->_intersect;
}	//	function getIntersect()

public function getGoodnessOfFit() {
	return $this->_goodnessOfFit;
}	//	function getYPointFromSlopeIntersect()

public function getStdevOfResiduals() {
	return $this->_stdevOfResiduals;
}	//	function getStdevOfResiduals()

private function _calculateGoodnessOfFit() {
	//	Calculate number of points
	$nY = count($this->_yValues);

	$meanY = array_sum($this->_yValues) / $nY;

	$SSreg = $SStot = 0.0;
	foreach($this->_xValues as $xKey => $xValue) {
		$bestFitY = $this->getValueOfYForX($xValue);

		$SSreg += ($this->_yValues[$xKey] - $bestFitY) * ($this->_yValues[$xKey] - $bestFitY);
		$SStot += ($this->_yValues[$xKey] - $meanY) * ($this->_yValues[$xKey] - $meanY);
	}

	$this->_stdevOfResiduals = sqrt($SSreg / ($nY - 2));
	if (($SStot == 0.0) || ($SSreg == $SStot)) {
		$this->_goodnessOfFit = 1;
	} else {
		$this->_goodnessOfFit = 1 - ($SSreg / $SStot);
	}
}	//	function _calculateGoodnessOfFit()

private function _exponential_regression($yValues, $xValues=array()) {
	//	Calculate number of points
	$nY = count($yValues);
	$nX = count($xValues);

	//	Define X Values if necessary
	if ($nX == 0) {
		$xValues = range(1,$nY);
		$nX = $nY;
	}
	//	Ensure both arrays of points are the same size
	if ($nY != $nX) {
		trigger_error("exponential_regression(): Number of elements in coordinate arrays do not match.", E_USER_ERROR);
	}
	$this->_xValues = $xValues;
	$this->_yValues = $yValues;

	foreach($yValues as $key => $value) {
		$yValues[$key] = log($yValues[$key]);
	}

	// calculate sums
	$x_sum = array_sum($xValues);
	$y_sum = array_sum($yValues);

	$xx_sum = $xy_sum = 0;

	for($i = 0; $i < $nY; $i++) {
		$xy_sum += $xValues[$i] * $yValues[$i];
		$xx_sum += $xValues[$i] * $xValues[$i];
	}

	// calculate slope
	$this->_slope = (($nY * $xy_sum) - ($x_sum * $y_sum)) / (($nY * $xx_sum) - ($x_sum * $x_sum));
	// calculate intersect
	$this->_intersect = ($y_sum - ($this->_slope * $x_sum)) / $nY;

	$this->_calculateGoodnessOfFit();

	foreach($xValues as $xValue) {
		$this->_yBestFitValues[] = $this->getValueOfYForX($xValue);
	}
}	//	function _exponential_regression()


function __construct($yValues, $xValues=array()) {
	$this->_exponential_regression($yValues, $xValues);
}	//	function __construct()

}	//	class exponentialBestFit

The getSlope(), getGoodnessOfFit() and getStdevOfResiduals() return exactly what I'd expect; and when I graph out the results by reading getValueOfYForX($xValue) for a series of $xValues, I get exactly the results that I expect.

However, I would have thought that i'd need to use

return $this->_intersect * exp($this->_slope * $xValue);

in getValueOfYForX(), but somehow I don't seem to be calculating the correct value for $this->_intersect

 

Can anybody with better math than myself help identify where I'm going wrong in calculating $this->_intersect, and confirm that I would be able to use the above Y = A * exp(B * X) if $this->_intersect (A) was being calculated correctly?

 

Link to comment
https://forums.phpfreaks.com/topic/137835-solved-exponential-best-fit/
Share on other sites

As an example,

I'm feeding the following data to the constructor:

x Values: 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 7, 7.5, 8
y Values: 2.633, 2.784, 2.955, 3.149, 3.368, 3.617, 3.899, 4.218, 4.58, 4.99, 5.455, 5.982, 6.578, 7.255, 8.021, 8.889

 

Using the Excel LOGEST function, I would expect a slope of 1.77018 and an intersect of 0.003762

giving an expected Y = 0.003762 * exp(1.77018 * X)

 

Using my class, I'm getting a slope of 1.17701774774 and an intersect of 0.824153597845

giving an expected Y = 0.824153597845 * exp(1.17701774774 * X)

 

The resulting best fit (returned as exp(X * slope + intersect)) has Y values of

2.47352819716, 2.6835418419, 2.91138658759, 3.15857637472, 3.42675368412, 3.71770045062, 4.03334990333, 4.37579941115, 4.74732441905, 5.15039356747, 5.58768509549, 6.06210463674, 6.57680452616, 7.13520474608, 7.74101565068, 8.39826261987

for my initial values of X

This matches my expected best fit curve when I plot it on the chart.

 

If I return the best fit as Y = intersect * exp(X * slope), then I get return values

0.894127936973, 0.970043411525, 1.05240445056, 1.14175831144, 1.23869871611, 1.34386979619, 1.45797037296, 1.58175860077, 1.71605700465, 1.86175794574, 2.0198295506, 2.19132214412, 2.37737522846, 2.57922505464, 2.79821283693, 3.03579366472

Clearly quite different, and nowhere near my expected curve

 

My bad on two counts, firstly for trusting LOGEST to give me correct results when the dataset doesn't include x=0 or x=1... I knew there were discrepancies between the trend values displayed in a chart and output by LOGEST, but didn't realise LOGEST itself returned relative intersect values based on the nearest datapoint to 0/1 rather than absolutes.

 

Second, it's not

Y = intersect * exp(slope * X)

for an exponential curve, it's

Y= intersect * slope^x;

 

For anybody who is interested, here's my corrected code for the class:

class exponentialBestFit {
private $_xValues			= array();

private $_yValues			= array();

private $_goodnessOfFit 	= 1;

private $_stdevOfResiduals	= 0;

private $_slope				= 0;

private $_increment			= 0;


public function getValueOfYForX($xValue) {
	return $this->getIntersect() * pow($this->getSlope(),$xValue);
}	//	function getValueOfYForX()

public function getSlope() {
	return exp($this->_slope);
}	//	function getSlope()

public function getIntersect() {
	return exp($this->_intersect);
}	//	function getIntersect()

public function getGoodnessOfFit() {
	return $this->_goodnessOfFit;
}	//	function getYPointFromSlopeIntersect()

public function getStdevOfResiduals() {
	return $this->_stdevOfResiduals;
}	//	function getStdevOfResiduals()

private function _calculateGoodnessOfFit() {
	//	Calculate number of points
	$nY = count($this->_yValues);

	$meanY = array_sum($this->_yValues) / $nY;

	$SSreg = $SStot = 0.0;
	foreach($this->_xValues as $xKey => $xValue) {
		$bestFitY = $this->getValueOfYForX($xValue);

		$SSreg += ($this->_yValues[$xKey] - $bestFitY) * ($this->_yValues[$xKey] - $bestFitY);
		$SStot += ($this->_yValues[$xKey] - $meanY) * ($this->_yValues[$xKey] - $meanY);
	}

	$this->_stdevOfResiduals = sqrt($SSreg / ($nY - 2));
	if (($SStot == 0.0) || ($SSreg == $SStot)) {
		$this->_goodnessOfFit = 1;
	} else {
		$this->_goodnessOfFit = 1 - ($SSreg / $SStot);
	}
}	//	function _calculateGoodnessOfFit()


private function _exponential_regression($yValues, $xValues=array()) {
	//	Calculate number of points
	$nY = count($yValues);
	$nX = count($xValues);

	//	Define X Values if necessary
	if ($nX == 0) {
		$xValues = range(1,$nY);
		$nX = $nY;
	}
	//	Ensure both arrays of points are the same size
	if ($nY != $nX) {
		trigger_error("exponential_regression(): Number of elements in coordinate arrays do not match.", E_USER_ERROR);
	}
	$this->_xValues = $xValues;
	$this->_yValues = $yValues;

	foreach($yValues as $key => $value) {
		$yValues[$key] = log($yValues[$key]);
	}

	// calculate sums
	$x_sum = array_sum($xValues);
	$y_sum = array_sum($yValues);
	$xx_sum = $xy_sum = 0;
	for($i = 0; $i < $nY; $i++) {
		$xy_sum += $xValues[$i] * $yValues[$i];
		$xx_sum += $xValues[$i] * $xValues[$i];
	}

	// calculate slope
	$this->_slope = (($nY * $xy_sum) - ($x_sum * $y_sum)) / (($nY * $xx_sum) - ($x_sum * $x_sum));
	// calculate intersect
	$this->_intersect = ($y_sum - ($this->_slope * $x_sum)) / $nY;

	$this->_calculateGoodnessOfFit();

	foreach($xValues as $xValue) {
		$this->_yBestFitValues[] = $this->getValueOfYForX($xValue);
	}
}	//	function _exponential_regression()


function __construct($yValues, $xValues=array()) {
	$this->_exponential_regression($yValues, $xValues);
}	//	function __construct()

}	//	class exponentialBestFit

 

I'm putting together a bestfit package. I already have linear, and now exponential, and am hoping to do logarithmic, powers and polynomials as well... so I'm sure I'll be back with more questions when my own understanding of the math gets a bit flakey.

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.