這裡介紹如何在 PHP 的程式中呼叫 R 語言進行各式的統計分析語資料繪圖,並將結果顯示於網頁之中。
PHP 是現今很熱門的程式語言之一,有非常多的網頁應用程式都是使用 PHP 來開發的(例如 WordPress 等),而 R 語言則是一種功能強大的統計分析工具,拜大資料的風潮所賜,目前 R 語言已經是資料科學領域最熱門的分析工具。
如果您想要開發一個線上分析資料的工具,網頁部份可以使用 PHP、HTML、JavaScript 與 CSS 等傳統技術來處理,而資料的統計分析與繪圖則可以借重 R 語言來解決,以下是常見的幾種 PHP 與 R 語言的整合方式與範例程式碼。
PHP 執行外部 R 指令稿
建立一個 PHP 指令稿,將其命名為 r.php
,內容如下:
<html><body> <form action='r.php' method='get'> 輸入 N 值: <input type='text' name='n' /> <input type='submit' /> </form> <?php if(isset($_GET['n'])) { $n = $_GET['n']; // 以外部指令的方式呼叫 R 進行繪圖 exec("Rscript script.R $n"); // 產生亂數 $nocache = rand(); // 輸出圖檔 echo("<img src='output/hist.png?$nocache' />"); } ?> </body></html>
上面這段程式碼的上半部是一個普通的 HTML form,可以用來送出使用者所輸入的參數,而下半部則是 PHP 的程式碼,在接收使用者輸入的 n
值之後,透過 PHP 的 exec
執行外部程式,而 Rscript
這個程式則是附屬在 R 中的一個程式,只要安裝好 R 之後系統上就會有這個程式,它是專門用來執行 R 指令稿的工具程式。
最後在執行完 R 指令稿之後,要顯示繪圖的結果,由於我們每一次所繪製的圖檔檔名都一樣,所以需要在圖檔後方加上一串亂數,強迫讓瀏覽器重新抓取新的圖檔(也就是不要使用瀏覽器的快取),這樣每次送出新的 n
值才會顯示新的結果。
以下是 script.R
這一個 R 指令稿的內容:
args <- commandArgs(TRUE) # 取得使用者輸入的 N 值 n <- args[1] # 產生資料 x <- rnorm(n, 0, 1) # 繪製直方圖 png(filename="output/hist.png", width = 500, height = 300) hist(x, col = "orange") dev.off()
在 R 中我們透過 commandArgs
取得從 shell 中傳入的參數,其中第一個參數就是使用者輸入的 n
值,藉由這樣的方式就可以取得從 PHP 傳過來的資料。接著產生一些常態分佈的亂數資料,並繪製一張直方圖,我們將圖形儲存至 output
這個目錄中,然後再讓網頁直接讀取這個圖檔,這樣就可以將結果傳給使用者。
這裡我是規劃 output
目錄專門用來放置輸出的圖檔,由於 R 的指令稿會以執行網頁伺服器的使用者(在 Ubuntu Linux 中通常是 www-data
)權限來執行,所以請注意目錄權限的設定,要讓伺服器有權限可以寫入這個目錄。
執行的結果會像這樣:
PHP 開啟管線執行 R 指令稿
以 PHP 的 exec
執行外部的 R 指令稿是一個比較簡單的方式,不過缺點就是它需要另外建立一個單獨的 R 指令稿,如果不想要另外建立一個 R 檔案,可以改用 proc_open
的方式,直接把 R 的指令從 PHP 中透過 Linux 的管線(pipe)寫到 R 的行程(process)中,這樣就可以省去建立 R 檔案的麻煩,以下是一個簡單的範例:
<html><body> <form action='r.php' method='get'> 輸入 N 值: <input type='text' name='n' /> <input type='submit' /> </form> <?php if(isset($_GET['n'])) { $n = $_GET['n']; $descriptorspec = array( 0 => array("pipe", "r"), // stdin 1 => array("file", "/tmp/output.txt", "w"),// stdout 2 => array("file", "/tmp/error.txt", "w") // stderr ); // 以管線的方式執行 R 指令稿進行繪圖 $rproc = proc_open("R --vanilla", $descriptorspec, $pipes); if (is_resource($rproc)) { fwrite($pipes[0], "x <- rnorm($n, 0, 1);"); fwrite($pipes[0], "png(filename='output/hist.png', width = 500, height = 300);"); fwrite($pipes[0], "hist(x, col = 'orange');"); fwrite($pipes[0], "dev.off();"); fclose($pipes[0]); proc_close($rproc); // 產生亂數 $nocache = rand(); // 輸出圖檔 echo("<img src='output/hist.png?$nocache' />"); } } ?> </body></html>
這個範例我們利用 proc_open
從 PHP 中開啟一個 R 的行程,在開啟新的行程之前,要先以 $descriptorspec
設定好新行程的標準輸入、標準輸出與標準錯誤,此處我們將新 R 行程的標準輸入指定為管線,方便我們直接從 PHP 寫入資料,而 R 的輸出與錯訊息則是導入兩個暫存檔中,通常在開發階段這樣可以方便我們檢視程式是否有正確執行,除錯也比較方便。
將 R 的指令都寫入 R 的行程之後,在呼叫 proc_close
關閉 R 行程之前,要記得先將所有的管線關閉,避免造成 deadlock。最後一樣照舊將圖檔顯示在網頁上,不管是使用 proc_close
還是 exec
來整合 PHP 與 R,顯示出來的效果看起來都相同,只有內部的程式結構上有些差異而已。
參考資料:R-bloggers