• 52.2.1 evalCpp() 转换单一计算表达式
  • 52.2.2 cppFunction() 转换简单的C++函数—Fibnacci例子
  • 52.2.3 sourceCpp() 转换C++程序—正负交替迭代例子
  • 52.2.4 sourceCpp() 转换C++源文件中的程序—正负交替迭代例子
  • 52.2.5 sourceCpp() 转换C++源程序文件—卷积例子
  • 52.2.6 随机数例子
  • 52.2.7 bootstrap例子
  • 52.2.8 在Rmd文件中使用C++源程序文件
  • 53 R与C++的类型转换
  • 53.1 wrap() 把C++变量返回到R中
  • 53.2 as() 函数把R变量转换为C++类型
  • 53.3 as() wrap() 的隐含调用
  • 54 Rcpp 属性
  • 54.1 Rcpp属性介绍
  • 54.2 在C++源程序中指定要导出的C++函数
  • 54.2.1 特殊注释 //[[Rcpp::export]]
  • 54.2.2 修改导出的函数名
  • 54.2.3 可导出的函数
  • 54.3 在R中编译链接C++代码
  • 54.3.1 sourceCpp() 函数中直接包含C++源程序字符串
  • 54.3.2 cppFunction() 函数中直接包含C++函数源程序字符串
  • 54.3.3 evalCpp() 函数中直接包含C++源程序表达式字符串
  • 54.3.4 depends 指定要链接的库
  • 54.4 Rcpp属性的其它功能
  • 54.4.1 自变量有缺省值的函数
  • 54.4.2 异常传递
  • 54.4.3 允许用户中断
  • 54.4.4 把R代码写在C++源文件中
  • 54.4.5 invisible 要求函数结果不自动显示
  • 54.4.6 在C++中调用R的随机数发生器
  • 55 Rcpp提供的C++数据类型
  • 55.1 RObject类
  • 55.2 IntegerVector类
  • 55.2.1 IntegerVector示例1:返回完全数
  • 55.2.2 IntegerVector示例2:输入整数向量
  • 55.3 NumericVector类
  • 55.3.1 示例1:计算元素 \(p\) 次方的和
  • 55.3.2 示例2: clone 函数
  • 55.3.3 示例3:向量子集
  • 55.4 NumericMatrix类
  • 55.4.1 示例1:计算矩阵各列模的最大值
  • 55.4.2 示例2:把输入矩阵制作副本计算元素平方根
  • 55.4.3 示例3:访问列子集
  • 55.5 Rcpp的其它向量类
  • 55.5.1 Rcpp的LogicalVector类
  • 55.5.2 Rcpp的CharacterVector类型
  • 55.6 Rcpp提供的其它数据类型
  • 55.6.1 Named类型
  • 55.6.2 List类型
  • 55.6.3 Rcpp的DataFrame类
  • 55.6.4 Rcpp的Function类
  • 55.6.5 Rcpp的Environment类
  • 56 Rcpp糖
  • 56.1 简单示例
  • 56.2 向量化的运算符
  • 56.2.1 向量化的四则运算
  • 56.2.2 向量化的赋值运算
  • 56.2.3 向量化的二元逻辑运算
  • 56.2.4 向量化的一元运算符
  • 56.3 用Rcpp访问数学函数
  • 56.4 用Rcpp访问统计分布类函数
  • 56.5 在Rcpp中产生随机数
  • 56.6 返回单一逻辑值的函数
  • 56.7 返回糖表达式的函数
  • 56.7.1 is_na
  • 56.7.2 seq_along
  • 56.7.3 seq_len
  • 56.7.4 pmin pmax
  • 56.7.5 ifelse
  • 56.7.6 sapply lapply
  • 56.7.7 sign
  • 56.7.8 diff
  • 56.8 R与Rcpp不同语法示例
  • 56.9 用RcppArmadillo执行矩阵运算
  • 56.9.1 生成多元正态分布随机数
  • 56.9.2 快速计算线性回归
  • 57 用Rcpp帮助制作R扩展包
  • 57.1 不用扩展包共享C++代码的方法
  • 57.2 生成扩展包
  • 57.2.1 利用已有基于Rcpp属性的源程序制作扩展包
  • 57.2.2 DESCRIPTION文件
  • 57.2.3 NAMESPACE文件
  • 57.3 重新编译
  • 57.4 建立C++用的接口界面
  • XI 其它
  • 58 R编程例子
  • 58.1 R语言
  • 58.1.1 用向量作逆变换
  • 58.1.2 斐波那契数列计算
  • 58.1.3 穷举所有排列
  • 58.1.4 可重复分组方式穷举
  • 58.1.5 升降连计数
  • 58.1.6 高斯八皇后问题
  • 58.1.7 最小能量路径
  • 58.2 概率
  • 58.2.1 智者千虑必有一失
  • 58.2.2 圆桌夫妇座位问题
  • 58.3 科学计算
  • 58.3.1 城市间最短路径
  • 58.3.2 Daubechies小波函数计算
  • 58.3.3 房间加热温度变化
  • 58.4 统计计算
  • 58.4.1 线性回归实例
  • 58.4.2 核回归与核密度估计
  • 58.4.3 二维随机模拟积分
  • 58.4.4 潜周期估计
  • 58.4.5 ARMA(1,1)模型估计
  • 58.4.6 VAR模型平稳性
  • 58.4.7 贮存可靠性评估
  • 58.5 数据处理
  • 58.5.1 小题分题型分数汇总
  • 58.5.2 类别编号重排
  • 58.6 文本处理
  • 58.6.1 用R语言下载处理《红楼梦》htm文件
  • 59 使用经验
  • 59.1 文件管理
  • 59.1.1 工作空间
  • 59.2 程序格式
  • A R Markdown文件格式
  • A.1 R Markdown文件
  • A.2 R Markdown文件的编译
  • A.2.1 编译的实际过程
  • A.3 在R Markdown文件中插入R代码
  • A.4 输出表格
  • A.5 利用R程序插图
  • A.6 冗余输出控制
  • A.7 代码段选项
  • A.7.1 代码和文本输出结果格式
  • A.7.2 图形选项
  • A.7.3 缓存(cache)选项
  • A.8 章节目录链接问题
  • A.9 其它编程语言引擎
  • A.10 交互内容
  • A.11 属性设置
  • A.11.1 YAML元数据
  • A.11.2 输出格式
  • A.11.3 输出格式设置
  • A.11.4 目录设置
  • A.11.5 章节自动编号
  • A.11.6 Word输出章节自动编号及模板功能
  • A.11.7 HTML特有输出格式设置
  • A.11.8 关于数学公式支持的设置
  • A.11.9 输出设置文件
  • A.12 LaTeX和PDF输出
  • A.12.1 TinyTex的安装使用
  • A.12.2 Rmd中Latex设置
  • A.13 生成期刊文章
  • A.14 附录:经验与问题
  • A.14.1 Word模板制作
  • A.14.2 数学公式设置补充
  • B 用bookdown制作图书
  • B.1 介绍
  • B.2 一本书的设置
  • B.3 章节结构
  • B.4 书的编译
  • B.5 交叉引用
  • B.6 数学公式和公式编号
  • B.7 定理类编号
  • B.8 文献引用
  • B.9 插图
  • B.10 表格
  • B.10.1 Markdown表格
  • B.10.2 kable() 函数制作表格
  • B.10.3 R中其它制作表格的包
  • B.11 数学公式的设置
  • B.12 使用经验
  • B.12.1 学位论文
  • B.12.2 LaTeX
  • B.12.3 算法
  • B.12.4 中文乱码
  • B.12.5 图片格式
  • B.12.6 其它经验
  • B.13 bookdown的一些使用问题
  • C 用R Markdown制作简易网站
  • C.1 介绍
  • C.2 简易网站制作
  • C.2.1 网站结构
  • C.2.2 编译
  • C.2.3 内容文件
  • C.2.4 网站设置
  • C.3 用blogdown制作网站
  • C.3.1 生成新网站的框架
  • C.3.2 网页内容文件及其设置
  • C.3.3 初学者的工作流程
  • C.3.4 网站设置文件
  • C.3.5 静态文件
  • D 制作幻灯片
  • D.1 介绍
  • D.2 Slidy幻灯片
  • D.2.1 文件格式
  • D.2.2 幻灯片编译
  • D.2.3 播放控制
  • D.2.4 生成单页HTML
  • D.2.5 数学公式处理与输出设置文件
  • D.2.6 其它选项
  • D.2.7 slidy幻灯片激光笔失效问题的修改
  • D.3 MS PowerPoint幻灯片
  • D.4 Bearmer幻灯片格式
  • D.5 R Presentation格式
  • References
  • 编著:李东风
  • \(f(x) = a + b x\) : 线性回归;
  • \(f(x) = a + b x + c x^2\) : 二次多项式回归;
  • \(f(x) = A e^{bx}\) : 指数模型,等等。
  • 二次多项式回归可以令 \(X_1 = x, X_2 = x^2\) , 变成二元回归模型来解决。 指数模型可以令 \(z = \ln Y\) , 模型化为 \(z = a + bx\) 。 有一些曲线模型可以通过变换化为线性回归。

    在多元情形, 一般的非线性回归模型为 Y = f(x_1, x_2, \dots, x_p) + \varepsilon 但是这样可选的模型就过于复杂, 难以把握。 比较简单的是不考虑变量之间交互作用的可加模型:

    Y = \sum_{j=1}^p f_j(x_j) + \varepsilon 其中 \(f_j(\cdot)\) 是未知的回归函数, 需要满足一定的光滑性条件。

    \(f_j(\cdot)\) 可以是参数形式的, 比如二次多项式、三次多项式、阶梯函数等。 较好的一种选择是使用三次样条函数。

    所谓参数回归, 是指回归函数 \(f(\cdot)\) 有预先确定的公式, 仅需要估计 \(f(\cdot)\) 的未知参数; 非参数回归, 就是 \(f(\cdot)\) 没有预先确定的公式, \(f(\cdot)\) 的形式本身也依赖于输入的样本 \((x_i, y_i)\) , \(i=1,2,\dots,n\) 。 下面描述的核回归就是这样典型的非参数回归, 样条平滑、样条函数回归一般也看作是非参数回归。

    35.2 样条平滑

    为了得到一般性的 \(Y\) \(X\) 的曲线关系 \(f(x)\) 的估计, 可以使用样条函数。 三次样条函数将实数轴用若干个 节点 (knots) \(\{ z_k \}\) 分成几段, 每一段上 \(\hat f(x)\) 为三次多项式, 函数在节点处有连续的二阶导数。 样条函数是光滑的分段三次多项式。 通常我们使用“自然样条函数”, 在最左边和最右边这两段为线性函数。

    用样条函数估计 \(f(x)\) 的准则是曲线接近各观测值点 \((x_i, y_i)\) ,同时曲线足够光滑。

    在R中用 stats::smooth.spline 函数进行样条曲线拟合。 取每个自变量 \(x_i\) 处为一个节点, 对于给定的某个光滑度/模型复杂度系数值 \(\lambda\) , 求函数 \(f(x)\) 使得 \[\begin{align} \min_{f(\cdot)} \left\{ \sum_i [y_i - f(x_i)]^2 + \lambda \int [f''(x)]^2 \,dx \right\} \tag{35.1} \end{align}\] 目标函数中第一项越小, 拟合误差越小, 但是也越容易产生过度拟合; 目标函数中第二项度量了函数 \(f(\cdot)\) 的不光滑性, 是一个对模型复杂度的惩罚项, \(\lambda\) 为调节参数, \(\lambda\) 越大, 惩罚越重,所得的曲线越光滑。 smooth.spline() 函数可以通过交叉验证方法自动取得一个对于预测最优的光滑参数 \(\lambda\) 值。

    问题 (35.1) 的理论解称为光滑样条, 是一个自然样条函数, 节点为输入的 \(x_i\) 的所有不同值。 此理论解与§ 35.4 直接指定这些节点进行样条函数回归的结果不同, 是依赖于调节参数 \(\lambda\) 进行了不光滑性惩罚的结果。 调节参数 \(\lambda\) 可以用交叉验证方法选择, 只要在 smooth.spline() 函数中指定 cv=TRUE 。 也可以指定 spar 选项或者 df 选项, 这里 df 代表“等效自由度”, 是模型复杂度的一个度量方式。

    下面的程序模拟产生了一个非线性回归数据集, 然后用 stats::smooth.spline() 进行样条平滑估计:

    图35.1: 曲线平滑示例

    其中res是 stats::smooth.splines() 的输出, 其元素y为拟合值, 元素x为拟合值对应的自变量值。

    R函数 spline(x,y) 不是做样条平滑, 而是做样条插值, 它输入一组x, y坐标, 用样条插值算法在原始的自变量 x 范围内产生等间隔距离的格子点值, 输出包含格子点上的样条插值 x y 坐标的列表。 样条平滑曲线不需要穿过输入的各个散点, 但是插值则需要穿过输入的各个散点。

    R函数 approx(x,y) 是另一个插值函数, 它用线性插值方法产生线性插值后在等间隔的横坐标上的坐标值。

    35.3 局部多项式曲线平滑

    另一种非参数的拟合非线性回归曲线 \(f(x)\) 的方法是对每个自变量 \(x\) 处选一个局部的小邻域, 用这个小邻域附近的 \((x_i, y_i)\) 点拟合一个局部的零次、一次或二次多项式, 用拟合的局部多项式在 \(x\) 处的值作为回归函数在 \(x\) 处的值的估计。

    局部零次多项式的方法叫做核回归, \hat f(x) = \frac{\sum\limits_{i = 1}^n {K\left( {\frac{{x - X_i }}{h}} \right)Y_i } } {\sum\limits_{i = 1}^n {K\left( {\frac{{x - X_i }}{h}} \right)} } 其中 \(K(\cdot)\) 称为核函数, 是类似标准正态密度这样的中间高两边低的可以用来加权的函数, 双三次核函数: K(x) = \left\{ {\begin{array}{*{20}c} {\left( {1 - \left| x \right|^3 } \right)^3 } & {\left| x \right| \leq 1} \\ 0 & \mbox{其它} \\ \end{array}} \right.

    图35.2: 双三次核函数

    参数 \(h\) 称为窗宽, \(h\) 越大,参与平均的点越多, 曲线越光滑, 回归复杂度越低。 \(h\) 选取可以用交叉验证方法。

    R扩展包KernSmooth的函数 locpoly(x, y, degree=1, bandwidth=0.25) 可以计算核回归, 其中 bandwidth 是输入的窗宽, 函数 dpill(x,y) 可以帮助确定窗宽。 locpoly(x, y, degree=1, bandwidth=dpill(x,y)) 。 stats包的 ksmooth() 函数也可以计算核回归。

    当局部拟合的是一次或者二次多项式时, 这种曲线拟合方法叫做局部多项式回归。 对每个自变量 \(x\) 的值, 给每对输入自变量、因变量 \((x_i, y_i)\) 加权重 \(\frac{1}{h} K(\frac{x-x_i}{h})\) , 然后拟合加权的线性回归或者二次多项式回归, 得到 \(x\) 处的回归函数值作为 \(f(x)\) 的估计。 如果核函数 \(K(\cdot)\) 是紧支集函数(仅在一个闭区间上非零), 则每个 \(x\) 处仅距离 \(x\) 较近的观测值的权重非零。 实际计算时, 可以对 \(\{x_i \}\) 取值范围内的一个等间隔格子点集合的 \(x\) 计算 \(f(x)\) 的估计值。

    R函数 loess(y ~ x, degree=1, span=0.75) 拟合局部线性函数, 用 span= 控制结果的光滑度,起到了类似窗宽 \(h\) 的作用。 span 是局部拟合所用的点的比例, 取值于 \([0, 1]\) 内, 越接近于1结果越光滑。 可以用指定 span , 也可以用交叉验证方法确定 span , 但 loess() 函数没有提供交叉验证功能。 取 degree=2 拟合局部二次多项式函数。 例子见图 35.1

    35.4 样条函数回归

    可以用指定节点集合的样条函数作为回归函数估计。 \(m\) 个节点的三次样条函数需要 \(m+4\) 个参数。 因为每段需要 \(4\) 个参数, \(m+1\) 段需要 \(4m+4\) 个参数, 而在 \(m\) 个节点上连续、一阶导数连续、二阶导数连续构成三个约束条件, 所以参数个数为 \(m+4\) 个。

    自然样条函数假定函数在最左边一段和最右边一段为线性函数, 这样 \(m\) 个节点需要 \(m+2\) 个参数。 R的 lm() 函数中对自变量 x 指定 ns(x) 可以对输入的 x 指定作自然样条变换, 在 ns() 中可以用 df= 指定自由度作为曲线复杂度的度量。

    给定输入 \((x_i, y_i)\) , \(i=1,2,\dots,n\) 后如何估计函数 \(f(\cdot)\) ? 计算时,用了基函数的思想将问题转化成了线性模型。 根据 df 的值可以确定样条函数节点个数 \(K\) , 然后可以选择 \(K\) 个节点 \(z_k, k=1,2,\dots,K\) , = \beta_0 + \beta_1 x + \beta_2 x^2 + \beta_3 x^3 + \sum_{k=1}^K \beta_{k+3} h(x, z_k), h(x, z) = \begin{cases} (x - z)^3, & x > z, \\ 0, & x \leq z . \end{cases} 这样就可以用线性模型的估计方法估计出未知参数 \(\beta_0, \dots, \beta_{K+3}\) , 从而得到样条函数估计。

    如何决定节点个数 \(K\) 以及节点位置? 节点越多, 函数 \(f(\cdot)\) 越复杂,计算量也越大。 lm() ns() bs() 函数可以用 df= 选项指定一个等效自由度, 等效自由度越大, 模型越复杂, 曲线光滑程度越低, df 值相当于多元线性回归中的自变量个数, 决定了节点的个数。 df=4 的复杂度类似于有4个自变量的线性回归模型, 它有3个内部节点, 将x轴分为4段, 拟合结果构成一个分4段的自然样条函数。 不人为指定时, 也可以通过交叉验证方法得到 df 的值。

    节点的位置最好在回归曲线变化剧烈的位置多取, 在变化缓慢的地方少取, 但是这个要求很难实现, 所以实际上是在输入的 \(x\) 变量值中取等分位间隔的 df 个点。

    例如,下面的程序中用了自由度为4和自由度为8的样条函数进行回归估计:

    图35.3: 自然样条回归示例

    在多元回归中也可以用 ns(x) 对单个自变量引入非线性。

    35.5 线性可加模型

    虽然在用 lm() 作多元回归时可以用 ns() poly() 等函数引入非线性成分, 但需要指定复杂度参数。 对可加模型 Y = \sum_{j=1}^p f_j(x_j) + \varepsilon 最好能从数据中自动确定 \(f_j(\cdot)\) 的复杂度(光滑度)参数。

    R扩展包mgcv的 gam() 函数可以执行这样的可加模型的非参数回归拟合。 模型中可以用 s(x) 指定 x 的样条平滑, 用 lo(x) 指定 x 的局部多项式平滑。 具体的计算是迭代地对每个自变量 \(x_j\) 进行, 估计 \(x_j\) 的平滑函数 \(f_j(\cdot)\) 时, 采用数据 \(\tilde y = Y - \sum_{k \neq j} f_k(x_k)\) , 迭代估计到结果基本不变为止。

    可加模型的优点是可以对多个自变量进行非线性的模型估计, 同时可加性使得每个自变量对因变量的解释很容易, 缺点是不容易考虑自变量之间的交互作用效应。 为了考虑交互作用效应可以使用基于树的随机森林或提升法(boosting)。

    例如,MASS包的rock数据框是石油勘探中12块岩石样本分别产生4个切片得到的测量数据, 共48个观测, 因变量是渗透率(permeability), 自变量为area, peri, shape。

    先作线性回归:

    ## Call: ## lm(formula = log(perm) ~ area + peri + shape, data = rock) ## Residuals: ## Min 1Q Median 3Q Max ## -1.8092 -0.5413 0.1734 0.6493 1.4788 ## Coefficients: ## Estimate Std. Error t value Pr(>|t|) ## (Intercept) 5.333e+00 5.487e-01 9.720 1.59e-12 *** ## area 4.850e-04 8.657e-05 5.602 1.29e-06 *** ## peri -1.527e-03 1.770e-04 -8.623 5.24e-11 *** ## shape 1.757e+00 1.756e+00 1.000 0.323 ## --- ## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 ## Residual standard error: 0.8521 on 44 degrees of freedom ## Multiple R-squared: 0.7483, Adjusted R-squared: 0.7311 ## F-statistic: 43.6 on 3 and 44 DF, p-value: 3.094e-13

    其中shape变量不显著。 可能的原因有:

  • shape与因变量没有关系;
  • 样本量不足;
  • shape与因变量之间有非线性关系,该非线性无法用线性近似。
  • 用样条平滑的 gam() 建模:

    ## Loading required package: nlme
    ## Attaching package: 'nlme'
    ## The following object is masked from 'package:dplyr':
    ##     collapse
    ## This is mgcv 1.8-40. For overview type 'help("mgcv-package")'.
    ## Family: gaussian ## Link function: identity ## Formula: ## log(perm) ~ s(area) + s(peri) + s(shape) ## Parametric coefficients: ## Estimate Std. Error t value Pr(>|t|) ## (Intercept) 5.1075 0.1222 41.81 <2e-16 *** ## --- ## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 ## Approximate significance of smooth terms: ## edf Ref.df F p-value ## s(area) 1.000 1.000 29.13 3.07e-06 *** ## s(peri) 1.000 1.000 71.30 < 2e-16 *** ## s(shape) 1.402 1.705 0.58 0.437 ## --- ## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 ## R-sq.(adj) = 0.735 Deviance explained = 75.4% ## GCV = 0.78865 Scale est. = 0.71631 n = 48

    gam() 回归结果做单个变量的曲线拟合图:

    图35.4: 渗透率gam建模 ## Model 1: log(perm) ~ area + peri + shape ## Model 2: log(perm) ~ s(area) + s(peri) + s(shape) ## Res.Df RSS Df Sum of Sq F Pr(>F) ## 1 44.000 31.949 ## 2 43.598 31.230 0.40237 0.71914 2.4951 0.125

    结果也不显著, 说明线性模型是可取的。