相关文章推荐
深沉的大白菜  ·  forEach ...·  1 年前    · 
另类的牙膏  ·  WPF ComboBox ...·  1 年前    · 
道上混的电影票  ·  macos - ...·  1 年前    · 
大鼻子的鸡蛋  ·  sql中where ...·  1 年前    · 
  • 45.2.1 evalCpp() 转换单一计算表达式
  • 45.2.2 cppFunction() 转换简单的C++函数—Fibnacci例子
  • 45.2.3 sourceCpp() 转换C++程序—正负交替迭代例子
  • 45.2.4 sourceCpp() 转换C++源文件中的程序—正负交替迭代例子
  • 45.2.5 sourceCpp() 转换C++源程序文件—卷积例子
  • 45.2.6 随机数例子
  • 45.2.7 bootstrap例子
  • 45.2.8 在Rmd文件中使用C++源程序文件
  • 46 R与C++的类型转换
  • 46.1 wrap() 把C++变量返回到R中
  • 46.2 as() 函数把R变量转换为C++类型
  • 46.3 as() wrap() 的隐含调用
  • 47 Rcpp 属性
  • 47.1 Rcpp属性介绍
  • 47.2 在C++源程序中指定要导出的C++函数
  • 47.2.1 特殊注释 //[[Rcpp::export]]
  • 47.2.2 修改导出的函数名
  • 47.2.3 可导出的函数
  • 47.3 在R中编译链接C++代码
  • 47.3.1 sourceCpp() 函数中直接包含C++源程序字符串
  • 47.3.2 cppFunction() 函数中直接包含C++函数源程序字符串
  • 47.3.3 evalCpp() 函数中直接包含C++源程序表达式字符串
  • 47.3.4 depends 指定要链接的库
  • 47.4 Rcpp属性的其它功能
  • 47.4.1 自变量有缺省值的函数
  • 47.4.2 异常传递
  • 47.4.3 允许用户中断
  • 47.4.4 把R代码写在C++源文件中
  • 47.4.5 invisible 要求函数结果不自动显示
  • 47.4.6 在C++中调用R的随机数发生器
  • 48 Rcpp提供的C++数据类型
  • 48.1 RObject类
  • 48.2 IntegerVector类
  • 48.2.1 IntegerVector示例1:返回完全数
  • 48.2.2 IntegerVector示例2:输入整数向量
  • 48.3 NumericVector类
  • 48.3.1 示例1:计算元素 \(p\) 次方的和
  • 48.3.2 示例2: clone 函数
  • 48.3.3 示例3:向量子集
  • 48.4 NumericMatrix类
  • 48.4.1 示例1:计算矩阵各列模的最大值
  • 48.4.2 示例2:把输入矩阵制作副本计算元素平方根
  • 48.4.3 示例3:访问列子集
  • 48.5 Rcpp的其它向量类
  • 48.5.1 Rcpp的LogicalVector类
  • 48.5.2 Rcpp的CharacterVector类型
  • 48.6 Rcpp提供的其它数据类型
  • 48.6.1 Named类型
  • 48.6.2 List类型
  • 48.6.3 Rcpp的DataFrame类
  • 48.6.4 Rcpp的Function类
  • 48.6.5 Rcpp的Environment类
  • 49 Rcpp糖
  • 49.1 简单示例
  • 49.2 向量化的运算符
  • 49.2.1 向量化的四则运算
  • 49.2.2 向量化的赋值运算
  • 49.2.3 向量化的二元逻辑运算
  • 49.2.4 向量化的一元运算符
  • 49.3 用Rcpp访问数学函数
  • 49.4 用Rcpp访问统计分布类函数
  • 49.5 在Rcpp中产生随机数
  • 49.6 返回单一逻辑值的函数
  • 49.7 返回糖表达式的函数
  • 49.7.1 is_na
  • 49.7.2 seq_along
  • 49.7.3 seq_len
  • 49.7.4 pmin pmax
  • 49.7.5 ifelse
  • 49.7.6 sapply lapply
  • 49.7.7 sign
  • 49.7.8 diff
  • 49.8 R与Rcpp不同语法示例
  • 49.9 用RcppArmadillo执行矩阵运算
  • 49.9.1 生成多元正态分布随机数
  • 49.9.2 快速计算线性回归
  • 50 用Rcpp帮助制作R扩展包
  • 50.1 不用扩展包共享C++代码的方法
  • 50.2 生成扩展包
  • 50.2.1 利用已有基于Rcpp属性的源程序制作扩展包
  • 50.2.2 DESCRIPTION文件
  • 50.2.3 NAMESPACE文件
  • 50.3 重新编译
  • 50.4 建立C++用的接口界面
  • 51 R编程例子
  • 51.1 R语言
  • 51.1.1 用向量作逆变换
  • 51.1.2 斐波那契数列计算
  • 51.1.3 穷举所有排列
  • 51.1.4 可重复分组方式穷举
  • 51.1.5 升降连计数
  • 51.1.6 高斯八皇后问题
  • 51.1.7 最小能量路径
  • 51.2 概率
  • 51.2.1 智者千虑必有一失
  • 51.2.2 圆桌夫妇座位问题
  • 51.3 科学计算
  • 51.3.1 城市间最短路径
  • 51.3.2 Daubechies小波函数计算
  • 51.3.3 房间加热温度变化
  • 51.4 统计计算
  • 51.4.1 线性回归实例
  • 51.4.2 核回归与核密度估计
  • 51.4.3 二维随机模拟积分
  • 51.4.4 潜周期估计
  • 51.4.5 ARMA(1,1)模型估计
  • 51.4.6 VAR模型平稳性
  • 51.4.7 贮存可靠性评估
  • 51.5 数据处理
  • 51.5.1 小题分题型分数汇总
  • 51.5.2 类别编号重排
  • 51.6 文本处理
  • 51.6.1 用R语言下载处理《红楼梦》htm文件
  • 52 使用经验
  • 52.1 文件管理
  • 52.1.1 文件备份
  • 52.1.2 工作空间
  • 52.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
  • 编著:李东风
  • 等同于 `-`(5, 2) 。因为 - 不是合法的R变量名(函数名), 所以在写成前缀形式时要用反向单撇号 ` 保护。 这样,在 lapply 等泛函中可以使用 `+` 这样的四则运算作为输入的操作。

    R语言允许在函数体内定义函数。

    有时内嵌函数仅仅是函数内用来实现模块化的一种工具, 和正常的函数作用相同,没有任何特殊作用。 例如,如下的程序在自变量x中输入一元二次方程\(a x^2 + b x + c = 0\)的三个系数,

    R函数在调用执行时, 除非用到某个形式变量的值才求出其对应实参的值。 这一点在实参是常数时无所谓, 但是如果实参是表达式就不一样了。 形参缺省值也是只有在函数运行时用到该形参的值时才求值。

    19.5.1 基本调试策略

    自己编写的某些涉及到复杂的算法的程序可能一开始并不能给出期望的结果。 这包括如下的情况:

  • 程序报错, 需要找到出错的地方加以纠正;
  • 程序正常运行, 输出了结果, 但是结果明显有错;
  • 最糟糕的是, 程序结果也看起来很正常, 但实际结果是错误的。
  • 以上这三种情况是依次越来越糟糕的。 直接运行出错的情况一般是比较容易处理的。

    为了尽可能保证程序结果正确, 在自己编写新算法时, 要运用模块化思想, 将问题分解为若干个小问题, 使得每一个小问题都比较容易验证结果是否正确, 将每一个小问题编写成一个单独的函数, 这样也可以避免一段很长的程序中许多变量混杂在一起。

    在划分模块并编写好程序后, 应该编写一系列的测试函数, 对每个函数进行测试, 保证其在各种情况下的结果是正确的。 最好采纳R的规则化的测试策略进行自动化测试, 在编写R扩展包时就推荐同时提供测试代码。

    如果程序还是有错误, 首先可以求助于搜索引擎、用户社区等。 如果这个问题是常见问题, 往往这样就可以解决问题。

    如果问题没有解决, 需要将问题最小化: 减少引起错误的程序的复杂程度, 将不必要的代码尽可能用固定的输入数据代替, 使得出错程序很短, 而且错误可重复。 有时会有不可重复的错误, 这样的错误往往很难解决, 超出了一般R用户的能力。

    在将问题程序简化并且错误可重复以后, 就要试图定位错误。 一般编程语言都有如下的一些一般性查错(debugging)方法:

  • 在程序中适当位置加上输出命令(语句), 输出怀疑有错的变量值。
  • 某些变成语言提供了跟踪以及条件跟踪命令, 可以在程序运行到某个语句或者触发了某个条件时程序中止, 但允许用户控制逐行运行程序并随时查看变量值, 称为跟踪调试(tracing)。 跟踪调试可以是命令行工具, 也可以是集成在RStudio这样的集成编程环境中的图形界面工具。
  • 在查错时, 科学研究思维照样起作用: 根据错误表现提出可能的原因假设, 制作测试输入数据验证假设, 记录相应输出并分析是否与假设的错误原因吻合。 如此反复直到找到出错原因并加以纠正。

    查错并纠正就可能会破坏了成熟的代码, 造成新的错误, 所以最好能有自动化的测试策略, 在每次修改程序后都重新测试程序是否保持输出正确。

    19.5.2 找到出错的函数

    在较复杂的程序出错时, 需要首先将错误定位到某一函数调用。如:

    R和RStudio提供了很好的跟踪运行程序的能力。 R的browser()命令可以用在程序中, 命令进入跟踪调试; RStudio的源文件显示界面可以用鼠标点击定义跟踪调试位置。

    函数定义一般都包含多行,所以一般不在命令行定义函数, 而是把函数定义以及较长的程序写在源程序文件中, 用source命令运行。 用source命令调入运行的程序与在命令行运行的效果基本相同, 这样定义的变量也是全局变量。

    考虑如下函数定义:

    browser()函数与R的if结构配合可以制作条件断点。 在调试带有循环的程序时, 发现错误发生在循环内, 如果从循环开始就跟踪运行, 会浪费许多时间。 设已知错误发生在循环变量i等于501的时候, 就可以在循环内插入:

    可以用debug(f)命令对函数f开启跟踪运行, 这时每次调用f()时都会自动进入跟踪运行状态。 用undebug(f)取消对f的这种操作。

    19.5.6 出错调试选项

    比较长的程序在调试时如果从开头就跟踪, 比较耗时。可以设置成出错后自动进入跟踪模式, 检查出错时的变量值。只要进行如下设置:

    编写程序时应尽可能提前发现不合法的输入和错误的状态。 发现错误时, 可以用stop(s)使程序运行出错停止, 其中s是一个字符型对象, 用来作为显示的出错信息。

    发现某些问题后如果不严重, 可以不停止程序运行, 但用warning(s)提交一个警告信息, 其中s是字符型的警告信息。 警告信息的显示可能比实际运行要延迟一些。

    有些警告信息实际是错误, 用options()warn参数可以设置警告级别, 如设置warn=2则所有警告当作错误处理。

    在编写自定义函数时, 可以检查自变量输入以确保输入符合要求。 函数stopifnot可以指定自变量的若干个条件, 当自变量不符合条件时自动出错停止。

    例如,函数f()需要输入两个数值型向量x, y, 需要长度相等, 可以用如下的程序

    R运行可以用stop()产生错误状态, 停止运行; 用warning()产生警告, 用message()产生提示。 基本R提供了tryCatch()函数, 用来保护可能出错的代码, 并可以指定出错时用的恢复或诊断代码。 try()函数也可以保护可能出错的代码, 使得出错时错误信息照样显示但不中断运行。

    扩展包rlang提供了一些对出错处理机制的增强功能。 详见(Hadley Wickham 2019)第8章:Conditions。

    19.6 单元测试

    现代程序设计的最重要的思想是模块化设计。 将整个任务分解为功能明确的多个小模块, 在R中用函数表示, 确保每个函数的功能正确就可以使得整个程序正确。

    在程序的反复修改调试过程中, 程序员会使用许多调试代码测试函数是否正确运行, 这些测试代码也是大的编程项目的重要内容, 所以应该将这些测试代码收集起来放到一个或若干个程序文件中, 每次程序修改后都重新运行这些测试代码, 看结果是否改变了。

    人工地去检查每个测试的结果是否正确过于繁琐了, 没有很好地发挥计算机的自动执行繁琐任务的能力。 应该将测试时调用每个函数的正确结果与测试代码放在一起, 直接判断函数的输出是否符合预期。 R的testthat扩展包提供了这样的自动化测试的功能。 这个功能主要用于R扩展包的开发测试, 但是较复杂的一般R程序也可以使用。

    testthat包用test_that()执行测试, 用expect_equal()等函数规定所测试的函数(代码)应有的行为。

    例如,自定义了如下的计算简单收益率的函数:

    Hadley Wickham and Bryan 2022)第12章:Testing。

    19.7 环境

    环境是R语言比较困难的概念, 一般用户也不需要了解环境就能很好地使用R, 也不影响自定义函数。 环境是支持变量作用域、命名空间、R6类型等功能的数据结构, 了解环境有助于更好地理解作用域等概念。

    这部分内容主要来自(Hadley Wickham 2019)相应章节。

    19.7.1 基本概念

    19.7.1.1 基本认识

    环境作为一个数据结构与有名的列表相似, 但是其中的名字必须都互不相同, 且没有次序(类似集合), 环境都有一个父环境, 修改环境内容时都不制作副本。

    rlang扩展包可以比较方便地操作R的语法内容。 可以用rlang::env()生成新的环境, 这类似于list()函数的用法,

    rlang::current_env()或基本R的environment()返回调用代码时所在的环境, 在命令行调用时,返回<environment: R_GlobalEnv>

    rlang::global_env()和基本R的globalenv()返回全局环境, 这是另一个重要环境, 也称为“工作空间”, 是在命令行运行时所处的环境。

    为了比较两个环境是否同一个, 需要用indentical(e1, e2), 而不能用==比较。

    19.7.1.3 父环境

    每个环境都有一个父环境, 这样在按句法规则查找变量的绑定对象时, 就依次查找环境本身、其父环境、父环境的父环境, 等等。内嵌函数的父环境是定义它的函数的内部环境。

    rlang的env()在生成环境时, 可以用第一个无名的参数输入父环境,

    用“<<-”在各级父环境中赋值, 最先在那一层父环境中找到变量就在那一层中赋值, 如果直到全局环境都没有找到变量, 就在全局环境中新建一个变量。 全局变量应谨慎使用, 它使得程序之间的数据输入输出变得不明晰。 在使用闭包时常常需要使用这种赋值方式保存并修改一个闭包的状态。

    类似于列表元素访问,用“环境$名字”格式或者“环境[["名字"]]”读取环境的元素, 不存在时返回NULL。如:

    rlang::env_bind_lazy()可以创造延迟的绑定, 就是类似于R函数形参缺省值的懒惰求值那样, 第一次使用其值的时候才进行绑定。 利用这种技术, 可以实现类似autoload()的功能, autoload()可以使得用到某个扩展包中指定的名字时才自动载入该扩展包, 利用延迟绑定, 可以使得数据框看起来像是已经在内存中, 但实际是用到该数据框时才中硬盘中读入。 基本R中提供了类似的delayedAssign()函数。

    rlang::env_bind_acitive()可以制造一个环境, 每次访问环境中的名字都重新求值并绑定一次。 基本R中提供了类似的makeActiveBinding()函数。

    19.7.2 逐层向上访问环境

    要逐层向上访问环境, 可以利用R的递归函数。 下面写一个逐层向上查找指定的名字所在的环境的自定义函数。 (来自Advanced R)。

    一般用户都不会用到rlang::env()生成的环境; 但是用户都会接触到使用R语言时自然产生的各种环境, 只不过许多用户可能没有认识到自己是在使用环境。

    R语言使用中涉及到环境的情景有:

  • 扩展包的环境和搜索路径;
  • 函数环境;
  • 命名空间;
  • 运行环境。
  • 19.7.3.1 扩展包环境

    每次用library()require()命令载入一个扩展包, 它定义的变量和函数就构成一个环境, 这个环境变成全局环境的父环境。 全局环境的各层父环境包括用户载入的扩展包和启动R会话时会自动载入的扩展包(如stats等), 载入最晚的一个扩展包的环境是全局环境的父环境, 载入越早的扩展包的环境距离全局环境的层次越远。 在查找某个名字时, 在当前环境没有找到时会逐层向父环境查找, 这些父环境一般就包括全局环境和全局环境上层的各个加载了的扩展包形成的环境, 这种搜索次序称为当前搜索路径, search()返回当前搜索路径,如:

    自定义函数包括形参表、函数体和定义时绑定的环境三个部分, 非内嵌的也不在扩展包中定义的函数一般都与全局环境绑定, 这样的函数的绑定环境没有什么用处, 即使不了解环境部分也能够很好地使用这样的函数。

    内嵌在函数内定义的函数称为闭包, 闭包的绑定的环境是定义它的函数的内部环境, 如果这个闭包作为定义它的函数的输出, 闭包对象带有一个私有环境, 即定义它的函数的内部环境, 可以用来保存闭包函数的状态。 在前面的讲到函数工厂时给出了闭包的例子。

    rlang::fn_env(f)可以求函数f的绑定环境。

    变量名和函数名的搜索路径中包含了已载入的扩展包的环境, 这就造成一个问题: 后载入的扩展包中的函数会遮盖住先载入的扩展包中的同名函数, 变量也是如此。 应该仅载入必要的扩展包, 尽可能用“扩展包名::函数名”的格式调用。

    这些问题是用户可控的, 还有一个本质性的问题: 假设扩展包A中的函数f1要用到扩展包B中的函数f11, 先载入了扩展包B, 然后载入了扩展包A, 这时调用A中的f1()没有问题。 现在假设随后又调入了一个扩展C, 扩展包C中也定义了一个f11函数, 现在调用A中的f1时, 会调用B中的f11还是C中的f11? 如果调用C中的f11就是会程序出错或给出错误结果。

    为了避免这样的不可控的错误发生, R语言的扩展包开发进行了严格的规定。 R的扩展包与两个环境有关, 一个就是扩展包的环境, 这实际是用户能看到的R扩展包提供的变量和函数, 在载入扩展包时会插入到搜索路径中。 另一个环境是命名空间环境, 这是扩展包私有的一个环境, 其中的变量和函数有一些对包的用户不可见, 扩展包环境中那些用户可见的变量和函数也在命名空间环境中。 R扩展包在设计时都会利用命名空间严格限定包内部调用的其它包中的函数, 不至于引起歧义。

    每个扩展包的命名空间环境都有如下的一套上层环境:

  • imports环境,其中包含所有的用到的其它扩展包的函数, 这是由扩展包的开发者确定的,所以不会错误调用错误的包;
  • imports环境的父环境是基本R环境对应的命名空间环境,但其父环境与基本R环境的父环境不同;
  • 基本R命名空间环境的父环境是全局环境。注意基本R环境的父环境是空环境。
  • 扩展包内调用其它扩展包的函数是需要开发者明确地加入到imports环境中的, 不受用户调用时载入了那些扩展包和载入次序影响。 扩展包环境(针对用户的)和扩展包命名空间环境(包开发者自用)这`两个环境不发生直接的引用联系, 可以通过函数环境逐层向上变量发生联系。

    19.7.3.4 运行环境

    函数在调用执行时自动生成一个运行环境, 其父环境为函数定义时的环境, 设f是在命令行定义的函数, 调用f()时自动生成一个f的运行环境, 相当于f的局部变量和形参的环境, 其父环境为f定义时的环境, 即全局环境。

    设函数f2在函数工厂f1中定义并被f1输出为一个闭包f2b, 则调用f2b时自动生成一个f2b的运行环境, 相当于f2b的局部变量和形参组成的环境, 此运行环境的父环境是定义时的环境, 即f2函数内部的环境。 函数执行结束则运行环境消失。

    为了能够保留下来运行环境, 一种办法是将运行环境在运行时用rlang::current_env()获取并作为函数的返回值保存到变量中, 另一种办法是像函数工厂那样输出一个闭包, 闭包的环境就是函数工厂的运行环境。

    19.7.4 调用栈

    函数在被调用时, 还涉及到调用它的环境, 可以用rlang::caller_env()获得。 调用环境与调用时的实参计算有关, 需要了解调用栈(call stack)概念, 调用栈由若干个分层的框架(frames)组成。 R运行出错时会显示一个traceback()结果, 就是调用栈的各个框架。

    环境中的变量都是引用, 或者绑定, 不需要制作环境副本, 这使得环境可以当作一种高级的数据类型使用。

    将环境作为一般数据结构使用, 可以用在如下一些方面:

  • 因为不需要制作副本, 所以可以节省内存空间。 但是直接使用环境不够友好, 可以使用R6类型的数据, R6类型是建立在环境的基础上的。
  • 在自己建立的扩展包中, 用环境保存包的状态。 这样一个包中的函数多次调用时, 可以在多次调用之间传递一些状态信息。 可以在包中用get.xxx()函数和set.xxx()函数提供包用户访问状态的接口函数。
  • 环境可以当作一个杂凑表用, 杂凑表可以在常数时间将名字对应到值。
  •