簡介
與其它開放原始碼語言(如Perl 和Python)相比,PHP 社群缺乏強而有力的工作來開發數學函式庫。
造成這種狀況的一個原因可能是由於已經存在大量成熟的數學工具,這可能阻礙了社區自行開發PHP 工具的工作。例如,我曾研究過一個功能強大的工具S System,它擁有一組令人印象深刻的統計庫,專門被設計成用來分析資料集,並且在1998 年由於其語言設計而獲得了ACM 獎。如果S 或其開放原始碼同類R 僅僅是一個exec_shell 調用,那麼為何還要麻煩用PHP 實現相同的統計計算功能呢?有關S System、它的ACM 獎或R 的更多信息,請參閱相關參考資料。
這不是在浪費開發人員的精力嗎?如果開發PHP 數學庫的動機是出於節省開發人員的精力以及使用最好的工具來完成工作,那麼PHP 現在的主題是很有意義的。
另一方面,出於教學動機可能會鼓勵對PHP 數學庫的發展。對大約10% 的人來說,數學是個值得探索的有趣課題。對於同時也熟練應用PHP 的人來說,PHP 數學庫的開發可以增強數學學習過程,換句話說,不要只閱讀有關T 測試的章節,還要實現一個能計算相應的中間值並用標準格式顯示它們的類別。
透過指導和訓練,我希望證明開發PHP 數學庫並不是一項很難的任務,它可能代表一項有趣的技術和學習難題。在本文中,我將提供一個PHP 數學庫範例,名為SimpleLinearRegression ,它示範了一個可以用來開發PHP 數學函式庫的通用方法。讓我們從討論一些通用的原則開始,這些原則指導我發展這個SimpleLinearRegression 類別。
指導原則
我使用了六個通用原則來指導SimpleLinearRegression 類別的開發。
每個分析模型建立一個類別。
使用逆向連結來開發類別。
預計有大量的getter。
儲存中間結果。
為詳細的API 制定首選項。
盡善盡美並非目標。
讓我們更詳細地逐條研究這些指導方針。
每個分析模型建立一個類別
每種主要的分析測試或過程應該有一個名稱與測試或過程名相同的PHP 類,這個類包含了輸入函數、計算中間值和匯總值的函數和輸出函數(將中間值和總計值以文字或圖形格式全部顯示在螢幕上)。
使用逆向連結來開發類別
在數學程式設計中,編碼的目標通常是分析過程(例如MultipleRegression 、 TimeSeries 或ChiSquared )所希望產生的標準輸出值。從解決問題的角度出發,這意味著您可以使用逆向連結來開發數學類的方法。
例如,總計輸出畫面顯示了一個或多個總計統計結果。這些總結統計結果依賴於中間統計結果的計算,這些統計結果可能會涉及到更深一層的中間統計結果,以此類推。這個基於逆向連結的開發方法導出了下一個原則。
預計有大量的getter
數學類別的大部分類別開發工作都涉及計算中間值和總和值。實際上,這意味著,如果您的類別包含許多計算中間值和總計值的getter 方法,您不應該感到驚訝。
儲存中間結果
將中間計算結果儲存在結果物件內,這樣您就可以將中間結果用作後續計算的輸入。在S 語言設計中實施了這項原則。在目前環境下,透過選擇實例變數來表示計算得到的中間值和總計結果,從而實施了該原則。
為詳細的API 制定首選項
當為SimpleLinearRegression 類別中的成員函數和實例變數制定命名方案時,我發現:如果我使用較長的名稱(類似於getSumSquaredError 這樣的名稱,而不是getYY2 )來描述成員函數和實例變量,那麼就更容易了解函數的操作內容和變數所代表的意義。
我沒有完全放棄簡寫名稱;但是,當我用簡寫形式的名稱時,我得設法提供註釋以完整闡述該名稱的含義。我的看法是:高度簡寫的命名方案在數學程式設計中很常見,但它們使得理解和證明某個數學程式是否按部就班更為困難,而原本不必造成此種困難。
盡善盡美並非目標
這個程式設計練習的目標不是一定要為PHP 開發高度優化和嚴格的數學引擎。在早期階段,應強調學習實現意義重大的分析測試,以及解決這方面的難題。
實例變數
當對統計測試或流程進行建模時,您需要指出聲明哪些實例變數。
實例變數的選擇可以透過說明由分析過程產生的中間值和總和值來決定。每個中間值和總計值都可以有一個對應的實例變量,將變數的值作為物件屬性。
我採用這樣的分析來確定為清單1 中的SimpleLinearRegression 類別聲明哪些變數。可以對MultipleRegression 、 ANOVA 或TimeSeries 過程執行類似的分析。
清單1. SimpleLinearRegression 類別的實例變數
<?php
// Copyright 2003, Paul Meagher
// Distributed under GPL
class SimpleLinearRegression {
var $n;
var $X = array();
var $Y = array();
var $ConfInt;
var $Alpha;
var $XMean;
var $YMean;
var $SumXX;
var $SumXY;
var $SumYY;
var $Slope;
var $YInt;
var $PredictedY = array();
var $Error = array();
var $SquaredError = array();
var $TotalError;
var $SumError;
var $SumSquaredError;
var $ErrorVariance;
var $StdErr;
var $SlopeStdErr;
var $SlopeVal; // T value of Slope
var $YIntStdErr;
var $YIntTVal; // T value for Y Intercept
var $R;
var $RSquared;
var $DF; // Degrees of Freedom
var $SlopeProb; // Probability of Slope Estimate
var $YIntProb; // Probability of Y Intercept Estimate
var $AlphaTVal; // T Value for given alpha setting
var $ConfIntOfSlope;
var $RPath = "/usr/local/bin/R"; // Your path here
var $format = "%01.2f"; // Used for formatting output
}
?>
建構子
SimpleLinearRegression 類別的建構子方法接受一個X和一個Y向量,每個向量都有相同數量的值。您也可以為您預期的Y值設定一個缺省為95% 的置信區間(confidence interval)。
構造函數方法從驗證資料形式是否適合處理開始。一旦輸入向量通過了「大小相等」和「值大於1」測試,就執行演算法的核心部分。
執行這項任務涉及透過一系列getter 方法計算統計過程的中間值和總和值。將每個方法呼叫的回傳值賦給該類別的一個實例變數。用這種方法儲存計算結果確保了前後連結的計算中的呼叫例程可以使用中間值和總計值。也可以透過呼叫該類別的輸出方法來顯示這些結果,如清單2 所描述的。
清單2. 呼叫類別輸出方法
<?php
// Copyright 2003, Paul Meagher
// Distributed under GPL
function SimpleLinearRegression($X, $Y, $ConfidenceInterval="95") {
$numX = count($X);
$numY = count($Y);
if ($numX != $numY) {
die("Error: Size of X and Y vectors must be the same.");
}
if ($numX <= 1) {
die("Error: Size of input array must be at least 2.");
}
$this->n = $numX;
$this->X = $X;
$this->Y = $Y;
$this->ConfInt = $ConfidenceInterval;
$this->Alpha = (1 + ($this->ConfInt / 100) ) / 2;
$this->XMean = $this->getMean($this->X);
$this->YMean = $this->getMean($this->Y);
$this->SumXX = $this->getSumXX();
$this->SumYY = $this->getSumYY();
$this->SumXY = $this->getSumXY();
$this->Slope = $this->getSlope();
$this->YInt = $this->getYInt();
$this->PredictedY = $this->getPredictedY();
$this->Error = $this->getError();
$this->SquaredError = $this->getSquaredError();
$this->SumError = $this->getSumError();
$this->TotalError = $this->getTotalError();
$this->SumSquaredError = $this->getSumSquaredError();
$this->ErrorVariance = $this->getErrorVariance();
$this->StdErr = $this->getStdErr();
$this->SlopeStdErr = $this->getSlopeStdErr();
$this->YIntStdErr = $this->getYIntStdErr();
$this->SlopeTVal = $this->getSlopeTVal();
$this->YIntTVal = $this->getYIntTVal();
$this->R = $this->getR();
$this->RSquared = $this->getRSquared();
$this->DF = $this->getDF();
$this->SlopeProb = $this->getStudentProb($this->SlopeTVal, $this->DF);
$this->YIntProb = $this->getStudentProb($this->YIntTVal, $this->DF);
$this->AlphaTVal = $this->getInverseStudentProb($this->Alpha, $this->DF);
$this->ConfIntOfSlope = $this->getConfIntOfSlope();
return true;
}
?>
方法名稱及其序列是透過結合逆向連結和參考大學本科學生使用的統計學教科書推導得出的,該教科書一步一步地說明瞭如何計算中間值。我需要計算的中間值的名稱帶有“get”前綴,從而推導出方法名。
使模型與資料相符
SimpleLinearRegression 過程用於產生與資料相吻合的直線,其中直線具有以下標準方程式:
y = b + mx
該方程式的PHP 格式看起來類似於清單3:
清單3. 使模型與資料相吻合的PHP 方程
$PredictedY[$i] = $YIntercept + $Slope * $X[$i]
SimpleLinearRegression 類別使用最小平方法準則推導出Y 軸截距(Y Intercept)和斜率(Slope)參數的估計值。這些估計的參數用來建構線性方程式(請參閱清單3),該方程式對X和Y值之間的關係進行建模。
使用推導出的線性方程,您就可以得到每個X值對應的預測Y值。如果線性方程式與數據非常吻合,那麼Y的觀測值與預測值趨近於一致。
如何確定是否非常吻合
SimpleLinearRegression 類別產生了相當多的總計值。一個重要的總和值是T統計值,它可以用來衡量一個線性方程式與資料的吻合程度。如果非常吻合,那麼T 統計值往往很大。如果T 統計值很小,那麼應用一個模型取代該線性方程,該模型假設Y值的平均值是最佳預測值(也就是說,一組值的平均值通常是下一個觀測值有用的預測值,使之成為缺省模型)。
要測試T 統計值是否大到足以不把Y值的平均值當作最佳預測值,您需要計算取得T 統計值的隨機機率。如果取得T 統計值的機率很低,那麼您可以否定平均值是最佳預測值這個無效假設,與此相對應,也就確信簡單線性模型與資料非常吻合。
那麼,如何計算T 統計值的機率呢?
計算T 統計值機率
由於PHP 缺少計算T 統計值機率的數學例程,因此我決定將此任務交給統計計算包R(請參閱參考資料中的www.r-project.org )來獲得必要的值。我還想提醒大家注意該套件,因為:
R 提供了許多想法,PHP 開發人員可能會在PHP 數學庫中模擬這些想法。
有了R,可以確定從PHP 數學庫獲得的值與那些從成熟的免費可用的開放源碼統計包中獲得的值是否一致。
清單4 中的程式碼示範了交給R 來處理以獲得一個值是多麼容易。
清單4. 交給R 統計計算包來處理以獲得一個值
<?php
// Copyright 2003, Paul Meagher
// Distributed under GPL
class SimpleLinearRegression {
var $RPath = "/usr/local/bin/R"; // Your path here
function getStudentProb($T, $df) {
$Probability = 0.0;
$cmd = "echo 'dt($T, $df)' | $this->RPath --slave";
$result = shell_exec($cmd);
list($LineNumber, $Probability) = explode(" ", trim($result));
return $Probability;
}
function getInverseStudentProb($alpha, $df) {
$InverseProbability = 0.0;
$cmd = "echo 'qt($alpha, $df)' | $this->RPath --slave";
$result = shell_exec($cmd);
list($LineNumber, $InverseProbability) = explode(" ", trim($result));
return $InverseProbability;
}
}
?>
請注意,這裡已經設定了到R 可執行檔的路徑,並在兩個函數中使用了該路徑。第一個函數根據學生的T 分佈傳回了與T 統計值相關的機率值,而第二個反函數計算了與給定的alpha 設定相對應的T 統計值。 getStudentProb 方法用來評估線性模型的吻合程度;getInverseStudentProb 方法傳回一個中間值,它用來計算每個預測的Y值的置信區間。
由於篇幅有限,我不可能逐一詳細說明這個類別中的所有函數,因此如果您想搞清楚簡單線性迴歸分析中所涉及的術語和步驟,我鼓勵您參考大學本科學生使用的統計學教科書。
燃耗研究
要示範如何使用該類,我可以使用來自公共事業中燃耗(burnout)研究中的數據。 Michael Leiter 和Kimberly Ann Meechan 研究了稱為消耗指數(Exhaustion Index)的燃耗度量單位和稱為集中度(Concentration)的獨立變數之間的關係。集中度是指人們的社交接觸中來自其工作環境的部分比例。
要研究他們樣本中個人的消耗指數值與集中度值之間的關係,請將這些值裝入適當命名的數組中,並用這些數組值對該類別進行實例化。將類別實例化後,顯示該類別所產生的某些總和值以評估線性模型與資料的吻合程度。
清單5 顯示了裝入
資料和顯示總計值的腳本:清單5. 用於裝入資料並顯示
總計值的腳本
<?php
// BurnoutStudy.php
// Copyright 2003, Paul Meagher
// Distributed under GPL
include "SimpleLinearRegression.php";
// Load data from burnout study
$Concentration = array(20,60,38,88,79,87,
68,12,35,70,80,92,
77,86,83,79,75,81,
75,77,77,77,17,85,96);
$ExhaustionIndex = array(100,525,300,980,310,900,
410,296,120,501,920,810,
506,493,892,527,600,855,
709,791,718,684,141,400,970);
$slr = new SimpleLinearRegression($Concentration, $ExhaustionIndex);
$YInt = sprintf($slr->format, $slr->YInt);
$Slope = sprintf($slr->format, $slr->Slope);
$SlopeTVal = sprintf($slr->format, $slr->SlopeTVal);
$SlopeProb = sprintf("%01.6f", $slr->SlopeProb);
?>
<table border='1' cellpadding='5'>
<tr>
<th align='right'>Equation:</th>
<td></td>
</tr>
<tr>
<th align='right'>T:</th>
<td></td>
</tr>
<tr>
<th align='right'>Prob > T:</th>
<td><td>
</tr>
</table>
透過Web 瀏覽器執行此腳本,產生以下輸出:
Equation: Exhaustion = -29.50 + (8.87 * Concentration)
T: 6.03
Prob > T: 0.000005
這張表的最後一行指出取得這樣大T值的隨機機率非常低。可以得出這樣的結論:與僅使用消耗值的平均值相比,簡單線性模型的預測能力更好。
知道了某個人的工作場所聯繫的集中度,就可以用來預測他們可能正在消耗的燃耗程度。這個方程式告訴我們:集中度值每增加1 個單位,社會服務領域中一個人的消耗值就會增加8 個單位。這進一步證明了:要減少潛在的燃耗,社會服務領域中的個人應考慮在其工作場所之外結交朋友。
這只是粗略地描述了這些結果可能表示的含義。為全面研究這個數據集的含義,您可能想更詳細地研究這個數據以確信這是正確的解釋。在下一篇文章中我將討論應執行其它哪些分析。
您學到了什麼?
其一,要開發意義重大的基於PHP 的數學包,您不必是火箭科學家。堅持標準的物件導向技術,並且明確地採用逆向連結問題解決方法,就可以相對方便地使用PHP 來實現某些較為基本的統計過程。
從教學的觀點出發,我認為:如果只是因為要求您在較高和較低的抽象層次思考統計測驗或例程,那麼這個練習是非常有用的。換句話說,補充您的統計測試或流程學習的一個好方法是將這個過程作為演算法實現。
要實現統計測試通常需要超出所給定資訊的範圍並創造性地解決和發現問題。對於發現對某個學科認識的不足而言,它也是一個好方法。
不利的一面,您發現PHP 對於取樣分佈缺乏內在手段,而這是實現大多數統計測試所必需的。您需要交給R 來處理以取得這些值,但是我擔心您會沒時間或沒興趣安裝R。某些常見機率函數的本機PHP 實作可以解決這個問題。
另一個問題是:該類別產生許多中間值和匯總值,但是匯總輸出實際上並沒有利用這一點。我提供了一些難處理的輸出,但這既不夠充分也沒進行很好的組織,以致您無法充分地解釋分析結果。實際上,我完全不知道如何可以將輸出方法整合到該類別中。這需要解決。
最後,要弄清楚數據,不只是察看匯總值就可以了。您還需要明白各個數據點是如何分佈的。最好的方法之一是將您的數據繪製成圖表。再次聲明,我對這方面不太了解,但是如果要用這個類別來分析實際資料的話就需要解決這個問題。