函数内部定义的函数称为闭包(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 估计这两个参数的值。

首先,均值为 μ ,标准差为 σ 的正态分布的密度函数为:

创建和使用闭包_似然函数_02


因此,给定观测数据 x ,其似然函数为:

创建和使用闭包_正态分布_03


为了简化计算,我们对似然函数取自然对数,并在等式两边添加负号,得到负对数似

然函数:

创建和使用闭包_数据_04


负对数似然函数与原函数单调性相同,所以其最优化的解也与原函数相同,但求解过

程却简单得多。因此在 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 所示。

创建和使用闭包_数据_05


图 9-2

可以看到用估计参数画出的密度曲线和真实模型非常接近。