R程序包整合C++:Rcpp使用简明指南

R语言轻巧简洁,便于开发新算法。但是R函数运行速度较慢, 在进行迭代计算或者跑蒙特卡罗马尔科夫链时,常常不能满足要求。 此外, 由于用C或者C++已经有很多算法库,人们希望能直接调用这些库中的函数。Rcpp正是为了让R开发者更方便地运用C++函数生的。 本文简要介绍如何编写一个含有C++函数的R程序包。

要在Windows下创建R程序包,首先要安装:

然后,还需要安装 Rcpp (http://cran.r-project.org/web/packages/Rcpp/index.html )程序包。

并配置好启动 路径, 即将所需要的任何exe文件的路径, 拷贝到 电脑>属性>高级系统设置>高级>环境变量>系统变量>路径 中 如:

此外, 这里假设读者使用文本编辑器 Notepad++ 。

Rcpp程序包已经提供了多种C++的类模板, 可以让C++函数返回多种R对象。假设现有一个C++函数, 名叫Eccentricity,该函数用于计算某天体的轨道偏心率随时间的变化,输入参数为儒略日(日期的某种记录方法), 在正常的C++语法中, 该函数定义如下:

1
2
3
4
5
6
7
8
double Eccentricity(double JD)

{
double JD3 = JD;
double T = (JD3 - 2451545.0) / 36525.0;
double Tsquared = T*T;
return ((1 - 0.002516*T - 0.0000074*Tsquared));
}

现在我们需要有一个Rcpp形成的包骨架(package skeleton),假设名叫 skycalc。在R中运行如下命令生成基于Rcpp的R包骨架:

1
2
3
library(Rcpp)
Rcpp.package.skeleton("skycalc")
getwd()

skycalc保存在getwd()所示的文件夹下, 有 man、R、src三个文件夹以及DESCRIPTION、NAMESPACE 以及 Read-and-delete-me三个文本文件,这个三个文件没有任何扩展名。

  • man文件夹保存的是所有R函数以及R程序包的帮助文件文件,遵循是Latex格式,需要逐项填写。
  • R文件夹保存的是R函数的文件,每个R函数单独一个文件
  • src文件夹放的是cpp文件, 即c++的源文件

默认的情况下,各文件夹下有以下示例文件:

  • man文件夹中包含 rcpp_hello_world.Rd 文件,
  • R文件夹中包含RcppExports.R文件
  • src 文件夹中包含rcpp_hello_world.cpp 和 RcppExports.cpp 两个C++源文件

这些示例文件在本例中均可以删除。

我们的目标是:将Eccentricity函数,放在C++源代码中。假设函数保存在名为Eccentricity.cpp的纯文本文件, 为了能够在R中直接调用该函数, 需要将其参数数据类型以及返回值的数据类型更改为R能够识别的格式。因为R只能识别一种称为SEXP的数据类型, 所以这里做如下更改:

1
2
3
4
5
6
7
RcppExport SEXP Eccentricity(SEXP JD)
{
double JD3 = Rcpp::as<double>(JD);
double T = (JD3 - 2451545.0) / 36525.0;
double Tsquared = T*T;
return (wrap(1 - 0.002516*T - 0.0000074*Tsquared));
}

在类型前,更加上RcppExport,返回值return里面,先套用wrap函数,以便返回为R能识别的SEXP数据类型。

为在Eccentricity.cpp文件中加上引用的头文件:

1
2
3
#include <Rcpp.h>
using namespace Rcpp;
using namespace std;

此时, Eccentricity.cpp文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
///////////////cpp文件开始//////////////////////

#include <Rcpp.h>
using namespace Rcpp;
using namespace std;
RcppExport SEXP Eccentricity(SEXP JD)
{
double JD3 = Rcpp::as<double>(JD);
double T = (JD3 - 2451545.0) / 36525.0;
double Tsquared = T*T;
return (wrap(1 - 0.002516*T - 0.0000074*Tsquared));
}

///////////////cpp文件结束//////////////////////

这样 Eccentricity.cpp 文件就准备好了,将其保存在src文件夹中。

为了能够在R中调用编译后的C++函数,可使用R的.Call()函数调用dll库(cpp文件经过编译后, 在Windows中编译为dll文件)。但为了更为方便地使用这些函数,一般为编写一个与C++函数形式上类似的R函数,该R函数的名称和各参数最好能与C++函数对应。为此,我们可以写一个R函数EccentricityR, 该R函数可定义如下:

1
2
3
EccentricityR <- function(JD) {
.Call('Eccentricity', JD, PACKAGE = 'skycalc')
}

其中JD为该R函数的参数, 直接通过.Call传递给DLL中的Eccentricity。

在Notepad++中,拷贝以上R函数, 并且另存为 EccentricityR.R 文件, 放在skycalc程序包下的R文件夹下。

为了生成该EccentricityR函数的帮助文件,需要将该函数粘贴到Rconsole中, 之后运行R命令 prompt(EccentricityR),所形成的EccentricityR.Rd 文件需要进一步根据参数的意义填写和编辑。之后放到man文件夹下。

编辑和填写DESCRIPTION以及NAMESPACE两个文本文件 (参考 http://blog.sciencenet.cn/blog-255662-247614.html )。删除 Read-and-delete-me文件。这样,程序包的源文件就做好了。

下面准备编译或安装R程序包的Window批处理文件,bat文件

R包的检查的bat文件

新建一个纯文本文件,并将扩展名txt更改为.bat,将以下内容拷贝到该文件夹中:

1
2
Rcmd check skycalc
PAUSE

将该文件命名为 check skycalc package.bat, 双击该bat文件, 可以对skycalc程序包中的错误进行检查。 如果要提交程序包到CRAN,检查过程中不能有任何错误或者warning。

建立Windows版本的安装包的bat文件

(2)新建一个纯文本文件,并将扩展名txt更改为.bat

将以下内容拷贝到该文件夹中

1
2
Rcmd INSTALL --build skycalc
PAUSE

将该文件命名为 build skycalc package Windows Binary.bat, 双击该文件, 可以建立Windows系统下的R程序包。

创建源代码包的bat文件

新建一个纯文本文件,并将扩展名txt更改为.bat,将以下内容拷贝到该文件夹中

1
2
Rcmd build skycalc
pause

将该文件命名为 build skycalc package Linux Source Code.bat, 双击该文件,可以建立Linux系统下的安装包,其实是源文件。

直接安装程序包的bat文件

新建一个纯文本文件,并将扩展名txt更改为.bat,将以下内容拷贝到该文件夹中:

1
2
Rcmd INSTALL skycalc
PAUSE

将该文件命名为 install skycalc package.bat, 双击该文件, 可以安装skycalc到当前的R中。

运行批处理命令

将以上四个.bat文件, 放置到skycalc文件夹所在的文件夹(例如, skycalc所 在的文件夹为study, 则bat文件需要放置在study文件夹下)

双击文件, 即可完成程序包的编译和检查等。

注意: 如果使用的64bit的Windows,源代码 skycalc文件夹下, 每次都会生成 src-x64 和 src-i386两个文件夹, 在编译linux源代码的时候, 需要删除。

进一步阅读