函數內部定義的函數稱爲閉包(closure)。閉包的特色是在其函數體中,可使用局部
參數,也可使用其父環境中的變量。
舉個例子,假設咱們有以下函數:
add <- function(x, y) {
x + y
}
此函數有兩個參數。每次調用 add( ) 函數時,都須要提供這兩個參數。若是使用閉
包,就能夠生成帶有事先指定參數的特殊版本。在下一節中,咱們將建立一個簡單的閉包。
1.建立一個簡單的閉包
如今咱們建立一個名爲 addn( ),包含一個參數 y 的函數。此函數不執行加法運算,
而是在其內部建立一個子函數,並將 y 加到其參數 x 上:
addn <- function(y) {
function(x) {
x + y
}
}
這裏你們可能須要多想幾遍才能理解,addn( ) 函數並不會像通常函數那樣返回一個
數值,而是返回一個閉包,即定義在一個函數內部的函數。此閉包計算 x+y 的值,其中 x 是
局部參數,y 是其封閉環境中的參數。換句話說,addn( ) 並非一個「計算器」,而是
一個產生「計算器」的「計算器工廠」。
函數工廠使咱們可以建立專用的計算函數。例如,能夠建立兩個函數,給一個數值向
量分別加上 1 和 2:
add1 <- addn(1)
add2 <- addn(2)
這兩個函數的做用就至關於 add(x, y) 的第 2 個參數 y 分別固定爲 1 和 2。下面的
代碼證明了 addn( ) 函數生成的「計算器」的做用:
add1(10)
## [1] 11
add2(10)
## [1] 12
以 add1( )爲例。代碼 add1<-addn(1) 是指執行 addn(1),並將生成的函數賦給 add1:
add1
## function(x) {
## x + y
## }
## <environment: 0x00000000139b0e58>
當咱們輸出 add1 時,與其餘函數有點不一樣的是,輸出結果中附帶了 add1 的環境。如
果一個函數不在當前環境(本例中爲全局環境)下,那麼輸出該函數時就會一併顯示其所屬
的環境。在 add1 的環境中,y 是在 addn(1) 中肯定的,咱們能夠運行以下代碼來證明:
environment(add1)$y
## [1] 1
咱們能夠對 add1 調用 environment( ) 函數來訪問其封閉環境,並捕獲到 y。這
個過程就是閉包的工做方式。一樣地,咱們也能夠對 add2 調用 environment( ) 函數,
查看 addn(2) 中肯定的 y 的值:
environment(add2)$y
## [1] 2
2.建立專用函數
閉包對於建立專用函數很是有用。例如,鑑於繪圖的靈活性,繪圖函數一般須要提供
許多參數。若是大多數狀況下,只使用部分參數,就能夠建立一個專用的簡版函數,這樣
可使代碼更容易編寫和閱讀。
下面這個 color_line( ) 函數就是固定了圖形和線條類型參數的 plot( ) 函數的
簡易版本,專門用來選擇繪圖顏色的函數。它就像一個能夠造出全部顏色畫筆的工廠:
color_line <- function(col) {
function(...) {
plot(..., type = "l", lty = 1, col = col)
}
}
若是咱們想要一支紅色的畫筆,就能夠調用 color_line( ) 函數生成一個專門畫紅
色線條的函數。此函數一樣能夠設置其餘參數,諸如標題和字體等:
red_line <- color_ _line("red")
red_ _line(rnorm(30), main = "Red line plot")
該函數生成的折線圖如圖 9-1 所示。算法
圖 9-1
相比於不使用專用函數的原版函數,上述代碼看起來可讀性更高一些:
plot(rnorm(30), type = "l", lty = 1, col = "red",
main = "Red line plot")
3.用極大似然估計擬合正態分佈
在使用一個含有給定數據的算法時,閉包是頗有用的。例如,最優化問題就是在給定
約束條件和數據,找出使目標函數最大化或最小化的一組參數。在統計學中,不少參數估
計問題本質上就是最優化問題。極大似然估計(maximum likelihood estimation,MLE)
是演示閉包的一個很好的例子。用數據估計統計模型的參數時,咱們常用極大似然估
計(MLE,參見 https://en.wikipedia.org/wiki/Maximum_likelihood)。MLE 的想法很簡單:
給定一個模型,參數的估計值應使觀測數據最有可能發生。
對參數進行極大似然估計時,咱們須要一個函數來衡量在給定模型下觀測到一組給定
數據的可能性,而後運用最優化技術找出使上述機率最大化的參數值。
例如咱們知道一組由正態分佈產生的觀測數據,可是不知道其參數:均值和標準差。
這裏根據給出的數據,用 MLE 估計這兩個參數的值。
首先,均值爲 μ ,標準差爲 σ 的正態分佈的密度函數爲:閉包
所以,給定觀測數據 x ,其似然函數爲:函數
爲了簡化計算,咱們對似然函數取天然對數,並在等式兩邊添加負號,獲得負對數似
然函數:字體
負對數似然函數與原函數單調性相同,因此其最優化的解也與原函數相同,但求解過
程卻簡單得多。所以在 MLE 中一般使用對數似然函數來求解。
下面定義一個 nloglik( )函數,給定觀測數據 x,該函數返回一個包含正態分佈的
兩個參數的閉包:
nloglik <- function(x) {
n <- length(x)
function(mean, sd) {
log(2 * pi) * n /2 + log(sd ^2) * n / 2 +sum((x - mean) ^2) /(2 * sd ^2)
}
}
這樣對於任何給定的觀測數據集,咱們均可以調用 nloglik( ) 函數,獲得參數爲均
值和標準差的負對數似然函數。它說明了在假定真實模型的兩個參數分別爲 mean 和
sd 時,不能觀測到給定數據的可能性有多大。
例如,咱們用rnorm( )生成 10000 個均值爲 1 ,標準差爲 2 的正態分佈隨機數。mean =
1 和 sd = 2 就是該分佈參數的真實值:
data <- rnorm(10000, 1, 2)
而後,調用 stats4 包中的 mle( )函數。此函數可以使用多種數值方法求解給定參數
的負對數似然函數的最小值。須要爲它設定一個數值搜索的起點和解的上下界:
fit <- stats4::mle(nloglik(data),
start = list(mean = 0, sd = 1), method = "L-BFGS-B",
lower = c(-5, 0.01), upper = c(5, 10))
通過屢次迭代後,函數找到一個 MLE 解,並返回一個 S4 對象,其中包括解的相關數
據。要查看估計值與真實值的差距,能夠從對象中提取參數 coef:
fit@coef
## mean sd
## 1.007548 1.990121
顯然,估計值很是接近真實值。相對而言,咱們能夠證明兩個估計值都有不到 1% 的
偏差:
(fit@coef -c(1, 2)) /c(1, 2)
## mean sd
## 0.007547752 -0.004939595
如下代碼的結果給出了數據直方圖、使用真實參數的正態密度曲線(紅線)和使用估
計參數的正態密度曲線(藍線)的組合圖:
hist(data, freq = FALSE, ylim = c(0, 0.25))
curve(dnorm(x, 1, 2), add = TRUE, col = rgb(1, 0, 0, 0.5), lwd = 6)
curve(dnorm(x, fit@coef[["mean"]], fit@coef[["sd"]]),
add = TRUE, col = "blue", lwd = 2)
結果生成了一個直方圖,而且添加了擬合正態密度曲線,如圖 9-2 所示。優化
圖 9-2
能夠看到用估計參數畫出的密度曲線和真實模型很是接近。orm