這裡介紹如何在 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)權限來執行,所以請注意目錄權限的設定,要讓伺服器有權限可以寫入這個目錄。

執行的結果會像這樣:

integrating-php-and-r-20161101-1

PHP 呼叫 R 的網頁

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